Skip to content

Commit a5d7538

Browse files
authored
fix: implement support for CLOSE
Add proper `CLOSE` instruction handling in the VM so open upvalues are closed over stack slots when leaving a scope (including via `break`). Update the code generator to track when scopes require closing and emit `CLOSE` during scope exit and break-jump patching.
1 parent f225734 commit a5d7538

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ Because TLC is designed to fit in a single file and be easily understood, we dec
9696

9797
- **Debug Symbols:** We don't strip line numbers or debug info, we never generate them! This drastically simplifies the Tokenizer and Parser.
9898
- **[Constant Folding](https://en.wikipedia.org/wiki/Constant_folding):** Standard Lua converts `local x = 2 + 3` into `local x = 5` at compile time. TLC calculates this at runtime.
99-
- **Unused Opcodes:** We skip `CLOSE` (which may break some code relying on it), `TESTSET` (it's just an optimization), and massive table constructors (over ~25k items).
99+
- **Unused Opcodes:** We skip `TESTSET` (it's just an optimization), and massive table constructors (over ~25k items).
100100

101101
Everything else should work just like standard Lua 5.1!
102102

the-tiny-lua-compiler.lua

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2066,7 +2066,7 @@ function CodeGenerator.new(ast)
20662066
self.currentScope = nil
20672067
self.stackSize = 0
20682068

2069-
self.breakJumpPCs = {}
2069+
self.breakJumpPCs = nil
20702070

20712071
return self
20722072
end
@@ -2078,6 +2078,7 @@ function CodeGenerator:enterScope(isFunctionScope)
20782078
locals = {},
20792079
parentScope = previousScope,
20802080
isFunction = (isFunctionScope and true) or false,
2081+
needClose = false,
20812082
}
20822083

20832084
if previousScope then
@@ -2125,9 +2126,15 @@ function CodeGenerator:leaveScope()
21252126

21262127
table.remove(scopes)
21272128

2129+
local needClose = currentScope.needClose and not currentScope.isFunction
21282130
currentScope = currentScope.parentScope
21292131
self.currentScope = currentScope
21302132
self.stackSize = (currentScope and currentScope.stackSize) or 0
2133+
2134+
if needClose then
2135+
if self.breakJumpPCs then self.breakJumpPCs.needClose = true end
2136+
self:emitInstruction("CLOSE", self.stackSize, 0, 0)
2137+
end
21312138
end
21322139

21332140
--// Variable Management //--
@@ -2175,6 +2182,7 @@ function CodeGenerator:getUpvalueType(variableName)
21752182
local isUpvalue = false
21762183
while scope do
21772184
if scope.locals[variableName] then
2185+
scope.needClose = true
21782186
return (isUpvalue and "Upvalue") or "Local"
21792187
elseif scope.isFunction then
21802188
isUpvalue = true
@@ -2360,6 +2368,9 @@ end
23602368
function CodeGenerator:patchBreakJumpsToHere()
23612369
if not self.breakJumpPCs then return end
23622370
self:patchJumpsToHere(self.breakJumpPCs)
2371+
if #self.breakJumpPCs > 0 and self.breakJumpPCs.needClose then
2372+
self:emitInstruction("CLOSE", self.stackSize, 0, 0)
2373+
end
23632374
self.breakJumpPCs = nil
23642375
end
23652376

@@ -2371,10 +2382,7 @@ function CodeGenerator:breakable(callback)
23712382
callback()
23722383
self:patchBreakJumpsToHere()
23732384

2374-
local breakJumpPCs = self.breakJumpPCs
23752385
self.breakJumpPCs = previousBreakJumpPCs
2376-
2377-
return breakJumpPCs
23782386
end
23792387

23802388
--// Auxiliary/Helper Methods //--
@@ -2969,6 +2977,11 @@ function CodeGenerator:processRepeatStatement(node)
29692977
self:processStatementList(body.statements)
29702978
local conditionRegister = self:processExpressionNode(condition)
29712979

2980+
if self.currentScope.needClose then
2981+
self:emitInstruction("CLOSE", self.currentScope.parentScope.stackSize, 0, 0)
2982+
self.currentScope.needClose = false
2983+
end
2984+
29722985
-- OP_TEST [A, C] if not (R(A) <=> C) then pc++
29732986
self:emitInstruction("TEST", conditionRegister, 0, 0)
29742987
self:emitJumpBack(loopStartPC)
@@ -3108,6 +3121,8 @@ function CodeGenerator:processBlockNode(blockNode)
31083121
end
31093122

31103123
function CodeGenerator:processFunctionBody(node)
3124+
local previousBreakJumpPCs = self.breakJumpPCs
3125+
self.breakJumpPCs = nil
31113126
self:enterScope(true)
31123127
for _, paramName in ipairs(node.parameters) do
31133128
self:declareLocalVariable(paramName)
@@ -3123,6 +3138,7 @@ function CodeGenerator:processFunctionBody(node)
31233138
-- OP_RETURN [A, B] return R(A), ... ,R(A+B-2)
31243139
self:emitInstruction("RETURN", 0, 1, 0)
31253140
self:leaveScope()
3141+
self.breakJumpPCs = previousBreakJumpPCs
31263142
end
31273143

31283144
-- Adds `proto` prototype to the `self.proto.protos` list and generates
@@ -3769,6 +3785,8 @@ function VirtualMachine:executeClosure(...)
37693785

37703786
local maxStackSize = proto.maxStackSize
37713787
local top = maxStackSize
3788+
local upvalueStack = {}
3789+
local maxUpvalue = -1
37723790

37733791
-- Gets a value from either the stack or constants table.
37743792
-- NOTE: Constant indices are represented as negative numbers
@@ -4162,7 +4180,15 @@ function VirtualMachine:executeClosure(...)
41624180

41634181
-- OP_VARARG [A] close all variables in the stack up to (>=) R(A)
41644182
elseif opcode == "CLOSE" then
4165-
-- Stub. No implementation needed for this VM.
4183+
for i = a, maxUpvalue do
4184+
local uv = upvalueStack[i]
4185+
if uv then
4186+
uv.stack = { stack[uv.index] }
4187+
uv.index = 1
4188+
upvalueStack[i] = nil
4189+
end
4190+
end
4191+
maxUpvalue = a - 1
41664192

41674193
-- OP_CLOSURE [A, Bx] R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))
41684194
-- Create a new closure (function) and store it in a register.
@@ -4178,17 +4204,18 @@ function VirtualMachine:executeClosure(...)
41784204

41794205
local pseudoInstruction = code[pc]
41804206
local opname = pseudoInstruction[1]
4207+
local index = pseudoInstruction[3]
41814208
if opname == "MOVE" then
4182-
table.insert(tProtoUpvalues, {
4209+
local upvalue = upvalueStack[index] or {
4210+
index = index,
41834211
stack = stack,
4184-
index = pseudoInstruction[3],
4185-
})
4212+
}
4213+
upvalueStack[index] = upvalue
4214+
if index > maxUpvalue then maxUpvalue = index end
4215+
table.insert(tProtoUpvalues, upvalue)
41864216
elseif opname == "GETUPVAL" then
4187-
local upvalue = upvalues[pseudoInstruction[3] + 1]
4188-
table.insert(tProtoUpvalues, {
4189-
stack = upvalue.stack,
4190-
index = upvalue.index,
4191-
})
4217+
local upvalue = upvalues[index + 1]
4218+
table.insert(tProtoUpvalues, upvalue)
41924219
else
41934220
error("Unexpected instruction while capturing upvalues: " .. tostring(opname))
41944221
end

0 commit comments

Comments
 (0)