Improved code generation

This commit is contained in:
Eduard Urbach 2024-07-09 17:00:04 +02:00
parent 1204591cdc
commit 4386392844
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
23 changed files with 201 additions and 95 deletions

View File

@ -68,16 +68,6 @@ func (a *Assembler) Call(name string) {
}) })
} }
// Jump jumps to a position that is identified by a label.
func (a *Assembler) Jump(name string) {
a.Instructions = append(a.Instructions, Instruction{
Mnemonic: JUMP,
Data: &Label{
Name: name,
},
})
}
// Return returns back to the caller. // Return returns back to the caller.
func (a *Assembler) Return() { func (a *Assembler) Return() {
if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN { if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN {

View File

@ -4,10 +4,6 @@ import "git.akyoto.dev/cli/q/src/build/cpu"
// unnecessary returns true if the register/register operation can be skipped. // unnecessary returns true if the register/register operation can be skipped.
func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool { func (a *Assembler) unnecessary(mnemonic Mnemonic, left cpu.Register, right cpu.Register) bool {
if mnemonic == MOVE && left == right {
return true
}
if len(a.Instructions) == 0 { if len(a.Instructions) == 0 {
return false return false
} }

View File

@ -41,7 +41,6 @@ func (f *Function) Compare(comparison *expression.Expression) error {
return err return err
} }
f.cpu.Use(tmp)
defer f.cpu.Free(tmp) defer f.cpu.Free(tmp)
return f.Execute(comparison.Token, tmp, right) return f.Execute(comparison.Token, tmp, right)
} }

View File

@ -37,22 +37,18 @@ func (f *Function) CompileCall(root *expression.Expression) error {
f.SaveRegister(f.cpu.Output[0]) f.SaveRegister(f.cpu.Output[0])
for _, register := range registers {
f.SaveRegister(register)
}
// Push // Push
for _, register := range f.cpu.General { for _, register := range f.cpu.General {
if f.cpu.IsUsed(register) { if f.cpu.IsUsed(register) {
f.assembler.Register(asm.PUSH, register) f.Register(asm.PUSH, register)
} }
} }
// Call // Call
if isSyscall { if isSyscall {
f.assembler.Syscall() f.Syscall()
} else { } else {
f.assembler.Call(funcName) f.Call(funcName)
} }
// Pop // Pop
@ -60,7 +56,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
register := f.cpu.General[i] register := f.cpu.General[i]
if f.cpu.IsUsed(register) { if f.cpu.IsUsed(register) {
f.assembler.Register(asm.POP, register) f.Register(asm.POP, register)
} }
} }

View File

@ -25,7 +25,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
f.JumpIfTrue(left.Token.Text(), successLabel) f.JumpIfTrue(left.Token.Text(), successLabel)
// Right // Right
f.assembler.Label(asm.LABEL, leftFailLabel) f.AddLabel(leftFailLabel)
right := condition.Children[1] right := condition.Children[1]
err = f.CompileCondition(right, successLabel, failLabel) err = f.CompileCondition(right, successLabel, failLabel)
@ -52,7 +52,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
f.JumpIfFalse(left.Token.Text(), failLabel) f.JumpIfFalse(left.Token.Text(), failLabel)
// Right // Right
f.assembler.Label(asm.LABEL, leftSuccessLabel) f.AddLabel(leftSuccessLabel)
right := condition.Children[1] right := condition.Children[1]
err = f.CompileCondition(right, successLabel, failLabel) err = f.CompileCondition(right, successLabel, failLabel)
@ -79,17 +79,17 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
func (f *Function) JumpIfFalse(operator string, label string) { func (f *Function) JumpIfFalse(operator string, label string) {
switch operator { switch operator {
case "==": case "==":
f.assembler.Label(asm.JNE, label) f.Jump(asm.JNE, label)
case "!=": case "!=":
f.assembler.Label(asm.JE, label) f.Jump(asm.JE, label)
case ">": case ">":
f.assembler.Label(asm.JLE, label) f.Jump(asm.JLE, label)
case "<": case "<":
f.assembler.Label(asm.JGE, label) f.Jump(asm.JGE, label)
case ">=": case ">=":
f.assembler.Label(asm.JL, label) f.Jump(asm.JL, label)
case "<=": case "<=":
f.assembler.Label(asm.JG, label) f.Jump(asm.JG, label)
} }
} }
@ -97,16 +97,16 @@ func (f *Function) JumpIfFalse(operator string, label string) {
func (f *Function) JumpIfTrue(operator string, label string) { func (f *Function) JumpIfTrue(operator string, label string) {
switch operator { switch operator {
case "==": case "==":
f.assembler.Label(asm.JE, label) f.Jump(asm.JE, label)
case "!=": case "!=":
f.assembler.Label(asm.JNE, label) f.Jump(asm.JNE, label)
case ">": case ">":
f.assembler.Label(asm.JG, label) f.Jump(asm.JG, label)
case "<": case "<":
f.assembler.Label(asm.JL, label) f.Jump(asm.JL, label)
case ">=": case ">=":
f.assembler.Label(asm.JGE, label) f.Jump(asm.JGE, label)
case "<=": case "<=":
f.assembler.Label(asm.JLE, label) f.Jump(asm.JLE, label)
} }
} }

View File

@ -43,7 +43,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
func (f *Function) AddVariable(variable *Variable) { func (f *Function) AddVariable(variable *Variable) {
if config.Comments { if config.Comments {
f.assembler.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)) f.Comment(fmt.Sprintf("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive))
} }
f.variables[variable.Name] = variable f.variables[variable.Name] = variable
@ -60,7 +60,7 @@ func (f *Function) useVariable(variable *Variable) {
if variable.Alive == 0 { if variable.Alive == 0 {
if config.Comments { if config.Comments {
f.assembler.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register)) f.Comment(fmt.Sprintf("%s died (%s)", variable.Name, variable.Register))
} }
f.cpu.Free(variable.Register) f.cpu.Free(variable.Register)

View File

@ -3,7 +3,6 @@ package core
import ( import (
"fmt" "fmt"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/ast"
) )
@ -17,8 +16,8 @@ func (f *Function) CompileIf(branch *ast.If) error {
return err return err
} }
f.assembler.Label(asm.LABEL, success) f.AddLabel(success)
defer f.assembler.Label(asm.LABEL, fail) defer f.AddLabel(fail)
f.count.branch++ f.count.branch++
return f.CompileAST(branch.Body) return f.CompileAST(branch.Body)
} }

View File

@ -10,8 +10,8 @@ import (
// CompileLoop compiles a loop instruction. // CompileLoop compiles a loop instruction.
func (f *Function) CompileLoop(loop *ast.Loop) error { func (f *Function) CompileLoop(loop *ast.Loop) error {
label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop)
f.assembler.Label(asm.LABEL, label) f.AddLabel(label)
defer f.assembler.Jump(label) defer f.Jump(asm.JUMP, label)
f.count.loop++ f.count.loop++
return f.CompileAST(loop.Body) return f.CompileAST(loop.Body)
} }

View File

@ -6,7 +6,7 @@ import (
// CompileReturn compiles a return instruction. // CompileReturn compiles a return instruction.
func (f *Function) CompileReturn(node *ast.Return) error { func (f *Function) CompileReturn(node *ast.Return) error {
defer f.assembler.Return() defer f.Return()
if node.Value == nil { if node.Value == nil {
return nil return nil

View File

@ -24,7 +24,6 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
} }
tmp := f.cpu.MustFindFree(f.cpu.General) tmp := f.cpu.MustFindFree(f.cpu.General)
f.cpu.Use(tmp)
defer f.cpu.Free(tmp) defer f.cpu.Free(tmp)
err := f.ExpressionToRegister(value, tmp) err := f.ExpressionToRegister(value, tmp)

View File

@ -11,22 +11,22 @@ import (
func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error { func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error {
switch operation.Text() { switch operation.Text() {
case "+", "+=": case "+", "+=":
f.assembler.RegisterNumber(asm.ADD, register, number) f.RegisterNumber(asm.ADD, register, number)
case "-", "-=": case "-", "-=":
f.assembler.RegisterNumber(asm.SUB, register, number) f.RegisterNumber(asm.SUB, register, number)
case "*", "*=": case "*", "*=":
f.assembler.RegisterNumber(asm.MUL, register, number) f.RegisterNumber(asm.MUL, register, number)
case "/", "/=": case "/", "/=":
f.assembler.RegisterNumber(asm.DIV, register, number) f.RegisterNumber(asm.DIV, register, number)
case "==", "!=", "<", "<=", ">", ">=": case "==", "!=", "<", "<=", ">", ">=":
f.assembler.RegisterNumber(asm.COMPARE, register, number) f.RegisterNumber(asm.COMPARE, register, number)
case "=": case "=":
f.assembler.RegisterNumber(asm.MOVE, register, number) f.RegisterNumber(asm.MOVE, register, number)
default: default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)

View File

@ -11,22 +11,22 @@ import (
func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error { func (f *Function) ExecuteRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error {
switch operation.Text() { switch operation.Text() {
case "+", "+=": case "+", "+=":
f.assembler.RegisterRegister(asm.ADD, destination, source) f.RegisterRegister(asm.ADD, destination, source)
case "-", "-=": case "-", "-=":
f.assembler.RegisterRegister(asm.SUB, destination, source) f.RegisterRegister(asm.SUB, destination, source)
case "*", "*=": case "*", "*=":
f.assembler.RegisterRegister(asm.MUL, destination, source) f.RegisterRegister(asm.MUL, destination, source)
case "/", "/=": case "/", "/=":
f.assembler.RegisterRegister(asm.DIV, destination, source) f.RegisterRegister(asm.DIV, destination, source)
case "==", "!=", "<", "<=", ">", ">=": case "==", "!=", "<", "<=", ">", ">=":
f.assembler.RegisterRegister(asm.COMPARE, destination, source) f.RegisterRegister(asm.COMPARE, destination, source)
case "=": case "=":
f.assembler.RegisterRegister(asm.MOVE, destination, source) f.RegisterRegister(asm.MOVE, destination, source)
default: default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position) return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)

View File

@ -18,7 +18,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
err := f.CompileCall(node) err := f.CompileCall(node)
if register != f.cpu.Output[0] { if register != f.cpu.Output[0] {
f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0]) f.RegisterRegister(asm.MOVE, register, f.cpu.Output[0])
} }
return err return err
@ -34,8 +34,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if f.UsesRegister(right, register) { if f.UsesRegister(right, register) {
register = f.cpu.MustFindFree(f.cpu.General) register = f.cpu.MustFindFree(f.cpu.General)
} else {
f.SaveRegister(register)
} }
f.cpu.Reserve(register) f.cpu.Reserve(register)
@ -45,11 +43,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
return err return err
} }
f.cpu.Use(register)
err = f.Execute(node.Token, register, right) err = f.Execute(node.Token, register, right)
if register != final { if register != final {
f.assembler.RegisterRegister(asm.MOVE, final, register) f.RegisterRegister(asm.MOVE, final, register)
f.cpu.Free(register) f.cpu.Free(register)
} }

View File

@ -46,9 +46,9 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
// Compile turns a function into machine code. // Compile turns a function into machine code.
func (f *Function) Compile() { func (f *Function) Compile() {
defer close(f.finished) defer close(f.finished)
f.assembler.Label(asm.LABEL, f.Name) f.AddLabel(f.Name)
f.err = f.CompileTokens(f.Body) f.err = f.CompileTokens(f.Body)
f.assembler.Return() f.Return()
} }
// CompileTokens compiles a token list. // CompileTokens compiles a token list.

View File

@ -0,0 +1,98 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/config"
"git.akyoto.dev/cli/q/src/build/cpu"
)
func (f *Function) AddLabel(label string) {
f.assembler.Label(asm.LABEL, label)
f.postInstruction()
}
func (f *Function) Call(label string) {
f.assembler.Call(label)
f.cpu.Use(f.cpu.Output[0])
f.postInstruction()
}
func (f *Function) Comment(comment string) {
f.assembler.Comment(comment)
f.postInstruction()
}
func (f *Function) Jump(mnemonic asm.Mnemonic, label string) {
f.assembler.Label(mnemonic, label)
f.postInstruction()
}
func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) {
f.assembler.Register(mnemonic, a)
if mnemonic == asm.POP {
f.cpu.Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) {
if f.cpu.IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.assembler.RegisterNumber(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.cpu.Use(a)
}
f.postInstruction()
}
func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu.Register) {
if mnemonic == asm.MOVE && a == b {
return
}
if f.cpu.IsUsed(a) && isDestructive(mnemonic) {
f.SaveRegister(a)
}
f.assembler.RegisterRegister(mnemonic, a, b)
if mnemonic == asm.MOVE {
f.cpu.Use(a)
}
f.postInstruction()
}
func (f *Function) Return() {
f.assembler.Return()
f.postInstruction()
}
func (f *Function) Syscall() {
f.assembler.Syscall()
f.cpu.Use(f.cpu.Output[0])
f.postInstruction()
}
func (f *Function) postInstruction() {
if !config.Assembler {
return
}
f.registerHistory = append(f.registerHistory, f.cpu.Used)
}
func isDestructive(mnemonic asm.Mnemonic) bool {
switch mnemonic {
case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV:
return true
default:
return false
}
}

View File

@ -14,6 +14,12 @@ func (f *Function) SaveRegister(register cpu.Register) {
return return
} }
for _, general := range f.cpu.General {
if register == general {
return
}
}
var variable *Variable var variable *Variable
for _, v := range f.variables { for _, v := range f.variables {
@ -29,13 +35,12 @@ func (f *Function) SaveRegister(register cpu.Register) {
newRegister := f.cpu.MustFindFree(f.cpu.General) newRegister := f.cpu.MustFindFree(f.cpu.General)
f.cpu.Reserve(newRegister) f.cpu.Reserve(newRegister)
f.cpu.Use(newRegister)
if config.Comments { if config.Comments {
f.assembler.Comment(fmt.Sprintf("save %s to %s", register, newRegister)) f.Comment(fmt.Sprintf("save %s to %s", register, newRegister))
} }
f.assembler.RegisterRegister(asm.MOVE, newRegister, register) f.RegisterRegister(asm.MOVE, newRegister, register)
f.cpu.Free(register) f.cpu.Free(register)
variable.Register = newRegister variable.Register = newRegister
} }

View File

@ -21,8 +21,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position)
} }
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
f.useVariable(variable) f.useVariable(variable)
f.RegisterRegister(asm.MOVE, register, variable.Register)
return nil return nil
case token.Number: case token.Number:
@ -33,7 +33,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
return err return err
} }
f.assembler.RegisterNumber(asm.MOVE, register, n) f.RegisterNumber(asm.MOVE, register, n)
return nil return nil
case token.String: case token.String:

View File

@ -1,6 +1,7 @@
package core package core
import ( import (
"bytes"
"fmt" "fmt"
"git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/asm"
@ -10,13 +11,14 @@ import (
// state is the data structure we embed in each function to preserve compilation state. // state is the data structure we embed in each function to preserve compilation state.
type state struct { type state struct {
err error err error
variables map[string]*Variable variables map[string]*Variable
functions map[string]*Function functions map[string]*Function
finished chan struct{} registerHistory []uint64
assembler asm.Assembler finished chan struct{}
cpu cpu.CPU assembler asm.Assembler
count counter cpu cpu.CPU
count counter
} }
// counter stores how often a certain statement appeared so we can generate a unique label from it. // counter stores how often a certain statement appeared so we can generate a unique label from it.
@ -28,30 +30,42 @@ type counter struct {
// PrintInstructions shows the assembly instructions. // PrintInstructions shows the assembly instructions.
func (s *state) PrintInstructions() { func (s *state) PrintInstructions() {
ansi.Dim.Println("╭────────────────────────────────────────────────────╮") ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮")
for _, x := range s.assembler.Instructions { for i, x := range s.assembler.Instructions {
ansi.Dim.Print("│ ") ansi.Dim.Print("│ ")
switch x.Mnemonic { switch x.Mnemonic {
case asm.LABEL: case asm.LABEL:
ansi.Yellow.Printf("%-50s", x.Data.String()+":") ansi.Yellow.Printf("%-44s", x.Data.String()+":")
case asm.COMMENT: case asm.COMMENT:
ansi.Dim.Printf("%-50s", x.Data.String()) ansi.Dim.Printf("%-44s", x.Data.String())
default: default:
ansi.Green.Printf("%-12s", x.Mnemonic.String()) ansi.Green.Printf("%-12s", x.Mnemonic.String())
if x.Data != nil { if x.Data != nil {
fmt.Printf("%-38s", x.Data.String()) fmt.Printf("%-32s", x.Data.String())
} else { } else {
fmt.Printf("%-38s", "") fmt.Printf("%-32s", "")
} }
} }
registers := bytes.Buffer{}
used := s.registerHistory[i]
for _, reg := range s.cpu.All {
if used&(1<<reg) != 0 {
registers.WriteString("⬤ ")
} else {
registers.WriteString("◯ ")
}
}
ansi.Dim.Print(registers.String())
ansi.Dim.Print(" │\n") ansi.Dim.Print(" │\n")
} }
ansi.Dim.Println("╰────────────────────────────────────────────────────╯") ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯")
} }

View File

@ -7,34 +7,34 @@ type CPU struct {
Syscall []Register Syscall []Register
Input []Register Input []Register
Output []Register Output []Register
reserved uint64 Reserved uint64
used uint64 Used uint64
} }
// Free will reset the reserved and used status which means the register can be allocated again. // Free will reset the reserved and used status which means the register can be allocated again.
func (c *CPU) Free(reg Register) { func (c *CPU) Free(reg Register) {
c.used &= ^(1 << reg) c.Used &= ^(1 << reg)
c.reserved &= ^(1 << reg) c.Reserved &= ^(1 << reg)
} }
// IsReserved returns true if the register was marked for future use. // IsReserved returns true if the register was marked for future use.
func (c *CPU) IsReserved(reg Register) bool { func (c *CPU) IsReserved(reg Register) bool {
return c.reserved&(1<<reg) != 0 return c.Reserved&(1<<reg) != 0
} }
// IsUsed returns true if the register is currently in use and holds a value. // IsUsed returns true if the register is currently in use and holds a value.
func (c *CPU) IsUsed(reg Register) bool { func (c *CPU) IsUsed(reg Register) bool {
return c.used&(1<<reg) != 0 return c.Used&(1<<reg) != 0
} }
// Reserve reserves a register for future use. // Reserve reserves a register for future use.
func (c *CPU) Reserve(reg Register) { func (c *CPU) Reserve(reg Register) {
c.reserved |= (1 << reg) c.Reserved |= (1 << reg)
} }
// Use marks a register to be currently in use which means it must be preserved across function calls. // Use marks a register to be currently in use which means it must be preserved across function calls.
func (c *CPU) Use(reg Register) { func (c *CPU) Use(reg Register) {
c.used |= (1 << reg) c.Used |= (1 << reg)
} }
// FindFree tries to find a free register in the given slice of registers. // FindFree tries to find a free register in the given slice of registers.

View File

@ -0,0 +1,12 @@
main() {
syscall(60, f(1, 2, 3))
}
f(x, y, z) {
w := g(4, 5, 6)
return x + y + z + w
}
g(x, y, z) {
return x + y + z
}

View File

@ -21,14 +21,15 @@ var programs = []struct {
{"square-sum", "", 25}, {"square-sum", "", 25},
{"chained-calls", "", 9}, {"chained-calls", "", 9},
{"nested-calls", "", 4}, {"nested-calls", "", 4},
{"overwrite", "", 3}, {"param", "", 3},
{"param-multi", "", 21},
{"reuse", "", 3}, {"reuse", "", 3},
{"return", "", 6}, {"return", "", 6},
{"reassign", "", 2}, {"reassign", "", 2},
{"branch", "", 0}, {"branch", "", 0},
{"branch-and", "", 0}, {"branch-and", "", 0},
{"branch-or", "", 0}, {"branch-or", "", 0},
{"branch-combined", "", 0}, {"branch-both", "", 0},
} }
func TestPrograms(t *testing.T) { func TestPrograms(t *testing.T) {
@ -42,7 +43,7 @@ func TestPrograms(t *testing.T) {
func BenchmarkPrograms(b *testing.B) { func BenchmarkPrograms(b *testing.B) {
for _, test := range programs { for _, test := range programs {
b.Run(test.Name, func(b *testing.B) { b.Run(test.Name, func(b *testing.B) {
compiler := build.New(filepath.Join("programs", test.Name)) compiler := build.New(filepath.Join("programs", test.Name+".q"))
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, err := compiler.Run() _, err := compiler.Run()