mirror of
https://github.com/RoBaertschi/tt.git
synced 2025-04-18 23:13:29 +00:00
begin cmd
This commit is contained in:
parent
4537f0ee69
commit
c33321c259
115
asm/amd64/amd64.go
Normal file
115
asm/amd64/amd64.go
Normal 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
136
asm/amd64/amd64_test.go
Normal 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
59
asm/amd64/codegen.go
Normal 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
26
cmd/cmd.go
Normal 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
13
test.asm
Normal 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
|
@ -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,
|
||||||
|
@ -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() {}
|
||||||
|
|
||||||
|
@ -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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -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")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user