;; NOTE: Assertions have been generated by update_lit_checks.py --all-items 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 (sub (struct (field (mut i32)) (field (mut f64))))) (type $struct.A (sub (struct (field (mut i32)) (field (mut f64))))) (type $struct.B (sub $struct.A (struct (field (mut i32)) (field (mut f64))))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $2 (func (result f64))) ;; CHECK: (type $struct.recursive (struct (field (mut (ref null $struct.recursive))))) ;; CHECK: (type $4 (func (result i32))) ;; CHECK: (type $5 (func (param (ref null $struct.A)))) ;; CHECK: (type $6 (func (result anyref))) ;; CHECK: (type $7 (func (param i32) (result f64))) ;; CHECK: (type $struct.packed (struct (field (mut i8)) (field (mut i32)))) (type $struct.packed (struct (field (mut i8)) (field (mut i32)))) (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: (type $9 (func (param (ref null $struct.recursive)))) ;; CHECK: (type $10 (func (param (ref $struct.A)))) ;; CHECK: (type $11 (func (param i32))) ;; CHECK: (type $12 (func (param eqref) (result i32))) ;; CHECK: (type $13 (func (param eqref eqref) (result i32))) ;; CHECK: (type $14 (func (param anyref) (result i32))) ;; CHECK: (type $15 (func (param anyref))) ;; 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: (local $temp (ref $struct.packed)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 1338) ;; 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: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 99998) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (i32.const 255) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.shr_s ;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: (i32.const 24) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 24) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 99999) ;; 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 nullref) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $packed (local $temp (ref $struct.packed)) (local.set $temp (struct.new $struct.packed (i32.const 1337) (i32.const 1338) ) ) (struct.set $struct.packed 0 (local.get $temp) (i32.const 99998) ) ;; Packed fields require masking of irrelevant bits on unsigned gets and ;; sign-extending on signed ones. (drop (struct.get $struct.packed 0 (local.get $temp) ) ) (drop (struct.get_s $struct.packed 0 (local.get $temp) ) ) ;; Unpacked fields in the same struct do not need anything. (struct.set $struct.packed 1 (local.get $temp) (i32.const 99999) ) (drop (struct.get $struct.packed 1 (local.get $temp) ) ) ;; Check we do not add any masking in new_default either. (local.set $temp (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 $5) (param $0 (ref null $struct.A)) ;; 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 $7) (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 $7) (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 $4) (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 $4) (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 $5) (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 $5) (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 $4) (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) ) ) ) ) ;; CHECK: (func $ref-eq (type $4) (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: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $ref-eq (result i32) ;; Comparing an allocation to something else results in 0, and we can ;; optimize away the allocation. (ref.eq (struct.new $struct.A (i32.const 0) (f64.const 0) ) (ref.null eq) ) ) ;; CHECK: (func $ref-eq-flip (type $12) (param $other eqref) (result i32) ;; 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: (local.get $other) ;; 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: (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: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $ref-eq-flip (param $other eqref) (result i32) ;; As above, but flipped, and compared to a local. (ref.eq (local.get $other) (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) ;; CHECK: (func $ref-eq-self (type $4) (result i32) ;; CHECK-NEXT: (local $eq eqref) ;; 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 $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (f64.const 0) ;; 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: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) (func $ref-eq-self (result i32) (local $eq eqref) ;; Comparing to oneself results in 1, and we can optimize away the ;; allocation. (ref.eq (local.tee $eq (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) (local.get $eq) ) ) ;; CHECK: (func $ref-eq-unreachable (type $4) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; 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: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-eq-unreachable (result i32) ;; When a child is unreachable, the result does not matter (but we should ;; still emit validating code). (ref.eq (struct.new $struct.A (i32.const 0) (f64.const 0) ) (unreachable) ) ) ;; CHECK: (func $ref-eq-unreachable-flipped (type $4) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) ;; 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: ) (func $ref-eq-unreachable-flipped (result i32) ;; As above, but with children flipped. (ref.eq (unreachable) (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) ;; CHECK: (func $ref-eq-unrelated (type $13) (param $x eqref) (param $y eqref) (result i32) ;; 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 $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (f64.const 0) ;; 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: (ref.eq ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-eq-unrelated (param $x eqref) (param $y eqref) (result i32) ;; We know nothing about either ref.eq arm, and do nothing, despite ;; another allocation in the function (which ensures we enter the ;; optimization part of the pass; that other allocation can be removed). (drop (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) (ref.eq (local.get $x) (local.get $y) ) ) ;; CHECK: (func $ref-eq-two (type $4) (result i32) ;; 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: (local $6 i32) ;; CHECK-NEXT: (local $7 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: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (f64.const 2.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $ref-eq-two (result i32) ;; Two separate allocations. We can optimize them away, and the result is 0. (ref.eq (struct.new $struct.A (i32.const 0) (f64.const 0) ) (struct.new $struct.A (i32.const 1) (f64.const 2.2) ) ) ) ;; CHECK: (func $ref-is (type $14) (param $x anyref) (result i32) ;; 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: (ref.is_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null ;; 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: (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: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-is (param $x anyref) (result i32) ;; A ref.is that we can do nothing for, and should not modify, even though ;; we optimize later. (drop (ref.is_null (local.get $x) ) ) ;; The result is 0 as the allocation is not null, and we can remove the ;; allocation. (ref.is_null (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) ;; CHECK: (func $ref-test (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: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (local $8 i32) ;; CHECK-NEXT: (local $9 f64) ;; CHECK-NEXT: (local $10 i32) ;; CHECK-NEXT: (local $11 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 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: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (f64.const 2.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $11 ;; CHECK-NEXT: (f64.const 4.4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-test ;; This cast must succeed (it tests the exact type), and we can remove the ;; allocation. (drop (ref.test (ref $struct.A) (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) ;; Testing a supertype also works. (drop (ref.test (ref null $struct.A) (struct.new $struct.A (i32.const 1) (f64.const 2.2) ) ) ) (drop (ref.test (ref null any) (struct.new $struct.A (i32.const 3) (f64.const 4.4) ) ) ) ) ;; CHECK: (func $ref-test-bad (type $15) (param $x anyref) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 f64) ;; CHECK-NEXT: (local $7 i32) ;; CHECK-NEXT: (local $8 f64) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $struct.A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; 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: (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: ) ;; CHECK-NEXT: (i32.const 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 $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (f64.const 2.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (local.get $8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-test-bad (param $x anyref) ;; We know nothing about this cast. (drop (ref.test (ref $struct.A) (local.get $x) ) ) ;; These casts must fail, but we can remove the allocation. (drop (ref.test (ref $struct.B) (struct.new $struct.A (i32.const 0) (f64.const 0) ) ) ) (drop (ref.test (ref null $struct.B) (struct.new $struct.A (i32.const 1) (f64.const 2.2) ) ) ) ) ) (module ;; CHECK: (type $A (sub (struct (field (ref null $A))))) (type $A (sub (struct (field (ref null $A))))) ;; CHECK: (type $1 (func (result anyref))) ;; CHECK: (type $B (sub $A (struct (field (ref $A)) (field i32)))) (type $B (sub $A (struct (field (ref $A)) (field i32)))) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (func $func (type $1) (result anyref) ;; CHECK-NEXT: (local $a (ref $A)) ;; CHECK-NEXT: (local $1 (ref $A)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 (ref $A)) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 1) ;; 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: ) ;; 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) ) (i32.const 1) ) ) ) ) ) ;; CHECK: (func $cast-success (type $1) (result anyref) ;; CHECK-NEXT: (local $0 (ref $A)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; 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 $3 ;; CHECK-NEXT: (i32.const 1) ;; 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 $cast-success (result anyref) (struct.get $A 0 (ref.cast (ref $A) (struct.new $B (struct.new $A (ref.null none) ) (i32.const 1) ) ) ) ) ;; CHECK: (func $cast-failure (type $1) (result anyref) ;; CHECK-NEXT: (local $0 (ref null $A)) ;; CHECK-NEXT: (local $1 (ref null $A)) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; 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: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cast-failure (result anyref) (struct.get $B 0 ;; The allocated $A arrives here, but the cast will fail. We can remove ;; the allocation and put an unreachable here. (Note that the inner ;; struct.new survives, which would take another cycle to remove.) (ref.cast (ref $B) (struct.new $A (struct.new $A (ref.null none) ) ) ) ) ) ;; CHECK: (func $cast-failure-nofield (type $3) (result i32) ;; CHECK-NEXT: (local $0 (ref null $A)) ;; CHECK-NEXT: (local $1 (ref null $A)) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; 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: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cast-failure-nofield (result i32) ;; As above, but we read from a field that only exists in $B, despite the ;; allocation that flows here being an $A. We should not error on that. (struct.get $B 1 ;; this changes from 0 to 1 (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: (type $0 (func)) ;; 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: (type $1 (func)) ;; CHECK: (type $2 (func (result 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) ) ) ;; Arrays. (module ;; CHECK: (type $array (array (mut i32))) (type $array (array (mut i32))) ;; CHECK: (type $1 (struct (field (mut i32)))) ;; CHECK: (type $2 (func)) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (type $4 (func (param i32) (result i32))) ;; CHECK: (type $5 (struct (field (mut i32)) (field (mut i32)))) ;; CHECK: (type $6 (func (param i32))) ;; CHECK: (type $7 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)))) ;; CHECK: (type $8 (struct)) ;; CHECK: (type $9 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)) (field (mut i32)))) ;; CHECK: (type $10 (func (param anyref))) ;; CHECK: (func $array.new_default (type $2) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; 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: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.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 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 30) ;; 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 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 $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 40) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.new_default (local $temp (ref $array)) ;; This fixed-size array can be replaced with locals. (local.set $temp (array.new_default $array (i32.const 3) ) ) (array.set $array (local.get $temp) (i32.const 0) (i32.const 10) ) (array.set $array (local.get $temp) (i32.const 1) (i32.const 20) ) (array.set $array (local.get $temp) (i32.const 2) (i32.const 30) ) (drop (array.get $array (local.get $temp) (i32.const 0) ) ) (drop (array.get $array (local.get $temp) (i32.const 1) ) ) (drop (array.get $array (local.get $temp) (i32.const 2) ) ) ;; OOB operations trap at runtime. (array.set $array (local.get $temp) (i32.const 3) (i32.const 40) ) (drop (array.get $array (local.get $temp) (i32.const 3) ) ) ) ;; CHECK: (func $array.new (type $3) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $5)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $1) ;; 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: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.new (result i32) (local $temp (ref $array)) ;; This is also optimizable. (local.set $temp (array.new $array (i32.const 1337) (i32.const 2) ) ) (array.get $array (local.get $temp) (i32.const 1) ) ) ;; CHECK: (func $array.new_fixed (type $4) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 1337) ;; 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: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.new_fixed (param $x i32) (result i32) (local $temp (ref $array)) ;; This is also optimizable. (local.set $temp (array.new_fixed $array 2 (call $get-i32) ;; test side effects in a value (i32.const 1337) ) ) (array.get $array (local.get $temp) (i32.const 0) ) ) ;; CHECK: (func $array.nonconstant_size (type $4) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.get $array ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.nonconstant_size (param $x i32) (result i32) (local $temp (ref $array)) (local.set $temp (array.new $array (i32.const 42) ;; We cannot optimize a nonconstant size. (local.get $x) ) ) (array.get $array (local.get $temp) (i32.const 0) ) ) ;; CHECK: (func $array.nonconstant_get (type $4) (param $x i32) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.get $array ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.nonconstant_get (param $x i32) (result i32) (local $temp (ref $array)) (local.set $temp (array.new $array (i32.const 42) (i32.const 3) ) ) (array.get $array (local.get $temp) ;; We cannot optimize a nonconstant get. (local.get $x) ) ) ;; CHECK: (func $array.nonconstant_set (type $6) (param $x i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $array ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.nonconstant_set (param $x i32) (local $temp (ref $array)) (local.set $temp (array.new $array (i32.const 42) (i32.const 3) ) ) (array.set $array (local.get $temp) ;; We cannot optimize a nonconstant set. (local.get $x) (i32.const 100) ) ) ;; CHECK: (func $array.local.super (type $2) ;; CHECK-NEXT: (local $temp anyref) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.local.super ;; This local is a supertype, and the allocation flows through a cast, all ;; of which we handle. (local $temp (ref null any)) (local.set $temp (array.new $array (i32.const 42) (i32.const 1) ) ) (array.set $array (ref.cast (ref $array) (local.get $temp) ) (i32.const 0) (i32.const 100) ) ) ;; CHECK: (func $array.folded (type $3) (result i32) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 i32) ;; CHECK-NEXT: (local $8 i32) ;; CHECK-NEXT: (local $9 i32) ;; CHECK-NEXT: (local $10 i32) ;; CHECK-NEXT: (local $11 i32) ;; CHECK-NEXT: (local $12 i32) ;; CHECK-NEXT: (local $13 i32) ;; CHECK-NEXT: (local $14 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $7)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $11 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $12 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $13 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $14 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $8) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $9) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $12) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (local.get $13) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (local.get $14) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) (func $array.folded (result i32) ;; Test a form without local.get/set operations. (array.get $array (array.new $array (call $get-i32) (i32.const 7) ) (i32.const 6) ) ) ;; CHECK: (func $array.folded.multiple (type $2) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 i32) ;; CHECK-NEXT: (local $8 i32) ;; CHECK-NEXT: (local $9 i32) ;; CHECK-NEXT: (local $10 i32) ;; CHECK-NEXT: (local $11 i32) ;; CHECK-NEXT: (local $12 i32) ;; CHECK-NEXT: (local $13 i32) ;; CHECK-NEXT: (local $14 i32) ;; CHECK-NEXT: (local $15 i32) ;; CHECK-NEXT: (local $16 i32) ;; CHECK-NEXT: (local $17 i32) ;; CHECK-NEXT: (local $18 i32) ;; CHECK-NEXT: (local $19 i32) ;; CHECK-NEXT: (local $20 i32) ;; CHECK-NEXT: (local $21 i32) ;; CHECK-NEXT: (local $22 i32) ;; CHECK-NEXT: (local $23 i32) ;; CHECK-NEXT: (local $24 i32) ;; CHECK-NEXT: (local $25 i32) ;; CHECK-NEXT: (local $26 i32) ;; CHECK-NEXT: (local $27 i32) ;; CHECK-NEXT: (local $28 i32) ;; CHECK-NEXT: (local $29 i32) ;; CHECK-NEXT: (local $30 i32) ;; CHECK-NEXT: (local $31 i32) ;; CHECK-NEXT: (local $32 i32) ;; CHECK-NEXT: (local $33 i32) ;; CHECK-NEXT: (local $34 i32) ;; CHECK-NEXT: (local $35 i32) ;; CHECK-NEXT: (local $36 i32) ;; CHECK-NEXT: (local $37 i32) ;; CHECK-NEXT: (local $38 i32) ;; CHECK-NEXT: (local $39 i32) ;; CHECK-NEXT: (local $40 i32) ;; CHECK-NEXT: (local $41 i32) ;; CHECK-NEXT: (local $42 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $8)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $9)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $25 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $26 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $27 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $28 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $29 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $30 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $31 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $32 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $33 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $34 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $35 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $36 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $37 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $38 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $39 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $40 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $41 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $42 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $24) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (local.get $25) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (local.get $26) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $27) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $9 ;; CHECK-NEXT: (local.get $28) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (local.get $29) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $11 ;; CHECK-NEXT: (local.get $30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $12 ;; CHECK-NEXT: (local.get $31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $13 ;; CHECK-NEXT: (local.get $32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $14 ;; CHECK-NEXT: (local.get $33) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $15 ;; CHECK-NEXT: (local.get $34) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $16 ;; CHECK-NEXT: (local.get $35) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $17 ;; CHECK-NEXT: (local.get $36) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $18 ;; CHECK-NEXT: (local.get $37) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $19 ;; CHECK-NEXT: (local.get $38) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $20 ;; CHECK-NEXT: (local.get $39) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $21 ;; CHECK-NEXT: (local.get $40) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $22 ;; CHECK-NEXT: (local.get $41) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $23 ;; CHECK-NEXT: (local.get $42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $11) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $array ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.folded.multiple ;; We allow sizes 0-19, so these will be optimized. (drop (array.new $array (call $get-i32) (i32.const 0) ) ) (drop (array.get $array (array.new $array (call $get-i32) (i32.const 1) ) (i32.const 0) ) ) (drop (array.get $array (array.new $array (call $get-i32) (i32.const 19) ) (i32.const 6) ) ) ;; 20 is too much, however. (drop (array.get $array (array.new $array (call $get-i32) (i32.const 20) ) (i32.const 6) ) ) ) ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $array.nested.refinalize.get (result i32) ;; The get here is of an index that is out of bounds, and will trap. We ;; should refinalize so the unreachability is propagated and we do not ;; error on validation. (array.get $array (array.new_default $array (i32.const 0) ) (i32.const 0) ) ) ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $array.nested.refinalize.set ;; As above, but with a set. (array.set $array (array.new_default $array (i32.const 0) ) (i32.const 0) (i32.const 42) ) ) ;; CHECK: (func $array.flowing.type (type $3) (result i32) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.flowing.type (result i32) (local $temp (ref $array)) ;; The array reference here flows through some places that need to be ;; updated when we optimize. In particular the blocks' types will change. (local.set $temp (array.new $array (i32.const 1) (i32.const 1) ) ) (drop (block $nullable (result (ref null $array)) (local.get $temp) ) ) (drop (block $non-nullable (result (ref $array)) (local.get $temp) ) ) ;; Test supertypes too. (drop (block $non-nullable (result (ref array)) (local.get $temp) ) ) (drop (block $non-nullable (result (ref null array)) (local.get $temp) ) ) ;; Read from the array as well. (array.get $array (local.get $temp) (i32.const 0) ) ) ;; CHECK: (func $get-i32 (type $3) (result i32) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) (func $get-i32 (result i32) ;; Helper for the above. (i32.const 1337) ) ;; CHECK: (func $ref-test (type $2) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; 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: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; 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: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-test ;; This cast must succeed (it tests the exact type), and we can remove the ;; allocation. (drop (ref.test (ref $array) (array.new_default $array (i32.const 1) ) ) ) ;; Testing a supertype also works. (drop (ref.test (ref null any) (array.new_default $array (i32.const 2) ) ) ) ) ;; CHECK: (func $ref-test-bad (type $10) (param $x anyref) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $array) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; 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: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-test-bad (param $x anyref) ;; We know nothing about this cast. (drop (ref.test (ref $array) (local.get $x) ) ) ;; This cast fails, but we can remove the allocation. (drop (ref.test (ref struct) (array.new_default $array (i32.const 2) ) ) ) ) ) ;; Arrays with reference values. (module ;; CHECK: (type $array (array (ref null $array))) (type $array (array (ref null $array))) ;; CHECK: (type $1 (struct (field (ref null $array)))) ;; CHECK: (type $2 (func)) ;; CHECK: (type $3 (struct (field (ref null $array)) (field (ref null $array)) (field (ref null $array)))) ;; CHECK: (type $4 (func (result anyref))) ;; CHECK: (func $nested (type $2) ;; CHECK-NEXT: (local $0 (ref null $array)) ;; CHECK-NEXT: (local $1 (ref null $array)) ;; CHECK-NEXT: (local $2 (ref null $array)) ;; CHECK-NEXT: (local $3 (ref null $array)) ;; CHECK-NEXT: (local $4 (ref null $array)) ;; CHECK-NEXT: (local $5 (ref null $array)) ;; CHECK-NEXT: (local $6 (ref null $array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $3)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (array.new $array ;; CHECK-NEXT: (array.new_default $array ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $nested ;; Nested array.new operations, all of whom can be optimized away in ;; principle. We do only a single iteration here for now, which optimizes ;; away the outermost array.new (see TODO in the pass about iterations). (drop (array.new $array (array.new $array (array.new_default $array (i32.const 1) ) (i32.const 2) ) (i32.const 3) ) ) ) ;; CHECK: (func $nested-unreachable (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable ArrayNew we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $nested-unreachable ;; The array.get in the middle is out of bounds, and will cause the outer ;; array.new to become unreachable. (drop (array.new $array (array.get $array (array.new_default $array (i32.const 0) ) (i32.const 0) ) (i32.const 0) ) ) ) ;; CHECK: (func $array.flowing.type (type $4) (result anyref) ;; CHECK-NEXT: (local $temp (ref $array)) ;; CHECK-NEXT: (local $1 (ref null $array)) ;; CHECK-NEXT: (local $2 (ref null $array)) ;; CHECK-NEXT: (local $3 (ref null $array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $1)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result (ref null $array)) ;; CHECK-NEXT: (block (result (ref null $array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.flowing.type (result anyref) (local $temp (ref $array)) ;; This is similar to $array.flowing.type from above, but now the array's ;; values are references. (local.set $temp (array.new $array (ref.null $array) (i32.const 1) ) ) (drop (block $nullable (result (ref null $array)) (local.get $temp) ) ) (drop (block $non-nullable (result (ref $array)) (local.get $temp) ) ) ;; Test supertypes too. (drop (block $non-nullable (result (ref array)) (local.get $temp) ) ) (drop (block $non-nullable (result (ref null array)) (local.get $temp) ) ) ;; This block's type should end up valid. In particular this test checks ;; that we do not get confused by the array's type, which we rewrite to the ;; struct type in Array2Struct - this array.get's type is the array type, ;; but only because the value we read is the array type, and not because the ;; allocation reaches here. (block (result anyref) (array.get $array (local.get $temp) (i32.const 0) ) ) ) ) ;; Packed arrays. (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $array8 (array (mut i8))) (type $array8 (array (mut i8))) ;; CHECK: (type $2 (func (result i32))) ;; CHECK: (type $array16 (array (mut i16))) (type $array16 (array (mut i16))) ;; CHECK: (func $array8 (type $0) ;; CHECK-NEXT: (local $temp (ref $array8)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; 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: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.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 $2 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: (i32.const 255) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.shr_s ;; CHECK-NEXT: (i32.shl ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: (i32.const 24) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 24) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array8 (local $temp (ref $array8)) (local.set $temp (array.new_default $array8 (i32.const 3) ) ) (array.set $array8 (local.get $temp) (i32.const 1) (i32.const 1337) ) ;; As with structs, we truncate/sign-extend gets of packed fields. (drop (array.get $array8 (local.get $temp) (i32.const 1) ) ) (drop (array.get_s $array8 (local.get $temp) (i32.const 1) ) ) ) ;; CHECK: (func $array16 (type $2) (result i32) ;; CHECK-NEXT: (local $temp (ref $array16)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; 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: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.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 $2 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: (i32.const 65535) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array16 (result i32) (local $temp (ref $array16)) (local.set $temp (array.new_default $array16 (i32.const 3) ) ) (array.set $array16 (local.get $temp) (i32.const 1) (i32.const 1337) ) (array.get $array16 (local.get $temp) (i32.const 1) ) ) ) ;; Array casts to structs and arrays. (module ;; CHECK: (type $1 (func)) ;; CHECK: (type $0 (func (result (ref struct)))) (type $0 (func (result (ref struct)))) ;; CHECK: (type $3 (func (result structref))) ;; CHECK: (type $4 (func (result (ref array)))) ;; CHECK: (type $array (array i8)) (type $array (array i8)) ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $array.cast.struct (result (ref struct)) (local $eq (ref eq)) ;; This cast will fail: we cast an array to struct. That we go through ;; (ref eq) in the middle, which seems like it could cast to struct, should ;; not confuse us. And, as the cast fails, the reference does not escape. ;; We can optimize here and will emit an unreachable for the failing cast. (ref.cast (ref struct) (local.tee $eq (array.new_fixed $array 0) ) ) ) ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $array.cast.struct.null (result (ref null struct)) (local $eq (ref eq)) ;; As above but the cast is to a nullable type, which changes nothing. (ref.cast (ref null struct) (local.tee $eq (array.new_fixed $array 0) ) ) ) ;; CHECK: (func $array.cast.array (type $4) (result (ref array)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (ref.cast (ref array) ;; CHECK-NEXT: (local.tee $eq ;; CHECK-NEXT: (array.new_fixed $array 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.cast.array (result (ref array)) (local $eq (ref eq)) ;; Now we cast to array, and the cast succeeds, so we escape, and do ;; nothing to optimize. (ref.cast (ref array) (local.tee $eq (array.new_fixed $array 0) ) ) ) ;; CHECK: (func $array.cast.array.set (type $1) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.cast.array.set (local $eq (ref eq)) (local $array (ref array)) ;; As above, but now we store the result in a local rather than return it ;; out, so it does not escape. (local.set $array (ref.cast (ref array) (local.tee $eq (array.new_fixed $array 0) ) ) ) ) ;; CHECK: (func $array.cast.struct.set (type $1) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $struct (ref struct)) ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array.cast.struct.set (local $eq (ref eq)) (local $struct (ref struct)) ;; As above, but now the cast fails and is stored to a struct local. We do not ;; escape and we emit an unreachable for the cast. (local.set $struct (ref.cast (ref struct) (local.tee $eq (array.new_fixed $array 0) ) ) ) ) ) (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $A (sub (struct (field structref)))) (type $A (sub (struct (field structref)))) ;; CHECK: (func $struct.get-ref.eq (type $0) ;; CHECK-NEXT: (local $x (ref $A)) ;; CHECK-NEXT: (local $1 structref) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; 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.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct.get-ref.eq (local $x (ref $A)) ;; This ref.eq ends up comparing the tee'd allocation with a read of null ;; from the allocation's field, which returns 0. This tests that we can ;; differentiate when the allocation gets somewhere vs when it flows out ;; from that place, as the allocation does reach the struct.get - hence we ;; need to update it, when we optimize - but it does not flow it back out. (if (ref.eq (struct.get $A 0 (local.tee $x (struct.new_default $A) ) ) (local.get $x) ) (then (unreachable) ) ) ) ) ;; Pops require fixups. (module (type $struct (struct (field (mut i32)))) (type $array (array (mut i32))) ;; CHECK: (type $0 (func)) ;; CHECK: (type $1 (func (param i32))) ;; CHECK: (type $2 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) ;; CHECK: (tag $tag (param i32)) (tag $tag (param i32)) ;; CHECK: (func $struct-with-pop (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; 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 $struct-with-pop (try (do (nop) ) (catch $tag (drop ;; We create a block when we replace the struct with locals, which the ;; pop must be moved out of. (struct.new $struct (pop i32) ) ) ) ) ) ;; CHECK: (func $array-with-pop (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $2)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array-with-pop (try (do (nop) ) (catch $tag (drop ;; As above, but with an array (array.new $array (pop i32) (i32.const 3) ) ) ) ) ) )