summaryrefslogtreecommitdiff
path: root/src/ir/effects.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/ir/effects.h')
-rw-r--r--src/ir/effects.h113
1 files changed, 84 insertions, 29 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h
index 6901f99de..3ecb54641 100644
--- a/src/ir/effects.h
+++ b/src/ir/effects.h
@@ -67,9 +67,23 @@ public:
// noticeable from the perspective of the caller, that is, effects that are
// only noticeable during the call, but "vanish" when the call stack is
// unwound.
+ //
+ // Unlike walking just the body, walking the function will also
+ // include the effects of any return calls the function makes. For that
+ // reason, it is a bug if a user of this code calls walk(Expression*) and not
+ // walk(Function*) if their intention is to scan an entire function body.
+ // Putting it another way, a return_call is syntax sugar for a return and a
+ // call, where the call executes at the function scope, so there is a
+ // meaningful difference between scanning an expression and scanning
+ // the entire function body.
void walk(Function* func) {
walk(func->body);
+ // Effects of return-called functions will be visible to the caller.
+ if (hasReturnCallThrow) {
+ throws_ = true;
+ }
+
// We can ignore branching out of the function body - this can only be
// a return, and that is only noticeable in the function, not outside.
branchesOut = false;
@@ -143,6 +157,22 @@ public:
// or a continuation that is never continued, are examples of that.
bool mayNotReturn = false;
+ // Since return calls return out of the body of the function before performing
+ // their call, they are indistinguishable from normal returns from the
+ // perspective of their surrounding code, and the return-callee's effects only
+ // become visible when considering the effects of the whole function
+ // containing the return call. To model this correctly, stash the callee's
+ // effects on the side and only merge them in after walking a full function
+ // body.
+ //
+ // We currently do this stashing only for the throw effect, but in principle
+ // we could do it for all effects if it made a difference. (Only throw is
+ // noticeable now because the only thing that can change between doing the
+ // call here and doing it outside at the function exit is the scoping of
+ // try-catch blocks. If future wasm scoping additions are added, we may need
+ // more here.)
+ bool hasReturnCallThrow = false;
+
// Helper functions to check for various effect types
bool accessesLocal() const {
@@ -466,43 +496,63 @@ private:
return;
}
+ const EffectAnalyzer* targetEffects = nullptr;
+ if (parent.funcEffectsMap) {
+ auto iter = parent.funcEffectsMap->find(curr->target);
+ if (iter != parent.funcEffectsMap->end()) {
+ targetEffects = &iter->second;
+ }
+ }
+
if (curr->isReturn) {
parent.branchesOut = true;
+ // When EH is enabled, any call can throw.
+ if (parent.features.hasExceptionHandling() &&
+ (!targetEffects || targetEffects->throws())) {
+ parent.hasReturnCallThrow = true;
+ }
}
- if (parent.funcEffectsMap) {
- auto iter = parent.funcEffectsMap->find(curr->target);
- if (iter != parent.funcEffectsMap->end()) {
- // We have effect information for this call target, and can just use
- // that. The one change we may want to make is to remove throws_, if
- // the target function throws and we know that will be caught anyhow,
- // the same as the code below for the general path.
- const auto& targetEffects = iter->second;
- if (targetEffects.throws_ && parent.tryDepth > 0) {
- auto filteredEffects = targetEffects;
- filteredEffects.throws_ = false;
- parent.mergeIn(filteredEffects);
- } else {
- // Just merge in all the effects.
- parent.mergeIn(targetEffects);
- }
- return;
+ if (targetEffects) {
+ // We have effect information for this call target, and can just use
+ // that. The one change we may want to make is to remove throws_, if the
+ // target function throws and we know that will be caught anyhow, the
+ // same as the code below for the general path. We can always filter out
+ // throws for return calls because they are already more precisely
+ // captured by `branchesOut`, which models the return, and
+ // `hasReturnCallThrow`, which models the throw that will happen after
+ // the return.
+ if (targetEffects->throws_ && (parent.tryDepth > 0 || curr->isReturn)) {
+ auto filteredEffects = *targetEffects;
+ filteredEffects.throws_ = false;
+ parent.mergeIn(filteredEffects);
+ } else {
+ // Just merge in all the effects.
+ parent.mergeIn(*targetEffects);
}
+ return;
}
parent.calls = true;
- // When EH is enabled, any call can throw.
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
+ // When EH is enabled, any call can throw. Skip this for return calls
+ // because the throw is already more precisely captured by the combination
+ // of `hasReturnCallThrow` and `branchesOut`.
+ if (parent.features.hasExceptionHandling() && parent.tryDepth == 0 &&
+ !curr->isReturn) {
parent.throws_ = true;
}
}
void visitCallIndirect(CallIndirect* curr) {
parent.calls = true;
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws_ = true;
- }
if (curr->isReturn) {
parent.branchesOut = true;
+ if (parent.features.hasExceptionHandling()) {
+ parent.hasReturnCallThrow = true;
+ }
+ }
+ if (parent.features.hasExceptionHandling() &&
+ (parent.tryDepth == 0 && !curr->isReturn)) {
+ parent.throws_ = true;
}
}
void visitLocalGet(LocalGet* curr) {
@@ -745,21 +795,26 @@ private:
}
}
void visitCallRef(CallRef* curr) {
+ if (curr->isReturn) {
+ parent.branchesOut = true;
+ if (parent.features.hasExceptionHandling()) {
+ parent.hasReturnCallThrow = true;
+ }
+ }
if (curr->target->type.isNull()) {
parent.trap = true;
return;
}
- parent.calls = true;
- if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
- parent.throws_ = true;
- }
- if (curr->isReturn) {
- parent.branchesOut = true;
- }
// traps when the call target is null
if (curr->target->type.isNullable()) {
parent.implicitTrap = true;
}
+
+ parent.calls = true;
+ if (parent.features.hasExceptionHandling() &&
+ (parent.tryDepth == 0 && !curr->isReturn)) {
+ parent.throws_ = true;
+ }
}
void visitRefTest(RefTest* curr) {}
void visitRefCast(RefCast* curr) {