@@ -93,29 +93,22 @@ struct SimplifyLocals
9393
9494 // Reverse index: for each local L, tracks which sinkable keys have effects
9595 // that read L. This allows checkInvalidations to find conflicting sinkables
96- // in O(|current effects|) instead of O(|all sinkables|).
97- std::unordered_map<Index, std::unordered_set<Index>> localReadBySinkable_;
96+ // in O(|current effects|) instead of O(|all sinkables|) when the current
97+ // expression only has local effects.
98+ std::unordered_map<Index, std::unordered_set<Index>> localReadBySinkable;
9899
99100 // Reverse index: for each local L, tracks which sinkable keys have effects
100101 // that write L. A sinkable at key K always writes K, but may also write
101102 // other locals if its value contains nested local.sets.
102- std::unordered_map<Index, std::unordered_set<Index>> localWrittenBySinkable_;
103-
104- // Sinkable keys that have non-local ordering-relevant effects (calls,
105- // memory, control flow, etc.). These need full orderedAfter checks when
106- // the current expression also has non-local effects.
107- std::unordered_set<Index> heavySinkables_;
103+ std::unordered_map<Index, std::unordered_set<Index>> localWrittenBySinkable;
108104
109105 void registerSinkable (Index key) {
110106 auto & effects = sinkables.at (key).effects ;
111107 for (auto L : effects.localsRead ) {
112- localReadBySinkable_ [L].insert (key);
108+ localReadBySinkable [L].insert (key);
113109 }
114110 for (auto L : effects.localsWritten ) {
115- localWrittenBySinkable_[L].insert (key);
116- }
117- if (effects.hasNonLocalOrderingEffects ()) {
118- heavySinkables_.insert (key);
111+ localWrittenBySinkable[L].insert (key);
119112 }
120113 }
121114
@@ -126,37 +119,34 @@ struct SimplifyLocals
126119 }
127120 auto & effects = it->second .effects ;
128121 for (auto L : effects.localsRead ) {
129- auto mapIt = localReadBySinkable_ .find (L);
130- if (mapIt != localReadBySinkable_ .end ()) {
122+ auto mapIt = localReadBySinkable .find (L);
123+ if (mapIt != localReadBySinkable .end ()) {
131124 mapIt->second .erase (key);
132125 if (mapIt->second .empty ()) {
133- localReadBySinkable_ .erase (mapIt);
126+ localReadBySinkable .erase (mapIt);
134127 }
135128 }
136129 }
137130 for (auto L : effects.localsWritten ) {
138- auto mapIt = localWrittenBySinkable_ .find (L);
139- if (mapIt != localWrittenBySinkable_ .end ()) {
131+ auto mapIt = localWrittenBySinkable .find (L);
132+ if (mapIt != localWrittenBySinkable .end ()) {
140133 mapIt->second .erase (key);
141134 if (mapIt->second .empty ()) {
142- localWrittenBySinkable_ .erase (mapIt);
135+ localWrittenBySinkable .erase (mapIt);
143136 }
144137 }
145138 }
146- heavySinkables_.erase (key);
147139 }
148140
149141 void clearSinkables () {
150142 sinkables.clear ();
151- localReadBySinkable_.clear ();
152- localWrittenBySinkable_.clear ();
153- heavySinkables_.clear ();
143+ localReadBySinkable.clear ();
144+ localWrittenBySinkable.clear ();
154145 }
155146
156147 Sinkables takeSinkables () {
157- localReadBySinkable_.clear ();
158- localWrittenBySinkable_.clear ();
159- heavySinkables_.clear ();
148+ localReadBySinkable.clear ();
149+ localWrittenBySinkable.clear ();
160150 return std::move (sinkables);
161151 }
162152
@@ -384,56 +374,56 @@ struct SimplifyLocals
384374 }
385375
386376 void checkInvalidations (EffectAnalyzer& effects) {
387- // Use targeted lookups instead of iterating all sinkables.
388- // We collect candidate sinkable keys that *might* conflict, then verify.
389- std::unordered_set<Index> candidates;
390-
391- // Local conflicts via reverse indices.
392- // When the current expression reads local L, any sinkable that writes L
393- // has a write-read conflict.
394- for (auto L : effects.localsRead ) {
395- auto it = localWrittenBySinkable_.find (L);
396- if (it != localWrittenBySinkable_.end ()) {
397- candidates.insert (it->second .begin (), it->second .end ());
377+ // If the current expression only has local effects, we can use reverse
378+ // indices to find exactly the conflicting sinkables in O(|locals|) instead
379+ // of iterating all sinkables. This is the common case for local.get,
380+ // local.set, const, and simple arithmetic expressions.
381+ if (!effects.hasNonLocalOrderingEffects ()) {
382+ std::unordered_set<Index> candidates;
383+ // When the current expression reads local L, any sinkable that writes L
384+ // has a write-read conflict.
385+ for (auto L : effects.localsRead ) {
386+ auto it = localWrittenBySinkable.find (L);
387+ if (it != localWrittenBySinkable.end ()) {
388+ candidates.insert (it->second .begin (), it->second .end ());
389+ }
398390 }
399- }
400- // When the current expression writes local L, any sinkable that reads L
401- // (read-write conflict) or writes L (write-write conflict) is a candidate.
402- for (auto L : effects.localsWritten ) {
403- auto it = localReadBySinkable_.find (L);
404- if (it != localReadBySinkable_.end ()) {
405- candidates.insert (it->second .begin (), it->second .end ());
391+ // When the current expression writes local L, any sinkable that reads L
392+ // (read-write conflict) or writes L (write-write conflict) is a
393+ // candidate.
394+ for (auto L : effects.localsWritten ) {
395+ auto it = localReadBySinkable.find (L);
396+ if (it != localReadBySinkable.end ()) {
397+ candidates.insert (it->second .begin (), it->second .end ());
398+ }
399+ auto it2 = localWrittenBySinkable.find (L);
400+ if (it2 != localWrittenBySinkable.end ()) {
401+ candidates.insert (it2->second .begin (), it2->second .end ());
402+ }
406403 }
407- auto it2 = localWrittenBySinkable_.find (L);
408- if (it2 != localWrittenBySinkable_.end ()) {
409- candidates.insert (it2->second .begin (), it2->second .end ());
404+ std::vector<Index> invalidated;
405+ for (auto key : candidates) {
406+ auto it = sinkables.find (key);
407+ if (it != sinkables.end () && effects.orderedAfter (it->second .effects )) {
408+ invalidated.push_back (key);
409+ }
410410 }
411- }
412-
413- // Non-local conflicts: if the current expression has non-local effects,
414- // check sinkables that also have non-local effects.
415- if (effects.hasNonLocalOrderingEffects ()) {
416- candidates.insert (heavySinkables_.begin (), heavySinkables_.end ());
417- }
418- // If current transfers control flow, all sinkables with any side effects
419- // (including local access) are invalidated. Since all sinkables access
420- // locals, this means all of them.
421- if (effects.transfersControlFlow ()) {
422- for (auto & [key, _] : sinkables) {
423- candidates.insert (key);
411+ for (auto key : invalidated) {
412+ eraseSinkable (key);
424413 }
414+ return ;
425415 }
426416
427- // Verify candidates with the full ordering check and invalidate.
417+ // The current expression has non-local effects (memory, calls, control
418+ // flow, etc.), so we must check all sinkables.
428419 std::vector<Index> invalidated;
429- for (auto key : candidates) {
430- auto it = sinkables.find (key);
431- if (it != sinkables.end () && effects.orderedAfter (it->second .effects )) {
432- invalidated.push_back (key);
420+ for (auto & [index, info] : sinkables) {
421+ if (effects.orderedAfter (info.effects )) {
422+ invalidated.push_back (index);
433423 }
434424 }
435- for (auto key : invalidated) {
436- eraseSinkable (key );
425+ for (auto index : invalidated) {
426+ eraseSinkable (index );
437427 }
438428 }
439429
0 commit comments