;; 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)))) ;; CHECK: (type $array (array (mut i8))) ;; CHECK: (import "env" "get-i32" (func $get-i32 (result i32))) (import "env" "get-i32" (func $get-i32 (result i32))) (type $empty (struct)) (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)) )) (type $array (array (mut i8))) ;; 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 extern) ;; CHECK-NEXT: ) (func $if-arms-subtype-fold (result anyref) (if (result anyref) (i32.const 0) (ref.null extern) (ref.null extern) ) ) ;; 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 extern) ;; CHECK-NEXT: (ref.null func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $if-arms-subtype-nofold (result anyref) (if (result anyref) (i32.const 0) (ref.null extern) (ref.null func) ) ) ;; 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: ) (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: ) (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: ) (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: ) (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: ) (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: ) (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: ) (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 ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $data) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $data) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $i31) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-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: ) (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: ) (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: ) (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: ) (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: ) (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_with_rtt $struct ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new_default_with_rtt $struct ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; 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_with_rtt $struct (rtt.canon $struct) ) (struct.new_default_with_rtt $struct (rtt.canon $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_with_rtt $struct (rtt.canon $struct) ) ) (local.get $x) ) (block (result eqref) (drop (struct.new_default_with_rtt $struct (rtt.canon $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) ) ) ) ) ;; 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 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (rtt.canon $struct) ;; 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 (local.get $x) (rtt.canon $struct) ) ) ) ) ;; CHECK: (func $flip-cast-of-as-non-null (param $x anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.cast ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $struct $i8 ;; CHECK-NEXT: (ref.cast ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast ;; CHECK-NEXT: (ref.as_func ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast ;; CHECK-NEXT: (ref.as_data ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast ;; CHECK-NEXT: (ref.as_i31 ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rtt.canon $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $flip-cast-of-as-non-null (param $x anyref) (drop (ref.cast ;; this can be moved through the ref.cast outward. (ref.as_non_null (local.get $x) ) (rtt.canon $struct) ) ) (drop ;; an example of how this helps: the struct.get will trap on null anyhow (struct.get_u $struct 0 (ref.cast ;; this can be moved through the ref.cast outward. (ref.as_non_null (local.get $x) ) (rtt.canon $struct) ) ) ) ;; other ref.as* operations are ignored for now (drop (ref.cast (ref.as_func (local.get $x) ) (rtt.canon $struct) ) ) (drop (ref.cast (ref.as_data (local.get $x) ) (rtt.canon $struct) ) ) (drop (ref.cast (ref.as_i31 (local.get $x) ) (rtt.canon $struct) ) ) ) ;; 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: ) (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) ) (rtt.canon $struct) ) ) ) ;; 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: ) (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: ) (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: ) (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: ) (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) ) ) ) ) )