diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/heap2local.wast | 1589 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.txt | 30 |
2 files changed, 1595 insertions, 24 deletions
diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast new file mode 100644 index 000000000..29c25a8df --- /dev/null +++ b/test/lit/passes/heap2local.wast @@ -0,0 +1,1589 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; (remove-unused-names allows the pass to see that blocks flow values) +;; RUN: wasm-opt %s -all --remove-unused-names --heap2local -S -o - | filecheck %s + +(module + (type $struct.A (struct (field (mut i32)) (field (mut f64)))) + + (type $struct.packed (struct (field (mut i8)))) + + (type $struct.nondefaultable (struct (field (rtt $struct.A)))) + + (type $struct.recursive (struct (field (mut (ref null $struct.recursive))))) + + (type $struct.nonnullable (struct (field (ref $struct.A)))) + + ;; CHECK: (func $simple + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simple + ;; Other passes can remove such a trivial case of an unused allocation, but + ;; we still optimize it. + (drop + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + + ;; CHECK: (func $to-local + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $to-local + (local $ref (ref null $struct.A)) + ;; While set to a local, this allocation has no get/set operations. Other + ;; optimizations can remove it, but so can we, turning the set into a + ;; drop (and adding some unnecessary code to allocate the values, which we + ;; depend on other passes to remove). + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + + ;; CHECK: (func $one-get + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $one-get + ;; An allocation followed by an immediate get of a field. This is a non- + ;; escaping allocation, with a use, so we can optimize it out. The + ;; allocation is dropped (letting later opts remove it), and the + ;; allocation's data is moved to locals: we write the initial value to the + ;; locals, and we read from the locals instead of the struct.get. + (drop + (struct.get $struct.A 0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $one-get-b + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $one-get-b + ;; Similar to the above, but using a different field index. + (drop + (struct.get $struct.A 1 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $one-set + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $one-set + ;; A simple optimizable allocation only used in one set. + (struct.set $struct.A 0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $packed + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_u $struct.packed 0 + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.packed + ;; CHECK-NEXT: (rtt.canon $struct.packed) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $packed + ;; We do not optimize packed structs yet. + (drop + (struct.get $struct.packed 0 + (struct.new_default_with_rtt $struct.packed + (rtt.canon $struct.packed) + ) + ) + ) + ) + + ;; CHECK: (func $with-init-values + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $struct.A + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $with-init-values + ;; When we get values to initialize the struct with, assign them to the + ;; proper locals. + (drop + (struct.get $struct.A 0 + (struct.new_with_rtt $struct.A + (i32.const 2) + (f64.const 3.14159) + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $ignore-unreachable + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (struct.new_with_rtt $struct.A + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ignore-unreachable + ;; An unreachable allocation is not worth trying to process; DCE should + ;; remove it. + (drop + (struct.get $struct.A 0 + (struct.new_with_rtt $struct.A + (i32.const 2) + (unreachable) + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $nondefaultable + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct.nondefaultable 0 + ;; CHECK-NEXT: (struct.new_with_rtt $struct.nondefaultable + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: (rtt.canon $struct.nondefaultable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nondefaultable + ;; We do not optimize structs with nondefaultable types that we cannot + ;; handle, like rtts. + (drop + (struct.get $struct.nondefaultable 0 + (struct.new_with_rtt $struct.nondefaultable + (rtt.canon $struct.A) + (rtt.canon $struct.nondefaultable) + ) + ) + ) + ) + + ;; CHECK: (func $simple-one-local-set + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simple-one-local-set + (local $ref (ref null $struct.A)) + ;; A simple optimizable allocation only used in one set, and also stored + ;; to a local. The local.set should not prevent our optimization, and the + ;; local.set can be turned into a drop. + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (struct.set $struct.A 0 + (local.get $ref) + (i32.const 1) + ) + ) + + ;; CHECK: (func $simple-one-local-get (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simple-one-local-get (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; A simple optimizable allocation only used in one get, via a local. + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $send-ref (param $0 (ref null $struct.A)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $send-ref (param (ref null $struct.A)) + ) + + ;; CHECK: (func $safe-to-drop (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $safe-to-drop (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; An extra drop does not let the allocation escape. + (drop + (local.get $ref) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $escape-via-call (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $send-ref + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $escape-via-call (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; The allocation escapes into a call. + (call $send-ref + (local.get $ref) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $safe-to-drop-multiflow (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $safe-to-drop-multiflow (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; An extra drop + multiple flows through things do not stop us. + (drop + (block (result (ref null $struct.A)) + (block (result (ref null $struct.A)) + (loop (result (ref null $struct.A)) + (local.get $ref) + ) + ) + ) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $escape-after-multiflow (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $send-ref + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $escape-after-multiflow (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; An escape after multiple flows. + (call $send-ref + (block (result (ref null $struct.A)) + (block (result (ref null $struct.A)) + (loop (result (ref null $struct.A)) + (local.get $ref) + ) + ) + ) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $non-exclusive-set (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (select (result (ref $struct.A)) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $non-exclusive-set (result f64) + (local $ref (ref null $struct.A)) + ;; A set that receives two different allocations, and so we should not try + ;; to optimize it. + (local.set $ref + (select + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + (i32.const 1) + ) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $local-copies (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-copies (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; Copying our allocation through locals does not bother us. + (local.set $ref + (local.get $ref) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $local-copies-2 + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $ref-2 (ref null $struct.A)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref-2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-copies-2 + (local $ref (ref null $struct.A)) + (local $ref-2 (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; Copying our allocation through locals does not bother us, even if it's + ;; another local. + (local.set $ref-2 + (local.get $ref) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref) + ) + ) + (drop + (struct.get $struct.A 1 + (local.get $ref-2) + ) + ) + ) + + ;; CHECK: (func $local-copies-conditional (param $x i32) (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $local-copies-conditional (param $x i32) (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; Possibly copying our allocation through locals does not bother us. Note + ;; that as a result of this the final local.get has two sets that send it + ;; values, but we know they are both the same allocation. + (if (local.get $x) + (local.set $ref + (local.get $ref) + ) + ) + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $branch-value (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct.A)) + ;; CHECK-NEXT: (call $send-ref + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $branch-value (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ;; Returning our allocation from a block does not bother us. + (struct.get $struct.A 1 + (block (result (ref null $struct.A)) + ;; This call in the block should not bother us either. + (call $send-ref + (ref.null $struct.A) + ) + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $non-exclusive-get (param $x i32) (result f64) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $non-exclusive-get (param $x i32) (result f64) + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (if (local.get $x) + (local.set $ref + (ref.null $struct.A) + ) + ) + ;; A get that receives two different allocations, and so we should not try + ;; to optimize it. + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + + ;; CHECK: (func $tee (result i32) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $tee (result i32) + (local $ref (ref null $struct.A)) + (struct.get $struct.A 0 + ;; A tee flows out the value, and we can optimize this allocation. + (local.tee $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $tee-set + ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) + ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.recursive)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.recursive + ;; CHECK-NEXT: (rtt.canon $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tee-set + (local $ref (ref null $struct.recursive)) + ;; As above, but with a set, and also a recursive type. + (struct.set $struct.recursive 0 + (local.tee $ref + (struct.new_default_with_rtt $struct.recursive + (rtt.canon $struct.recursive) + ) + ) + (ref.null $struct.recursive) + ) + ) + + ;; CHECK: (func $set-value + ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) + ;; CHECK-NEXT: (struct.set $struct.recursive 0 + ;; CHECK-NEXT: (ref.null $struct.recursive) + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.recursive + ;; CHECK-NEXT: (rtt.canon $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set-value + (local $ref (ref null $struct.recursive)) + (struct.set $struct.recursive 0 + (ref.null $struct.recursive) + ;; As above, but operands reversed: the allocation is now the value, not + ;; the reference, and so it escapes. + (local.tee $ref + (struct.new_default_with_rtt $struct.recursive + (rtt.canon $struct.recursive) + ) + ) + ) + ) + + ;; CHECK: (func $initialize-with-reference + ;; CHECK-NEXT: (local $0 (ref null $struct.recursive)) + ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) + ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.recursive)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.recursive + ;; CHECK-NEXT: (rtt.canon $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $struct.recursive + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (rtt.canon $struct.recursive) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct.recursive)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $initialize-with-reference + (local $0 (ref null $struct.recursive)) + (local.set $0 + ;; The outer allocation can be optimized, as it does not escape. + (struct.new_with_rtt $struct.recursive + ;; The inner allocation should not prevent the outer one from being + ;; optimized through some form of confusion. + ;; After the outer one is optimized, the inner one can be optimized in + ;; principle, as it can be seen to no longer escape. However, we depend + ;; on other optimizations to actually remove the outer allocation (like + ;; vacuum), and so it cannot be optimized. If we ran vaccum, and then + ;; additional iterations, this might be handled. + (struct.new_default_with_rtt $struct.recursive + (rtt.canon $struct.recursive) + ) + (rtt.canon $struct.recursive) + ) + ) + (drop + (struct.get $struct.recursive 0 + (local.get $0) + ) + ) + ) + + ;; CHECK: (func $escape-flow-out (result anyref) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (struct.set $struct.A 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + (func $escape-flow-out (result anyref) + (local $ref (ref null $struct.A)) + (struct.set $struct.A 0 + (local.tee $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (i32.const 1) + ) + ;; The allocation escapes out to the caller by flowing out. + (local.get $ref) + ) + + ;; CHECK: (func $escape-return (result anyref) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (struct.set $struct.A 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $escape-return (result anyref) + (local $ref (ref null $struct.A)) + (struct.set $struct.A 0 + (local.tee $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (i32.const 1) + ) + ;; The allocation escapes out to the caller by a return. + (return + (local.get $ref) + ) + ) + + ;; CHECK: (func $non-nullable (param $a (ref $struct.A)) + ;; CHECK-NEXT: (local $1 (ref null $struct.A)) + ;; CHECK-NEXT: (local $2 (ref null $struct.A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.nonnullable)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $struct.nonnullable + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rtt.canon $struct.nonnullable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $non-nullable (param $a (ref $struct.A)) + (drop + ;; An optimizable case where the type is non-nullable, which requires + ;; special handling in locals. + (struct.get $struct.nonnullable 0 + (struct.new_with_rtt $struct.nonnullable + (local.get $a) + (rtt.canon $struct.nonnullable) + ) + ) + ) + ) + + ;; CHECK: (func $before-loop-use-multi (param $x i32) + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 f64) + ;; CHECK-NEXT: (loop $outer + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (f64.const 2.1828) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $struct.A + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (loop $inner + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $inner + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $before-loop-use-multi (param $x i32) + (local $ref (ref null $struct.A)) + ;; Allocate in a loop, and use that allocation multiple times in that loop + ;; in various ways inside. + (loop $outer + (local.set $ref + (struct.new_with_rtt $struct.A + (i32.const 2) + (f64.const 2.1828) + (rtt.canon $struct.A) + ) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref) + ) + ) + (if (local.get $x) + (drop + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + (struct.set $struct.A 1 + (local.get $ref) + (f64.const 42) + ) + ) + (loop $inner + (struct.set $struct.A 0 + (local.get $ref) + (i32.add + (struct.get $struct.A 0 + (local.get $ref) + ) + (i32.const 1) + ) + ) + (br_if $inner + (local.get $x) + ) + ) + (if (local.get $x) + (drop + (struct.get $struct.A 0 + (local.get $ref) + ) + ) + (drop + (struct.get $struct.A 1 + (local.get $ref) + ) + ) + ) + (br $outer) + ) + ) + + ;; CHECK: (func $multi-separate + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-separate + ;; Multiple independent things we can optimize. + (drop + (struct.get $struct.A 0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + (drop + (struct.get $struct.A 0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + (drop + (struct.get $struct.A 1 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + ) + ) + + ;; CHECK: (func $multi-separate-same-local-index + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-separate-same-local-index + (local $ref (ref null $struct.A)) + ;; Multiple independent things we can optimize that use the same local + ;; index, but they do not conflict in their live ranges. + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref) + ) + ) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref) + ) + ) + ) + + ;; CHECK: (func $multi-separate-different-local-index-overlapping-lifetimes + ;; CHECK-NEXT: (local $ref1 (ref null $struct.A)) + ;; CHECK-NEXT: (local $ref2 (ref null $struct.A)) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 f64) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multi-separate-different-local-index-overlapping-lifetimes + (local $ref1 (ref null $struct.A)) + (local $ref2 (ref null $struct.A)) + ;; Multiple independent things we can optimize that use different local + ;; indexes, but whose lifetimes overlap. We should not be confused by that. + (local.set $ref1 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (local.set $ref2 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref1) + ) + ) + (drop + (struct.get $struct.A 0 + (local.get $ref2) + ) + ) + ) + + ;; CHECK: (func $get-through-block (result f64) + ;; CHECK-NEXT: (local $0 (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-through-block (result f64) + (local $0 (ref null $struct.A)) + (local.set $0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (struct.get $struct.A 1 + (block $block (result (ref null $struct.A)) + (drop + ;; A branch to the block. This ensures its name is not removable. And + ;; it indicates that the block does not have a single value that + ;; flows out, which means we do not have exclusive use of the + ;; allocation on this path, and must give up. + (br_if $block + (ref.null $struct.A) + (i32.const 0) + ) + ) + (local.get $0) + ) + ) + ) + + ;; CHECK: (func $branch-to-block (result f64) + ;; CHECK-NEXT: (local $0 (ref null $struct.A)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get $struct.A 1 + ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $block + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $branch-to-block (result f64) + (local $0 (ref null $struct.A)) + (local.set $0 + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (struct.get $struct.A 1 + (block $block (result (ref null $struct.A)) + (drop + ;; A branch to the block of our allocation. However, there is also + ;; a fallthrough value as well, so we must give up. + (br_if $block + (local.get $0) + (i32.const 0) + ) + ) + (ref.null $struct.A) + ) + ) + ) + + ;; CHECK: (func $ref-as-non-null + ;; CHECK-NEXT: (local $ref (ref null $struct.A)) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct.A)) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct.A + ;; CHECK-NEXT: (rtt.canon $struct.A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-as-non-null + (local $ref (ref null $struct.A)) + (local.set $ref + (struct.new_default_with_rtt $struct.A + (rtt.canon $struct.A) + ) + ) + (struct.set $struct.A 0 + ;; We can see that the input to this RefAsNonNull is always non-null, as + ;; it is our allocation, and so it does not prevent us from optimizing + ;; here. + (ref.as_non_null + (local.get $ref) + ) + (i32.const 1) + ) + ;; Another RefAsNonNull, to check we do not modify irrelevant ones. + (drop + (ref.as_non_null + (ref.null any) + ) + ) + ) +) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index cf3335dce..b5bda3fb6 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -60,38 +60,20 @@ (export "init-array-packed" (func $10)) (export "cast-func-to-struct" (func $12)) (func $0 (; has Stack IR ;) - (local $0 (ref null $struct)) + (local $0 i32) (call $log - (struct.get $struct 0 - (local.tee $0 - (struct.new_default_with_rtt $struct - (rtt.canon $struct) - ) - ) - ) - ) - (struct.set $struct 0 - (local.get $0) - (i32.const 42) + (i32.const 0) ) (call $log - (struct.get $struct 0 - (local.get $0) - ) - ) - (struct.set $struct 0 - (local.get $0) - (i32.const 100) + (i32.const 42) ) (call $log - (struct.get $struct 0 - (local.get $0) + (local.tee $0 + (i32.const 100) ) ) (call $log - (struct.get $struct 0 - (local.get $0) - ) + (i32.const 100) ) ) (func $1 (; has Stack IR ;) |