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.
func (a *Assembler) 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.
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 {
return false
}

View File

@ -41,7 +41,6 @@ func (f *Function) Compare(comparison *expression.Expression) error {
return err
}
f.cpu.Use(tmp)
defer f.cpu.Free(tmp)
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])
for _, register := range registers {
f.SaveRegister(register)
}
// Push
for _, register := range f.cpu.General {
if f.cpu.IsUsed(register) {
f.assembler.Register(asm.PUSH, register)
f.Register(asm.PUSH, register)
}
}
// Call
if isSyscall {
f.assembler.Syscall()
f.Syscall()
} else {
f.assembler.Call(funcName)
f.Call(funcName)
}
// Pop
@ -60,7 +56,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
register := f.cpu.General[i]
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)
// Right
f.assembler.Label(asm.LABEL, leftFailLabel)
f.AddLabel(leftFailLabel)
right := condition.Children[1]
err = f.CompileCondition(right, successLabel, failLabel)
@ -52,7 +52,7 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab
f.JumpIfFalse(left.Token.Text(), failLabel)
// Right
f.assembler.Label(asm.LABEL, leftSuccessLabel)
f.AddLabel(leftSuccessLabel)
right := condition.Children[1]
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) {
switch operator {
case "==":
f.assembler.Label(asm.JNE, label)
f.Jump(asm.JNE, label)
case "!=":
f.assembler.Label(asm.JE, label)
f.Jump(asm.JE, label)
case ">":
f.assembler.Label(asm.JLE, label)
f.Jump(asm.JLE, label)
case "<":
f.assembler.Label(asm.JGE, label)
f.Jump(asm.JGE, label)
case ">=":
f.assembler.Label(asm.JL, label)
f.Jump(asm.JL, label)
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) {
switch operator {
case "==":
f.assembler.Label(asm.JE, label)
f.Jump(asm.JE, label)
case "!=":
f.assembler.Label(asm.JNE, label)
f.Jump(asm.JNE, label)
case ">":
f.assembler.Label(asm.JG, label)
f.Jump(asm.JG, label)
case "<":
f.assembler.Label(asm.JL, label)
f.Jump(asm.JL, label)
case ">=":
f.assembler.Label(asm.JGE, label)
f.Jump(asm.JGE, label)
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) {
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
@ -60,7 +60,7 @@ func (f *Function) useVariable(variable *Variable) {
if variable.Alive == 0 {
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)

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import (
// CompileReturn compiles a return instruction.
func (f *Function) CompileReturn(node *ast.Return) error {
defer f.assembler.Return()
defer f.Return()
if node.Value == 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)
f.cpu.Use(tmp)
defer f.cpu.Free(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 {
switch operation.Text() {
case "+", "+=":
f.assembler.RegisterNumber(asm.ADD, register, number)
f.RegisterNumber(asm.ADD, register, number)
case "-", "-=":
f.assembler.RegisterNumber(asm.SUB, register, number)
f.RegisterNumber(asm.SUB, register, number)
case "*", "*=":
f.assembler.RegisterNumber(asm.MUL, register, number)
f.RegisterNumber(asm.MUL, register, number)
case "/", "/=":
f.assembler.RegisterNumber(asm.DIV, register, number)
f.RegisterNumber(asm.DIV, register, number)
case "==", "!=", "<", "<=", ">", ">=":
f.assembler.RegisterNumber(asm.COMPARE, register, number)
f.RegisterNumber(asm.COMPARE, register, number)
case "=":
f.assembler.RegisterNumber(asm.MOVE, register, number)
f.RegisterNumber(asm.MOVE, register, number)
default:
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 {
switch operation.Text() {
case "+", "+=":
f.assembler.RegisterRegister(asm.ADD, destination, source)
f.RegisterRegister(asm.ADD, destination, source)
case "-", "-=":
f.assembler.RegisterRegister(asm.SUB, destination, source)
f.RegisterRegister(asm.SUB, destination, source)
case "*", "*=":
f.assembler.RegisterRegister(asm.MUL, destination, source)
f.RegisterRegister(asm.MUL, destination, source)
case "/", "/=":
f.assembler.RegisterRegister(asm.DIV, destination, source)
f.RegisterRegister(asm.DIV, destination, source)
case "==", "!=", "<", "<=", ">", ">=":
f.assembler.RegisterRegister(asm.COMPARE, destination, source)
f.RegisterRegister(asm.COMPARE, destination, source)
case "=":
f.assembler.RegisterRegister(asm.MOVE, destination, source)
f.RegisterRegister(asm.MOVE, destination, source)
default:
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)
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
@ -34,8 +34,6 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
if f.UsesRegister(right, register) {
register = f.cpu.MustFindFree(f.cpu.General)
} else {
f.SaveRegister(register)
}
f.cpu.Reserve(register)
@ -45,11 +43,10 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp
return err
}
f.cpu.Use(register)
err = f.Execute(node.Token, register, right)
if register != final {
f.assembler.RegisterRegister(asm.MOVE, final, register)
f.RegisterRegister(asm.MOVE, final, 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.
func (f *Function) Compile() {
defer close(f.finished)
f.assembler.Label(asm.LABEL, f.Name)
f.AddLabel(f.Name)
f.err = f.CompileTokens(f.Body)
f.assembler.Return()
f.Return()
}
// 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
}
for _, general := range f.cpu.General {
if register == general {
return
}
}
var variable *Variable
for _, v := range f.variables {
@ -29,13 +35,12 @@ func (f *Function) SaveRegister(register cpu.Register) {
newRegister := f.cpu.MustFindFree(f.cpu.General)
f.cpu.Reserve(newRegister)
f.cpu.Use(newRegister)
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)
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)
}
f.assembler.RegisterRegister(asm.MOVE, register, variable.Register)
f.useVariable(variable)
f.RegisterRegister(asm.MOVE, register, variable.Register)
return nil
case token.Number:
@ -33,7 +33,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
return err
}
f.assembler.RegisterNumber(asm.MOVE, register, n)
f.RegisterNumber(asm.MOVE, register, n)
return nil
case token.String:

View File

@ -1,6 +1,7 @@
package core
import (
"bytes"
"fmt"
"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.
type state struct {
err error
variables map[string]*Variable
functions map[string]*Function
finished chan struct{}
assembler asm.Assembler
cpu cpu.CPU
count counter
err error
variables map[string]*Variable
functions map[string]*Function
registerHistory []uint64
finished chan struct{}
assembler asm.Assembler
cpu cpu.CPU
count counter
}
// 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.
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("│ ")
switch x.Mnemonic {
case asm.LABEL:
ansi.Yellow.Printf("%-50s", x.Data.String()+":")
ansi.Yellow.Printf("%-44s", x.Data.String()+":")
case asm.COMMENT:
ansi.Dim.Printf("%-50s", x.Data.String())
ansi.Dim.Printf("%-44s", x.Data.String())
default:
ansi.Green.Printf("%-12s", x.Mnemonic.String())
if x.Data != nil {
fmt.Printf("%-38s", x.Data.String())
fmt.Printf("%-32s", x.Data.String())
} 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.Println("╰────────────────────────────────────────────────────╯")
ansi.Dim.Println("╰──────────────────────────────────────────────────────────────────────────────╯")
}

View File

@ -7,34 +7,34 @@ type CPU struct {
Syscall []Register
Input []Register
Output []Register
reserved uint64
used uint64
Reserved uint64
Used uint64
}
// Free will reset the reserved and used status which means the register can be allocated again.
func (c *CPU) Free(reg Register) {
c.used &= ^(1 << reg)
c.reserved &= ^(1 << reg)
c.Used &= ^(1 << reg)
c.Reserved &= ^(1 << reg)
}
// IsReserved returns true if the register was marked for future use.
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.
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.
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.
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.

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},
{"chained-calls", "", 9},
{"nested-calls", "", 4},
{"overwrite", "", 3},
{"param", "", 3},
{"param-multi", "", 21},
{"reuse", "", 3},
{"return", "", 6},
{"reassign", "", 2},
{"branch", "", 0},
{"branch-and", "", 0},
{"branch-or", "", 0},
{"branch-combined", "", 0},
{"branch-both", "", 0},
}
func TestPrograms(t *testing.T) {
@ -42,7 +43,7 @@ func TestPrograms(t *testing.T) {
func BenchmarkPrograms(b *testing.B) {
for _, test := range programs {
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++ {
_, err := compiler.Run()