summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorThomas Lively <tlively@google.com>2023-03-14 15:23:09 -0500
committerGitHub <noreply@github.com>2023-03-14 20:23:09 +0000
commitb1f0e98ce3899e803e829df8a57df879695113ee (patch)
tree1b314d841775215195e6f366f14868005b3e9f79 /test
parent5661c6f7e7eeb79445a0913ee0ed356b881a1ba2 (diff)
downloadbinaryen-b1f0e98ce3899e803e829df8a57df879695113ee.tar.gz
binaryen-b1f0e98ce3899e803e829df8a57df879695113ee.tar.bz2
binaryen-b1f0e98ce3899e803e829df8a57df879695113ee.zip
Fix misoptimization in TypeMerging (#5572)
TypeMerging previously tried to merge types with their supertypes and siblings in a single step, but this could cause a misoptimization in which a type was merged with its parent's sibling without being merged with its parent, breaking subtyping. Fix the bug by merging with supertypes and siblings separately. Since we now have multiple merging steps, also take the opportunity to run the sibling merging step multiple times to exploit more merging opportunities. Fixes #5556.
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/type-merging.wast195
1 files changed, 164 insertions, 31 deletions
diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast
index ef62c669c..4fd8ff75c 100644
--- a/test/lit/passes/type-merging.wast
+++ b/test/lit/passes/type-merging.wast
@@ -232,10 +232,10 @@
(module
(rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (struct (field (ref null $A))))
(type $A (struct (ref null $X)))
(type $B (struct_subtype (ref null $Y) $A))
- ;; CHECK: (rec
- ;; CHECK-NEXT: (type $X (struct (field (ref null $X))))
(type $X (struct (ref null $A)))
(type $Y (struct_subtype (ref null $B) $X))
)
@@ -243,10 +243,10 @@
;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
- ;; CHECK-NEXT: (local $a (ref null $X))
- ;; CHECK-NEXT: (local $b (ref null $X))
- ;; CHECK-NEXT: (local $x (ref null $X))
- ;; CHECK-NEXT: (local $y (ref null $X))
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $x (ref null $A))
+ ;; CHECK-NEXT: (local $y (ref null $A))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo
@@ -261,26 +261,54 @@
(module
(rec
- (type $A (struct (ref null $X)))
- (type $B (struct_subtype (ref null $Y) $A))
+ (type $A (struct (ref null $X) i32))
;; CHECK: (rec
- ;; CHECK-NEXT: (type $X (struct (field (ref null $X))))
- (type $X (struct (ref null $A)))
- (type $Y (struct_subtype (ref null $B) $X))
+ ;; CHECK-NEXT: (type $Y (struct (field (ref null $B)) (field f32)))
+
+ ;; CHECK: (type $B (struct (field (ref null $Y)) (field i32)))
+ (type $B (struct (ref null $Y) i32))
+ (type $X (struct (ref null $A) f32))
+ (type $Y (struct (ref null $B) f32))
+ )
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $B))
+ ;; CHECK-NEXT: (local $b (ref null $B))
+ ;; CHECK-NEXT: (local $x (ref null $Y))
+ ;; CHECK-NEXT: (local $y (ref null $Y))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; As above with the differentiated chains, but now the types are top-level
+ ;; siblings instead of subtypes
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ (local $x (ref null $X))
+ (local $y (ref null $Y))
)
+)
+(module
+ (rec
+ (type $A (struct (ref null $X)))
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $B (struct (field (ref null $B))))
+ (type $B (struct (ref null $Y)))
+ (type $X (struct (ref null $A)))
+ (type $Y (struct (ref null $B)))
+ )
;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
- ;; CHECK-NEXT: (local $a (ref null $X))
- ;; CHECK-NEXT: (local $b (ref null $X))
- ;; CHECK-NEXT: (local $x (ref null $X))
- ;; CHECK-NEXT: (local $y (ref null $X))
+ ;; CHECK-NEXT: (local $a (ref null $B))
+ ;; CHECK-NEXT: (local $b (ref null $B))
+ ;; CHECK-NEXT: (local $x (ref null $B))
+ ;; CHECK-NEXT: (local $y (ref null $B))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo
- ;; As above, but now the A->B and X->Y chains are not differentiated by the
- ;; i32 and f32, so all four types can be merged into a single type.
+ ;; As above, but with all the types merging into a single type.
(local $a (ref null $A))
(local $b (ref null $B))
(local $x (ref null $X))
@@ -467,7 +495,7 @@
;; CHECK-NEXT: )
(func $foo
;; B and C cannot be merged into A because they refine A's field, but B and
- ;; C can still be merged with each other even though they are siblings.
+ ;; C can still be merged with each other.
(local $a (ref null $A))
(local $b (ref null $B))
(local $c (ref null $C))
@@ -479,8 +507,8 @@
;; CHECK: (rec
;; CHECK-NEXT: (type $A (struct (field anyref)))
(type $A (struct anyref))
- ;; CHECK: (type $B (struct_subtype (field eqref) $A))
(type $B (struct_subtype eqref $A))
+ ;; CHECK: (type $C (struct_subtype (field eqref) $A))
(type $C (struct_subtype eqref $A))
)
@@ -488,8 +516,8 @@
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref null $A))
- ;; CHECK-NEXT: (local $b (ref null $B))
- ;; CHECK-NEXT: (local $c (ref null $B))
+ ;; CHECK-NEXT: (local $b (ref null $C))
+ ;; CHECK-NEXT: (local $c (ref null $C))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo
@@ -508,10 +536,8 @@
(type $A (struct anyref))
(type $B (struct_subtype anyref $A))
(type $C (struct_subtype anyref $A))
- ;; CHECK: (type $E (struct_subtype (field eqref) $A))
-
- ;; CHECK: (type $D (struct_subtype (field eqref) $A))
(type $D (struct_subtype eqref $B))
+ ;; CHECK: (type $E (struct_subtype (field eqref) $A))
(type $E (struct_subtype eqref $C))
)
@@ -521,14 +547,13 @@
;; CHECK-NEXT: (local $a (ref null $A))
;; CHECK-NEXT: (local $b (ref null $A))
;; CHECK-NEXT: (local $c (ref null $A))
- ;; CHECK-NEXT: (local $d (ref null $D))
+ ;; CHECK-NEXT: (local $d (ref null $E))
;; CHECK-NEXT: (local $e (ref null $E))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo
;; D and E should be mergeable because they have identical shapes and will
- ;; be siblings after B and C get merged, but we don't support this case yet.
- ;; TODO: support this.
+ ;; be siblings after B and C get merged.
(local $a (ref null $A))
(local $b (ref null $B))
(local $c (ref null $C))
@@ -537,6 +562,60 @@
)
)
+;; Check that we fully optimize a type graph that requires multiple iterations
+;; of supertype and sibling merging.
+(module
+ (rec
+ ;; These will get merged in the initial supertype merging stage.
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $B' (struct (field (ref $A))))
+
+ ;; CHECK: (type $C (struct_subtype (field (ref $A)) (field i32) $B'))
+
+ ;; CHECK: (type $D' (struct_subtype (field (ref $A)) (field i32) (field i32) $C))
+
+ ;; CHECK: (type $A (struct ))
+ (type $A (struct))
+ (type $A' (struct_subtype $A))
+
+ ;; These siblings will be merged only after $a and $a' are merged.
+ (type $B (struct (ref $A)))
+ (type $B' (struct (ref $A')))
+
+ ;; These will get merged only after $b and $b' are merged.
+ (type $C (struct_subtype (ref $A) i32 $B))
+ (type $C' (struct_subtype (ref $A') i32 $B'))
+
+ ;; These will get merged only after $c and $c' are merged.
+ (type $D (struct_subtype (ref $A) i32 i32 $C))
+ (type $D' (struct_subtype (ref $A') i32 i32 $C'))
+ )
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $a' (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $B'))
+ ;; CHECK-NEXT: (local $b' (ref null $B'))
+ ;; CHECK-NEXT: (local $c (ref null $C))
+ ;; CHECK-NEXT: (local $c' (ref null $C))
+ ;; CHECK-NEXT: (local $d (ref null $D'))
+ ;; CHECK-NEXT: (local $d' (ref null $D'))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ (local $a (ref null $A))
+ (local $a' (ref null $A'))
+ (local $b (ref null $B))
+ (local $b' (ref null $B'))
+ (local $c (ref null $C))
+ (local $c' (ref null $C'))
+ (local $d (ref null $D))
+ (local $d' (ref null $D'))
+ )
+)
+
;; Check that we refinalize properly.
(module
;; CHECK: (rec
@@ -758,18 +837,19 @@
;; incorrect.
(module
(rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (struct (field (ref $A))))
+
+ ;; CHECK: (type $A (struct ))
(type $A (struct))
(type $B (struct_subtype $A))
- ;; CHECK: (rec
- ;; CHECK-NEXT: (type $X (struct (field (ref $A'))))
(type $X (struct (ref $B)))
- ;; CHECK: (type $A' (struct ))
(type $A' (struct))
)
;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $foo (type $none_=>_none)
- ;; CHECK-NEXT: (local $b (ref null $A'))
+ ;; CHECK-NEXT: (local $b (ref null $A))
;; CHECK-NEXT: (local $x (ref null $X))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
@@ -779,6 +859,59 @@
)
)
+;; Regression test for bug where we unsoundly merged supertypes and siblings in
+;; a single step.
+(module
+ (rec
+ ;; $x and $y are structurally identical, but won't be merged because there is
+ ;; a cast to $y.
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $b (struct (field (ref null $x))))
+
+ ;; CHECK: (type $b1 (struct_subtype (field (ref null $y)) $b))
+
+ ;; CHECK: (type $x (struct (field anyref)))
+ (type $x (struct anyref))
+ ;; CHECK: (type $y (struct_subtype (field anyref) $x))
+ (type $y (struct_subtype anyref $x))
+
+ ;; If we did vertical and horizontal merges at the same time, these three
+ ;; types would be put into the same initial partition and $b1 would be merged
+ ;; into $a. This would be incorrect because then $b1 would no longer be a
+ ;; subtype of $b.
+ ;; CHECK: (type $a (struct (field (ref null $y))))
+ (type $a (struct (ref null $y)))
+ (type $b (struct (ref null $x)))
+ (type $b1 (struct_subtype (ref null $y) $b))
+ )
+
+ ;; CHECK: (type $none_=>_ref|$b| (func (result (ref $b))))
+
+ ;; CHECK: (func $test (type $none_=>_ref|$b|) (result (ref $b))
+ ;; CHECK-NEXT: (local $0 (ref null $a))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test $y
+ ;; CHECK-NEXT: (struct.new_default $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new_default $b1)
+ ;; CHECK-NEXT: )
+ (func $test (result (ref $b))
+ ;; Use $a to prevent it from being dropped completely.
+ (local (ref null $a))
+
+ ;; Cast to prevent $x and $y from being merged.
+ (drop
+ (ref.test $y
+ (struct.new_default $x)
+ )
+ )
+
+ ;; If $b1 were merged with $a, this would cause a validation failure.
+ (struct.new_default $b1)
+ )
+)
+
;; Check that a ref.test inhibits merging (ref.cast is already checked above).
(module
;; CHECK: (rec