Skip to content

Commit 2d093c2

Browse files
NFC: Propagate global effects via strongly-connected components (#8607)
Helps prevent huge visited sets and work queues for edges when traversing large call graphs. After adding support for indirect call effects, the previous algorithm would OOM or timeout when computing global effects for large binaries like calcworker. Yields a small runtime improvement when tested on calcworker: 0.13451015 -> 0.1328012 (1.3%, averaged from 20 tries with a release build). Part of #8615.
1 parent 069c945 commit 2d093c2

File tree

1 file changed

+136
-54
lines changed

1 file changed

+136
-54
lines changed

src/passes/GlobalEffects.cpp

Lines changed: 136 additions & 54 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,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

Comments
 (0)