blob: 01f248524167b31d29d4cdce3e585b9229a393d7 [file] [edit]
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \
;; RUN: | filecheck %s
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $struct (sub (struct)))
(type $struct (sub (struct)))
;; CHECK: (type $struct2 (struct))
(type $struct2 (struct))
;; CHECK: (type $substruct (sub $struct (struct)))
(type $substruct (sub $struct (struct)))
)
;; CHECK: (type $struct-nn (struct (field (ref any))))
(type $struct-nn (struct (field (ref any))))
;; CHECK: (global $struct (ref $struct) (struct.new_default $struct))
(global $struct (ref $struct) (struct.new $struct))
;; CHECK: (func $br_on-if (type $10) (param $0 (ref struct))
;; CHECK-NEXT: (block $label
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref struct))
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on-if (param $0 (ref struct))
(block $label
(drop
;; This br is never taken, as the input is non-nullable, so we can remove
;; it. When we do so, we replace it with the if. We should not rescan that
;; if, which has already been walked, as that would hit an assertion.
;;
(br_on_null $label
;; This if can also be turned into a select, separately from the above
;; (that is not specifically intended to be tested here).
(if (result (ref struct))
(i32.const 0)
(then
(local.get $0)
)
(else
(local.get $0)
)
)
)
)
)
)
;; CHECK: (func $br_on_cast (type $4) (param $0 (ref $struct)) (result (ref $struct))
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (block $block (result (ref $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast $block (ref $struct) (ref $substruct)
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast (param (ref $struct)) (result (ref $struct))
(local $struct (ref null $struct))
(block $block (result (ref $struct))
(drop
;; This cast can be computed at compile time: it will definitely be taken,
;; so we can turn it into a normal br.
(br_on_cast $block anyref (ref $struct)
(struct.new $struct)
)
)
(drop
;; This cast can be partially computed at compile time, but we still need to
;; do a null check.
(br_on_cast $block anyref (ref $struct)
(local.get $struct)
)
)
(drop
;; This cast cannot be optimized at all.
(br_on_cast $block anyref (ref $substruct)
(local.get 0)
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast-fallthrough (type $4) (param $0 (ref $struct)) (result (ref $struct))
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (block $block (result (ref $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (ref.cast (ref (exact $struct))
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (ref.cast (ref null $struct)
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast $block anyref (ref $substruct)
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast-fallthrough (param (ref $struct)) (result (ref $struct))
;; Same as above, but now the type information comes from fallthrough values.
(local $struct (ref null $struct))
(local $any anyref)
(block $block (result (ref $struct))
(drop
;; Definitely taken, but will need a cast for validity.
(br_on_cast $block anyref (ref $struct)
(local.tee $any (struct.new $struct))
)
)
(drop
;; Needs a null check and cast for validity.
(br_on_cast $block anyref (ref $struct)
(local.tee $any (local.get $struct))
)
)
(drop
;; This cannot be optimized, but at least it still doesn't need an
;; additional cast.
(br_on_cast $block anyref (ref $substruct)
(local.tee $any (local.get 0))
)
)
(unreachable)
)
)
;; CHECK: (func $nested_br_on_cast (type $11) (result i31ref)
;; CHECK-NEXT: (block $label$1 (result (ref i31))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $label$1
;; CHECK-NEXT: (ref.i31
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $nested_br_on_cast (result i31ref)
(block $label$1 (result i31ref)
(drop
;; The inner br_on_cast will become a direct br since the type proves it
;; is in fact i31. That then becomes unreachable, and the parent must
;; handle that properly (do nothing without hitting an assertion).
(br_on_cast $label$1 (ref any) (ref i31)
(br_on_cast $label$1 (ref any) (ref i31)
(ref.i31 (i32.const 0))
)
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_unrelated (type $5) (result (ref null $struct))
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result nullref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast $block (ref null $struct2) nullref
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_unrelated (result (ref null $struct))
(local $nullable-struct2 (ref null $struct2))
(block $block (result (ref null $struct))
(drop
;; This cast can be computed at compile time: it will definitely fail, so we
;; can remove it.
(br_on_cast $block anyref (ref $struct)
(struct.new $struct2)
)
)
(drop
;; We can still remove it even if the cast allows nulls.
(br_on_cast $block anyref (ref null $struct)
(struct.new $struct2)
)
)
(drop
;; Or if the cast does not allow nulls and the value is nullable.
(br_on_cast $block anyref (ref $struct)
(local.get $nullable-struct2)
)
)
(drop
;; But if both are nullable, then the cast will succeed only if the value is
;; null, so we can partially optimize.
;; TODO: Optimize this.
(br_on_cast $block anyref (ref null $struct)
(local.get $nullable-struct2)
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $5) (result (ref null $struct))
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result nullref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast $block anyref nullref
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_unrelated-fallthrough (result (ref null $struct))
;; Same as above, but now all the type information comes from fallthrough values.
(local $any anyref)
(local $nullable-struct2 (ref null $struct2))
(block $block (result (ref null $struct))
(drop
;; Definitely not taken.
(br_on_cast $block anyref (ref $struct)
(local.tee $any (struct.new $struct2))
)
)
(drop
;; Still not taken. Note that we start by flowing out a non-nullable value,
;; and will add a cast to ensure we still do after optimization.
(br_on_cast $block anyref (ref null $struct)
(local.tee $any (struct.new $struct2))
)
)
(drop
;; Also not taken.
(br_on_cast $block anyref (ref $struct)
(local.tee $any (local.get $nullable-struct2))
)
)
(drop
;; Taken only if null.
;; TODO: Optimize this.
(br_on_cast $block anyref (ref null $struct)
(local.tee $any (local.get $nullable-struct2))
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_fail (type $6) (param $0 (ref $struct)) (result anyref)
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (block $block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_fail $block (ref null $struct) (ref $struct)
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_fail $block (ref $struct) (ref $substruct)
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_fail (param (ref $struct)) (result anyref)
(local $struct (ref null $struct))
(block $block (result anyref)
(drop
;; This cast can be computed at compile time: it will definitely succeed so
;; the branch will not be taken.
(br_on_cast_fail $block anyref (ref $struct)
(struct.new $struct)
)
)
(drop
;; This cast can be partially computed at compile time, but we still need to
;; do a null check.
;; TODO: optimize this.
(br_on_cast_fail $block anyref (ref $struct)
(local.get $struct)
)
)
(drop
;; This cast cannot be optimized at all.
(br_on_cast_fail $block anyref (ref $substruct)
(local.get 0)
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_fail-fallthrough (type $6) (param $0 (ref $struct)) (result anyref)
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (block $block (result anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref (exact $struct))
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_fail $block anyref (ref $struct)
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_fail $block anyref (ref $substruct)
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_fail-fallthrough (param (ref $struct)) (result anyref)
;; Same as above, but now the type information comes from fallthrough values.
(local $any anyref)
(local $struct (ref null $struct))
(block $block (result anyref)
(drop
;; This cast will succeed. We will need a cast for validity.
(br_on_cast_fail $block anyref (ref $struct)
(local.tee $any (struct.new $struct))
)
)
(drop
;; We will still need a null check.
;; TODO: optimize this.
(br_on_cast_fail $block anyref (ref $struct)
(local.tee $any (local.get $struct))
)
)
(drop
;; This cast cannot be optimized at all.
(br_on_cast_fail $block anyref (ref $substruct)
(local.tee $any (local.get 0))
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_fail_unrelated (type $7) (result anyref)
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result (ref null $struct2))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_fail_unrelated (result anyref)
(local $nullable-struct2 (ref null $struct2))
(block $block (result anyref)
(drop
;; This cast can be computed at compile time: it will definitely fail, so we
;; can replace it with an unconditional br.
(br_on_cast_fail $block anyref (ref $struct)
(struct.new $struct2)
)
)
(drop
;; We can still replace it even if the cast allows nulls.
(br_on_cast_fail $block anyref (ref null $struct)
(struct.new $struct2)
)
)
(drop
;; Or if the cast does not allow nulls and the value is nullable.
(br_on_cast_fail $block anyref (ref $struct)
(local.get $nullable-struct2)
)
)
(drop
;; But if both are nullable, then we can only partially optimize because we
;; still have to do a null check.
(br_on_cast_fail $block anyref (ref null $struct)
(local.get $nullable-struct2)
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough (type $7) (result anyref)
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_fail_unrelated-fallthrough (result anyref)
;; Same as above, but now type information comes from fallthrough values.
(local $any anyref)
(local $nullable-struct2 (ref null $struct2))
(block $block (result anyref)
(drop
;; Will definitely take the branch.
(br_on_cast_fail $block anyref (ref $struct)
(local.tee $any (struct.new $struct2))
)
)
(drop
;; Ditto, but also add a ref.as_non_null, as we must keep sending a non-
;; null value to the block (the block would still validate either way, but
;; we do not want to un-refine the sent value). See the next function for a
;; test with a non-nullable block.
(br_on_cast_fail $block anyref (ref null $struct)
(local.tee $any (struct.new $struct2))
)
)
(drop
;; Ditto.
(br_on_cast_fail $block anyref (ref $struct)
(local.tee $any (local.get $nullable-struct2))
)
)
(drop
;; Still has to do a null check.
(br_on_cast_fail $block anyref (ref null $struct)
(local.tee $any (local.get $nullable-struct2))
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast_fail_unrelated-fallthrough-non-null (type $12) (result (ref any))
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result (ref any))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (struct.new_default $struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (local.tee $any
;; CHECK-NEXT: (local.get $nullable-struct2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast_fail_unrelated-fallthrough-non-null (result (ref any))
;; Same as above, but the block is now non-nullable. Only the branches that
;; work with that are tested.
(local $any anyref)
(local $nullable-struct2 (ref null $struct2))
(block $block (result (ref any)) ;; this changed, and the function's results
(drop
;; Will definitely take the branch.
(br_on_cast_fail $block anyref (ref null $struct)
(local.tee $any (struct.new $struct2))
)
)
(drop
;; Still has to do a null check.
(br_on_cast_fail $block anyref (ref null $struct)
(local.tee $any (local.get $nullable-struct2))
)
)
(unreachable)
)
)
;; CHECK: (func $br_on_cast-unreachable (type $8) (param $i31ref i31ref) (result anyref)
;; CHECK-NEXT: (block $block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref none))
;; CHECK-NEXT: (ref.cast (ref none)
;; CHECK-NEXT: (block (result i31ref)
;; CHECK-NEXT: (local.get $i31ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref none))
;; CHECK-NEXT: (ref.cast (ref none)
;; CHECK-NEXT: (block (result i31ref)
;; CHECK-NEXT: (local.get $i31ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $br_on_cast-unreachable (param $i31ref i31ref) (result anyref)
;; Optimize out br_on_cast* where the input is uninhabitable.
(block $block (result anyref)
(drop
(br_on_cast $block anyref (ref i31)
(block (result anyref)
(ref.cast (ref struct)
(block (result anyref)
(local.get $i31ref)
)
)
)
)
)
(br_on_cast_fail $block anyref (ref i31)
(block (result anyref)
(ref.cast (ref struct)
(block (result anyref)
(local.get $i31ref)
)
)
)
)
)
)
;; CHECK: (func $fallthrough-unreachable (type $8) (param $0 i31ref) (result anyref)
;; CHECK-NEXT: (block $outer
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref none)
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $fallthrough-unreachable (param $0 i31ref) (result anyref)
(block $outer (result (ref none))
(drop
;; This should not crash due to the new unreachable below.
(br_on_cast $outer (ref none) (ref none)
(ref.cast (ref none)
;; This will be optimized to a drop + unreachable.
(br_on_cast $outer (ref none) (ref none)
(ref.cast (ref none)
(local.get $0)
)
)
)
)
)
(unreachable)
)
)
;; CHECK: (func $casts-are-costly (type $9) (param $x i32)
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result i32)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (ref.test (ref none)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result nullref)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (ref.cast nullref
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result (ref null $struct))
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (block $something (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (br_on_non_null $something
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result nullref)
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (block $nothing
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (br $nothing)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $casts-are-costly (param $x i32)
;; We never turn an if into a select if an arm has a cast of any kind, as
;; those things involve branches internally, so we'd be adding more than we
;; save.
(local $struct (ref null $struct))
(drop
(if (result i32)
(local.get $x)
(then
(ref.test (ref $struct)
(ref.null any)
)
)
(else
(i32.const 0)
)
)
)
(drop
(if (result anyref)
(local.get $x)
(then
(ref.null any)
)
(else
(ref.cast (ref null $struct)
(ref.null any)
)
)
)
)
;; We do not selectify here because the amount of work in the if is
;; significant (there is a cast and a branch).
(drop
(if (result anyref)
(local.get $x)
(then
(block (result anyref)
(block $something (result anyref)
(drop
(br_on_cast $something anyref (ref $struct)
(local.get $struct)
)
)
(ref.null any)
)
)
)
(else
(ref.null any)
)
)
)
;; However, null checks are fairly fast, and we will emit a select here.
(drop
(if (result anyref)
(local.get $x)
(then
(block (result anyref)
(block $nothing
(drop
(br_on_null $nothing
(ref.null $struct)
)
)
)
(ref.null any)
)
)
(else
(ref.null any)
)
)
)
)
;; CHECK: (func $allocations-are-costly (type $9) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result (ref null (exact $struct)))
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result nullref)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $allocations-are-costly (param $x i32)
;; Allocations are too expensive for us to unconditionalize and selectify
;; here.
(drop
(if (result anyref)
(local.get $x)
(then
(struct.new $struct)
)
(else
(ref.null any)
)
)
)
;; But two nulls are fine.
(drop
(if (result anyref)
(local.get $x)
(then
(ref.null any)
)
(else
(ref.null any)
)
)
)
)
;; CHECK: (func $threading (type $13) (param $x anyref)
;; CHECK-NEXT: (block $outer
;; CHECK-NEXT: (block $inner
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_null $outer
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $threading (param $x anyref)
(block $outer
(block $inner
;; This jump can go to $outer.
(drop
(br_on_null $inner
(local.get $x)
)
)
)
)
)
;; CHECK: (func $test (type $14) (param $x (ref any))
;; CHECK-NEXT: (local $temp anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block (result (ref $struct-nn))
;; CHECK-NEXT: (struct.new $struct-nn
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (br_on_cast $block anyref (ref $struct-nn)
;; CHECK-NEXT: (local.tee $temp
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (param $x (ref any))
(local $temp anyref)
;; Read the inline comments blow from the bottom to the top (order of
;; execution). Basically, the story is that the br_on_cast begins as
;; flowing out a non-nullable type, since the cast allows nulls (so only a
;; non-null can flow out). We can see that the br_on_cast receives a non-
;; nullable value, even though it flows through a local.tee that un-refines
;; it. Using the non-nullability, we can refine the cast type (type sent on
;; the branch) to be non-nullable. But then the type of the br_on_cast itself
;; becomes nullable, since nulls no longer get sent on the branch, which
;; breaks the parent that must receive a non-nullable value.
;;
;; To fix this, we add a cast on the br's output, forcing it to the exact
;; same type it had before.
(drop
(block $block (result anyref)
(struct.new $struct-nn ;; must provide a NON-
;; nullable value for the
;; struct field
(br_on_cast $block anyref (ref null $struct-nn) ;; GLB on the castType
;; makes it non-nullable,
;; which makes the type
;; of the br_on_cast
;; nullable
(local.tee $temp ;; nullable
(local.get $x) ;; non-nullable
)
)
)
)
)
)
;; CHECK: (func $select-refinalize (type $15) (param $param (ref $struct)) (result (ref struct))
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (global.get $struct)
;; CHECK-NEXT: (global.get $struct)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $param)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-refinalize (param $param (ref $struct)) (result (ref struct))
;; The inner if can turn into a select. The type then changes, allowing the
;; outer select to be refined, which will error if we do not refinalize.
(select (result (ref struct))
(if (result (ref struct))
(i32.const 0)
(then
(global.get $struct)
)
(else
(global.get $struct)
)
)
(local.get $param)
(i32.const 0)
)
)
)