begin cmd

This commit is contained in:
Robin Bärtschi 2025-01-21 14:24:32 +01:00
parent 4537f0ee69
commit c33321c259
9 changed files with 366 additions and 6 deletions

115
asm/amd64/amd64.go Normal file
View File

@ -0,0 +1,115 @@
package amd64
import (
"fmt"
"strings"
)
type Program struct {
Functions []Function
}
func (p *Program) Emit() string {
var builder strings.Builder
for _, function := range p.Functions {
builder.WriteString(function.Emit())
builder.WriteString("\n")
}
return builder.String()
}
type Function struct {
Name string
Instructions []Instruction
}
// This calls the main function and uses it's return value to exit
const executableAsmHeader = "format ELF64 executable\n" +
"segment readable executable\n" +
"entry _start\n" +
"_start:\n" +
" call main\n" +
" mov rdi, rax\n" +
" mov rax, 60\n" +
" syscall\n"
func (f *Function) Emit() string {
var builder strings.Builder
builder.WriteString(executableAsmHeader)
builder.WriteString(fmt.Sprintf("%s:\n", f.Name))
for _, inst := range f.Instructions {
builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString()))
}
return builder.String()
}
type Opcode string
const (
Mov Opcode = "mov"
Ret Opcode = "ret"
)
type Instruction struct {
Opcode Opcode
Lhs Operand
Rhs Operand
}
func (i *Instruction) InstructionString() string {
if i.Lhs == nil {
return fmt.Sprintf("%s", i.Opcode)
}
return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight))
}
type OperandSize int
type Operand interface {
OperandString(OperandSize) string
}
type Register int
const (
AX Register = iota
R10
One OperandSize = iota
Four
Eight
)
func (r Register) OperandString(size OperandSize) string {
switch r {
case AX:
switch size {
case One:
return "al"
case Four:
return "eax"
}
return "rax"
case R10:
switch size {
case One:
return "r10b"
case Four:
return "r10d"
}
return "r10"
}
return "INVALID_REGISTER"
}
type Imm int64
func (i Imm) OperandString(size OperandSize) string {
return fmt.Sprintf("%d", i)
}

136
asm/amd64/amd64_test.go Normal file
View File

@ -0,0 +1,136 @@
package amd64
import (
"strings"
"testing"
"robaertschi.xyz/robaertschi/tt/ttir"
)
func TestOperands(t *testing.T) {
var op Operand
op = AX
if str := op.OperandString(One); str != "al" {
t.Errorf("The register AX should be \"al\" but got %q", str)
}
op = Imm(3)
if str := op.OperandString(One); str != "3" {
t.Errorf("The immediate value 3 should be \"3\" but got %q", str)
}
}
func TestCodegen(t *testing.T) {
program := ttir.Program{
Functions: []ttir.Function{
{
Name: "main",
Instructions: []ttir.Instruction{
&ttir.Ret{Op: &ttir.Constant{Value: 0}},
},
},
},
}
expectedProgram := Program{
Functions: []Function{
{
Name: "main",
Instructions: []Instruction{
{
Opcode: Mov,
Lhs: AX,
Rhs: Imm(0),
},
{
Opcode: Ret,
},
},
},
},
}
actualProgram := CgProgram(program)
expectProgram(t, expectedProgram, actualProgram)
actual := actualProgram.Emit()
expected := executableAsmHeader + "main:\n mov rax, 0\n ret\n"
if strings.Trim(actual, " \n\t") != strings.Trim(expected, " \n\t") {
t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", expected, actual)
}
}
func expectProgram(t *testing.T, expected Program, actual Program) {
t.Helper()
if len(expected.Functions) != len(actual.Functions) {
t.Errorf("Expected %d functions but got %d", len(expected.Functions), len(actual.Functions))
return
}
for i := range expected.Functions {
expectFunction(t, expected.Functions[i], actual.Functions[i])
}
}
func expectFunction(t *testing.T, expected Function, actual Function) {
t.Helper()
if expected.Name != actual.Name {
t.Errorf("Expected function name %q but got %q", expected.Name, actual.Name)
}
if len(expected.Instructions) != len(actual.Instructions) {
t.Errorf("Expected %d instructions but got %d, expected: %v, actual: %v", len(expected.Instructions), len(actual.Instructions), expected.Instructions, actual.Instructions)
return
}
for i := range expected.Instructions {
expectInstruction(t, expected.Instructions[i], actual.Instructions[i])
}
}
func expectInstruction(t *testing.T, expected Instruction, actual Instruction) {
t.Helper()
if expected.Opcode != actual.Opcode {
t.Errorf("Expected opcode %q but got %q", expected.Opcode, actual.Opcode)
}
switch expected.Opcode {
case Mov:
expectOperand(t, expected.Lhs, actual.Lhs)
expectOperand(t, expected.Rhs, actual.Rhs)
case Ret:
// nothing to do
}
}
func expectOperand(t *testing.T, expected Operand, actual Operand) {
t.Helper()
switch expected := expected.(type) {
case Register:
actual, ok := actual.(Register)
if !ok {
t.Errorf("Expected Register but got %T", actual)
return
}
if expected != actual {
t.Errorf("Expected Register %q but got %q", expected, actual)
}
case Imm:
actual, ok := actual.(Imm)
if !ok {
t.Errorf("Expected Immediate but got %T", actual)
return
}
if expected != actual {
t.Errorf("Expected Immediate %q but got %q", expected, actual)
}
default:
t.Errorf("Unknown operand type %T", expected)
}
}

59
asm/amd64/codegen.go Normal file
View File

@ -0,0 +1,59 @@
package amd64
import (
"fmt"
"robaertschi.xyz/robaertschi/tt/ttir"
)
func toAsmOperand(op ttir.Operand) Operand {
switch op := op.(type) {
case *ttir.Constant:
return Imm(op.Value)
default:
panic(fmt.Sprintf("unkown operand %T", op))
}
}
func CgProgram(prog ttir.Program) Program {
funcs := make([]Function, 0)
for _, f := range prog.Functions {
funcs = append(funcs, cgFunction(f))
}
return Program{
Functions: funcs,
}
}
func cgFunction(f ttir.Function) Function {
newInstructions := []Instruction{}
for _, inst := range f.Instructions {
newInstructions = append(newInstructions, cgInstruction(inst)...)
}
return Function{
Name: f.Name,
Instructions: newInstructions,
}
}
func cgInstruction(i ttir.Instruction) []Instruction {
switch i := i.(type) {
case *ttir.Ret:
return []Instruction{
{
Opcode: Mov,
Lhs: AX,
Rhs: toAsmOperand(i.Op),
},
{
Opcode: Ret,
},
}
}
return []Instruction{}
}

26
cmd/cmd.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"flag"
"fmt"
"os"
)
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [flags] input\nPossible flags:\n", os.Args[0])
flag.PrintDefaults()
}
var output string
flag.StringVar(&output, "o", "", "Output a executable named `file`")
flag.StringVar(&output, "output", "", "Output a executable named `file`")
flag.Parse()
input := flag.Arg(0)
if input == "" {
flag.Usage()
os.Exit(1)
}
}

13
test.asm Normal file
View File

@ -0,0 +1,13 @@
format ELF64 executable
segment readable executable
main:
mov rax, 0
ret
entry _start
_start:
call main
mov rdi, rax
mov rax, 60
syscall

View File

@ -18,7 +18,7 @@ func EmitProgram(program *tast.Program) *Program {
func emitFunction(function *tast.FunctionDeclaration) *Function { func emitFunction(function *tast.FunctionDeclaration) *Function {
value, instructions := emitExpression(function.Body) value, instructions := emitExpression(function.Body)
instructions = append(instructions, &Ret{op: value}) instructions = append(instructions, &Ret{Op: value})
return &Function{ return &Function{
Name: function.Name, Name: function.Name,
Instructions: instructions, Instructions: instructions,

View File

@ -30,11 +30,11 @@ type Instruction interface {
} }
type Ret struct { type Ret struct {
op Operand Op Operand
} }
func (r *Ret) String() string { func (r *Ret) String() string {
return fmt.Sprintf("ret %s\n", r.op) return fmt.Sprintf("ret %s\n", r.Op)
} }
func (r *Ret) instruction() {} func (r *Ret) instruction() {}

View File

@ -85,7 +85,7 @@ func expectInstruction(t *testing.T, inst Instruction, actual Instruction) {
return return
} }
expectOperand(t, inst.op, ret.op) expectOperand(t, inst.Op, ret.Op)
} }
} }
@ -116,7 +116,7 @@ func TestBasicFunction(t *testing.T) {
Name: "main", Name: "main",
Instructions: []Instruction{ Instructions: []Instruction{
&Ret{ &Ret{
op: &Constant{Value: 0}, Op: &Constant{Value: 0},
}, },
}, },
}, },

View File

@ -9,7 +9,9 @@ import (
"robaertschi.xyz/robaertschi/tt/token" "robaertschi.xyz/robaertschi/tt/token"
) )
type Checker struct{} type Checker struct {
foundMain bool
}
func New() *Checker { func New() *Checker {
return &Checker{} return &Checker{}
@ -32,6 +34,11 @@ func (c *Checker) CheckProgram(program *ast.Program) (*tast.Program, error) {
} }
} }
if !c.foundMain {
// TODO: Add support for libraries
errs = append(errs, errors.New("no function called 'main' found"))
}
return &tast.Program{Declarations: decls}, errors.Join(errs...) return &tast.Program{Declarations: decls}, errors.Join(errs...)
} }
@ -44,6 +51,10 @@ func (c *Checker) checkDeclaration(decl ast.Declaration) (tast.Declaration, erro
return nil, err return nil, err
} }
if decl.Name == "main" {
c.foundMain = true
}
return &tast.FunctionDeclaration{Token: decl.Token, Body: body, ReturnType: body.Type(), Name: decl.Name}, nil return &tast.FunctionDeclaration{Token: decl.Token, Body: body, ReturnType: body.Type(), Name: decl.Name}, nil
} }
return nil, errors.New("unhandled declaration in type checker") return nil, errors.New("unhandled declaration in type checker")