66import de .peeeq .wurstscript .translation .imtranslation .ImHelper ;
77import de .peeeq .wurstscript .translation .imtranslation .ImTranslator ;
88import de .peeeq .wurstscript .types .TypesHelper ;
9- import io .vavr .collection .HashSet ;
109import io .vavr .collection .Set ;
1110import it .unimi .dsi .fastutil .objects .Object2IntOpenHashMap ;
1211import it .unimi .dsi .fastutil .objects .ObjectOpenHashSet ;
@@ -43,9 +42,10 @@ private void optimizeFunctions(List<ImFunction> functions) {
4342 public String getName () { return "Local variables merged" ; }
4443
4544 void optimizeFunc (ImFunction func ) {
46- Map <ImStmt , Set <ImVar >> livenessInfo = calculateLiveness (func );
45+ LivenessAnalysis liveness = analyzeLiveness (func );
46+ Map <ImStmt , Set <ImVar >> livenessInfo = liveness .liveOut ;
4747 eliminateDeadCode (livenessInfo );
48- mergeLocals (livenessInfo , func );
48+ mergeLocals (livenessInfo , liveness . liveAtEntry , func );
4949 }
5050
5151 void optimizeFunc (ImFunction func , LocalPlayerContextAnalyzer analyzer ) {
@@ -55,11 +55,23 @@ void optimizeFunc(ImFunction func, LocalPlayerContextAnalyzer analyzer) {
5555
5656 private boolean canMerge (ImType a , ImType b ) { return a .equalsType (b ); }
5757
58- private void mergeLocals (Map <ImStmt , Set <ImVar >> livenessInfo , ImFunction func ) {
59- Map <ImVar , Set <ImVar >> interference = calculateInferenceGraph (livenessInfo );
58+ private void mergeLocals (Map <ImStmt , Set <ImVar >> livenessInfo , Set <ImVar > liveAtEntry ,
59+ ImFunction func ) {
60+ Map <ImVar , java .util .Set <ImVar >> interference =
61+ calculateInterferenceGraph (livenessInfo , liveAtEntry , func );
62+
63+ Map <ImVar , Integer > declarationOrder = new IdentityHashMap <>();
64+ int nextOrder = 0 ;
65+ for (ImVar parameter : func .getParameters ()) {
66+ declarationOrder .put (parameter , nextOrder ++);
67+ }
68+ for (ImVar local : func .getLocals ()) {
69+ declarationOrder .put (local , nextOrder ++);
70+ }
6071
6172 PriorityQueue <ImVar > queue = new PriorityQueue <>(
62- (x , y ) -> interference .get (y ).size () - interference .get (x ).size ()
73+ Comparator .<ImVar >comparingInt (v -> interference .get (v ).size ()).reversed ()
74+ .thenComparingInt (declarationOrder ::get )
6375 );
6476 queue .addAll (interference .keySet ());
6577
@@ -81,8 +93,8 @@ private void mergeLocals(Map<ImStmt, Set<ImVar>> livenessInfo, ImFunction func)
8193 continue ;
8294 }
8395 if (localPlayerContextAnalyzer != null
84- && ( localPlayerContextAnalyzer .isLocalPlayerDependent (v )
85- || localPlayerContextAnalyzer .isLocalPlayerDependent (color ) )) {
96+ && localPlayerContextAnalyzer .isLocalPlayerDependent (v )
97+ != localPlayerContextAnalyzer .isLocalPlayerDependent (color )) {
8698 continue ;
8799 }
88100
@@ -158,17 +170,91 @@ private static int removeUnusedLocals(ImFunction f) {
158170 return before - kept .size ();
159171 }
160172
161- private Map <ImVar , Set <ImVar >> calculateInferenceGraph (Map <ImStmt , Set <ImVar >> livenessInfo ) {
162- Map <ImVar , Set <ImVar >> g = new LinkedHashMap <>();
163- for (Map .Entry <ImStmt , Set <ImVar >> e : livenessInfo .entrySet ()) {
164- Set <ImVar > live = e .getValue ();
165- for (ImVar v1 : live ) {
166- Set <ImVar > set = g .getOrDefault (v1 , HashSet .empty ());
167- set = set .addAll (live .filter (v2 -> canMerge (v1 .getType (), v2 .getType ())));
168- g .put (v1 , set );
173+ private Map <ImVar , java .util .Set <ImVar >> calculateInterferenceGraph (
174+ Map <ImStmt , Set <ImVar >> livenessInfo , Set <ImVar > liveAtEntry , ImFunction func ) {
175+ Map <ImVar , java .util .Set <ImVar >> graph = new LinkedHashMap <>();
176+ for (ImVar parameter : func .getParameters ()) {
177+ graph .put (parameter , new ObjectOpenHashSet <>());
178+ }
179+ for (ImVar local : func .getLocals ()) {
180+ graph .put (local , new ObjectOpenHashSet <>());
181+ }
182+
183+ // A definition interferes with every compatible value that remains live after it.
184+ // Building only those edges is equivalent to cliquing every live set, while avoiding
185+ // the old O(statements * liveValues^2) behavior on large inlined functions.
186+ for (Map .Entry <ImStmt , Set <ImVar >> entry : livenessInfo .entrySet ()) {
187+ List <ImVar > defined = definedLocals (entry .getKey ());
188+ if (defined .isEmpty ()) {
189+ continue ;
190+ }
191+ for (int i = 0 ; i < defined .size (); i ++) {
192+ ImVar definition = defined .get (i );
193+ java .util .Set <ImVar > neighbors = graph .computeIfAbsent (definition , ignored -> new ObjectOpenHashSet <>());
194+ for (ImVar live : entry .getValue ()) {
195+ if (live == definition || !canMerge (definition .getType (), live .getType ())) {
196+ continue ;
197+ }
198+ neighbors .add (live );
199+ graph .computeIfAbsent (live , ignored -> new ObjectOpenHashSet <>()).add (definition );
200+ }
201+ // Vararg tuple components are assigned at the same loop boundary. They must
202+ // occupy distinct slots even when neither component is live before the loop.
203+ for (int j = i + 1 ; j < defined .size (); j ++) {
204+ ImVar other = defined .get (j );
205+ if (canMerge (definition .getType (), other .getType ())) {
206+ neighbors .add (other );
207+ graph .computeIfAbsent (other , ignored -> new ObjectOpenHashSet <>()).add (definition );
208+ }
209+ }
210+ }
211+ }
212+
213+ // A local live at entry is read before every control-flow path has assigned it. Its
214+ // target-default value must remain distinct from every incoming parameter and from the
215+ // other entry-live locals, even if a later assignment eventually defines it.
216+ List <ImVar > entryDefinitions = new ArrayList <>(func .getParameters ());
217+ for (ImVar local : func .getLocals ()) {
218+ if (liveAtEntry .contains (local )) {
219+ entryDefinitions .add (local );
169220 }
170221 }
171- return g ;
222+ for (int i = 0 ; i < entryDefinitions .size (); i ++) {
223+ ImVar definition = entryDefinitions .get (i );
224+ java .util .Set <ImVar > neighbors = graph .get (definition );
225+ for (int j = i + 1 ; j < entryDefinitions .size (); j ++) {
226+ ImVar other = entryDefinitions .get (j );
227+ if (canMerge (definition .getType (), other .getType ())) {
228+ neighbors .add (other );
229+ graph .get (other ).add (definition );
230+ }
231+ }
232+ }
233+ return graph ;
234+ }
235+
236+ private static List <ImVar > definedLocals (ImStmt stmt ) {
237+ if (stmt instanceof ImVarargLoop loop ) {
238+ List <ImVar > result = new ArrayList <>(loop .getLoopVars ().size ());
239+ for (ImVarargLoopVar loopVar : loop .getLoopVars ()) {
240+ result .add (loopVar .getVar ());
241+ }
242+ return result ;
243+ }
244+ if (!(stmt instanceof ImSet set )) {
245+ return Collections .emptyList ();
246+ }
247+ ImLExpr left = set .getLeft ();
248+ if (left instanceof ImVarAccess access && !access .getVar ().isGlobal ()) {
249+ return Collections .singletonList (access .getVar ());
250+ }
251+ if (left instanceof ImTupleSelection selection ) {
252+ ImVar var = TypesHelper .getSimpleAndPureTupleVar (selection );
253+ if (var != null && !var .isGlobal ()) {
254+ return Collections .singletonList (var );
255+ }
256+ }
257+ return Collections .emptyList ();
172258 }
173259
174260 private void eliminateDeadCode (Map <ImStmt , Set <ImVar >> livenessInfo ) {
@@ -250,6 +336,10 @@ private static boolean hasSideEffects(Element e) {
250336 * over the strongly connected components of the control flow graph.
251337 */
252338 public Map <ImStmt , Set <ImVar >> calculateLiveness (ImFunction func ) {
339+ return analyzeLiveness (func ).liveOut ;
340+ }
341+
342+ private LivenessAnalysis analyzeLiveness (ImFunction func ) {
253343 // 1. Build Control Flow Graph
254344 ControlFlowGraph cfg = new ControlFlowGraph (func .getBody ());
255345 final List <Node > nodes = cfg .getNodes ();
@@ -272,6 +362,17 @@ public Map<ImStmt, Set<ImVar>> calculateLiveness(ImFunction func) {
272362 ImStmt stmt = node .getStmt ();
273363 if (stmt == null ) continue ;
274364
365+ if (stmt instanceof ImVarargLoop loop ) {
366+ for (ImVarargLoopVar loopVar : loop .getLoopVars ()) {
367+ if (!loopVar .getVar ().isGlobal ()) {
368+ def [i ].add (loopVar .getVar ());
369+ }
370+ }
371+ // The loop body has its own CFG nodes. Visiting it here would incorrectly
372+ // classify all body reads as uses at the loop header.
373+ continue ;
374+ }
375+
275376 final int ii = i ;
276377 stmt .accept (new ImStmt .DefaultVisitor () {
277378 @ Override public void visit (ImVarAccess va ) {
@@ -376,6 +477,19 @@ protected Collection<Node> getIncidentNodes(Node t) {
376477 result .put (stmt , io .vavr .collection .HashSet .ofAll (out [i ]));
377478 }
378479 }
379- return result ;
480+ Set <ImVar > liveAtEntry = N == 0
481+ ? io .vavr .collection .HashSet .empty ()
482+ : io .vavr .collection .HashSet .ofAll (in [0 ]);
483+ return new LivenessAnalysis (result , liveAtEntry );
484+ }
485+
486+ private static final class LivenessAnalysis {
487+ private final Map <ImStmt , Set <ImVar >> liveOut ;
488+ private final Set <ImVar > liveAtEntry ;
489+
490+ private LivenessAnalysis (Map <ImStmt , Set <ImVar >> liveOut , Set <ImVar > liveAtEntry ) {
491+ this .liveOut = liveOut ;
492+ this .liveAtEntry = liveAtEntry ;
493+ }
380494 }
381495}
0 commit comments