summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/heap2local.wast1589
-rw-r--r--test/passes/Oz_fuzz-exec_all-features.txt30
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 ;)