@@ -216,7 +216,10 @@ class StackIROptimizer {
216216 auto & sets = localGraph.getSetses [get];
217217 if (sets.size () == 1 && *sets.begin () == set) {
218218 auto & setInfluences = localGraph.setInfluences [set];
219- if (setInfluences.size () == 1 ) {
219+ // If this has the proper value of 1, also do the potentially-
220+ // expensive check of whether we can remove this pair at all.
221+ if (setInfluences.size () == 1 &&
222+ canRemoveSetGetPair (index, i)) {
220223 assert (*setInfluences.begin () == get);
221224 // Do it! The set and the get can go away, the proper
222225 // value is on the stack.
@@ -354,6 +357,120 @@ class StackIROptimizer {
354357 // Otherwise, for basic instructions, just count the expression children.
355358 return ChildIterator (inst->origin ).children .size ();
356359 }
360+
361+ // Given a pair of a local.set and local.get, see if we can remove them
362+ // without breaking validation. Specifically, we must keep sets of non-
363+ // nullable locals that dominate a get until the end of the block, such as
364+ // here:
365+ //
366+ // local.set 0 ;; Can we remove
367+ // local.get 0 ;; this pair?
368+ // if
369+ // local.set 0
370+ // else
371+ // local.set 0
372+ // end
373+ // local.get 0 ;; This get poses a problem.
374+ //
375+ // Logically the 2nd&3rd sets ensure a value is applied to the local before we
376+ // read it, but the validation rules only track each set until the end of its
377+ // scope, so the 1st set (before the if, in the pair) is necessary.
378+ //
379+ // The logic below is related to LocalStructuralDominance, but sharing code
380+ // with it is difficult as this uses StackIR and not BinaryenIR, and it checks
381+ // a particular set/get pair.
382+ //
383+ // We are given the indexes of the set and get instructions in |insts|.
384+ bool canRemoveSetGetPair (Index setIndex, Index getIndex) {
385+ // The set must be before the get.
386+ assert (setIndex < getIndex);
387+
388+ auto * set = insts[setIndex]->origin ->cast <LocalSet>();
389+ if (func->isParam (set->index ) ||
390+ !func->getLocalType (set->index ).isNonNullable ()) {
391+ // This local cannot pose a problem for validation (params are always
392+ // initialized, and nullable locals may be uninitialized).
393+ return true ;
394+ }
395+
396+ // Track the depth (in block/if/loop/etc. scopes) relative to our starting
397+ // point. Anything less deep than that is not interesting, as we can only
398+ // help things at our depth or deeper to validate.
399+ Index currDepth = 0 ;
400+
401+ // Look for a different get than the one in getIndex (since that one is
402+ // being removed) which would stop validating without the set. While doing
403+ // so, note other sets that ensure validation even if our set is removed. We
404+ // track those in this stack of booleans, one for each scope, which is true
405+ // if another sets covers us and ours is not needed.
406+ //
407+ // We begin in the current scope and with no other set covering us.
408+ std::vector<bool > coverStack = {false };
409+
410+ // Track the total number of covers as well, for quick checking below.
411+ Index covers = 0 ;
412+
413+ // TODO: We could look before us as well, but then we might end up scanning
414+ // much of the function every time.
415+ for (Index i = setIndex + 1 ; i < insts.size (); i++) {
416+ auto * inst = insts[i];
417+ if (!inst) {
418+ continue ;
419+ }
420+ if (isControlFlowBegin (inst)) {
421+ // A new scope begins.
422+ currDepth++;
423+ coverStack.push_back (false );
424+ } else if (isControlFlowEnd (inst)) {
425+ if (currDepth == 0 ) {
426+ // Less deep than the start, so we found no problem.
427+ return true ;
428+ }
429+ currDepth--;
430+
431+ if (coverStack.back ()) {
432+ // A cover existed in the scope which ended.
433+ covers--;
434+ }
435+ coverStack.pop_back ();
436+ } else if (isControlFlowBarrier (inst)) {
437+ // A barrier, like the else in an if-else, not only ends a scope but
438+ // opens a new one.
439+ if (currDepth == 0 ) {
440+ // Another scope with the same depth begins, but ours ended, so stop.
441+ return true ;
442+ }
443+
444+ if (coverStack.back ()) {
445+ // A cover existed in the scope which ended.
446+ covers--;
447+ }
448+ coverStack.back () = false ;
449+ } else if (auto * otherSet = inst->origin ->dynCast <LocalSet>()) {
450+ // We are covered in this scope henceforth.
451+ if (otherSet->index == set->index ) {
452+ if (!coverStack.back ()) {
453+ covers++;
454+ if (currDepth == 0 ) {
455+ // We have a cover at depth 0, so everything from here on out
456+ // will be covered.
457+ return true ;
458+ }
459+ coverStack.back () = true ;
460+ }
461+ }
462+ } else if (auto * otherGet = inst->origin ->dynCast <LocalGet>()) {
463+ if (otherGet->index == set->index && i != getIndex && !covers) {
464+ // We found a get that might be a problem: it uses the same index, but
465+ // is not the get we were told about, and no other set covers us.
466+ return false ;
467+ }
468+ }
469+ }
470+
471+ // No problem.
472+ return true ;
473+ }
357474};
358475
359476struct OptimizeStackIR : public WalkerPass <PostWalker<OptimizeStackIR>> {
0 commit comments