Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 91 additions & 43 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand Down Expand Up @@ -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();
Comment thread
stevenfontanella marked this conversation as resolved.
} else if (auto* callIndirect = curr->dynCast<CallIndirect>()) {
type = callIndirect->heapType;
} else {
Fatal() << "Unexpected call type";
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
}

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,
Expand All @@ -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>;
Comment thread
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.
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
*/
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
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;
Comment thread
stevenfontanella marked this conversation as resolved.
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;
}
Comment thread
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);
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
for (HeapType type : allFunctionTypes) {
subtypes.iterSubTypes(type, [&callGraph, type](HeapType sub, auto _) {
callGraph[type].insert(sub);
return true;
});
}

return callGraph;
Expand Down Expand Up @@ -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
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
// 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 thread
stevenfontanella marked this conversation as resolved.
Outdated
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;
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated

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));
}
}
Expand All @@ -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) {
Comment thread
stevenfontanella marked this conversation as resolved.
// 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) {
Expand Down Expand Up @@ -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);

Expand Down
Loading
Loading