diff options
author | Heejin Ahn <aheejin@gmail.com> | 2021-12-06 14:09:35 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-06 14:09:35 -0800 |
commit | 9659f9b07c1196447edee68fe04c8d7dd2480652 (patch) | |
tree | 8e29497d0d4820abc6707044b9ec709f40867989 /src | |
parent | 80ba2559f08422e60281ebf15856676b81f22a76 (diff) | |
download | binaryen-9659f9b07c1196447edee68fe04c8d7dd2480652.tar.gz binaryen-9659f9b07c1196447edee68fe04c8d7dd2480652.tar.bz2 binaryen-9659f9b07c1196447edee68fe04c8d7dd2480652.zip |
[EH] Support try-delegate in EffectAnalyzer (#4368)
This adds support for try-delegate in `EffectAnalyzer`. Without this
support, the expresion below has been incorrectly classified as "cannot
throw", because the previous code considered everything inside
`try`-`catch_all` as "cannot throw". This is not the case when there is
a `delegate` that can bypass the `catch_all`.
```wasm
try $l0
try
try
throw $e
delegate $l0
catch_all
end
end
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/effects.h | 52 | ||||
-rw-r--r-- | src/ir/properties.h | 2 | ||||
-rw-r--r-- | src/passes/CodeFolding.cpp | 2 | ||||
-rw-r--r-- | src/passes/LoopInvariantCodeMotion.cpp | 2 | ||||
-rw-r--r-- | src/passes/SimplifyLocals.cpp | 2 | ||||
-rw-r--r-- | src/passes/Vacuum.cpp | 2 |
6 files changed, 43 insertions, 19 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h index ced8e63cb..808693e95 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -111,9 +111,9 @@ public: // An atomic load/store/RMW/Cmpxchg or an operator that has a defined ordering // wrt atomics (e.g. memory.grow) bool isAtomic = false; - bool throws = false; + bool throws_ = false; // The nested depth of try-catch_all. If an instruction that may throw is - // inside an inner try-catch_all, we don't mark it as 'throws', because it + // inside an inner try-catch_all, we don't mark it as 'throws_', because it // will be caught by an inner catch_all. We only count 'try's with a // 'catch_all' because instructions within a 'try' without a 'catch_all' can // still throw outside of the try. @@ -144,6 +144,7 @@ public: return accessesMutableStruct() || readsImmutableStruct; } bool accessesArray() const { return calls || readsArray || writesArray; } + bool throws() const { return throws_ || !delegateTargets.empty(); } // Check whether this may transfer control flow to somewhere outside of this // expression (aside from just flowing out normally). That includes a break // or a throw (if the throw is not known to be caught inside this expression; @@ -152,7 +153,7 @@ public: // caught in the function at all, which would mean control flow cannot be // transferred inside the function, but this expression does not know that). bool transfersControlFlow() const { - return branchesOut || throws || hasExternalBreakTargets(); + return branchesOut || throws() || hasExternalBreakTargets(); } // Changes something in globally-stored state. @@ -167,7 +168,7 @@ public: bool hasNonTrapSideEffects() const { return localsWritten.size() > 0 || danglingPop || writesGlobalState() || - throws || transfersControlFlow(); + throws() || transfersControlFlow(); } bool hasSideEffects() const { return trap || hasNonTrapSideEffects(); } @@ -261,7 +262,7 @@ public: // function, so transfersControlFlow would be true) - while we allow the // reordering of traps with each other, we do not reorder exceptions with // anything. - assert(!((trap && other.throws) || (throws && other.trap))); + assert(!((trap && other.throws()) || (throws() && other.trap))); // We can't reorder an implicit trap in a way that could alter what global // state is modified. if ((trap && other.writesGlobalState()) || @@ -287,7 +288,7 @@ public: implicitTrap = implicitTrap || other.implicitTrap; trapsNeverHappen = trapsNeverHappen || other.trapsNeverHappen; isAtomic = isAtomic || other.isAtomic; - throws = throws || other.throws; + throws_ = throws_ || other.throws_; danglingPop = danglingPop || other.danglingPop; for (auto i : other.localsRead) { localsRead.insert(i); @@ -307,6 +308,9 @@ public: for (auto i : other.breakTargets) { breakTargets.insert(i); } + for (auto i : other.delegateTargets) { + delegateTargets.insert(i); + } } // the checks above happen after the node's children were processed, in the @@ -329,6 +333,7 @@ public: } std::set<Name> breakTargets; + std::set<Name> delegateTargets; private: struct InternalAnalyzer @@ -369,6 +374,18 @@ private: static void doStartCatch(InternalAnalyzer* self, Expression** currp) { Try* curr = (*currp)->cast<Try>(); + // This is conservative. When an inner try-delegate targets the current + // expression, even if the try-delegate's body can't throw, we consider + // the current expression can throw for simplicity, unless the current + // expression is not inside a try-catch_all. It is hard to figure out + // whether the original try-delegate's body throws or not at this point. + if (curr->name.is()) { + if (self->parent.delegateTargets.count(curr->name) && + self->parent.tryDepth == 0) { + self->parent.throws_ = true; + } + self->parent.delegateTargets.erase(curr->name); + } // We only count 'try's with a 'catch_all' because instructions within a // 'try' without a 'catch_all' can still throw outside of the try. if (curr->hasCatchAll()) { @@ -423,7 +440,7 @@ private: parent.calls = true; // When EH is enabled, any call can throw. if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { - parent.throws = true; + parent.throws_ = true; } if (curr->isReturn) { parent.branchesOut = true; @@ -438,7 +455,7 @@ private: void visitCallIndirect(CallIndirect* curr) { parent.calls = true; if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { - parent.throws = true; + parent.throws_ = true; } if (curr->isReturn) { parent.branchesOut = true; @@ -631,15 +648,19 @@ private: parent.readsTable = true; parent.writesTable = true; } - void visitTry(Try* curr) {} + void visitTry(Try* curr) { + if (curr->delegateTarget.is()) { + parent.delegateTargets.insert(curr->delegateTarget); + } + } void visitThrow(Throw* curr) { if (parent.tryDepth == 0) { - parent.throws = true; + parent.throws_ = true; } } void visitRethrow(Rethrow* curr) { if (parent.tryDepth == 0) { - parent.throws = true; + parent.throws_ = true; } // traps when the arg is null parent.implicitTrap = true; @@ -658,7 +679,7 @@ private: void visitCallRef(CallRef* curr) { parent.calls = true; if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) { - parent.throws = true; + parent.throws_ = true; } if (curr->isReturn) { parent.branchesOut = true; @@ -811,7 +832,7 @@ public: if (isAtomic) { effects |= SideEffects::IsAtomic; } - if (throws) { + if (throws_) { effects |= SideEffects::Throws; } if (danglingPop) { @@ -826,7 +847,10 @@ public: } private: - void pre() { breakTargets.clear(); } + void pre() { + breakTargets.clear(); + delegateTargets.clear(); + } void post() { assert(tryDepth == 0); diff --git a/src/ir/properties.h b/src/ir/properties.h index 8800f1049..d2c5affd2 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -283,7 +283,7 @@ inline Expression* getImmediateFallthrough(Expression* curr, return br->value; } } else if (auto* tryy = curr->dynCast<Try>()) { - if (!EffectAnalyzer(passOptions, module, tryy->body).throws) { + if (!EffectAnalyzer(passOptions, module, tryy->body).throws()) { return tryy->body; } } else if (auto* as = curr->dynCast<RefCast>()) { diff --git a/src/passes/CodeFolding.cpp b/src/passes/CodeFolding.cpp index 7a492dbea..0323223e1 100644 --- a/src/passes/CodeFolding.cpp +++ b/src/passes/CodeFolding.cpp @@ -319,7 +319,7 @@ private: // conservative approximation because there can be cases that 'try' is // within the expression that may throw so it is safe to take the // expression out. - if (effects.throws && !FindAll<Try>(outOf).list.empty()) { + if (effects.throws() && !FindAll<Try>(outOf).list.empty()) { return false; } } diff --git a/src/passes/LoopInvariantCodeMotion.cpp b/src/passes/LoopInvariantCodeMotion.cpp index f339f8126..7fbd3ef55 100644 --- a/src/passes/LoopInvariantCodeMotion.cpp +++ b/src/passes/LoopInvariantCodeMotion.cpp @@ -125,7 +125,7 @@ struct LoopInvariantCodeMotion (effects.readsMutableGlobalState() && loopEffects.writesGlobalState()); // TODO: look into optimizing this with exceptions. for now, disallow - if (effects.throws || loopEffects.throws) { + if (effects.throws() || loopEffects.throws()) { unsafeToMove = true; } if (!unsafeToMove) { diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index 8ed822774..35980f503 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -304,7 +304,7 @@ struct SimplifyLocals if (curr->is<Try>()) { std::vector<Index> invalidated; for (auto& [index, info] : self->sinkables) { - if (info.effects.throws) { + if (info.effects.throws()) { invalidated.push_back(index); } } diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index abd54f334..6adab04e9 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -360,7 +360,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { void visitTry(Try* curr) { // If try's body does not throw, the whole try-catch can be replaced with // the try's body. - if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws) { + if (!EffectAnalyzer(getPassOptions(), *getModule(), curr->body).throws()) { replaceCurrent(curr->body); for (auto* catchBody : curr->catchBodies) { typeUpdater.noteRecursiveRemoval(catchBody); |