tt/asm/qbe/qbe.go

173 lines
3.5 KiB
Go

package qbe
import (
"fmt"
"io"
"strings"
"robaertschi.xyz/robaertschi/tt/ast"
"robaertschi.xyz/robaertschi/tt/ttir"
_ "embed"
)
var extraLabelId int64 = 0
func extraLabel() string {
extraLabelId += 1
return fmt.Sprintf("qbe.extra.%d", extraLabelId)
}
//go:embed qbe_stub.asm
var Stub string
func emitf(w io.Writer, format string, args ...any) error {
_, err := w.Write(fmt.Appendf(nil, format, args...))
return err
}
func emit(w io.Writer, content string) error {
_, err := w.Write(fmt.Append(nil, content))
return err
}
func Emit(output io.Writer, input *ttir.Program) error {
if input.MainFunction.HasReturnValue {
emitf(output, `
export function $_start() {
@start
%%result =l call $main()
call $syscall1(l 60, l %%result)
hlt
}
`,
)
} else {
emitf(output, `
export function $_start() {
@start
call $main()
call $syscall1(l 60, l 0)
hlt
}
`,
)
}
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
}
}
b := strings.Builder{}
for i, arg := range f.Arguments {
if i > 0 {
b.WriteString(", ")
}
b.WriteString("l %")
b.WriteString(arg)
}
if err := emitf(w, "$%s(%v) {\n@start\n", f.Name, b.String()); 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 {
return emitf(w, "\tret %s\n", emitOperand(i.Op))
} else {
return emitf(w, "\tret\n")
}
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.Equal:
inst = "ceql"
case ast.NotEqual:
inst = "cnel"
case ast.GreaterThan:
inst = "csgtl"
case ast.GreaterThanEqual:
inst = "csgel"
case ast.LessThan:
inst = "csltl"
case ast.LessThanEqual:
inst = "cslel"
}
return emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs))
case *ttir.Copy:
emitf(w, "\t%s =l copy %s\n", emitOperand(i.Dst), emitOperand(i.Src))
case ttir.Label:
return emitf(w, "@%s\n", string(i))
case ttir.Jump:
return emitf(w, "\tjmp @%s\n", string(i))
case *ttir.JumpIfNotZero:
after := extraLabel()
return emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), i.Label, after, after)
case *ttir.JumpIfZero:
after := extraLabel()
return emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), after, i.Label, after)
case *ttir.Call:
b := strings.Builder{}
b.WriteRune('\t')
if i.ReturnValue != nil {
b.WriteString(emitOperand(i.ReturnValue) + " =l ")
}
b.WriteString("call $" + i.FunctionName + "(")
for j, arg := range i.Arguments {
b.WriteString("l " + emitOperand(arg))
if j < (len(i.Arguments) - 1) {
b.WriteString(", ")
}
}
b.WriteString(")\n")
return emit(w, b.String())
default:
panic("unkown instruction")
}
return nil
}