From 16983b0d718189400bbd0ec4ecb47f273c3bd1a1 Mon Sep 17 00:00:00 2001 From: Frotty Date: Thu, 3 Sep 2026 10:31:20 +0200 Subject: [PATCH] Elide total unused Lua tuple components --- .../imtranslation/EliminateTuples.java | 30 +++++++++++--- .../tests/LuaBackendAuditTests.java | 40 +++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateTuples.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateTuples.java index 647e2e369..287a798d1 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateTuples.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateTuples.java @@ -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)) { @@ -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; + }; } /** @@ -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)); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java index c338b692c..00fbd7f68 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java @@ -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(