;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s (module ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $2 (func (result (ref $struct)))) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (type $4 (func (result (ref any)))) ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $no-non-null (type $4) (result (ref any)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $no-non-null (result (ref any)) ;; The only possible value at the location of this ref.as_non_null is a ;; null - but that value does not have a compatible type (null is nullable). ;; Therefore we can infer that nothing is possible here, and the code must ;; trap, and we'll optimize this to an unreachable. (ref.as_non_null (ref.null any) ) ) ;; CHECK: (func $nested (type $3) (result i32) ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $nested (result i32) ;; As above, but add other instructions on the outside, which can also be ;; replaced with an unreachable (except for the loop, which as a control ;; flow structure with a name we keep it around and just add an unreachable ;; after it; and for now we don't optimize ref.is* so that stays). (ref.is_null (loop $loop (result (ref func)) (nop) (ref.cast (ref func) (ref.as_non_null (ref.null func) ) ) ) ) ) ;; CHECK: (func $yes-non-null (type $4) (result (ref any)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $yes-non-null (result (ref any)) ;; Similar to the above but now there *is* an non-null value here, so there ;; is nothing for us to optimize or change here. (ref.as_non_null (struct.new $struct) ) ) ;; CHECK: (func $breaks (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref $struct)) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $breaks ;; Check that we notice values sent along breaks. We should optimize ;; nothing in the first block here. (drop (block $block (result (ref any)) (br $block (struct.new $struct) ) ) ) ;; But here we send a null so we can optimize to an unreachable. (drop (ref.as_non_null (block $block2 (result (ref null any)) (br $block2 (ref.null $struct) ) ) ) ) ) ;; CHECK: (func $get-nothing (type $2) (result (ref $struct)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $get-nothing (result (ref $struct)) ;; This function returns a non-nullable struct by type, but does not ;; actually return a value in practice, and our whole-program analysis ;; should pick that up in optimizing the callers (but nothing changes here). (unreachable) ) ;; CHECK: (func $get-nothing-calls (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $get-nothing-calls ;; This can be optimized since the call does not actually return any ;; possible content (it has an unreachable), which means we can optimize ;; away the call's value - we must keep it around in a drop, since it can ;; have side effects, but the drop ignores the value which we do not need. (drop (call $get-nothing) ) ;; As above, add another instruction in the middle. We can optimize it to ;; an unreachable, like the call. (drop (ref.as_non_null (call $get-nothing) ) ) ;; As above, but we do not optimize ref.is_null yet so nothing happens for ;; it (but the call still gets optimized as before). (drop (ref.is_null (call $get-nothing) ) ) ) ;; CHECK: (func $two-inputs (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $two-inputs ;; As above, but now the outer instruction is a select, and some of the arms ;; may have a possible type - we check all 4 permutations. Only in the ;; case where both inputs are nothing can we optimize away the select (that ;; is, drop it and ignore its value), as only then will the select never ;; have any contents. ;; (Note: we are not fully optimal here since we could notice that the ;; select executes both arms unconditionally, so if one traps then it will ;; all trap.) (drop (select (result (ref any)) (struct.new $struct) (call $get-nothing) (call $import) ) ) (drop (select (result (ref any)) (call $get-nothing) (struct.new $struct) (call $import) ) ) (drop (select (result (ref any)) (struct.new $struct) (struct.new $struct) (call $import) ) ) (drop (select (result (ref any)) (call $get-nothing) (call $get-nothing) (call $import) ) ) ) ;; CHECK: (func $get-something-flow (type $2) (result (ref $struct)) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) (func $get-something-flow (result (ref $struct)) ;; Return a value by flowing it out. Helper for later code. (struct.new $struct) ) ;; CHECK: (func $get-something-return (type $2) (result (ref $struct)) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $get-something-return (result (ref $struct)) ;; Return a value using an explicit return. Helper for later code. (return (struct.new $struct) ) ) ;; CHECK: (func $call-get-something (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-something-flow) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-something-return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call-get-something ;; In both of these cases a value is actually returned and there is nothing ;; to optimize, unlike get-nothing from above. (drop (call $get-something-flow) ) (drop (call $get-something-return) ) ) ;; CHECK: (func $locals (type $1) ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local $y anyref) ;; CHECK-NEXT: (local $z anyref) ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $get-nothing) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $z ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $locals (local $x (ref null any)) (local $y (ref null any)) (local $z (ref null any)) ;; Assign to x from a call that actually will not return anything. We will ;; be able to optimize away the call's return value (drop it) and append an ;; unreachable. (local.set $x (call $get-nothing) ) ;; Never assign to y. ;; Assign to z an actual value. (local.set $z (struct.new $struct) ) ;; Get the 3 locals, to check that we optimize. We can replace x with an ;; unreachable and y with a null constant. (drop (local.get $x) ) (drop (local.get $y) ) (drop (local.get $z) ) ) ) (module ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (global $null anyref (ref.null none)) (global $null (ref null any) (ref.null any)) ;; CHECK: (global $something anyref (struct.new_default $struct)) (global $something (ref null any) (struct.new $struct)) ;; CHECK: (global $mut-null (mut anyref) (ref.null none)) (global $mut-null (mut (ref null any)) (ref.null any)) ;; CHECK: (global $mut-something (mut anyref) (ref.null none)) (global $mut-something (mut (ref null any)) (ref.null any)) ;; CHECK: (func $read-globals (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $mut-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $read-globals ;; This global has no possible contents aside from a null, which we can ;; infer and place here. (drop (global.get $null) ) ;; This global has no possible contents aside from a null, so the ;; ref.as_non_null can be optimized to an unreachable (since a null is not ;; compatible with its non-nullable type). (drop (ref.as_non_null (global.get $null) ) ) ;; This global has a possible non-null value (in the initializer), so there ;; is nothing to do. (drop (ref.as_non_null (global.get $something) ) ) ;; This mutable global has a write aside from the initializer, but it is ;; also of a null, so we can optimize here. (drop (ref.as_non_null (global.get $mut-null) ) ) ;; This one also has a later write, of a non-null value, so there is nothing ;; to do. (drop (ref.as_non_null (global.get $mut-something) ) ) ) ;; CHECK: (func $write-globals (type $1) ;; CHECK-NEXT: (global.set $mut-null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $mut-something ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $write-globals (global.set $mut-null (ref.null $struct) ) (global.set $mut-something (struct.new $struct) ) ) ) ;; As above, but now with a chain of globals: A starts with a value, which is ;; copied to B, and then C, and then C is read. We will be able to optimize ;; away *-null (which is where A-null starts with null) but not *-something ;; (which is where A-something starts with a value). (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (global $A-null anyref (ref.null none)) (global $A-null (ref null any) (ref.null any)) ;; CHECK: (global $A-something anyref (struct.new_default $struct)) (global $A-something (ref null any) (struct.new $struct)) ;; CHECK: (global $B-null (mut anyref) (ref.null none)) (global $B-null (mut (ref null any)) (ref.null any)) ;; CHECK: (global $B-something (mut anyref) (ref.null none)) (global $B-something (mut (ref null any)) (ref.null any)) ;; CHECK: (global $C-null (mut anyref) (ref.null none)) (global $C-null (mut (ref null any)) (ref.null any)) ;; CHECK: (global $C-something (mut anyref) (ref.null none)) (global $C-something (mut (ref null any)) (ref.null any)) ;; CHECK: (func $read-globals (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $A-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $B-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $C-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $read-globals (drop (ref.as_non_null (global.get $A-null) ) ) (drop (ref.as_non_null (global.get $A-something) ) ) (drop (ref.as_non_null (global.get $B-null) ) ) (drop (ref.as_non_null (global.get $B-something) ) ) (drop (ref.as_non_null (global.get $C-null) ) ) (drop (ref.as_non_null (global.get $C-something) ) ) ) ;; CHECK: (func $write-globals (type $0) ;; CHECK-NEXT: (global.set $B-null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $C-null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $B-something ;; CHECK-NEXT: (global.get $A-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $C-something ;; CHECK-NEXT: (global.get $B-something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $write-globals (global.set $B-null (global.get $A-null) ) (global.set $C-null (global.get $B-null) ) (global.set $B-something (global.get $A-something) ) (global.set $C-something (global.get $B-something) ) ) ) (module ;; CHECK: (type $0 (func (param (ref any)) (result (ref any)))) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $2 (func (param i32) (result i32))) ;; CHECK: (type $3 (func (param (ref any) (ref any) (ref any)))) ;; CHECK: (type $4 (func)) ;; CHECK: (func $never-called (type $2) (param $x i32) (result i32) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $never-called (param $x i32) (result i32) ;; This function is never called, so the parameter has no possible contents, ;; and we can optimize to an unreachable. (local.get $x) ) ;; CHECK: (func $never-called-ref (type $0) (param $x (ref any)) (result (ref any)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $never-called-ref (param $x (ref any)) (result (ref any)) ;; As above but with a reference type. Again, we can apply an unreachable. (local.get $x) ) ;; CHECK: (func $recursion (type $0) (param $x (ref any)) (result (ref any)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $recursion ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $recursion (param $x (ref any)) (result (ref any)) ;; This function calls itself recursively. That forms a loop, but still, ;; nothing reaches here, so we can optimize to an unreachable (we cannot ;; remove the call though, as it has effects, so we drop it). (call $recursion (local.get $x) ) ) ;; CHECK: (func $called (type $3) (param $x (ref any)) (param $y (ref any)) (param $z (ref any)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref any)) (param $y (ref any)) (param $z (ref any)) ;; This function is called, with possible (non-null) values in the 1st & 3rd ;; params, but nothing can arrive in the 2nd, which we can optimize to an ;; unreachable. (drop (local.get $x) ) (drop (local.get $y) ) (drop (local.get $z) ) ) ;; CHECK: (func $call-called (type $4) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call-called ;; Call the above function as described there: Nothing can arrive in the ;; second param (since we cast a null to non-null there), while the others ;; have both a null and a non-null (different in the 2 calls here). (With ;; more precise analysis we could see that the ref.as must trap, and we ;; could optimize even more here.) (call $called (struct.new $struct) (ref.as_non_null (ref.null any) ) (ref.as_non_null (ref.null any) ) ) (call $called (ref.as_non_null (ref.null any) ) (ref.as_non_null (ref.null any) ) (struct.new $struct) ) ) ) ;; As above, but using indirect calls. (module ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $two-params (func (param (ref $struct) (ref $struct)))) (type $two-params (func (param (ref $struct)) (param (ref $struct)))) ;; CHECK: (type $three-params (func (param (ref $struct) (ref $struct) (ref $struct)))) (type $three-params (func (param (ref $struct)) (param (ref $struct)) (param (ref $struct)))) (table 10 funcref) (elem (i32.const 0) funcref (ref.func $func-2params-a) (ref.func $func-2params-b) (ref.func $func-3params) ) ;; CHECK: (table $0 10 funcref) ;; CHECK: (elem $0 (i32.const 0) $func-2params-a $func-2params-b $func-3params) ;; CHECK: (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_indirect $0 (type $two-params) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) ;; Only null is possible for the first, so we can optimize it to an ;; unreachable. (drop (local.get $x) ) (drop (local.get $y) ) ;; Send a value only to the second param. (call_indirect (type $two-params) (ref.as_non_null (ref.null $struct) ) (struct.new $struct) (i32.const 0) ) ) ;; CHECK: (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) ;; Another function with the same signature as before, which we should ;; optimize in the same way: the indirect call can go to either. (drop (local.get $x) ) (drop (local.get $y) ) ) ;; CHECK: (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_indirect $0 (type $three-params) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_indirect $0 (type $three-params) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct)) (drop (local.get $x) ) (drop (local.get $y) ) (drop (local.get $z) ) ;; Send a non-null value only to the first and third param. Do so in two ;; separate calls. The second param, $y, can be optimized. (call_indirect (type $three-params) (struct.new $struct) (ref.as_non_null (ref.null $struct) ) (ref.as_non_null (ref.null $struct) ) (i32.const 0) ) (call_indirect (type $three-params) (ref.as_non_null (ref.null $struct) ) (ref.as_non_null (ref.null $struct) ) (struct.new $struct) (i32.const 0) ) ) ) ;; As above, but using call_ref. (module ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $two-params (func (param (ref $struct) (ref $struct)))) (type $two-params (func (param (ref $struct)) (param (ref $struct)))) ;; CHECK: (elem declare func $func-2params-a) ;; CHECK: (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $two-params ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (ref.func $func-2params-a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) (drop (local.get $x) ) (drop (local.get $y) ) ;; Send a non-null value only to the second param. (call_ref $two-params (ref.as_non_null (ref.null $struct) ) (struct.new $struct) (ref.func $func-2params-a) ) ) ) ;; Array creation. (module ;; CHECK: (type $vector (array (mut f64))) (type $vector (array (mut f64))) ;; CHECK: (type $1 (func)) ;; CHECK: (func $arrays (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (array.new $vector ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (array.new_default $vector ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (array.new_fixed $vector 2 ;; CHECK-NEXT: (f64.const 1.1) ;; CHECK-NEXT: (f64.const 2.2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $arrays (drop (ref.as_non_null (array.new $vector (f64.const 3.14159) (i32.const 1) ) ) ) (drop (ref.as_non_null (array.new_default $vector (i32.const 100) ) ) ) (drop (ref.as_non_null (array.new_fixed $vector 2 (f64.const 1.1) (f64.const 2.2) ) ) ) ;; In the last case we have no possible non-null value and can optimize to ;; an unreachable. (drop (ref.as_non_null (ref.null $vector) ) ) ) ) ;; Struct fields. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (sub (struct ))) (type $struct (sub (struct))) ;; CHECK: (type $parent (sub (struct (field (mut (ref null $struct)))))) (type $parent (sub (struct (field (mut (ref null $struct)))))) ;; CHECK: (type $child (sub $parent (struct (field (mut (ref null $struct))) (field (mut (ref null $struct)))))) (type $child (sub $parent (struct (field (mut (ref null $struct))) (field (mut (ref null $struct)))))) ;; CHECK: (type $unrelated (struct )) (type $unrelated (struct)) ) ;; CHECK: (type $4 (func)) ;; CHECK: (func $func (type $4) ;; CHECK-NEXT: (local $child (ref null $child)) ;; CHECK-NEXT: (local $parent (ref null $parent)) ;; CHECK-NEXT: (local.set $child ;; CHECK-NEXT: (struct.new $child ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (struct.new $parent ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $parent (result (ref none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $parent (ref $unrelated) (ref none) ;; CHECK-NEXT: (struct.new_default $unrelated) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (local $child (ref null $child)) (local $parent (ref null $parent)) ;; We create a child with a non-null value in field 0 and null in 1. (local.set $child (struct.new $child (struct.new $struct) (ref.null $struct) ) ) ;; Getting field 0 should not be optimized or changed in any way. (drop (struct.get $child 0 (local.get $child) ) ) ;; Field one can be optimized into a null constant (+ a drop of the get). (drop (struct.get $child 1 (local.get $child) ) ) ;; Create a parent with a null. The child wrote to the shared field, but ;; using exact type info we can infer that the get's value must be a null, ;; so we can optimize. (local.set $parent (struct.new $parent (ref.null $struct) ) ) (drop (struct.get $parent 0 (local.get $parent) ) ) ;; An unrelated type is cast to a struct type, and then we read from that. ;; The cast will trap at runtime, of course; for here, we should not error ;; and also we can optimize these to unreachables. atm we filter out ;; trapping contents in ref.cast, but not br_on_cast, so test both. (drop (struct.get $parent 0 (ref.cast (ref $parent) (struct.new $unrelated) ) ) ) (drop (struct.get $parent 0 (block $parent (result (ref $parent)) (drop (br_on_cast $parent anyref (ref $parent) (struct.new $unrelated) ) ) (unreachable) ) ) ) ) ;; CHECK: (func $nulls (type $4) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result nullref) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block0 (result nullref) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block1 (result nullref) ;; CHECK-NEXT: (br $block1 ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $nulls ;; Leave null constants alone. (drop (ref.null $parent) ) ;; Reading from a null reference is easy to optimize - it will trap. (drop (struct.get $parent 0 (ref.null $parent) ) ) ;; Send a null to the block, which is the only value exiting, so we can ;; optimize here. (drop (block $block (result (ref null any)) (br $block (ref.null any) ) (unreachable) ) ) ;; Send a more specific type. We should emit a valid null constant (but in ;; this case, a null of either $parent or $child would be ok). (drop (block $block (result (ref null $parent)) (br $block (ref.null $child) ) (unreachable) ) ) ;; Send a less specific type, via a cast. But all nulls are identical and ;; ref.cast null passes nulls through, so this is ok, but we must be careful to ;; emit a ref.null $child on the outside (to not change the outer type to a ;; less refined one). (drop (block $block (result (ref null $child)) (br $block (ref.cast (ref null $child) (ref.null $parent) ) ) (unreachable) ) ) ) ) ;; Default values in struct fields. (module (rec (type $A (sub (struct (field i32)))) (type $B (sub (struct (field i32)))) (type $C (sub (struct (field i32)))) ) ;; CHECK: (type $0 (func)) ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; Create a struct with default values. We can propagate a 0 to the get. (drop (struct.get $A 0 (struct.new_default $A) ) ) ;; Allocate with a non-default value, that can also be propagated. (drop (struct.get $B 0 (struct.new $B (i32.const 1) ) ) ) ) ) ;; Exact types: Writes to the parent class do not confuse us. (module ;; CHECK: (type $struct (sub (struct ))) (type $struct (sub (struct))) ;; CHECK: (type $parent (sub (struct (field (mut (ref null $struct)))))) (type $parent (sub (struct (field (mut (ref null $struct)))))) ;; CHECK: (type $child (sub $parent (struct (field (mut (ref null $struct))) (field i32)))) (type $child (sub $parent (struct (field (mut (ref null $struct))) (field i32)))) ;; CHECK: (type $3 (func)) ;; CHECK: (func $func (type $3) ;; CHECK-NEXT: (local $child (ref null $child)) ;; CHECK-NEXT: (local $parent (ref null $parent)) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (struct.new $parent ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $child ;; CHECK-NEXT: (struct.new $child ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (local $child (ref null $child)) (local $parent (ref null $parent)) ;; Allocate when writing to the parent's field. (local.set $parent (struct.new $parent (struct.new $struct) ) ) ;; This cannot be optimized in any way. (drop (ref.as_non_null (struct.get $parent 0 (local.get $parent) ) ) ) ;; The child writes a null to the first field. (local.set $child (struct.new $child (ref.null $struct) (i32.const 0) ) ) ;; The parent wrote to the shared field, but that does not prevent us from ;; seeing that the child must have a null there, and so this will trap. (drop (ref.as_non_null (struct.get $child 0 (local.get $child) ) ) ) ) ) ;; Write values to the parent *and* the child and read from the child. (module ;; CHECK: (type $parent (sub (struct (field (mut i32))))) (type $parent (sub (struct (field (mut i32))))) ;; CHECK: (type $child (sub $parent (struct (field (mut i32)) (field i32)))) (type $child (sub $parent (struct (field (mut i32)) (field i32)))) ;; CHECK: (type $2 (func)) ;; CHECK: (func $func (type $2) ;; CHECK-NEXT: (local $child (ref null $child)) ;; CHECK-NEXT: (local $parent (ref null $parent)) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (struct.new $parent ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $child ;; CHECK-NEXT: (struct.new $child ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (local $child (ref null $child)) (local $parent (ref null $parent)) (local.set $parent (struct.new $parent (i32.const 10) ) ) ;; This can be optimized to 10. The child also sets this field, but the ;; reference in the local $parent can only be a $parent and nothing else. (drop (struct.get $parent 0 (local.get $parent) ) ) (local.set $child (struct.new $child ;; The value here conflicts with the parent's for this field, but the ;; local $child can only contain a $child and nothing else, so we can ;; optimize the get below us. (i32.const 20) (i32.const 30) ) ) (drop (struct.get $child 0 (local.get $child) ) ) ;; This get aliases nothing but 30, so we can optimize. (drop (struct.get $child 1 (local.get $child) ) ) ) ) ;; As above, but the $parent local can now contain a child too. (module ;; CHECK: (type $parent (sub (struct (field (mut i32))))) (type $parent (sub (struct (field (mut i32))))) ;; CHECK: (type $child (sub $parent (struct (field (mut i32)) (field i32)))) (type $child (sub $parent (struct (field (mut i32)) (field i32)))) ;; CHECK: (type $2 (func (param i32))) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $2) (param $x i32) ;; CHECK-NEXT: (local $child (ref null $child)) ;; CHECK-NEXT: (local $parent (ref null $parent)) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (struct.new $parent ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (local.tee $child ;; CHECK-NEXT: (struct.new $child ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (export "func") (param $x i32) (local $child (ref null $child)) (local $parent (ref null $parent)) (local.set $parent (struct.new $parent (i32.const 10) ) ) ;; Another, optional, set to $parent. (if (local.get $x) (then (local.set $parent (local.tee $child (struct.new $child (i32.const 20) (i32.const 30) ) ) ) ) ) ;; This get cannot be optimized because before us the local might be set a ;; child as well. So the local $parent can refer to either type, and they ;; disagree on the aliased value. (drop (struct.get $parent 0 (local.get $parent) ) ) ;; But this one can be optimized as $child can only contain a child. (drop (struct.get $child 0 (local.get $child) ) ) ) ) ;; As above, but now the parent and child happen to agree on the aliased value. (module ;; CHECK: (type $parent (sub (struct (field (mut i32))))) (type $parent (sub (struct (field (mut i32))))) ;; CHECK: (type $child (sub $parent (struct (field (mut i32)) (field i32)))) (type $child (sub $parent (struct (field (mut i32)) (field i32)))) ;; CHECK: (type $2 (func)) ;; CHECK: (func $func (type $2) ;; CHECK-NEXT: (local $child (ref null $child)) ;; CHECK-NEXT: (local $parent (ref null $parent)) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (struct.new $parent ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $parent ;; CHECK-NEXT: (local.tee $child ;; CHECK-NEXT: (struct.new $child ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (local $child (ref null $child)) (local $parent (ref null $parent)) (local.set $parent (struct.new $parent (i32.const 10) ) ) (drop (struct.get $parent 0 (local.get $parent) ) ) (local.set $parent (local.tee $child (struct.new $child (i32.const 10) ;; This is 10, like above, so we can optimize the get ;; before us. (i32.const 30) ) ) ) (drop (struct.get $child 0 (local.get $child) ) ) ) ) ;; Arrays get/set (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $nothing (sub (array (mut anyref)))) (type $nothing (sub (array (mut (ref null any))))) ;; CHECK: (type $null (sub (array (mut anyref)))) (type $null (sub (array (mut (ref null any))))) ;; CHECK: (type $something (sub (array (mut anyref)))) (type $something (sub (array (mut (ref null any))))) ;; CHECK: (type $something-child (sub $something (array (mut anyref)))) (type $something-child (sub $something (array (mut (ref null any))))) ) ;; CHECK: (type $4 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (func $func (type $4) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $null ;; CHECK-NEXT: (array.new_default $null ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $something ;; CHECK-NEXT: (array.new_default $something ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (array.get $something ;; CHECK-NEXT: (array.new_default $something ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; Reading from a null will trap, and we can optimize to an unreachable. (drop (array.get $nothing (ref.null $nothing) (i32.const 0) ) ) ;; Write a null to this array. (array.set $null (array.new_default $null (i32.const 10) ) (i32.const 0) (ref.null any) ) ;; We can only read a null here, so this will trap and can be optimized. (drop (ref.as_non_null (array.get $null (array.new_default $null (i32.const 10) ) (i32.const 0) ) ) ) ;; In $something we do actually write a non-null value, so we cannot add ;; unreachables here. (array.set $something (array.new_default $something (i32.const 10) ) (i32.const 0) (struct.new $struct) ) (drop (ref.as_non_null (array.get $something (array.new_default $something (i32.const 10) ) (i32.const 0) ) ) ) ;; $something-child has nothing written to it, but its parent does. Still, ;; with exact type info that does not confuse us, and we can optimize to an ;; unreachable. (drop (ref.as_non_null (array.get $something-child (ref.cast (ref $something-child) (array.new_default $something (i32.const 10) ) ) (i32.const 0) ) ) ) ) ) ;; A big chain, from an allocation that passes through many locations along the ;; way before it is used. Nothing here can be optimized. (module ;; CHECK: (type $storage (struct (field (mut anyref)))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) (type $storage (struct (field (mut (ref null any))))) ;; CHECK: (type $3 (func (param anyref) (result anyref))) ;; CHECK: (global $x (mut anyref) (ref.null none)) (global $x (mut (ref null any)) (ref.null any)) ;; CHECK: (func $foo (type $1) ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (struct.get $storage 0 ;; CHECK-NEXT: (struct.new $storage ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (global.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (local $x (ref null any)) ;; Allocate a non-null value and pass it through a local. (local.set $x (struct.new $struct) ) ;; Pass it through a global. (global.set $x (local.get $x) ) ;; Pass it through a call, then write it to a struct, then read it from ;; there, and coerce to non-null which we would optimize if the value were ;; only a null. But it is not a null, and no optimizations happen here. (drop (ref.as_non_null (struct.get $storage 0 (struct.new $storage (call $pass-through (global.get $x) ) ) ) ) ) ) ;; CHECK: (func $pass-through (type $3) (param $x anyref) (result anyref) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) (func $pass-through (param $x (ref null any)) (result (ref null any)) (local.get $x) ) ) ;; As above, but the chain is turned into a loop, replacing the initial ;; allocation with a get from the end. We can optimize such cycles. (module (type $struct (struct)) ;; CHECK: (type $0 (func)) ;; CHECK: (type $storage (struct (field (mut anyref)))) (type $storage (struct (field (mut (ref null any))))) ;; CHECK: (type $2 (func (param anyref) (result anyref))) ;; CHECK: (global $x (mut anyref) (ref.null none)) (global $x (mut (ref null any)) (ref.null any)) ;; CHECK: (func $foo (type $0) ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (local $x (ref null any)) ;; Replace the initial allocation with a read from the global. That is ;; written to lower down, forming a loop - a loop with no actual allocation ;; anywhere, so we can infer the possible values are only a null. (local.set $x (global.get $x) ) (global.set $x (struct.get $storage 0 (struct.new $storage (call $pass-through (local.get $x) ) ) ) ) (drop (ref.as_non_null (global.get $x) ) ) ) ;; CHECK: (func $pass-through (type $2) (param $x anyref) (result anyref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $pass-through (param $x (ref null any)) (result (ref null any)) (local.get $x) ) ) ;; A single long chain as above, but now we break the chain in the middle by ;; adding a non-null value. (module ;; CHECK: (type $storage (struct (field (mut anyref)))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) (type $storage (struct (field (mut (ref null any))))) ;; CHECK: (type $3 (func (param anyref) (result anyref))) ;; CHECK: (global $x (mut anyref) (ref.null none)) (global $x (mut (ref null any)) (ref.null any)) ;; CHECK: (func $foo (type $1) ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (global.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x ;; CHECK-NEXT: (struct.get $storage 0 ;; CHECK-NEXT: (struct.new $storage ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (global.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (local $x (ref null any)) (local.set $x (global.get $x) ) (global.set $x (struct.get $storage 0 (struct.new $storage (call $pass-through ;; The only change is to allocate here instead of reading the local ;; $x. This causes us to not optimize anything in this function. (struct.new $struct) ) ) ) ) (drop (ref.as_non_null (global.get $x) ) ) ) ;; CHECK: (func $pass-through (type $3) (param $x anyref) (result anyref) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) (func $pass-through (param $x (ref null any)) (result (ref null any)) (local.get $x) ) ) ;; Exceptions. (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $1 (func (param anyref))) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (tag $nothing (param anyref)) (tag $nothing (param (ref null any))) ;; CHECK: (tag $something (param anyref)) (tag $something (param (ref null any))) ;; CHECK: (tag $empty) (tag $empty (param)) ;; CHECK: (func $func (type $0) ;; CHECK-NEXT: (local $0 anyref) ;; CHECK-NEXT: (throw $nothing ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try $try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $nothing ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (pop anyref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (throw $something ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try $try0 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $something ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (pop anyref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; This tag receives no non-null value, so we can optimize the pop of it, ;; in the next try-catch, to an unreachable. (throw $nothing (ref.null $struct) ) (try (do) (catch $nothing (drop (ref.as_non_null (pop (ref null any)) ) ) ) ) ;; This tag cannot be optimized as we send it something. (throw $something (struct.new $struct) ) (try (do) (catch $something (drop (ref.as_non_null (pop (ref null any)) ) ) ) ) ) ;; CHECK: (func $empty-tag (type $0) ;; CHECK-NEXT: (try $label$3 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $empty-tag ;; Check we do not error on catching an empty tag. (try $label$3 (do (nop) ) (catch $empty (nop) ) ) ) ;; CHECK: (func $try-results (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (try $try (result i32) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (try $try1 (result i32) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (try $try2 (result i32) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (try $try3 (result i32) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $empty ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch_all ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $try-results ;; If all values flowing out are identical, we can optimize. That is only ;; the case in the very first try. (drop (try (result i32) (do (i32.const 0) ) (catch $empty (i32.const 0) ) (catch_all (i32.const 0) ) ) ) ;; If any of the values is changed, we cannot. (drop (try (result i32) (do (i32.const 42) ) (catch $empty (i32.const 0) ) (catch_all (i32.const 0) ) ) ) (drop (try (result i32) (do (i32.const 0) ) (catch $empty (i32.const 42) ) (catch_all (i32.const 0) ) ) ) (drop (try (result i32) (do (i32.const 0) ) (catch $empty (i32.const 0) ) (catch_all (i32.const 42) ) ) ) ) ) ;; Exceptions with a tuple (module ;; CHECK: (type $0 (func (param anyref anyref))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (tag $tag (param anyref anyref)) (tag $tag (param (ref null any)) (param (ref null any))) ;; CHECK: (func $func (type $1) ;; CHECK-NEXT: (local $0 (tuple anyref anyref)) ;; CHECK-NEXT: (throw $tag ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try $try ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (try $try0 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (tuple.extract 2 1 ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; This tag receives a null in the first parameter. (throw $tag (ref.null $struct) (struct.new $struct) ) ;; Catch the first, which we can optimize to a null. (try (do) (catch $tag (drop (tuple.extract 2 0 (pop (tuple (ref null any) (ref null any))) ) ) ) ) ;; Catch the second, which we cannot optimize. (try (do) (catch $tag (drop (tuple.extract 2 1 (pop (tuple (ref null any) (ref null any))) ) ) ) ) ) ) (module ;; CHECK: (type $"{}" (sub (struct ))) (type $"{}" (sub (struct))) ;; CHECK: (type $1 (func (result (ref $"{}")))) ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $func (result (ref $"{}")) ;; This block can only return a null in theory (in practice, not even that - ;; the br will not be taken, but this pass is not smart enough to see that). ;; We can optimize to an unreachable here, but must be careful - we cannot ;; remove the block as the wasm would not validate (not unless we also ;; removed the br, which we don't do atm). All we will do is add an ;; unreachable after the block, on the outside of it (which would help other ;; passes do more work). (block $block (result (ref $"{}")) (br_on_non_null $block (ref.null $"{}") ) (unreachable) ) ) ) (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $A (sub (struct (field i32)))) (type $A (sub (struct (field i32)))) ;; CHECK: (type $B (sub (struct (field i64)))) (type $B (sub (struct (field i64)))) ;; CHECK: (type $C (sub (struct (field f32)))) (type $C (sub (struct (field f32)))) ;; CHECK: (type $D (sub (struct (field f64)))) (type $D (sub (struct (field f64)))) ;; CHECK: (func $many-types (type $0) ;; CHECK-NEXT: (local $x anyref) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i64.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (f32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (struct.new $D ;; CHECK-NEXT: (f64.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $many-types (local $x (ref null any)) ;; Write 4 different types into $x. That should not confuse us, and we ;; should not make any changes in this function. (local.set $x (struct.new $A (i32.const 0) ) ) (local.set $x (struct.new $B (i64.const 1) ) ) (local.set $x (struct.new $C (f32.const 2) ) ) (local.set $x (struct.new $D (f64.const 3) ) ) (drop (ref.as_non_null (local.get $x) ) ) ) ) ;; Test a vtable-like pattern. This tests ref.func values flowing into struct ;; locations being properly noticed, both from global locations (the global's ;; init) and a function ($create). (module ;; CHECK: (type $vtable-A (sub (struct (field funcref) (field funcref) (field funcref)))) (type $vtable-A (sub (struct (field (ref null func)) (field (ref null func)) (field (ref null func))))) ;; CHECK: (type $1 (func)) ;; CHECK: (global $global-A (ref $vtable-A) (struct.new $vtable-A ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: )) (global $global-A (ref $vtable-A) (struct.new $vtable-A (ref.func $foo) (ref.null func) (ref.func $foo) ) ) ;; CHECK: (elem declare func $foo $test) ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $vtable-A 2 ;; CHECK-NEXT: (global.get $global-A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test ;; The first item here contains a fixed value (ref.func $foo) in both the ;; global init and in the function $create, which we can apply. (drop (struct.get $vtable-A 0 (global.get $global-A) ) ) ;; The second item here contains a null in all cases, which we can also ;; apply. (drop (struct.get $vtable-A 1 (global.get $global-A) ) ) ;; The third item has more than one possible value, due to the function ;; $create later down, so we cannot optimize. (drop (struct.get $vtable-A 2 (global.get $global-A) ) ) ) ;; CHECK: (func $create (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $vtable-A ;; CHECK-NEXT: (ref.func $foo) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: (ref.func $test) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $create (drop (struct.new $vtable-A (ref.func $foo) (ref.null func) (ref.func $test) ) ) ) ;; CHECK: (func $foo (type $1) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $foo) ) (module ;; CHECK: (type $struct (sub (struct (field i32)))) (type $struct (sub (struct (field i32)))) ;; CHECK: (type $1 (func)) ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (local $ref (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (block (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (local $ref (ref null $struct)) ;; Regression test for an assertion firing in this case. We should properly ;; handle the multiple intermediate blocks here, allowing us to optimize the ;; get below to a 42. (local.set $ref (block (result (ref $struct)) (block (result (ref $struct)) (struct.new $struct (i32.const 42) ) ) ) ) (drop (struct.get $struct 0 (local.get $ref) ) ) ) ) ;; Casts. (module ;; CHECK: (type $struct (sub (struct (field i32)))) (type $struct (sub (struct (field i32)))) ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field i32)))) (type $substruct (sub $struct (struct (field i32) (field i32)))) ;; CHECK: (type $2 (func)) ;; CHECK: (type $subsubstruct (sub $substruct (struct (field i32) (field i32) (field i32)))) (type $subsubstruct (sub $substruct (struct (field i32) (field i32) (field i32)))) ;; CHECK: (type $4 (func (param i32))) ;; CHECK: (type $other (sub (struct ))) (type $other (sub (struct))) ;; CHECK: (type $6 (func (result i32))) ;; CHECK: (type $7 (func (param i32 (ref null $struct) (ref null $struct) (ref null $other) (ref $struct) (ref $struct) (ref $other)))) ;; CHECK: (type $8 (func (result (ref eq)))) ;; CHECK: (import "a" "b" (func $import (type $6) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (export "test-cones" (func $test-cones)) ;; CHECK: (export "ref.test-inexact" (func $ref.test-inexact)) ;; CHECK: (export "ref.eq-zero" (func $ref.eq-zero)) ;; CHECK: (export "ref.eq-unknown" (func $ref.eq-unknown)) ;; CHECK: (export "ref.eq-cone" (func $ref.eq-cone)) ;; CHECK: (export "local-no" (func $ref.eq-local-no)) ;; CHECK: (func $test (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $substruct) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $subsubstruct) ;; CHECK-NEXT: (struct.new $subsubstruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test ;; The cast here will fail, and the ref.cast null allows nothing through, so we ;; can emit an unreachable here. (drop (ref.cast (ref $substruct) (struct.new $struct (i32.const 0) ) ) ) ;; This cast of a type to itself can succeed (in fact, it will), so we make ;; no changes here. (drop (ref.cast (ref $substruct) (struct.new $substruct (i32.const 1) (i32.const 2) ) ) ) ;; This cast of a subtype will also succeed. As above, we make no changes. (drop (ref.cast (ref $substruct) (struct.new $subsubstruct (i32.const 3) (i32.const 4) (i32.const 5) ) ) ) ) ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast nullref ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (select (result (ref null $struct)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-nulls ;; Only a null can flow through the cast, which we can infer for the value ;; of the cast. (drop (ref.cast (ref null $struct) (select (ref.null $struct) (ref.null $struct) (call $import) ) ) ) ;; A null or an i31 will reach the cast; only the null can actually pass ;; through (an i31 would fail the cast). Given that, we can infer a null for ;; the value of the cast. (The cast itself will also be turned into a cast ;; to null, but it is dropped right before we return a null, so that has no ;; benefit in this case.) (drop (ref.cast (ref null $struct) (select (ref.null $struct) (ref.i31 (i32.const 0)) (call $import) ) ) ) ;; A null or a $struct may arrive, and so we cannot do anything here. (drop (ref.cast (ref null $struct) (select (ref.null $struct) (struct.new $struct (i32.const 6) ) (call $import) ) ) ) ) ;; CHECK: (func $test-cones (type $4) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (select (result (ref null $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $struct) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $substruct) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test-cones (export "test-cones") (param $x i32) ;; The input to the ref.cast null is potentially null, so we cannot infer here. (drop (ref.cast (ref null $struct) (select (struct.new $struct (i32.const 0) ) (ref.null any) (local.get $x) ) ) ) ;; The input to the ref.cast is either $struct or $substruct, both of which ;; work, so we cannot optimize anything here away. (drop (ref.cast (ref $struct) (select (struct.new $struct (i32.const 1) ) (struct.new $substruct (i32.const 2) (i32.const 3) ) (local.get $x) ) ) ) ;; As above, but now we test with $substruct, so one possibility fails and ;; one succeeds. We cannot infer here either. (drop (ref.cast (ref $substruct) (select (struct.new $struct (i32.const 4) ) (struct.new $substruct (i32.const 5) (i32.const 6) ) (local.get $x) ) ) ) ;; Two possible types, both are supertypes, so neither is a subtype, and we ;; can infer an unreachable. The combination of these two is a cone from ;; $struct of depth 1, which does not overlap with $subsubstruct. (drop (ref.cast (ref $subsubstruct) (select (struct.new $struct (i32.const 7) ) (struct.new $substruct (i32.const 8) (i32.const 9) ) (local.get $x) ) ) ) ) ;; CHECK: (func $ref.test-exact (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; 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.test-exact ;; This cast will fail: we know the exact type of the reference, and it is ;; not a subtype. (drop (ref.test (ref $substruct) (struct.new $struct (i32.const 0) ) ) ) ;; Casting a thing to itself must succeed. (drop (ref.test (ref $substruct) (struct.new $substruct (i32.const 1) (i32.const 2) ) ) ) ;; Casting a thing to a supertype must succeed. (drop (ref.test (ref $substruct) (struct.new $subsubstruct (i32.const 3) (i32.const 4) (i32.const 5) ) ) ) ) ;; CHECK: (func $ref.test-inexact (type $4) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $struct) ;; CHECK-NEXT: (select (result (ref null $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $substruct) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.test-inexact (export "ref.test-inexact") (param $x i32) ;; The input to the ref.test is potentially null, so we cannot infer here. (drop (ref.test (ref $struct) (select (struct.new $struct (i32.const 0) ) (ref.null any) (local.get $x) ) ) ) ;; The input to the ref.test is either $struct or $substruct, both of which ;; work, so here we can infer a 1. We do so using a cone type: the ;; combination of those two types is a cone on $struct of depth 1, and that ;; cone is 100% a subtype of $struct, so the test will succeed. (drop (ref.test (ref $struct) (select (struct.new $struct (i32.const 1) ) (struct.new $substruct (i32.const 2) (i32.const 3) ) (local.get $x) ) ) ) ;; As above, but now we test with $substruct, so one possibility fails and ;; one succeeds. We cannot infer here. (drop (ref.test (ref $substruct) (select (struct.new $struct (i32.const 4) ) (struct.new $substruct (i32.const 5) (i32.const 6) ) (local.get $x) ) ) ) ;; Two possible types, both are supertypes, so neither is a subtype, and we ;; can infer a 0. The combination of these two is a cone from $struct of ;; depth 1, which does not overlap with $subsubstruct. (drop (ref.test (ref $subsubstruct) (select (struct.new $struct (i32.const 7) ) (struct.new $substruct (i32.const 8) (i32.const 9) ) (local.get $x) ) ) ) ) ;; CHECK: (func $ref.eq-zero (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.eq-zero (export "ref.eq-zero") ;; We do not track specific references, so only the types can be used here. ;; Using the types, we can infer that two different ExactTypes cannot ;; contain the same reference, so we infer a 0. (drop (ref.eq (struct.new $struct (i32.const 1) ) (struct.new $substruct (i32.const 2) (i32.const 3) ) ) ) ;; A null and a non-null reference cannot be identical, so we infer 0. (drop (ref.eq (ref.null $struct) (struct.new $struct (i32.const 5) ) ) ) (drop (ref.eq (struct.new $struct (i32.const 5) ) (ref.null $struct) ) ) ) ;; CHECK: (func $ref.eq-unknown (type $7) (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (local.get $struct2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (local.get $nn-struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $nn-struct) ;; CHECK-NEXT: (local.get $nn-struct2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.eq-unknown (export "ref.eq-unknown") (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other)) ;; Here we cannot infer as the type is identical. (Though, if we used more ;; than the type, we could see they cannot be identical.) (drop (ref.eq (struct.new $struct (i32.const 4) ) (struct.new $struct (i32.const 5) ) ) ) ;; These nulls are identical, so we could infer 1, but we leave that for ;; other passes, and do not infer here. (drop (ref.eq (ref.null $struct) (ref.null $struct) ) ) ;; When nulls are possible, we cannot infer anything (with or without the ;; same type on both sides). (drop (ref.eq (local.get $struct) (local.get $other) ) ) (drop (ref.eq (local.get $struct) (local.get $struct2) ) ) ;; A null is only possible on one side, but the same non-null value could be ;; on both. (drop (ref.eq (local.get $struct) (local.get $nn-struct) ) ) ;; The type is identical, and non-null, but we don't know if the value is ;; the same or not. (drop (ref.eq (local.get $nn-struct) (local.get $nn-struct2) ) ) ;; Non-null on both sides, and incompatible types. We can infer 0 here. (drop (ref.eq (local.get $nn-struct) (local.get $nn-other) ) ) ;; We can ignore unreachable code. (drop (ref.eq (ref.null $struct) (unreachable) ) ) ;; The called function here traps and never returns an actual value, which ;; will lead to an unreachable emitted right after the call. We should not ;; prevent that from happening: an unreachable must be emitted (we will also ;; emit an i32.const 0, which will never be reached, and not cause issues). (drop (ref.eq (ref.null $struct) (call $unreachable) ) ) ) ;; CHECK: (func $ref.eq-cone (type $4) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $subsubstruct ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (select (result (ref $struct)) ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (select (result (ref $substruct)) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $subsubstruct ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: (i32.const 7) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (select (result (ref $substruct)) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.eq-cone (export "ref.eq-cone") (param $x i32) ;; One side has two possible types, so we have a cone there. This cone is ;; of subtypes of the other type, which is exact, so we cannot intersect ;; here and we infer a 0. (drop (ref.eq (struct.new $struct (i32.const 1) ) (select (struct.new $substruct (i32.const 2) (i32.const 3) ) (struct.new $subsubstruct (i32.const 4) (i32.const 5) (i32.const 6) ) (local.get $x) ) ) ) ;; Now the cone is large enough, so there might be an intersection, and we ;; do not optimize (the cone of $struct and $subsubstruct contains ;; $substruct which is in the middle). (drop (ref.eq (struct.new $substruct (i32.const 1) (i32.const 2) ) (select (struct.new $struct (i32.const 3) ) (struct.new $subsubstruct (i32.const 4) (i32.const 5) (i32.const 6) ) (local.get $x) ) ) ) (drop (ref.eq (struct.new $substruct (i32.const 1) (i32.const 2) ) (select (struct.new $struct (i32.const 3) ) (struct.new $substruct ;; As above, but with this changed. We still (i32.const 4) ;; cannot optimize. (i32.const 5) ) (local.get $x) ) ) ) (drop (ref.eq (struct.new $substruct (i32.const 1) (i32.const 2) ) (select (struct.new $substruct ;; As above, but with this changed. We still (i32.const 3) ;; cannot optimize. (i32.const 4) ) (struct.new $subsubstruct (i32.const 5) (i32.const 6) (i32.const 7) ) (local.get $x) ) ) ) (drop (ref.eq (struct.new $substruct (i32.const 1) (i32.const 2) ) (select (struct.new $substruct (i32.const 3) (i32.const 4) ) (struct.new $substruct ;; As above, but with this changed. We still (i32.const 5) ;; cannot optimize (here the type is actually (i32.const 6) ;; exact, despite the select). ) (local.get $x) ) ) ) ) ;; CHECK: (func $unreachable (type $8) (result (ref eq)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $unreachable (result (ref eq)) (unreachable) ) ;; CHECK: (func $ref.eq-updates (type $2) ;; CHECK-NEXT: (local $x eqref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.eq-updates (local $x (ref null eq)) ;; The local.get will be optimized to a ref.null. After that we will leave ;; the ref.eq as it is. This guards against a possible bug of us not ;; setting the contents of the new ref.null expression just created: the ;; parent ref.eq will query the contents right after adding that expression, ;; and the contents must be set or else we'll think nothing is possible ;; there. ;; ;; (We could optimize ref.eq of two nulls to 1, but we leave that for other ;; passes.) (drop (ref.eq (ref.null eq) (local.get $x) ) ) ;; Another situation we need to be careful with effects of updates. Here ;; we have a block whose result we can infer to a null, but that does not ;; let us optimize the ref.eq, and we also must be careful to not drop side ;; effects - the call must remain. (drop (ref.eq (block (result eqref) (drop (call $import) ) (ref.null $struct) ) (ref.null $struct) ) ) ) ;; CHECK: (func $ref.eq-local-no (type $4) (param $x i32) ;; CHECK-NEXT: (local $ref (ref $struct)) ;; CHECK-NEXT: (local $ref-null (ref null $struct)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $ref-null ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (local.get $ref-null) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $ref.eq-local-no (export "local-no") (param $x i32) (local $ref (ref $struct)) (local $ref-null (ref null $struct)) ;; Always set the non-nullable ref, but only sometimes set the nullable. (local.set $ref (struct.new $struct (i32.const 0) ) ) (if (local.get $x) (then (local.set $ref-null (local.get $ref) ) ) ) ;; If the |if| executed they are equal, but otherwise not, so we can't ;; optimize. (drop (ref.eq (local.get $ref) (local.get $ref-null) ) ) ) ) ;; Test ref.eq on globals. (module ;; CHECK: (type $A (sub (struct (field i32)))) (type $A (sub (struct (field i32)))) (type $B (sub $A (struct (field i32)))) ;; CHECK: (type $1 (func)) ;; CHECK: (global $a (ref $A) (struct.new $A ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: )) (global $a (ref $A) (struct.new $A (i32.const 0) )) ;; CHECK: (global $a-other (ref $A) (struct.new $A ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: )) (global $a-other (ref $A) (struct.new $A (i32.const 1) )) ;; CHECK: (global $a-copy (ref $A) (global.get $a)) (global $a-copy (ref $A) (global.get $a)) ;; CHECK: (global $a-mut (mut (ref $A)) (struct.new $A ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: )) (global $a-mut (mut (ref $A)) (struct.new $A (i32.const 2) )) ;; CHECK: (global $a-mut-copy (mut (ref $A)) (global.get $a)) (global $a-mut-copy (mut (ref $A)) (global.get $a)) ;; CHECK: (global $a-mut-copy-written (mut (ref $A)) (global.get $a)) (global $a-mut-copy-written (mut (ref $A)) (global.get $a)) ;; CHECK: (func $compare-a (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a-other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a-mut) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $a-mut-copy-written ;; CHECK-NEXT: (global.get $a-other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (global.get $a) ;; CHECK-NEXT: (global.get $a-mut-copy-written) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $compare-a ;; Comparisons of $a to everything else. ;; ;; GUFA does not compute the results of these yet, as it leaves it to other ;; passes. This test guards against us doing anything unexpected here. ;; ;; What we do change here is update a copied global to the original, ;; so $a-copy will turn into $a (because that is the only value it can ;; contain). That should happen for the first three only. (For the 3rd, it ;; works even though it is mutable, since there is only a single write ;; anywhere.) (drop (ref.eq (global.get $a) (global.get $a) ) ) (drop (ref.eq (global.get $a) (global.get $a-copy) ) ) (drop (ref.eq (global.get $a) (global.get $a-mut-copy) ) ) (drop (ref.eq (global.get $a) (global.get $a-other) ) ) (drop (ref.eq (global.get $a) (global.get $a-mut) ) ) (global.set $a-mut-copy-written (global.get $a-other) ) (drop (ref.eq (global.get $a) (global.get $a-mut-copy-written) ) ) ) ) (module (type $A (sub (struct (field i32)))) (type $B (sub (struct (ref $A)))) (type $C (sub (struct (ref $B)))) ;; CHECK: (type $0 (func)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test ;; Test nested struct.get operations. We can optimize all this into the ;; constant 42. (drop (struct.get $A 0 (struct.get $B 0 (struct.get $C 0 (struct.new $C (struct.new $B (struct.new $A (i32.const 42) ) ) ) ) ) ) ) ) ) (module ;; CHECK: (type $A (sub (struct (field i32)))) (type $A (sub (struct (field i32)))) ;; CHECK: (type $B (sub (struct (field (ref $A))))) (type $B (sub (struct (ref $A)))) ;; CHECK: (type $C (sub (struct (field (ref $B))))) (type $C (sub (struct (ref $B)))) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (type $4 (func)) ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $test (type $4) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (struct.get $C 0 ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test ;; As above, but now call an import for the i32; we cannot optimize. (drop (struct.get $A 0 (struct.get $B 0 (struct.get $C 0 (struct.new $C (struct.new $B (struct.new $A (call $import) ) ) ) ) ) ) ) ) ) ;; ref.as* test. (module ;; CHECK: (type $A (sub (struct (field i32)))) (type $A (sub (struct (field i32)))) ;; CHECK: (type $B (sub $A (struct (field i32) (field f64)))) (type $B (sub $A (struct (field i32) (field f64)))) ;; CHECK: (type $2 (func (result i32))) ;; CHECK: (type $3 (func (result (ref $B)))) ;; CHECK: (import "a" "b" (func $import (type $2) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $foo (type $3) (result (ref $B)) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.tee $A ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (f64.const 13.37) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (result (ref $B)) (local $A (ref null $A)) ;; Read the following from the most nested comment first. (ref.cast (ref $B) ;; if we mistakenly think this contains content of ;; type $A, it would trap, but it should not, and we ;; have nothing to optimize here (ref.as_non_null ;; also $B, based on the child's *contents* (not type!) (local.tee $A ;; flows out a $B, but has type $A (struct.new $B ;; returns a $B (i32.const 42) (f64.const 13.37) ) ) ) ) ) ) (module ;; CHECK: (type $A (sub (struct (field i32)))) (type $A (sub (struct (field i32)))) ;; CHECK: (type $1 (func (result i32))) ;; CHECK: (type $B (sub $A (struct (field i32) (field i32)))) (type $B (sub $A (struct (field i32) (field i32)))) ;; CHECK: (func $0 (type $1) (result i32) ;; CHECK-NEXT: (local $ref (ref null $A)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $0 (result i32) (local $ref (ref null $A)) (local.set $ref (struct.new $B (i32.const 0) (i32.const 1) ) ) ;; This struct.get has a reference of type $A, but we can infer the type ;; present in the reference must actually be a $B, and $B precisely - no ;; sub or supertypes. So we can infer a value of 0. ;; ;; A possible bug that this is a regression test for is a confusion between ;; the type of the content and the declared type. If we mixed them up and ;; thought this must be precisely an $A and not a $B then we'd emit an ;; unreachable here (since no $A is ever allocated). (struct.get $A 0 (local.get $ref) ) ) ) ;; array.copy between types. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $bytes (array (mut anyref))) (type $bytes (array (mut anyref))) ;; CHECK: (type $chars (array (mut anyref))) (type $chars (array (mut anyref))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (func $test (type $2) ;; CHECK-NEXT: (local $bytes (ref null $bytes)) ;; CHECK-NEXT: (local $chars (ref null $chars)) ;; CHECK-NEXT: (local.set $bytes ;; CHECK-NEXT: (array.new_fixed $bytes 1 ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $chars ;; CHECK-NEXT: (array.new_fixed $chars 1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.copy $chars $bytes ;; CHECK-NEXT: (local.get $chars) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $bytes) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $bytes ;; CHECK-NEXT: (local.get $bytes) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (local $bytes (ref null $bytes)) (local $chars (ref null $chars)) ;; Write something to $bytes, but just a null to $chars. But then do a copy ;; which means two things are possible in $chars, and we can't optimize ;; there. (local.set $bytes (array.new_fixed $bytes 1 (ref.i31 (i32.const 0)) ) ) (local.set $chars (array.new_fixed $chars 1 (ref.null any) ) ) (array.copy $chars $bytes (local.get $chars) (i32.const 0) (local.get $bytes) (i32.const 0) (i32.const 1) ) (drop (array.get $bytes (local.get $bytes) (i32.const 0) ) ) (drop (array.get $chars (local.get $chars) (i32.const 0) ) ) ) ) ;; As above, but with a copy in the opposite direction. Now $chars has a single ;; value (a null) which we can optimize, but $bytes has two values and we ;; cannot optimize there. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $bytes (array (mut anyref))) (type $bytes (array (mut anyref))) ;; CHECK: (type $chars (array (mut anyref))) (type $chars (array (mut anyref))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (func $test (type $2) ;; CHECK-NEXT: (local $bytes (ref null $bytes)) ;; CHECK-NEXT: (local $chars (ref null $chars)) ;; CHECK-NEXT: (local.set $bytes ;; CHECK-NEXT: (array.new_fixed $bytes 1 ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $chars ;; CHECK-NEXT: (array.new_fixed $chars 1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.copy $bytes $chars ;; CHECK-NEXT: (local.get $bytes) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (local.get $chars) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $bytes ;; CHECK-NEXT: (local.get $bytes) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (local $bytes (ref null $bytes)) (local $chars (ref null $chars)) (local.set $bytes (array.new_fixed $bytes 1 (ref.i31 (i32.const 0)) ) ) (local.set $chars (array.new_fixed $chars 1 (ref.null any) ) ) (array.copy $bytes $chars (local.get $bytes) (i32.const 0) (local.get $chars) (i32.const 0) (i32.const 1) ) (drop (array.get $bytes (local.get $bytes) (i32.const 0) ) ) (drop (array.get $chars (local.get $chars) (i32.const 0) ) ) ) ) ;; Basic tests for all instructions appearing in possible-contents.cpp but not ;; already shown above. If we forgot to add the proper links to any of them, ;; they might appear as if no content were possible there, and we'd emit an ;; unreachable. That should not happen anywhere here. (module (type $A (sub (struct))) ;; CHECK: (type $0 (func)) ;; CHECK: (type $B (array (mut anyref))) (type $B (array (mut anyref))) ;; CHECK: (type $2 (func (param i32))) ;; CHECK: (type $3 (func (param (ref $B)))) ;; CHECK: (memory $0 10) ;; CHECK: (table $t 0 externref) ;; CHECK: (tag $e-i32 (param i32)) (tag $e-i32 (param i32)) (memory $0 10) (table $t 0 externref) ;; CHECK: (func $br_table (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $A (result i32) ;; CHECK-NEXT: (br_table $A $A ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $br_table (drop ;; The value 1 can be inferred here. (block $A (result i32) (br_table $A $A (i32.const 1) (i32.const 2) ) ) ) ) ;; CHECK: (func $memory (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.load ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.atomic.rmw.add ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.atomic.rmw.cmpxchg ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i32.const 15) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.atomic.wait32 ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i64.const 15) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.atomic.notify ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.size) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (memory.grow ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $memory (drop (i32.load (i32.const 5) ) ) (drop (i32.atomic.rmw.add (i32.const 5) (i32.const 10) ) ) (drop (i32.atomic.rmw.cmpxchg (i32.const 5) (i32.const 10) (i32.const 15) ) ) (drop (memory.atomic.wait32 (i32.const 5) (i32.const 10) (i64.const 15) ) ) (drop (memory.atomic.notify (i32.const 5) (i32.const 10) ) ) (drop (memory.size) ) (drop (memory.grow (i32.const 1) ) ) ) ;; CHECK: (func $simd (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i8x16.extract_lane_s 0 ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i8x16.replace_lane 0 ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (v128.bitselect ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000) ;; CHECK-NEXT: (v128.const i32x4 0x00000005 0x00000000 0x00000006 0x00000000) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i8x16.shr_s ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (v128.load8_splat ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (v128.load8_lane 0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $simd (drop (i8x16.extract_lane_s 0 (v128.const i64x2 1 2) ) ) (drop (i8x16.replace_lane 0 (v128.const i64x2 1 2) (i32.const 3) ) ) (drop (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 (v128.const i64x2 1 2) (v128.const i64x2 3 4) ) ) (drop (v128.bitselect (v128.const i64x2 1 2) (v128.const i64x2 3 4) (v128.const i64x2 5 6) ) ) (drop (i8x16.shr_s (v128.const i64x2 1 2) (i32.const 3) ) ) (drop (v128.load8_splat (i32.const 0) ) ) (drop (v128.load8_lane 0 (i32.const 0) (v128.const i64x2 1 2) ) ) ) ;; CHECK: (func $unary (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.eqz ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $unary (drop (i32.eqz (i32.const 1) ) ) ) ;; CHECK: (func $binary (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.add ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $binary (drop (i32.add (i32.const 1) (i32.const 2) ) ) ) ;; CHECK: (func $table (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (table.get $t ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (table.size $t) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (table.grow $t ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $table (drop (table.get $t (i32.const 1) ) ) (drop (table.size $t) ) (drop (table.grow $t (ref.null extern) (i32.const 1) ) ) ) ;; CHECK: (func $i31 (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i31.get_s ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $i31 (drop (i31.get_s (ref.i31 (i32.const 0) ) ) ) ) ;; CHECK: (func $arrays (type $3) (param $B (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.len ;; CHECK-NEXT: (array.new_fixed $B 2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $arrays (param $B (ref $B)) (drop (array.len (array.new_fixed $B 2 (ref.null none) (ref.null none) ) ) ) ) ;; CHECK: (func $rethrow (type $0) ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (try $l0 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (throw $e-i32 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $e-i32 ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (rethrow $l0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $rethrow (try $l0 (do (throw $e-i32 (i32.const 0) ) ) (catch $e-i32 (drop (pop i32) ) (rethrow $l0) ) ) ) ;; CHECK: (func $tuples (type $0) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (tuple.make 2 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $tuples (tuple.drop 2 (tuple.make 2 (i32.const 1) (i32.const 2) ) ) ) ) (module ;; CHECK: (type $struct (sub (struct (field (mut i32))))) (type $struct (sub (struct (mut i32)))) ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) (type $substruct (sub $struct (struct (mut i32) f64))) ;; CHECK: (type $2 (func)) ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: )) (global $something (mut (ref $struct)) (struct.new $struct (i32.const 10) )) ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct ;; CHECK-NEXT: (i32.const 22) ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: )) (global $subsomething (mut (ref $substruct)) (struct.new $substruct (i32.const 22) (f64.const 3.14159) )) ;; CHECK: (func $foo (type $2) ;; CHECK-NEXT: (global.set $something ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: (i32.const 12) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 22) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo ;; The global $something has an initial value and this later value, and they ;; are both of type $struct, so we can infer an exact type for the global. (global.set $something (struct.new $struct (i32.const 10) ) ) ;; Write to that global here. This can only affect $struct, and *not* ;; $substruct, thanks to the exact type. (struct.set $struct 0 (global.get $something) (i32.const 12) ) ;; We cannot optimize the first get here, as it might be 10 or 11. (drop (struct.get $struct 0 (global.get $something) ) ) ;; We can optimize this get, however, as nothing aliased it and 22 is the ;; only possibility. (drop (struct.get $substruct 0 (global.get $subsomething) ) ) ) ) ;; As above, but we can no longer infer an exact type for the struct.set on the ;; global $something. (module ;; CHECK: (type $struct (sub (struct (field (mut i32))))) (type $struct (sub (struct (mut i32)))) ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) (type $substruct (sub $struct (struct (mut i32) f64))) ;; CHECK: (type $2 (func)) ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: )) (global $something (mut (ref $struct)) (struct.new $struct (i32.const 10) )) ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct ;; CHECK-NEXT: (i32.const 22) ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: )) (global $subsomething (mut (ref $substruct)) (struct.new $substruct (i32.const 22) (f64.const 3.14159) )) ;; CHECK: (func $foo (type $2) ;; CHECK-NEXT: (global.set $something ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 22) ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: (i32.const 12) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $substruct 0 ;; CHECK-NEXT: (global.get $subsomething) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo ;; Write a $substruct to $something, so that the global might contain either ;; of the two types. (global.set $something (struct.new $substruct (i32.const 22) (f64.const 3.14159) ) ) ;; This write might alias both types now. (struct.set $struct 0 (global.get $something) (i32.const 12) ) ;; As a result, we can optimize neither of these gets. (drop (struct.get $struct 0 (global.get $something) ) ) (drop (struct.get $substruct 0 (global.get $subsomething) ) ) ) ) ;; As above, but change the constants in the first field in all cases to 10. Now ;; we can optimize. (module ;; CHECK: (type $struct (sub (struct (field (mut i32))))) (type $struct (sub (struct (mut i32)))) ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64)))) (type $substruct (sub $struct (struct (mut i32) f64))) ;; CHECK: (type $2 (func)) ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: )) (global $something (mut (ref $struct)) (struct.new $struct (i32.const 10) )) ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: )) (global $subsomething (mut (ref $substruct)) (struct.new $substruct (i32.const 10) (f64.const 3.14159) )) ;; CHECK: (func $foo (type $2) ;; CHECK-NEXT: (global.set $something ;; CHECK-NEXT: (struct.new $substruct ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (f64.const 3.14159) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (global.get $something) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (global.set $something (struct.new $substruct (i32.const 10) (f64.const 3.14159) ) ) (struct.set $struct 0 (global.get $something) (i32.const 10) ) (drop (struct.get $struct 0 (global.get $something) ) ) (drop (struct.get $substruct 0 (global.get $subsomething) ) ) ) ) ;; call_ref types (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $i1 (func (param i32))) (type $i1 (func (param i32))) ;; CHECK: (type $i2 (func (param i32))) (type $i2 (func (param i32))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (global $func (ref func) (ref.func $reffed-in-global-code)) (global $func (ref func) (ref.func $reffed-in-global-code)) ;; CHECK: (elem declare func $reffed1 $reffed2) ;; CHECK: (func $reffed1 (type $i1) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reffed1 (type $i1) (param $x i32) ;; This is called with one possible value, 42, which we can optimize the ;; param to. (drop (local.get $x) ) ) ;; CHECK: (func $not-reffed (type $i1) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $not-reffed (type $i1) (param $x i32) ;; This function has the same type as the previous one, but it is never ;; taken by reference, which means the call_refs below do not affect it. As ;; there are no other calls, this local.get can be turned into an ;; unreachable. (drop (local.get $x) ) ) ;; CHECK: (func $reffed-in-global-code (type $i1) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reffed-in-global-code (type $i1) (param $x i32) ;; The only ref to this function is in global code, so this tests that we ;; scan that properly. This can be optimized like $reffed, that is, we can ;; infer 42 here. (drop (local.get $x) ) ) ;; CHECK: (func $reffed2 (type $i2) (param $x i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reffed2 (type $i2) (param $x i32) ;; This is called with two possible values, so we cannot optimize. (drop (local.get $x) ) ) ;; CHECK: (func $do-calls (type $2) ;; CHECK-NEXT: (call_ref $i1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (ref.func $reffed1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $i1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (ref.func $reffed1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $i2 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: (ref.func $reffed2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $i2 ;; CHECK-NEXT: (i32.const 99999) ;; CHECK-NEXT: (ref.func $reffed2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $do-calls ;; Call $i1 twice with the same value, and $i2 twice with different values. ;; Note that structurally the types are identical, but we still ;; differentiate them, allowing us to optimize. (call_ref $i1 (i32.const 42) (ref.func $reffed1) ) (call_ref $i1 (i32.const 42) (ref.func $reffed1) ) (call_ref $i2 (i32.const 1337) (ref.func $reffed2) ) (call_ref $i2 (i32.const 99999) (ref.func $reffed2) ) ) ;; CHECK: (func $call_ref-nofunc (type $2) ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call_ref-nofunc ;; Test a call_ref of something of type nofunc. That has a heap type, but it ;; is not a signature type. We should not crash on that. (call_ref $i1 (i32.const 1) (ref.null nofunc) ) ) ) ;; Limited cone reads. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "reads" (func $reads)) ;; CHECK: (func $reads (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reads (export "reads") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) ;; B and C agree on their value. (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 20) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; We can optimize the last of these, which mixes B and C, into 20. (drop (struct.get $A 0 (select (local.get $A) (local.get $B) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $A) (local.get $C) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $B) (local.get $C) (local.get $x) ) ) ) ) ) ;; As above, but now A and B agree on the value and not B and C. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "reads" (func $reads)) ;; CHECK: (func $reads (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (select (result (ref $B)) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reads (export "reads") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) ;; A and B agree on their value. (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 10) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; We can optimize the first of these, which mixes A and B, into 10. (drop (struct.get $A 0 (select (local.get $A) (local.get $B) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $A) (local.get $C) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $B) (local.get $C) (local.get $x) ) ) ) ) ) ;; As above but now A has two subtypes, instead of a chain A->B->C (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $A (struct (field (mut i32))))) (type $C (sub $A (struct (field (mut i32))))) ;; This line changed. ) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "reads" (func $reads)) ;; CHECK: (func $reads (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $reads (export "reads") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) ;; A and B agree on their value. (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 10) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; We cannot optimize any of these. The first is optimizable in theory, ;; since A and B agree on the value, but we end up with a cone on A of depth ;; 1, and that includes B and C. To optimize this we'd need a sum type. (drop (struct.get $A 0 (select (local.get $A) (local.get $B) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $A) (local.get $C) (local.get $x) ) ) ) (drop (struct.get $A 0 (select (local.get $B) (local.get $C) (local.get $x) ) ) ) ) ) ;; Cone writes. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "write" (func $write)) ;; CHECK: (func $write (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (select (result (ref $A)) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $write (export "write") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) ;; A and B agree on their value. (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 10) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; Do a cone write. This writes the same value as they already have. (struct.set $A 0 (select (local.get $A) (local.get $B) (local.get $x) ) (i32.const 10) ) ;; Read from all the locals. We can optimize them all, to 10, 10, 20. (drop (struct.get $A 0 (local.get $A) ) ) (drop (struct.get $B 0 (local.get $B) ) ) (drop (struct.get $C 0 (local.get $C) ) ) ) ) ;; As above, but write a different value. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "write" (func $write)) ;; CHECK: (func $write (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $B 0 ;; CHECK-NEXT: (select (result (ref $B)) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $C 0 ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $write (export "write") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 10) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; Do a different cone write from before: now we write to B and C. This ;; means C can have 10 or 20, and so we don't optimize it down below. (struct.set $A 0 (select (local.get $B) (local.get $C) (local.get $x) ) (i32.const 10) ) (drop (struct.get $A 0 (local.get $A) ) ) (drop (struct.get $B 0 (local.get $B) ) ) (drop (struct.get $C 0 (local.get $C) ) ) ) ) ;; As above, but write a different cone. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param i32))) ;; CHECK: (export "write" (func $write)) ;; CHECK: (func $write (type $3) (param $x i32) ;; CHECK-NEXT: (local $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local $C (ref $C)) ;; CHECK-NEXT: (local.set $A ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $C ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $B 0 ;; CHECK-NEXT: (select (result (ref $B)) ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: (local.get $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $write (export "write") (param $x i32) (local $A (ref $A)) (local $B (ref $B)) (local $C (ref $C)) (local.set $A (struct.new $A (i32.const 10) ) ) (local.set $B (struct.new $B (i32.const 10) ) ) (local.set $C (struct.new $C (i32.const 20) ) ) ;; Write a different value now: 20. This prevents us from optimizing B, but ;; we can still optimize A and C. (struct.set $A 0 (select (local.get $B) (local.get $C) (local.get $x) ) (i32.const 20) ) (drop (struct.get $A 0 (local.get $A) ) ) (drop (struct.get $B 0 (local.get $B) ) ) (drop (struct.get $C 0 (local.get $C) ) ) ) ) ;; Tests for proper inference of imported etc. values - we do know their type, ;; at least. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref $A)))) ;; CHECK: (type $3 (func (result (ref $A)))) ;; CHECK: (type $4 (func)) ;; CHECK: (import "a" "b" (global $A (ref $A))) (import "a" "b" (global $A (ref $A))) ;; CHECK: (import "a" "c" (func $A (type $3) (result (ref $A)))) (import "a" "c" (func $A (result (ref $A)))) ;; CHECK: (global $mut_A (ref $A) (struct.new $A ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) (global $mut_A (ref $A) (struct.new $A (i32.const 42) )) ;; CHECK: (export "mut_A" (global $mut_A)) (export "mut_A" (global $mut_A)) ;; CHECK: (export "yes" (func $yes)) ;; CHECK: (export "no" (func $no)) ;; CHECK: (func $yes (type $2) (param $A (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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 $yes (export "yes") (param $A (ref $A)) ;; An imported global has a known type, at least, which in this case is ;; enough for us to infer a result of 1. (drop (ref.test (ref $A) (global.get $A) ) ) ;; Likewise, a function result. (drop (ref.test (ref $A) (call $A) ) ) ;; Likewise, a parameter to this function, which is exported, but we do ;; still know the type it will be called with, and can optimize to 1. (drop (ref.test (ref $A) (local.get $A) ) ) ;; Likewise, an exported mutable global can be modified by the outside, but ;; the type remains known, and we can optimize to 1. (drop (ref.test (ref $A) (global.get $A) ) ) ) ;; CHECK: (func $no (type $2) (param $A (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (global.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (call $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (global.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $no (export "no") (param $A (ref $A)) ;; Identical to the above function, but now all tests are vs type $B. We ;; cannot optimize any of these, as all we know is the type is $A. (drop (ref.test (ref $B) (global.get $A) ) ) (drop (ref.test (ref $B) (call $A) ) ) (drop (ref.test (ref $B) (local.get $A) ) ) (drop (ref.test (ref $B) (global.get $A) ) ) ) ;; CHECK: (func $filtering (type $4) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $B (result (ref $B)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $B (ref $A) (ref $B) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $A (result (ref $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $A (ref $A) (ref $A) ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 200) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $filtering ;; Check for filtering of values by the declared type in the wasm. We do not ;; have specific filtering or flowing for br_on_* yet, so it will always ;; send the value to the branch target. But the target has a declared type ;; of $B, which means the exact $A gets filtered out, and nothing remains, ;; so we can append an unreachable. ;; ;; When we add filtering/flowing for br_on_* this test should continue to ;; pass and only the comment will need to be updated, so if you are reading ;; this and it is stale, please fix that :) (drop (block $B (result (ref $B)) (drop (br_on_cast $B anyref (ref $B) (struct.new $A (i32.const 100) ) ) ) (unreachable) ) ) ;; But casting to $A will succeed, so the block is reachable, and also the ;; cast will return 1. (drop (ref.test (ref $A) (block $A (result (ref $A)) (drop (br_on_cast $A anyref (ref $A) (struct.new $A (i32.const 200) ) ) ) (unreachable) ) ) ) ) ) ;; Check that array.new_data and array.new_seg are handled properly. (module ;; CHECK: (type $array-i8 (array i8)) (type $array-i8 (array i8)) ;; CHECK: (type $array-funcref (array funcref)) (type $array-funcref (array funcref)) (data "hello") (elem func $test) ;; CHECK: (type $2 (func (param (ref $array-i8) (ref $array-funcref)))) ;; CHECK: (data $0 "hello") ;; CHECK: (elem $0 func $test) ;; CHECK: (export "test" (func $test)) ;; CHECK: (func $test (type $2) (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.new_data $array-i8 $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.new_elem $array-funcref $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get_u $array-i8 ;; CHECK-NEXT: (local.get $array-i8) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $array-funcref ;; CHECK-NEXT: (local.get $array-funcref) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (export "test") (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref)) (drop (array.new_data $array-i8 0 (i32.const 0) (i32.const 5) ) ) (drop (array.new_elem $array-funcref 0 (i32.const 0) (i32.const 1) ) ) (drop (array.get $array-i8 (local.get $array-i8) (i32.const 0) ) ) (drop (array.get $array-funcref (local.get $array-funcref) (i32.const 0) ) ) ) ) ;; Verify we do not error or misoptimize with array.init_elem. (module ;; CHECK: (type $vector (array (mut funcref))) (type $vector (array (mut funcref))) (elem func) ;; CHECK: (type $1 (func)) ;; CHECK: (elem $0 func) ;; CHECK: (elem declare func $test) ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (local $ref (ref $vector)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (array.new $vector ;; CHECK-NEXT: (ref.func $test) ;; CHECK-NEXT: (i32.const 100) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.init_elem $vector $0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $vector ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (local $ref (ref $vector)) (local.set $ref (array.new $vector (ref.func $test) (i32.const 100) ) ) (array.init_elem $vector 0 (local.get $ref) (i32.const 1) (i32.const 1) (i32.const 1) ) ;; We wrote a specific ref.func earlier, but also we did an init_elem whose ;; values we consider unknown, so we will not optimize this get. (drop (array.get $vector (local.get $ref) (i32.const 1) ) ) ) ) ;; Packed field combination. (module (rec ;; CHECK: (type $0 (func)) ;; CHECK: (rec ;; CHECK-NEXT: (type $A (struct (field i8))) (type $A (struct (field i8))) ;; CHECK: (type $B (struct (field i8))) (type $B (struct (field i8))) ) ;; CHECK: (func $A (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $A 0 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 305419896) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get_u $A 0 ;; CHECK-NEXT: (struct.new_default $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $A ;; We write two values to $A, which are different, so we cannot infer. (drop (struct.get_u $A 0 (struct.new $A (i32.const 0x12345678) ) ) ) (drop (struct.get_u $A 0 (struct.new_default $A) ) ) ) ;; CHECK: (func $B (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $B ;; We write two values to $B, which *seem* different, but given the field is ;; packed they are both actually 0, so we can optimize here. (drop (struct.get_u $B 0 (struct.new $B (i32.const 0x12345600) ;; only this changed compared to func $A ) ) ) (drop (struct.get_u $B 0 (struct.new_default $B) ) ) ) ) ;; Test that we do not error on array.init of a bottom type. (module (type $"[mut:i32]" (array (mut i32))) ;; CHECK: (type $0 (func)) ;; CHECK: (data $0 "") (data $0 "") ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (array.init_data $"[mut:i32]" $0 (ref.as_non_null (ref.null none) ) (i32.const 0) (i32.const 0) (i32.const 1) ) ) ) (module ;; CHECK: (type $A (sub (struct ))) (type $A (sub (struct))) ;; CHECK: (type $B (sub $A (struct ))) (type $B (sub $A (struct))) ;; CHECK: (type $2 (func (result (ref $A)))) ;; CHECK: (type $3 (func (result anyref))) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $2) (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (call $get-B-def-any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (export "func") (result (ref $A)) ;; Call a function that actually returns a B, though it is defined as ;; returning an anyref. Then cast it to A. We can infer that it will be a B, ;; so we can cast to B here instead. (ref.cast (ref $A) (call $get-B-def-any) ) ) ;; CHECK: (func $get-B-def-any (type $3) (result anyref) ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) (func $get-B-def-any (result anyref) (struct.new $B) ) ) ;; A situation that we need traps-never-happens to optimize. Here we do nothing, ;; while in gufa-tnh we test with that flag. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $1 (func (param (ref null $A)))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $1) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) ;; The param is cast. (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $3) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (call $called (ref.cast (ref $A) ;; This cast will could be refined with TNH, since we call a ;; function that casts (so if we do not trap as TNH assumes, ;; we must be sending in a $B). But without that flag we do ;; nothing. (local.get $any) ) ) ) ) (module ;; CHECK: (type $A (struct )) (type $A (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (func $func (type $1) ;; CHECK-NEXT: (local $temp anyref) ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (struct.new_default $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label (result (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_on_cast $label (ref $A) (ref $A) ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (local $temp anyref) ;; Write an $A into the anyref local. (local.set $temp (struct.new $A) ) (drop (block $label (result anyref) (drop (br_on_cast $label anyref (ref struct) ;; This cast can be refined since we know the input is $A. After we ;; do that, we must refinalize, as the br_on_cast's types must be ;; valid - specifically, we can't end up with the input type being ;; $A and the output type still being (ref struct), as the output ;; type must be a subtype. After refinalizing, both will become $A. (ref.cast anyref (local.get $temp) ) ) ) (ref.null none) ) ) ) )