Skip to content

Commit c96e370

Browse files
committed
Stage tuple return components before publishing
1 parent ad994db commit c96e370

2 files changed

Lines changed: 38 additions & 8 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateTuples.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -840,23 +840,32 @@ private static ImStatementExpr inReturn(ImReturn parent, ImTupleExpr tupleExpr,
840840
"Cannot return tuple with " + flatExprs.size() + " element(s) from function expecting " + returnVars.size() + " element(s)");
841841
}
842842

843-
// 2) Assign per component, converting nulls to proper defaults of LHS type
843+
// 2) Capture every component before publishing any shared return slot. A later
844+
// component can call this function (or a sibling in its dispatch group) and write
845+
// the same slots, so assigning slots while components are still being evaluated
846+
// would corrupt the outer result.
847+
List<ImVar> staged = new ArrayList<>(returnVars.size());
844848
for (int i = 0; i < returnVars.size(); i++) {
845849
ImVar rv = returnVars.get(i);
846850
ImExpr rhs = flatExprs.get(i);
847851
rhs.setParent(null);
848852

849853
if (rhs instanceof ImNull) {
850-
// Use the *component target type* to build the correct default (0 for ints,
851-
// (0,0) for tuple components if those ever occur, etc)
852-
ImExpr defaultRhs = ImHelper.defaultValueForComplexType(rv.getType());
853-
stmts.add(JassIm.ImSet(parent.getTrace(), JassIm.ImVarAccess(rv), defaultRhs));
854-
} else {
855-
stmts.add(JassIm.ImSet(parent.getTrace(), JassIm.ImVarAccess(rv), rhs));
854+
rhs = ImHelper.defaultValueForComplexType(rv.getType());
856855
}
856+
ImVar temp = JassIm.ImVar(rhs.attrTrace(), rv.getType(), "tuple_return", false);
857+
f.getLocals().add(temp);
858+
stmts.add(JassIm.ImSet(parent.getTrace(), JassIm.ImVarAccess(temp), rhs));
859+
staged.add(temp);
860+
}
861+
862+
// 3) Publish the complete value only after all potentially re-entrant evaluation.
863+
for (int i = 0; i < returnVars.size(); i++) {
864+
stmts.add(JassIm.ImSet(parent.getTrace(), JassIm.ImVarAccess(returnVars.get(i)),
865+
JassIm.ImVarAccess(staged.get(i))));
857866
}
858867

859-
// 3) Return the first component temp
868+
// 4) Return the first component slot
860869
stmts.add(JassIm.ImReturn(parent.getTrace(), JassIm.ImVarAccess(returnVars.get(0))));
861870
return ImHelper.statementExprVoid(stmts);
862871
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,27 @@ public void selectingLaterTupleComponentStillInvokesProducer() throws IOExceptio
191191
assertFalse(compiled.contains("tupleCopy"));
192192
}
193193

194+
@Test
195+
public void tupleReturnStagesComponentsBeforeRecursiveSlotWrites() throws IOException {
196+
test().testLua(true).executeProg().lines(
197+
"package Test",
198+
"native testSuccess()",
199+
"tuple pair(int x, int y)",
200+
"class Producer",
201+
" @noinline function produce(int seed) returns pair",
202+
" if seed == 0",
203+
" return pair(7, produce(1).x)",
204+
" return pair(seed, 99)",
205+
"init",
206+
" let result = new Producer().produce(0)",
207+
" if result == pair(7, 1)",
208+
" testSuccess()"
209+
);
210+
211+
String compiled = compiledLua("tupleReturnStagesComponentsBeforeRecursiveSlotWrites");
212+
assertFalse(compiled.contains("tupleCopy"));
213+
}
214+
194215
@Test
195216
public void compiletimeGenericArrayReplayLeavesAreSplit() {
196217
String compiled = compileLuaWithRunArgs(

0 commit comments

Comments
 (0)