mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-16 05:53:30 +00:00
wip qbe support
This commit is contained in:
parent
dee1914d90
commit
f1ff94c357
@ -1 +1,99 @@
|
|||||||
package qbe
|
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
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"robaertschi.xyz/robaertschi/tt/asm"
|
"robaertschi.xyz/robaertschi/tt/asm"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/asm/qbe"
|
||||||
"robaertschi.xyz/robaertschi/tt/utils"
|
"robaertschi.xyz/robaertschi/tt/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,9 +74,16 @@ func (sp *SourceProgram) Build(backend asm.Backend, emitAsmOnly bool, toPrint To
|
|||||||
return id - 1
|
return id - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
err := sp.buildFasm(addRootNode, addNode, emitAsmOnly, toPrint)
|
if backend == asm.Fasm {
|
||||||
if err != nil {
|
err := sp.buildFasm(addRootNode, addNode, emitAsmOnly, toPrint)
|
||||||
return err
|
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)
|
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"
|
mainAsmOutput := strings.TrimSuffix(sp.InputFile, filepath.Ext(sp.InputFile)) + ".asm"
|
||||||
|
|
||||||
asmFile := addRootNode(NewFuncTask("generating assembly for "+sp.InputFile, func(output io.Writer) error {
|
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 {
|
if !emitAsmOnly {
|
||||||
@ -108,3 +116,80 @@ func (sp *SourceProgram) buildFasm(addRootNode func(task) int, addNode func(task
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -9,7 +9,9 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"robaertschi.xyz/robaertschi/tt/asm"
|
||||||
"robaertschi.xyz/robaertschi/tt/asm/amd64"
|
"robaertschi.xyz/robaertschi/tt/asm/amd64"
|
||||||
|
"robaertschi.xyz/robaertschi/tt/asm/qbe"
|
||||||
"robaertschi.xyz/robaertschi/tt/lexer"
|
"robaertschi.xyz/robaertschi/tt/lexer"
|
||||||
"robaertschi.xyz/robaertschi/tt/parser"
|
"robaertschi.xyz/robaertschi/tt/parser"
|
||||||
"robaertschi.xyz/robaertschi/tt/token"
|
"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 }
|
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 {
|
type funcTask struct {
|
||||||
f func(io.Writer) error
|
f func(io.Writer) error
|
||||||
name string
|
name string
|
||||||
@ -108,7 +146,7 @@ func (rft *funcTask) WithName(name string) {
|
|||||||
rft.name = name
|
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)
|
file, err := os.Open(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not open file %q because: %v", input, err)
|
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,
|
io.WriteString(outputWriter,
|
||||||
fmt.Sprintf("TTIR:\n%s\n%+#v\n", ir.String(), ir))
|
fmt.Sprintf("TTIR:\n%s\n%+#v\n", ir.String(), ir))
|
||||||
}
|
}
|
||||||
asm := amd64.CgProgram(ir)
|
|
||||||
|
|
||||||
asmOutput := asm.Emit()
|
|
||||||
|
|
||||||
asmOutputFile, err := os.Create(output)
|
asmOutputFile, err := os.Create(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create/truncate asm file %q because: %v\n", output, err)
|
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
|
return nil
|
||||||
@ -284,7 +329,9 @@ func runTasks(nodes map[int]*node, rootNodes []int, l *utils.Logger) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for id, node := range nodes {
|
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])
|
l.Infof("task %q output: %s", node.task.Name(), output[id])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
21
main.go
21
main.go
@ -14,18 +14,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := term.EnterRawMode()
|
// err := term.EnterRawMode()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
fmt.Printf("could not enter raw mode %v", err)
|
// fmt.Printf("could not enter raw mode %v", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
defer term.LeaveRawMode()
|
// defer term.LeaveRawMode()
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [flags] input\nPossible flags:\n", os.Args[0])
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [flags] input\nPossible flags:\n", os.Args[0])
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qbe := flag.Bool("qbe", false, "Use the qbe backend")
|
||||||
|
|
||||||
var output string
|
var output string
|
||||||
flag.StringVar(&output, "o", "", "Output a executable named `file`")
|
flag.StringVar(&output, "o", "", "Output a executable named `file`")
|
||||||
flag.StringVar(&output, "output", "", "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)
|
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 {
|
if err != nil {
|
||||||
logger.Fatalln(err)
|
logger.Fatalln(err)
|
||||||
term.Exit(1)
|
term.Exit(1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user