;; 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: foreach %s %t wasm-opt -all --remove-unused-names --heap2local -S -o - | filecheck %s (module ;; CHECK: (type $struct.A (struct (field (mut i32)) (field (mut f64)))) (type $struct.A (struct (field (mut i32)) (field (mut f64)))) ;; CHECK: (type $struct.recursive (struct (field (mut (ref null $struct.recursive))))) ;; CHECK: (type $struct.packed (struct (field (mut i8)))) (type $struct.packed (struct (field (mut i8)))) (type $struct.nondefaultable (struct (field (ref $struct.A)))) (type $struct.recursive (struct (field (mut (ref null $struct.recursive))))) (type $struct.nonnullable (struct (field (ref $struct.A)))) ;; CHECK: (func $simple (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) ;; CHECK: (func $to-local (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) ;; CHECK: (func $one-get (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) ) ;; CHECK: (func $one-get-b (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) ) ;; CHECK: (func $one-set (type $1) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; 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 $struct.A) (i32.const 1) ) ) ;; CHECK: (func $packed (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct.packed 0 ;; CHECK-NEXT: (struct.new_default $struct.packed) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $packed ;; We do not optimize packed structs yet. (drop (struct.get $struct.packed 0 (struct.new_default $struct.packed) ) ) ) ;; CHECK: (func $with-init-values (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A (i32.const 2) (f64.const 3.14159) ) ) ) ) ;; CHECK: (func $ignore-unreachable (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; 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 $struct.A (i32.const 2) (unreachable) ) ) ) ) ;; CHECK: (func $nondefaultable (type $1) ;; CHECK-NEXT: (local $0 (ref $struct.A)) ;; CHECK-NEXT: (local $1 (ref $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $nondefaultable ;; The non-nullable types here can fit in locals. (drop (struct.get $struct.nondefaultable 0 (struct.new $struct.nondefaultable (struct.new_default $struct.A) ) ) ) ) ;; CHECK: (func $simple-one-local-set (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $struct.A) ) (struct.set $struct.A 0 (local.get $ref) (i32.const 1) ) ) ;; CHECK: (func $simple-one-local-get (type $2) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 (type $4) (param $0 (ref null $struct.A)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $send-ref (param (ref null $struct.A)) ) ;; CHECK: (func $safe-to-drop (type $2) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 (type $2) (result f64) ;; CHECK-NEXT: (local $ref (ref null $struct.A)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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 $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 (type $2) (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 nullref) ;; 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: (ref.null none) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 (type $2) (result f64) ;; CHECK-NEXT: (local $ref (ref null $struct.A)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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 $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 (type $2) (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 $struct.A) ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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 $struct.A) (struct.new_default $struct.A) (i32.const 1) ) ) (struct.get $struct.A 1 (local.get $ref) ) ) ;; CHECK: (func $local-copies (type $2) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 (type $8) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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) (then (local.set $ref (local.get $ref) ) ) ) (struct.get $struct.A 1 (local.get $ref) ) ) ;; CHECK: (func $block-value (type $2) (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 nullref) ;; 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: (ref.null none) ;; 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 none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $block-value (result f64) (local $ref (ref null $struct.A)) (local.set $ref (struct.new_default $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 (type $8) (param $x i32) (result f64) ;; CHECK-NEXT: (local $ref (ref null $struct.A)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; 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 $struct.A) ) (if (local.get $x) (then (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 (type $5) (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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) ) ;; CHECK: (func $tee-set (type $1) ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; 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 $struct.recursive) ) (ref.null $struct.recursive) ) ) ;; CHECK: (func $set-value (type $9) (param $struct.recursive (ref null $struct.recursive)) ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (struct.set $struct.recursive 0 ;; CHECK-NEXT: (local.get $struct.recursive) ;; CHECK-NEXT: (local.tee $ref ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $set-value (param $struct.recursive (ref null $struct.recursive)) (local $ref (ref null $struct.recursive)) (struct.set $struct.recursive 0 (local.get $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 $struct.recursive) ) ) ) ;; CHECK: (func $initialize-with-reference (type $1) ;; 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 nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $struct.recursive)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $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 $struct.recursive) ) ) (drop (struct.get $struct.recursive 0 (local.get $0) ) ) ) ;; CHECK: (func $escape-flow-out (type $6) (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 $struct.A) ;; 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 $struct.A) ) (i32.const 1) ) ;; The allocation escapes out to the caller by flowing out. (local.get $ref) ) ;; CHECK: (func $escape-return (type $6) (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 $struct.A) ;; 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 $struct.A) ) (i32.const 1) ) ;; The allocation escapes out to the caller by a return. (return (local.get $ref) ) ) ;; CHECK: (func $non-nullable (type $10) (param $a (ref $struct.A)) ;; CHECK-NEXT: (local $1 (ref $struct.A)) ;; CHECK-NEXT: (local $2 (ref $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; 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 $struct.nonnullable (local.get $a) ) ) ) ) ;; CHECK: (func $before-loop-use-multi (type $11) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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: (then ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; 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 $struct.A (i32.const 2) (f64.const 2.1828) ) ) (drop (struct.get $struct.A 0 (local.get $ref) ) ) (if (local.get $x) (then (drop (struct.get $struct.A 1 (local.get $ref) ) ) ) (else (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) (then (drop (struct.get $struct.A 0 (local.get $ref) ) ) ) (else (drop (struct.get $struct.A 1 (local.get $ref) ) ) ) ) (br $outer) ) ) ;; CHECK: (func $multi-separate (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 nullref) ;; 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: (ref.null none) ;; 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 $struct.A) ) ) (drop (struct.get $struct.A 0 (struct.new_default $struct.A) ) ) (drop (struct.get $struct.A 1 (struct.new_default $struct.A) ) ) ) ;; CHECK: (func $multi-separate-same-local-index (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $struct.A) ) (drop (struct.get $struct.A 0 (local.get $ref) ) ) (local.set $ref (struct.new_default $struct.A) ) (drop (struct.get $struct.A 0 (local.get $ref) ) ) ) ;; CHECK: (func $multi-separate-different-local-index-overlapping-lifetimes (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 $struct.A) ) (local.set $ref2 (struct.new_default $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 (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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 none) ;; 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 $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 (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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 none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $branch-to-block (result f64) (local $0 (ref null $struct.A)) (local.set $0 (struct.new_default $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 $branch-to-block-no-fallthrough (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (f64.const 2.1828) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $branch-to-block-no-fallthrough (result f64) (local $0 (ref null $struct.A)) (local.set $0 (struct.new_default $struct.A) ) (struct.get $struct.A 1 (block $block (result (ref null $struct.A)) (drop ;; A branch to the block of our allocation. In this case there is no ;; other value reaching the block, and so our branch is the sole value ;; which means there is no mixing, and we can optimize this. (br_if $block (local.get $0) (i32.const 0) ) ) (return (f64.const 2.1828)) ) ) ) ;; CHECK: (func $two-branches (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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: (drop ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (f64.const 2.1828) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $two-branches (result f64) (local $0 (ref null $struct.A)) (local.set $0 (struct.new_default $struct.A) ) (struct.get $struct.A 1 (block $block (result (ref null $struct.A)) (drop ;; A branch to the block of our allocation. (br_if $block (local.get $0) (i32.const 0) ) ) (drop ;; Another branch, causing mixing that prevents optimizations. (br_if $block (ref.null $struct.A) (i32.const 0) ) ) (return (f64.const 2.1828)) ) ) ) ;; CHECK: (func $two-branches-b (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; 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: (drop ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (f64.const 2.1828) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $two-branches-b (result f64) (local $0 (ref null $struct.A)) (local.set $0 (struct.new_default $struct.A) ) (struct.get $struct.A 1 (block $block (result (ref null $struct.A)) (drop (br_if $block (local.get $0) (i32.const 0) ) ) (drop ;; As in $two-branches, but the value here is our allocation, the same ;; as in the first branch above us. We do not yet optimize such merges ;; of our allocation, but we could in the future. (br_if $block (local.get $0) (i32.const 0) ) ) (return (f64.const 2.1828)) ) ) ) ;; CHECK: (func $br_if_flow (type $2) (result f64) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $struct.A 1 ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (call $send-ref ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (f64.const 2.1828) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $br_if_flow (result f64) (local $0 (ref null $struct.A)) (local.set $0 (struct.new_default $struct.A) ) (struct.get $struct.A 1 (block $block (result (ref null $struct.A)) ;; If it were not for the call here then we would be able to optimize ;; the allocation in this function. (The branch with the allocation is ;; ok, but the br_if also flows the value into a call, that escapes it.) (call $send-ref (br_if $block (local.get $0) (i32.const 0) ) ) (return (f64.const 2.1828)) ) ) ) ;; CHECK: (func $ref-as-non-null (type $1) ;; 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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; 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 none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-as-non-null (local $ref (ref null $struct.A)) (local.set $ref (struct.new_default $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) ) ) ) ;; CHECK: (func $ref-as-non-null-through-local (type $5) (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 nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-as-non-null-through-local (result i32) (local $ref (ref null $struct.A)) (local.set $ref (struct.new_default $struct.A) ) ;; Copy the allocation through a ref.as_non_null. This must not trap: it may ;; trap if we leave the ref.as_non_null there and also we do not assign ;; anything to the local (if we skip assignments to the local when we ;; optimize). To avoid that, we should remove the ref.as_non_null, which is ;; safe since we know our allocation is passed into it, which is not null, ;; and will not trap. (local.set $ref (ref.as_non_null (local.get $ref) ) ) (struct.get $struct.A 0 (local.get $ref) ) ) ;; CHECK: (func $br_if-allocation (type $2) (result f64) ;; CHECK-NEXT: (local $0 (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 $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (f64.const 13.37) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (f64.const 2.1828) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) (func $br_if-allocation (result f64) (local $0 (ref null $struct.A)) (struct.get $struct.A 1 (block $block (result (ref null $struct.A)) (drop ;; Our allocation flows into a br_if, which therefore has non-nullable ;; type, which we must update after optimizing. (br_if $block (struct.new $struct.A (i32.const 42) (f64.const 13.37) ) (i32.const 0) ) ) (return (f64.const 2.1828)) ) ) ) ;; CHECK: (func $pass-through-loop (type $1) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop $loop (result (ref null $struct.A)) ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $pass-through-loop (local $0 (ref null $struct.A)) ;; The allocation pass through a loop, which should change type to be ;; nullable. (drop (loop $loop (result (ref $struct.A)) ;; Include a branch to the loop, so that the testcase does not become ;; trivial (remove-unused-names will turn a loop with no name into a ;; block). (br_if $loop (i32.const 0)) ;; The allocation that will be turned into locals. (struct.new_default $struct.A) ) ) ) ;; CHECK: (func $non-nullable-local (type $6) (result anyref) ;; CHECK-NEXT: (local $0 (ref null $struct.A)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $non-nullable-local (result anyref) (local $0 (ref $struct.A)) ;; The local.get here is in unreachable code, which means we won't do ;; anything to it. But when we remove the local.set during optimization (we ;; can replace it with new locals for the fields of $struct.A), we must make ;; sure that validation still passes, that is, since the local.get is ;; around we must have a local.set for it, or it must become nullable (which ;; is what the fixup will do). (local.set $0 (struct.new_default $struct.A) ) (unreachable) (local.get $0) ) ;; CHECK: (func $to-param (type $4) (param $ref (ref null $struct.A)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct.A 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $to-param (param $ref (ref null $struct.A)) ;; Get a value from a param that was passed in. This must remain as it is. (drop (struct.get $struct.A 0 (local.get $ref) ) ) ;; This new allocation can be moved to locals, and we can read from one of ;; those locals in the later struct.get (but not the one before us!). (local.set $ref (struct.new_default $struct.A) ) (drop (struct.get $struct.A 0 (local.get $ref) ) ) ) ;; CHECK: (func $to-param-loop (type $4) (param $ref (ref null $struct.A)) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct.A 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct.A 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $loop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $to-param-loop (param $ref (ref null $struct.A)) ;; As above, but the body is in a loop. Here we cannot optimize at all, as ;; the first struct.get might read from the value sent from the caller or ;; from the allocation in this function. (loop $loop (drop (struct.get $struct.A 0 (local.get $ref) ) ) (local.set $ref (struct.new_default $struct.A) ) (drop (struct.get $struct.A 0 (local.get $ref) ) ) (br $loop) ) ) ;; CHECK: (func $ref-cast (type $5) (result i32) ;; 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 nullref) ;; 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: (local.set $0 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) (func $ref-cast (result i32) (struct.get $struct.A 0 (ref.cast (ref $struct.A) (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) ) ) (module ;; CHECK: (type $A (sub (struct (field (ref null $A))))) (type $A (sub (struct (field (ref null $A))))) ;; CHECK: (type $B (sub $A (struct (field (ref $A))))) (type $B (sub $A (struct (field (ref $A))))) ;; CHECK: (func $func (type $1) (result anyref) ;; CHECK-NEXT: (local $a (ref $A)) ;; CHECK-NEXT: (local $1 (ref $A)) ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (result anyref) (local $a (ref $A)) ;; Refinalization will be needed here, as a struct.new of $B can be ;; optimized, and that reference flows through a tee, which loses type ;; precision, into a local.get of $A. After heap2local we'll end up using a ;; local of the type of $B's field which is more precise than $A's, and the ;; cast must be updated to be non-nullable. (ref.cast (ref null $B) (struct.get $A 0 (local.tee $a (struct.new $B (struct.new $A (ref.null none) ) ) ) ) ) ) ;; CHECK: (func $cast-success (type $1) (result anyref) ;; CHECK-NEXT: (local $0 (ref $A)) ;; CHECK-NEXT: (local $1 (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) (func $cast-success (result anyref) (struct.get $A 0 (ref.cast (ref $A) (struct.new $B (struct.new $A (ref.null none) ) ) ) ) ) ;; CHECK: (func $cast-failure (type $1) (result anyref) ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cast-failure (result anyref) (struct.get $B 0 ;; The allocated $A arrives here, but the cast will fail, ;; so we do not optimize. (ref.cast (ref $B) (struct.new $A (struct.new $A (ref.null none) ) ) ) ) ) ) (module (type $A (sub (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; We can replace the allocation with a local for the i32. While doing so we ;; must be careful to still validate, and so when we remove the cast we must ;; also ensure the blocks around it have types that still validate (using ;; refinalize, which will make them all nullref, since the unused value ;; flowing through them will be replaced with a null). (drop (block (result (ref $B)) (ref.cast (ref $B) (block (result (ref $A)) (struct.new $B (i32.const 0) ) ) ) ) ) ) ) (module ;; CHECK: (type $struct (struct (field (mut anyref)))) (type $struct (struct (field (mut anyref)))) ;; CHECK: (func $multiple-interactions (type $1) ;; CHECK-NEXT: (local $temp (ref $struct)) ;; CHECK-NEXT: (local $1 anyref) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $multiple-interactions (local $temp (ref $struct)) (local.set $temp (struct.new_default $struct) ) ;; This expression interacts with its children in two different ways: the ;; reference does not escape from the function, so we can optimize it into ;; locals, while the value read from the local is written to the heap, so it ;; does escape. However, we can optimize it after we optimize the first ;; allocation away, which would happen if we ran another pass of heap2local ;; (but we do not here). (struct.set $struct 0 (struct.new_default $struct) (local.get $temp) ) ) ;; CHECK: (func $multiple-interactions-both-locals (type $1) ;; CHECK-NEXT: (local $temp (ref $struct)) ;; CHECK-NEXT: (local $temp2 (ref $struct)) ;; CHECK-NEXT: (local $2 anyref) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $multiple-interactions-both-locals (local $temp (ref $struct)) (local $temp2 (ref $struct)) (local.set $temp (struct.new_default $struct) ) ;; Now both allocations are written to locals. We can still optimize the ;; second. (local.set $temp2 (struct.new_default $struct) ) (struct.set $struct 0 (local.get $temp2) (local.get $temp) ) ) ;; CHECK: (func $multiple-interactions-escapes (type $2) (result anyref) ;; CHECK-NEXT: (local $temp (ref $struct)) ;; CHECK-NEXT: (local $temp2 (ref $struct)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $temp2 ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (local.get $temp2) ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $temp2) ;; CHECK-NEXT: ) (func $multiple-interactions-escapes (result anyref) (local $temp (ref $struct)) (local $temp2 (ref $struct)) (local.set $temp (struct.new_default $struct) ) (local.set $temp2 (struct.new_default $struct) ) (struct.set $struct 0 (local.get $temp2) (local.get $temp) ) ;; As above, but now the second allocation escapes, so nothing is ;; optimized. (local.get $temp2) ) )