mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-16 05:53:30 +00:00
173 lines
3.5 KiB
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
|
|
}
|