Skip to content

Commit 4f1295a

Browse files
authored
StackIR local2stack: Make sure we do not break non-nullable validation (#5919)
local2stack removes a pair of local.set 0 local.get 0 when that set is not used anywhere else: whatever value is put into the local, we can just leave it on the stack to replace the get. However, we only handled actual uses of the set which we checked using LocalGraph. There may be code that does not actually use the set local, but needs that set purely for validation reasons: local.set 0 local.get 0 block local.set 0 end local.get That last get reads the value set in the block, so the first set is not used by it. But for validation purposes, the inner set stops helping at the block end, so we do need that initial set. To fix this, check for gets that need our set to validate before removing any. Fixes #5917
1 parent a62ee0d commit 4f1295a

File tree

2 files changed

+954
-1
lines changed

2 files changed

+954
-1
lines changed

src/passes/StackIR.cpp

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

359476
struct OptimizeStackIR : public WalkerPass<PostWalker<OptimizeStackIR>> {

0 commit comments

Comments
 (0)