-
Notifications
You must be signed in to change notification settings - Fork 857
Use effects for indirect call expressions #8625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
58eb5c2
253c092
2156d99
7facc2a
5aac82a
63d5515
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |||||
| #include "ir/intrinsics.h" | ||||||
| #include "pass.h" | ||||||
| #include "support/name.h" | ||||||
| #include "support/utilities.h" | ||||||
| #include "wasm-traversal.h" | ||||||
| #include "wasm-type.h" | ||||||
| #include "wasm.h" | ||||||
|
|
@@ -716,71 +717,47 @@ class EffectAnalyzer { | |||||
| } | ||||||
|
|
||||||
| void visitCall(Call* curr) { | ||||||
| // call.without.effects has no effects. | ||||||
| if (Intrinsics(parent.module).isCallWithoutEffects(curr)) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // Get the target's effects, if they exist. Note that we must handle the | ||||||
| // case of the function not yet existing (we may be executed in the middle | ||||||
| // of a pass, which may have built up calls but not the targets of those | ||||||
| // calls; in such a case, we do not find the targets and therefore assume | ||||||
| // we know nothing about the effects, which is safe). | ||||||
| const EffectAnalyzer* targetEffects = nullptr; | ||||||
| if (auto* target = parent.module.getFunctionOrNull(curr->target)) { | ||||||
| targetEffects = target->effects.get(); | ||||||
| const EffectAnalyzer* bodyEffects = nullptr; | ||||||
| if (auto* target = parent.module.getFunctionOrNull(curr->target); | ||||||
| target && target->effects) { | ||||||
| bodyEffects = target->effects.get(); | ||||||
| } | ||||||
|
|
||||||
| 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 (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); | ||||||
| } | ||||||
| populateEffectsForCall(curr, bodyEffects); | ||||||
| } | ||||||
| void visitCallIndirect(CallIndirect* curr) { | ||||||
| auto* table = parent.module.getTable(curr->table); | ||||||
| if (!Type::isSubType(Type(curr->heapType, Nullability::Nullable), | ||||||
| table->type)) { | ||||||
| parent.trap = true; | ||||||
|
kripken marked this conversation as resolved.
|
||||||
| return; | ||||||
| } | ||||||
| if (table->type.isNullable()) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we check only this but call |
||||||
| parent.implicitTrap = true; | ||||||
|
kripken marked this conversation as resolved.
|
||||||
| } | ||||||
|
|
||||||
| parent.calls = true; | ||||||
| // 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; | ||||||
| const EffectAnalyzer* bodyEffects = nullptr; | ||||||
| if (auto it = parent.module.typeEffects.find(curr->heapType); | ||||||
| it != parent.module.typeEffects.end() && it->second) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a case |
||||||
| bodyEffects = it->second.get(); | ||||||
| } | ||||||
| populateEffectsForCall(curr, bodyEffects); | ||||||
| } | ||||||
| void visitCallIndirect(CallIndirect* curr) { | ||||||
| parent.calls = true; | ||||||
| if (curr->isReturn) { | ||||||
| parent.branchesOut = true; | ||||||
| if (parent.features.hasExceptionHandling()) { | ||||||
| parent.hasReturnCallThrow = true; | ||||||
| } | ||||||
| void visitCallRef(CallRef* curr) { | ||||||
| if (trapOnNull(curr->target)) { | ||||||
| return; | ||||||
| } | ||||||
| if (parent.features.hasExceptionHandling() && | ||||||
| (parent.tryDepth == 0 && !curr->isReturn)) { | ||||||
| parent.throws_ = true; | ||||||
|
|
||||||
| const EffectAnalyzer* bodyEffects = nullptr; | ||||||
| if (auto it = | ||||||
| parent.module.typeEffects.find(curr->target->type.getHeapType()); | ||||||
| it != parent.module.typeEffects.end() && it->second) { | ||||||
| bodyEffects = it->second.get(); | ||||||
| } | ||||||
| populateEffectsForCall(curr, bodyEffects); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps |
||||||
| } | ||||||
| void visitLocalGet(LocalGet* curr) { | ||||||
| parent.localsRead.insert(curr->index); | ||||||
|
|
@@ -1038,22 +1015,6 @@ class EffectAnalyzer { | |||||
| void visitTupleExtract(TupleExtract* curr) {} | ||||||
| void visitRefI31(RefI31* curr) {} | ||||||
| void visitI31Get(I31Get* curr) { trapOnNull(curr->i31); } | ||||||
| void visitCallRef(CallRef* curr) { | ||||||
| if (trapOnNull(curr->target)) { | ||||||
| return; | ||||||
| } | ||||||
| if (curr->isReturn) { | ||||||
| parent.branchesOut = true; | ||||||
| if (parent.features.hasExceptionHandling()) { | ||||||
| parent.hasReturnCallThrow = true; | ||||||
| } | ||||||
| } | ||||||
| parent.calls = true; | ||||||
| if (parent.features.hasExceptionHandling() && | ||||||
| (parent.tryDepth == 0 && !curr->isReturn)) { | ||||||
| parent.throws_ = true; | ||||||
| } | ||||||
| } | ||||||
| void visitRefTest(RefTest* curr) {} | ||||||
|
|
||||||
| void visitRefCast(RefCast* curr) { | ||||||
|
|
@@ -1335,6 +1296,55 @@ class EffectAnalyzer { | |||||
| parent.throws_ = true; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| private: | ||||||
| // Populate effects of the function's body that were computed from | ||||||
| // GlobalEffects. Note that calls may have other effects that aren't | ||||||
| // captured by the function body of the target (e.g. a call_ref may trap on | ||||||
| // null refs). | ||||||
| template<typename CallType> | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I believe this is the pattern we use in general. |
||||||
| void populateFunctionBodyEffects(const CallType* curr, | ||||||
| const EffectAnalyzer& funcEffects) { | ||||||
| if (curr->isReturn) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||
| if (funcEffects.throws()) { | ||||||
| parent.hasReturnCallThrow = true; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (funcEffects.throws_ && (parent.tryDepth > 0 || curr->isReturn)) { | ||||||
| auto filteredEffects = funcEffects; | ||||||
| filteredEffects.throws_ = false; | ||||||
| parent.mergeIn(filteredEffects); | ||||||
| } else { | ||||||
| parent.mergeIn(funcEffects); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| template<typename CallType> | ||||||
| void | ||||||
| populateEffectsForCall(const CallType* curr, | ||||||
| NullablePtr<const EffectAnalyzer*> bodyEffects) { | ||||||
| if (curr->isReturn) { | ||||||
| parent.branchesOut = true; | ||||||
| } | ||||||
|
|
||||||
| if (bodyEffects) { | ||||||
| populateFunctionBodyEffects(curr, *bodyEffects); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| parent.calls = true; | ||||||
| // If EH is enabled and we don't have global effects information, | ||||||
| // assume that the call body may throw. | ||||||
| if (parent.features.hasExceptionHandling()) { | ||||||
| if (curr->isReturn) { | ||||||
| parent.hasReturnCallThrow = true; | ||||||
| } | ||||||
| if (parent.tryDepth == 0 && !curr->isReturn) { | ||||||
| parent.throws_ = true; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| public: | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,19 @@ class Fatal { | |
| #define WASM_UNREACHABLE(msg) wasm::handle_unreachable() | ||
| #endif | ||
|
|
||
| // Helper to create an invocable with an overloaded operator(), for use with | ||
| // std::visit e.g. | ||
| // std::visit( | ||
| // overloaded{ | ||
| // [](const A& a) { ... }, | ||
| // [](const B& b) { ... }}, | ||
| // variant) | ||
| template<class... Ts> struct overloaded : Ts... { | ||
| using Ts::operator()...; | ||
| }; | ||
|
|
||
| template<typename T> using NullablePtr = T; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this for? |
||
|
|
||
| } // namespace wasm | ||
|
|
||
| #endif // wasm_support_utilities_h | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2722,6 +2722,12 @@ class Module { | |
| std::unordered_map<HeapType, TypeNames> typeNames; | ||
| std::unordered_map<HeapType, Index> typeIndices; | ||
|
|
||
| // Potential effects for bodies of indirect calls to this type. | ||
| // TODO: Use Type instead of HeapType to account for nullability and | ||
| // exactness. | ||
| std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>> | ||
| typeEffects; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is this managed? Who is responsible for updating it? Please document this here. |
||
|
|
||
| MixedArena allocator; | ||
|
|
||
| private: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.