-
Notifications
You must be signed in to change notification settings - Fork 850
Compute effects for indirect calls in GlobalEffects #8609
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
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 | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |||||||
|
|
||||||||
| #include "ir/effects.h" | ||||||||
| #include "ir/module-utils.h" | ||||||||
| #include "ir/subtypes.h" | ||||||||
| #include "pass.h" | ||||||||
| #include "support/strongly_connected_components.h" | ||||||||
| #include "wasm.h" | ||||||||
|
|
@@ -39,6 +40,9 @@ struct FuncInfo { | |||||||
|
|
||||||||
| // Directly-called functions from this function. | ||||||||
| std::unordered_set<Name> calledFunctions; | ||||||||
|
|
||||||||
| // Types that are targets of indirect calls. | ||||||||
| std::unordered_set<HeapType> indirectCalledTypes; | ||||||||
| }; | ||||||||
|
|
||||||||
| std::map<Function*, FuncInfo> analyzeFuncs(Module& module, | ||||||||
|
|
@@ -83,11 +87,19 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module, | |||||||
| if (auto* call = curr->dynCast<Call>()) { | ||||||||
| // Note the direct call. | ||||||||
| funcInfo.calledFunctions.insert(call->target); | ||||||||
| } else if (effects.calls && options.closedWorld) { | ||||||||
| HeapType type; | ||||||||
| if (auto* callRef = curr->dynCast<CallRef>()) { | ||||||||
| type = callRef->target->type.getHeapType(); | ||||||||
| } else if (auto* callIndirect = curr->dynCast<CallIndirect>()) { | ||||||||
| type = callIndirect->heapType; | ||||||||
| } else { | ||||||||
| Fatal() << "Unexpected call type"; | ||||||||
| } | ||||||||
|
|
||||||||
| funcInfo.indirectCalledTypes.insert(type); | ||||||||
| } else if (effects.calls) { | ||||||||
| // This is an indirect call of some sort, so we must assume the | ||||||||
| // worst. To do so, clear the effects, which indicates nothing | ||||||||
| // is known (so anything is possible). | ||||||||
| // TODO: We could group effects by function type etc. | ||||||||
| assert(!options.closedWorld); | ||||||||
| funcInfo.effects = UnknownEffects; | ||||||||
| } else { | ||||||||
| // No call here, but update throwing if we see it. (Only do so, | ||||||||
|
|
@@ -107,20 +119,56 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module, | |||||||
| return std::move(analysis.map); | ||||||||
| } | ||||||||
|
|
||||||||
| using CallGraph = std::unordered_map<Function*, std::unordered_set<Function*>>; | ||||||||
|
|
||||||||
| CallGraph buildCallGraph(const Module& module, | ||||||||
| const std::map<Function*, FuncInfo>& funcInfos) { | ||||||||
| using CallGraphNode = std::variant<Function*, HeapType>; | ||||||||
|
stevenfontanella marked this conversation as resolved.
|
||||||||
| using CallGraph = | ||||||||
| std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>; | ||||||||
|
|
||||||||
| /* Build a call graph for indirect and direct calls. | ||||||||
|
|
||||||||
| key (caller) -> value (callee) | ||||||||
| Name -> Name : direct call | ||||||||
| Name -> HeapType : indirect call to the given HeapType | ||||||||
| HeapType -> Name : The function `callee` has the type `caller`. The | ||||||||
| HeapType may essentially 'call' any of its | ||||||||
| potential implementations. | ||||||||
| HeapType -> HeapType : `callee` is a subtype of `caller`. A call_ref | ||||||||
| could target any subtype of the ref, so we need to | ||||||||
| aggregate effects of subtypes of the target type. | ||||||||
|
|
||||||||
| If we're running in an open world, we only include Name -> Name edges. | ||||||||
| */ | ||||||||
| CallGraph buildCallGraph(Module& module, | ||||||||
| const std::map<Function*, FuncInfo>& funcInfos, | ||||||||
| bool closedWorld) { | ||||||||
| CallGraph callGraph; | ||||||||
| for (const auto& [func, info] : funcInfos) { | ||||||||
| if (info.calledFunctions.empty()) { | ||||||||
|
|
||||||||
| std::unordered_set<HeapType> allFunctionTypes; | ||||||||
|
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
Member
Author
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. Not sure if I got the comment, you wanted the below comment about open world, or did you want a comment explaining why we're noting allFunctionTypes?
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. I was just suggesting that, after the previous comment that documents what we do in open world, we could document briefly how closed world is different.
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. (Though if we merge the loops as I suggested elsewhere, maybe neither is needed?) |
||||||||
| for (const auto& [caller, callerInfo] : funcInfos) { | ||||||||
| auto& callees = callGraph[caller]; | ||||||||
| for (Name calleeFunction : callerInfo.calledFunctions) { | ||||||||
| callees.insert(module.getFunction(calleeFunction)); | ||||||||
| } | ||||||||
|
|
||||||||
| // In open world, just connect functions. Indirect calls are already handled | ||||||||
| // by giving such functions unknown effects. | ||||||||
| if (!closedWorld) { | ||||||||
| continue; | ||||||||
| } | ||||||||
|
stevenfontanella marked this conversation as resolved.
|
||||||||
|
|
||||||||
| auto& callees = callGraph[func]; | ||||||||
| for (Name callee : info.calledFunctions) { | ||||||||
| callees.insert(module.getFunction(callee)); | ||||||||
| allFunctionTypes.insert(caller->type.getHeapType()); | ||||||||
| for (HeapType calleeType : callerInfo.indirectCalledTypes) { | ||||||||
| callees.insert(calleeType); | ||||||||
| allFunctionTypes.insert(calleeType); | ||||||||
| } | ||||||||
| callGraph[caller->type.getHeapType()].insert(caller); | ||||||||
| } | ||||||||
|
|
||||||||
| SubTypes subtypes(module); | ||||||||
|
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. Since we already have all the function types, we can just walk up their supertype chains to construct the edges from supertypes to subtypes rather than using a |
||||||||
| for (HeapType type : allFunctionTypes) { | ||||||||
| subtypes.iterSubTypes(type, [&callGraph, type](HeapType sub, auto _) { | ||||||||
| callGraph[type].insert(sub); | ||||||||
| return true; | ||||||||
| }); | ||||||||
| } | ||||||||
|
|
||||||||
| return callGraph; | ||||||||
|
|
@@ -153,61 +201,58 @@ void propagateEffects(const Module& module, | |||||||
| std::map<Function*, FuncInfo>& funcInfos, | ||||||||
| const CallGraph& callGraph) { | ||||||||
| struct CallGraphSCCs | ||||||||
| : SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs> { | ||||||||
| : SCCs<std::vector<CallGraphNode>::const_iterator, CallGraphSCCs> { | ||||||||
| const std::map<Function*, FuncInfo>& funcInfos; | ||||||||
| const std::unordered_map<Function*, std::unordered_set<Function*>>& | ||||||||
| callGraph; | ||||||||
| const CallGraph& callGraph; | ||||||||
| const Module& module; | ||||||||
|
|
||||||||
| CallGraphSCCs( | ||||||||
| const std::vector<Function*>& funcs, | ||||||||
| const std::vector<CallGraphNode>& nodes, | ||||||||
| const std::map<Function*, FuncInfo>& funcInfos, | ||||||||
| const std::unordered_map<Function*, std::unordered_set<Function*>>& | ||||||||
| callGraph, | ||||||||
| const std::unordered_map<CallGraphNode, | ||||||||
| std::unordered_set<CallGraphNode>>& callGraph, | ||||||||
| const Module& module) | ||||||||
| : SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs>( | ||||||||
| funcs.begin(), funcs.end()), | ||||||||
| : SCCs<std::vector<CallGraphNode>::const_iterator, CallGraphSCCs>( | ||||||||
| nodes.begin(), nodes.end()), | ||||||||
| funcInfos(funcInfos), callGraph(callGraph), module(module) {} | ||||||||
|
|
||||||||
| void pushChildren(Function* f) { | ||||||||
| auto callees = callGraph.find(f); | ||||||||
| if (callees == callGraph.end()) { | ||||||||
| return; | ||||||||
| } | ||||||||
|
|
||||||||
| for (auto* callee : callees->second) { | ||||||||
| void pushChildren(CallGraphNode node) { | ||||||||
| for (CallGraphNode callee : callGraph.at(node)) { | ||||||||
| push(callee); | ||||||||
| } | ||||||||
| } | ||||||||
| }; | ||||||||
|
|
||||||||
| std::vector<Function*> allFuncs; | ||||||||
| // We only care about Functions that are roots, not types | ||||||||
| // A type would be a root if a function exists with that type, but no-one | ||||||||
| // indirect calls the type. | ||||||||
| std::vector<CallGraphNode> allFuncs; | ||||||||
|
Comment on lines
+226
to
+229
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. I think this is fine, but it's more obviously correct to just pass all the graph nodes into the SCC utility. And then because we have C++20 now, we can avoid creating this vector entirely and use |
||||||||
| for (auto& [func, info] : funcInfos) { | ||||||||
| allFuncs.push_back(func); | ||||||||
| } | ||||||||
|
|
||||||||
| CallGraphSCCs sccs(allFuncs, funcInfos, callGraph, module); | ||||||||
|
|
||||||||
| std::vector<std::optional<EffectAnalyzer>> componentEffects; | ||||||||
| // Points to an index in componentEffects | ||||||||
| std::unordered_map<Function*, Index> funcComponents; | ||||||||
| std::unordered_map<CallGraphNode, Index> funcComponents; | ||||||||
|
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
|
||||||||
|
|
||||||||
| for (auto ccIterator : sccs) { | ||||||||
| std::optional<EffectAnalyzer>& ccEffects = | ||||||||
| componentEffects.emplace_back(std::in_place, passOptions, module); | ||||||||
| std::vector<CallGraphNode> cc(ccIterator.begin(), ccIterator.end()); | ||||||||
|
|
||||||||
| std::vector<Function*> ccFuncs(ccIterator.begin(), ccIterator.end()); | ||||||||
|
|
||||||||
| for (Function* f : ccFuncs) { | ||||||||
| funcComponents.emplace(f, componentEffects.size() - 1); | ||||||||
| std::vector<Function*> ccFuncs; | ||||||||
| for (CallGraphNode node : cc) { | ||||||||
| funcComponents.emplace(node, componentEffects.size() - 1); | ||||||||
| if (auto** func = std::get_if<Function*>(&node)) { | ||||||||
| ccFuncs.push_back(*func); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| std::unordered_set<int> calleeSccs; | ||||||||
| for (Function* caller : ccFuncs) { | ||||||||
| auto callees = callGraph.find(caller); | ||||||||
| if (callees == callGraph.end()) { | ||||||||
| continue; | ||||||||
| } | ||||||||
| for (auto* callee : callees->second) { | ||||||||
| for (CallGraphNode caller : cc) { | ||||||||
| for (CallGraphNode callee : callGraph.at(caller)) { | ||||||||
| calleeSccs.insert(funcComponents.at(callee)); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
@@ -219,11 +264,13 @@ void propagateEffects(const Module& module, | |||||||
| } | ||||||||
|
|
||||||||
| // Add trap effects for potential cycles. | ||||||||
| if (ccFuncs.size() > 1) { | ||||||||
| if (cc.size() > 1) { | ||||||||
| if (ccEffects != UnknownEffects) { | ||||||||
| ccEffects->trap = true; | ||||||||
| } | ||||||||
| } else { | ||||||||
| } else if (ccFuncs.size() == 1) { | ||||||||
| // It's possible for a CC to only contain 1 type, but that is not a | ||||||||
| // cycle in the call graph. | ||||||||
| auto* func = ccFuncs[0]; | ||||||||
| if (funcInfos.at(func).calledFunctions.contains(func->name)) { | ||||||||
| if (ccEffects != UnknownEffects) { | ||||||||
|
|
@@ -267,7 +314,8 @@ struct GenerateGlobalEffects : public Pass { | |||||||
| std::map<Function*, FuncInfo> funcInfos = | ||||||||
| analyzeFuncs(*module, getPassOptions()); | ||||||||
|
|
||||||||
| auto callGraph = buildCallGraph(*module, funcInfos); | ||||||||
| auto callGraph = | ||||||||
| buildCallGraph(*module, funcInfos, getPassOptions().closedWorld); | ||||||||
|
|
||||||||
| propagateEffects(*module, getPassOptions(), funcInfos, callGraph); | ||||||||
|
|
||||||||
|
|
||||||||
Uh oh!
There was an error while loading. Please reload this page.