Skip to content

Commit 9bc667f

Browse files
Cherrypick SCC changes
1 parent fc43f0d commit 9bc667f

File tree

1 file changed

+130
-52
lines changed

1 file changed

+130
-52
lines changed

src/passes/GlobalEffects.cpp

Lines changed: 130 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
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

2828
namespace wasm {
@@ -107,89 +107,167 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
107107
return std::move(analysis.map);
108108
}
109109

110+
std::unordered_map<Function*, std::unordered_set<Function*>>
111+
buildCallGraph(const Module& module,
112+
const std::map<Function*, FuncInfo>& funcInfos) {
113+
std::unordered_map<Function*, std::unordered_set<Function*>> callGraph;
114+
for (const auto& [func, info] : funcInfos) {
115+
for (Name callee : info.calledFunctions) {
116+
callGraph[func].insert(module.getFunction(callee));
117+
}
118+
}
119+
120+
return callGraph;
121+
}
122+
110123
// Propagate effects from callees to callers transitively
111124
// e.g. if A -> B -> C (A calls B which calls C)
112125
// Then B inherits effects from C and A inherits effects from both B and C.
126+
//
127+
// Generate SCC for the call graph, then traverse it in reverse topological
128+
// order processing each callee before its callers. When traversing:
129+
// - Merge all of the effects of functions within the CC
130+
// - Also merge the (already computed) effects of each callee CC
131+
// - Add trap effects for potentially recursive call chains
113132
void propagateEffects(
114133
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;
134+
const PassOptions& passOptions,
135+
std::map<Function*, FuncInfo>& funcInfos,
136+
const std::unordered_map<Function*, std::unordered_set<Function*>>
137+
callGraph) {
138+
struct CallGraphSCCs
139+
: SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs> {
140+
const std::map<Function*, FuncInfo>& funcInfos;
141+
const std::unordered_map<Function*, std::unordered_set<Function*>>&
142+
callGraph;
143+
const Module& module;
144+
145+
CallGraphSCCs(
146+
const std::vector<Function*>& funcs,
147+
const std::map<Function*, FuncInfo>& funcInfos,
148+
const std::unordered_map<Function*, std::unordered_set<Function*>>&
149+
callGraph,
150+
const Module& module)
151+
: SCCs<std::vector<Function*>::const_iterator, CallGraphSCCs>(
152+
funcs.begin(), funcs.end()),
153+
funcInfos(funcInfos), callGraph(callGraph), module(module) {}
154+
155+
void pushChildren(Function* f) {
156+
auto callees = callGraph.find(f);
157+
if (callees == callGraph.end()) {
158+
return;
159+
}
119160

120-
for (const auto& [callee, callers] : reverseCallGraph) {
121-
for (const auto& caller : callers) {
122-
work.push(std::pair(callee, caller));
161+
for (auto* callee : callees->second) {
162+
push(callee);
163+
}
123164
}
165+
};
166+
167+
std::vector<Function*> allFuncs;
168+
for (auto& [func, info] : funcInfos) {
169+
allFuncs.push_back(func);
124170
}
171+
CallGraphSCCs sccs(allFuncs, funcInfos, callGraph, module);
172+
173+
std::unordered_map<Function*, int> sccMembers;
174+
std::unordered_map<int, std::optional<EffectAnalyzer>> componentEffects;
175+
176+
int ccIndex = 0;
177+
for (auto ccIterator : sccs) {
178+
ccIndex++;
179+
std::optional<EffectAnalyzer>& ccEffects = componentEffects[ccIndex];
180+
std::vector<Function*> ccFuncs(ccIterator.begin(), ccIterator.end());
181+
182+
ccEffects.emplace(passOptions, module);
125183

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;
184+
for (Function* f : ccFuncs) {
185+
sccMembers.emplace(f, ccIndex);
132186
}
133187

134-
if (!calleeEffects) {
135-
callerEffects = UnknownEffects;
136-
return;
188+
std::unordered_set<int> calleeSccs;
189+
for (Function* caller : ccFuncs) {
190+
auto callees = callGraph.find(caller);
191+
if (callees == callGraph.end()) {
192+
continue;
193+
}
194+
for (auto* callee : callees->second) {
195+
calleeSccs.insert(sccMembers.at(callee));
196+
}
137197
}
138198

139-
callerEffects->mergeIn(*calleeEffects);
140-
};
199+
// Merge in effects from callees
200+
for (int calleeScc : calleeSccs) {
201+
const auto& calleeComponentEffects = componentEffects.at(calleeScc);
202+
if (calleeComponentEffects == UnknownEffects) {
203+
ccEffects = UnknownEffects;
204+
break;
205+
}
141206

142-
while (!work.empty()) {
143-
auto [callee, caller] = work.pop();
207+
else if (ccEffects != UnknownEffects) {
208+
ccEffects->mergeIn(*calleeComponentEffects);
209+
}
210+
}
144211

145-
if (callee == caller) {
146-
auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects;
147-
if (callerEffects) {
148-
callerEffects->trap = true;
212+
// Add trap effects for potential cycles.
213+
if (ccFuncs.size() > 1) {
214+
if (ccEffects != UnknownEffects) {
215+
ccEffects->trap = true;
216+
}
217+
} else {
218+
auto* func = ccFuncs[0];
219+
if (funcInfos.at(func).calledFunctions.contains(func->name)) {
220+
if (ccEffects != UnknownEffects) {
221+
ccEffects->trap = true;
222+
}
149223
}
150224
}
151225

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);
226+
// Aggregate effects within this CC
227+
if (ccEffects) {
228+
for (Function* f : ccFuncs) {
229+
const auto& effects = funcInfos.at(f).effects;
230+
if (effects == UnknownEffects) {
231+
ccEffects = UnknownEffects;
232+
break;
233+
}
156234

157-
const auto& callerCallers = reverseCallGraph.find(caller);
158-
if (callerCallers == reverseCallGraph.end()) {
159-
continue;
235+
ccEffects->mergeIn(*effects);
236+
}
160237
}
161238

162-
for (const Name& callerCaller : callerCallers->second) {
163-
work.push(std::pair(callee, callerCaller));
239+
// Assign each function's effects to its CC effects.
240+
for (Function* f : ccFuncs) {
241+
if (!ccEffects) {
242+
funcInfos.at(f).effects = UnknownEffects;
243+
} else {
244+
funcInfos.at(f).effects.emplace(*ccEffects);
245+
}
164246
}
165247
}
166248
}
167249

250+
void copyEffectsToFunctions(const std::map<Function*, FuncInfo> funcInfos) {
251+
for (auto& [func, info] : funcInfos) {
252+
func->effects.reset();
253+
if (!info.effects) {
254+
continue;
255+
}
256+
257+
func->effects = std::make_shared<EffectAnalyzer>(*info.effects);
258+
}
259+
}
260+
168261
struct GenerateGlobalEffects : public Pass {
169262
void run(Module* module) override {
170263
std::map<Function*, FuncInfo> funcInfos =
171264
analyzeFuncs(*module, getPassOptions());
172265

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-
}
266+
auto callGraph = buildCallGraph(*module, funcInfos);
180267

181-
propagateEffects(*module, callers, funcInfos);
268+
propagateEffects(*module, getPassOptions(), funcInfos, callGraph);
182269

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-
}
270+
copyEffectsToFunctions(funcInfos);
193271
}
194272
};
195273

0 commit comments

Comments
 (0)