Improved code generation
This commit is contained in:
parent
795935ddfb
commit
4d88260333
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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])
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
37
src/build/core/ExecuteLeaf.go
Normal file
37
src/build/core/ExecuteLeaf.go
Normal 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)
|
||||
}
|
33
src/build/core/ExecuteRegisterNumber.go
Normal file
33
src/build/core/ExecuteRegisterNumber.go
Normal 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
|
||||
}
|
35
src/build/core/ExecuteRegisterRegister.go
Normal file
35
src/build/core/ExecuteRegisterRegister.go
Normal 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
|
||||
}
|
33
src/build/core/ExpressionToRegister.go
Normal file
33
src/build/core/ExpressionToRegister.go
Normal 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)
|
||||
}
|
20
src/build/core/ExpressionsToRegisters.go
Normal file
20
src/build/core/ExpressionsToRegisters.go
Normal 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
|
||||
}
|
@ -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{},
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user