summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-08-12 12:30:23 -0700
committerGitHub <noreply@github.com>2024-08-12 12:30:23 -0700
commita4f9128f94b540fa04b67610eb501cb32ea203b4 (patch)
tree8ae772674ca1e90b40e9cde9fe5ad6be5d945415 /test
parente729e012c50eb118be09f6a8005f0c9090fff3a0 (diff)
downloadbinaryen-a4f9128f94b540fa04b67610eb501cb32ea203b4.tar.gz
binaryen-a4f9128f94b540fa04b67610eb501cb32ea203b4.tar.bz2
binaryen-a4f9128f94b540fa04b67610eb501cb32ea203b4.zip
GlobalTypeOptimization: Reorder fields in order to remove them (#6820)
Before, we only removed fields from the end of a struct. If we had, say struct Foo { int x; int y; int z; }; // Add no fields but inherit the parent's. struct Bar : Foo {}; If y is only used in Bar, but never Foo, then we still kept it around, because if we removed it from Foo we'd end up with Foo = {x, z}, Bar = {x, y, z} which is invalid - Bar no longer extends Foo. But we can do this if we first reorder the two: struct Foo { int x; int z; int y; // now y is at the end }; struct Bar : Foo {}; And the optimized form is struct Foo { int x; int z; }; struct Bar : Foo { int y; // now y is added in Bar }; This lets us remove all fields possible in all cases AFAIK. This situation is not super-common, as most fields are actually used both up and down the hierarchy (if they are used at all), but testing on some large real-world codebases, I see 10 fields removed in Java, 45 in Kotlin, and 31 in Dart testcases. The NFC change to src/wasm-type-ordering.h was needed for this to compile.
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/gto-removals.wast458
1 files changed, 443 insertions, 15 deletions
diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast
index c5e148afa..c2e82aee9 100644
--- a/test/lit/passes/gto-removals.wast
+++ b/test/lit/passes/gto-removals.wast
@@ -637,26 +637,27 @@
)
)
-;; We can remove fields from the end if they are only used in subtypes, because
-;; the subtypes can always add fields at the end (and only at the end).
+;; We can remove fields if they are only used in subtypes, because we can
+;; reorder the fields in the super and re-add them in the sub, appending on top
+;; of the now-shorter super.
(module
;; CHECK: (rec
- ;; CHECK-NEXT: (type $parent (sub (struct (field i32) (field i64))))
+ ;; CHECK-NEXT: (type $parent (sub (struct (field i64))))
(type $parent (sub (struct (field i32) (field i64) (field f32) (field f64))))
- ;; CHECK: (type $child (sub $parent (struct (field i32) (field i64) (field f32) (field f64) (field anyref))))
+ ;; CHECK: (type $child (sub $parent (struct (field i64) (field i32) (field f32) (field f64) (field anyref))))
(type $child (sub $parent (struct (field i32) (field i64) (field f32) (field f64) (field anyref))))
;; CHECK: (type $2 (func (param (ref $parent) (ref $child))))
;; CHECK: (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (struct.get $parent 1
+ ;; CHECK-NEXT: (struct.get $parent 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (struct.get $child 0
+ ;; CHECK-NEXT: (struct.get $child 1
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -678,9 +679,10 @@
;; CHECK-NEXT: )
(func $func (param $x (ref $parent)) (param $y (ref $child))
;; The parent has fields 0, 1, 2, 3 and the child adds 4.
- ;; Use fields only 1 in the parent, and all the rest in the child. We can
- ;; only remove from the end in the child, which means we can remove 2 and 3
- ;; in the parent, but not 0.
+ ;; Use only field 1 in the parent, and all the rest in the child. We can
+ ;; reorder field 1 to the start of the parent (flipping its position with
+ ;; field 0) and then remove all the fields but the now-first. The child
+ ;; keeps all fields, but is reordered.
(drop (struct.get $parent 1 (local.get $x)))
(drop (struct.get $child 0 (local.get $y)))
(drop (struct.get $child 2 (local.get $y)))
@@ -691,31 +693,31 @@
(module
;; CHECK: (rec
- ;; CHECK-NEXT: (type $parent (sub (struct (field i32) (field i64) (field (mut f32)))))
+ ;; CHECK-NEXT: (type $parent (sub (struct (field i64) (field (mut f32)))))
(type $parent (sub (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)))))
- ;; CHECK: (type $child (sub $parent (struct (field i32) (field i64) (field (mut f32)) (field f64) (field anyref))))
+ ;; CHECK: (type $child (sub $parent (struct (field i64) (field (mut f32)) (field i32) (field f64) (field anyref))))
(type $child (sub $parent (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)) (field (mut anyref)))))
;; CHECK: (type $2 (func (param (ref $parent) (ref $child))))
;; CHECK: (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
- ;; CHECK-NEXT: (struct.set $parent 2
+ ;; CHECK-NEXT: (struct.set $parent 1
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (f32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (struct.get $parent 1
+ ;; CHECK-NEXT: (struct.get $parent 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (struct.get $child 0
+ ;; CHECK-NEXT: (struct.get $child 2
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (struct.get $child 2
+ ;; CHECK-NEXT: (struct.get $child 1
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -743,6 +745,54 @@
)
)
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $parent (sub (struct (field i64) (field (mut f32)))))
+ (type $parent (sub (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)))))
+
+ ;; CHECK: (type $child (sub $parent (struct (field i64) (field (mut f32)) (field i32) (field anyref))))
+ (type $child (sub $parent (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)) (field (mut anyref)))))
+
+ ;; CHECK: (type $2 (func (param (ref $parent) (ref $child))))
+
+ ;; CHECK: (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
+ ;; CHECK-NEXT: (struct.set $parent 1
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (f32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $parent 0
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $child 2
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $child 1
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $child 3
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $func (param $x (ref $parent)) (param $y (ref $child))
+ ;; As above, but now we remove fields in the child as well: 3 is not used.
+ (struct.set $parent 2 (local.get $x) (f32.const 0))
+
+ (drop (struct.get $parent 1 (local.get $x)))
+ (drop (struct.get $child 0 (local.get $y)))
+ (drop (struct.get $child 2 (local.get $y)))
+ ;; the read of 3 was removed here.
+ (drop (struct.get $child 4 (local.get $y)))
+ )
+)
+
;; A parent with two children, and there are only reads of the parent. Those
;; reads might be of data of either child, of course (as a refernce to the
;; parent might point to them), so we cannot optimize here.
@@ -977,3 +1027,381 @@
)
)
)
+
+;; A parent with two children, with fields used in various combinations.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field i64) (field v128) (field nullref))))
+ (type $A (sub (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+
+ ;; CHECK: (type $C (sub $A (struct (field i64) (field v128) (field nullref) (field f64) (field anyref))))
+ (type $C (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+
+ ;; CHECK: (type $B (sub $A (struct (field i64) (field v128) (field nullref) (field f32) (field anyref))))
+ (type $B (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+ )
+
+ ;; CHECK: (type $3 (func (param anyref)))
+
+ ;; CHECK: (func $func (type $3) (param $x anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 3
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 3
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 4
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 4
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 1
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 1
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 2
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 2
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $func (param $x anyref)
+ ;; Field 0 (i32) is used nowhere.
+ ;; Field 1 (i64) is used only in $A.
+ ;; Field 2 (f32) is used only in $B.
+ ;; Field 3 (f64) is used only in $C.
+ ;; Field 4 (anyref) is used only in $B and $C.
+ ;; Field 5 (v128) is used only in $A and $C.
+ ;; Field 6 (nullref) is used only in $A and $B.
+ ;; As a result:
+ ;; * A can keep only fields 1, 5, 6 (i64, v128, nullref).
+ ;; * B keeps A's fields, and appends 2, 4 (f32, anyref).
+ ;; * C keeps A's fields, and appends 3, 4 (f64, anyref).
+
+ (drop (struct.get $A 1 (ref.cast (ref $A) (local.get $x))))
+
+ (drop (struct.get $B 2 (ref.cast (ref $B) (local.get $x))))
+
+ (drop (struct.get $C 3 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $B 4 (ref.cast (ref $B) (local.get $x))))
+ (drop (struct.get $C 4 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $A 5 (ref.cast (ref $A) (local.get $x))))
+ (drop (struct.get $C 5 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $A 6 (ref.cast (ref $A) (local.get $x))))
+ (drop (struct.get $B 6 (ref.cast (ref $B) (local.get $x))))
+ )
+)
+
+;; As above, but instead of $A having children $B, $C, now they are a chain,
+;; $A :> $B :> $C
+;; $C must now also include $B's fields (specifically field 2, the f32).
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field i64) (field v128) (field nullref))))
+ (type $A (sub (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+
+ ;; CHECK: (type $B (sub $A (struct (field i64) (field v128) (field nullref) (field f32) (field anyref))))
+ (type $B (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+
+ ;; CHECK: (type $C (sub $B (struct (field i64) (field v128) (field nullref) (field f32) (field anyref) (field f64))))
+ (type $C (sub $B (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field v128) (field nullref))))
+ )
+
+ ;; CHECK: (type $3 (func (param anyref)))
+
+ ;; CHECK: (func $func (type $3) (param $x anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 3
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 5
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 4
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 4
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 1
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $C 1
+ ;; CHECK-NEXT: (ref.cast (ref $C)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 2
+ ;; CHECK-NEXT: (ref.cast (ref $A)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 2
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $func (param $x anyref)
+ ;; Same uses as before.
+
+ (drop (struct.get $A 1 (ref.cast (ref $A) (local.get $x))))
+
+ (drop (struct.get $B 2 (ref.cast (ref $B) (local.get $x))))
+
+ (drop (struct.get $C 3 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $B 4 (ref.cast (ref $B) (local.get $x))))
+ (drop (struct.get $C 4 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $A 5 (ref.cast (ref $A) (local.get $x))))
+ (drop (struct.get $C 5 (ref.cast (ref $C) (local.get $x))))
+
+ (drop (struct.get $A 6 (ref.cast (ref $A) (local.get $x))))
+ (drop (struct.get $B 6 (ref.cast (ref $B) (local.get $x))))
+ )
+)
+
+;; The parent $A is an empty struct, with nothing to remove. See we do not error
+;; here.
+(module
+ ;; CHECK: (type $A (sub (struct)))
+ (type $A (sub (struct)))
+
+ ;; CHECK: (type $B (sub $A (struct)))
+ (type $B (sub $A (struct)))
+
+ ;; CHECK: (type $2 (func (param (ref $B))))
+
+ ;; CHECK: (func $func (type $2) (param $x (ref $B))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $func (param $x (ref $B))
+ ;; Use $B in a param to keep it alive, and lead us to process it and $A.
+ )
+)
+
+;; As above, but now $B has fields to remove.
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct)))
+ (type $A (sub (struct)))
+
+ ;; CHECK: (type $B (sub $A (struct)))
+ (type $B (sub $A (struct (field i32) (field i64))))
+
+ ;; CHECK: (type $2 (func (param (ref $B))))
+
+ ;; CHECK: (func $func (type $2) (param $x (ref $B))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $func (param $x (ref $B))
+ )
+)
+
+;; As above, but now $B's fields are used.
+(module
+ ;; CHECK: (type $A (sub (struct)))
+ (type $A (sub (struct)))
+
+ ;; CHECK: (type $B (sub $A (struct (field i32) (field i64))))
+ (type $B (sub $A (struct (field i32) (field i64))))
+
+ ;; CHECK: (type $2 (func (param (ref $B))))
+
+ ;; CHECK: (func $func (type $2) (param $x (ref $B))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 0
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 1
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $func (param $x (ref $B))
+ (drop (struct.get $B 0 (local.get $x)))
+ (drop (struct.get $B 1 (local.get $x)))
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field i32))))
+ (type $A (sub (struct (field i32))))
+ ;; CHECK: (type $B (sub $A (struct (field i32))))
+ (type $B (sub $A (struct (field i32) (field f64))))
+ )
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (func $test (type $2)
+ ;; CHECK-NEXT: (local $x (ref null $A))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 0
+ ;; CHECK-NEXT: (ref.cast (ref $B)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (local $x (ref null $A))
+ ;; We cannot remove anything from $A, but we can from $B. That $A is
+ ;; unchanged should not confuse us.
+ (drop
+ (struct.get $A 0
+ (local.get $x)
+ )
+ )
+ ;; $B reads field 0, but not its new field 1.
+ (drop
+ (struct.get $B 0
+ (ref.cast (ref $B)
+ (local.get $x)
+ )
+ )
+ )
+ )
+)
+
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct)))
+ (type $A (sub (struct (field i32))))
+ ;; CHECK: (type $B (sub $A (struct (field i32) (field f64))))
+ (type $B (sub $A (struct (field i32) (field f64))))
+ )
+
+ ;; CHECK: (type $2 (func))
+
+ ;; CHECK: (func $test (type $2)
+ ;; CHECK-NEXT: (local $x (ref null $B))
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (struct.new $B
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (f64.const 3.14159)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 0
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B 1
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test
+ (local $x (ref null $B))
+ ;; We can remove everything from $A, but nothing from $B. That $A changes
+ ;; entirely, and $B changes not at all, should not cause any errors.
+ (local.set $x
+ (struct.new $B
+ (i32.const 42)
+ (f64.const 3.14159)
+ )
+ )
+ (drop
+ (struct.get $B 0
+ (local.get $x)
+ )
+ )
+ (drop
+ (struct.get $B 1
+ (local.get $x)
+ )
+ )
+ )
+)