Skip to content

Commit a9c493e

Browse files
Factor out some code
Remove the flip
1 parent eb6c79d commit a9c493e

1 file changed

Lines changed: 58 additions & 52 deletions

File tree

src/passes/GlobalEffects.cpp

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,53 @@
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+
template<typename T>
34+
std::unordered_map<T, std::unordered_set<T>>
35+
transitiveClosure(const std::unordered_map<T, std::unordered_set<T>>& in) {
36+
std::unordered_map<T, std::unordered_set<T>> transitive;
37+
38+
std::unordered_set<std::pair<T, T>> processed;
39+
std::deque<std::pair<T, T>> work;
40+
41+
for (const auto& [k, neighbors] : in) {
42+
for (const auto& neighbor : neighbors) {
43+
work.emplace_back(k, neighbor);
44+
processed.emplace(k, neighbor);
45+
}
46+
}
47+
48+
while (!work.empty()) {
49+
auto [curr, next] = work.back();
50+
work.pop_back();
51+
52+
transitive[curr].insert(next);
53+
54+
const auto& neighborNeighbors = in.find(next);
55+
if (neighborNeighbors == in.end()) {
56+
continue;
57+
}
58+
59+
for (const T& neighborNeighbor : neighborNeighbors->second) {
60+
if (processed.count({curr, neighborNeighbor})) {
61+
continue;
62+
}
63+
64+
processed.emplace(curr, neighborNeighbor);
65+
work.emplace_back(curr, neighborNeighbor);
66+
}
67+
}
68+
69+
return transitive;
70+
}
71+
3072
struct GenerateGlobalEffects : public Pass {
3173
void run(Module* module) override {
3274
// First, we do a scan of each function to see what effects they have,
@@ -108,76 +150,38 @@ struct GenerateGlobalEffects : public Pass {
108150
// callers[foo] = [func that calls foo, another func that calls foo, ..]
109151
//
110152
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});
119-
}
153+
for (const auto& [func, info] : analysis.map) {
154+
callers[func->name].insert(info.calledFunctions.begin(),
155+
info.calledFunctions.end());
120156
}
121157

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-
}
158+
auto callersTransitive = transitiveClosure(callers);
148159

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).
160+
// Check for functions that may have infinite recursion
153161
for (auto& [func, info] : analysis.map) {
154-
if (callers[func->name].contains(func->name)) {
162+
if (auto it = callersTransitive.find(func->name);
163+
it != callersTransitive.end() && it->second.contains(func->name)) {
155164
if (info.effects) {
156165
info.effects->trap = true;
157166
}
158167
}
159168
}
160169

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;
170+
for (const auto& [caller, callees] : callersTransitive) {
171+
auto& callerEffects = analysis.map[module->getFunction(caller)].effects;
172+
for (const auto& callee : callees) {
173+
const auto& calleeEffects =
174+
analysis.map[module->getFunction(callee)].effects;
167175
if (!callerEffects) {
168-
// Nothing is known for the caller, which is already the worst case.
169176
continue;
170177
}
171178

172-
if (!funcEffects) {
173-
// Nothing is known for the called function, which means nothing is
174-
// known for the caller either.
179+
if (!calleeEffects) {
175180
callerEffects.reset();
176181
continue;
177182
}
178183

179-
// Add func's effects to the caller.
180-
callerEffects->mergeIn(*funcEffects);
184+
callerEffects->mergeIn(*calleeEffects);
181185
}
182186
}
183187

@@ -202,6 +206,8 @@ struct DiscardGlobalEffects : public Pass {
202206
}
203207
};
204208

209+
} // namespace
210+
205211
Pass* createGenerateGlobalEffectsPass() { return new GenerateGlobalEffects(); }
206212

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

0 commit comments

Comments
 (0)