diff options
author | Alon Zakai <azakai@google.com> | 2021-10-14 13:05:37 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-14 13:05:37 -0700 |
commit | d592bad2b8fa777dab9682d2d2e47f9957c8051d (patch) | |
tree | ad8d90cb77c22baaf19dec222e4b963adf40e7c2 /test/lit/passes | |
parent | 5dfff5cc2c75a6d2b6fde7f20f46ba169020b116 (diff) | |
download | binaryen-d592bad2b8fa777dab9682d2d2e47f9957c8051d.tar.gz binaryen-d592bad2b8fa777dab9682d2d2e47f9957c8051d.tar.bz2 binaryen-d592bad2b8fa777dab9682d2d2e47f9957c8051d.zip |
[Wasm GC] Optimize subsequent struct.sets after a struct.new (#4244)
This optimizes this type of pattern:
(local.set $x (struct.new X Y Z))
(struct.set (local.get $x) X')
=>
(local.set $x (struct.new X' Y Z))
Note how the struct.set is removed, and X' moves to where X was.
This removes almost 90% (!) of the struct.sets in j2wasm output, which reduces
total code size by 2.5%. However, I see no speedup with this - I guess that either
this is not on the hot path, or V8 optimizes it well already, or the CPU is making
stores "free" anyhow...
Diffstat (limited to 'test/lit/passes')
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-heap.wast | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/test/lit/passes/optimize-instructions-gc-heap.wast b/test/lit/passes/optimize-instructions-gc-heap.wast new file mode 100644 index 000000000..4554e651d --- /dev/null +++ b/test/lit/passes/optimize-instructions-gc-heap.wast @@ -0,0 +1,719 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions -all -S -o - \ +;; RUN: | filecheck %s +;; +;; --remove-unused-names allows the optimizer to see that the blocks have no +;; breaks to them, and so they have no nonlinear control flow. + +(module + ;; CHECK: (type $struct (struct (field (mut i32)))) + (type $struct (struct (field (mut i32)))) + + ;; CHECK: (type $struct2 (struct (field (mut i32)) (field (mut i32)))) + (type $struct2 (struct (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (func $tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tee + (local $ref (ref null $struct)) + ;; The set is not needed as we can apply the 20 in the new. + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $side-effects-in-old-value + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effects-in-old-value + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + ;; Side effects here force us to keep the old value around. + (call $helper-i32 (i32.const 0)) + ) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $side-effects-in-new-value + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effects-in-new-value + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; Side effects here are not a problem. + (call $helper-i32 (i32.const 0)) + ) + ) + + ;; CHECK: (func $many-fields + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $many-fields + (local $ref (ref null $struct2)) + ;; Set to the first field. + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + (i32.const 10) + (i32.const 20) + ) + ) + (i32.const 30) + ) + ;; Set to the second. + (struct.set $struct2 1 + (local.tee $ref + (struct.new $struct2 + (i32.const 40) + (i32.const 50) + ) + ) + (i32.const 60) + ) + ) + + ;; CHECK: (func $side-effect-conflict + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (struct.set $struct2 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effect-conflict + (local $ref (ref null $struct2)) + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + (i32.const 10) + ;; Side effects on the second field prevent us from moving the set's + ;; value past it to replace the first field above it. + (call $helper-i32 (i32.const 0)) + ) + ) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $side-effect-ok + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effect-ok + (local $ref (ref null $struct2)) + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + ;; Side effects on the first field do not interfere. + (call $helper-i32 (i32.const 0)) + (i32.const 10) + ) + ) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $optimize-subsequent + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $optimize-subsequent + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; A set that comes right after can be optimized too. + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ) + + ;; CHECK: (func $optimize-subsequent-bad-local + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $optimize-subsequent-bad-local + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; As above, but the local.get uses a different local, so we have nothing + ;; to optimize. + (struct.set $struct 0 + (local.get $other) + (i32.const 20) + ) + ) + + ;; CHECK: (func $optimize-chain + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $optimize-chain + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ;; The value in the last item in the chain should apply. + (struct.set $struct 0 + (local.get $ref) + (i32.const 30) + ) + ) + + ;; CHECK: (func $pattern-breaker + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pattern-breaker + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; Anything that we don't recognize breaks the pattern. + (nop) + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ) + + ;; CHECK: (func $ref-local-write + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-write + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A write to the ref local prevents us from optimizing. + (local.set $ref + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-write-tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-write-tee + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (block (result i32) + ;; As above, but now in a tee. + (local.set $ref + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $other-local-write + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $other + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other-local-write + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A write to another local is fine. + (local.set $other + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-read + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-read + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A read of the ref local prevents us from optimizing. + (drop + (local.get $ref) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-read-tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-read-tee + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (block (result i32) + ;; As above, but now in a tee. + (drop + (local.get $ref) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-other-read + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $ref-other-read + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A read of another local is fine. + (drop + (local.get $other) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $tee-and-subsequent + ;; CHECK-NEXT: (local $ref (ref null $struct3)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $tee-and-subsequent + (local $ref (ref null $struct3)) + ;; Test the common pattern of several subsequent sets, one of which is + ;; using a tee. + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + (struct.set $struct3 1 + (local.get $ref) + (i32.const 50) + ) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 60) + ) + ) + + ;; CHECK: (func $side-effect-subsequent-ok + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $side-effect-subsequent-ok + (local $ref (ref null $struct2)) + (local.set $ref + (struct.new $struct2 + ;; The first field has side effects, but the second does not. + (call $helper-i32 (i32.const 0)) + (i32.const 10) + ) + ) + ;; Replace the second field with something with side effects. + (struct.set $struct2 1 + (local.get $ref) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $default + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $default + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + ;; Ignore a new_default for now. If the fields are defaultable then we + ;; could add them, in principle, but that might increase code size. + (struct.new_default $struct) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $many-news + ;; CHECK-NEXT: (local $ref (ref null $struct3)) + ;; CHECK-NEXT: (local $ref2 (ref null $struct3)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct3 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 400) + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref2 + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 400) + ;; CHECK-NEXT: (i32.const 600) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $many-news + (local $ref (ref null $struct3)) + (local $ref2 (ref null $struct3)) + ;; Test that we optimize for multiple struct.news in the same function. + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + (struct.set $struct3 1 + (local.get $ref) + (i32.const 50) + ) + (nop) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 60) + ) + (nop) + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 100) + (i32.const 200) + (i32.const 300) + ) + ) + (i32.const 400) + ) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 500) + ) + ;; Test inside an inner block. + (block $inner + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + ;; Use a different ref local here. + (struct.set $struct3 0 + (local.tee $ref2 + (struct.new $struct3 + (i32.const 100) + (i32.const 200) + (i32.const 300) + ) + ) + (i32.const 400) + ) + (struct.set $struct3 2 + (local.get $ref2) + (i32.const 500) + ) + (struct.set $struct3 1 + (local.get $ref2) + (i32.const 600) + ) + ) + ) + + ;; CHECK: (func $helper-i32 (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $helper-i32 (param $x i32) (result i32) + (i32.const 42) + ) +) |