Skip to content

Commit 17d120a

Browse files
authored
Merge more sibling types in TypeMerging (#5452)
TypeMerging was previously able to merge identical root types and identical siblings that were children of a distinct type, but only when the siblings had the same top-level structure as their parent. Improve the optimization by also merging identical children when they have a different top-level structure from their parent. This solution is not fully general because it does not merge shape-refining children of separate types that would become identical siblings only after their parent types are merged, but that full generality would require either modifying Valmari-Lehtinen DFA minimization or performing the DFA minimization in a loop until reaching a fixpoint.
1 parent 720d644 commit 17d120a

File tree

2 files changed

+84
-14
lines changed

2 files changed

+84
-14
lines changed

src/passes/TypeMerging.cpp

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,20 @@ void TypeMerging::run(Module* module_) {
187187
// Map each type to its partition in the list.
188188
std::unordered_map<HeapType, Partitions::iterator> typePartitions;
189189

190-
// Map the top-level structures of root types to their partitions in the list.
191-
std::unordered_map<HeapType, Partitions::iterator, ShapeHash, ShapeEq>
190+
// Map optional supertypes and the top-level structures of their refined
191+
// children to partitions so that different children that refine the supertype
192+
// in the same way can be assigned to the same partition and potentially
193+
// merged.
194+
// TODO: This is not fully general because it still prevents types from being
195+
// merged if they are identical subtypes of two other types that end up being
196+
// merged. Fixing this would require 1) merging such input partitions and
197+
// re-running DFA minimization until convergence or 2) treating supertypes as
198+
// special transitions in the DFA and augmenting Valmari-Lehtinen DFA
199+
// minimization so that these special transitions cannot be used to split a
200+
// partition if they are self-transitions.
201+
std::unordered_map<
202+
std::optional<HeapType>,
203+
std::unordered_map<HeapType, Partitions::iterator, ShapeHash, ShapeEq>>
192204
shapePartitions;
193205

194206
#if TYPE_MERGING_DEBUG
@@ -220,9 +232,10 @@ void TypeMerging::run(Module* module_) {
220232
};
221233

222234
// Similar to the above, but look up or create a partition associated with the
223-
// type's top-level shape rather than its identity.
235+
// type's supertype and top-level shape rather than its identity.
224236
auto ensureShapePartition = [&](HeapType type) -> Partitions::iterator {
225-
auto [it, inserted] = shapePartitions.insert({type, partitions.end()});
237+
auto [it, inserted] =
238+
shapePartitions[type.getSuperType()].insert({type, partitions.end()});
226239
if (inserted) {
227240
it->second = partitions.insert(partitions.end(), Partition{});
228241
}
@@ -243,22 +256,16 @@ void TypeMerging::run(Module* module_) {
243256
ensurePartition(type);
244257
continue;
245258
}
259+
// If there is no supertype to merge with or if this type refines its
260+
// supertype, then we can still potentially merge it with sibling types with
261+
// the same structure. Find and add to the partition with other such types.
246262
auto super = type.getSuperType();
247-
// If there is no supertype to merge with, then we can still merge with
248-
// other root types with the same structure. Find and add to the partition
249-
// with other such types.
250-
if (!super) {
263+
if (!super || !shapeEq(type, *super)) {
251264
auto it = ensureShapePartition(type);
252265
it->push_back(makeDFAState(type));
253266
typePartitions[type] = it;
254267
continue;
255268
}
256-
// If this type refines its supertype in some way, then we cannot merge it.
257-
// Create a new partition for it.
258-
if (!shapeEq(type, *super)) {
259-
ensurePartition(type);
260-
continue;
261-
}
262269
// The current type and its supertype have the same top-level structure and
263270
// are not distinguished, so add the current type to its supertype's
264271
// partition.

test/lit/passes/type-merging.wast

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,69 @@
474474
)
475475
)
476476

477+
(module
478+
(rec
479+
;; CHECK: (rec
480+
;; CHECK-NEXT: (type $A (struct (field anyref)))
481+
(type $A (struct anyref))
482+
;; CHECK: (type $B (struct_subtype (field eqref) $A))
483+
(type $B (struct_subtype eqref $A))
484+
(type $C (struct_subtype eqref $A))
485+
)
486+
487+
;; CHECK: (type $none_=>_none (func))
488+
489+
;; CHECK: (func $foo (type $none_=>_none)
490+
;; CHECK-NEXT: (local $a (ref null $A))
491+
;; CHECK-NEXT: (local $b (ref null $B))
492+
;; CHECK-NEXT: (local $c (ref null $B))
493+
;; CHECK-NEXT: (nop)
494+
;; CHECK-NEXT: )
495+
(func $foo
496+
;; This is the same as above, but now B and C refine A such that they have a
497+
;; different top-level structure. They can still be merged.
498+
(local $a (ref null $A))
499+
(local $b (ref null $B))
500+
(local $c (ref null $C))
501+
)
502+
)
503+
504+
(module
505+
(rec
506+
;; CHECK: (rec
507+
;; CHECK-NEXT: (type $A (struct (field anyref)))
508+
(type $A (struct anyref))
509+
(type $B (struct_subtype anyref $A))
510+
(type $C (struct_subtype anyref $A))
511+
;; CHECK: (type $E (struct_subtype (field eqref) $A))
512+
513+
;; CHECK: (type $D (struct_subtype (field eqref) $A))
514+
(type $D (struct_subtype eqref $B))
515+
(type $E (struct_subtype eqref $C))
516+
)
517+
518+
;; CHECK: (type $none_=>_none (func))
519+
520+
;; CHECK: (func $foo (type $none_=>_none)
521+
;; CHECK-NEXT: (local $a (ref null $A))
522+
;; CHECK-NEXT: (local $b (ref null $A))
523+
;; CHECK-NEXT: (local $c (ref null $A))
524+
;; CHECK-NEXT: (local $d (ref null $D))
525+
;; CHECK-NEXT: (local $e (ref null $E))
526+
;; CHECK-NEXT: (nop)
527+
;; CHECK-NEXT: )
528+
(func $foo
529+
;; D and E should be mergeable because they have identical shapes and will
530+
;; be siblings after B and C get merged, but we don't support this case yet.
531+
;; TODO: support this.
532+
(local $a (ref null $A))
533+
(local $b (ref null $B))
534+
(local $c (ref null $C))
535+
(local $d (ref null $D))
536+
(local $e (ref null $E))
537+
)
538+
)
539+
477540
;; Check that we refinalize properly.
478541
(module
479542
;; CHECK: (rec

0 commit comments

Comments
 (0)