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 {
|
||||
value, instructions := emitExpression(function.Body)
|
||||
instructions = append(instructions, &Ret{op: value})
|
||||
instructions = append(instructions, &Ret{Op: value})
|
||||
return &Function{
|
||||
Name: function.Name,
|
||||
Instructions: instructions,
|
||||
|
@ -30,11 +30,11 @@ type Instruction interface {
|
||||
}
|
||||
|
||||
type Ret struct {
|
||||
op Operand
|
||||
Op Operand
|
||||
}
|
||||
|
||||
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() {}
|
||||
|
||||
|
@ -85,7 +85,7 @@ func expectInstruction(t *testing.T, inst Instruction, actual Instruction) {
|
||||
return
|
||||
}
|
||||
|
||||
expectOperand(t, inst.op, ret.op)
|
||||
expectOperand(t, inst.Op, ret.Op)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ func TestBasicFunction(t *testing.T) {
|
||||
Name: "main",
|
||||
Instructions: []Instruction{
|
||||
&Ret{
|
||||
op: &Constant{Value: 0},
|
||||
Op: &Constant{Value: 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -9,7 +9,9 @@ import (
|
||||
"robaertschi.xyz/robaertschi/tt/token"
|
||||
)
|
||||
|
||||
type Checker struct{}
|
||||
type Checker struct {
|
||||
foundMain bool
|
||||
}
|
||||
|
||||
func New() *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...)
|
||||
}
|
||||
|
||||
@ -44,6 +51,10 @@ func (c *Checker) checkDeclaration(decl ast.Declaration) (tast.Declaration, erro
|
||||
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 nil, errors.New("unhandled declaration in type checker")
|
||||
|
Loading…
x
Reference in New Issue
Block a user