Skip to content

Commit 113c475

Browse files
Refactor to use graph traversal
1 parent eb6c79d commit 113c475

File tree

1 file changed

+82
-84
lines changed

1 file changed

+82
-84
lines changed

src/passes/GlobalEffects.cpp

Lines changed: 82 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,92 @@
2222
#include "ir/effects.h"
2323
#include "ir/module-utils.h"
2424
#include "pass.h"
25+
#include "support/hash.h"
2526
#include "support/unique_deferring_queue.h"
2627
#include "wasm.h"
2728

2829
namespace wasm {
2930

31+
namespace {
32+
33+
struct FuncInfo {
34+
// Effects in this function.
35+
std::optional<EffectAnalyzer> effects;
36+
37+
// Directly-called functions from this function.
38+
std::unordered_set<Name> calledFunctions;
39+
};
40+
41+
// Propagate effects from callees to callers transitively
42+
// e.g. if A -> B -> C (A calls B which calls C)
43+
// Then B inherits effects from C and A inherits effects from both B and C.
44+
void propagateEffects(
45+
const Module& module,
46+
const std::unordered_map<Name, std::unordered_set<Name>>& in,
47+
std::map<Function*, FuncInfo>& funcInfos) {
48+
49+
std::unordered_set<std::pair<Name, Name>> processed;
50+
std::deque<std::pair<Name, Name>> work;
51+
52+
for (const auto& [callee, callers] : in) {
53+
for (const auto& caller : callers) {
54+
work.emplace_back(callee, caller);
55+
processed.emplace(callee, caller);
56+
}
57+
}
58+
59+
auto propagate = [&](Name callee, Name caller) {
60+
auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects;
61+
const auto& calleeEffects =
62+
funcInfos.at(module.getFunction(callee)).effects;
63+
if (!callerEffects) {
64+
return;
65+
}
66+
67+
if (!calleeEffects) {
68+
callerEffects.reset();
69+
return;
70+
}
71+
72+
callerEffects->mergeIn(*calleeEffects);
73+
};
74+
75+
while (!work.empty()) {
76+
auto [callee, caller] = work.back();
77+
work.pop_back();
78+
79+
if (callee == caller) {
80+
auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects;
81+
if (callerEffects) {
82+
callerEffects->trap = true;
83+
}
84+
}
85+
86+
// TODO: can have early return here
87+
propagate(callee, caller);
88+
89+
const auto& callerCallers = in.find(caller);
90+
if (callerCallers == in.end()) {
91+
continue;
92+
}
93+
94+
for (const Name& callerCaller : callerCallers->second) {
95+
if (processed.contains({callee, callerCaller})) {
96+
continue;
97+
}
98+
99+
processed.emplace(callee, callerCaller);
100+
work.emplace_back(callee, callerCaller);
101+
}
102+
}
103+
}
104+
30105
struct GenerateGlobalEffects : public Pass {
31106
void run(Module* module) override {
32107
// First, we do a scan of each function to see what effects they have,
33108
// including which functions they call directly (so that we can compute
34109
// transitive effects later).
35110

36-
struct FuncInfo {
37-
// Effects in this function.
38-
std::optional<EffectAnalyzer> effects;
39-
40-
// Directly-called functions from this function.
41-
std::unordered_set<Name> calledFunctions;
42-
};
43-
44111
ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis(
45112
*module, [&](Function* func, FuncInfo& funcInfo) {
46113
if (func->imported()) {
@@ -100,86 +167,15 @@ struct GenerateGlobalEffects : public Pass {
100167
}
101168
});
102169

103-
// Compute the transitive closure of effects. To do so, first construct for
104-
// each function a list of the functions that it is called by (so we need to
105-
// propagate its effects to them), and then we'll construct the closure of
106-
// that.
107-
//
108-
// callers[foo] = [func that calls foo, another func that calls foo, ..]
109-
//
170+
// callee : caller
110171
std::unordered_map<Name, std::unordered_set<Name>> callers;
111-
112-
// Our work queue contains info about a new call pair: a call from a caller
113-
// to a called function, that is information we then apply and propagate.
114-
using CallPair = std::pair<Name, Name>; // { caller, called }
115-
UniqueDeferredQueue<CallPair> work;
116-
for (auto& [func, info] : analysis.map) {
117-
for (auto& called : info.calledFunctions) {
118-
work.push({func->name, called});
172+
for (const auto& [func, info] : analysis.map) {
173+
for (const auto& callee : info.calledFunctions) {
174+
callers[callee].insert(func->name);
119175
}
120176
}
121177

122-
// Compute the transitive closure of the call graph, that is, fill out
123-
// |callers| so that it contains the list of all callers - even through a
124-
// chain - of each function.
125-
while (!work.empty()) {
126-
auto [caller, called] = work.pop();
127-
128-
// We must not already have an entry for this call (that would imply we
129-
// are doing wasted work).
130-
assert(!callers[called].contains(caller));
131-
132-
// Apply the new call information.
133-
callers[called].insert(caller);
134-
135-
// We just learned that |caller| calls |called|. It also calls
136-
// transitively, which we need to propagate to all places unaware of that
137-
// information yet.
138-
//
139-
// caller => called => called by called
140-
//
141-
auto& calledInfo = analysis.map[module->getFunction(called)];
142-
for (auto calledByCalled : calledInfo.calledFunctions) {
143-
if (!callers[calledByCalled].contains(caller)) {
144-
work.push({caller, calledByCalled});
145-
}
146-
}
147-
}
148-
149-
// Now that we have transitively propagated all static calls, apply that
150-
// information. First, apply infinite recursion: if a function can call
151-
// itself then it might recurse infinitely, which we consider an effect (a
152-
// trap).
153-
for (auto& [func, info] : analysis.map) {
154-
if (callers[func->name].contains(func->name)) {
155-
if (info.effects) {
156-
info.effects->trap = true;
157-
}
158-
}
159-
}
160-
161-
// Next, apply function effects to their callers.
162-
for (auto& [func, info] : analysis.map) {
163-
auto& funcEffects = info.effects;
164-
165-
for (auto& caller : callers[func->name]) {
166-
auto& callerEffects = analysis.map[module->getFunction(caller)].effects;
167-
if (!callerEffects) {
168-
// Nothing is known for the caller, which is already the worst case.
169-
continue;
170-
}
171-
172-
if (!funcEffects) {
173-
// Nothing is known for the called function, which means nothing is
174-
// known for the caller either.
175-
callerEffects.reset();
176-
continue;
177-
}
178-
179-
// Add func's effects to the caller.
180-
callerEffects->mergeIn(*funcEffects);
181-
}
182-
}
178+
propagateEffects(*module, callers, analysis.map);
183179

184180
// Generate the final data, starting from a blank slate where nothing is
185181
// known.
@@ -202,6 +198,8 @@ struct DiscardGlobalEffects : public Pass {
202198
}
203199
};
204200

201+
} // namespace
202+
205203
Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); }
206204

207205
Pass* createDiscardGlobalEffectsPass() { return new DiscardGlobalEffects(); }

0 commit comments

Comments
 (0)