2222#include " ir/effects.h"
2323#include " ir/module-utils.h"
2424#include " pass.h"
25- #include " support/unique_deferring_queue .h"
25+ #include " support/strongly_connected_components .h"
2626#include " wasm.h"
2727
2828namespace wasm {
@@ -107,61 +107,158 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
107107 return std::move (analysis.map );
108108}
109109
110+ using CallGraph = std::unordered_map<Function*, std::unordered_set<Function*>>;
111+
112+ CallGraph buildCallGraph (const Module& module ,
113+ const std::map<Function*, FuncInfo>& funcInfos) {
114+ CallGraph callGraph;
115+ for (const auto & [func, info] : funcInfos) {
116+ if (info.calledFunctions .empty ()) {
117+ continue ;
118+ }
119+
120+ auto & callees = callGraph[func];
121+ for (Name callee : info.calledFunctions ) {
122+ callees.insert (module .getFunction (callee));
123+ }
124+ }
125+
126+ return callGraph;
127+ }
128+
129+ void mergeMaybeEffects (std::optional<EffectAnalyzer>& dest,
130+ const std::optional<EffectAnalyzer>& src) {
131+ if (dest == UnknownEffects) {
132+ return ;
133+ }
134+ if (src == UnknownEffects) {
135+ dest = UnknownEffects;
136+ return ;
137+ }
138+
139+ dest->mergeIn (*src);
140+ }
141+
110142// Propagate effects from callees to callers transitively
111143// e.g. if A -> B -> C (A calls B which calls C)
112144// 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;
145+ //
146+ // Generate SCC for the call graph, then traverse it in reverse topological
147+ // order processing each callee before its callers. When traversing:
148+ // - Merge all of the effects of functions within the CC
149+ // - Also merge the (already computed) effects of each callee CC
150+ // - Add trap effects for potentially recursive call chains
151+ void propagateEffects (const Module& module ,
152+ const PassOptions& passOptions,
153+ std::map<Function*, FuncInfo>& funcInfos,
154+ const CallGraph& callGraph) {
155+ struct CallGraphSCCs
156+ : SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs> {
157+ const std::map<Function*, FuncInfo>& funcInfos;
158+ const std::unordered_map<Function*, std::unordered_set<Function*>>&
159+ callGraph;
160+ const Module& module ;
161+
162+ CallGraphSCCs (
163+ const std::vector<Function*>& funcs,
164+ const std::map<Function*, FuncInfo>& funcInfos,
165+ const std::unordered_map<Function*, std::unordered_set<Function*>>&
166+ callGraph,
167+ const Module& module )
168+ : SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs>(
169+ funcs.begin(), funcs.end()),
170+ funcInfos (funcInfos), callGraph(callGraph), module (module ) {}
171+
172+ void pushChildren (Function* f) {
173+ auto callees = callGraph.find (f);
174+ if (callees == callGraph.end ()) {
175+ return ;
176+ }
119177
120- for (const auto & [ callee, callers] : reverseCallGraph ) {
121- for ( const auto & caller : callers) {
122- work. push ( std::pair (callee, caller));
178+ for (auto * callee : callees-> second ) {
179+ push (callee);
180+ }
123181 }
182+ };
183+
184+ std::vector<Function*> allFuncs;
185+ for (auto & [func, info] : funcInfos) {
186+ allFuncs.push_back (func);
124187 }
188+ CallGraphSCCs sccs (allFuncs, funcInfos, callGraph, module );
189+
190+ std::vector<std::optional<EffectAnalyzer>> componentEffects;
191+ // Points to an index in componentEffects
192+ std::unordered_map<Function*, Index> funcComponents;
125193
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 ;
194+ for (auto ccIterator : sccs) {
195+ std::optional<EffectAnalyzer>& ccEffects =
196+ componentEffects.emplace_back (std::in_place, passOptions, module );
197+
198+ std::vector<Function*> ccFuncs (ccIterator.begin (), ccIterator.end ());
199+
200+ for (Function* f : ccFuncs) {
201+ funcComponents.emplace (f, componentEffects.size () - 1 );
132202 }
133203
134- if (!calleeEffects) {
135- callerEffects = UnknownEffects;
136- return ;
204+ std::unordered_set<int > calleeSccs;
205+ for (Function* caller : ccFuncs) {
206+ auto callees = callGraph.find (caller);
207+ if (callees == callGraph.end ()) {
208+ continue ;
209+ }
210+ for (auto * callee : callees->second ) {
211+ calleeSccs.insert (funcComponents.at (callee));
212+ }
137213 }
138214
139- callerEffects->mergeIn (*calleeEffects);
140- };
215+ // Merge in effects from callees
216+ for (int calleeScc : calleeSccs) {
217+ const auto & calleeComponentEffects = componentEffects.at (calleeScc);
218+ mergeMaybeEffects (ccEffects, calleeComponentEffects);
219+ }
141220
142- while (!work.empty ()) {
143- auto [callee, caller] = work.pop ();
221+ // Add trap effects for potential cycles.
222+ if (ccFuncs.size () > 1 ) {
223+ if (ccEffects != UnknownEffects) {
224+ ccEffects->trap = true ;
225+ }
226+ } else {
227+ auto * func = ccFuncs[0 ];
228+ if (funcInfos.at (func).calledFunctions .contains (func->name )) {
229+ if (ccEffects != UnknownEffects) {
230+ ccEffects->trap = true ;
231+ }
232+ }
233+ }
144234
145- if (callee == caller) {
146- auto & callerEffects = funcInfos.at (module .getFunction (caller)).effects ;
147- if (callerEffects) {
148- callerEffects->trap = true ;
235+ // Aggregate effects within this CC
236+ if (ccEffects) {
237+ for (Function* f : ccFuncs) {
238+ const auto & effects = funcInfos.at (f).effects ;
239+ mergeMaybeEffects (ccEffects, effects);
149240 }
150241 }
151242
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);
243+ // Assign each function's effects to its CC effects.
244+ for (Function* f : ccFuncs) {
245+ if (!ccEffects) {
246+ funcInfos.at (f).effects = UnknownEffects;
247+ } else {
248+ funcInfos.at (f).effects .emplace (*ccEffects);
249+ }
250+ }
251+ }
252+ }
156253
157- const auto & callerCallers = reverseCallGraph.find (caller);
158- if (callerCallers == reverseCallGraph.end ()) {
254+ void copyEffectsToFunctions (const std::map<Function*, FuncInfo>& funcInfos) {
255+ for (auto & [func, info] : funcInfos) {
256+ func->effects .reset ();
257+ if (!info.effects ) {
159258 continue ;
160259 }
161260
162- for (const Name& callerCaller : callerCallers->second ) {
163- work.push (std::pair (callee, callerCaller));
164- }
261+ func->effects = std::make_shared<EffectAnalyzer>(*info.effects );
165262 }
166263}
167264
@@ -170,26 +267,11 @@ struct GenerateGlobalEffects : public Pass {
170267 std::map<Function*, FuncInfo> funcInfos =
171268 analyzeFuncs (*module , getPassOptions ());
172269
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 );
178- }
179- }
270+ auto callGraph = buildCallGraph (*module , funcInfos);
180271
181- propagateEffects (*module , callers , funcInfos);
272+ propagateEffects (*module , getPassOptions () , funcInfos, callGraph );
182273
183- // Generate the final data, starting from a blank slate where nothing is
184- // known.
185- for (auto & [func, info] : funcInfos) {
186- func->effects .reset ();
187- if (!info.effects ) {
188- continue ;
189- }
190-
191- func->effects = std::make_shared<EffectAnalyzer>(*info.effects );
192- }
274+ copyEffectsToFunctions (funcInfos);
193275 }
194276};
195277
0 commit comments