Skip to content

Commit b75c885

Browse files
authored
[Custom Descriptors] Fix TypeMerging topological sort (#7764)
TypeMerging uses a topological sort to process supertypes first in various places. It also treats descriptor chains as single units represented by their base described type. Types in WebAssembly cannot have more than one supertype, but the types in a descriptor chain will have different supertypes and those supertypes may not necessarily be all in a single descriptor chain together. As a result, when descriptor chains are treated as units, it is possible for one chain to have multiple supertype chains. The topological sort in TypeMerging did not previously take this into account, and as a result the pass tried to merge types in the wrong order, leading to assertion failures. Fix the topological sort to correctly treat descriptor chains as units. Fixes #7757.
1 parent 5ca16fe commit b75c885

File tree

2 files changed

+42
-14
lines changed

2 files changed

+42
-14
lines changed

src/passes/TypeMerging.cpp

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@
4545
#include "pass.h"
4646
#include "support/dfa_minimization.h"
4747
#include "support/small_set.h"
48-
#include "wasm-builder.h"
49-
#include "wasm-type-ordering.h"
48+
#include "support/topological_sort.h"
5049
#include "wasm-type.h"
5150
#include "wasm.h"
5251

@@ -169,13 +168,31 @@ struct TypeMerging : public Pass {
169168

170169
std::vector<HeapType>
171170
mergeableSupertypesFirst(const std::vector<HeapType>& types) {
172-
return HeapTypeOrdering::supertypesFirst(
173-
types, [&](HeapType type) -> std::optional<HeapType> {
171+
// Topological sort so that supertypes come first. Since we treat descriptor
172+
// chains as units represented by their base described types, we must handle
173+
// the case where one chain has multiple unrelated chains as supertypes.
174+
InsertOrderedMap<HeapType, std::vector<HeapType>> subtypes;
175+
for (auto type : types) {
176+
// Skip descriptor types, since they will be considered as a unit with
177+
// their base described types.
178+
if (type.getDescribedType()) {
179+
continue;
180+
}
181+
subtypes.insert({type, {}});
182+
}
183+
// Find the base described type (`superBase`) for each supertype in the
184+
// chain starting at `subBase`.
185+
for (auto [subBase, _] : subtypes) {
186+
for (auto type : subBase.getDescriptorChain()) {
174187
if (auto super = type.getDeclaredSuperType()) {
175-
return getMerged(*super);
188+
auto superBase = getMerged(getBaseDescribedType(*super));
189+
if (auto it = subtypes.find(superBase); it != subtypes.end()) {
190+
it->second.push_back(subBase);
191+
}
176192
}
177-
return std::nullopt;
178-
});
193+
}
194+
}
195+
return TopologicalSort::sortOf(subtypes.begin(), subtypes.end());
179196
}
180197

181198
void run(Module* module_) override;
@@ -354,13 +371,7 @@ bool TypeMerging::merge(MergeKind kind) {
354371
// For each type, either create a new partition or add to its supertype's
355372
// partition.
356373
for (auto type : mergeableSupertypesFirst(mergeable)) {
357-
// Skip descriptor types. Since types in descriptor chains all have to be
358-
// merged into matching descriptor chains together, only the base described
359-
// type in each chain is considered, and its DFA state will include the
360-
// shape of its entire descriptor chain.
361-
if (type.getDescribedType()) {
362-
continue;
363-
}
374+
assert(!type.getDescribedType());
364375
// We need partitions for any public children of this type since those
365376
// children will participate in the DFA we're creating. We use the base
366377
// described type of the child because that's the type that the DFA state

test/lit/passes/type-merging-desc.wast

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,23 @@
306306
(global $B (ref null $B) (ref.null none))
307307
)
308308

309+
(module
310+
;; We can merge the $B chain into the $A chain, but we cannot merge in the
311+
;; other direction because that would make $B.desc its own subtype.
312+
(rec
313+
;; CHECK: (rec
314+
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
315+
(type $A (sub (descriptor $A.desc (struct))))
316+
;; CHECK: (type $A.desc (sub (describes $A (struct))))
317+
(type $A.desc (sub (describes $A (struct))))
318+
(type $B (sub (descriptor $B.desc (struct))))
319+
(type $B.desc (sub $A.desc (describes $B (struct))))
320+
)
321+
322+
;; CHECK: (global $B.desc (ref null $A.desc) (ref.null none))
323+
(global $B.desc (ref null $B.desc) (ref.null none))
324+
)
325+
309326
(module
310327
;; CHECK: (type $public (sub (struct)))
311328
(type $public (sub (struct)))

0 commit comments

Comments
 (0)