finished binary expression

This commit is contained in:
Robin 2025-01-22 21:16:14 +01:00
parent 28d161ed62
commit 8bb0bb9a4e
5 changed files with 276 additions and 31 deletions

View File

@ -9,22 +9,6 @@ type Program struct {
Functions []Function 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 // This calls the main function and uses it's return value to exit
const executableAsmHeader = "format ELF64 executable\n" + const executableAsmHeader = "format ELF64 executable\n" +
"segment readable executable\n" + "segment readable executable\n" +
@ -35,11 +19,28 @@ const executableAsmHeader = "format ELF64 executable\n" +
" mov rax, 60\n" + " mov rax, 60\n" +
" syscall\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 { func (f *Function) Emit() string {
var builder strings.Builder var builder strings.Builder
builder.WriteString(executableAsmHeader) builder.WriteString(fmt.Sprintf("%s:\n push rbp\n mov rbp, rsp\n add rsp, %d\n", f.Name, f.StackOffset))
builder.WriteString(fmt.Sprintf("%s:\n", f.Name))
for _, inst := range f.Instructions { for _, inst := range f.Instructions {
builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString())) builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString()))
@ -51,25 +52,46 @@ func (f *Function) Emit() string {
type Opcode string type Opcode string
const ( const (
Mov Opcode = "mov"
Ret Opcode = "ret" // Two operands
Add Opcode = "add" Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax
Sub Opcode = "sub" Add Opcode = "add"
Imull Opcode = "imul" Sub Opcode = "sub"
Idiv Opcode = "idiv" Imul Opcode = "imul"
// One operand
Idiv Opcode = "idiv"
// No operands
Ret Opcode = "ret"
Cdq Opcode = "cdq"
) )
type Instruction struct { type Instruction struct {
Opcode Opcode Opcode Opcode
Lhs Operand // Dst
Rhs Operand Lhs Operand
// Src
Rhs Operand
} }
func (i *Instruction) InstructionString() string { 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 { if i.Lhs == nil {
return fmt.Sprintf("%s", i.Opcode) 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)) return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight))
} }
@ -84,6 +106,7 @@ type Register int
const ( const (
AX Register = iota AX Register = iota
R10 R10
R11
One OperandSize = iota One OperandSize = iota
Four Four
@ -108,6 +131,14 @@ func (r Register) OperandString(size OperandSize) string {
return "r10d" return "r10d"
} }
return "r10" return "r10"
case R11:
switch size {
case One:
return "r11b"
case Four:
return "r11d"
}
return "r11"
} }
return "INVALID_REGISTER" return "INVALID_REGISTER"
} }
@ -121,5 +152,22 @@ func (i Imm) OperandString(size OperandSize) string {
type Stack int64 type Stack int64
func (s Stack) OperandString(size OperandSize) string { 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")
} }

View File

@ -3,6 +3,7 @@ package amd64
import ( import (
"fmt" "fmt"
"robaertschi.xyz/robaertschi/tt/ast"
"robaertschi.xyz/robaertschi/tt/ttir" "robaertschi.xyz/robaertschi/tt/ttir"
) )
@ -10,6 +11,8 @@ func toAsmOperand(op ttir.Operand) Operand {
switch op := op.(type) { switch op := op.(type) {
case *ttir.Constant: case *ttir.Constant:
return Imm(op.Value) return Imm(op.Value)
case *ttir.Var:
return Pseudo(op.Value)
default: default:
panic(fmt.Sprintf("unkown operand %T", op)) panic(fmt.Sprintf("unkown operand %T", op))
} }
@ -22,9 +25,14 @@ func CgProgram(prog *ttir.Program) Program {
funcs = append(funcs, cgFunction(f)) funcs = append(funcs, cgFunction(f))
} }
return Program{ newProgram := Program{
Functions: funcs, Functions: funcs,
} }
newProgram = replacePseudo(newProgram)
newProgram = instructionFixup(newProgram)
return newProgram
} }
func cgFunction(f ttir.Function) Function { func cgFunction(f ttir.Function) Function {
@ -53,7 +61,156 @@ func cgInstruction(i ttir.Instruction) []Instruction {
Opcode: Ret, Opcode: Ret,
}, },
} }
case *ttir.Binary:
return cgBinary(i)
} }
return []Instruction{} 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}
}

View File

@ -17,10 +17,19 @@ import (
"robaertschi.xyz/robaertschi/tt/typechecker" "robaertschi.xyz/robaertschi/tt/typechecker"
) )
type ToPrintFlags int
const (
PrintAst ToPrintFlags = 1 << iota
PrintTAst
PrintIr
)
type Arguments struct { type Arguments struct {
Output string Output string
Input string Input string
OnlyEmitAsm bool OnlyEmitAsm bool
ToPrint ToPrintFlags
} }
// Prefix writer writes a prefix before each new line from another io.Writer // Prefix writer writes a prefix before each new line from another io.Writer
@ -109,19 +118,27 @@ func Compile(args Arguments) {
}) })
program := p.ParseProgram() program := p.ParseProgram()
if p.Errors() > 0 { if p.Errors() > 0 {
fmt.Printf("Parser encountered 1 or more errors, quiting...\n") fmt.Printf("Parser encountered 1 or more errors, quiting...\n")
os.Exit(1) os.Exit(1)
} }
if (args.ToPrint & PrintAst) != 0 {
fmt.Printf("AST:\n%s\n", program.String())
}
tprogram, err := typechecker.New().CheckProgram(program) tprogram, err := typechecker.New().CheckProgram(program)
if err != nil { if err != nil {
fmt.Printf("Typechecker failed with %e\n", err) fmt.Printf("Typechecker failed with %e\n", err)
os.Exit(1) os.Exit(1)
} }
if (args.ToPrint & PrintTAst) != 0 {
fmt.Printf("TAST:\n%s\n", tprogram.String())
}
ir := ttir.EmitProgram(tprogram) ir := ttir.EmitProgram(tprogram)
if (args.ToPrint & PrintIr) != 0 {
fmt.Printf("TTIR:\n%s\n", ir.String())
}
asm := amd64.CgProgram(ir) asm := amd64.CgProgram(ir)
asmOutput := asm.Emit() asmOutput := asm.Emit()

17
main.go
View File

@ -20,6 +20,10 @@ func main() {
flag.StringVar(&output, "o", "", "Output a executable named `file`") flag.StringVar(&output, "o", "", "Output a executable named `file`")
flag.StringVar(&output, "output", "", "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") 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() flag.Parse()
input := flag.Arg(0) input := flag.Arg(0)
@ -32,5 +36,16 @@ func main() {
output = strings.TrimSuffix(input, filepath.Ext(input)) 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})
} }

View File

@ -11,6 +11,14 @@ type Program struct {
Functions []Function 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 { type Function struct {
Name string Name string
Instructions []Instruction Instructions []Instruction
@ -48,7 +56,7 @@ type Binary struct {
} }
func (b *Binary) String() string { 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() {} func (b *Binary) instruction() {}