1919// PassOptions structure; see more details there.
2020//
2121
22+ #include < ranges>
23+
2224#include " ir/effects.h"
2325#include " ir/module-utils.h"
2426#include " pass.h"
@@ -92,7 +94,7 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
9294 }
9395
9496 funcInfo.indirectCalledTypes .insert (type);
95- funcInfo.effects .reset ();
97+ // funcInfo.effects.reset();
9698 } else {
9799 // No call here, but update throwing if we see it. (Only do so,
98100 // however, if we have effects; if we cleared it - see before -
@@ -111,59 +113,131 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
111113 return analysis.map ;
112114}
113115
116+ std::unordered_map<HeapType, std::unordered_set<Name>>
117+ typeToFunctionNames (const Module& module ) {
118+ std::unordered_map<HeapType, std::unordered_set<Name>> ret;
119+
120+ for (const auto & func : module .functions ) {
121+ ret[func->type .getHeapType ()].insert (func->name );
122+ }
123+
124+ return ret;
125+ }
126+
127+ std::unordered_map<Name, std::unordered_set<Name>>
128+ transitiveClosure (const Module& module ,
129+ const std::map<Function*, FuncInfo>& funcInfos) {
130+ // Compute the transitive closure of effects. To do so, first construct for
131+ // each function a list of the functions that it is called by (so we need to
132+ // propogate its effects to them), and then we'll construct the closure of
133+ // that.
134+ //
135+ // callers[foo] = [func that calls foo, another func that calls foo, ..]
136+ //
137+ std::unordered_map<Name, std::unordered_set<Name>> callers;
138+
139+ // Our work queue contains info about a new call pair: a call from a caller
140+ // to a called function, that is information we then apply and propagate.
141+ using CallPair = std::pair<Name, Name>; // { caller, called }
142+ UniqueDeferredQueue<CallPair> work;
143+ for (auto & [func, info] : funcInfos) {
144+ for (auto & called : info.calledFunctions ) {
145+ work.push ({func->name , called});
146+ }
147+ }
148+
149+ // Compute the transitive closure of the call graph, that is, fill out
150+ // |callers| so that it contains the list of all callers - even through a
151+ // chain - of each function.
152+ while (!work.empty ()) {
153+ auto [caller, called] = work.pop ();
154+
155+ // We must not already have an entry for this call (that would imply we
156+ // are doing wasted work).
157+ assert (!callers[called].contains (caller));
158+
159+ // Apply the new call information.
160+ callers[called].insert (caller);
161+
162+ // We just learned that |caller| calls |called|. It also calls
163+ // transitively, which we need to propagate to all places unaware of that
164+ // information yet.
165+ //
166+ // caller => called => called by called
167+ //
168+ auto & calledInfo = funcInfos.at (module .getFunction (called));
169+ for (auto calledByCalled : calledInfo.calledFunctions ) {
170+ if (!callers[calledByCalled].contains (caller)) {
171+ work.push ({caller, calledByCalled});
172+ }
173+ }
174+ }
175+
176+ return callers;
177+ }
178+
179+ std::unordered_map<Name, std::unordered_set<Name>> transitiveClosure (
180+ const Module& module ,
181+ const std::unordered_map<Name, std::unordered_set<Name>>& funcInfos) {
182+ std::map<Function*, FuncInfo> other;
183+ auto _ =
184+ funcInfos | std::views::transform (
185+ [&](const auto & pair) -> std::pair<Function*, FuncInfo> {
186+ auto & [k, v] = pair;
187+
188+ auto & func = module .getFunction (k);
189+ FuncInfo info;
190+ info.calledFunctions = v;
191+ return {func->name , info};
192+ });
193+
194+ return transitiveClosure (module , other);
195+ }
196+
114197struct GenerateGlobalEffects : public Pass {
115198 void run (Module* module ) override {
116199 // First, we do a scan of each function to see what effects they have,
117200 // including which functions they call directly (so that we can compute
118201 // transitive effects later).
119202 auto funcInfos = analyzeFuncs (*module , getPassOptions ());
120203
121- // Our work queue contains info about a new call pair: a call from a caller
122- // to a called function, that is information we then apply and propagate.
123- using CallPair = std::pair<Name, Name>; // { caller, called }
124- UniqueDeferredQueue<CallPair> work;
125- for (auto & [func, info] : funcInfos) {
126- for (auto & called : info.calledFunctions ) {
127- work.push ({func->name , called});
128- }
129- }
130-
131204 // Compute the transitive closure of effects. To do so, first construct for
132205 // each function a list of the functions that it is called by (so we need to
133206 // propagate its effects to them), and then we'll construct the closure of
134207 // that.
135208 //
136209 // callers[foo] = [func that calls foo, another func that calls foo, ..]
137210 //
138- std::unordered_map<Name, std::unordered_set<Name>> callers;
139-
140- // Compute the transitive closure of the call graph, that is, fill out
141- // |callers| so that it contains the list of all callers - even through a
142- // chain - of each function.
143- while (!work.empty ()) {
144- auto [caller, called] = work.pop ();
145-
146- // We must not already have an entry for this call (that would imply we
147- // are doing wasted work).
148- assert (!callers[called].contains (caller));
149-
150- // Apply the new call information.
151- callers[called].insert (caller);
152-
153- // We just learned that |caller| calls |called|. It also calls
154- // transitively, which we need to propagate to all places unaware of that
155- // information yet.
156- //
157- // caller => called => called by called
158- //
159- auto & calledInfo = funcInfos[module ->getFunction (called)];
160- for (auto calledByCalled : calledInfo.calledFunctions ) {
161- if (!callers[calledByCalled].contains (caller)) {
162- work.push ({caller, calledByCalled});
211+ std::unordered_map<Name, std::unordered_set<Name>> callers =
212+ transitiveClosure (*module , funcInfos);
213+
214+ const auto functionsWithType = typeToFunctionNames (*module );
215+ std::unordered_map<Name, std::unordered_set<Name>>
216+ indirectCallersNonTransitive;
217+ for (auto & [func, info] : funcInfos) {
218+ for (auto & calledType : info.indirectCalledTypes ) {
219+ // auto asdf = functionsWithType.at(calledType);
220+ // auto foo = indirectCallersNonTransitive[func->name];
221+ // asdf.merge(foo);
222+ // foo.merge(asdf);
223+
224+ if (auto it = functionsWithType.find (calledType);
225+ it != functionsWithType.end ()) {
226+ indirectCallersNonTransitive[func->name ].insert (it->second .begin (),
227+ it->second .end ());
163228 }
229+ // indirectCallersNonTransitive[func->name].merge(functionsWithType.at(calledType));
164230 }
231+ // for (const auto& name : functionsWitType[])
232+ // for ()
233+ // info.indirectCalledTypes[func->name]
165234 }
166235
236+ // indirectCallers[foo] = [func that indirect calls something with the same
237+ // type as foo, ..]
238+ const std::unordered_map<Name, std::unordered_set<Name>> indirectCallers =
239+ transitiveClosure (*module , indirectCallersNonTransitive);
240+
167241 // Now that we have transitively propagated all static calls, apply that
168242 // information. First, apply infinite recursion: if a function can call
169243 // itself then it might recurse infinitely, which we consider an effect (a
@@ -180,7 +254,29 @@ struct GenerateGlobalEffects : public Pass {
180254 for (auto & [func, info] : funcInfos) {
181255 auto & funcEffects = info.effects ;
182256
183- for (auto & caller : callers[func->name ]) {
257+ for (const auto & caller : callers[func->name ]) {
258+ auto & callerEffects = funcInfos[module ->getFunction (caller)].effects ;
259+ if (!callerEffects) {
260+ // Nothing is known for the caller, which is already the worst case.
261+ continue ;
262+ }
263+
264+ if (!funcEffects) {
265+ // Nothing is known for the called function, which means nothing is
266+ // known for the caller either.
267+ callerEffects.reset ();
268+ continue ;
269+ }
270+
271+ // Add func's effects to the caller.
272+ callerEffects->mergeIn (*funcEffects);
273+ }
274+
275+ auto indirectCallersOfThisFunction = indirectCallers.find (func->name );
276+ if (indirectCallersOfThisFunction == indirectCallers.end ()) {
277+ continue ;
278+ }
279+ for (Name caller : indirectCallersOfThisFunction->second ) {
184280 auto & callerEffects = funcInfos[module ->getFunction (caller)].effects ;
185281 if (!callerEffects) {
186282 // Nothing is known for the caller, which is already the worst case.
0 commit comments