From 43cdac55723ab7863bec8cdda5222c432052e493 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sat, 20 Jul 2024 23:20:23 +0200 Subject: [PATCH] Added scope package --- src/build/core/AddVariable.go | 18 +++++ src/build/core/Comment.go | 11 +++ src/build/core/Compare.go | 6 +- src/build/core/Compile.go | 57 --------------- src/build/core/CompileAST.go | 18 +++++ src/build/core/CompileASTNode.go | 31 ++++++++ src/build/core/CompileAssign.go | 4 +- src/build/core/CompileCall.go | 6 +- src/build/core/CompileCondition.go | 37 ---------- src/build/core/CompileDefinition.go | 70 ------------------- src/build/core/CompileIf.go | 4 +- src/build/core/CompileLoop.go | 6 +- src/build/core/CompileTokens.go | 19 +++++ src/build/core/Execute.go | 4 +- src/build/core/ExecuteLeaf.go | 2 +- src/build/core/ExpressionToRegister.go | 6 +- src/build/core/Function.go | 56 +++++---------- src/build/core/Instructions.go | 34 +++------ src/build/core/JumpIfFalse.go | 23 ++++++ src/build/core/JumpIfTrue.go | 23 ++++++ src/build/core/NewFunction.go | 35 ++++++++++ .../core/{State.go => PrintInstructions.go} | 29 ++------ src/build/core/SaveRegister.go | 6 +- src/build/core/Scope.go | 65 ----------------- src/build/core/TokenToRegister.go | 2 +- src/build/core/Variable.go | 29 -------- src/build/core/VariableByName.go | 8 +++ src/build/core/VariableInRegister.go | 17 +++++ src/build/core/identifierExists.go | 13 ++++ src/build/core/postInstruction.go | 11 +++ src/build/core/storeVariableInRegister.go | 20 ++++++ src/build/core/useVariable.go | 36 ++++++++++ src/build/scanner/Scanner.go | 2 +- src/build/scanner/scanFile.go | 3 +- src/build/scope/Scope.go | 12 ++++ src/build/scope/Stack.go | 51 ++++++++++++++ src/build/scope/Variable.go | 13 ++++ 37 files changed, 416 insertions(+), 371 deletions(-) create mode 100644 src/build/core/AddVariable.go create mode 100644 src/build/core/Comment.go create mode 100644 src/build/core/CompileAST.go create mode 100644 src/build/core/CompileASTNode.go create mode 100644 src/build/core/CompileTokens.go create mode 100644 src/build/core/JumpIfFalse.go create mode 100644 src/build/core/JumpIfTrue.go create mode 100644 src/build/core/NewFunction.go rename src/build/core/{State.go => PrintInstructions.go} (64%) delete mode 100644 src/build/core/Scope.go delete mode 100644 src/build/core/Variable.go create mode 100644 src/build/core/VariableByName.go create mode 100644 src/build/core/VariableInRegister.go create mode 100644 src/build/core/identifierExists.go create mode 100644 src/build/core/postInstruction.go create mode 100644 src/build/core/storeVariableInRegister.go create mode 100644 src/build/core/useVariable.go create mode 100644 src/build/scope/Scope.go create mode 100644 src/build/scope/Stack.go create mode 100644 src/build/scope/Variable.go diff --git a/src/build/core/AddVariable.go b/src/build/core/AddVariable.go new file mode 100644 index 0000000..522f1df --- /dev/null +++ b/src/build/core/AddVariable.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// AddVariable adds a new variable to the current scope. +func (f *Function) AddVariable(variable *scope.Variable) { + if config.Comments { + f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) + } + + scope := f.CurrentScope() + variable.Scope = scope + scope.Variables[variable.Name] = variable + scope.Use(variable.Register) +} diff --git a/src/build/core/Comment.go b/src/build/core/Comment.go new file mode 100644 index 0000000..d5f0590 --- /dev/null +++ b/src/build/core/Comment.go @@ -0,0 +1,11 @@ +package core + +import ( + "fmt" +) + +// Comment adds a comment to the assembler. +func (f *Function) Comment(format string, args ...any) { + f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.postInstruction() +} diff --git a/src/build/core/Compare.go b/src/build/core/Compare.go index ca19ada..3be29ec 100644 --- a/src/build/core/Compare.go +++ b/src/build/core/Compare.go @@ -14,7 +14,7 @@ func (f *Function) Compare(comparison *expression.Expression) error { if left.IsLeaf() && left.Token.Kind == token.Identifier { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -34,13 +34,13 @@ func (f *Function) Compare(comparison *expression.Expression) error { return f.Execute(comparison.Token, f.cpu.Output[0], right) } - tmp := f.Scope().MustFindFree(f.cpu.General) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) err := f.ExpressionToRegister(left, tmp) if err != nil { return err } - defer f.Scope().Free(tmp) + defer f.CurrentScope().Free(tmp) return f.Execute(comparison.Token, tmp, right) } diff --git a/src/build/core/Compile.go b/src/build/core/Compile.go index c581921..4c2cb94 100644 --- a/src/build/core/Compile.go +++ b/src/build/core/Compile.go @@ -1,11 +1,5 @@ package core -import ( - "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/token" -) - // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) @@ -13,54 +7,3 @@ func (f *Function) Compile() { f.Err = f.CompileTokens(f.Body) f.Return() } - -// CompileTokens compiles a token list. -func (f *Function) CompileTokens(tokens token.List) error { - body, err := ast.Parse(tokens) - - if err != nil { - err.(*errors.Error).File = f.File - return err - } - - return f.CompileAST(body) -} - -// CompileAST compiles an abstract syntax tree. -func (f *Function) CompileAST(tree ast.AST) error { - for _, node := range tree { - err := f.CompileASTNode(node) - - if err != nil { - return err - } - } - - return nil -} - -// CompileASTNode compiles a node in the AST. -func (f *Function) CompileASTNode(node ast.Node) error { - switch node := node.(type) { - case *ast.Assign: - return f.CompileAssign(node) - - case *ast.Call: - return f.CompileCall(node.Expression) - - case *ast.Define: - return f.CompileDefinition(node) - - case *ast.Return: - return f.CompileReturn(node) - - case *ast.If: - return f.CompileIf(node) - - case *ast.Loop: - return f.CompileLoop(node) - - default: - panic("unknown AST type") - } -} diff --git a/src/build/core/CompileAST.go b/src/build/core/CompileAST.go new file mode 100644 index 0000000..a2faba8 --- /dev/null +++ b/src/build/core/CompileAST.go @@ -0,0 +1,18 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileAST compiles an abstract syntax tree. +func (f *Function) CompileAST(tree ast.AST) error { + for _, node := range tree { + err := f.CompileASTNode(node) + + if err != nil { + return err + } + } + + return nil +} diff --git a/src/build/core/CompileASTNode.go b/src/build/core/CompileASTNode.go new file mode 100644 index 0000000..34ce620 --- /dev/null +++ b/src/build/core/CompileASTNode.go @@ -0,0 +1,31 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileASTNode compiles a node in the AST. +func (f *Function) CompileASTNode(node ast.Node) error { + switch node := node.(type) { + case *ast.Assign: + return f.CompileAssign(node) + + case *ast.Call: + return f.CompileCall(node.Expression) + + case *ast.Define: + return f.CompileDefinition(node) + + case *ast.Return: + return f.CompileReturn(node) + + case *ast.If: + return f.CompileIf(node) + + case *ast.Loop: + return f.CompileLoop(node) + + default: + panic("unknown AST type") + } +} diff --git a/src/build/core/CompileAssign.go b/src/build/core/CompileAssign.go index 489b28e..384630d 100644 --- a/src/build/core/CompileAssign.go +++ b/src/build/core/CompileAssign.go @@ -17,7 +17,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.IsLeaf() { name := left.Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Token.Position) @@ -29,7 +29,7 @@ func (f *Function) CompileAssign(node *ast.Assign) error { if left.Token.Kind == token.Operator && left.Token.Text() == "@" { name := left.Children[0].Token.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, left.Children[0].Token.Position) diff --git a/src/build/core/CompileCall.go b/src/build/core/CompileCall.go index 53250d9..4c2adc6 100644 --- a/src/build/core/CompileCall.go +++ b/src/build/core/CompileCall.go @@ -47,7 +47,7 @@ func (f *Function) CompileCall(root *expression.Expression) error { // Push for _, register := range f.cpu.General { - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.PUSH, register) } } @@ -64,14 +64,14 @@ func (f *Function) CompileCall(root *expression.Expression) error { continue } - f.Scope().Free(register) + f.CurrentScope().Free(register) } // Pop for i := len(f.cpu.General) - 1; i >= 0; i-- { register := f.cpu.General[i] - if f.Scope().IsUsed(register) { + if f.CurrentScope().IsUsed(register) { f.Register(asm.POP, register) } } diff --git a/src/build/core/CompileCondition.go b/src/build/core/CompileCondition.go index b257cee..621a7e7 100644 --- a/src/build/core/CompileCondition.go +++ b/src/build/core/CompileCondition.go @@ -3,7 +3,6 @@ package core import ( "fmt" - "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/expression" ) @@ -74,39 +73,3 @@ func (f *Function) CompileCondition(condition *expression.Expression, successLab return err } } - -// JumpIfFalse jumps to the label if the previous comparison was false. -func (f *Function) JumpIfFalse(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JNE, label) - case "!=": - f.Jump(asm.JE, label) - case ">": - f.Jump(asm.JLE, label) - case "<": - f.Jump(asm.JGE, label) - case ">=": - f.Jump(asm.JL, label) - case "<=": - f.Jump(asm.JG, label) - } -} - -// JumpIfTrue jumps to the label if the previous comparison was true. -func (f *Function) JumpIfTrue(operator string, label string) { - switch operator { - case "==": - f.Jump(asm.JE, label) - case "!=": - f.Jump(asm.JNE, label) - case ">": - f.Jump(asm.JG, label) - case "<": - f.Jump(asm.JL, label) - case ">=": - f.Jump(asm.JGE, label) - case "<=": - f.Jump(asm.JLE, label) - } -} diff --git a/src/build/core/CompileDefinition.go b/src/build/core/CompileDefinition.go index bf215a1..a8fa1eb 100644 --- a/src/build/core/CompileDefinition.go +++ b/src/build/core/CompileDefinition.go @@ -2,9 +2,7 @@ package core import ( "git.akyoto.dev/cli/q/src/build/ast" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/errors" - "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/token" ) @@ -24,71 +22,3 @@ func (f *Function) CompileDefinition(node *ast.Define) error { return f.storeVariableInRegister(name, node.Value, uses) } - -func (f *Function) AddVariable(variable *Variable) { - if config.Comments { - f.Comment("%s = %s (%d uses)", variable.Name, variable.Register, variable.Alive) - } - - scope := f.Scope() - variable.Scope = scope - - scope.variables[variable.Name] = variable - scope.Use(variable.Register) -} - -func (f *Function) useVariable(variable *Variable) { - for i, scope := range f.scopes { - if scope.inLoop && variable.Scope != scope { - continue - } - - local := scope.variables[variable.Name] - - if local == nil { - continue - } - - local.Alive-- - - if local.Alive < 0 { - panic("incorrect number of variable use calls") - } - - if local.Alive == 0 { - if config.Comments { - f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) - } - - scope.Free(local.Register) - } else if config.Comments { - f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) - } - } -} - -// identifierExists returns true if the identifier has been defined. -func (f *Function) identifierExists(name string) bool { - variable := f.Variable(name) - - if variable != nil { - return true - } - - _, exists := f.Functions[name] - return exists -} - -func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { - reg := f.Scope().MustFindFree(f.cpu.General) - f.Scope().Reserve(reg) - err := f.ExpressionToRegister(value, reg) - - f.AddVariable(&Variable{ - Name: name, - Register: reg, - Alive: uses, - }) - - return err -} diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go index 4d63a44..1b61c3c 100644 --- a/src/build/core/CompileIf.go +++ b/src/build/core/CompileIf.go @@ -18,9 +18,9 @@ func (f *Function) CompileIf(branch *ast.If) error { } f.AddLabel(success) - f.pushScope(branch.Body) + f.PushScope(branch.Body) err = f.CompileAST(branch.Body) - f.popScope() + f.PopScope() f.AddLabel(fail) return err } diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index c8e8258..de9d8b6 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -12,10 +12,10 @@ func (f *Function) CompileLoop(loop *ast.Loop) error { f.count.loop++ label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) f.AddLabel(label) - scope := f.pushScope(loop.Body) - scope.inLoop = true + scope := f.PushScope(loop.Body) + scope.InLoop = true err := f.CompileAST(loop.Body) f.Jump(asm.JUMP, label) - f.popScope() + f.PopScope() return err } diff --git a/src/build/core/CompileTokens.go b/src/build/core/CompileTokens.go new file mode 100644 index 0000000..7b41804 --- /dev/null +++ b/src/build/core/CompileTokens.go @@ -0,0 +1,19 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// CompileTokens compiles a token list. +func (f *Function) CompileTokens(tokens token.List) error { + body, err := ast.Parse(tokens) + + if err != nil { + err.(*errors.Error).File = f.File + return err + } + + return f.CompileAST(body) +} diff --git a/src/build/core/Execute.go b/src/build/core/Execute.go index 22d687a..f8c8484 100644 --- a/src/build/core/Execute.go +++ b/src/build/core/Execute.go @@ -23,8 +23,8 @@ func (f *Function) Execute(operation token.Token, register cpu.Register, value * return f.ExecuteRegisterRegister(operation, register, f.cpu.Output[0]) } - tmp := f.Scope().MustFindFree(f.cpu.General) - defer f.Scope().Free(tmp) + tmp := f.CurrentScope().MustFindFree(f.cpu.General) + defer f.CurrentScope().Free(tmp) err := f.ExpressionToRegister(value, tmp) diff --git a/src/build/core/ExecuteLeaf.go b/src/build/core/ExecuteLeaf.go index 3b0c5f6..947518d 100644 --- a/src/build/core/ExecuteLeaf.go +++ b/src/build/core/ExecuteLeaf.go @@ -13,7 +13,7 @@ func (f *Function) ExecuteLeaf(operation token.Token, register cpu.Register, ope switch operand.Kind { case token.Identifier: name := operand.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, operand.Position) diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 9de8671..f2b2fa1 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -34,14 +34,14 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp final := register if f.UsesRegister(right, register) { - register = f.Scope().MustFindFree(f.cpu.General) + register = f.CurrentScope().MustFindFree(f.cpu.General) if config.Comments { f.Comment("temporary register %s", register) } } - f.Scope().Reserve(register) + f.CurrentScope().Reserve(register) err := f.ExpressionToRegister(left, register) if err != nil { @@ -52,7 +52,7 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp if register != final { f.RegisterRegister(asm.MOVE, final, register) - f.Scope().Free(register) + f.CurrentScope().Free(register) } return err diff --git a/src/build/core/Function.go b/src/build/core/Function.go index f154692..79e1b31 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -1,52 +1,32 @@ package core import ( - "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/fs" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) // Function represents the smallest unit of code. type Function struct { - Name string - File *fs.File - Body token.List - State + scope.Stack + Name string + File *fs.File + Body token.List + Assembler asm.Assembler + Functions map[string]*Function + Err error + registerHistory []uint64 + finished chan struct{} + cpu cpu.CPU + count counter } -// NewFunction creates a new function. -func NewFunction(name string, file *fs.File, body token.List) *Function { - return &Function{ - Name: name, - File: file, - Body: body, - State: State{ - Assembler: asm.Assembler{ - Instructions: make([]asm.Instruction, 0, 32), - }, - cpu: cpu.CPU{ - All: x64.AllRegisters, - Input: x64.CallRegisters, - General: x64.GeneralRegisters, - Syscall: x64.SyscallRegisters, - Output: x64.ReturnValueRegisters, - }, - scopes: []*Scope{ - {variables: map[string]*Variable{}}, - }, - finished: make(chan struct{}), - }, - } -} - -// String returns the function name. -func (f *Function) String() string { - return f.Name -} - -// Wait will block until the compilation finishes. -func (f *Function) Wait() { - <-f.finished +// counter stores how often a certain statement appeared so we can generate a unique label from it. +type counter struct { + branch int + data int + loop int + subBranch int } diff --git a/src/build/core/Instructions.go b/src/build/core/Instructions.go index 44e8116..e06dcff 100644 --- a/src/build/core/Instructions.go +++ b/src/build/core/Instructions.go @@ -1,10 +1,7 @@ package core import ( - "fmt" - "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/config" "git.akyoto.dev/cli/q/src/build/cpu" ) @@ -15,12 +12,7 @@ func (f *Function) AddLabel(label string) { func (f *Function) Call(label string) { f.Assembler.Call(label) - f.Scope().Use(f.cpu.Output[0]) - f.postInstruction() -} - -func (f *Function) Comment(format string, args ...any) { - f.Assembler.Comment(fmt.Sprintf(format, args...)) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } @@ -38,35 +30,35 @@ func (f *Function) Register(mnemonic asm.Mnemonic, a cpu.Register) { f.Assembler.Register(mnemonic, a) if mnemonic == asm.POP { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterNumber(mnemonic asm.Mnemonic, a cpu.Register, b int) { - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterNumber(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() } func (f *Function) RegisterLabel(mnemonic asm.Mnemonic, register cpu.Register, label string) { - if f.Scope().IsUsed(register) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(register) && isDestructive(mnemonic) { f.SaveRegister(register) } f.Assembler.RegisterLabel(mnemonic, register, label) if mnemonic == asm.MOVE { - f.Scope().Use(register) + f.CurrentScope().Use(register) } f.postInstruction() @@ -77,14 +69,14 @@ func (f *Function) RegisterRegister(mnemonic asm.Mnemonic, a cpu.Register, b cpu return } - if f.Scope().IsUsed(a) && isDestructive(mnemonic) { + if f.CurrentScope().IsUsed(a) && isDestructive(mnemonic) { f.SaveRegister(a) } f.Assembler.RegisterRegister(mnemonic, a, b) if mnemonic == asm.MOVE { - f.Scope().Use(a) + f.CurrentScope().Use(a) } f.postInstruction() @@ -97,18 +89,10 @@ func (f *Function) Return() { func (f *Function) Syscall() { f.Assembler.Syscall() - f.Scope().Use(f.cpu.Output[0]) + f.CurrentScope().Use(f.cpu.Output[0]) f.postInstruction() } -func (f *Function) postInstruction() { - if !config.Assembler { - return - } - - f.registerHistory = append(f.registerHistory, f.Scope().Used) -} - func isDestructive(mnemonic asm.Mnemonic) bool { switch mnemonic { case asm.MOVE, asm.ADD, asm.SUB, asm.MUL, asm.DIV: diff --git a/src/build/core/JumpIfFalse.go b/src/build/core/JumpIfFalse.go new file mode 100644 index 0000000..baedd51 --- /dev/null +++ b/src/build/core/JumpIfFalse.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfFalse jumps to the label if the previous comparison was false. +func (f *Function) JumpIfFalse(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JNE, label) + case "!=": + f.Jump(asm.JE, label) + case ">": + f.Jump(asm.JLE, label) + case "<": + f.Jump(asm.JGE, label) + case ">=": + f.Jump(asm.JL, label) + case "<=": + f.Jump(asm.JG, label) + } +} diff --git a/src/build/core/JumpIfTrue.go b/src/build/core/JumpIfTrue.go new file mode 100644 index 0000000..4c04a0b --- /dev/null +++ b/src/build/core/JumpIfTrue.go @@ -0,0 +1,23 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" +) + +// JumpIfTrue jumps to the label if the previous comparison was true. +func (f *Function) JumpIfTrue(operator string, label string) { + switch operator { + case "==": + f.Jump(asm.JE, label) + case "!=": + f.Jump(asm.JNE, label) + case ">": + f.Jump(asm.JG, label) + case "<": + f.Jump(asm.JL, label) + case ">=": + f.Jump(asm.JGE, label) + case "<=": + f.Jump(asm.JLE, label) + } +} diff --git a/src/build/core/NewFunction.go b/src/build/core/NewFunction.go new file mode 100644 index 0000000..1394c4e --- /dev/null +++ b/src/build/core/NewFunction.go @@ -0,0 +1,35 @@ +package core + +import ( + "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/fs" + "git.akyoto.dev/cli/q/src/build/scope" + "git.akyoto.dev/cli/q/src/build/token" +) + +// NewFunction creates a new function. +func NewFunction(name string, file *fs.File, body token.List) *Function { + return &Function{ + Name: name, + File: file, + Body: body, + Assembler: asm.Assembler{ + Instructions: make([]asm.Instruction, 0, 32), + }, + Stack: scope.Stack{ + Scopes: []*scope.Scope{ + {Variables: map[string]*scope.Variable{}}, + }, + }, + cpu: cpu.CPU{ + All: x64.AllRegisters, + Input: x64.CallRegisters, + General: x64.GeneralRegisters, + Syscall: x64.SyscallRegisters, + Output: x64.ReturnValueRegisters, + }, + finished: make(chan struct{}), + } +} diff --git a/src/build/core/State.go b/src/build/core/PrintInstructions.go similarity index 64% rename from src/build/core/State.go rename to src/build/core/PrintInstructions.go index a75c58c..0c78ba4 100644 --- a/src/build/core/State.go +++ b/src/build/core/PrintInstructions.go @@ -5,35 +5,14 @@ import ( "fmt" "git.akyoto.dev/cli/q/src/build/asm" - "git.akyoto.dev/cli/q/src/build/cpu" "git.akyoto.dev/go/color/ansi" ) -// State is the data structure we embed in each function to preserve compilation State. -type State struct { - Assembler asm.Assembler - Functions map[string]*Function - Err error - scopes []*Scope - registerHistory []uint64 - finished chan struct{} - cpu cpu.CPU - count counter -} - -// counter stores how often a certain statement appeared so we can generate a unique label from it. -type counter struct { - branch int - data int - loop int - subBranch int -} - // PrintInstructions shows the assembly instructions. -func (s *State) PrintInstructions() { +func (f *Function) PrintInstructions() { ansi.Dim.Println("╭──────────────────────────────────────────────────────────────────────────────╮") - for i, x := range s.Assembler.Instructions { + for i, x := range f.Assembler.Instructions { ansi.Dim.Print("│ ") switch x.Mnemonic { @@ -54,9 +33,9 @@ func (s *State) PrintInstructions() { } registers := bytes.Buffer{} - used := s.registerHistory[i] + used := f.registerHistory[i] - for _, reg := range s.cpu.All { + for _, reg := range f.cpu.All { if used&(1< 0 { - lastScope := f.scopes[len(f.scopes)-1] - scope.State = lastScope.State - scope.variables = make(map[string]*Variable, len(lastScope.variables)) - scope.inLoop = lastScope.inLoop - - for k, v := range lastScope.variables { - count := ast.Count(body, token.Identifier, v.Name) - - if count == 0 { - continue - } - - scope.variables[k] = &Variable{ - Name: v.Name, - Register: v.Register, - Alive: count, - } - } - } else { - scope.variables = map[string]*Variable{} - } - - f.scopes = append(f.scopes, scope) - - if config.Comments { - f.Comment("scope %d start", len(f.scopes)) - } - - return scope -} - -// popScope removes the scope at the top of the stack. -func (f *Function) popScope() { - if config.Comments { - f.Comment("scope %d end", len(f.scopes)) - } - - f.scopes = f.scopes[:len(f.scopes)-1] -} diff --git a/src/build/core/TokenToRegister.go b/src/build/core/TokenToRegister.go index 85e8bf9..a40f471 100644 --- a/src/build/core/TokenToRegister.go +++ b/src/build/core/TokenToRegister.go @@ -16,7 +16,7 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { switch t.Kind { case token.Identifier: name := t.Text() - variable := f.Variable(name) + variable := f.VariableByName(name) if variable == nil { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) diff --git a/src/build/core/Variable.go b/src/build/core/Variable.go deleted file mode 100644 index 2045aef..0000000 --- a/src/build/core/Variable.go +++ /dev/null @@ -1,29 +0,0 @@ -package core - -import ( - "git.akyoto.dev/cli/q/src/build/cpu" -) - -// Variable represents a named register. -type Variable struct { - Scope *Scope - Name string - Register cpu.Register - Alive int -} - -// Variable returns the variable with the given name or `nil` if it doesn't exist. -func (s *State) Variable(name string) *Variable { - return s.Scope().variables[name] -} - -// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. -func (s *State) VariableInRegister(register cpu.Register) *Variable { - for _, v := range s.Scope().variables { - if v.Register == register { - return v - } - } - - return nil -} diff --git a/src/build/core/VariableByName.go b/src/build/core/VariableByName.go new file mode 100644 index 0000000..a7b9ff7 --- /dev/null +++ b/src/build/core/VariableByName.go @@ -0,0 +1,8 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/scope" + +// VariableByName returns the variable with the given name or `nil` if it doesn't exist. +func (f *Function) VariableByName(name string) *scope.Variable { + return f.CurrentScope().Variables[name] +} diff --git a/src/build/core/VariableInRegister.go b/src/build/core/VariableInRegister.go new file mode 100644 index 0000000..4666141 --- /dev/null +++ b/src/build/core/VariableInRegister.go @@ -0,0 +1,17 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/scope" +) + +// VariableInRegister returns the variable that occupies the given register or `nil` if none occupy the register. +func (f *Function) VariableInRegister(register cpu.Register) *scope.Variable { + for _, v := range f.CurrentScope().Variables { + if v.Register == register { + return v + } + } + + return nil +} diff --git a/src/build/core/identifierExists.go b/src/build/core/identifierExists.go new file mode 100644 index 0000000..96bd55f --- /dev/null +++ b/src/build/core/identifierExists.go @@ -0,0 +1,13 @@ +package core + +// identifierExists returns true if the identifier has been defined. +func (f *Function) identifierExists(name string) bool { + variable := f.VariableByName(name) + + if variable != nil { + return true + } + + _, exists := f.Functions[name] + return exists +} diff --git a/src/build/core/postInstruction.go b/src/build/core/postInstruction.go new file mode 100644 index 0000000..b4c3d66 --- /dev/null +++ b/src/build/core/postInstruction.go @@ -0,0 +1,11 @@ +package core + +import "git.akyoto.dev/cli/q/src/build/config" + +func (f *Function) postInstruction() { + if !config.Assembler { + return + } + + f.registerHistory = append(f.registerHistory, f.CurrentScope().Used) +} diff --git a/src/build/core/storeVariableInRegister.go b/src/build/core/storeVariableInRegister.go new file mode 100644 index 0000000..7adf578 --- /dev/null +++ b/src/build/core/storeVariableInRegister.go @@ -0,0 +1,20 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/expression" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) storeVariableInRegister(name string, value *expression.Expression, uses int) error { + reg := f.CurrentScope().MustFindFree(f.cpu.General) + f.CurrentScope().Reserve(reg) + err := f.ExpressionToRegister(value, reg) + + f.AddVariable(&scope.Variable{ + Name: name, + Register: reg, + Alive: uses, + }) + + return err +} diff --git a/src/build/core/useVariable.go b/src/build/core/useVariable.go new file mode 100644 index 0000000..8109f20 --- /dev/null +++ b/src/build/core/useVariable.go @@ -0,0 +1,36 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/config" + "git.akyoto.dev/cli/q/src/build/scope" +) + +func (f *Function) useVariable(variable *scope.Variable) { + for i, scope := range f.Scopes { + if scope.InLoop && variable.Scope != scope { + continue + } + + local := scope.Variables[variable.Name] + + if local == nil { + continue + } + + local.Alive-- + + if local.Alive < 0 { + panic("incorrect number of variable use calls") + } + + if local.Alive == 0 { + if config.Comments { + f.Comment("%s (%s) died in scope %d", local.Name, local.Register, i) + } + + scope.Free(local.Register) + } else if config.Comments { + f.Comment("%s (%s) used in scope %d", local.Name, local.Register, i) + } + } +} diff --git a/src/build/scanner/Scanner.go b/src/build/scanner/Scanner.go index ff6ea26..0c829ac 100644 --- a/src/build/scanner/Scanner.go +++ b/src/build/scanner/Scanner.go @@ -10,6 +10,6 @@ import ( type Scanner struct { functions chan *core.Function errors chan error - group sync.WaitGroup queued sync.Map + group sync.WaitGroup } diff --git a/src/build/scanner/scanFile.go b/src/build/scanner/scanFile.go index 7407bd7..b075fc6 100644 --- a/src/build/scanner/scanFile.go +++ b/src/build/scanner/scanFile.go @@ -12,6 +12,7 @@ import ( "git.akyoto.dev/cli/q/src/build/expression" "git.akyoto.dev/cli/q/src/build/fs" "git.akyoto.dev/cli/q/src/build/keyword" + "git.akyoto.dev/cli/q/src/build/scope" "git.akyoto.dev/cli/q/src/build/token" ) @@ -214,7 +215,7 @@ func (s *Scanner) scanFile(path string, pkg string) error { return errors.New(&errors.UnusedVariable{Name: name}, file, tokens[0].Position) } - variable := &core.Variable{ + variable := &scope.Variable{ Name: name, Register: register, Alive: uses, diff --git a/src/build/scope/Scope.go b/src/build/scope/Scope.go new file mode 100644 index 0000000..793bb27 --- /dev/null +++ b/src/build/scope/Scope.go @@ -0,0 +1,12 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Scope represents an independent code block. +type Scope struct { + Variables map[string]*Variable + InLoop bool + cpu.State +} diff --git a/src/build/scope/Stack.go b/src/build/scope/Stack.go new file mode 100644 index 0000000..5f634ff --- /dev/null +++ b/src/build/scope/Stack.go @@ -0,0 +1,51 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/ast" + "git.akyoto.dev/cli/q/src/build/token" +) + +type Stack struct { + Scopes []*Scope +} + +// CurrentScope returns the current scope. +func (stack *Stack) CurrentScope() *Scope { + return stack.Scopes[len(stack.Scopes)-1] +} + +// PopScope removes the scope at the top of the stack. +func (stack *Stack) PopScope() { + stack.Scopes = stack.Scopes[:len(stack.Scopes)-1] +} + +// PushScope pushes a new scope to the top of the stack. +func (stack *Stack) PushScope(body ast.AST) *Scope { + s := &Scope{} + + if len(stack.Scopes) > 0 { + lastScope := stack.Scopes[len(stack.Scopes)-1] + s.State = lastScope.State + s.Variables = make(map[string]*Variable, len(lastScope.Variables)) + s.InLoop = lastScope.InLoop + + for k, v := range lastScope.Variables { + count := ast.Count(body, token.Identifier, v.Name) + + if count == 0 { + continue + } + + s.Variables[k] = &Variable{ + Name: v.Name, + Register: v.Register, + Alive: count, + } + } + } else { + s.Variables = map[string]*Variable{} + } + + stack.Scopes = append(stack.Scopes, s) + return s +} diff --git a/src/build/scope/Variable.go b/src/build/scope/Variable.go new file mode 100644 index 0000000..940d595 --- /dev/null +++ b/src/build/scope/Variable.go @@ -0,0 +1,13 @@ +package scope + +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// Variable represents a named register. +type Variable struct { + Scope *Scope + Name string + Register cpu.Register + Alive int +}