wip qbe support

This commit is contained in:
Robin Bärtschi 2025-01-31 21:04:09 +01:00
parent dee1914d90
commit f1ff94c357
4 changed files with 257 additions and 20 deletions

View File

@ -1 +1,99 @@
package qbe
import (
"fmt"
"io"
"robaertschi.xyz/robaertschi/tt/ast"
"robaertschi.xyz/robaertschi/tt/ttir"
_ "embed"
)
//go:embed qbe_stub.asm
var Stub string
func emitf(w io.Writer, format string, args ...any) error {
_, err := w.Write([]byte(fmt.Sprintf(format, args...)))
return err
}
func Emit(output io.Writer, input *ttir.Program) error {
for _, f := range input.Functions {
err := emitFunction(output, f)
if err != nil {
return err
}
}
return nil
}
func emitFunction(w io.Writer, f ttir.Function) error {
emitf(w, "export function ")
if f.HasReturnValue {
if err := emitf(w, "l "); err != nil {
return err
}
}
if err := emitf(w, "$%s() {\n@start\n", f.Name); err != nil {
return err
}
for _, i := range f.Instructions {
if err := emitInstruction(w, i); err != nil {
return err
}
}
return emitf(w, "}\n")
}
func emitOperand(op ttir.Operand) string {
switch op := op.(type) {
case *ttir.Constant:
return fmt.Sprintf("%d", op.Value)
case *ttir.Var:
return "%" + op.Value
}
panic(fmt.Sprintf("invalid operand %T", op))
}
func emitInstruction(w io.Writer, i ttir.Instruction) error {
switch i := i.(type) {
case *ttir.Ret:
if op := i.Op; op != nil {
if err := emitf(w, "\tret %s\n", emitOperand(i.Op)); err != nil {
return err
}
} else {
if err := emitf(w, "\tret\n"); err != nil {
return err
}
}
case *ttir.Binary:
var inst string
switch i.Operator {
case ast.Add:
inst = "add"
case ast.Subtract:
inst = "sub"
case ast.Multiply:
inst = "mul"
case ast.Divide:
inst = "div"
case ast.GreaterThan:
inst = "csgtl"
case ast.GreaterThanEqual:
inst = "csgel"
case ast.LessThan:
inst = "csltl"
case ast.LessThanEqual:
inst = "cslel"
}
if err := emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs)); err != nil {
return err
}
}
return nil
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"robaertschi.xyz/robaertschi/tt/asm"
"robaertschi.xyz/robaertschi/tt/asm/qbe"
"robaertschi.xyz/robaertschi/tt/utils"
)
@ -73,10 +74,17 @@ func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint To
return id - 1
}
if backend == asm.Fasm {
err := sp.buildFasm(addRootNode, addNode, emitAsmOnly, toPrint)
if err != nil {
return err
}
} else if backend == asm.Qbe {
err := sp.buildQbe(addRootNode, addNode, emitAsmOnly, toPrint)
if err != nil {
return err
}
}
return runTasks(nodes, rootNodes, l)
}
@ -93,7 +101,7 @@ func (sp *SourceProgram) buildFasm(addRootNode func(task) int, addNode func(task
mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".asm"
asmFile := addRootNode(NewFuncTask("generating assembly for "+sp.InputFile, func(output io.Writer) error {
return build(output, sp.InputFile, mainAsmOutput, toPrint)
return build(output, sp.InputFile, mainAsmOutput, toPrint, asm.Fasm)
}))
if !emitAsmOnly {
@ -108,3 +116,80 @@ func (sp *SourceProgram) buildFasm(addRootNode func(task) int, addNode func(task
return nil
}
func (sp *SourceProgram) buildQbe(addRootNode func(task) int, addNode func(task, ...int) int, emitAsmOnly bool, toPrint ToPrintFlags) error {
fasmPath, err := exec.LookPath("fasm")
if err != nil {
fasmPath, err = exec.LookPath("fasm2")
if err != nil {
return fmt.Errorf("could not find fasm or fasm2, please install any those two using your systems package manager or from https://flatassembler.net")
}
}
qbePath, err := exec.LookPath("qbe")
if err != nil {
return fmt.Errorf("could not find qbe, please install using your systems package manager or from https://https://c9x.me/compile")
}
asPath, err := exec.LookPath("as")
if err != nil {
return fmt.Errorf("could not find the system `as` assembler, please install it using your systems package manager")
}
ldPath, err := exec.LookPath("ld")
if err != nil {
return fmt.Errorf("could not find the system `ld` linker, please install it using your systems package manager")
}
mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".qbe"
asmFile := addRootNode(NewFuncTask("generating assembly for "+sp.InputFile, func(output io.Writer) error {
return build(output, sp.InputFile, mainAsmOutput, toPrint, asm.Qbe)
}))
if !emitAsmOnly {
objectFileTasks := []int{}
qbeStubAsm := "qbe_stub.asm"
qbeStubO := "qbe_stub.o"
generatedAsmFile := addRootNode(NewCreateFileTask(qbeStubAsm, qbe.Stub))
id := addNode(NewProcessTask(fasmPath, qbeStubAsm, qbeStubO), generatedAsmFile)
objectFileTasks = append(objectFileTasks, id)
sp.ObjectFiles = append(sp.ObjectFiles, qbeStubO)
qbeOutput := strings.TrimSuffix(mainAsmOutput, filepath.Ext(mainAsmOutput)) + ".S"
task := NewProcessTask(qbePath, mainAsmOutput, "-o", qbeOutput)
task.WithName("running qbe on " + mainAsmOutput)
qbeTask := addNode(task, asmFile)
sp.InputAssemblies = append(sp.InputAssemblies, qbeOutput)
for _, asmFile := range sp.InputAssemblies {
outputFile := strings.TrimSuffix(asmFile, filepath.Ext(asmFile)) + ".o"
if filepath.Ext(asmFile) == ".asm" {
id := addRootNode(NewProcessTask(fasmPath, asmFile, outputFile))
objectFileTasks = append(objectFileTasks, id)
} else if filepath.Ext(asmFile) == ".S" {
id := addRootNode(NewProcessTask(asPath, asmFile, "-o", outputFile))
objectFileTasks = append(objectFileTasks, id)
} else {
panic(fmt.Sprintf("unkown asm file extension %q", filepath.Ext(asmFile)))
}
sp.ObjectFiles = append(sp.ObjectFiles, outputFile)
}
ldTask := NewProcessTask(ldPath, append([]string{"-o", sp.OutputFile}, sp.ObjectFiles...)...)
ldTaskId := addNode(ldTask, append([]int{generatedAsmFile}, objectFileTasks...)...)
for _, object := range sp.ObjectFiles {
// Cleanup object files
addNode(NewRemoveFileTask(object), ldTaskId)
}
// Cleanup
addNode(NewRemoveFileTask(mainAsmOutput), ldTaskId, qbeTask)
addNode(NewRemoveFileTask(qbeStubAsm), ldTaskId, generatedAsmFile)
addNode(NewRemoveFileTask(qbeOutput), ldTaskId)
}
return nil
}

View File

@ -9,7 +9,9 @@ import (
"slices"
"strings"
"robaertschi.xyz/robaertschi/tt/asm"
"robaertschi.xyz/robaertschi/tt/asm/amd64"
"robaertschi.xyz/robaertschi/tt/asm/qbe"
"robaertschi.xyz/robaertschi/tt/lexer"
"robaertschi.xyz/robaertschi/tt/parser"
"robaertschi.xyz/robaertschi/tt/token"
@ -84,6 +86,42 @@ func (rft *removeFileTask) Name() string { return rft.name }
func (rft *removeFileTask) WithName(name string) { rft.name = name }
type createFileTask struct {
file string
content string
name string
}
func NewCreateFileTask(file string, content string) task {
return &createFileTask{
file: file,
content: content,
name: fmt.Sprintf("writing file %q", file),
}
}
func (cft *createFileTask) Run(id int, output io.Writer, doneChan chan taskResult) {
file, err := os.Create(cft.file)
if err != nil {
doneChan <- taskResult{
Id: id,
Err: err,
}
return
}
_, err = file.WriteString(cft.content)
doneChan <- taskResult{
Id: id,
Err: err,
}
return
}
func (cft *createFileTask) Name() string { return cft.name }
func (cft *createFileTask) WithName(name string) { cft.name = name }
type funcTask struct {
f func(io.Writer) error
name string
@ -108,7 +146,7 @@ func (rft *funcTask) WithName(name string) {
rft.name = name
}
func build(outputWriter io.Writer, input string, output string, toPrint ToPrintFlags) error {
func build(outputWriter io.Writer, input string, output string, toPrint ToPrintFlags, backend asm.Backend) error {
file, err := os.Open(input)
if err != nil {
return fmt.Errorf("could not open file %q because: %v", input, err)
@ -158,20 +196,27 @@ func build(outputWriter io.Writer, input string, output string, toPrint ToPrintF
io.WriteString(outputWriter,
fmt.Sprintf("TTIR:\n%s\n%+#v\n", ir.String(), ir))
}
asm := amd64.CgProgram(ir)
asmOutput := asm.Emit()
asmOutputFile, err := os.Create(output)
if err != nil {
return fmt.Errorf("failed to create/truncate asm file %q because: %v\n", output, err)
}
defer asmOutputFile.Close()
if backend == asm.Fasm {
asm := amd64.CgProgram(ir)
asmOutput := asm.Emit()
_, err = asmOutputFile.WriteString(asmOutput)
asmOutputFile.Close()
if err != nil {
return fmt.Errorf("failed to write to file %q because: %v\n", output, err)
}
} else if backend == asm.Qbe {
err := qbe.Emit(asmOutputFile, ir)
if err != nil {
return fmt.Errorf("failed to write to file %q because: %v\n", output, err)
}
}
return nil
}
@ -284,7 +329,9 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *utils.Logger) error {
}
for id, node := range nodes {
if output[id].Len() > 0 {
if output[id] == nil {
l.Errorf("output of task %q is nil", nodes[id].task.Name())
} else if output[id].Len() > 0 {
l.Infof("task %q output: %s", node.task.Name(), output[id])
}
}

21
main.go
View File

@ -14,18 +14,20 @@ import (
)
func main() {
err := term.EnterRawMode()
if err != nil {
fmt.Printf("could not enter raw mode %v", err)
return
}
defer term.LeaveRawMode()
// err := term.EnterRawMode()
// if err != nil {
// fmt.Printf("could not enter raw mode %v", err)
// return
// }
// defer term.LeaveRawMode()
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [flags] input\nPossible flags:\n", os.Args[0])
flag.PrintDefaults()
}
qbe := flag.Bool("qbe", false, "Use the qbe backend")
var output string
flag.StringVar(&output, "o", "", "Output a executable named `file`")
flag.StringVar(&output, "output", "", "Output a executable named `file`")
@ -59,7 +61,12 @@ func main() {
logger := log.New(os.Stderr, "", log.Lshortfile)
err = build.NewSourceProgram(input, output).Build(asm.Fasm, *emitAsmOnly, build.ToPrintFlags(toPrint))
backend := asm.Fasm
if *qbe {
backend = asm.Qbe
}
err := build.NewSourceProgram(input, output).Build(backend, *emitAsmOnly, build.ToPrintFlags(toPrint))
if err != nil {
logger.Fatalln(err)
term.Exit(1)