Skip to content

Commit bf88eae

Browse files
Support indirect call effects
1 parent ce7f869 commit bf88eae

2 files changed

Lines changed: 69 additions & 22 deletions

File tree

src/ir/subtypes.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ struct SubTypes {
172172
// false, we stop. Returns the last value returned to it, that is, returns
173173
// true if we did not stop early, and false if we did.
174174
template<typename F>
175-
bool iterSubTypes(HeapType type, Index depth, F func) const {
175+
bool iterSubTypes(HeapType type, Index depth, F func) const
176+
requires requires(F func, HeapType subtype, Index depth) { { func(subtype, depth) } -> std::same_as<bool>; } {
176177
// Start by traversing the type itself.
177178
if (!func(type, 0)) {
178179
return false;
@@ -219,7 +220,9 @@ struct SubTypes {
219220
}
220221

221222
// As above, but iterate to the maximum depth.
222-
template<typename F> bool iterSubTypes(HeapType type, F func) const {
223+
template<typename F>
224+
bool iterSubTypes(HeapType type, F func) const
225+
requires requires(F func, HeapType subtype, Index depth) { { func(subtype, depth) } -> std::same_as<bool>; } {
223226
return iterSubTypes(type, std::numeric_limits<Index>::max(), func);
224227
}
225228

src/passes/GlobalEffects.cpp

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
*/
1616

1717
//
18-
// Handle the computation of global effects. The effects are stored on the
19-
// PassOptions structure; see more details there.
18+
// Handle the computation of global effects. The effects are stored on
19+
// Function::effects; see more details there.
2020
//
2121

22+
#include "ir/subtypes.h"
2223
#include "ir/effects.h"
2324
#include "ir/module-utils.h"
2425
#include "pass.h"
@@ -39,6 +40,9 @@ struct FuncInfo {
3940

4041
// Directly-called functions from this function.
4142
std::unordered_set<Name> calledFunctions;
43+
44+
// Types that are targets of indirect calls.
45+
std::unordered_set<HeapType> indirectCalledTypes;
4246
};
4347

4448
std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
@@ -84,11 +88,16 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
8488
// Note the direct call.
8589
funcInfo.calledFunctions.insert(call->target);
8690
} else if (effects.calls) {
87-
// This is an indirect call of some sort, so we must assume the
88-
// worst. To do so, clear the effects, which indicates nothing
89-
// is known (so anything is possible).
90-
// TODO: We could group effects by function type etc.
91-
funcInfo.effects = UnknownEffects;
91+
HeapType type;
92+
if (auto* callRef = curr->dynCast<CallRef>()) {
93+
type = callRef->target->type.getHeapType();
94+
} else if (auto* callIndirect = curr->dynCast<CallIndirect>()) {
95+
type = callIndirect->heapType;
96+
} else {
97+
assert(false && "Unexpected type of call");
98+
}
99+
100+
funcInfo.indirectCalledTypes.insert(type);
92101
} else {
93102
// No call here, but update throwing if we see it. (Only do so,
94103
// however, if we have effects; if we cleared it - see before -
@@ -107,43 +116,54 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
107116
return std::move(analysis.map);
108117
}
109118

119+
using CallGraphNode = std::variant<Name, HeapType>;
120+
110121
// Propagate effects from callees to callers transitively
111122
// e.g. if A -> B -> C (A calls B which calls C)
112123
// Then B inherits effects from C and A inherits effects from both B and C.
113124
void propagateEffects(
114125
const Module& module,
115-
const std::unordered_map<Name, std::unordered_set<Name>>& reverseCallGraph,
126+
const std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>>& reverseCallGraph,
116127
std::map<Function*, FuncInfo>& funcInfos) {
117128

118-
UniqueNonrepeatingDeferredQueue<std::pair<Name, Name>> work;
129+
using CallGraphEdge = std::pair<CallGraphNode, CallGraphNode>;
130+
UniqueNonrepeatingDeferredQueue<CallGraphEdge> work;
119131

120132
for (const auto& [callee, callers] : reverseCallGraph) {
121133
for (const auto& caller : callers) {
122134
work.push(std::pair(callee, caller));
123135
}
124136
}
125137

126-
auto propagate = [&](Name callee, Name caller) {
127-
auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects;
138+
auto propagate = [&](Name* callee, Name* caller) {
139+
if (callee == nullptr || caller == nullptr) {
140+
return;
141+
}
142+
143+
auto& callerEffects = funcInfos.at(module.getFunction(*caller)).effects;
128144
const auto& calleeEffects =
129-
funcInfos.at(module.getFunction(callee)).effects;
130-
if (!callerEffects) {
145+
funcInfos.at(module.getFunction(*callee)).effects;
146+
if (callerEffects == UnknownEffects) {
131147
return;
132148
}
133149

134-
if (!calleeEffects) {
150+
if (calleeEffects == UnknownEffects) {
135151
callerEffects = UnknownEffects;
136152
return;
137153
}
138154

139-
callerEffects->mergeIn(*calleeEffects);
155+
if (*callee == *caller) {
156+
callerEffects->trap = true;
157+
} else {
158+
callerEffects->mergeIn(*calleeEffects);
159+
}
140160
};
141161

142162
while (!work.empty()) {
143163
auto [callee, caller] = work.pop();
144164

145-
if (callee == caller) {
146-
auto& callerEffects = funcInfos.at(module.getFunction(caller)).effects;
165+
if (std::get_if<Name>(&callee) == std::get_if<Name>(&caller) && std::holds_alternative<Name>(callee)) {
166+
auto& callerEffects = funcInfos.at(module.getFunction(std::get<Name>(caller))).effects;
147167
if (callerEffects) {
148168
callerEffects->trap = true;
149169
}
@@ -152,14 +172,14 @@ void propagateEffects(
152172
// Even if nothing changed, we still need to keep traversing the callers
153173
// to look for a potential cycle which adds a trap affect on the above
154174
// lines.
155-
propagate(callee, caller);
175+
propagate(std::get_if<Name>(&callee), std::get_if<Name>(&caller));
156176

157177
const auto& callerCallers = reverseCallGraph.find(caller);
158178
if (callerCallers == reverseCallGraph.end()) {
159179
continue;
160180
}
161181

162-
for (const Name& callerCaller : callerCallers->second) {
182+
for (const CallGraphNode& callerCaller : callerCallers->second) {
163183
work.push(std::pair(callee, callerCaller));
164184
}
165185
}
@@ -171,11 +191,35 @@ struct GenerateGlobalEffects : public Pass {
171191
analyzeFuncs(*module, getPassOptions());
172192

173193
// callee : caller
174-
std::unordered_map<Name, std::unordered_set<Name>> callers;
194+
std::unordered_map<CallGraphNode, std::unordered_set<CallGraphNode>> callers;
195+
196+
std::unordered_set<HeapType> allIndirectCalledTypes;
175197
for (const auto& [func, info] : funcInfos) {
198+
// Name -> Name for direct calls
176199
for (const auto& callee : info.calledFunctions) {
177200
callers[callee].insert(func->name);
178201
}
202+
203+
// HeapType -> Name for indirect calls
204+
for (const auto& calleeType : info.indirectCalledTypes) {
205+
callers[calleeType].insert(func->name);
206+
}
207+
208+
// Name -> HeapType for function types
209+
callers[func->name].insert(func->type.getHeapType());
210+
211+
allIndirectCalledTypes.insert(func->type.getHeapType());
212+
}
213+
214+
SubTypes subtypes(*module);
215+
for (auto type : allIndirectCalledTypes) {
216+
subtypes.iterSubTypes(type, [&callers, type](HeapType sub, int _) {
217+
// HeapType -> HeapType
218+
// A subtype is a 'callee' of its supertype. Supertypes need to inherit effects from their subtypes
219+
// See the example in (TODO)
220+
callers[sub].insert(type);
221+
return true;
222+
});
179223
}
180224

181225
propagateEffects(*module, callers, funcInfos);

0 commit comments

Comments
 (0)