Skip to content

Commit 4d6322b

Browse files
authored
Preserve Jass return paths after non-exiting loops (#1586)
1 parent 168b9a9 commit 4d6322b

3 files changed

Lines changed: 57 additions & 4 deletions

File tree

‎de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/ControlFlowGraph.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,16 @@ public static final class Node {
4040
private final Reference2ObjectOpenHashMap<ImVarargLoop, Node> varargLoopEnd = new Reference2ObjectOpenHashMap<>();
4141
private final ObjectArrayList<Node> nodeList = new ObjectArrayList<>();
4242
private final ImStmts body;
43+
private final boolean loopsMayFallThrough;
4344

4445
public ControlFlowGraph(ImStmts stmts) {
46+
this(stmts, false);
47+
}
48+
49+
/** Conservative loop exits for Jass liveness: its validator requires returns after non-exiting loops too. */
50+
public ControlFlowGraph(ImStmts stmts, boolean loopsMayFallThrough) {
4551
this.body = stmts;
52+
this.loopsMayFallThrough = loopsMayFallThrough;
4653
// a light hint helps the first growth step avoid rehash
4754
nodes.trim(0);
4855
buildCfg(stmts);
@@ -120,6 +127,7 @@ private void buildCfg(ImStmts stmts) {
120127
buildCfg(body);
121128
if (!body.isEmpty()) addSuccessor(current, getNode(body.get(0)));
122129
Node endloopNode = getEndloopNode(imLoop);
130+
if (loopsMayFallThrough) addSuccessor(current, endloopNode);
123131
nodeList.add(endloopNode);
124132
addAllSuccessors(endloopNode, getSuccessorList(imLoop, i));
125133
} else if (s instanceof ImVarargLoop l) {

‎de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/LocalMerger.java‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@ private void optimizeFunctions(List<ImFunction> functions) {
5151

5252
void optimizeFunc(ImFunction func) {
5353
LivenessAnalysis liveness = analyzeLiveness(func);
54-
// The liveness is that of the code which a path reaches: a local which only code no path reaches reads (after
55-
// a loop which nothing leaves, say) is live nowhere, and eliminateDeadCode removes the assignments to it. That
56-
// code goes too, so that no read is left with no assignment before it, which pjass rejects.
54+
// Remove unreachable reads together with their assignments. On Jass the graph conservatively lets loops
55+
// fall through: pjass requires a return after a non-exiting loop, and its operands must stay initialised.
5756
List<ImStmt> unreachable = liveness.cfg.unreachableStatements();
5857
if (!unreachable.isEmpty()) {
5958
for (ImStmt s : unreachable) {
@@ -399,7 +398,9 @@ public Map<ImStmt, Set<ImVar>> calculateLiveness(ImFunction func) {
399398

400399
private LivenessAnalysis analyzeLiveness(ImFunction func) {
401400
// 1. Build Control Flow Graph
402-
ControlFlowGraph cfg = new ControlFlowGraph(func.getBody());
401+
boolean needsJassReturn = translator != null && !translator.isLuaTarget()
402+
&& !(func.getReturnType() instanceof ImVoid);
403+
ControlFlowGraph cfg = new ControlFlowGraph(func.getBody(), needsJassReturn);
403404
final List<Node> nodes = cfg.getNodes();
404405
final int N = nodes.size();
405406

‎de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2725,6 +2725,50 @@ public void garbageRemovalAnalysesAgainWhenABlizzardGlobalLosesItsLastRead() {
27252725
" testSuccess()"));
27262726
}
27272727

2728+
/** Issue #1577: pjass requires a trailing return even when the loop only exits by returning. */
2729+
@Test
2730+
public void localOptimizationsKeepJassReturnAfterNonExitingLoop() {
2731+
for (boolean lua : new boolean[]{false, true}) {
2732+
TestConfig config = test().localOptimizations().executeProg();
2733+
if (lua) config.testLua(true);
2734+
config.compilationUnits(
2735+
compilationUnit("returningLoop.j",
2736+
"function returningLoop takes integer n returns integer",
2737+
" local integer result = n + 7",
2738+
" loop",
2739+
" if n > 0 then",
2740+
" return result",
2741+
" endif",
2742+
" set n = n + 1",
2743+
" endloop",
2744+
" return result",
2745+
"endfunction",
2746+
"function nestedReturningLoop takes integer n returns string",
2747+
" local string result = \"done\"",
2748+
" if n >= 0 then",
2749+
" loop",
2750+
" loop",
2751+
" exitwhen n > 0",
2752+
" set n = n + 1",
2753+
" endloop",
2754+
" if n > 0 then",
2755+
" return result",
2756+
" endif",
2757+
" endloop",
2758+
" else",
2759+
" return \"negative\"",
2760+
" endif",
2761+
" return result",
2762+
"endfunction"),
2763+
compilationUnit("test.wurst",
2764+
"package test",
2765+
"native testSuccess()",
2766+
"init",
2767+
" if returningLoop(0) == 7 and nestedReturningLoop(0) == \"done\" and nestedReturningLoop(-1) == \"negative\"",
2768+
" testSuccess()"));
2769+
}
2770+
}
2771+
27282772
/**
27292773
* The only read of `a` is after a loop which always returns, so it is never reached. The optimisations do not
27302774
* leave that read with no assignment before it, which pjass rejects.

0 commit comments

Comments
 (0)