diff options
author | Alon Zakai <azakai@google.com> | 2021-05-12 07:43:35 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-12 07:43:35 -0700 |
commit | bfd01369a6dbb4629e88d227f085f959549e3dd5 (patch) | |
tree | b8cc90e26721f0338646a31c9956e41cf2fed2d8 /test | |
parent | 4cfbb5d90bd253c066d92affa685dbab5d824699 (diff) | |
download | binaryen-bfd01369a6dbb4629e88d227f085f959549e3dd5.tar.gz binaryen-bfd01369a6dbb4629e88d227f085f959549e3dd5.tar.bz2 binaryen-bfd01369a6dbb4629e88d227f085f959549e3dd5.zip |
Heap2Local: Use escape analysis to turn heap allocations into local data (#3866)
If we allocate some GC data, and do not let the reference escape, then we can
replace the allocation with locals, one local for each field in the allocation
basically. This avoids the allocation, and also allows us to optimize the locals
further.
On the Dart DeltaBlue benchmark, this is a 24% speedup (making it faster than
the JS version, incidentially), and also a 6% reduction in code size.
The tests are not the best way to show what this does, as the pass assumes
other passes will clean up after. Here is an example to clarify. First, in pseudocode:
ref = new Int(42)
do {
ref.set(ref.get() + 1)
} while (import(ref.get())
That is, we allocate an int on the heap and use it as a counter. Unnecessarily,
as it could be a normal int on the stack.
Wat:
(module
;; A boxed integer: an entire struct just to hold an int.
(type $boxed-int (struct (field (mut i32))))
(import "env" "import" (func $import (param i32) (result i32)))
(func "example"
(local $ref (ref null $boxed-int))
;; Allocate a boxed integer of 42 and save the reference to it.
(local.set $ref
(struct.new_with_rtt $boxed-int
(i32.const 42)
(rtt.canon $boxed-int)
)
)
;; Increment the integer in a loop, looking for some condition.
(loop $loop
(struct.set $boxed-int 0
(local.get $ref)
(i32.add
(struct.get $boxed-int 0
(local.get $ref)
)
(i32.const 1)
)
)
(br_if $loop
(call $import
(struct.get $boxed-int 0
(local.get $ref)
)
)
)
)
)
)
Before this pass, the optimizer could do essentially nothing with this.
Even with this pass, running -O1 has no effect, as the pass is only
used in -O2+. However, running --heap2local -O1 leads to this:
(func $0
(local $0 i32)
(local.set $0
(i32.const 42)
)
(loop $loop
(br_if $loop
(call $import
(local.tee $0
(i32.add
(local.get $0)
(i32.const 1)
)
)
)
)
)
)
All the GC heap operations have been removed, and we just
have a plain int now, allowing a bunch of other opts to run. That
output is basically the optimal code, I think.
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 ;) |