Skip to content

Commit 2d61760

Browse files
authored
[GC] GUFA: Handle function subtyping (#8112)
We were ignoring subtyping entirely, that is, the pass didn't realize that an indirect call to a type might call something of a subtype. That situation seems to not be common in practice, given we've never gotten a bug report. We could fully optimize this as we do data reads and writes: we could consider the type of the flowing reference in CallRef (but not CallIndirect), etc. However, for now do the far simpler thing and use the static type to connect results to subtypes as needed. For params, do something even simpler: Just hook them up at the type level, which ignores exactness (as this would require even more work to optimize, see TODO in the code).
1 parent 8e5f757 commit 2d61760

File tree

2 files changed

+436
-9
lines changed

2 files changed

+436
-9
lines changed

src/ir/possible-contents.cpp

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -446,8 +446,13 @@ namespace {
446446

447447
// Information that is shared with InfoCollector.
448448
struct SharedInfo {
449+
// Subtyping info.
450+
const SubTypes& subTypes;
451+
449452
// The names of tables that are imported or exported.
450453
std::unordered_set<Name> publicTables;
454+
455+
SharedInfo(const SubTypes& subTypes) : subTypes(subTypes) {}
451456
};
452457

453458
// The data we gather from each function, as we process them in parallel. Later
@@ -826,29 +831,53 @@ struct InfoCollector
826831
return ResultLocation{target, i};
827832
});
828833
}
829-
template<typename T> void handleIndirectCall(T* curr, HeapType targetType) {
834+
template<typename T>
835+
void handleIndirectCall(T* curr, HeapType targetType, Exactness exact) {
830836
// If the heap type is not a signature, which is the case for a bottom type
831837
// (null) then nothing can be called.
832838
if (!targetType.isSignature()) {
833839
assert(targetType.isBottom());
834840
return;
835841
}
842+
// Connect us to the given type.
843+
auto sig = targetType.getSignature();
836844
handleCall(
837845
curr,
838846
[&](Index i) {
839-
assert(i <= targetType.getSignature().params.size());
847+
assert(i <= sig.params.size());
840848
return SignatureParamLocation{targetType, i};
841849
},
842850
[&](Index i) {
843-
assert(i <= targetType.getSignature().results.size());
851+
assert(i <= sig.results.size());
844852
return SignatureResultLocation{targetType, i};
845853
});
854+
// If the type is exact, we only need to read SignatureParamLocation /
855+
// SignatureResultLocation of this exact type, and we are done.
856+
if (exact == Exact) {
857+
return;
858+
}
859+
// Inexact type, so subtyping is relevant: add the relevant links.
860+
// TODO: SignatureParamLocation is handled below in an inefficient way, see
861+
// there.
862+
// TODO: For CallRef, we could do something like readFromData() and use the
863+
// flowing function reference's type, not the static type. We could
864+
// even reuse ConeReadLocation if we generalized it to function types.
865+
for (Index i = 0; i < sig.results.size(); i++) {
866+
if (isRelevant(sig.results[i])) {
867+
shared.subTypes.iterSubTypes(
868+
targetType, [&](HeapType subType, Index depth) {
869+
info.links.push_back({SignatureResultLocation{subType, i},
870+
ExpressionLocation{curr, i}});
871+
});
872+
}
873+
}
846874
}
847875
template<typename T> void handleIndirectCall(T* curr, Type targetType) {
848876
// If the type is unreachable, nothing can be called (and there is no heap
849877
// type to get).
850878
if (targetType != Type::unreachable) {
851-
handleIndirectCall(curr, targetType.getHeapType());
879+
handleIndirectCall(
880+
curr, targetType.getHeapType(), targetType.getExactness());
852881
}
853882
}
854883

@@ -891,7 +920,8 @@ struct InfoCollector
891920
}
892921
void visitCallIndirect(CallIndirect* curr) {
893922
// TODO: optimize the call target like CallRef
894-
handleIndirectCall(curr, curr->heapType);
923+
// CallIndirect only knows a heap type, so it is always inexact.
924+
handleIndirectCall(curr, curr->heapType, Inexact);
895925

896926
// If this goes to a public table, then we must root the output, as the
897927
// table could contain anything at all, and calling functions there could
@@ -2241,13 +2271,20 @@ Flower::Flower(Module& wasm, const PassOptions& options)
22412271
tnhOracle = std::make_unique<TNHOracle>(wasm, options);
22422272
}
22432273

2274+
#ifdef POSSIBLE_CONTENTS_DEBUG
2275+
std::cout << "subtypes phase\n";
2276+
#endif
2277+
2278+
subTypes = std::make_unique<SubTypes>(wasm);
2279+
maxDepths = subTypes->getMaxDepths();
2280+
22442281
#ifdef POSSIBLE_CONTENTS_DEBUG
22452282
std::cout << "parallel phase\n";
22462283
#endif
22472284

22482285
// Compute shared info that we need for the main pass over each function, such
22492286
// as the imported/exported tables.
2250-
SharedInfo shared;
2287+
SharedInfo shared(*subTypes);
22512288

22522289
for (auto& table : wasm.tables) {
22532290
if (table->imported()) {
@@ -2443,11 +2480,29 @@ Flower::Flower(Module& wasm, const PassOptions& options)
24432480
}
24442481

24452482
#ifdef POSSIBLE_CONTENTS_DEBUG
2446-
std::cout << "struct phase\n";
2483+
std::cout << "function subtyping phase\n";
24472484
#endif
24482485

2449-
subTypes = std::make_unique<SubTypes>(wasm);
2450-
maxDepths = subTypes->getMaxDepths();
2486+
// Link function subtyping params. When a function of type B has a supertype
2487+
// A, then we may call B using A's type. That means the parameters to
2488+
// (indirect) calls to B must look at supertypes, which is the opposite of the
2489+
// logic for results, readFromData(), etc. For now, we just connect these
2490+
// types directly, which does not fully optimize exact types. TODO: Add a new
2491+
// mechanism to optimize here.
2492+
for (auto type : subTypes->types) {
2493+
if (!type.isFunction()) {
2494+
continue;
2495+
}
2496+
auto super = type.getSuperType();
2497+
if (!super) {
2498+
continue;
2499+
}
2500+
auto params = type.getSignature().params;
2501+
for (Index i = 0; i < params.size(); i++) {
2502+
links.insert(getIndexes(LocationLink{SignatureParamLocation{*super, i},
2503+
SignatureParamLocation{type, i}}));
2504+
}
2505+
}
24512506

24522507
#ifdef POSSIBLE_CONTENTS_DEBUG
24532508
std::cout << "Link-targets phase\n";

0 commit comments

Comments
 (0)