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
2829namespace 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+
3072struct 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+
205211Pass* createGenerateGlobalEffectsPass () { return new GenerateGlobalEffects (); }
206212
207213Pass* createDiscardGlobalEffectsPass () { return new DiscardGlobalEffects (); }
0 commit comments