Fixed variable lifetime in loops

This commit is contained in:
Eduard Urbach 2024-07-16 20:22:28 +02:00
parent 1825a72f8c
commit f9d72fe490
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
11 changed files with 90 additions and 18 deletions

View File

@ -22,8 +22,12 @@ func (a *Assembler) Call(name string) {
// Return returns back to the caller.
func (a *Assembler) Return() {
if len(a.Instructions) > 0 && a.Instructions[len(a.Instructions)-1].Mnemonic == RETURN {
return
if len(a.Instructions) > 0 {
lastMnemonic := a.Instructions[len(a.Instructions)-1].Mnemonic
if lastMnemonic == RETURN || lastMnemonic == JUMP {
return
}
}
a.Instructions = append(a.Instructions, Instruction{Mnemonic: RETURN})

View File

@ -9,6 +9,10 @@ func Count(body AST, kind token.Kind, name string) int {
for _, node := range body {
switch node := node.(type) {
case *Assign:
if node.Name.Kind == kind && node.Name.Text() == name {
count++
}
count += node.Value.Count(kind, name)
case *Call:
@ -18,7 +22,9 @@ func Count(body AST, kind token.Kind, name string) int {
count += node.Value.Count(kind, name)
case *Return:
count += node.Value.Count(kind, name)
if node.Value != nil {
count += node.Value.Count(kind, name)
}
case *If:
count += node.Condition.Count(kind, name)

View File

@ -30,6 +30,10 @@ func toASTNode(tokens token.List) (Node, error) {
word := tokens[0].Text()
if word == keyword.Return {
if len(tokens) == 1 {
return &Return{}, nil
}
value := expression.Parse(tokens[1:])
return &Return{Value: value}, nil
}

View File

@ -51,6 +51,10 @@ func (f *Function) CompileCall(root *expression.Expression) error {
f.Call(funcName)
}
for _, register := range registers {
f.Scope().Free(register)
}
// Pop
for i := len(f.cpu.General) - 1; i >= 0; i-- {
register := f.cpu.General[i]

View File

@ -44,26 +44,35 @@ func (f *Function) AddVariable(variable *Variable) {
f.Comment("%s = %s (%s, %d uses)", variable.Name, variable.Value, variable.Register, variable.Alive)
}
f.Scope().variables[variable.Name] = variable
f.Scope().Reserve(variable.Register)
f.Scope().Use(variable.Register)
scope := f.Scope()
variable.Scope = scope
scope.variables[variable.Name] = variable
scope.Reserve(variable.Register)
scope.Use(variable.Register)
}
func (f *Function) useVariable(variable *Variable) {
for _, scope := range f.scopes {
for i, scope := range f.scopes {
if scope.inLoop && variable.Scope != scope {
continue
}
local := scope.variables[variable.Name]
if local != nil {
local.Alive--
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 died (%s)", local.Name, local.Register)
f.Comment("%s died (%s) in scope %d", local.Name, local.Register, i)
}
scope.Free(local.Register)

View File

@ -12,9 +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)
f.pushScope(loop.Body)
scope := f.pushScope(loop.Body)
scope.inLoop = true
err := f.CompileAST(loop.Body)
f.popScope()
f.Jump(asm.JUMP, label)
f.popScope()
return err
}

View File

@ -2,6 +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/cpu"
"git.akyoto.dev/cli/q/src/build/token"
)
@ -10,6 +11,7 @@ import (
type Scope struct {
cpu.State
variables map[string]*Variable
inLoop bool
}
// Scope returns the current scope.
@ -18,13 +20,14 @@ func (s *state) Scope() *Scope {
}
// pushScope pushes a new scope to the top of the stack.
func (s *state) pushScope(body ast.AST) {
func (f *Function) pushScope(body ast.AST) *Scope {
scope := &Scope{}
if len(s.scopes) > 0 {
lastScope := s.scopes[len(s.scopes)-1]
if len(f.scopes) > 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)
@ -44,10 +47,20 @@ func (s *state) pushScope(body ast.AST) {
scope.variables = map[string]*Variable{}
}
s.scopes = append(s.scopes, scope)
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 (s *state) popScope() {
s.scopes = s.scopes[:len(s.scopes)-1]
func (f *Function) popScope() {
if config.Comments {
f.Comment("scope %d end", len(f.scopes))
}
f.scopes = f.scopes[:len(f.scopes)-1]
}

View File

@ -11,6 +11,7 @@ type Variable struct {
Name string
Register cpu.Register
Alive int
Scope *Scope
}
// Variable returns the variable with the given name or `nil` if it doesn't exist.

View File

@ -0,0 +1,17 @@
main() {
n := 10
x := 1
loop {
if n == 0 {
return
}
f(x)
n -= 1
}
}
f(x) {
return x
}

11
tests/programs/loop.q Normal file
View File

@ -0,0 +1,11 @@
main() {
n := 10
loop {
if n == 0 {
return
}
n -= 1
}
}

View File

@ -31,6 +31,8 @@ var programs = []struct {
{"branch-or", "", 0},
{"branch-both", "", 0},
{"jump-near", "", 0},
{"loop", "", 0},
{"loop-lifetime", "", 0},
}
func TestPrograms(t *testing.T) {