Improved code generation

This commit is contained in:
Eduard Urbach 2024-07-03 16:37:59 +02:00
parent 795935ddfb
commit 4d88260333
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
15 changed files with 215 additions and 275 deletions

View File

@ -1,7 +1,7 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/expression"
)
@ -9,9 +9,43 @@ import (
// All call registers must hold the correct parameter values before the function invocation.
// Registers that are in use must be saved if they are modified by the function.
// After the function call, they must be restored in reverse order.
func (f *Function) CompileCall(expr *expression.Expression) error {
_, err := f.EvaluateCall(expr)
return err
func (f *Function) CompileCall(root *expression.Expression) error {
funcName := root.Children[0].Token.Text()
parameters := root.Children[1:]
registers := f.cpu.Input[:len(parameters)]
isSyscall := funcName == "syscall"
if isSyscall {
registers = f.cpu.Syscall[:len(parameters)]
}
err := f.ExpressionsToRegisters(parameters, registers)
if err != nil {
return err
}
for _, register := range f.cpu.General {
if !f.cpu.IsFree(register) {
f.assembler.Register(asm.PUSH, register)
}
}
if isSyscall {
f.assembler.Syscall()
} else {
f.assembler.Call(funcName)
}
for i := len(f.cpu.General) - 1; i >= 0; i-- {
register := f.cpu.General[i]
if !f.cpu.IsFree(register) {
f.assembler.Register(asm.POP, register)
}
}
return nil
}
// CompileSyscall executes a syscall.
@ -23,17 +57,3 @@ func (f *Function) CompileSyscall(expr *expression.Expression) error {
f.sideEffects++
return err
}
// ExpressionsToRegisters moves multiple expressions into the specified registers.
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
for i := len(registers) - 1; i >= 0; i-- {
f.SaveRegister(registers[i])
err := f.EvaluateTo(expressions[i], registers[i])
if err != nil {
return err
}
}
return nil
}

View File

@ -59,27 +59,6 @@ func (f *Function) AddVariable(variable *Variable) {
f.cpu.Use(variable.Register)
}
func (f *Function) addTemporary(root *expression.Expression) *Variable {
f.count.tmps++
name := fmt.Sprintf("t%d", f.count.tmps)
register := f.cpu.MustUseFree(f.cpu.General)
tmp := &Variable{
Name: name,
Value: root,
Alive: 1,
Register: register,
}
f.variables[name] = tmp
if config.Comments {
f.assembler.Comment(fmt.Sprintf("%s = %s (%s)", name, root, register))
}
return tmp
}
func (f *Function) useVariable(variable *Variable) {
variable.Alive--
@ -121,7 +100,7 @@ func (f *Function) storeVariableInRegister(name string, value *expression.Expres
panic("no free registers")
}
err := f.EvaluateTo(value, reg)
err := f.ExpressionToRegister(value, reg)
f.AddVariable(&Variable{
Name: name,

View File

@ -12,5 +12,5 @@ func (f *Function) CompileReturn(node *ast.Return) error {
return nil
}
return f.EvaluateTo(node.Value, f.cpu.Return[0])
return f.ExpressionToRegister(node.Value, f.cpu.Output[0])
}

View File

@ -1,123 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
)
// Evaluate evaluates the result of an expression and saves it into a temporary register.
func (f *Function) Evaluate(root *expression.Expression) (*Variable, error) {
if root.IsLeaf() {
return f.EvaluateLeaf(root)
}
if ast.IsFunctionCall(root) {
return f.EvaluateCall(root)
}
left := root.Children[0]
right := root.Children[1]
tmpLeft, err := f.Evaluate(left)
if err != nil {
return nil, err
}
tmpLeftExpr := expression.NewLeaf(token.Token{
Kind: token.Identifier,
Position: left.Token.Position,
Bytes: []byte(tmpLeft.Name),
})
tmpLeftExpr.Parent = root
root.Children[0].Parent = nil
root.Children[0] = tmpLeftExpr
tmpRight, err := f.Evaluate(right)
if err != nil {
return nil, err
}
tmpRightExpr := expression.NewLeaf(token.Token{
Kind: token.Identifier,
Position: left.Token.Position,
Bytes: []byte(tmpRight.Name),
})
tmpRightExpr.Parent = root
root.Children[1].Parent = nil
root.Children[1] = tmpRightExpr
tmp := f.addTemporary(root)
f.assembler.RegisterRegister(asm.MOVE, tmp.Register, tmpLeft.Register)
f.useVariable(tmpLeft)
err = f.opRegisterRegister(root.Token, tmp.Register, tmpRight.Register)
f.useVariable(tmpRight)
return tmp, err
}
func (f *Function) EvaluateCall(root *expression.Expression) (*Variable, error) {
funcName := root.Children[0].Token.Text()
parameters := root.Children[1:]
registers := f.cpu.Call[:len(parameters)]
isSyscall := funcName == "syscall"
if isSyscall {
registers = f.cpu.Syscall[:len(parameters)]
}
err := f.ExpressionsToRegisters(parameters, registers)
for _, register := range f.cpu.General {
if !f.cpu.IsFree(register) {
f.assembler.Register(asm.PUSH, register)
}
}
if isSyscall {
f.assembler.Syscall()
} else {
f.assembler.Call(funcName)
}
for i := len(f.cpu.General) - 1; i >= 0; i-- {
register := f.cpu.General[i]
if !f.cpu.IsFree(register) {
f.assembler.Register(asm.POP, register)
}
}
tmp := f.addTemporary(root)
f.assembler.RegisterRegister(asm.MOVE, tmp.Register, f.cpu.Return[0])
return tmp, err
}
func (f *Function) EvaluateLeaf(root *expression.Expression) (*Variable, error) {
tmp := f.addTemporary(root)
err := f.TokenToRegister(root.Token, tmp.Register)
return tmp, err
}
func (f *Function) EvaluateTo(root *expression.Expression, register cpu.Register) error {
tmp, err := f.Evaluate(root)
if err != nil {
return err
}
if register != tmp.Register {
f.assembler.RegisterRegister(asm.MOVE, register, tmp.Register)
}
f.useVariable(tmp)
return nil
}

View File

@ -1,11 +1,8 @@
package core
import (
"strconv"
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/expression"
"git.akyoto.dev/cli/q/src/build/token"
)
@ -16,54 +13,24 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value *
return f.ExecuteLeaf(operation, register, value.Token)
}
var temporary cpu.Register
if ast.IsFunctionCall(value) {
temporary = f.cpu.Return[0]
} else {
found := false
temporary, found = f.cpu.FindFree(f.cpu.General)
if !found {
panic("no free registers")
}
}
f.cpu.Use(temporary)
defer f.cpu.Free(temporary)
err := f.EvaluateTo(value, temporary)
if err != nil {
return err
}
return f.opRegisterRegister(operation, register, temporary)
}
// ExecuteLeaf performs an operation on a register with the given leaf operand.
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
switch operand.Kind {
case token.Identifier:
name := operand.Text()
variable, exists := f.variables[name]
if !exists {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
}
defer f.useVariable(variable)
return f.opRegisterRegister(operation, register, variable.Register)
case token.Number:
value := operand.Text()
number, err := strconv.Atoi(value)
err := f.CompileCall(value)
if err != nil {
return err
}
return f.opRegisterNumber(operation, register, number)
return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0])
}
return errors.New(errors.NotImplemented, f.File, operation.Position)
tmp := f.cpu.MustUseFree(f.cpu.General)
defer f.cpu.Free(tmp)
err := f.ExpressionToRegister(value, tmp)
if err != nil {
return err
}
return f.ExecuteRegisterRegister(operation, register, tmp)
}

View File

@ -0,0 +1,37 @@
package core
import (
"strconv"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// ExecuteLeaf performs an operation on a register with the given leaf operand.
func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, operand token.Token) error {
switch operand.Kind {
case token.Identifier:
name := operand.Text()
variable, exists := f.variables[name]
if !exists {
return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position)
}
defer f.useVariable(variable)
return f.ExecuteRegisterRegister(operation, register, variable.Register)
case token.Number:
value := operand.Text()
number, err := strconv.Atoi(value)
if err != nil {
return err
}
return f.ExecuteRegisterNumber(operation, register, number)
}
return errors.New(errors.NotImplemented, f.File, operation.Position)
}

View File

@ -0,0 +1,33 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// ExecuteRegisterNumber performs an operation on a register and a number.
func (f *Function) ExecuteRegisterNumber(operation token.Token, register cpu.Register, number int) error {
switch operation.Text() {
case "+", "+=":
f.assembler.RegisterNumber(asm.ADD, register, number)
case "-", "-=":
f.assembler.RegisterNumber(asm.SUB, register, number)
case "*", "*=":
f.assembler.RegisterNumber(asm.MUL, register, number)
case "/", "/=":
f.assembler.RegisterNumber(asm.DIV, register, number)
case "=":
f.assembler.RegisterNumber(asm.MOVE, register, number)
default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
}
return nil
}

View File

@ -0,0 +1,35 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// ExecuteRegisterRegister performs an operation on two registers.
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)
case "-", "-=":
f.assembler.RegisterRegister(asm.SUB, destination, source)
case "*", "*=":
f.assembler.RegisterRegister(asm.MUL, destination, source)
case "/", "/=":
f.assembler.RegisterRegister(asm.DIV, destination, source)
case "=":
if destination != source {
f.assembler.RegisterRegister(asm.MOVE, destination, source)
}
default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
}
return nil
}

View File

@ -0,0 +1,33 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/ast"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/expression"
)
// ExpressionToRegister puts the result of an expression into the specified register.
func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error {
if root.IsLeaf() {
return f.TokenToRegister(root.Token, register)
}
if ast.IsFunctionCall(root) {
err := f.CompileCall(root)
f.assembler.RegisterRegister(asm.MOVE, register, f.cpu.Output[0])
return err
}
left := root.Children[0]
right := root.Children[1]
err := f.ExpressionToRegister(left, register)
if err != nil {
return err
}
f.SaveRegister(register)
return f.Execute(root.Token, register, right)
}

View File

@ -0,0 +1,20 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/expression"
)
// ExpressionsToRegisters moves multiple expressions into the specified registers.
func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error {
for i := len(registers) - 1; i >= 0; i-- {
f.SaveRegister(registers[i])
err := f.ExpressionToRegister(expressions[i], registers[i])
if err != nil {
return err
}
}
return nil
}

View File

@ -31,10 +31,10 @@ func NewFunction(name string, file *fs.File, body token.List) *Function {
Instructions: make([]asm.Instruction, 0, 32),
},
cpu: cpu.CPU{
Call: x64.CallRegisters,
Input: x64.CallRegisters,
General: x64.GeneralRegisters,
Syscall: x64.SyscallRegisters,
Return: x64.ReturnValueRegisters,
Output: x64.ReturnValueRegisters,
},
definitions: map[string]*Definition{},
variables: map[string]*Variable{},

View File

@ -18,7 +18,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error {
constant, exists := f.definitions[name]
if exists {
return f.EvaluateTo(constant.Value, register)
return f.ExpressionToRegister(constant.Value, register)
}
variable, exists := f.variables[name]

View File

@ -1,60 +0,0 @@
package core
import (
"git.akyoto.dev/cli/q/src/build/asm"
"git.akyoto.dev/cli/q/src/build/cpu"
"git.akyoto.dev/cli/q/src/build/errors"
"git.akyoto.dev/cli/q/src/build/token"
)
// opRegisterNumber performs an operation on a register and a number.
func (f *Function) opRegisterNumber(operation token.Token, register cpu.Register, number int) error {
switch operation.Text() {
case "+", "+=":
f.assembler.RegisterNumber(asm.ADD, register, number)
case "-", "-=":
f.assembler.RegisterNumber(asm.SUB, register, number)
case "*", "*=":
f.assembler.RegisterNumber(asm.MUL, register, number)
case "/", "/=":
f.assembler.RegisterNumber(asm.DIV, register, number)
case "=":
f.assembler.RegisterNumber(asm.MOVE, register, number)
default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
}
return nil
}
// opRegisterRegister performs an operation on two registers.
func (f *Function) opRegisterRegister(operation token.Token, destination cpu.Register, source cpu.Register) error {
switch operation.Text() {
case "+", "+=":
f.assembler.RegisterRegister(asm.ADD, destination, source)
case "-", "-=":
f.assembler.RegisterRegister(asm.SUB, destination, source)
case "*", "*=":
f.assembler.RegisterRegister(asm.MUL, destination, source)
case "/", "/=":
f.assembler.RegisterRegister(asm.DIV, destination, source)
case "=":
if destination != source {
f.assembler.RegisterRegister(asm.MOVE, destination, source)
}
default:
return errors.New(&errors.InvalidOperator{Operator: operation.Text()}, f.File, operation.Position)
}
return nil
}

View File

@ -24,7 +24,6 @@ type state struct {
// counter stores how often a certain statement appeared so we can generate a unique label from it.
type counter struct {
loop int
tmps int
}
// PrintInstructions shows the assembly instructions.

View File

@ -2,10 +2,10 @@ package cpu
// CPU represents the processor.
type CPU struct {
Call []Register
General []Register
Syscall []Register
Return []Register
Input []Register
Output []Register
usage uint64
}