;; 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 (module ;; CHECK: (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)))) (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))) (type $A (struct (field i32))) ;; CHECK: (type $B (struct_subtype (field i32) (field i32) (field f32) $A)) ;; CHECK: (type $array (array (mut i8))) (type $array (array (mut i8))) (type $B (struct_subtype (field i32) (field i32) (field f32) $A)) ;; CHECK: (type $void (func)) ;; CHECK: (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)) (type $empty (struct)) ;; CHECK: (type $void2 (func_subtype $void)) ;; CHECK: (type $C (struct_subtype (field i32) (field i32) (field f64) $A)) (type $C (struct_subtype (field i32) (field i32) (field f64) $A)) (type $void (func)) (type $void2 (func_subtype $void)) ;; CHECK: (type $struct_i64 (func (param structref) (result i64))) (type $struct_i64 (func (param (ref null struct)) (result i64))) ;; CHECK: (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 (type $none_=>_anyref) (result anyref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-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 (type $i31ref_=>_anyref) (param $i31ref i31ref) (result anyref) ;; CHECK-NEXT: (if (result anyref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $i31ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $if-arms-subtype-nofold (param $i31ref i31ref) (result anyref) (if (result anyref) (i32.const 0) (ref.null none) (local.get $i31ref) ) ) ;; Stored values automatically truncate unneeded bytes. ;; CHECK: (func $store-trunc (type $ref?|$struct|_=>_none) (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: ) (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 (type $ref?|$array|_=>_none) (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: ) (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 (type $ref|$struct|_ref|func|_ref|i31|_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) ;; 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 $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unneeded_is (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) (drop (ref.is_null (local.get $struct)) ) (drop (ref.is_func (local.get $func)) ) (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 (type $ref?|$struct|_funcref_i31ref_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) ;; 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 $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unneeded_is_null (param $struct (ref null $struct)) (param $func (ref null func)) (param $i31 (ref null i31)) (drop (ref.is_null (local.get $struct)) ) ;; This can be optimized to !is_null rather than ref.test func, since we ;; know the heap type is what we want, so the only possible issue is a null. (drop (ref.is_func (local.get $func)) ) ;; This can be optimized similarly. (drop (ref.is_i31 (local.get $i31)) ) ) ;; 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 (type $ref|$struct|_ref|func|_ref|i31|_=>_none) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) ;; 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 $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unneeded_as (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) (drop (ref.as_non_null (local.get $struct)) ) (drop (ref.as_func (local.get $func)) ) (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 (type $ref?|$struct|_funcref_i31ref_=>_none) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) ;; 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 $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unneeded_as_null (param $struct (ref null $struct)) (param $func (ref null func)) (param $i31 (ref null i31)) (drop (ref.as_non_null (local.get $struct)) ) (drop (ref.as_func (local.get $func)) ) (drop (ref.as_i31 (local.get $i31)) ) ) ;; CHECK: (func $unneeded_unreachability (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_func ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 (type $ref?|$struct|_ref?|$array|_ref?|$void|_=>_none) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) ;; 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 ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) (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) ) ) ) (call_ref $void (ref.as_non_null (local.get $f) ) ) ) ;; CHECK: (func $get-eqref (type $none_=>_eqref) (result eqref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $get-eqref (result eqref) (unreachable) ) ;; CHECK: (func $ref-eq (type $eqref_eqref_=>_none) (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: ) (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 (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $nothing) ;; CHECK: (func $ref-eq-corner-cases (type $eqref_=>_none) (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: ) (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 (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 null $struct (local.get $x) ) ) ) ) ;; CHECK: (func $flip-cast-of-as-non-null (type $anyref_=>_none) (param $x anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct $i8 ;; CHECK-NEXT: (ref.cast $struct ;; 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_i31 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $flip-cast-of-as-non-null (param $x anyref) (drop (ref.cast $struct ;; this can be folded into the outer cast, which checks for null too (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 $struct ;; this can be moved through the ref.cast null outward. (ref.as_non_null (local.get $x) ) ) ) ) ;; This will trap, so we can emit an unreachable. (drop (ref.cast $struct (ref.as_i31 (local.get $x) ) ) ) ) ;; CHECK: (func $flip-tee-of-as-non-null (type $anyref_=>_none) (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: ) (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 (type $ref|any|_anyref_=>_none) (param $x (ref any)) (param $y anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $flip-tee-of-as-non-null-non-nullable (param $x (ref any)) (param $y (ref null 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 (local.get $y) ) ) ) ) ;; CHECK: (func $ternary-identical-arms (type $i32_ref?|$struct|_ref?|$struct|_=>_none) (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: ) (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 (type $ref?|$struct|_ref?|$struct|_i32_=>_none) (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: ) (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 (type $ref|$struct|_ref|$struct|_i32_=>_none) (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: ) (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 (type $ref?|$struct|_ref?|$struct|_i32_=>_none) (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: ) (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 (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-squared (param $x eqref) ;; Identical ref.casts can be folded together. (drop (ref.cast null $struct (ref.cast null $struct (local.get $x) ) ) ) ) ;; CHECK: (func $ref-cast-squared-fallthrough (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-squared-fallthrough (param $x eqref) ;; A fallthrough in the middle does not prevent this optimization. (drop (ref.cast null $struct (local.tee $x (ref.cast null $struct (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-cubed (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-cubed (param $x eqref) ;; Three and more also work. (drop (ref.cast null $struct (ref.cast null $struct (ref.cast null $struct (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-squared-different (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-squared-different (param $x eqref) ;; Different casts cannot be folded. We can emit a cast to null here, which ;; is the only possible thing that can pass through. (drop (ref.cast null $struct (ref.cast null $empty (local.get $x) ) ) ) ) ;; CHECK: (func $ref-eq-null (type $eqref_=>_none) (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: ) (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 (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.cast null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast null $array ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 null $struct (local.get $x) ) (ref.cast null $array (local.get $y) ) ) ) ) ;; CHECK: (func $ref-eq-impossible (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $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 null $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $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 $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $array ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $struct (ref.as_non_null (local.get $x) ) ) (ref.cast null $array (local.get $y) ) ) ) ;; As above but the cast is on the other one. (drop (ref.eq (ref.cast null $struct (local.get $x) ) (ref.cast $array (ref.as_non_null (local.get $y) ) ) ) ) ;; As above but the cast is both. (drop (ref.eq (ref.cast $struct (ref.as_non_null (local.get $x) ) ) (ref.cast $array (ref.as_non_null (local.get $y) ) ) ) ) ) ;; CHECK: (func $ref-eq-possible-b (type $eqref_eqref_=>_none) (param $x eqref) (param $y eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.cast $A ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast $B ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.cast $B ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast $A ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $A (local.get $x) ) (ref.cast $B (local.get $y) ) ) ) ;; As above but flipped. (drop (ref.eq (ref.cast $B (local.get $x) ) (ref.cast $A (local.get $y) ) ) ) ) ;; CHECK: (func $hoist-LUB-danger (type $i32_ref|$B|_ref|$C|_=>_i32) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (struct.get $B 1 ;; CHECK-NEXT: (local.get $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $C 1 ;; CHECK-NEXT: (local.get $c) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $hoist-LUB-danger (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (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 (local.get $b) ) (struct.get $C 1 (local.get $c) ) ) ) ;; CHECK: (func $incompatible-cast-of-non-null (type $ref|$struct|_=>_none) (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: ) (func $incompatible-cast-of-non-null (param $struct (ref $struct)) (drop (ref.cast $array (local.get $struct) ) ) ) ;; CHECK: (func $incompatible-cast-of-null (type $ref?|$struct|_=>_none) (param $x (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $incompatible-cast-of-null (param $x (ref null $struct)) (drop (ref.cast $array ;; The child is null, so the cast will trap. Replace it with an ;; unreachable. (ref.null none) ) ) (drop (ref.cast $array ;; Even though the child type is non-null, it is still valid to do this ;; transformation. In practice this code will trap before getting to our ;; new unreachable. (ref.as_non_null (local.get $x) ) ) ) ) ;; CHECK: (func $incompatible-cast-of-unknown (type $ref?|$struct|_=>_none) (param $struct (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) (drop (ref.cast null $array (local.get $struct) ) ) ) ;; CHECK: (func $incompatible-test (type $ref?|$struct|_=>_none) (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: (drop ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.get $struct) ;; 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 $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $incompatible-test (param $struct (ref null $struct)) (drop ;; This test will definitely fail, so we can turn it into 0. (ref.test $array (local.get $struct) ) ) (drop ;; But this one might succeed due to a null, so don't optimize it away. ;; We can however change it from ref.test to ref.is_null, as a null is the ;; only possible way this will succeed. (ref.test null $array (local.get $struct) ) ) (drop ;; This one cannot succeed due to a null, so optimize it. (ref.test null $array (ref.as_non_null (local.get $struct) ) ) ) ) ;; CHECK: (func $subtype-compatible (type $ref?|$A|_ref?|$B|_=>_none) (param $A (ref null $A)) (param $B (ref null $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test $B ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; 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 $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $B (local.get $A) ) ) (drop ;; The other direction can work too. It will only fail if the input is a ;; null, so we can switch to checking that. (ref.test $A (local.get $B) ) ) (drop ;; If the test is nullable, this will succeed. (ref.test null $A (local.get $B) ) ) (drop ;; We will also succeed if the input is non-nullable. (ref.test $A (ref.as_non_null (local.get $B) ) ) ) (drop ;; Or if the test is nullable and the input is non-nullable. (ref.test null $A (ref.as_non_null (local.get $B) ) ) ) ) ;; CHECK: (func $ref.test-unreachable (type $ref?|$A|_=>_none) (param $A (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test $A ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test null $A ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $A (unreachable) ) ) (drop (ref.test null $A (unreachable) ) ) ) ;; CHECK: (func $ref-cast-static-null (type $void) ;; CHECK-NEXT: (local $a (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-static-null (local $a (ref null $A)) ;; Casting nulls results in a null. (drop (ref.cast null $A (ref.null none) ) ) ;; A fallthrough works too. (drop (ref.cast null $B (local.tee $a (ref.null none) ) ) ) ;; A non-null cast of a falling-though null will trap. (drop (ref.cast $A (local.tee $a (ref.null none) ) ) ) ;; The prior two examples work even if the fallthrough is only later proven ;; to be null. (drop (ref.cast null $B (block (result (ref null $A)) (ref.cast null none (local.get $a) ) ) ) ) (drop (ref.cast $B (block (result (ref null $A)) (ref.cast null none (local.get $a) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-general (type $ref?|$A|_ref?|$B|_=>_none) (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 null $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: ) (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 null $A (local.get $a) ) ) (drop (ref.cast null $A (local.get $b) ) ) ;; This is the only one that we cannot know for sure will succeed. (drop (ref.cast null $B (local.get $a) ) ) ;; A fallthrough works too. (drop (ref.cast null $A (local.tee $a (local.get $a) ) ) ) ) ;; CHECK: (func $ref-cast-static-squared (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $A ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-static-squared (param $x eqref) ;; Identical ref.casts can be folded together. (drop (ref.cast null $A (ref.cast null $A (local.get $x) ) ) ) ;; When subtypes exist, we only need the stricter one. (drop (ref.cast null $A (ref.cast null $B (local.get $x) ) ) ) (drop (ref.cast null $B (ref.cast null $A (local.get $x) ) ) ) ) ;; CHECK: (func $ref-cast-static-many (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 null $A (ref.cast null $B (ref.cast null $B-child (local.get $x) ) ) ) ) (drop (ref.cast null $A (ref.cast null $B-child (ref.cast null $B (local.get $x) ) ) ) ) (drop (ref.cast null $B (ref.cast null $A (ref.cast null $B-child (local.get $x) ) ) ) ) (drop (ref.cast null $B (ref.cast null $B-child (ref.cast null $A (local.get $x) ) ) ) ) (drop (ref.cast null $B-child (ref.cast null $A (ref.cast null $B (local.get $x) ) ) ) ) (drop (ref.cast null $B-child (ref.cast null $B (ref.cast null $A (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-very-many (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $B-child ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-static-very-many (param $x eqref) ;; We should optimize an arbitrarily-long long sequence of static casts. (drop (ref.cast null $A (ref.cast null $B (ref.cast null $B-child (ref.cast null $A (ref.cast null $A (ref.cast null $B-child (ref.cast null $B-child (ref.cast null $B (ref.cast null $B (ref.cast null $B (ref.cast null $B-child (ref.cast null $A (local.get $x) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-fallthrough-remaining (type $eqref_=>_none) (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 null $B ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-static-fallthrough-remaining (param $x eqref) (drop (ref.cast null $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 null $B (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-fallthrough-remaining-child (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null $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 null $A ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 null $B (block (result (eqref)) (call $ref-cast-static-fallthrough-remaining-child (local.get $x) ) (ref.cast null $A (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-fallthrough-remaining-impossible (type $ref|eq|_=>_none) (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 $struct ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $array (block (result (ref eq)) (call $ref-cast-static-fallthrough-remaining-impossible (local.get $x) ) (ref.cast $struct (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (type $ref|eq|_=>_none) (param $x (ref eq)) ;; CHECK-NEXT: (local $1 (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.tee $1 ;; CHECK-NEXT: (ref.cast $B ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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: The outer cast is not needed, so we can optimize ;; it out, but we have to be careful not to remove any side effects. (drop (ref.cast $A (block (result (ref eq)) (call $ref-cast-static-fallthrough-remaining (local.get $x) ) (ref.cast $B (local.get $x) ) ) ) ) ) ;; CHECK: (func $ref-cast-static-squared-impossible (type $eqref_=>_none) (param $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $array ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $array ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $array ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-static-squared-impossible (param $x eqref) ;; Impossible casts will trap unless the input is null. Only the first one ;; here, which lets a null get through, will not trap. (drop (ref.cast null $struct (ref.cast null $array (local.get $x) ) ) ) (drop (ref.cast $struct (ref.cast null $array (local.get $x) ) ) ) (drop (ref.cast null $struct (ref.cast $array (local.get $x) ) ) ) (drop (ref.cast $struct (ref.cast $array (ref.as_non_null (local.get $x)) ) ) ) ) ;; CHECK: (func $ref-test-static-same-type (type $ref?|$A|_ref|$A|_=>_none) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.get $nullable) ;; CHECK-NEXT: ) ;; 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: ) (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. But we can at least use !ref.is_null rather than ref.test. (drop (ref.test $A (local.get $nullable) ) ) ;; But if it is non-nullable, it must succeed. (drop (ref.test $A (local.get $non-nullable) ) ) ) ;; CHECK: (func $ref-test-static-subtype (type $ref?|$B|_ref|$B|_=>_none) (param $nullable (ref null $B)) (param $non-nullable (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.get $nullable) ;; CHECK-NEXT: ) ;; 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: ) (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 $A (local.get $nullable) ) ) (drop (ref.test $A (local.get $non-nullable) ) ) ) ;; CHECK: (func $ref-test-static-supertype (type $ref?|$A|_ref|$A|_=>_none) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test $B ;; CHECK-NEXT: (local.get $nullable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test $B ;; CHECK-NEXT: (local.get $non-nullable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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 $B (local.get $nullable) ) ) (drop (ref.test $B (local.get $non-nullable) ) ) ) ;; CHECK: (func $ref-test-static-impossible (type $ref?|$array|_ref|$array|_=>_none) (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: ) (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 $struct (local.get $nullable) ) ) (drop (ref.test $struct (local.get $non-nullable) ) ) ) ;; CHECK: (func $ref-boolean (type $eqref_eqref_=>_none) (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_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test $A ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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_null (local.get $x) ) (i32.const 1) ) ) (drop (i32.and (ref.test $A (local.get $x) ) (i32.const 1) ) ) ) ;; CHECK: (func $impossible (type $none_=>_ref|none|) (result (ref none)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $impossible (result (ref none)) (unreachable) ) ;; CHECK: (func $bottom-type-accessors (type $ref|none|_nullref_=>_none) (param $bot (ref none)) (param $null nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $impossible) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $bottom-type-accessors (param $bot (ref none)) (param $null nullref) (drop (struct.get $A 0 (local.get $bot) ) ) (drop (array.get $array (local.get $null) (i32.const 0) ) ) (struct.set $A 0 (ref.null none) (i32.const 42) ) (array.set $array (call $impossible) (i32.const 1) (i32.const 2) ) (call_ref $void (ref.null nofunc) ) ) ;; CHECK: (func $ref-cast-heap-type (type $ref?|$B|_ref|$B|_=>_none) (param $null-b (ref null $B)) (param $b (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $null-b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $null-b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-heap-type (param $null-b (ref null $B)) (param $b (ref $B)) ;; We are casting a heap type to a supertype, which always succeeds. However ;; we need to check for nullability. ;; Non-nullable casts. When the input is non-nullable we must succeed. (drop (ref.cast $A (local.get $b) ) ) ;; When the input can be null, we might fail if it is a null. But we can ;; switch to checking only that. (drop (ref.cast $A (local.get $null-b) ) ) ;; Null casts. Both of these must succeed. (drop (ref.cast null $A (local.get $b) ) ) (drop (ref.cast null $A (local.get $null-b) ) ) ) ;; CHECK: (func $ref-cast-heap-type-incompatible (type $ref?|$B|_ref|$B|_=>_none) (param $null-b (ref null $B)) (param $b (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $null-b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast null none ;; CHECK-NEXT: (local.get $null-b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref-cast-heap-type-incompatible (param $null-b (ref null $B)) (param $b (ref $B)) ;; As above, but replacing $A with $struct. Now we have two incompatible ;; types, $B and $struct, so the only possible way the cast succeeds is if ;; the cast allows null and the input is a null. (drop (ref.cast $struct (local.get $b) ) ) (drop (ref.cast $struct (local.get $null-b) ) ) (drop (ref.cast null $struct (local.get $b) ) ) ;; This last case is the only one that can succeed. We turn it into a cast ;; to a null. (drop (ref.cast null $struct (local.get $null-b) ) ) ) ;; CHECK: (func $as_of_unreachable (type $none_=>_ref|$A|) (result (ref $A)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $as_of_unreachable (result (ref $A)) ;; The cast will definitely fail, so we can turn it into an unreachable. The ;; ref.as must then ignore the unreachable input and not error on trying to ;; infer anything about it. (ref.as_non_null (ref.cast $A (ref.null none) ) ) ) ;; CHECK: (func $cast-internalized-extern (type $externref_=>_none) (param $externref externref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $A ;; CHECK-NEXT: (extern.internalize ;; CHECK-NEXT: (local.get $externref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $cast-internalized-extern (param $externref externref) ;; We cannot optimize this cast, and in particular we should not treat the ;; externref as falling through to the cast and incorrectly inferring that ;; the cast cannot succeed. (drop (ref.cast $A (extern.internalize (local.get $externref) ) ) ) ) ;; CHECK: (func $struct.set.null.fallthrough (type $void) ;; CHECK-NEXT: (local $temp (ref null $struct)) ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $temp ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct.set.null.fallthrough (local $temp (ref null $struct)) ;; The value falling through the tee shows the struct.set will trap. We can ;; append an unreachable after it. While doing so we must not emit a drop of ;; the struct.set (which would be valid for a struct.get etc.). (struct.set $struct 0 (local.tee $temp (ref.as_non_null (ref.null none) ) ) (i32.const 100) ) ) ;; CHECK: (func $set.array.null (type $void) ;; CHECK-NEXT: (local $temp (ref none)) ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $temp ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $set.array.null (local $temp (ref none)) ;; The cast of none will be inferred to be an unreachable. That does not ;; propagate through the tee during this pass, however, as it only ;; happens during the refinalize at the very end. We must be careful not to ;; hit an internal error while processing the array.set, as its reference's ;; fallthrough value has null type and not array type. (array.set $array (local.tee $temp (ref.as_non_null (ref.null none) ) ) (i32.const 2) (i32.const 3) ) ) ;; CHECK: (func $refinalize.select.arm (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $void2 ;; CHECK-NEXT: (ref.func $refinalize.select.arm) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm (type $void) ;; Pick one of the two select sides using the condition. This changes the ;; type (the arms are more refined than the declared type), so we must ;; refinalize or we'll error. (drop (ref.cast null $void2 (select (result (ref null $void)) (ref.func $refinalize.select.arm) (ref.func $refinalize.select.arm) (i32.const 1) ) ) ) ) ;; CHECK: (func $refinalize.select.arm.flip (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $void2 ;; CHECK-NEXT: (ref.func $refinalize.select.arm) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm.flip ;; Flipped of the above. (drop (ref.cast null $void2 (select (result (ref null $void)) (ref.func $refinalize.select.arm) (ref.func $refinalize.select.arm) (i32.const 0) ) ) ) ) ;; CHECK: (func $refinalize.select.arm.unknown (type $i32_=>_none) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast $void2 ;; CHECK-NEXT: (ref.func $refinalize.select.arm) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $refinalize.select.arm.unknown (param $x i32) ;; As above but use an unknown value at compile time for the condition. (drop (ref.cast null $void2 (select (result (ref null $void)) (ref.func $refinalize.select.arm) (ref.func $refinalize.select.arm) (local.get $x) ) ) ) ) ;; CHECK: (func $non-null-bottom-ref (type $none_=>_ref|func|) (result (ref func)) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $0 ;; CHECK-NEXT: (loop (result (ref nofunc)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $non-null-bottom-ref (result (ref func)) (local $0 (ref null func)) ;; The reference is uninhabitable, a non-null bottom type. The cast is not ;; even reached, but we need to be careful: The tee makes this a corner case ;; since it makes the type nullable again, so if we thought the cast would ;; succeed, and replaced the cast with its child, we'd fail to validate. ;; Instead, since the cast fails, we can replace it with an unreachable ;; (after the dropped child). (ref.cast func (local.tee $0 (loop (result (ref nofunc)) (unreachable) ) ) ) ) ;; CHECK: (func $non-null-bottom-cast (type $none_=>_ref|nofunc|) (result (ref nofunc)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $non-null-bottom-cast (result (ref nofunc)) ;; As above, but now the cast is uninhabitable. (ref.cast nofunc (ref.func $non-null-bottom-cast) ) ) ;; CHECK: (func $non-null-bottom-ref-test (type $none_=>_i32) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (local.tee $0 ;; CHECK-NEXT: (loop (result (ref nofunc)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $non-null-bottom-ref-test (result i32) (local $0 (ref null func)) ;; As above, but ref.test instead of cast. This is ok - we can turn the test ;; into a ref.is_null. TODO: if ref.test looked into intermediate casts ;; before it, it could do better. (ref.test func (local.tee $0 (loop (result (ref nofunc)) (unreachable) ) ) ) ) ;; CHECK: (func $non-null-bottom-ref-test-notee (type $none_=>_i32) (result i32) ;; CHECK-NEXT: (local $0 funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop (result (ref nofunc)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $non-null-bottom-ref-test-notee (result i32) (local $0 (ref null func)) ;; As above, but without an intermediate local.tee. Now ref.test will see ;; that it is unreachable, as the input is uninhabitable. (ref.test func (loop (result (ref nofunc)) (unreachable) ) ) ) ;; CHECK: (func $non-null-bottom-test (type $none_=>_i32) (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $non-null-bottom-test (result i32) ;; As above, but now the cast type is uninhabitable, and also use ref.test. ;; This cast cannot succeed, so return 0. (ref.test nofunc (ref.func $non-null-bottom-cast) ) ) ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $none_=>_i32) (result i32) ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.test-then-optimizeAddedConstants (result i32) ;; The cast will become unreachable, and then the test as well. We should ;; not error in the subsequent optimizeAddedConstants function that will be ;; called on the adds. The risk here is that that code does not expect an ;; unreachable to appear inside a non-unreachable add, which can happen as ;; we delay updating types til the end of the pass. To avoid that, the ;; ref.test should not change its type, but only replace itself with a block ;; containing an unreachable (but declared as the old type; leaving ;; optimizing it further for other passes). (i32.add (i32.const 1) (i32.add (i32.const 2) (ref.test func (ref.cast func (ref.null nofunc) ) ) ) ) ) ;; CHECK: (func $gc_to_unreachable_in_added_constants (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.wrap_i64 ;; CHECK-NEXT: (i64.add ;; CHECK-NEXT: (call $struct_i64_helper ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i64.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $gc_to_unreachable_in_added_constants (drop (i32.wrap_i64 (i64.add (i64.const 1) (i64.add (i64.const 1) (call_ref $struct_i64 ;; This will turn into an unreachable. That should not cause an ;; error in later i64 add optimizations (optimizeAddedConstants), ;; which should not assume this is an i64-typed expression. (ref.as_non_null (ref.null none) ) (ref.func $struct_i64_helper) ) ) ) ) ) ) ;; CHECK: (func $struct_i64_helper (type $struct_i64) (param $0 structref) (result i64) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $struct_i64_helper (type $struct_i64) (param $0 (ref null struct)) (result i64) (unreachable) ) ;; CHECK: (func $array-copy-non-null (type $ref?|$array|_=>_none) (param $x (ref null $array)) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (array.copy $array $array ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (br $block) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $array-copy-non-null (param $x (ref null $array)) (block $block (array.copy $array $array ;; This cast cannot be removed: while the array.copy will trap anyhow ;; if $x is null, we might branch out in the if, so removing a trap ;; here could be noticeable. (ref.as_non_null (local.get $x) ) (if (result i32) (i32.const 1) (br $block) (i32.const 10) ) ;; There are no tricky effects after this, so this cast can be removed. (ref.as_non_null (local.get $x) ) (i32.const 42) (i32.const 1337) ) ) ) )