22
33import com .google .common .collect .Lists ;
44import de .peeeq .wurstio .TimeTaker ;
5+ import de .peeeq .wurstscript .WurstOperator ;
56import de .peeeq .wurstscript .WLogger ;
67import de .peeeq .wurstscript .intermediatelang .optimizer .BranchMerger ;
78import de .peeeq .wurstscript .intermediatelang .optimizer .ConstantAndCopyPropagation ;
89import de .peeeq .wurstscript .intermediatelang .optimizer .LocalMerger ;
10+ import de .peeeq .wurstscript .intermediatelang .optimizer .SideEffectAnalyzer ;
911import de .peeeq .wurstscript .intermediatelang .optimizer .SimpleRewrites ;
1012import de .peeeq .wurstscript .jassIm .*;
1113import 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}
0 commit comments