Skip to content

Commit 75dd08c

Browse files
authored
Emit less garbage without opts, if it has no side effects (#1141)
1 parent 03acf56 commit 75dd08c

7 files changed

Lines changed: 546 additions & 7 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,11 @@ public JassProg transformProgToJass() {
563563
// translate flattened intermediate lang to jass:
564564

565565
beginPhase(14, "translate to jass");
566+
optimizer.removeGarbage();
567+
imProg.flatten(imTranslator);
568+
imTranslator.removeEmptyPackageInits();
569+
optimizer.removeGarbage();
570+
imProg.flatten(imTranslator);
566571
getImTranslator().calculateCallRelationsAndReadVariables();
567572
ImToJassTranslator translator =
568573
new ImToJassTranslator(getImProg(), getImTranslator().getCalledFunctions(), getImTranslator().getMainFunc(), getImTranslator().getConfFunc());

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
99

1010
import java.util.Collection;
11+
import java.util.HashMap;
1112
import java.util.LinkedHashSet;
13+
import java.util.Map;
1214
import java.util.Set;
15+
import java.util.function.Predicate;
1316
import java.util.stream.Collectors;
1417
import java.util.stream.Stream;
1518

@@ -455,4 +458,123 @@ public boolean hasSideEffects(Element elem) {
455458
Set<ImVar> imVars = directlySetVariables(elem);
456459
return natives.size() + directFuncs.size() + imVars.size() > 0;
457460
}
461+
462+
/**
463+
* Checks if the given element has observable side effects.
464+
* Pure natives can be configured via the predicate.
465+
*/
466+
public boolean hasObservableSideEffects(Element elem, Predicate<ImFunction> isNativeWithoutSideEffects) {
467+
return new ObservableSideEffectChecker(isNativeWithoutSideEffects).hasSideEffects(elem);
468+
}
469+
470+
private final class ObservableSideEffectChecker {
471+
private final Predicate<ImFunction> isNativeWithoutSideEffects;
472+
private final Map<ImFunction, Boolean> cache = new HashMap<>();
473+
private final Set<ImFunction> inProgress = new LinkedHashSet<>();
474+
475+
private ObservableSideEffectChecker(Predicate<ImFunction> isNativeWithoutSideEffects) {
476+
this.isNativeWithoutSideEffects = isNativeWithoutSideEffects;
477+
}
478+
479+
private boolean hasSideEffects(Element elem) {
480+
if (!directlySetVariables(elem).isEmpty()) {
481+
return true;
482+
}
483+
for (ImFunction nativeFunc : calledNatives(elem)) {
484+
if (!isNativeWithoutSideEffects.test(nativeFunc)) {
485+
return true;
486+
}
487+
}
488+
for (ImFunction called : calledFunctions(elem)) {
489+
if (functionHasSideEffects(called)) {
490+
return true;
491+
}
492+
}
493+
return false;
494+
}
495+
496+
private boolean functionHasSideEffects(ImFunction func) {
497+
Boolean cached = cache.get(func);
498+
if (cached != null) {
499+
return cached;
500+
}
501+
if (func.isNative()) {
502+
boolean sideEffect = !isNativeWithoutSideEffects.test(func);
503+
cache.put(func, sideEffect);
504+
return sideEffect;
505+
}
506+
if (func.isExtern()) {
507+
// Extern implementations are not represented in IM and may have runtime effects.
508+
cache.put(func, true);
509+
return true;
510+
}
511+
if (!inProgress.add(func)) {
512+
return true;
513+
}
514+
boolean sideEffect = hasGlobalSideEffects(func.getBody());
515+
inProgress.remove(func);
516+
cache.put(func, sideEffect);
517+
return sideEffect;
518+
}
519+
520+
private boolean hasGlobalSideEffects(Element elem) {
521+
if (hasMemberWrites(elem)) {
522+
return true;
523+
}
524+
for (ImVar var : directlySetVariables(elem)) {
525+
// Some optimization passes temporarily detach vars; in that state isGlobal() throws.
526+
if (isAttachedGlobal(var)) {
527+
return true;
528+
}
529+
}
530+
for (ImFunction nativeFunc : calledNatives(elem)) {
531+
if (!isNativeWithoutSideEffects.test(nativeFunc)) {
532+
return true;
533+
}
534+
}
535+
for (ImFunction called : calledFunctions(elem)) {
536+
if (functionHasSideEffects(called)) {
537+
return true;
538+
}
539+
}
540+
return false;
541+
}
542+
543+
private boolean hasMemberWrites(Element elem) {
544+
final boolean[] foundMemberWrite = {false};
545+
elem.accept(new ImStmt.DefaultVisitor() {
546+
@Override
547+
public void visit(ImSet set) {
548+
super.visit(set);
549+
if (isMemberWrite(set.getLeft())) {
550+
foundMemberWrite[0] = true;
551+
}
552+
}
553+
554+
private boolean isMemberWrite(ImLExpr left) {
555+
if (left instanceof ImMemberAccess) {
556+
return true;
557+
}
558+
if (left instanceof ImTupleSelection) {
559+
ImExpr tupleExpr = ((ImTupleSelection) left).getTupleExpr();
560+
return tupleExpr instanceof ImLExpr && isMemberWrite((ImLExpr) tupleExpr);
561+
}
562+
if (left instanceof ImTupleExpr) {
563+
for (ImExpr expr : ((ImTupleExpr) left).getExprs()) {
564+
if (expr instanceof ImLExpr && isMemberWrite((ImLExpr) expr)) {
565+
return true;
566+
}
567+
}
568+
}
569+
return false;
570+
}
571+
});
572+
return foundMemberWrite[0];
573+
}
574+
575+
private boolean isAttachedGlobal(ImVar var) {
576+
Element parent = var.getParent();
577+
return parent != null && parent.getParent() instanceof ImProg;
578+
}
579+
}
458580
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import com.google.common.collect.Lists;
44
import de.peeeq.wurstio.TimeTaker;
5+
import de.peeeq.wurstscript.WurstOperator;
56
import de.peeeq.wurstscript.WLogger;
67
import de.peeeq.wurstscript.intermediatelang.optimizer.BranchMerger;
78
import de.peeeq.wurstscript.intermediatelang.optimizer.ConstantAndCopyPropagation;
89
import de.peeeq.wurstscript.intermediatelang.optimizer.LocalMerger;
10+
import de.peeeq.wurstscript.intermediatelang.optimizer.SideEffectAnalyzer;
911
import de.peeeq.wurstscript.intermediatelang.optimizer.SimpleRewrites;
1012
import de.peeeq.wurstscript.jassIm.*;
1113
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
@@ -102,6 +104,7 @@ public boolean removeGarbage() {
102104
trans.calculateCallRelationsAndReadVariables();
103105
final Set<ImVar> readVars = trans.getReadVariables();
104106
final Set<ImFunction> usedFuncs = trans.getUsedFunctions();
107+
SideEffectAnalyzer sideEffectAnalyzer = new SideEffectAnalyzer(prog);
105108

106109
// keep only used variables
107110
int globalsBefore = prog.getGlobals().size();
@@ -142,25 +145,30 @@ public void visit(ImSet e) {
142145
if (e.getLeft() instanceof ImVarAccess) {
143146
ImVarAccess va = (ImVarAccess) e.getLeft();
144147
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
145-
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
148+
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
149+
replacements.add(Pair.create(e, sideEffects));
146150
}
147151
} else if (e.getLeft() instanceof ImVarArrayAccess) {
148152
ImVarArrayAccess va = (ImVarArrayAccess) e.getLeft();
149153
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
150-
// IMPORTANT: removeAll() clears parent references
151-
List<ImExpr> exprs = va.getIndexes().removeAll();
152-
exprs.add(e.getRight());
154+
List<ImExpr> exprs = new ArrayList<>();
155+
for (ImExpr index : va.getIndexes()) {
156+
exprs.addAll(collectSideEffects(index, sideEffectAnalyzer));
157+
}
158+
exprs.addAll(collectSideEffects(e.getRight(), sideEffectAnalyzer));
153159
replacements.add(Pair.create(e, exprs));
154160
}
155161
} else if (e.getLeft() instanceof ImTupleSelection) {
156162
ImVar var = TypesHelper.getTupleVar((ImTupleSelection) e.getLeft());
157163
if(var != null && !readVars.contains(var) && !TRVEHelper.protectedVariables.contains(var.getName())) {
158-
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
164+
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
165+
replacements.add(Pair.create(e, sideEffects));
159166
}
160167
} else if(e.getLeft() instanceof ImMemberAccess) {
161168
ImMemberAccess va = ((ImMemberAccess) e.getLeft());
162169
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
163-
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
170+
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
171+
replacements.add(Pair.create(e, sideEffects));
164172
}
165173
}
166174
}
@@ -170,7 +178,9 @@ public void visit(ImSet e) {
170178
for (Pair<ImStmt, List<ImExpr>> pair : replacements) {
171179
changes = true;
172180
ImExpr r;
173-
if (pair.getB().size() == 1) {
181+
if (pair.getB().isEmpty()) {
182+
r = ImHelper.statementExprVoid(JassIm.ImStmts());
183+
} else if (pair.getB().size() == 1) {
174184
r = pair.getB().get(0);
175185
// CRITICAL: Clear parent before reusing the node
176186
r.setParent(null);
@@ -194,4 +204,76 @@ public void visit(ImSet e) {
194204
}
195205
return anyChanges;
196206
}
207+
208+
private List<ImExpr> collectSideEffects(ImExpr expr, SideEffectAnalyzer analyzer) {
209+
if (expr == null) {
210+
return Collections.emptyList();
211+
}
212+
if (mayTrapAtRuntime(expr)) {
213+
return Collections.singletonList(expr);
214+
}
215+
if (analyzer.hasObservableSideEffects(expr, func -> func.isNative()
216+
&& UselessFunctionCallsRemover.isFunctionWithoutSideEffect(func.getName()))) {
217+
return Collections.singletonList(expr);
218+
}
219+
return Collections.emptyList();
220+
}
221+
222+
private boolean mayTrapAtRuntime(Element elem) {
223+
return mayTrapAtRuntime(elem, new HashMap<>(), new LinkedHashSet<>());
224+
}
225+
226+
private boolean mayTrapAtRuntime(Element elem, Map<ImFunction, Boolean> functionCache, Set<ImFunction> inProgress) {
227+
if (elem instanceof ImFunctionCall) {
228+
ImFunction calledFunc = ((ImFunctionCall) elem).getFunc();
229+
if (functionMayTrapAtRuntime(calledFunc, functionCache, inProgress)) {
230+
return true;
231+
}
232+
} else if (elem instanceof ImMethodCall) {
233+
ImFunction calledFunc = ((ImMethodCall) elem).getMethod().getImplementation();
234+
if (calledFunc == null || functionMayTrapAtRuntime(calledFunc, functionCache, inProgress)) {
235+
return true;
236+
}
237+
}
238+
239+
if (elem instanceof ImOperatorCall) {
240+
ImOperatorCall opCall = (ImOperatorCall) elem;
241+
WurstOperator op = opCall.getOp();
242+
if ((op == WurstOperator.DIV_INT || op == WurstOperator.MOD_INT) && opCall.getArguments().size() >= 2) {
243+
ImExpr denominator = opCall.getArguments().get(1);
244+
// Preserve integer div/mod unless denominator is provably non-zero.
245+
if (!(denominator instanceof ImIntVal) || ((ImIntVal) denominator).getValI() == 0) {
246+
return true;
247+
}
248+
}
249+
}
250+
for (int i = 0; i < elem.size(); i++) {
251+
Element child = elem.get(i);
252+
if (mayTrapAtRuntime(child, functionCache, inProgress)) {
253+
return true;
254+
}
255+
}
256+
return false;
257+
}
258+
259+
private boolean functionMayTrapAtRuntime(ImFunction function, Map<ImFunction, Boolean> functionCache, Set<ImFunction> inProgress) {
260+
if (function.isNative()) {
261+
return false;
262+
}
263+
264+
Boolean cachedResult = functionCache.get(function);
265+
if (cachedResult != null) {
266+
return cachedResult;
267+
}
268+
269+
if (!inProgress.add(function)) {
270+
// Recursive cycles are conservatively treated as potentially trapping.
271+
return true;
272+
}
273+
274+
boolean mayTrap = mayTrapAtRuntime(function.getBody(), functionCache, inProgress);
275+
inProgress.remove(function);
276+
functionCache.put(function, mayTrap);
277+
return mayTrap;
278+
}
197279
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,29 @@ private static ImStatementExpr inSet(ImSet imSet, ImFunction f) {
455455
+ "\nLHS=" + left + "\nRHS=" + right);
456456
}
457457

458+
boolean allLiteral = true;
459+
for (ImExpr r : rhsLeaves) {
460+
if (!isSimpleLiteral(r)) {
461+
allLiteral = false;
462+
break;
463+
}
464+
}
465+
466+
if (allLiteral) {
467+
for (int i = 0; i < lhsLeaves.size(); i++) {
468+
ImLExpr l = lhsLeaves.get(i);
469+
ImType targetT = l.attrTyp();
470+
ImExpr r = rhsLeaves.get(i);
471+
if (r instanceof ImNull) {
472+
r = ImHelper.defaultValueForComplexType(targetT);
473+
}
474+
l.setParent(null);
475+
r.setParent(null);
476+
stmts.add(JassIm.ImSet(imSet.getTrace(), l, r));
477+
}
478+
return ImHelper.statementExprVoid(stmts);
479+
}
480+
458481
// 4) Evaluate RHS leaves first into temps (preserve side-effect order & alias safety)
459482
List<ImVar> temps = new ArrayList<>(rhsLeaves.size());
460483
for (int i = 0; i < rhsLeaves.size(); i++) {
@@ -485,6 +508,14 @@ private static ImStatementExpr inSet(ImSet imSet, ImFunction f) {
485508
return ImHelper.statementExprVoid(stmts);
486509
}
487510

511+
private static boolean isSimpleLiteral(ImExpr expr) {
512+
return expr instanceof ImBoolVal
513+
|| expr instanceof ImIntVal
514+
|| expr instanceof ImRealVal
515+
|| expr instanceof ImStringVal
516+
|| expr instanceof ImNull;
517+
}
518+
488519
/** Flatten LHS recursively into addressable leaves (ImLExpr), hoisting side-effects */
489520
private static void flattenLhsTuple(ImExpr e, List<ImLExpr> out, ImStmts sideStmts) {
490521
ImExpr x = extractSideEffect(e, sideStmts);

0 commit comments

Comments
 (0)