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+ 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+
30105struct 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+
205203Pass* createGenerateGlobalEffectsPass () { return new GenerateGlobalEffects (); }
206204
207205Pass* createDiscardGlobalEffectsPass () { return new DiscardGlobalEffects (); }
0 commit comments