Skip to content

Commit c86c680

Browse files
done?
1 parent 2760fc2 commit c86c680

File tree

5 files changed

+181
-88
lines changed

5 files changed

+181
-88
lines changed

src/ir/module-utils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ template<typename T> inline void iterModuleItems(Module& wasm, T visitor) {
271271
template<typename K, typename V> using DefaultMap = std::map<K, V>;
272272
template<typename T,
273273
Mutability Mut = Immutable,
274-
template<typename, typename> class MapT = DefaultMap>
274+
template<typename, typename, typename...> class MapT = DefaultMap>
275275
struct ParallelFunctionAnalysis {
276276
Module& wasm;
277277

src/passes/GlobalEffects.cpp

Lines changed: 44 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ struct FuncInfo {
4747
std::unordered_set<HeapType> indirectCalledTypes;
4848
};
4949

50-
std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
51-
const PassOptions& passOptions) {
52-
ModuleUtils::ParallelFunctionAnalysis<FuncInfo> analysis(
53-
module, [&](Function* func, FuncInfo& funcInfo) {
50+
std::unordered_map<Function*, FuncInfo>
51+
analyzeFuncs(Module& module, const PassOptions& passOptions) {
52+
ModuleUtils::ParallelFunctionAnalysis<FuncInfo, Immutable, std::unordered_map>
53+
analysis(module, [&](Function* func, FuncInfo& funcInfo) {
5454
if (func->imported()) {
5555
// Imports can do anything, so we need to assume the worst anyhow,
5656
// which is the same as not specifying any effects for them in the
@@ -101,7 +101,7 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
101101
} else if (auto* callIndirect = curr->dynCast<CallIndirect>()) {
102102
type = callIndirect->heapType;
103103
} else {
104-
assert(false && "Unexpected type of call");
104+
assert("Unexpected type of call");
105105
}
106106

107107
funcInfo.indirectCalledTypes.insert(type);
@@ -123,58 +123,34 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
123123
return std::move(analysis.map);
124124
}
125125

126-
// Funcs that can be the target of a virtual call
127-
// These are either:
128-
// - Part of an (elem declare ...) or (elem ...) directive
129-
// - Exported, since they may flow back to us from the host
130-
std::unordered_set<Name> getFuncsWithAddress(Module& module) {
131-
std::unordered_set<Name> funcsWithAddress;
132-
for (const auto& fun : module.functions) {
133-
funcsWithAddress.insert(fun->name);
134-
}
135-
return funcsWithAddress;
136-
137-
// {
138-
// auto refFuncs = TableUtils::getFunctionsNeedingElemDeclare(module);
139-
// funcsWithAddress.insert(refFuncs.begin(), refFuncs.end());
140-
// }
141-
142-
// ElementUtils::iterAllElementFunctionNames(
143-
// &module,
144-
// [&funcsWithAddress](Name name) { funcsWithAddress.insert(name); });
145-
// for (const auto& export_ : module.exports) {
146-
// if (export_->kind == ExternalKind::Function) {
147-
// // This exported function might flow back to us even in a closed world,
148-
// // so it's essentially addressed.
149-
// funcsWithAddress.insert(export_->name);
150-
// }
151-
// }
152-
153-
// return funcsWithAddress;
154-
}
155-
156126
using CallGraphNode = std::variant<Name, HeapType>;
157127

158128
// Build a call graph for indirect and direct calls.
159129
// key (callee) -> value (caller)
160130
// Name -> Name : callee is called directly by caller
161-
// Name -> HeapType : callee is a potential target of a virtual call with this HeapType
162-
// HeapType -> Name : callee is indirectly called by caller
163-
// HeapType -> HeapType : callee is a subtype of caller
164-
165-
// TODO: only track indirect calls in closed world
166-
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>> buildReverseCallGraph(Module& module, const std::map<Function*, FuncInfo> funcInfos) {
131+
// Name -> HeapType : callee is a potential target of a virtual call
132+
// with this HeapType HeapType -> Name : callee is indirectly called by
133+
// caller HeapType -> HeapType : callee is a subtype of caller If we're
134+
// running in an open world, we only include Name -> Name edges.
135+
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>
136+
buildReverseCallGraph(Module& module,
137+
const std::unordered_map<Function*, FuncInfo>& funcInfos,
138+
bool closedWorld) {
167139
// callee : caller
168-
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>
169-
callers;
140+
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>> callers;
170141

171-
std::unordered_set<HeapType> allIndirectCalledTypes;
142+
if (!closedWorld) {
143+
for (const auto& [func, info] : funcInfos) {
144+
// Name -> Name for direct calls
145+
for (const auto& callee : info.calledFunctions) {
146+
callers[callee].insert(func->name);
147+
}
148+
}
149+
150+
return callers;
151+
}
172152

173-
// Funcs that can be the target of a virtual call
174-
// These are either:
175-
// - Part of an (elem declare ...) or (elem ...) directive
176-
// - Exported, since they may flow back to us from the host
177-
std::unordered_set<Name> funcsWithAddress = getFuncsWithAddress(module);
153+
std::unordered_set<HeapType> allIndirectCalledTypes;
178154

179155
for (const auto& [func, info] : funcInfos) {
180156
// Name -> Name for direct calls
@@ -188,16 +164,16 @@ std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>> buildRevers
188164
}
189165

190166
// Name -> HeapType for function types
191-
if (funcsWithAddress.contains(func->name)) {
192-
callers[func->name].insert(func->type.getHeapType());
193-
}
167+
// TODO: only look at functions that are addressable
168+
// i.e. appear in a (ref.func) or are exported
169+
callers[func->name].insert(func->type.getHeapType());
194170

195171
allIndirectCalledTypes.insert(func->type.getHeapType());
196172
}
197173

198174
SubTypes subtypes(module);
199175
for (auto type : allIndirectCalledTypes) {
200-
subtypes.iterSubTypes(type, [&callers, type](HeapType sub, int _) {
176+
subtypes.iterSubTypes(type, [&callers, type](HeapType sub, Index _) {
201177
// HeapType -> HeapType
202178
// A subtype is a 'callee' of its supertype.
203179
// Supertypes need to inherit effects from their subtypes since they may
@@ -217,19 +193,27 @@ void propagateEffects(
217193
const Module& module,
218194
const std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>&
219195
reverseCallGraph,
220-
std::map<Function*, FuncInfo>& funcInfos) {
196+
std::unordered_map<Function*, FuncInfo>& funcInfos) {
221197

222198
using CallGraphEdge = std::pair<CallGraphNode, CallGraphNode>;
223199
UniqueNonrepeatingDeferredQueue<CallGraphEdge> work;
224200

225201
for (const auto& [callee, callers] : reverseCallGraph) {
202+
// We only care about roots that will lead to a Name -> Name connection
203+
// If there's a HeapType with no Name callee, we don't need to process it
204+
// anyway.
205+
if (!std::holds_alternative<Name>(callee)) {
206+
continue;
207+
}
226208
for (const auto& caller : callers) {
227209
work.push(std::pair(callee, caller));
228210
}
229211
}
230212

231-
auto propagate = [&](const CallGraphNode& calleeNode, const CallGraphNode& callerNode) {
232-
if (!std::get_if<Name>(&calleeNode) || !std::get_if<Name>(&callerNode)) {
213+
auto propagate = [&](const CallGraphNode& calleeNode,
214+
const CallGraphNode& callerNode) {
215+
if (!std::holds_alternative<Name>(calleeNode) ||
216+
!std::holds_alternative<Name>(callerNode)) {
233217
return;
234218
}
235219

@@ -257,15 +241,6 @@ void propagateEffects(
257241
while (!work.empty()) {
258242
auto [callee, caller] = work.pop();
259243

260-
if (std::get_if<Name>(&callee) == std::get_if<Name>(&caller) &&
261-
std::holds_alternative<Name>(callee)) {
262-
auto& callerEffects =
263-
funcInfos.at(module.getFunction(std::get<Name>(caller))).effects;
264-
if (callerEffects) {
265-
callerEffects->trap = true;
266-
}
267-
}
268-
269244
// Even if nothing changed, we still need to keep traversing the callers
270245
// to look for a potential cycle which adds a trap affect on the above
271246
// lines.
@@ -276,6 +251,7 @@ void propagateEffects(
276251
continue;
277252
}
278253

254+
// TODO: handle exact refs here
279255
for (const CallGraphNode& callerCaller : callerCallers->second) {
280256
work.push(std::pair(callee, callerCaller));
281257
}
@@ -284,21 +260,13 @@ void propagateEffects(
284260

285261
struct GenerateGlobalEffects : public Pass {
286262
void run(Module* module) override {
287-
std::map<Function*, FuncInfo> funcInfos =
263+
std::unordered_map<Function*, FuncInfo> funcInfos =
288264
analyzeFuncs(*module, getPassOptions());
289265

290266
// callee : caller
291267
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>
292-
callers = buildReverseCallGraph(*module, funcInfos);
293-
294-
// for (const auto& [callee, callers] : callers) {
295-
// for (const auto& caller : callers) {
296-
// const auto* calleeName = std::get_if<Name>(&callee);
297-
// const auto* callerName = std::get_if<Name>(&caller);
298-
// if (!calleeName || !callerName) continue;
299-
// std::cout<<*calleeName<<"\t\t->\t\t"<<*callerName<<"\n";
300-
// }
301-
// }
268+
callers =
269+
buildReverseCallGraph(*module, funcInfos, getPassOptions().closedWorld);
302270

303271
propagateEffects(*module, callers, funcInfos);
304272

test/lit/passes/global-effects-closed-world.wast

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,56 @@
155155
)
156156
)
157157

158+
;; Same as above but this time our reference is the exact supertype
159+
;; So we know not to aggregate effects from the subtype.
160+
;; TODO: this case doesn't optimize today. Add exact ref support in the pass.
161+
(module
162+
;; CHECK: (type $super (sub (struct)))
163+
(type $super (sub (struct)))
164+
;; CHECK: (type $sub (sub $super (struct)))
165+
(type $sub (sub $super (struct)))
166+
167+
;; Supertype
168+
;; CHECK: (type $func-with-sub-param (sub (func (param (ref $sub)))))
169+
(type $func-with-sub-param (sub (func (param (ref $sub)))))
170+
;; Subtype
171+
;; CHECK: (type $func-with-super-param (sub $func-with-sub-param (func (param (ref $super)))))
172+
(type $func-with-super-param (sub $func-with-sub-param (func (param (ref $super)))))
173+
174+
;; CHECK: (func $nop-with-supertype (type $func-with-sub-param) (param $0 (ref $sub))
175+
;; CHECK-NEXT: (nop)
176+
;; CHECK-NEXT: )
177+
(func $nop-with-supertype (export "nop-with-supertype") (type $func-with-sub-param) (param (ref $sub))
178+
)
179+
180+
;; CHECK: (func $effectful-with-subtype (type $func-with-super-param) (param $0 (ref $super))
181+
;; CHECK-NEXT: (unreachable)
182+
;; CHECK-NEXT: )
183+
(func $effectful-with-subtype (export "effectful-with-subtype") (type $func-with-super-param) (param (ref $super))
184+
(unreachable)
185+
)
186+
187+
;; CHECK: (func $calls-ref-with-subtype (type $3) (param $func (ref (exact $func-with-sub-param))) (param $sub (ref $sub))
188+
;; CHECK-NEXT: (call_ref $func-with-sub-param
189+
;; CHECK-NEXT: (local.get $sub)
190+
;; CHECK-NEXT: (local.get $func)
191+
;; CHECK-NEXT: )
192+
;; CHECK-NEXT: )
193+
(func $calls-ref-with-subtype (param $func (ref (exact $func-with-sub-param))) (param $sub (ref $sub))
194+
(call_ref $func-with-sub-param (local.get $sub) (local.get $func))
195+
)
196+
197+
;; CHECK: (func $f (type $3) (param $func (ref (exact $func-with-sub-param))) (param $sub (ref $sub))
198+
;; CHECK-NEXT: (call $calls-ref-with-subtype
199+
;; CHECK-NEXT: (local.get $func)
200+
;; CHECK-NEXT: (local.get $sub)
201+
;; CHECK-NEXT: )
202+
;; CHECK-NEXT: )
203+
(func $f (param $func (ref (exact $func-with-sub-param))) (param $sub (ref $sub))
204+
(call $calls-ref-with-subtype (local.get $func) (local.get $sub))
205+
)
206+
)
207+
158208
(module
159209
;; CHECK: (type $only-has-effects-in-not-addressable-function (func (param i32)))
160210
(type $only-has-effects-in-not-addressable-function (func (param i32)))
@@ -183,9 +233,15 @@
183233
)
184234

185235
;; CHECK: (func $f (type $1) (param $ref (ref $only-has-effects-in-not-addressable-function))
186-
;; CHECK-NEXT: (nop)
236+
;; CHECK-NEXT: (call $calls-type-with-effects-but-not-addressable
237+
;; CHECK-NEXT: (local.get $ref)
238+
;; CHECK-NEXT: )
187239
;; CHECK-NEXT: )
188240
(func $f (param $ref (ref $only-has-effects-in-not-addressable-function))
241+
;; The type $has-effects-but-not-exported doesn't have an address because
242+
;; it's not exported and it's never the target of a ref.func.
243+
;; We should be able to determine that $ref can only point to $nop
244+
;; TODO: Only aggregate effects from functions that are addressed.
189245
(call $calls-type-with-effects-but-not-addressable (local.get $ref))
190246
)
191247
)
@@ -255,7 +311,9 @@
255311
)
256312

257313
;; CHECK: (func $f (type $1) (param $ref (ref $t))
258-
;; CHECK-NEXT: (nop)
314+
;; CHECK-NEXT: (call $indirect-calls
315+
;; CHECK-NEXT: (local.get $ref)
316+
;; CHECK-NEXT: )
259317
;; CHECK-NEXT: )
260318
(func $f (param $ref (ref $t))
261319
;; $indirect-calls might end up calling an imported function,
@@ -264,5 +322,5 @@
264322
)
265323
)
266324

267-
;; TODO exact types
325+
268326
;; TODO functions that are referenced other ways besides exporting

test/lit/passes/global-effects-eh-legacy.wast

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,22 @@
327327
;; WITHOUT-NEXT: (call $return-call-ref-throw-and-catch)
328328
;; WITHOUT-NEXT: )
329329
;; INCLUDE: (func $call-return-call-throw-and-catch (type $void)
330+
;; INCLUDE-NEXT: (try
331+
;; INCLUDE-NEXT: (do
332+
;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
333+
;; INCLUDE-NEXT: )
334+
;; INCLUDE-NEXT: (catch_all
335+
;; INCLUDE-NEXT: (nop)
336+
;; INCLUDE-NEXT: )
337+
;; INCLUDE-NEXT: )
338+
;; INCLUDE-NEXT: (try
339+
;; INCLUDE-NEXT: (do
340+
;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)
341+
;; INCLUDE-NEXT: )
342+
;; INCLUDE-NEXT: (catch_all
343+
;; INCLUDE-NEXT: (nop)
344+
;; INCLUDE-NEXT: )
345+
;; INCLUDE-NEXT: )
330346
;; INCLUDE-NEXT: (call $return-call-throw-and-catch)
331347
;; INCLUDE-NEXT: (call $return-call-indirect-throw-and-catch)
332348
;; INCLUDE-NEXT: (call $return-call-ref-throw-and-catch)

0 commit comments

Comments
 (0)