Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ private static void retainDiscardedValue(ImExpr value, ImStmts stmts, ImTranslat
}
return;
}
if (isTriviallyDiscardable(value)) {
if (isSafelyDiscardable(value)) {
return;
}
if (SideEffectAnalyzer.quickcheckHasSideeffects(value)) {
Expand All @@ -284,14 +284,34 @@ private static void retainDiscardedValue(ImExpr value, ImStmts stmts, ImTranslat
}
}

private static boolean isTriviallyDiscardable(ImExpr value) {
return value instanceof ImBoolVal
private static boolean isSafelyDiscardable(ImExpr value) {
if (value instanceof ImBoolVal
|| value instanceof ImIntVal
|| value instanceof ImRealVal
|| value instanceof ImStringVal
|| value instanceof ImNull
|| value instanceof ImVarAccess
|| value instanceof ImFuncRef;
|| value instanceof ImFuncRef) {
return true;
}
if (value instanceof ImOperatorCall operatorCall
&& isTotalOperator(operatorCall.getOp())) {
return operatorCall.getArguments().stream()
.allMatch(EliminateTuples::isSafelyDiscardable);
}
return false;
}

/**
* Operators other than integer division and modulo are total for well-typed scalar IM values.
* Keeping their unused results in a Lua discard sink only repeats pure scalar work and prevents
* tuple-component DCE. Division and modulo stay conservative because a zero divisor can fail.
*/
private static boolean isTotalOperator(WurstOperator operator) {
return switch (operator) {
case DIV_INT, MOD_INT, MOD_REAL, JASS_MOD_INT -> false;
default -> true;
};
}

/**
Expand Down Expand Up @@ -1006,7 +1026,7 @@ private static void lowerBundleInto(ImExpr expression, ImFunction f, boolean cap

value.setParent(null);
boolean requiresEagerEvaluation = capturePotentiallyFailingValues
&& !isTriviallyDiscardable(value)
&& !isSafelyDiscardable(value)
&& !SideEffectAnalyzer.quickcheckHasSideeffects(value);
if ((capture || requiresEagerEvaluation) && !isImmutableValue(value)) {
result.values.add(captureValue(value, temporaryName, result.prelude, f));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,46 @@ public void tupleSelectionPreservesLeftToRightEvaluation() throws IOException {
assertFalse(compiled.contains("tupleCopy"));
}

@Test
public void unusedTotalTupleComponentDoesNotSurviveLuaLowering() throws IOException {
test().testLua(true).executeProg().lines(
"package Test",
"native testSuccess()",
"tuple pair(int x, int y)",
"function selectFirst(int left, int right) returns int",
" return pair(left + 1, right + 2).x",
"init",
" if selectFirst(4, 10) == 5",
" testSuccess()"
);

String compiled = compiledLua("unusedTotalTupleComponentDoesNotSurviveLuaLowering");
assertFalse("unused total tuple components should not require a Lua discard call",
compiled.contains("__wurst_tuple_discard_"));
assertFalse("unused total tuple components should not be evaluated",
compiled.contains("right + 2"));
}

@Test
public void unreadTupleReturnSlotsAreRemoved() throws IOException {
test().testLua(true).executeProg().lines(
"package Test",
"native testSuccess()",
"tuple pair(int x, int y)",
"@noinline function makePair(int left, int right) returns pair",
" return pair(left + 1, right + 2)",
"init",
" if makePair(4, 10).x == 5",
" testSuccess()"
);

String compiled = compiledLua("unreadTupleReturnSlotsAreRemoved");
assertFalse("unread scalar return slots should be removed after tuple lowering",
compiled.contains("makePair_return_y"));
assertFalse("the producer for an unread scalar return slot should be removed",
compiled.contains("right + 2"));
}

@Test
public void discardedTupleComponentsThatCanFailAreStillEvaluated() {
String compiled = compileLuaWithRunArgs(
Expand Down
Loading