2727
2828namespace wasm {
2929
30- struct GenerateGlobalEffects : public Pass {
31- void run (Module* module ) override {
32- // First, we do a scan of each function to see what effects they have,
33- // including which functions they call directly (so that we can compute
34- // transitive effects later).
35-
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-
44- ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis (
45- *module , [&](Function* func, FuncInfo& funcInfo) {
46- if (func->imported ()) {
47- // Imports can do anything, so we need to assume the worst anyhow,
48- // which is the same as not specifying any effects for them in the
49- // map (which we do by not setting funcInfo.effects).
50- return ;
51- }
52-
53- // Gather the effects.
54- funcInfo.effects .emplace (getPassOptions (), *module , func);
55-
56- if (funcInfo.effects ->calls ) {
57- // There are calls in this function, which we will analyze in detail.
58- // Clear the |calls| field first, and we'll handle calls of all sorts
59- // below.
60- funcInfo.effects ->calls = false ;
61-
62- // Clear throws as well, as we are "forgetting" calls right now, and
63- // want to forget their throwing effect as well. If we see something
64- // else that throws, below, then we'll note that there.
65- funcInfo.effects ->throws_ = false ;
66-
67- struct CallScanner
68- : public PostWalker<CallScanner,
69- UnifiedExpressionVisitor<CallScanner>> {
70- Module& wasm;
71- PassOptions& options;
72- FuncInfo& funcInfo;
73-
74- CallScanner (Module& wasm, PassOptions& options, FuncInfo& funcInfo)
75- : wasm(wasm), options(options), funcInfo(funcInfo) {}
76-
77- void visitExpression (Expression* curr) {
78- ShallowEffectAnalyzer effects (options, wasm, curr);
79- if (auto * call = curr->dynCast <Call>()) {
80- // Note the direct call.
81- funcInfo.calledFunctions .insert (call->target );
82- } else if (effects.calls ) {
83- // This is an indirect call of some sort, so we must assume the
84- // worst. To do so, clear the effects, which indicates nothing
85- // is known (so anything is possible).
86- // TODO: We could group effects by function type etc.
87- funcInfo.effects .reset ();
88- } else {
89- // No call here, but update throwing if we see it. (Only do so,
90- // however, if we have effects; if we cleared it - see before -
91- // then we assume the worst anyhow, and have nothing to update.)
92- if (effects.throws_ && funcInfo.effects ) {
93- funcInfo.effects ->throws_ = true ;
94- }
30+ namespace {
31+
32+ constexpr auto UnknownEffects = std::nullopt ;
33+
34+ struct FuncInfo {
35+ // Effects in this function. nullopt / UnknownEffects means that we don't know
36+ // what effects this function has, so we conservatively assume all effects.
37+ // Nullopt cases won't be copied to Function::effects.
38+ std::optional<EffectAnalyzer> effects;
39+
40+ // Directly-called functions from this function.
41+ std::unordered_set<Name> calledFunctions;
42+ };
43+
44+ std::map<Function*, FuncInfo> analyzeFuncs (Module& module ,
45+ const PassOptions& passOptions) {
46+ ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis (
47+ module , [&](Function* func, FuncInfo& funcInfo) {
48+ if (func->imported ()) {
49+ // Imports can do anything, so we need to assume the worst anyhow,
50+ // which is the same as not specifying any effects for them in the
51+ // map (which we do by not setting funcInfo.effects).
52+ return ;
53+ }
54+
55+ // Gather the effects.
56+ funcInfo.effects .emplace (passOptions, module , func);
57+
58+ if (funcInfo.effects ->calls ) {
59+ // There are calls in this function, which we will analyze in detail.
60+ // Clear the |calls| field first, and we'll handle calls of all sorts
61+ // below.
62+ funcInfo.effects ->calls = false ;
63+
64+ // Clear throws as well, as we are "forgetting" calls right now, and
65+ // want to forget their throwing effect as well. If we see something
66+ // else that throws, below, then we'll note that there.
67+ funcInfo.effects ->throws_ = false ;
68+
69+ struct CallScanner
70+ : public PostWalker<CallScanner,
71+ UnifiedExpressionVisitor<CallScanner>> {
72+ Module& wasm;
73+ const PassOptions& options;
74+ FuncInfo& funcInfo;
75+
76+ CallScanner (Module& wasm,
77+ const PassOptions& options,
78+ FuncInfo& funcInfo)
79+ : wasm(wasm), options(options), funcInfo(funcInfo) {}
80+
81+ void visitExpression (Expression* curr) {
82+ ShallowEffectAnalyzer effects (options, wasm, curr);
83+ if (auto * call = curr->dynCast <Call>()) {
84+ // Note the direct call.
85+ funcInfo.calledFunctions .insert (call->target );
86+ } else if (effects.calls ) {
87+ // This is an indirect call of some sort, so we must assume the
88+ // worst. To do so, clear the effects, which indicates nothing
89+ // is known (so anything is possible).
90+ // TODO: We could group effects by function type etc.
91+ funcInfo.effects = UnknownEffects;
92+ } else {
93+ // No call here, but update throwing if we see it. (Only do so,
94+ // however, if we have effects; if we cleared it - see before -
95+ // then we assume the worst anyhow, and have nothing to update.)
96+ if (effects.throws_ && funcInfo.effects ) {
97+ funcInfo.effects ->throws_ = true ;
9598 }
9699 }
97- };
98- CallScanner scanner (*module , getPassOptions (), funcInfo);
99- scanner.walkFunction (func);
100- }
101- });
102-
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- //
110- 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});
100+ }
101+ };
102+ CallScanner scanner (module , passOptions, funcInfo);
103+ scanner.walkFunction (func);
119104 }
105+ });
106+
107+ return std::move (analysis.map );
108+ }
109+
110+ // Propagate effects from callees to callers transitively
111+ // e.g. if A -> B -> C (A calls B which calls C)
112+ // Then B inherits effects from C and A inherits effects from both B and C.
113+ void propagateEffects (
114+ const Module& module ,
115+ const std::unordered_map<Name, std::unordered_set<Name>>& reverseCallGraph,
116+ std::map<Function*, FuncInfo>& funcInfos) {
117+
118+ UniqueNonrepeatingDeferredQueue<std::pair<Name, Name>> work;
119+
120+ for (const auto & [callee, callers] : reverseCallGraph) {
121+ for (const auto & caller : callers) {
122+ work.push (std::pair (callee, caller));
120123 }
124+ }
121125
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- }
126+ auto propagate = [&](Name callee, Name caller) {
127+ auto & callerEffects = funcInfos.at (module .getFunction (caller)).effects ;
128+ const auto & calleeEffects =
129+ funcInfos.at (module .getFunction (callee)).effects ;
130+ if (!callerEffects) {
131+ return ;
147132 }
148133
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- }
134+ if (!calleeEffects) {
135+ callerEffects = UnknownEffects;
136+ return ;
137+ }
138+
139+ callerEffects->mergeIn (*calleeEffects);
140+ };
141+
142+ while (!work.empty ()) {
143+ auto [callee, caller] = work.pop ();
144+
145+ if (callee == caller) {
146+ auto & callerEffects = funcInfos.at (module .getFunction (caller)).effects ;
147+ if (callerEffects) {
148+ callerEffects->trap = true ;
158149 }
159150 }
160151
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);
152+ // Even if nothing changed, we still need to keep traversing the callers
153+ // to look for a potential cycle which adds a trap affect on the above
154+ // lines.
155+ propagate (callee, caller);
156+
157+ const auto & callerCallers = reverseCallGraph.find (caller);
158+ if (callerCallers == reverseCallGraph.end ()) {
159+ continue ;
160+ }
161+
162+ for (const Name& callerCaller : callerCallers->second ) {
163+ work.push (std::pair (callee, callerCaller));
164+ }
165+ }
166+ }
167+
168+ struct GenerateGlobalEffects : public Pass {
169+ void run (Module* module ) override {
170+ std::map<Function*, FuncInfo> funcInfos =
171+ analyzeFuncs (*module , getPassOptions ());
172+
173+ // callee : caller
174+ std::unordered_map<Name, std::unordered_set<Name>> callers;
175+ for (const auto & [func, info] : funcInfos) {
176+ for (const auto & callee : info.calledFunctions ) {
177+ callers[callee].insert (func->name );
181178 }
182179 }
183180
181+ propagateEffects (*module , callers, funcInfos);
182+
184183 // Generate the final data, starting from a blank slate where nothing is
185184 // known.
186- for (auto & [func, info] : analysis. map ) {
185+ for (auto & [func, info] : funcInfos ) {
187186 func->effects .reset ();
188187 if (!info.effects ) {
189188 continue ;
@@ -202,6 +201,8 @@ struct DiscardGlobalEffects : public Pass {
202201 }
203202};
204203
204+ } // namespace
205+
205206Pass* createGenerateGlobalEffectsPass () { return new GenerateGlobalEffects (); }
206207
207208Pass* createDiscardGlobalEffectsPass () { return new DiscardGlobalEffects (); }
0 commit comments