| ;; 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.set $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) | |
| ) | |
| ) |