summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHeejin Ahn <aheejin@gmail.com>2021-12-06 14:09:35 -0800
committerGitHub <noreply@github.com>2021-12-06 14:09:35 -0800
commit9659f9b07c1196447edee68fe04c8d7dd2480652 (patch)
tree8e29497d0d4820abc6707044b9ec709f40867989 /src
parent80ba2559f08422e60281ebf15856676b81f22a76 (diff)
downloadbinaryen-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.h52
-rw-r--r--src/ir/properties.h2
-rw-r--r--src/passes/CodeFolding.cpp2
-rw-r--r--src/passes/LoopInvariantCodeMotion.cpp2
-rw-r--r--src/passes/SimplifyLocals.cpp2
-rw-r--r--src/passes/Vacuum.cpp2
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);