@@ -216,7 +216,10 @@ class StackIROptimizer {
216
216
auto & sets = localGraph.getSetses [get];
217
217
if (sets.size () == 1 && *sets.begin () == set) {
218
218
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)) {
220
223
assert (*setInfluences.begin () == get);
221
224
// Do it! The set and the get can go away, the proper
222
225
// value is on the stack.
@@ -354,6 +357,120 @@ class StackIROptimizer {
354
357
// Otherwise, for basic instructions, just count the expression children.
355
358
return ChildIterator (inst->origin ).children .size ();
356
359
}
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
+ }
357
474
};
358
475
359
476
struct OptimizeStackIR : public WalkerPass <PostWalker<OptimizeStackIR>> {
0 commit comments