diff --git a/asm/qbe/qbe.go b/asm/qbe/qbe.go index 954de33..9e3d1e0 100644 --- a/asm/qbe/qbe.go +++ b/asm/qbe/qbe.go @@ -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 +} diff --git a/build/build.go b/build/build.go index 822dad9..cafe0c7 100644 --- a/build/build.go +++ b/build/build.go @@ -10,6 +10,7 @@ import ( "strings" "robaertschi.xyz/robaertschi/tt/asm" + "robaertschi.xyz/robaertschi/tt/asm/qbe" "robaertschi.xyz/robaertschi/tt/utils" ) @@ -73,9 +74,16 @@ func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint To return id - 1 } - err := sp.buildFasm(addRootNode, addNode, emitAsmOnly, toPrint) - if err != nil { - return err + 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 +} diff --git a/build/task.go b/build/task.go index fe9fc74..83d37f6 100644 --- a/build/task.go +++ b/build/task.go @@ -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,19 +196,26 @@ 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) + 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) + } - _, err = asmOutputFile.WriteString(asmOutput) - asmOutputFile.Close() - 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]) } } diff --git a/main.go b/main.go index 388f628..99146e0 100644 --- a/main.go +++ b/main.go @@ -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)