diff options
Diffstat (limited to 'src/ir/effects.h')
-rw-r--r-- | src/ir/effects.h | 113 |
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) { |