| ;; 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) |
| ) |
| ) |
| ) |