From ec5240425bb14d5f398ec497de66d0408c39b008 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Thu, 27 Jun 2024 15:23:02 +0200 Subject: [PATCH] Added simple variable lifetimes --- src/build/Assignment.go | 1 + src/build/Execute.go | 21 ++++++++------- src/build/Function.go | 38 ++++++++++++++++++++++++++- src/build/Variable.go | 5 ++-- src/build/VariableDefinition.go | 35 +++++++++++++++++++++++-- src/build/scan.go | 46 ++++++++++++++++----------------- 6 files changed, 107 insertions(+), 39 deletions(-) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index f2618f6..895078b 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -14,5 +14,6 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, expr.Children[0].Token.Position) } + defer f.useVariable(variable) return f.Execute(expr.Token, variable.Register, expr.Children[1]) } diff --git a/src/build/Execute.go b/src/build/Execute.go index d636bed..1312766 100644 --- a/src/build/Execute.go +++ b/src/build/Execute.go @@ -62,6 +62,17 @@ func (f *Function) ExecuteFunctionCall(expr *expression.Expression) error { // 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) @@ -71,16 +82,6 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope } return f.ExecuteRegisterNumber(operation, register, number) - - case token.Identifier: - name := operand.Text() - variable, exists := f.Variables[name] - - if !exists { - return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) - } - - return f.ExecuteRegisterRegister(operation, register, variable.Register) } return errors.New(errors.NotImplemented, f.File, operation.Position) diff --git a/src/build/Function.go b/src/build/Function.go index fadb68b..8815d26 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "git.akyoto.dev/cli/q/src/build/arch/x64" "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/cli/q/src/build/expression" @@ -141,17 +142,51 @@ func (f *Function) ExpressionToRegister(root *expression.Expression, register cp // ExpressionsToRegisters moves multiple expressions into the specified registers. func (f *Function) ExpressionsToRegisters(expressions []*expression.Expression, registers []cpu.Register) error { + var destinations []cpu.Register + for i := len(expressions) - 1; i >= 0; i-- { - err := f.ExpressionToRegister(expressions[i], registers[i]) + original := registers[i] + expression := expressions[i] + + if expression.IsLeaf() { + variable, exists := f.Variables[expression.Token.Text()] + + if exists && variable.Register == original { + continue + } + } + + register := original + save := !f.CPU.IsFree(register) + + if save { + register = x64.RAX + } + + err := f.ExpressionToRegister(expression, register) if err != nil { return err } + + if save { + destinations = append(destinations, original) + f.Assembler.Register(asm.PUSH, x64.RAX) + } + } + + for i := len(destinations) - 1; i >= 0; i-- { + f.Assembler.Register(asm.POP, destinations[i]) } return nil } +func (f *Function) Log(messages ...any) { + fmt.Printf("[%s] ", f.Name) + fmt.Println(messages...) +} + // TokenToRegister moves a token into a register. // It only works with identifiers, numbers and strings. func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { @@ -168,6 +203,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { f.Assembler.RegisterRegister(asm.MOVE, register, variable.Register) } + f.useVariable(variable) return nil case token.Number: diff --git a/src/build/Variable.go b/src/build/Variable.go index c8189f1..cb6ff43 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -6,6 +6,7 @@ import ( // Variable represents a variable in a function. type Variable struct { - Name string - Register cpu.Register + Name string + Register cpu.Register + UsesRemaining int } diff --git a/src/build/VariableDefinition.go b/src/build/VariableDefinition.go index b7ac7ca..b952bab 100644 --- a/src/build/VariableDefinition.go +++ b/src/build/VariableDefinition.go @@ -2,6 +2,7 @@ package build import ( "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/token" "git.akyoto.dev/cli/q/src/errors" ) @@ -34,7 +35,37 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error Register: reg, } - f.Variables[name] = variable - f.CPU.Use(reg) + f.addVariable(variable) + f.useVariable(variable) return nil } + +func (f *Function) addVariable(variable *Variable) { + variable.UsesRemaining = countIdentifier(f.Body, variable.Name) + f.Variables[variable.Name] = variable + f.CPU.Use(variable.Register) +} + +func (f *Function) useVariable(variable *Variable) { + variable.UsesRemaining-- + + if variable.UsesRemaining < 0 { + panic("incorrect number of variable use calls") + } + + if variable.UsesRemaining == 0 { + f.CPU.Free(variable.Register) + } +} + +func countIdentifier(tokens token.List, name string) int { + count := 0 + + for _, t := range tokens { + if t.Kind == token.Identifier && t.Text() == name { + count++ + } + } + + return count +} diff --git a/src/build/scan.go b/src/build/scan.go index f9b7ea0..fd01a26 100644 --- a/src/build/scan.go +++ b/src/build/scan.go @@ -224,43 +224,41 @@ func scanFile(path string, functions chan<- *Function) error { return errors.New(errors.ExpectedFunctionDefinition, file, tokens[i].Position) } - cpu := cpu.CPU{ - Call: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Return: x64.ReturnValueRegisters, + function := &Function{ + Name: tokens[nameStart].Text(), + File: file, + Body: tokens[bodyStart:i], + Variables: map[string]*Variable{}, + CPU: cpu.CPU{ + Call: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Return: x64.ReturnValueRegisters, + }, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, } parameters := tokens[paramsStart:paramsEnd] - variables := map[string]*Variable{} - err := expression.EachParameter(parameters, func(parameter token.List) error { - if len(parameter) == 1 { - name := parameter[0].Text() - register := x64.SyscallRegisters[1+len(variables)] - variables[name] = &Variable{Name: name, Register: register} - cpu.Use(register) + err := expression.EachParameter(parameters, func(tokens token.List) error { + if len(tokens) == 1 { + name := tokens[0].Text() + register := x64.CallRegisters[len(function.Variables)] + variable := &Variable{Name: name, Register: register} + function.addVariable(variable) return nil } - return errors.New(errors.NotImplemented, file, parameter[0].Position) + return errors.New(errors.NotImplemented, file, tokens[0].Position) }) if err != nil { return err } - functions <- &Function{ - Name: tokens[nameStart].Text(), - File: file, - Body: tokens[bodyStart:i], - Variables: variables, - CPU: cpu, - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - } - + functions <- function nameStart = -1 paramsStart = -1 bodyStart = -1