@@ -216,7 +216,7 @@ struct SimplifyLocals
216216 }
217217 }
218218
219- void visitGetLocal (GetLocal* curr) {
219+ void optimizeGetLocal (GetLocal* curr) {
220220 auto found = sinkables.find (curr->index );
221221 if (found != sinkables.end ()) {
222222 auto * set = (*found->second .item )
@@ -311,8 +311,61 @@ struct SimplifyLocals
311311 static void
312312 visitPost (SimplifyLocals<allowTee, allowStructure, allowNesting>* self,
313313 Expression** currp) {
314+ // Handling invalidations in the case where the current node is a get
315+ // that we sink into is not trivial in general. In the simple case,
316+ // all current sinkables are compatible with each other (otherwise one
317+ // would have invalidated a previous one, and removed it). Given that, if
318+ // we sink one of the sinkables, then that new code cannot invalidate any
319+ // other sinkable - we've already compared them. However, a tricky case
320+ // is when a sinkable contains another sinkable,
321+ //
322+ // (local.set $x
323+ // (block (result i32)
324+ // (A (local.get $y))
325+ // (local.set $y B)
326+ // )
327+ // )
328+ // (C (local.get $y))
329+ // (D (local.get $x))
330+ //
331+ // If we sink the set of $y, we have
332+ //
333+ // (local.set $x
334+ // (block (result i32)
335+ // (A (local.get $y))
336+ // (nop)
337+ // )
338+ // )
339+ // (C B)
340+ // (D (local.get $x))
341+ //
342+ // There is now a risk that the set of $x should be invalidated, because
343+ // if we sink it then A may happen after B (imagine that B contains
344+ // something dangerous for that). To verify the risk, we could recursively
345+ // scan all of B, but that is less efficient. Instead, the key thing is
346+ // that if we sink out an inner part of a set, we should just leave further
347+ // work on it to a later iteration. This is achieved by checking for
348+ // invalidation on the original node, the local.get $y, which is guaranteed
349+ // to invalidate the parent whose inner part was removed (since the inner
350+ // part has a set, and the original node is a get of that same local).
351+ //
352+ // To implement this, if the current node is a get, note it and use it
353+ // for invalidations later down. We must note it since optimizing the get
354+ // may perform arbitrary changes to the graph, including reuse the get.
355+
356+ Expression* original = *currp;
357+
358+ GetLocal originalGet;
359+
360+ if (auto * get = (*currp)->dynCast <GetLocal>()) {
361+ // Note: no visitor for GetLocal, so that we can handle it here.
362+ originalGet = *get;
363+ original = &originalGet;
364+ self->optimizeGetLocal (get);
365+ }
366+
314367 // perform main SetLocal processing here, since we may be the result of
315- // replaceCurrent, i.e., the visitor was not called .
368+ // replaceCurrent, i.e., no visitor for SetLocal, like GetLocal above .
316369 auto * set = (*currp)->dynCast <SetLocal>();
317370
318371 if (set) {
@@ -332,7 +385,7 @@ struct SimplifyLocals
332385 }
333386
334387 EffectAnalyzer effects (self->getPassOptions ());
335- if (effects.checkPost (*currp )) {
388+ if (effects.checkPost (original )) {
336389 self->checkInvalidations (effects);
337390 }
338391
0 commit comments