diff --git a/asm/amd64/amd64.go b/asm/amd64/amd64.go index 785f784..89853dc 100644 --- a/asm/amd64/amd64.go +++ b/asm/amd64/amd64.go @@ -9,22 +9,6 @@ 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" + @@ -35,11 +19,28 @@ const executableAsmHeader = "format ELF64 executable\n" + " mov rax, 60\n" + " syscall\n" +func (p *Program) Emit() string { + var builder strings.Builder + builder.WriteString(executableAsmHeader) + + for _, function := range p.Functions { + builder.WriteString(function.Emit()) + builder.WriteString("\n") + } + + return builder.String() +} + +type Function struct { + StackOffset int64 + Name string + Instructions []Instruction +} + func (f *Function) Emit() string { var builder strings.Builder - builder.WriteString(executableAsmHeader) - builder.WriteString(fmt.Sprintf("%s:\n", f.Name)) + builder.WriteString(fmt.Sprintf("%s:\n push rbp\n mov rbp, rsp\n add rsp, %d\n", f.Name, f.StackOffset)) for _, inst := range f.Instructions { builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString())) @@ -51,25 +52,46 @@ func (f *Function) Emit() string { type Opcode string const ( - Mov Opcode = "mov" - Ret Opcode = "ret" - Add Opcode = "add" - Sub Opcode = "sub" - Imull Opcode = "imul" - Idiv Opcode = "idiv" + + // Two operands + Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax + Add Opcode = "add" + Sub Opcode = "sub" + Imul Opcode = "imul" + + // One operand + Idiv Opcode = "idiv" + + // No operands + Ret Opcode = "ret" + Cdq Opcode = "cdq" ) type Instruction struct { Opcode Opcode - Lhs Operand - Rhs Operand + // Dst + Lhs Operand + // Src + Rhs Operand } func (i *Instruction) InstructionString() string { + + if i.Opcode == Ret { + return fmt.Sprintf("mov rsp, rbp\n pop rbp\n ret\n") + } + + // No operands if i.Lhs == nil { return fmt.Sprintf("%s", i.Opcode) } + // One operand + if i.Rhs == nil { + return fmt.Sprintf("%s %s", i.Opcode, i.Lhs.OperandString(Eight)) + } + + // Two operands return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight)) } @@ -84,6 +106,7 @@ type Register int const ( AX Register = iota R10 + R11 One OperandSize = iota Four @@ -108,6 +131,14 @@ func (r Register) OperandString(size OperandSize) string { return "r10d" } return "r10" + case R11: + switch size { + case One: + return "r11b" + case Four: + return "r11d" + } + return "r11" } return "INVALID_REGISTER" } @@ -121,5 +152,22 @@ func (i Imm) OperandString(size OperandSize) string { type Stack int64 func (s Stack) OperandString(size OperandSize) string { - return fmt.Sprintf("rbp(%d)", s) + + var sizeString string + switch size { + case One: + sizeString = "byte" + case Four: + sizeString = "dword" + case Eight: + sizeString = "qword" + } + + return fmt.Sprintf("%s [rsp %+d]", sizeString, s) +} + +type Pseudo string + +func (s Pseudo) OperandString(size OperandSize) string { + panic("Pseudo Operands cannot be represented in asm") } diff --git a/asm/amd64/codegen.go b/asm/amd64/codegen.go index d7c8054..bde3a6f 100644 --- a/asm/amd64/codegen.go +++ b/asm/amd64/codegen.go @@ -3,6 +3,7 @@ package amd64 import ( "fmt" + "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/ttir" ) @@ -10,6 +11,8 @@ func toAsmOperand(op ttir.Operand) Operand { switch op := op.(type) { case *ttir.Constant: return Imm(op.Value) + case *ttir.Var: + return Pseudo(op.Value) default: panic(fmt.Sprintf("unkown operand %T", op)) } @@ -22,9 +25,14 @@ func CgProgram(prog *ttir.Program) Program { funcs = append(funcs, cgFunction(f)) } - return Program{ + newProgram := Program{ Functions: funcs, } + + newProgram = replacePseudo(newProgram) + newProgram = instructionFixup(newProgram) + + return newProgram } func cgFunction(f ttir.Function) Function { @@ -53,7 +61,156 @@ func cgInstruction(i ttir.Instruction) []Instruction { Opcode: Ret, }, } + case *ttir.Binary: + return cgBinary(i) } return []Instruction{} } + +func cgBinary(b *ttir.Binary) []Instruction { + switch b.Operator { + case ast.Add, ast.Subtract, ast.Multiply: + var opcode Opcode + switch b.Operator { + case ast.Add: + opcode = Add + case ast.Subtract: + opcode = Sub + case ast.Multiply: + opcode = Imul + } + + return []Instruction{ + {Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Lhs)}, + {Opcode: opcode, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Rhs)}, + } + case ast.Divide: + return []Instruction{ + {Opcode: Mov, Lhs: Register(AX), Rhs: toAsmOperand(b.Lhs)}, + {Opcode: Cdq}, + {Opcode: Idiv, Lhs: toAsmOperand(b.Rhs)}, + {Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: Register(AX)}, + } + } + + panic(fmt.Sprintf("unknown binary operator, %v", b)) +} + +// Second pass, replace all the pseudos with stack addresses +type replacePseudoPass struct { + identToOffset map[string]int64 + currentOffset int64 +} + +func replacePseudo(prog Program) Program { + newFunctions := make([]Function, 0) + + for _, f := range prog.Functions { + newFunctions = append(newFunctions, rpFunction(f)) + } + + return Program{Functions: newFunctions} +} + +func rpFunction(f Function) Function { + newInstructions := make([]Instruction, 0) + + r := &replacePseudoPass{ + identToOffset: make(map[string]int64), + } + for _, i := range f.Instructions { + newInstructions = append(newInstructions, rpInstruction(i, r)) + } + + return Function{Instructions: newInstructions, Name: f.Name, StackOffset: r.currentOffset} +} + +func rpInstruction(i Instruction, r *replacePseudoPass) Instruction { + + newInstruction := Instruction{Opcode: i.Opcode} + if i.Lhs != nil { + newInstruction.Lhs = pseudoToStack(i.Lhs, r) + } + if i.Rhs != nil { + newInstruction.Rhs = pseudoToStack(i.Rhs, r) + } + + return newInstruction +} + +func pseudoToStack(op Operand, r *replacePseudoPass) Operand { + if pseudo, ok := op.(Pseudo); ok { + if offset, ok := r.identToOffset[string(pseudo)]; ok { + return Stack(offset) + } else { + r.currentOffset -= 4 + r.identToOffset[string(pseudo)] = r.currentOffset + return Stack(r.currentOffset) + } + } + return op +} + +// Third pass, fixup invalid instructions + +func instructionFixup(prog Program) Program { + newFuncs := make([]Function, 0) + + for _, f := range prog.Functions { + newFuncs = append(newFuncs, fixupFunction(f)) + } + + return Program{Functions: newFuncs} +} + +func fixupFunction(f Function) Function { + // The function will at minimum require the same amount of instructions, but never less + newInstructions := make([]Instruction, 0) + + for _, i := range f.Instructions { + newInstructions = append(newInstructions, fixupInstruction(i)...) + } + + return Function{Name: f.Name, Instructions: newInstructions, StackOffset: f.StackOffset} +} + +func fixupInstruction(i Instruction) []Instruction { + + switch i.Opcode { + case Mov: + if lhs, ok := i.Lhs.(Stack); ok { + if rhs, ok := i.Rhs.(Stack); ok { + return []Instruction{ + {Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, + {Opcode: Mov, Lhs: lhs, Rhs: Register(R10)}, + } + } + } + case Imul: + if lhs, ok := i.Lhs.(Stack); ok { + return []Instruction{ + {Opcode: Mov, Lhs: Register(R11), Rhs: lhs}, + {Opcode: Imul, Lhs: Register(R11), Rhs: i.Rhs}, + {Opcode: Mov, Lhs: lhs, Rhs: Register(R11)}, + } + } + fallthrough + case Add, Sub, Idiv /* Imul (fallthrough) */ : + if lhs, ok := i.Lhs.(Stack); ok { + if rhs, ok := i.Rhs.(Stack); ok { + return []Instruction{ + {Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, + {Opcode: i.Opcode, Lhs: lhs, Rhs: Register(R10)}, + } + } + } else if lhs, ok := i.Lhs.(Imm); ok && i.Opcode == Idiv { + return []Instruction{ + {Opcode: Mov, Lhs: Register(R10), Rhs: lhs}, + {Opcode: Idiv, Lhs: Register(R10)}, + } + } + } + + return []Instruction{i} +} diff --git a/cmd/cmd.go b/cmd/cmd.go index 8b3dfc8..8563baf 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -17,10 +17,19 @@ import ( "robaertschi.xyz/robaertschi/tt/typechecker" ) +type ToPrintFlags int + +const ( + PrintAst ToPrintFlags = 1 << iota + PrintTAst + PrintIr +) + type Arguments struct { Output string Input string OnlyEmitAsm bool + ToPrint ToPrintFlags } // Prefix writer writes a prefix before each new line from another io.Writer @@ -109,19 +118,27 @@ func Compile(args Arguments) { }) program := p.ParseProgram() - if p.Errors() > 0 { fmt.Printf("Parser encountered 1 or more errors, quiting...\n") os.Exit(1) } + if (args.ToPrint & PrintAst) != 0 { + fmt.Printf("AST:\n%s\n", program.String()) + } tprogram, err := typechecker.New().CheckProgram(program) if err != nil { fmt.Printf("Typechecker failed with %e\n", err) os.Exit(1) } + if (args.ToPrint & PrintTAst) != 0 { + fmt.Printf("TAST:\n%s\n", tprogram.String()) + } ir := ttir.EmitProgram(tprogram) + if (args.ToPrint & PrintIr) != 0 { + fmt.Printf("TTIR:\n%s\n", ir.String()) + } asm := amd64.CgProgram(ir) asmOutput := asm.Emit() diff --git a/main.go b/main.go index 4cb95c7..674f834 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,10 @@ func main() { flag.StringVar(&output, "o", "", "Output a executable named `file`") flag.StringVar(&output, "output", "", "Output a executable named `file`") onlyEmitAsm := flag.Bool("S", false, "Only emit the asembly file and exit") + + printAst := flag.Bool("ast", false, "Print the AST out to stdout") + printTAst := flag.Bool("tast", false, "Print the typed AST out to stdout") + printIr := flag.Bool("ttir", false, "Print the TTIR out to stdout") flag.Parse() input := flag.Arg(0) @@ -32,5 +36,16 @@ func main() { output = strings.TrimSuffix(input, filepath.Ext(input)) } - cmd.Compile(cmd.Arguments{Output: output, Input: input, OnlyEmitAsm: *onlyEmitAsm}) + var toPrint cmd.ToPrintFlags + if *printAst { + toPrint |= cmd.PrintAst + } + if *printTAst { + toPrint |= cmd.PrintTAst + } + if *printIr { + toPrint |= cmd.PrintIr + } + + cmd.Compile(cmd.Arguments{Output: output, Input: input, OnlyEmitAsm: *onlyEmitAsm, ToPrint: toPrint}) } diff --git a/ttir/ttir.go b/ttir/ttir.go index d4b930c..61caf9a 100644 --- a/ttir/ttir.go +++ b/ttir/ttir.go @@ -11,6 +11,14 @@ type Program struct { Functions []Function } +func (p *Program) String() string { + var builder strings.Builder + for _, f := range p.Functions { + builder.WriteString(f.String()) + } + return builder.String() +} + type Function struct { Name string Instructions []Instruction @@ -48,7 +56,7 @@ type Binary struct { } func (b *Binary) String() string { - return fmt.Sprintf("%s = %s %s, %s", b.Dst, b.Operator, b.Lhs, b.Rhs) + return fmt.Sprintf("%s = %s %s, %s\n", b.Dst, b.Operator, b.Lhs, b.Rhs) } func (b *Binary) instruction() {}