| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| ;; RUN: wasm-opt %s --remove-unused-names --heap-store-optimization -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)))) |
| |
| ;; CHECK: (type $struct2 (struct (field (mut i32)) (field (mut i32)))) |
| |
| ;; CHECK: (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) |
| |
| ;; CHECK: (tag $tag (type $1)) |
| (tag $tag) |
| |
| (type $struct (struct (field (mut i32)))) |
| |
| (type $struct2 (struct (field (mut i32)) (field (mut i32)))) |
| |
| (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) |
| |
| ;; CHECK: (func $tee (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local $ref2 (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref2 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; 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 $ref2 (ref null $struct)) |
| (local.set $ref |
| (struct.new $struct |
| (i32.const 10) |
| ) |
| ) |
| ;; Any instruction that can not be swapped and is not |
| ;; the expected struct.set breaks the pattern. |
| (local.set $ref2 (local.get $ref)) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 20) |
| ) |
| ) |
| |
| ;; CHECK: (func $dont-swap-subsequent-struct-new (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local $ref2 (ref null $struct)) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref2 |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dont-swap-subsequent-struct-new |
| (local $ref (ref null $struct)) |
| (local $ref2 (ref null $struct)) |
| (local.set $ref |
| (struct.new $struct |
| (i32.const 10) |
| ) |
| ) |
| (nop) |
| ;; We do not swap with another local.set of struct.new. |
| (local.set $ref2 |
| (struct.new $struct |
| (i32.const 20) |
| ) |
| ) |
| ;; last instruction in the block won't be swapped. |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 20) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-local-write (type $1) |
| ;; 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 none) |
| ;; 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 (type $1) |
| ;; 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 none) |
| ;; 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 (type $1) |
| ;; 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 none) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; 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 (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local $ref3 (ref null $struct3)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref3 |
| ;; CHECK-NEXT: (struct.new $struct3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 33) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $default |
| (local $ref (ref null $struct)) |
| (local $ref3 (ref null $struct3)) |
| ;; We optimize new_default as well, adding default values as needed. |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new_default $struct) |
| ) |
| (i32.const 10) |
| ) |
| (struct.set $struct3 1 |
| (local.tee $ref3 |
| (struct.new_default $struct3) |
| ) |
| (i32.const 33) |
| ) |
| ) |
| |
| ;; CHECK: (func $many-news (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct3)) |
| ;; CHECK-NEXT: (local $ref2 (ref null $struct3)) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (nop) |
| ;; 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: (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 $unreachable (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unreachable |
| (local $ref (ref null $struct)) |
| ;; Do not optimize unreachable code, either in the new (first pair) or the |
| ;; set (second pair) |
| (local.set $ref |
| (struct.new $struct |
| (unreachable) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 10) |
| ) |
| (nop) |
| (local.set $ref |
| (struct.new $struct |
| (i32.const 20) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (unreachable) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper-i32 (type $7) (param $x i32) (result i32) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| (func $helper-i32 (param $x i32) (result i32) |
| (i32.const 42) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block $label |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $label) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value (result i32) |
| ;; Test we handle control flow in the struct.set's value when we combine a |
| ;; struct.set with a struct.new. We should not optimize here. |
| (local $ref (ref null $struct)) |
| (block $label |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| ;; This conditional break happens *after* the local.tee of $ref. We |
| ;; must not move code around that reorders it, since there is a use |
| ;; of the local below that could notice changes. |
| (br $label) |
| ) |
| (else |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| ;; We did not reach the struct.set, but we did reach the local.tee, so this |
| ;; reads the initial value of 1 (and does not trap on a nullref). |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-safe (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-safe (result i32) |
| ;; As above, but now the control flow in the value is safe: it does not |
| ;; escape out. We should optimize here. |
| (local $ref (ref null $struct)) |
| (block $label |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (i32.const 1337) ;; the break to $out was replaced |
| ) |
| (else |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-safe-call (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (call $helper-i32 |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-safe-call (result i32) |
| ;; As above, but now the possible control flow is a call. It may throw, but |
| ;; that would go outside of the function, which is fine. |
| (local $ref (ref null $struct)) |
| (block $label |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (call $helper-i32 (i32.const 42)) ;; the if was replaced by this call |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-safe-return (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (return |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-safe-return (result i32) |
| ;; As above, but replace the call with a return in an if. We can still |
| ;; optimize (if the return is taken, we go outside of the function anyhow). |
| (local $ref (ref null $struct)) |
| (block $label |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (return (i32.const 42)) |
| ) |
| (else |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-unsafe-call (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block $label |
| ;; CHECK-NEXT: (try_table (catch $tag $label) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper-i32 |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-unsafe-call (result i32) |
| ;; As above, but now the call's possible throw could be caught *inside* the |
| ;; function, which means it is dangerous, and we do not optimize. |
| (local $ref (ref null $struct)) |
| (block $label |
| (try_table (catch $tag $label) ;; this try was added |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (call $helper-i32 (i32.const 42)) |
| ) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-later (type $5) (param $x i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-later (param $x i32) |
| (local $ref (ref null $struct)) |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ;; This later control flow should not prevent optimizing the struct.set |
| ;; before it. |
| (block $out |
| (br_if $out |
| (local.get $x) |
| ) |
| ) |
| (if |
| (local.get $x) |
| (then |
| (nop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $loop (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $loop |
| (local $ref (ref null $struct)) |
| (loop $loop |
| ;; There is a use of the reference at the top of the loop, and the br_if |
| ;; may get here, so this is a basic block before the struct.set that we |
| ;; need to be careful of reaching. We should not optimize here. |
| (drop |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (br $loop) |
| ) |
| (else |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $loop-more-flow (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $loop-more-flow |
| (local $ref (ref null $struct)) |
| (loop $loop |
| ;; As above, but add this if which adds more control flow at the loop top. |
| ;; We should still not optimize here. |
| (if |
| (i32.const 1) |
| (then |
| (br $loop) |
| ) |
| ) |
| (drop |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (br $loop) |
| ) |
| (else |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $loop-in-value (type $5) (param $x i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (loop $loop (result i32) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $loop-in-value (param $x i32) |
| (local $ref (ref null $struct)) |
| ;; The struct.set's value has a loop in it, but that is fine, as while there |
| ;; are backedges there, they are still contained in the value: we can't skip |
| ;; the struct.set. We can optimize here. |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (loop $loop (result i32) |
| (br_if $loop |
| (local.get $x) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $in-if-arm (type $6) (param $x i32) (param $y i32) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.tee $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $in-if-arm (param $x i32) (param $y i32) (result i32) |
| (local $ref (ref null $struct)) |
| (if |
| (local.get $x) |
| (then |
| (block $out |
| ;; We cannot optimize here, as the struct.get outside of the if can |
| ;; read different state if the br_if happens. |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (block (result i32) |
| (br_if $out |
| (local.get $x) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $in-if-arm-yes (type $6) (param $x i32) (param $y i32) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| (func $in-if-arm-yes (param $x i32) (param $y i32) (result i32) |
| (local $ref (ref null $struct)) |
| ;; As before, but the struct.get at the end is removed, so we can optimize. |
| (if |
| (local.get $x) |
| (then |
| (block $out |
| (struct.set $struct 0 |
| (local.tee $ref |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (block (result i32) |
| (br_if $out |
| (local.get $x) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ) |
| ) |
| (i32.const 1337) ;; this changed |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-sequence (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $out (result i32) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-sequence (result i32) |
| (local $ref (ref null $struct)) |
| (drop |
| (block $out (result i32) |
| (local.set $ref |
| ;; Also test struct.new_default here, with control flow. |
| (struct.new_default $struct) |
| ) |
| ;; The struct.get outside is a problem, so we do not optimize here, |
| ;; nor the set after us. |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 3) |
| ) |
| ;; This struct.set is not a problem: if we branch, we don't reach it |
| ;; anyhow. |
| (drop |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-sequence-2 (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $out (result i32) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-sequence-2 (result i32) |
| (local $ref (ref null $struct)) |
| (drop |
| (block $out (result i32) |
| (local.set $ref |
| (struct.new_default $struct) |
| ) |
| ;; As above, but the order of struct.sets is flipped. We can at least |
| ;; optimize the first here. |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 3) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (drop |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow-in-set-value-sequence-yes (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $out (result i32) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| (func $control-flow-in-set-value-sequence-yes (result i32) |
| (local $ref (ref null $struct)) |
| ;; As above, but the struct.get at the end is removed, allowing us to |
| ;; optimize it all. |
| (drop |
| (block $out (result i32) |
| (local.set $ref |
| (struct.new_default $struct) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (i32.const 3) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| ;; Note how this struct.get remains and does not stop us. |
| (drop |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| (i32.const 1337) |
| ) |
| |
| ;; CHECK: (func $multi-control-flow-in-set-value-sequence-yes (type $4) (result i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $out (result i32) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 5) |
| ;; CHECK-NEXT: (i32.const 6) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| (func $multi-control-flow-in-set-value-sequence-yes (result i32) |
| (local $ref (ref null $struct)) |
| ;; As above, but now we have multiple br_ifs. We optimize one, but then |
| ;; stop because of the control flow that is now in the struct.new. TODO we |
| ;; could be more precise here. |
| (drop |
| (block $out (result i32) |
| (local.set $ref |
| (struct.new_default $struct) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 3) |
| (i32.const 4) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 5) |
| (i32.const 6) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| (i32.const 1337) |
| ) |
| |
| ;; CHECK: (func $multi-control-flow-in-set-value-sequence-no (type $8) (result anyref) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $out (result i32) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: (br_if $out |
| ;; CHECK-NEXT: (i32.const 5) |
| ;; CHECK-NEXT: (i32.const 6) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| (func $multi-control-flow-in-set-value-sequence-no (result anyref) |
| (local $ref (ref null $struct)) |
| ;; As above, but now we have a dangerous local.get at the end, stopping us |
| ;; from optimizing. |
| (drop |
| (block $out (result i32) |
| (local.set $ref |
| (struct.new_default $struct) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 3) |
| (i32.const 4) |
| ) |
| ) |
| (struct.set $struct 0 |
| (local.get $ref) |
| (br_if $out |
| (i32.const 5) |
| (i32.const 6) |
| ) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| (local.get $ref) |
| ) |
| ) |