Skip to content

Commit 11d3aef

Browse files
authored
[Custom Descriptors] Fix TypeMerging sibling criteria (#7842)
When merging sibling types, we must first partition the types by their supertypes and shapes. With custom descriptors, we consider full descriptor chains as single units to be merged. We had updated the shape partitioning to account for this, but not the supertype partitioning. As a result, we were incorrectly merging chains with different descriptor supertypes as long as the base described types had no supertype. Fix the bug by partitioning not just on the base described type's supertype, but the sequence of all supertypes in the chain.
1 parent 71ddfcf commit 11d3aef

File tree

2 files changed

+92
-23
lines changed

2 files changed

+92
-23
lines changed

src/passes/TypeMerging.cpp

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,30 @@ struct ShapeHash {
248248
size_t operator()(const HeapType& type) const { return shapeHash(type); }
249249
};
250250

251+
// The supertypes of each type in a descriptor chain. Hash and equality-compare
252+
// them using real identity to identify siblings chains that share the same
253+
// supertypes.
254+
using ChainSupers = std::vector<std::optional<HeapType>>;
255+
256+
struct ChainSupersEq {
257+
bool operator()(const ChainSupers& a, const ChainSupers& b) const {
258+
return a == b;
259+
}
260+
};
261+
262+
struct ChainSupersHash {
263+
size_t operator()(const ChainSupers& supers) const {
264+
auto digest = wasm::hash(supers.size());
265+
for (auto& super : supers) {
266+
wasm::rehash(digest, !!super);
267+
if (super) {
268+
wasm::rehash(digest, *super);
269+
}
270+
}
271+
return digest;
272+
}
273+
};
274+
251275
void TypeMerging::run(Module* module_) {
252276
module = module_;
253277

@@ -339,8 +363,10 @@ bool TypeMerging::merge(MergeKind kind) {
339363
// that siblings that refine the supertype in the same way can be assigned to
340364
// the same partition and potentially merged.
341365
std::unordered_map<
342-
std::optional<HeapType>,
343-
std::unordered_map<HeapType, Partitions::iterator, ShapeHash, ShapeEq>>
366+
std::vector<std::optional<HeapType>>,
367+
std::unordered_map<HeapType, Partitions::iterator, ShapeHash, ShapeEq>,
368+
ChainSupersHash,
369+
ChainSupersEq>
344370
shapePartitions;
345371

346372
// Ensure the type has a partition and return a reference to it. Since we
@@ -358,12 +384,16 @@ bool TypeMerging::merge(MergeKind kind) {
358384
// Similar to the above, but look up or create a partition associated with the
359385
// type's supertype and top-level shape rather than its identity.
360386
auto ensureShapePartition = [&](HeapType type) -> Partitions::iterator {
361-
auto super = type.getDeclaredSuperType();
362-
if (super) {
363-
super = getMerged(*super);
387+
ChainSupers supers;
388+
for (auto t : type.getDescriptorChain()) {
389+
auto super = t.getDeclaredSuperType();
390+
if (super) {
391+
super = getMerged(*super);
392+
}
393+
supers.push_back(super);
364394
}
365395
auto [it, inserted] =
366-
shapePartitions[super].insert({type, partitions.end()});
396+
shapePartitions[supers].insert({type, partitions.end()});
367397
if (inserted) {
368398
it->second = partitions.insert(partitions.end(), Partition{});
369399
}

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

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -306,23 +306,6 @@
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-
326309
(module
327310
;; CHECK: (type $public (sub (struct)))
328311
(type $public (sub (struct)))
@@ -368,3 +351,59 @@
368351
;; CHECK: (export "public" (global $public))
369352
(export "public" (global $public))
370353
)
354+
355+
(module
356+
;; In principle we could merge the $B chain into the $A chain, because $A and
357+
;; $B are mergeable as siblings and $B.desc is mergeable into its supertype
358+
;; A.desc. However we do not currently do this kind of merge in either phase.
359+
(rec
360+
;; CHECK: (rec
361+
;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct))))
362+
(type $A (sub (descriptor $A.desc (struct))))
363+
;; CHECK: (type $A.desc (sub (describes $A (struct))))
364+
(type $A.desc (sub (describes $A (struct))))
365+
;; CHECK: (type $B (sub (descriptor $B.desc (struct))))
366+
(type $B (sub (descriptor $B.desc (struct))))
367+
;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct))))
368+
(type $B.desc (sub $A.desc (describes $B (struct))))
369+
)
370+
371+
;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none))
372+
(global $B.desc (ref null $B.desc) (ref.null none))
373+
)
374+
375+
(module
376+
(rec
377+
;; Like above, but now we have an unrelated C chain. We have these chains:
378+
;;
379+
;; A -> A.desc
380+
;; ^
381+
;; B -> B.desc
382+
;;
383+
;; C -> C.desc
384+
;;
385+
;; The C chain should not be considered a sibling of the B chain because
386+
;; C.desc is not a subtype of A.desc, even though they would look like
387+
;; siblings if you only considered the base type in the chain.
388+
;; CHECK: (rec
389+
;; CHECK-NEXT: (type $A (descriptor $A.desc (struct)))
390+
(type $A (descriptor $A.desc (struct)))
391+
;; CHECK: (type $A.desc (sub (describes $A (struct))))
392+
(type $A.desc (sub (describes $A (struct))))
393+
;; CHECK: (type $B (descriptor $B.desc (struct)))
394+
(type $B (descriptor $B.desc (struct)))
395+
;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct))))
396+
(type $B.desc (sub $A.desc (describes $B (struct))))
397+
;; CHECK: (type $C (descriptor $C.desc (struct)))
398+
(type $C (descriptor $C.desc (struct)))
399+
;; CHECK: (type $C.desc (describes $C (struct)))
400+
(type $C.desc (describes $C (struct)))
401+
)
402+
403+
;; CHECK: (global $C (ref null $C) (ref.null none))
404+
(global $C (ref null $C) (ref.null none))
405+
406+
;; This would become invalid if $B.desc were merged into $C.desc.
407+
;; CHECK: (global $A.desc (ref null $A.desc) (struct.new_default $B.desc))
408+
(global $A.desc (ref null $A.desc) (struct.new_default $B.desc))
409+
)

0 commit comments

Comments
 (0)