Skip to content

Commit 6185535

Browse files
authored
fix(codegenerator): ensure assignment reads occur before writes #5
Fix Lua edge-case simultaneous assignment ordering by reading all LHS bases/indices before performing any writes. Replace per-lvalue set logic with a recursive multi-assign helper, and remove the README note claiming edge-case assignments may differ.
1 parent fe4aebd commit 6185535

2 files changed

Lines changed: 19 additions & 23 deletions

File tree

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ 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-
- **Edge-Case Assignments:** Simultaneous assignments where the Left-Hand Side depends on a variable changing in the same statement (e.g., [`i, a[i] = i+1, 20`](https://www.lua.org/manual/5.1/manual.html#2.4.3)) are rare, but TLC may evaluate them differently than the standard C implementation.
10099
- **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).
101100

102101
Everything else should work just like standard Lua 5.1!

the-tiny-lua-compiler.lua

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,11 +2415,16 @@ function CodeGenerator:splitTableElements(elements)
24152415
return implicitElems, explicitElems
24162416
end
24172417

2418-
-- Sets the value of a variable or table index from a register.
2419-
function CodeGenerator:setRegisterValue(node, copyFromRegister)
2418+
-- Sets the value of the list of variables or table indices from a range of registers.
2419+
function CodeGenerator:assignValuesToRegisters(nodes, index, copyFromRegister)
2420+
if index > #nodes then return end
2421+
local node = nodes[index]
24202422
local nodeKind = node.kind
24212423

24222424
if nodeKind == "Variable" then
2425+
-- First give the other index assignments a chance to read the base and index before it might change.
2426+
self:assignValuesToRegisters(nodes, index + 1, copyFromRegister + 1)
2427+
24232428
local variableType = node.variableType
24242429
local variableName = node.name
24252430
if variableType == "Local" then
@@ -2443,6 +2448,10 @@ function CodeGenerator:setRegisterValue(node, copyFromRegister)
24432448
local baseRegister = self:processConstantOrExpression(baseNode)
24442449
local indexRegister = self:processConstantOrExpression(indexNode)
24452450

2451+
-- This index assignemnt did read it's base and index.
2452+
-- Now give other index assginments a chance before doing the assignment.
2453+
self:assignValuesToRegisters(nodes, index + 1, copyFromRegister + 1)
2454+
24462455
-- OP_SETTABLE [A, B, C] R(A)[RK(B)] := RK(C)
24472456
self:emitInstruction("SETTABLE", baseRegister, indexRegister, copyFromRegister)
24482457
self:freeIfRegister(baseRegister)
@@ -2454,6 +2463,12 @@ function CodeGenerator:setRegisterValue(node, copyFromRegister)
24542463
error("CodeGenerator: Unsupported lvalue kind in setRegisterValue: " .. nodeKind)
24552464
end
24562465

2466+
-- Sets the value of a variable or table index from a register.
2467+
-- Unused, for compatibility
2468+
function CodeGenerator:setRegisterValue(node, copyFromRegister)
2469+
self:assignValuesToRegisters({node}, 1, copyFromRegister)
2470+
end
2471+
24572472
-- Processes a single page (50 or less) of implicit table elements.
24582473
function CodeGenerator:processTablePage(
24592474
register,
@@ -2800,32 +2815,14 @@ function CodeGenerator:processLocalFunctionDeclaration(node)
28002815
self:processFunction(body, variableRegister)
28012816
end
28022817

2803-
-- NOTE:
2804-
-- According to the Lua 5.1 assignment semantics, when there are variables
2805-
-- used in both sides of the assignment, the right-hand side expressions
2806-
-- should be evaluated first, and then assigned to the left-hand side
2807-
-- variables. This means that this code will incorrectly throw an error
2808-
-- if compiled with our current implementation:
2809-
-- ```lua
2810-
-- local a, b = {}, 2
2811-
-- a[b], b = 10, 20
2812-
-- assert(a[2] == 10 and b == 20)
2813-
-- ```
2814-
-- I haven't found an easy way to implement this behavior yet, so for now,
2815-
-- we will leave it as is.
2816-
--
2817-
-- Reference: https://www.lua.org/manual/5.1/manual.html#2.4.3
28182818
function CodeGenerator:processAssignmentStatement(node)
28192819
local lvalues = node.lvalues
28202820
local expressions = node.expressions
28212821

2822-
local variableBaseRegister = self.stackSize - 1
2822+
local variableBaseRegister = self.stackSize
28232823
local lvalueRegisterCount = self:processExpressionList(expressions, #lvalues)
28242824

2825-
for index, lvalue in ipairs(lvalues) do
2826-
local lvalueRegister = variableBaseRegister + index
2827-
self:setRegisterValue(lvalue, lvalueRegister)
2828-
end
2825+
self:assignValuesToRegisters(lvalues, 1, variableBaseRegister)
28292826

28302827
self:freeRegisters(lvalueRegisterCount)
28312828
end

0 commit comments

Comments
 (0)