| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| ;; RUN: wasm-opt %s --optimize-instructions --traps-never-happen -all -S -o - | filecheck %s --check-prefix TNH |
| ;; RUN: wasm-opt %s --optimize-instructions -all -S -o - | filecheck %s --check-prefix NO_TNH |
| |
| (module |
| ;; TNH: (type $struct (sub (struct (field (mut i32))))) |
| ;; NO_TNH: (type $struct (sub (struct (field (mut i32))))) |
| (type $struct (sub (struct (field (mut i32))))) |
| |
| ;; TNH: (type $void (func)) |
| ;; NO_TNH: (type $void (func)) |
| (type $void (func)) |
| |
| ;; TNH: (import "a" "b" (func $import (type $2) (result i32))) |
| ;; NO_TNH: (import "a" "b" (func $import (type $2) (result i32))) |
| (import "a" "b" (func $import (result i32))) |
| |
| ;; TNH: (func $ref.eq (type $7) (param $a eqref) (param $b eqref) (result i32) |
| ;; TNH-NEXT: (ref.eq |
| ;; TNH-NEXT: (local.get $a) |
| ;; TNH-NEXT: (local.get $b) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $ref.eq (type $7) (param $a eqref) (param $b eqref) (result i32) |
| ;; NO_TNH-NEXT: (ref.eq |
| ;; NO_TNH-NEXT: (ref.cast (ref $struct) |
| ;; NO_TNH-NEXT: (local.get $a) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.cast (ref struct) |
| ;; NO_TNH-NEXT: (local.get $b) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $ref.eq (param $a (ref null eq)) (param $b (ref null eq)) (result i32) |
| ;; When traps never happen we can remove all the casts here, since they do |
| ;; not affect the comparison of the references. |
| (ref.eq |
| ;; When traps can happen we can still improve this by removing and |
| ;; combining redundant casts. |
| (ref.cast (ref struct) |
| (ref.as_non_null |
| (ref.cast (ref null $struct) |
| (local.get $a) |
| ) |
| ) |
| ) |
| ;; Note that we can remove the non-null casts here in both modes, as the |
| ;; ref.cast struct also checks for null. |
| (ref.cast (ref struct) |
| (ref.as_non_null |
| (ref.as_non_null |
| (local.get $b) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $ref.eq-no (type $8) (param $a eqref) (param $b eqref) (param $any anyref) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $ref.eq-no (type $8) (param $a eqref) (param $b eqref) (param $any anyref) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.eq |
| ;; NO_TNH-NEXT: (ref.cast (ref null $struct) |
| ;; NO_TNH-NEXT: (local.get $any) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.cast (ref struct) |
| ;; NO_TNH-NEXT: (local.get $any) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq)) (param $any anyref) |
| ;; We must leave the inputs to ref.eq of type eqref or a subtype. |
| (drop |
| (ref.eq |
| (ref.cast (ref null $struct) |
| (local.get $any) ;; *Not* an eqref! |
| ) |
| (ref.as_non_null |
| (ref.cast (ref struct) |
| (ref.as_non_null |
| (local.get $any) ;; *Not* an eqref! |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $ref.is (type $3) (param $a eqref) (result i32) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.cast (ref $struct) |
| ;; TNH-NEXT: (local.get $a) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 0) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $ref.is (type $3) (param $a eqref) (result i32) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.cast (ref $struct) |
| ;; NO_TNH-NEXT: (local.get $a) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 0) |
| ;; NO_TNH-NEXT: ) |
| (func $ref.is (param $a (ref null eq)) (result i32) |
| ;; In this case non-nullability is enough to tell that the ref.is will |
| ;; return 0. TNH does not help here. |
| (ref.is_null |
| (ref.cast (ref $struct) |
| (ref.as_non_null |
| (ref.cast (ref struct) |
| (local.get $a) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $ref.is_b (type $9) (param $a eqref) (param $f funcref) (result i32) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.is_null |
| ;; TNH-NEXT: (local.get $a) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (ref.is_null |
| ;; TNH-NEXT: (local.get $f) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $ref.is_b (type $9) (param $a eqref) (param $f funcref) (result i32) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.is_null |
| ;; NO_TNH-NEXT: (ref.cast (ref null $struct) |
| ;; NO_TNH-NEXT: (local.get $a) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.is_null |
| ;; NO_TNH-NEXT: (ref.cast (ref null $void) |
| ;; NO_TNH-NEXT: (local.get $f) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $ref.is_b (param $a eqref) (param $f funcref) (result i32) |
| ;; Here we only have a cast, and no cast operations that force the value |
| ;; to be non-nullable. That means we cannot remove the ref.is, but we can |
| ;; remove the cast in TNH. |
| (drop |
| (ref.is_null |
| (ref.cast (ref null $struct) |
| (local.get $a) |
| ) |
| ) |
| ) |
| ;; It works on func references, too. |
| (ref.is_null |
| (ref.cast (ref null $void) |
| (local.get $f) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $ref.test (type $3) (param $a eqref) (result i32) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result i32) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.cast i31ref |
| ;; TNH-NEXT: (local.get $a) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block (result i32) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.as_non_null |
| ;; TNH-NEXT: (local.get $a) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $ref.test (type $3) (param $a eqref) (result i32) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result i32) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.cast i31ref |
| ;; NO_TNH-NEXT: (local.get $a) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (block (result i32) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.as_non_null |
| ;; NO_TNH-NEXT: (local.get $a) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $ref.test (param $a eqref) (result i32) |
| (drop |
| (ref.test i31ref |
| (ref.cast i31ref |
| (local.get $a) |
| ) |
| ) |
| ) |
| (ref.test (ref eq) |
| (ref.cast (ref eq) |
| (local.get $a) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $if.arm.null (type $4) (param $x i32) (param $ref (ref $struct)) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $if.arm.null (type $4) (param $x i32) (param $ref (ref $struct)) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (if (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: (then |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (else |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (if (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: (then |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (else |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 2) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $if.arm.null (param $x i32) (param $ref (ref $struct)) |
| ;; A set will trap on a null, so in tnh mode we know the null arm is not |
| ;; executed, and the other one is. |
| (struct.set $struct 0 |
| (if (result (ref null $struct)) |
| (local.get $x) |
| (then |
| (local.get $ref) |
| ) |
| (else |
| (ref.null none) |
| ) |
| ) |
| (i32.const 1) |
| ) |
| (struct.set $struct 0 |
| (if (result (ref null $struct)) |
| (local.get $x) |
| (then |
| (ref.null none) |
| ) |
| (else |
| (local.get $ref) |
| ) |
| ) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; TNH: (func $select.arm.null (type $4) (param $x i32) (param $ref (ref $struct)) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (block |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $select.arm.null (type $4) (param $x i32) (param $ref (ref $struct)) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (select (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (select (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 2) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $select.arm.null (param $x i32) (param $ref (ref $struct)) |
| ;; As above but with a select. |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (local.get $ref) |
| (ref.null none) |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (ref.null none) |
| (local.get $ref) |
| (local.get $x) |
| ) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; TNH: (func $select.arm.null.effects (type $void) |
| ;; TNH-NEXT: (local $temp i32) |
| ;; TNH-NEXT: (local $1 (ref $struct)) |
| ;; TNH-NEXT: (local $2 (ref $struct)) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (local.set $1 |
| ;; TNH-NEXT: (struct.new $struct |
| ;; TNH-NEXT: (local.tee $temp |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result nullref) |
| ;; TNH-NEXT: (local.set $temp |
| ;; TNH-NEXT: (i32.const 2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $temp) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result nullref) |
| ;; TNH-NEXT: (local.set $temp |
| ;; TNH-NEXT: (i32.const 2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block (result (ref $struct)) |
| ;; TNH-NEXT: (local.set $2 |
| ;; TNH-NEXT: (struct.new $struct |
| ;; TNH-NEXT: (local.tee $temp |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $temp) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (local.get $2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 2) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $select.arm.null.effects (type $void) |
| ;; NO_TNH-NEXT: (local $temp i32) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (select (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (struct.new $struct |
| ;; NO_TNH-NEXT: (local.tee $temp |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (block (result nullref) |
| ;; NO_TNH-NEXT: (local.set $temp |
| ;; NO_TNH-NEXT: (i32.const 2) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (local.get $temp) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (select (result (ref null $struct)) |
| ;; NO_TNH-NEXT: (block (result nullref) |
| ;; NO_TNH-NEXT: (local.set $temp |
| ;; NO_TNH-NEXT: (i32.const 2) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.new $struct |
| ;; NO_TNH-NEXT: (local.tee $temp |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (local.get $temp) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 2) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $select.arm.null.effects |
| (local $temp i32) |
| ;; As above but there are conflicting effects and we must add a local when |
| ;; we optimize. |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (struct.new $struct |
| (local.tee $temp |
| (i32.const 1) |
| ) |
| ) |
| (block (result (ref null none)) |
| (local.set $temp |
| (i32.const 2) |
| ) |
| (ref.null none) |
| ) |
| (local.get $temp) |
| ) |
| (i32.const 1) |
| ) |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (block (result (ref null none)) |
| (local.set $temp |
| (i32.const 2) |
| ) |
| (ref.null none) |
| ) |
| (struct.new $struct |
| (local.tee $temp |
| (i32.const 1) |
| ) |
| ) |
| (local.get $temp) |
| ) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; TNH: (func $null.arm.null.effects (type $void) |
| ;; TNH-NEXT: (block ;; (replaces unreachable StructSet we can't emit) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (select |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: (call $get-i32) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result nullref) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (call $get-i32) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $null.arm.null.effects (type $void) |
| ;; NO_TNH-NEXT: (block ;; (replaces unreachable StructSet we can't emit) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (select |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: (call $get-i32) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (block |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result nullref) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (call $get-i32) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (ref.null none) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $null.arm.null.effects |
| ;; Verify we do not error on a null reference in a select, even if cast to |
| ;; non-null. |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| (ref.null none) |
| (call $get-i32) |
| ) |
| (i32.const 1) |
| ) |
| ;; The same, but without ref.as_non_null. |
| (struct.set $struct 0 |
| (select (result (ref null $struct)) |
| (ref.null none) |
| (ref.null none) |
| (call $get-i32) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; TNH: (func $set-get-cast (type $10) (param $ref structref) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (struct.get $struct 0 |
| ;; TNH-NEXT: (ref.cast (ref $struct) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (ref.cast (ref $struct) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (ref.cast (ref null $struct) |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (block (result i32) |
| ;; TNH-NEXT: (return) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $set-get-cast (type $10) (param $ref structref) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (struct.get $struct 0 |
| ;; NO_TNH-NEXT: (ref.cast (ref $struct) |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (ref.cast (ref null $struct) |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (ref.cast (ref null $struct) |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (block (result i32) |
| ;; NO_TNH-NEXT: (return) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $set-get-cast (param $ref (ref null struct)) |
| ;; A nullable cast flowing into a place that traps on null can become a |
| ;; non-nullable cast. |
| (drop |
| (struct.get $struct 0 |
| (ref.cast (ref null $struct) |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ;; Ditto for a set, at least in traps-happen mode. |
| ;; TODO handle non-TNH as well, but we need to be careful of effects in |
| ;; other children. |
| (struct.set $struct 0 |
| (ref.cast (ref null $struct) |
| (local.get $ref) |
| ) |
| (i32.const 1) |
| ) |
| ;; Even in TNH mode, a child with an effect of control flow transfer |
| ;; prevents us from optimizing - if the parent is not necessarily reached, |
| ;; we cannot infer the child won't trap. |
| (struct.set $struct 0 |
| (ref.cast (ref null $struct) |
| (local.get $ref) |
| ) |
| (block (result i32) |
| ;; This block has type i32, to check that we don't just look for |
| ;; unreachable. We must scan for any transfer of control flow in the |
| ;; child of the struct.set. |
| (return) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $cast-if-null (type $5) (param $x (ref none)) (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $cast-if-null (type $5) (param $x (ref none)) (result (ref $struct)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (if (result (ref none)) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: (then |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (else |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| (func $cast-if-null (param $x (ref none)) (result (ref $struct)) |
| ;; We can remove the unreachable arm of the if here in TNH mode. While doing |
| ;; so we must refinalize properly or else we'll hit an error in pass-debug |
| ;; mode. |
| (ref.cast (ref $struct) |
| (if (result (ref none)) |
| (i32.const 1) |
| (then |
| (unreachable) |
| ) |
| (else |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $cast-if-null-flip (type $5) (param $x (ref none)) (result (ref $struct)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $cast-if-null-flip (type $5) (param $x (ref none)) (result (ref $struct)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (if (result (ref none)) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: (then |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (else |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| (func $cast-if-null-flip (param $x (ref none)) (result (ref $struct)) |
| ;; As above but with arms flipped. |
| (ref.cast (ref $struct) |
| (if (result (ref none)) |
| (i32.const 1) |
| (then |
| (local.get $x) |
| ) |
| (else |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $cast-to-bottom (type $11) (param $ref (ref any)) (param $nullable-ref anyref) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result (ref none)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result (ref none)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $nullable-ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result (ref none)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result nullref) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (local.get $nullable-ref) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (ref.null none) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $cast-to-bottom (type $11) (param $ref (ref any)) (param $nullable-ref anyref) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result (ref none)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result (ref none)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (local.get $nullable-ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result (ref none)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (local.get $ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.cast nullref |
| ;; NO_TNH-NEXT: (local.get $nullable-ref) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $cast-to-bottom (param $ref (ref any)) (param $nullable-ref anyref) |
| ;; Non-nullable casts to none must trap (regardless of whether the input is |
| ;; nullable or not, the output is an impossible type). |
| (drop |
| (ref.cast (ref none) |
| (local.get $ref) |
| ) |
| ) |
| (drop |
| (ref.cast (ref none) |
| (local.get $nullable-ref) |
| ) |
| ) |
| ;; Nullable casts to null have more possibilities. First, if the input is |
| ;; non-nullable then we trap. |
| (drop |
| (ref.cast nullref |
| (local.get $ref) |
| ) |
| ) |
| ;; Second, if the value may be a null, then we either return a null or we |
| ;; trap. In TNH mode we dismiss the possibility of a trap and so we can just |
| ;; return a null here. (In non-TNH mode we could do a check for null etc., |
| ;; but we'd be increasing code size.) |
| (drop |
| (ref.cast nullref |
| (local.get $nullable-ref) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $null.cast-other.effects (type $12) (param $x (ref null $struct)) |
| ;; TNH-NEXT: (local $i i32) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: (call $import) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: (i32.const 10) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (struct.set $struct 0 |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: (local.tee $i |
| ;; TNH-NEXT: (i32.const 10) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $null.cast-other.effects (type $12) (param $x (ref null $struct)) |
| ;; NO_TNH-NEXT: (local $i i32) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (ref.as_non_null |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (call $import) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: (i32.const 10) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (struct.set $struct 0 |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: (local.tee $i |
| ;; NO_TNH-NEXT: (i32.const 10) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $null.cast-other.effects (param $x (ref null $struct)) |
| (local $i i32) |
| (struct.set $struct 0 |
| ;; We cannot remove this ref.as_non_null, even though the struct.set will |
| ;; trap if the ref is null, because that would move the trap from before |
| ;; the call to the import to be after it. But in TNH we can assume it does |
| ;; not trap, and remove it. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (call $import) |
| ) |
| (struct.set $struct 0 |
| ;; This one can be removed even without TNH, as there are no effects after |
| ;; it. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (i32.const 10) |
| ) |
| (struct.set $struct 0 |
| ;; This one can be removed even without TNH, even though there are effects |
| ;; in it. A tee only has local effects, which do not interfere with a |
| ;; trap. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (local.tee $i |
| (i32.const 10) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $select.unreachable.child (type $6) (param $x (ref $struct)) (result (ref $struct)) |
| ;; TNH-NEXT: (block ;; (replaces unreachable RefCast we can't emit) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $select.unreachable.child (type $6) (param $x (ref $struct)) (result (ref $struct)) |
| ;; NO_TNH-NEXT: (block ;; (replaces unreachable RefCast we can't emit) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $select.unreachable.child (param $x (ref $struct)) (result (ref $struct)) |
| ;; We will turn the false arm of the select into an unreachable first, and |
| ;; then process the select. While doing so we must not error, as the select |
| ;; itself will still have a reachable type (a full refinalize only |
| ;; happens at the very end of the function). |
| (ref.cast (ref $struct) |
| (select (result (ref $struct)) |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $select.unreachable.child.flip (type $6) (param $x (ref $struct)) (result (ref $struct)) |
| ;; TNH-NEXT: (select |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $select.unreachable.child.flip (type $6) (param $x (ref $struct)) (result (ref $struct)) |
| ;; NO_TNH-NEXT: (select |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $select.unreachable.child.flip (param $x (ref $struct)) (result (ref $struct)) |
| ;; Flip case of the above. |
| (ref.cast (ref $struct) |
| (select (result (ref $struct)) |
| (local.get $x) |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $if.null.child.but.no.flow (type $void) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (block (result (ref nofunc)) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (if (result (ref nofunc)) |
| ;; TNH-NEXT: (i32.const 1) |
| ;; TNH-NEXT: (then |
| ;; TNH-NEXT: (return) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (else |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $if.null.child.but.no.flow (type $void) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (block (result (ref nofunc)) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (if (result (ref nofunc)) |
| ;; NO_TNH-NEXT: (i32.const 1) |
| ;; NO_TNH-NEXT: (then |
| ;; NO_TNH-NEXT: (return) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (else |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $if.null.child.but.no.flow |
| ;; The if's true arm has a bottom type, which the cast would trap on. But we |
| ;; cannot optimize using that fact, as the null does not actually flow out - |
| ;; we return from the function before. So we should not replace the if with |
| ;; the false arm (that would trap, and change the behavior; tnh can remove |
| ;; traps, not add them). |
| (drop |
| (ref.cast (ref func) |
| (if (result (ref nofunc)) |
| (i32.const 1) |
| (then |
| (block (result (ref nofunc)) |
| (return) |
| ) |
| ) |
| (else |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; TNH: (func $set-of-as-non-null (type $void) |
| ;; TNH-NEXT: (local $x anyref) |
| ;; TNH-NEXT: (local.set $x |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: (drop |
| ;; TNH-NEXT: (ref.as_non_null |
| ;; TNH-NEXT: (local.tee $x |
| ;; TNH-NEXT: (local.get $x) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $set-of-as-non-null (type $void) |
| ;; NO_TNH-NEXT: (local $x anyref) |
| ;; NO_TNH-NEXT: (local.set $x |
| ;; NO_TNH-NEXT: (ref.as_non_null |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: (drop |
| ;; NO_TNH-NEXT: (ref.as_non_null |
| ;; NO_TNH-NEXT: (local.tee $x |
| ;; NO_TNH-NEXT: (local.get $x) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| ;; NO_TNH-NEXT: ) |
| (func $set-of-as-non-null |
| (local $x anyref) |
| ;; We can remove the ref.as_non_null here because the local is nullable and |
| ;; we are ignoring traps. |
| ;; TODO: Should we keep the cast to let us refine the local later? |
| (local.set $x |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ;; The same for a tee. |
| (drop |
| (local.tee $x |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| |
| ;; Helper functions. |
| |
| ;; TNH: (func $get-i32 (type $2) (result i32) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $get-i32 (type $2) (result i32) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| (func $get-i32 (result i32) |
| (unreachable) |
| ) |
| ;; TNH: (func $get-ref (type $13) (result (ref $struct)) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $get-ref (type $13) (result (ref $struct)) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| (func $get-ref (result (ref $struct)) |
| (unreachable) |
| ) |
| ;; TNH: (func $get-null (type $14) (result nullref) |
| ;; TNH-NEXT: (unreachable) |
| ;; TNH-NEXT: ) |
| ;; NO_TNH: (func $get-null (type $14) (result nullref) |
| ;; NO_TNH-NEXT: (unreachable) |
| ;; NO_TNH-NEXT: ) |
| (func $get-null (result (ref null none)) |
| (unreachable) |
| ) |
| ) |