| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| ;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions --enable-reference-types --enable-gc -S -o - \ |
| ;; RUN: | filecheck %s |
| ;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions --enable-reference-types --enable-gc --nominal -S -o - \ |
| ;; RUN: | filecheck %s --check-prefix NOMNL |
| |
| (module |
| ;; CHECK: (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)))) |
| ;; NOMNL: (type $struct (struct_subtype (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)) data)) |
| (type $struct (struct |
| (field $i8 (mut i8)) |
| (field $i16 (mut i16)) |
| (field $i32 (mut i32)) |
| (field $i64 (mut i64)) |
| )) |
| |
| ;; CHECK: (type $A (struct (field i32))) |
| ;; NOMNL: (type $A (struct_subtype (field i32) data)) |
| (type $A (struct (field i32))) |
| |
| ;; CHECK: (type $B (struct (field i32) (field i32) (field f32))) |
| |
| ;; CHECK: (type $array (array (mut i8))) |
| ;; NOMNL: (type $B (struct_subtype (field i32) (field i32) (field f32) $A)) |
| |
| ;; NOMNL: (type $array (array_subtype (mut i8) data)) |
| (type $array (array (mut i8))) |
| |
| (type $B (struct_subtype (field i32) (field i32) (field f32) $A)) |
| |
| ;; CHECK: (type $B-child (struct (field i32) (field i32) (field f32) (field i64))) |
| ;; NOMNL: (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B)) |
| (type $B-child (struct_subtype (field i32) (field i32) (field f32) (field i64) $B)) |
| |
| ;; NOMNL: (type $C (struct_subtype (field i32) (field i32) (field f64) $A)) |
| |
| ;; NOMNL: (type $empty (struct_subtype data)) |
| (type $empty (struct)) |
| |
| ;; CHECK: (type $C (struct (field i32) (field i32) (field f64))) |
| (type $C (struct_subtype (field i32) (field i32) (field f64) $A)) |
| |
| ;; CHECK: (import "env" "get-i32" (func $get-i32 (result i32))) |
| ;; NOMNL: (import "env" "get-i32" (func $get-i32 (result i32))) |
| (import "env" "get-i32" (func $get-i32 (result i32))) |
| |
| ;; These functions test if an `if` with subtyped arms is correctly folded |
| ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold) |
| ;; CHECK: (func $if-arms-subtype-fold (result anyref) |
| ;; CHECK-NEXT: (ref.null eq) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $if-arms-subtype-fold (type $none_=>_anyref) (result anyref) |
| ;; NOMNL-NEXT: (ref.null eq) |
| ;; NOMNL-NEXT: ) |
| (func $if-arms-subtype-fold (result anyref) |
| (if (result anyref) |
| (i32.const 0) |
| (ref.null eq) |
| (ref.null eq) |
| ) |
| ) |
| ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold) |
| ;; CHECK: (func $if-arms-subtype-nofold (result anyref) |
| ;; CHECK-NEXT: (if (result anyref) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (ref.null data) |
| ;; CHECK-NEXT: (ref.null i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $if-arms-subtype-nofold (type $none_=>_anyref) (result anyref) |
| ;; NOMNL-NEXT: (if (result anyref) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: (ref.null data) |
| ;; NOMNL-NEXT: (ref.null i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $if-arms-subtype-nofold (result anyref) |
| (if (result anyref) |
| (i32.const 0) |
| (ref.null data) |
| (ref.null i31) |
| ) |
| ) |
| |
| ;; Stored values automatically truncate unneeded bytes. |
| ;; CHECK: (func $store-trunc (param $x (ref null $struct)) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 35) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i16 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 9029) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $store-trunc (type $ref?|$struct|_=>_none) (param $x (ref null $struct)) |
| ;; NOMNL-NEXT: (struct.set $struct $i8 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (i32.const 35) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (struct.set $struct $i16 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (i32.const 9029) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (struct.set $struct $i8 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (call $get-i32) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $store-trunc (param $x (ref null $struct)) |
| (struct.set $struct $i8 |
| (local.get $x) |
| (i32.const 0x123) ;; data over 0xff is unnecessary |
| ) |
| (struct.set $struct $i16 |
| (local.get $x) |
| (i32.const 0x12345) ;; data over 0xffff is unnecessary |
| ) |
| (struct.set $struct $i8 |
| (local.get $x) |
| (i32.and ;; truncating bits using an and is unnecessary |
| (call $get-i32) |
| (i32.const 0xff) |
| ) |
| ) |
| ) |
| |
| ;; Similar, but for arrays. |
| ;; CHECK: (func $store-trunc2 (param $x (ref null $array)) |
| ;; CHECK-NEXT: (array.set $array |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 35) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $store-trunc2 (type $ref?|$array|_=>_none) (param $x (ref null $array)) |
| ;; NOMNL-NEXT: (array.set $array |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: (i32.const 35) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $store-trunc2 (param $x (ref null $array)) |
| (array.set $array |
| (local.get $x) |
| (i32.const 0) |
| (i32.const 0x123) ;; data over 0xff is unnecessary |
| ) |
| ) |
| |
| ;; ref.is_null is not needed on a non-nullable value, and if something is |
| ;; a func we don't need that either etc. if we know the result |
| ;; CHECK: (func $unneeded_is (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_is (type $ref|$struct|_ref|func|_dataref_i31ref_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_is |
| (param $struct (ref $struct)) |
| (param $func (ref func)) |
| (param $data (ref data)) |
| (param $i31 (ref i31)) |
| (drop |
| (ref.is_null (local.get $struct)) |
| ) |
| (drop |
| (ref.is_func (local.get $func)) |
| ) |
| (drop |
| (ref.is_data (local.get $data)) |
| ) |
| (drop |
| (ref.is_i31 (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_is, but the values are nullable. we can at least |
| ;; leave just the null check. |
| ;; CHECK: (func $unneeded_is_null (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_is_null (type $ref?|$struct|_funcref_ref?|data|_ref?|i31|_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.eqz |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.eqz |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.eqz |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_is_null |
| (param $struct (ref null $struct)) |
| (param $func (ref null func)) |
| (param $data (ref null data)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.is_null (local.get $struct)) |
| ) |
| (drop |
| (ref.is_func (local.get $func)) |
| ) |
| (drop |
| (ref.is_data (local.get $data)) |
| ) |
| (drop |
| (ref.is_i31 (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_is, but the values are of mixed kind (is_func of |
| ;; data, etc.). regardless of nullability the result here is always 0. |
| ;; CHECK: (func $unneeded_is_bad_kinds (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $data) |
| ;; 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: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; 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: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_is_bad_kinds (type $funcref_ref?|data|_ref?|i31|_=>_none) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_is_bad_kinds |
| (param $func (ref null func)) |
| (param $data (ref null data)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.is_func (local.get $data)) |
| ) |
| (drop |
| (ref.is_data (local.get $i31)) |
| ) |
| (drop |
| (ref.is_i31 (local.get $func)) |
| ) |
| ;; also check non-nullable types as inputs |
| (drop |
| (ref.is_func (ref.as_non_null (local.get $data))) |
| ) |
| (drop |
| (ref.is_data (ref.as_non_null (local.get $i31))) |
| ) |
| (drop |
| (ref.is_i31 (ref.as_non_null (local.get $func))) |
| ) |
| ) |
| |
| ;; ref.as_non_null is not needed on a non-nullable value, and if something is |
| ;; a func we don't need that either etc., and can just return the value. |
| ;; CHECK: (func $unneeded_as (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_as (type $ref|$struct|_ref|func|_dataref_i31ref_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $data dataref) (param $i31 i31ref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_as |
| (param $struct (ref $struct)) |
| (param $func (ref func)) |
| (param $data (ref data)) |
| (param $i31 (ref i31)) |
| (drop |
| (ref.as_non_null (local.get $struct)) |
| ) |
| (drop |
| (ref.as_func (local.get $func)) |
| ) |
| (drop |
| (ref.as_data (local.get $data)) |
| ) |
| (drop |
| (ref.as_i31 (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_as, but the values are nullable. we can turn the |
| ;; more specific things into ref.as_non_null. |
| ;; CHECK: (func $unneeded_as_null (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_as_null (type $ref?|$struct|_funcref_ref?|data|_ref?|i31|_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_as_null |
| (param $struct (ref null $struct)) |
| (param $func (ref null func)) |
| (param $data (ref null data)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.as_non_null (local.get $struct)) |
| ) |
| (drop |
| (ref.as_func (local.get $func)) |
| ) |
| (drop |
| (ref.as_data (local.get $data)) |
| ) |
| (drop |
| (ref.as_i31 (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_as, but the values are of mixed kind (as_func of |
| ;; data, etc.), so we know we will trap |
| ;; CHECK: (func $unneeded_as_bad_kinds (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref func)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result dataref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref func)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $data) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result dataref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_as_bad_kinds (type $funcref_ref?|data|_ref?|i31|_=>_none) (param $func funcref) (param $data (ref null data)) (param $i31 (ref null i31)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref func)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result dataref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i31ref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref func)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $data) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result dataref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $i31) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i31ref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_as_bad_kinds |
| (param $func (ref null func)) |
| (param $data (ref null data)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.as_func (local.get $data)) |
| ) |
| (drop |
| (ref.as_data (local.get $i31)) |
| ) |
| (drop |
| (ref.as_i31 (local.get $func)) |
| ) |
| ;; also check non-nullable types as inputs |
| (drop |
| (ref.as_func (ref.as_non_null (local.get $data))) |
| ) |
| (drop |
| (ref.as_data (ref.as_non_null (local.get $i31))) |
| ) |
| (drop |
| (ref.as_i31 (ref.as_non_null (local.get $func))) |
| ) |
| ) |
| |
| ;; CHECK: (func $unneeded_unreachability |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_func |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_func |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $unneeded_unreachability (type $none_=>_none) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_func |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_func |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $unneeded_unreachability |
| ;; unreachable instructions can simply be ignored |
| (drop |
| (ref.is_func (unreachable)) |
| ) |
| (drop |
| (ref.as_func (unreachable)) |
| ) |
| ) |
| |
| ;; CHECK: (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.set $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.get_u $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.len $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $redundant-non-null-casts (type $ref?|$struct|_ref?|$array|_=>_none) (param $x (ref null $struct)) (param $y (ref null $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (struct.set $struct $i8 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (array.set $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: (i32.const 2) |
| ;; NOMNL-NEXT: (i32.const 3) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (array.get_u $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: (i32.const 4) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (array.len $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) |
| (drop |
| (ref.as_non_null |
| (ref.as_non_null |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (struct.set $struct 0 |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| (drop |
| (struct.get_u $struct 0 |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| (array.set $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| (drop |
| (array.get $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| (i32.const 4) |
| ) |
| ) |
| (drop |
| (array.len $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-eqref (result eqref) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $get-eqref (type $none_=>_eqref) (result eqref) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| (func $get-eqref (result eqref) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $ref-eq (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (local $lx eqref) |
| ;; CHECK-NEXT: (local $ly eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $lx |
| ;; CHECK-NEXT: (call $get-eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) |
| ;; NOMNL-NEXT: (local $lx eqref) |
| ;; NOMNL-NEXT: (local $ly eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (local.set $lx |
| ;; NOMNL-NEXT: (call $get-eqref) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq (param $x eqref) (param $y eqref) |
| (local $lx eqref) |
| (local $ly eqref) |
| ;; identical parameters are equal |
| (drop |
| (ref.eq |
| (local.get $x) |
| (local.get $x) |
| ) |
| ) |
| ;; different ones might not be |
| (drop |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ;; identical locals are |
| (local.set $lx |
| (call $get-eqref) |
| ) |
| (drop |
| (ref.eq |
| (local.get $lx) |
| (local.get $lx) |
| ) |
| ) |
| ;; fallthroughs work ok (but we need --remove-unused-names so that we can |
| ;; trivially tell that there are no breaks) |
| (drop |
| (ref.eq |
| (block (result eqref) |
| (nop) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (nop) |
| (drop |
| (i32.const 10) |
| ) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nothing |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $nothing (type $none_=>_none) |
| ;; NOMNL-NEXT: (nop) |
| ;; NOMNL-NEXT: ) |
| (func $nothing) |
| |
| |
| ;; CHECK: (func $ref-eq-corner-cases (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $nothing) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $nothing) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-corner-cases (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (block (result eqref) |
| ;; NOMNL-NEXT: (call $nothing) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (block (result eqref) |
| ;; NOMNL-NEXT: (call $nothing) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (struct.new_default $struct) |
| ;; NOMNL-NEXT: (struct.new_default $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.tee $x |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-corner-cases (param $x eqref) |
| ;; side effects prevent optimization |
| (drop |
| (ref.eq |
| (block (result eqref) |
| (call $nothing) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (call $nothing) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; allocation prevents optimization |
| (drop |
| (ref.eq |
| (struct.new_default $struct) |
| (struct.new_default $struct) |
| ) |
| ) |
| ;; but irrelevant allocations do not prevent optimization |
| (drop |
| (ref.eq |
| (block (result eqref) |
| ;; an allocation that does not trouble us |
| (drop |
| (struct.new_default $struct) |
| ) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (drop |
| (struct.new_default $struct) |
| ) |
| ;; add a nop to make the two inputs to ref.eq not structurally equal, |
| ;; but in a way that does not matter (since only the value falling |
| ;; out does) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; a tee does not prevent optimization, as we can fold the tee and the get. |
| (drop |
| (ref.eq |
| (local.tee $x |
| (local.get $x) |
| ) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-ref-cast (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-ref-cast (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-ref-cast (param $x eqref) |
| ;; it is almost valid to look through a cast, except that it might trap so |
| ;; there is a side effect |
| (drop |
| (ref.eq |
| (local.get $x) |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $flip-cast-of-as-non-null (param $x anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (ref.as_func |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (ref.as_data |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (ref.as_i31 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $flip-cast-of-as-non-null (type $anyref_=>_none) (param $x anyref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (ref.as_func |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (ref.as_data |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (ref.as_i31 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $flip-cast-of-as-non-null (param $x anyref) |
| (drop |
| (ref.cast_static $struct |
| ;; this can be moved through the ref.cast outward. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| ;; an example of how this helps: the struct.get will trap on null anyhow |
| (struct.get_u $struct 0 |
| (ref.cast_static $struct |
| ;; this can be moved through the ref.cast outward. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; other ref.as* operations are ignored for now |
| (drop |
| (ref.cast_static $struct |
| (ref.as_func |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $struct |
| (ref.as_data |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $struct |
| (ref.as_i31 |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $flip-tee-of-as-non-null (param $x anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $flip-tee-of-as-non-null (type $anyref_=>_none) (param $x anyref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (local.tee $x |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $flip-tee-of-as-non-null (param $x anyref) |
| (drop |
| (local.tee $x |
| ;; this can be moved through the tee outward. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (param $x (ref any)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $flip-tee-of-as-non-null-non-nullable (type $ref|any|_=>_none) (param $x (ref any)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.tee $x |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.null any) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $flip-tee-of-as-non-null-non-nullable (param $x (ref any)) |
| (drop |
| (local.tee $x |
| ;; this *cannnot* be moved through the tee outward, as the param is in |
| ;; fact non-nullable, and we depend on the ref.as_non_null in order to |
| ;; get a valid type to assign to it |
| (ref.as_non_null |
| (ref.null any) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (if (result (ref null $struct)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ternary-identical-arms (type $i32_ref?|$struct|_ref?|$struct|_=>_none) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (if (result (ref null $struct)) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: (local.get $z) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) |
| (drop |
| (if (result i32) |
| (local.get $x) |
| (ref.is_null (local.get $y)) |
| (ref.is_null (local.get $z)) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $select-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $select-identical-arms-but-side-effect (type $ref?|$struct|_ref?|$struct|_i32_=>_none) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (select |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (local.get $z) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $select-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| (drop |
| (select |
| ;; the arms are equal but have side effects |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| (local.get $z) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (select (result (ref $struct)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ternary-identical-arms-no-side-effect (type $ref|$struct|_ref|$struct|_i32_=>_none) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (select (result (ref $struct)) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: (local.get $z) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) |
| (drop |
| (select |
| ;; the arms are equal and as the params are non-null, there are no possible side effects |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| (local.get $z) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $if-identical-arms-with-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (if (result (ref null $struct)) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $if-identical-arms-with-side-effect (type $ref?|$struct|_ref?|$struct|_i32_=>_none) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (struct.get_u $struct $i8 |
| ;; NOMNL-NEXT: (if (result (ref null $struct)) |
| ;; NOMNL-NEXT: (local.get $z) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $if-identical-arms-with-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| (drop |
| (if (result i32) |
| (local.get $z) |
| ;; the arms are equal and have side effects, but that is ok with an if |
| ;; which only executes one side anyhow |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-squared (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-squared (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-squared (param $x eqref) |
| ;; Identical ref.casts can be folded together. |
| (drop |
| (ref.cast_static $struct |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-squared-fallthrough (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-squared-fallthrough (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.tee $x |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-squared-fallthrough (param $x eqref) |
| ;; A fallthrough in the middle does not prevent this optimization. |
| (drop |
| (ref.cast_static $struct |
| (local.tee $x |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-cubed (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-cubed (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-cubed (param $x eqref) |
| ;; Three and more also work. |
| (drop |
| (ref.cast_static $struct |
| (ref.cast_static $struct |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-squared-different (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-squared-different (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (ref.cast_static $empty |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-squared-different (param $x eqref) |
| ;; Different casts cannot be folded. |
| (drop |
| (ref.cast_static $struct |
| (ref.cast_static $empty |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-null (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-null (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_null |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-null (param $x eqref) |
| ;; Equality to null can be done with ref.is_null. |
| (drop |
| (ref.eq |
| (local.get $x) |
| (ref.null eq) |
| ) |
| ) |
| (drop |
| (ref.eq |
| (ref.null eq) |
| (local.get $x) |
| ) |
| ) |
| ;; Also check that we turn a comparison of two nulls into 1, using the rule |
| ;; for comparing the same thing to itself (i.e., that we run that rule first |
| ;; and not the check for one of them being null, which would require more |
| ;; work afterwards). |
| (drop |
| (ref.eq |
| (ref.null eq) |
| (ref.null eq) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-possible (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-possible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-possible (param $x eqref) (param $y eqref) |
| ;; These casts are to types that are incompatible. However, it is possible |
| ;; they are both null, so we cannot optimize here. |
| (drop |
| (ref.eq |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| (ref.cast_static $array |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-impossible (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; 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: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; 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: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-impossible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-impossible (param $x eqref) (param $y eqref) |
| ;; As above but cast one of them to non-null, which proves they cannot be |
| ;; equal, and the result must be 0. |
| (drop |
| (ref.eq |
| (ref.cast_static $struct |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast_static $array |
| (local.get $y) |
| ) |
| ) |
| ) |
| ;; As above but the cast is on the other one. |
| (drop |
| (ref.eq |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| (ref.cast_static $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ;; As above but the cast is both. |
| (drop |
| (ref.eq |
| (ref.cast_static $struct |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast_static $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-possible-b (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $A |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $A |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $A |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-eq-possible-b (param $x eqref) (param $y eqref) |
| ;; As above but the casts are to things that are compatible, since B is a |
| ;; subtype of A, so we cannot optimize. |
| (drop |
| (ref.eq |
| (ref.cast_static $A |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast_static $B |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ;; As above but flipped. |
| (drop |
| (ref.eq |
| (ref.cast_static $B |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast_static $A |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $hoist-LUB-danger (param $x i32) (result i32) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (struct.get $B 1 |
| ;; CHECK-NEXT: (ref.null $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get $C 1 |
| ;; CHECK-NEXT: (ref.null $C) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $hoist-LUB-danger (type $i32_=>_i32) (param $x i32) (result i32) |
| ;; NOMNL-NEXT: (if (result i32) |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (struct.get $B 1 |
| ;; NOMNL-NEXT: (ref.null $B) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (struct.get $C 1 |
| ;; NOMNL-NEXT: (ref.null $C) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $hoist-LUB-danger (param $x i32) (result i32) |
| ;; In nominal typing, if we hoist the struct.get out of the if, then the if |
| ;; will have a new type, $A, but $A does not have field "1" which would be an |
| ;; error. We disallow subtyping for this reason. |
| ;; |
| ;; We also disallow subtyping in structural typing, even though atm there |
| ;; might not be a concrete risk there: future instructions might introduce |
| ;; such things, and it reduces the complexity of having differences with |
| ;; nominal typing. |
| (if (result i32) |
| (local.get $x) |
| (struct.get $B 1 |
| (ref.null $B) |
| ) |
| (struct.get $C 1 |
| (ref.null $C) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-non-null (param $struct (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $incompatible-cast-of-non-null (type $ref|$struct|_=>_none) (param $struct (ref $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $incompatible-cast-of-non-null (param $struct (ref $struct)) |
| (drop |
| (ref.cast_static $array |
| (local.get $struct) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-null |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $array) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $array) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $incompatible-cast-of-null (type $none_=>_none) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.null $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $array) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (block (result (ref null $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.null $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $array) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $incompatible-cast-of-null |
| (drop |
| (ref.cast_static $array |
| (ref.null $struct) |
| ) |
| ) |
| (drop |
| (ref.cast_static $array |
| ;; The fallthrough is null, but the node's child's type is non-nullable, |
| ;; so we must add a ref.as_non_null on the outside to keep the type |
| ;; identical. |
| (ref.as_non_null |
| (ref.null $struct) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $incompatible-cast-of-unknown (type $ref?|$struct|_=>_none) (param $struct (ref null $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) |
| (drop |
| (ref.cast_static $array |
| (local.get $struct) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test (param $struct (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $incompatible-test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $struct) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $incompatible-test (param $struct (ref null $struct)) |
| (drop |
| ;; This test will definitely fail, so we can turn it into 0. |
| (ref.test_static $array |
| (local.get $struct) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $B |
| ;; CHECK-NEXT: (local.get $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $A |
| ;; CHECK-NEXT: (local.get $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $subtype-compatible (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $B |
| ;; NOMNL-NEXT: (local.get $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $A |
| ;; NOMNL-NEXT: (local.get $B) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) |
| (drop |
| ;; B is a subtype of A, so this can work. |
| (ref.test_static $B |
| (local.get $A) |
| ) |
| ) |
| (drop |
| ;; The other direction works too. |
| (ref.test_static $A |
| (local.get $B) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref.test-unreachable (param $A (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $A |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref.test-unreachable (type $ref?|$A|_=>_none) (param $A (ref null $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $A |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref.test-unreachable (param $A (ref null $A)) |
| (drop |
| ;; We should ignore unreachable ref.tests and not try to compare their |
| ;; HeapTypes. |
| (ref.test_static $A |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $consecutive-opts-with-unreachable (param $func funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (block (result dataref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $consecutive-opts-with-unreachable (type $funcref_=>_none) (param $func funcref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (block (result dataref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $consecutive-opts-with-unreachable (param $func funcref) |
| (drop |
| (ref.cast_static $struct |
| ;; Casting a funcref to data will definitely fail, so this will be |
| ;; replaced with an unreachable. But it should be enclosed in a block of |
| ;; the previous type, so that the outside ref.cast is not confused. This |
| ;; is a regression test for a bug where we replace this node with an |
| ;; unreachable one, but we left refinalize til the end of all the other |
| ;; opts - and that meant that we got to our parent, the ref.cast, with |
| ;; one unreachable child but before it itself was refinalized, so its |
| ;; type was *not* unreachable yet, which meant it saw inconsistent IR |
| ;; that then led to an assertion. |
| (ref.as_data |
| (local.get $func) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-null |
| ;; CHECK-NEXT: (local $a (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-null (type $none_=>_none) |
| ;; NOMNL-NEXT: (local $a (ref null $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.null $B) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $B)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $B) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.tee $a |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.null $A) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-null |
| (local $a (ref null $A)) |
| ;; Casting nulls results in a null. |
| (drop |
| (ref.cast_static $A |
| (ref.null $A) |
| ) |
| ) |
| (drop |
| (ref.cast_static $A |
| (ref.null $B) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B |
| (ref.null $A) |
| ) |
| ) |
| ;; A fallthrough works too. |
| (drop |
| (ref.cast_static $A |
| (local.tee $a |
| (ref.null $A) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-impossible (param $func (ref func)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-impossible (type $ref|func|_=>_none) (param $func (ref func)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $func) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-impossible (param $func (ref func)) |
| ;; A func cannot be cast to a struct, so this will trap. |
| (drop |
| (ref.cast_static $struct |
| (local.get $func) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-general (type $ref?|$A|_ref?|$B|_=>_none) (param $a (ref null $A)) (param $b (ref null $B)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $a) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $b) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $a) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.tee $a |
| ;; NOMNL-NEXT: (local.get $a) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B)) |
| ;; In the general case, a static cast of something simply succeeds if the |
| ;; type is a subtype. |
| (drop |
| (ref.cast_static $A |
| (local.get $a) |
| ) |
| ) |
| (drop |
| (ref.cast_static $A |
| (local.get $b) |
| ) |
| ) |
| ;; This is the only one that we cannot know for sure will succeed. |
| (drop |
| (ref.cast_static $B |
| (local.get $a) |
| ) |
| ) |
| ;; A fallthrough works too. |
| (drop |
| (ref.cast_static $A |
| (local.tee $a |
| (local.get $a) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-squared (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-squared (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $A |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-squared (param $x eqref) |
| ;; Identical ref.casts can be folded together. |
| (drop |
| (ref.cast_static $A |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; When subtypes exist, we only need the stricter one. |
| (drop |
| (ref.cast_static $A |
| (ref.cast_static $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-many (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-many (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-many (param $x eqref) |
| ;; We should optimize a long sequence of static casts when we can. All six |
| ;; orderings of these casts should collapse into the strictest one. |
| (drop |
| (ref.cast_static $A |
| (ref.cast_static $B |
| (ref.cast_static $B-child |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $A |
| (ref.cast_static $B-child |
| (ref.cast_static $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B |
| (ref.cast_static $A |
| (ref.cast_static $B-child |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B |
| (ref.cast_static $B-child |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B-child |
| (ref.cast_static $A |
| (ref.cast_static $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $B-child |
| (ref.cast_static $B |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-very-many (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-very-many (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-very-many (param $x eqref) |
| ;; We should optimize an arbitrarily-long long sequence of static casts. |
| (drop |
| (ref.cast_static $A |
| (ref.cast_static $B |
| (ref.cast_static $B-child |
| (ref.cast_static $A |
| (ref.cast_static $A |
| (ref.cast_static $B-child |
| (ref.cast_static $B-child |
| (ref.cast_static $B |
| (ref.cast_static $B |
| (ref.cast_static $B |
| (ref.cast_static $B-child |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $B)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-fallthrough-remaining (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref null $B)) |
| ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining (param $x eqref) |
| (drop |
| (ref.cast_static $A |
| (block (result (ref null $B)) |
| ;; Additional contents in between redundant casts must be preserved. |
| ;; That is, when we see that the casts are redundant, by seeing that |
| ;; the fallthrough value reaching the outer cast is already cast, we |
| ;; can avoid a duplicate cast, but we do still need to keep any code |
| ;; in the middle, as it may have side effects. |
| ;; |
| ;; In this first testcase, the outer cast is not needed as the inside |
| ;; is already a more specific type. |
| (call $ref-cast-static-fallthrough-remaining |
| (local.get $x) |
| ) |
| (ref.cast_static $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-child (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast_static $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-fallthrough-remaining-child (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (block (result eqref) |
| ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining-child |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.cast_static $A |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-child (param $x eqref) |
| (drop |
| ;; As above, but with $A and $B flipped. Now the inner cast is not needed. |
| ;; However, we do not remove it, as it may be necessary for validation, |
| ;; and we hope other opts help out here. |
| (ref.cast_static $B |
| (block (result (eqref)) |
| (call $ref-cast-static-fallthrough-remaining-child |
| (local.get $x) |
| ) |
| (ref.cast_static $A |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-impossible (param $x (ref eq)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref eq)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining-impossible |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-fallthrough-remaining-impossible (type $ref|eq|_=>_none) (param $x (ref eq)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref eq)) |
| ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining-impossible |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-impossible (param $x (ref eq)) |
| (drop |
| ;; As above, but with an impossible cast of an array to a struct. The |
| ;; block with the side effects and the inner cast must be kept around and |
| ;; dropped, and then we replace the outer cast with an unreachable. |
| (ref.cast_static $array |
| (block (result (ref eq)) |
| (call $ref-cast-static-fallthrough-remaining-impossible |
| (local.get $x) |
| ) |
| (ref.cast_static $struct |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (param $x (ref eq)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $A |
| ;; CHECK-NEXT: (block (result (ref eq)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast_static $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-fallthrough-remaining-nonnull (type $ref|eq|_=>_none) (param $x (ref eq)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $A |
| ;; NOMNL-NEXT: (block (result (ref eq)) |
| ;; NOMNL-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (ref.cast_static $B |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-nonnull (param $x (ref eq)) |
| ;; The input is non-nullable here, and the middle block is of a simpler |
| ;; type than either the parent or the child. This checks that we do not |
| ;; mis-optimize this case: In general the outer cast is not needed, but |
| ;; the middle block prevents us from seeing that (after other opts run, |
| ;; however, we would). |
| (drop |
| (ref.cast_static $A |
| (block (result (ref eq)) |
| (call $ref-cast-static-fallthrough-remaining |
| (local.get $x) |
| ) |
| (ref.cast_static $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-squared-impossible (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast_static $struct |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.cast_static $array |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-cast-static-squared-impossible (type $eqref_=>_none) (param $x eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.cast_static $struct |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result (ref $struct)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.as_non_null |
| ;; NOMNL-NEXT: (ref.cast_static $array |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (unreachable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-cast-static-squared-impossible (param $x eqref) |
| ;; Impossible casts will trap unless the input is null. |
| (drop |
| (ref.cast_static $struct |
| (ref.cast_static $array |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast_static $struct |
| (ref.cast_static $array |
| (ref.as_non_null (local.get $x)) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $A |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-test-static-same-type (type $ref?|$A|_ref|$A|_=>_none) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $A |
| ;; NOMNL-NEXT: (local.get $nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $non-nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; A nullable value cannot be optimized here even though it is the same |
| ;; type. |
| (drop |
| (ref.test_static $A |
| (local.get $nullable) |
| ) |
| ) |
| ;; But if it is non-nullable, it must succeed. |
| (drop |
| (ref.test_static $A |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $A |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-test-static-subtype (type $ref?|$B|_ref|$B|_=>_none) (param $nullable (ref null $B)) (param $non-nullable (ref $B)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $A |
| ;; NOMNL-NEXT: (local.get $nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $non-nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 1) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B)) |
| ;; As above, but the input is a subtype, so the same things happen. |
| (drop |
| (ref.test_static $A |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test_static $A |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $B |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $B |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-test-static-supertype (type $ref?|$A|_ref|$A|_=>_none) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $B |
| ;; NOMNL-NEXT: (local.get $nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $B |
| ;; NOMNL-NEXT: (local.get $non-nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; As above, but the input is a supertype. We can't know at compile time |
| ;; what to do here. |
| (drop |
| (ref.test_static $B |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test_static $B |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-test-static-impossible (type $ref?|$array|_ref|$array|_=>_none) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (block (result i32) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (local.get $non-nullable) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (i32.const 0) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array)) |
| ;; Testing an impossible cast will definitely fail. |
| (drop |
| (ref.test_static $struct |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test_static $struct |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-boolean (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_func |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test_static $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; NOMNL: (func $ref-boolean (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.eq |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: (local.get $y) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.is_func |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: (drop |
| ;; NOMNL-NEXT: (ref.test_static $A |
| ;; NOMNL-NEXT: (local.get $x) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| ;; NOMNL-NEXT: ) |
| (func $ref-boolean (param $x eqref) (param $y eqref) |
| ;; ref.eq returns a boolean, so &1 on it is not needed. |
| (drop |
| (i32.and |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ;; likewise ref.is and ref.test |
| (drop |
| (i32.and |
| (ref.is_func |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| (drop |
| (i32.and |
| (ref.test_static $A |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |