blob: d70de6a7a623dedab91e4cc463e21be5175656d7 [file] [log] [blame] [edit]
;; 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
;; 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
;; 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 (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 (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 (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 (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
;; 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
;; 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 "yes" (func $yes))
;; CHECK: (export "no" (func $no))
;; CHECK: (export "mut_A" (global $mut_A))
(export "mut_A" (global $mut_A))
;; 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)
)
)
)
)
;; Packed fields with signed gets.
(module
;; CHECK: (type $array (array (mut i8)))
;; CHECK: (type $1 (func))
;; CHECK: (type $struct (struct (field i16)))
(type $struct (struct (field i16)))
(type $array (array (mut i8)))
;; CHECK: (func $test-struct (type $1)
;; CHECK-NEXT: (local $x (ref $struct))
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (struct.new $struct
;; 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 65535)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-struct
(local $x (ref $struct))
(local.set $x
(struct.new $struct
(i32.const -1)
)
)
;; This reads -1.
(drop
(struct.get_s $struct 0
(local.get $x)
)
)
;; This reads 65535, as the other bits were truncated.
(drop
(struct.get_u $struct 0
(local.get $x)
)
)
)
;; CHECK: (func $test-array (type $1)
;; CHECK-NEXT: (local $x (ref $array))
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (array.new_fixed $array 1
;; CHECK-NEXT: (i32.const -1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (array.get_s $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const -1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (array.get_u $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 255)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-array
(local $x (ref $array))
(local.set $x
(array.new_fixed $array 1
(i32.const -1)
)
)
;; This reads -1.
(drop
(array.get_s $array
(local.get $x)
(i32.const 0)
)
)
;; This reads 255, as the other bits were truncated.
(drop
(array.get_u $array
(local.get $x)
(i32.const 0)
)
)
)
)
;; Packed fields with conflicting sets.
(module
;; CHECK: (type $struct (struct (field i16)))
;; CHECK: (type $1 (func))
;; CHECK: (import "a" "b" (global $import i32))
(import "a" "b" (global $import i32))
(type $struct (struct (field i16)))
;; CHECK: (func $test-struct (type $1)
;; CHECK-NEXT: (local $x (ref null $struct))
;; CHECK-NEXT: (if
;; CHECK-NEXT: (global.get $import)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const -1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get_s $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get_u $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-struct
(local $x (ref null $struct))
(if
(global.get $import)
(then
(local.set $x
(struct.new $struct
(i32.const -1)
)
)
)
(else
(local.set $x
(struct.new $struct
(i32.const 42)
)
)
)
)
;; We cannot infer anything for these reads.
(drop
(struct.get_s $struct 0
(local.get $x)
)
)
(drop
(struct.get_u $struct 0
(local.get $x)
)
)
)
)
;; Packed fields with different sets that actually do not conflict.
(module
;; CHECK: (type $struct (struct (field i16)))
;; CHECK: (type $1 (func))
;; CHECK: (import "a" "b" (global $import i32))
(import "a" "b" (global $import i32))
(type $struct (struct (field i16)))
;; CHECK: (func $test-struct (type $1)
;; CHECK-NEXT: (local $x (ref null $struct))
;; CHECK-NEXT: (if
;; CHECK-NEXT: (global.get $import)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const -1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 65535)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get_s $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const -1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get_u $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 65535)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-struct
(local $x (ref null $struct))
(if
(global.get $import)
(then
(local.set $x
(struct.new $struct
(i32.const -1)
)
)
)
(else
(local.set $x
(struct.new $struct
(i32.const 65535)
)
)
)
)
;; We can infer here because -1 and 65535 are actually the same, after
;; truncation.
(drop
(struct.get_s $struct 0
(local.get $x)
)
)
(drop
(struct.get_u $struct 0
(local.get $x)
)
)
)
)
;; 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 ;; (replaces unreachable ArrayInitData we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; 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 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; 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)
)
)
)
)
(module
;; CHECK: (type $array (sub (array (mut i8))))
(type $array (sub (array (mut i8))))
;; CHECK: (type $1 (func))
;; CHECK: (global $global (ref null $array) (array.new_fixed $array 0))
(global $global (ref null $array) (array.new_fixed $array 0))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast nullref
;; CHECK-NEXT: (global.get $global)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; We should not error on sets to bottom types, even if they are cast from
;; valid values.
(array.set $array
(ref.cast nullref
(global.get $global)
)
(i32.const 0)
(i32.const 0)
)
)
)