blob: 603dc514ca840005bf2317f57de82d5fdf625122 [file] [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 --nominal -S -o - | filecheck %s
(module
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func))
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $none_=>_ref|any| (func_subtype (result (ref any)) func))
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $no-non-null (type $none_=>_ref|any|) (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 $none_=>_i32) (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.as_func
(ref.as_non_null
(ref.null any)
)
)
)
)
)
;; CHECK: (func $yes-non-null (type $none_=>_ref|any|) (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 $none_=>_none)
;; 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 $none_=>_ref|$struct|) (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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 $none_=>_ref|$struct|) (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 $none_=>_ref|$struct|) (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 $none_=>_none)
;; 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 $none_=>_none)
;; 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: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null any)
;; 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 and y with
;; a null constant. (x will not actually contain null since the call will
;; trap, but the only value we see x can contain is the default value, and
;; we don't use SSA yet, so all values written to x anywhere are considered
;; possible at all local.gets)
(drop
(local.get $x)
)
(drop
(local.get $y)
)
(drop
(local.get $z)
)
)
)
(module
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (global $null anyref (ref.null any))
(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 any))
(global $mut-null (mut (ref null any)) (ref.null any))
;; CHECK: (global $mut-something (mut anyref) (ref.null any))
(global $mut-something (mut (ref null any)) (ref.null any))
;; CHECK: (func $read-globals (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null any)
;; 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 $none_=>_none)
;; CHECK-NEXT: (global.set $mut-null
;; CHECK-NEXT: (ref.null $struct)
;; 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 $none_=>_none (func_subtype func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (global $A-null anyref (ref.null any))
(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 any))
(global $B-null (mut (ref null any)) (ref.null any))
;; CHECK: (global $B-something (mut anyref) (ref.null any))
(global $B-something (mut (ref null any)) (ref.null any))
;; CHECK: (global $C-null (mut anyref) (ref.null any))
(global $C-null (mut (ref null any)) (ref.null any))
;; CHECK: (global $C-something (mut anyref) (ref.null any))
(global $C-something (mut (ref null any)) (ref.null any))
;; CHECK: (func $read-globals (type $none_=>_none)
;; 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 $none_=>_none)
;; CHECK-NEXT: (global.set $B-null
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.set $C-null
;; CHECK-NEXT: (ref.null any)
;; 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 $ref|any|_=>_ref|any| (func_subtype (param (ref any)) (result (ref any)) func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (type $i32_=>_i32 (func_subtype (param i32) (result i32) func))
;; CHECK: (type $ref|any|_ref|any|_ref|any|_=>_none (func_subtype (param (ref any) (ref any) (ref any)) func))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $never-called (type $i32_=>_i32) (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 $ref|any|_=>_ref|any|) (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 $ref|any|_=>_ref|any|) (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 $ref|any|_ref|any|_ref|any|_=>_none) (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 $none_=>_none)
;; 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_subtype data))
;; CHECK: (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func))
(type $two-params (func (param (ref $struct)) (param (ref $struct))))
;; CHECK: (type $three-params (func_subtype (param (ref $struct) (ref $struct) (ref $struct)) func))
(type $three-params (func (param (ref $struct)) (param (ref $struct)) (param (ref $struct))))
(type $struct (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 (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_subtype data))
;; CHECK: (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func))
(type $two-params (func (param (ref $struct)) (param (ref $struct))))
(type $struct (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
;; 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
(ref.as_non_null
(ref.null $struct)
)
(struct.new $struct)
(ref.func $func-2params-a)
)
)
)
;; Array creation.
(module
;; CHECK: (type $vector (array_subtype (mut f64) data))
(type $vector (array (mut f64)))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $arrays (type $none_=>_none)
;; 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.init_static $vector
;; 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.init_static $vector
(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
;; CHECK: (type $parent (struct_subtype (field (mut (ref null $struct))) data))
;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct_subtype data))
(type $parent (struct_subtype (field (mut (ref null $struct))) data))
(type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (elem declare func $func)
;; CHECK: (func $func (type $none_=>_none)
;; 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 $struct)
;; 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 (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $child 1
;; CHECK-NEXT: (local.get $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $parent
;; CHECK-NEXT: (struct.new $parent
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $parent 0
;; CHECK-NEXT: (local.get $parent)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; 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 $parent))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $none_=>_none))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_static $parent $parent
;; CHECK-NEXT: (ref.func $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.func $func)
;; 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)
)
)
;; A ref.func 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_static $parent
(ref.func $func)
)
)
)
(drop
(struct.get $parent 0
(block $parent (result (ref $parent))
(drop
(br_on_cast_static $parent $parent
(ref.func $func)
)
)
(unreachable)
)
)
)
)
;; CHECK: (func $nulls (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null $parent)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block (result anyref)
;; CHECK-NEXT: (br $block
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $child))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block0 (result (ref null $child))
;; CHECK-NEXT: (br $block0
;; CHECK-NEXT: (ref.null $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $child))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block1 (result (ref null $child))
;; CHECK-NEXT: (br $block1
;; CHECK-NEXT: (block (result (ref null $child))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast_static $child
;; CHECK-NEXT: (ref.null $parent)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $child)
;; 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 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_static $child
(ref.null $parent)
)
)
(unreachable)
)
)
)
)
;; Default values in struct fields.
(module
(type $A (struct_subtype (field i32) data))
(type $B (struct_subtype (field i32) data))
(type $C (struct_subtype (field i32) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $func (type $none_=>_none)
;; 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 (struct_subtype data))
(type $struct (struct_subtype data))
;; CHECK: (type $parent (struct_subtype (field (mut (ref null $struct))) data))
(type $parent (struct_subtype (field (mut (ref null $struct))) data))
;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent))
(type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $func (type $none_=>_none)
;; 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 $struct)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $child 0
;; CHECK-NEXT: (local.get $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; 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 (struct_subtype (field (mut i32)) data))
(type $parent (struct_subtype (field (mut i32)) data))
;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
(type $child (struct_subtype (field (mut i32)) (field i32) $parent))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $func (type $none_=>_none)
;; 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 (struct_subtype (field (mut i32)) data))
(type $parent (struct_subtype (field (mut i32)) data))
;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
(type $child (struct_subtype (field (mut i32)) (field i32) $parent))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $func (type $none_=>_none)
;; 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: (struct.get $parent 0
;; CHECK-NEXT: (local.get $parent)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; 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: (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
(local $child (ref null $child))
(local $parent (ref null $parent))
(local.set $parent
(struct.new $parent
(i32.const 10)
)
)
;; This get cannot be optimized because later down the local is written 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)
)
)
;; This extra local.set to $parent is added here.
(local.set $parent
(local.tee $child
(struct.new $child
(i32.const 20)
(i32.const 30)
)
)
)
;; 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 (struct_subtype (field (mut i32)) data))
(type $parent (struct_subtype (field (mut i32)) data))
;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent))
(type $child (struct_subtype (field (mut i32)) (field i32) $parent))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $func (type $none_=>_none)
;; 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
(type $nothing (array_subtype (mut (ref null any)) data))
;; CHECK: (type $null (array_subtype (mut anyref) data))
(type $null (array_subtype (mut (ref null any)) data))
;; CHECK: (type $something (array_subtype (mut anyref) data))
(type $something (array_subtype (mut (ref null any)) data))
(type $something-child (array_subtype (mut (ref null any)) $something))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (func $func (type $none_=>_none)
;; 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 any)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result anyref)
;; 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 any)
;; 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_static $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_subtype (field (mut anyref)) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
(type $storage (struct (field (mut (ref null any)))))
;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
;; CHECK: (global $x (mut anyref) (ref.null any))
(global $x (mut (ref null any)) (ref.null any))
;; CHECK: (func $foo (type $none_=>_none)
;; 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 $anyref_=>_anyref) (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 $none_=>_none (func_subtype func))
;; CHECK: (type $storage (struct_subtype (field (mut anyref)) data))
(type $storage (struct (field (mut (ref null any)))))
;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
;; CHECK: (global $x (mut anyref) (ref.null any))
(global $x (mut (ref null any)) (ref.null any))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (local $x anyref)
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: (global.set $x
;; CHECK-NEXT: (block (result anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $storage
;; CHECK-NEXT: (block (result anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $pass-through
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null any)
;; 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 $anyref_=>_anyref) (param $x anyref) (result anyref)
;; CHECK-NEXT: (ref.null any)
;; 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_subtype (field (mut anyref)) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
(type $storage (struct (field (mut (ref null any)))))
;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func))
;; CHECK: (global $x (mut anyref) (ref.null any))
(global $x (mut (ref null any)) (ref.null any))
;; CHECK: (func $foo (type $none_=>_none)
;; 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 $anyref_=>_anyref) (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 $none_=>_none (func_subtype func))
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (type $anyref_=>_none (func_subtype (param anyref) func))
;; 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 (param))
(tag $empty (param))
;; CHECK: (func $func (type $none_=>_none)
;; CHECK-NEXT: (local $0 anyref)
;; CHECK-NEXT: (throw $nothing
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (try $try
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $nothing
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (pop anyref)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (throw $something
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (try $try0
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $something
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (pop anyref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func
;; This tag receives no non-null value, so we can optimize the pop of it,
;; in the next try-catch, to an unreachable.
(throw $nothing
(ref.null $struct)
)
(try
(do)
(catch $nothing
(drop
(ref.as_non_null
(pop (ref null any))
)
)
)
)
;; This tag cannot be optimized as we send it something.
(throw $something
(struct.new $struct)
)
(try
(do)
(catch $something
(drop
(ref.as_non_null
(pop (ref null any))
)
)
)
)
)
;; CHECK: (func $empty-tag (type $none_=>_none)
;; 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 $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try $try (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $empty
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try $try1 (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $empty
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try $try2 (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $empty
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try $try3 (result i32)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $empty
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $try-results
;; If all values flowing out are identical, we can optimize. That is only
;; the case in the very first try.
(drop
(try (result i32)
(do
(i32.const 0)
)
(catch $empty
(i32.const 0)
)
(catch_all
(i32.const 0)
)
)
)
;; If any of the values is changed, we cannot.
(drop
(try (result i32)
(do
(i32.const 42)
)
(catch $empty
(i32.const 0)
)
(catch_all
(i32.const 0)
)
)
)
(drop
(try (result i32)
(do
(i32.const 0)
)
(catch $empty
(i32.const 42)
)
(catch_all
(i32.const 0)
)
)
)
(drop
(try (result i32)
(do
(i32.const 0)
)
(catch $empty
(i32.const 0)
)
(catch_all
(i32.const 42)
)
)
)
)
)
;; Exceptions with a tuple
(module
;; CHECK: (type $struct (struct_subtype data))
(type $struct (struct))
;; CHECK: (type $anyref_anyref_=>_none (func_subtype (param anyref anyref) func))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (tag $tag (param anyref anyref))
(tag $tag (param (ref null any)) (param (ref null any)))
;; CHECK: (func $func (type $none_=>_none)
;; CHECK-NEXT: (local $0 (anyref anyref))
;; CHECK-NEXT: (throw $tag
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (try $try
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (pop anyref anyref)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (try $try0
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (tuple.extract 1
;; CHECK-NEXT: (pop 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 0
(pop (ref null any) (ref null any))
)
)
)
)
;; Catch the second, which we cannot optimize.
(try
(do)
(catch $tag
(drop
(tuple.extract 1
(pop (ref null any) (ref null any))
)
)
)
)
)
)
(module
;; CHECK: (type ${} (struct_subtype data))
(type ${} (struct_subtype data))
;; CHECK: (type $none_=>_ref|${}| (func_subtype (result (ref ${})) func))
;; CHECK: (func $func (type $none_=>_ref|${}|) (result (ref ${}))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block (result (ref ${}))
;; CHECK-NEXT: (br_on_non_null $block
;; CHECK-NEXT: (ref.null ${})
;; 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 $none_=>_none (func_subtype func))
;; CHECK: (type $A (struct_subtype (field i32) data))
(type $A (struct_subtype (field i32) data))
;; CHECK: (type $B (struct_subtype (field i64) data))
(type $B (struct_subtype (field i64) data))
;; CHECK: (type $C (struct_subtype (field f32) data))
(type $C (struct_subtype (field f32) data))
;; CHECK: (type $D (struct_subtype (field f64) data))
(type $D (struct_subtype (field f64) data))
;; CHECK: (func $many-types (type $none_=>_none)
;; 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 (struct_subtype (field funcref) (field funcref) (field funcref) data))
(type $vtable-A (struct_subtype (field (ref null func)) (field (ref null func)) (field (ref null func)) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (global $global-A (ref $vtable-A) (struct.new $vtable-A
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: (ref.null func)
;; 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 $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null func)
;; 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 $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $vtable-A
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: (ref.null func)
;; 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 $none_=>_none)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $foo)
)
(module
;; CHECK: (type $struct (struct_subtype (field i32) data))
(type $struct (struct_subtype (field i32) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $test (type $none_=>_none)
;; 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 (struct_subtype (field i32) data))
(type $struct (struct_subtype (field i32) data))
;; CHECK: (type $substruct (struct_subtype (field i32) (field i32) $struct))
(type $substruct (struct_subtype (field i32) (field i32) $struct))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
(type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast_static $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_static $substruct
;; 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 allows nothing through, so we
;; can emit an unreachable here.
(drop
(ref.cast_static $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_static $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_static $substruct
(struct.new $subsubstruct
(i32.const 3)
(i32.const 4)
(i32.const 5)
)
)
)
)
;; CHECK: (func $test-nulls (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast_static $struct
;; CHECK-NEXT: (block (result (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast_static $struct
;; CHECK-NEXT: (select (result eqref)
;; CHECK-NEXT: (ref.null $struct)
;; CHECK-NEXT: (i31.new
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast_static $struct
;; CHECK-NEXT: (select (result (ref null $struct))
;; CHECK-NEXT: (ref.null $struct)
;; 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_static $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.
(drop
(ref.cast_static $struct
(select
(ref.null $struct)
(i31.new (i32.const 0))
(call $import)
)
)
)
;; A null or a $struct may arrive, and so we cannot do anything here.
(drop
(ref.cast_static $struct
(select
(ref.null $struct)
(struct.new $struct
(i32.const 6)
)
(call $import)
)
)
)
)
)
(module
(type $A (struct_subtype (field i32) data))
(type $B (struct_subtype (ref $A) data))
(type $C (struct_subtype (ref $B) data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $test (type $none_=>_none)
;; 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 (struct_subtype (field i32) data))
(type $A (struct_subtype (field i32) data))
;; CHECK: (type $B (struct_subtype (field (ref $A)) data))
(type $B (struct_subtype (ref $A) data))
;; CHECK: (type $C (struct_subtype (field (ref $B)) data))
(type $C (struct_subtype (ref $B) data))
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $none_=>_none)
;; 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 (struct_subtype (field i32) data))
(type $A (struct_subtype (field i32) data))
;; CHECK: (type $B (struct_subtype (field i32) (field f64) $A))
(type $B (struct_subtype (field i32) (field f64) $A))
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $none_=>_ref|$B| (func_subtype (result (ref $B)) func))
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $foo (type $none_=>_ref|$B|) (result (ref $B))
;; CHECK-NEXT: (local $A (ref null $A))
;; CHECK-NEXT: (ref.cast_static $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_static $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 (struct_subtype (field i32) data))
(type $A (struct_subtype (field i32) data))
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $B (struct_subtype (field i32) (field i32) $A))
(type $B (struct_subtype (field i32) (field i32) $A))
;; CHECK: (func $0 (type $none_=>_i32) (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
;; CHECK: (type $bytes (array_subtype (mut anyref) data))
(type $bytes (array (mut anyref)))
;; CHECK: (type $chars (array_subtype (mut anyref) data))
(type $chars (array (mut anyref)))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (local $bytes (ref null $bytes))
;; CHECK-NEXT: (local $chars (ref null $chars))
;; CHECK-NEXT: (local.set $bytes
;; CHECK-NEXT: (array.init_static $bytes
;; CHECK-NEXT: (i31.new
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $chars
;; CHECK-NEXT: (array.init_static $chars
;; CHECK-NEXT: (ref.null any)
;; 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.init_static $bytes
(i31.new (i32.const 0))
)
)
(local.set $chars
(array.init_static $chars
(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
;; CHECK: (type $bytes (array_subtype (mut anyref) data))
(type $bytes (array (mut anyref)))
;; CHECK: (type $chars (array_subtype (mut anyref) data))
(type $chars (array (mut anyref)))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (local $bytes (ref null $bytes))
;; CHECK-NEXT: (local $chars (ref null $chars))
;; CHECK-NEXT: (local.set $bytes
;; CHECK-NEXT: (array.init_static $bytes
;; CHECK-NEXT: (i31.new
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $chars
;; CHECK-NEXT: (array.init_static $chars
;; CHECK-NEXT: (ref.null any)
;; 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 anyref)
;; 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 any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $bytes (ref null $bytes))
(local $chars (ref null $chars))
(local.set $bytes
(array.init_static $bytes
(i31.new (i32.const 0))
)
)
(local.set $chars
(array.init_static $chars
(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 (struct_subtype data))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (type $i32_=>_none (func_subtype (param i32) func))
;; CHECK: (type $B (array_subtype (mut anyref) data))
(type $B (array (mut anyref)))
;; 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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 $none_=>_none)
;; 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 extern)
;; 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 $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i31.get_s
;; CHECK-NEXT: (i31.new
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $i31
(drop
(i31.get_s
(i31.new
(i32.const 0)
)
)
)
)
;; CHECK: (func $arrays (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (array.len $B
;; CHECK-NEXT: (ref.null $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $arrays
(drop
(array.len $B
(ref.null $B)
)
)
)
;; CHECK: (func $rethrow (type $none_=>_none)
;; 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 $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (tuple.make
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $tuples
(drop
(tuple.make
(i32.const 1)
(i32.const 2)
)
)
)
)
(module
;; CHECK: (type $struct (struct_subtype (field (mut i32)) data))
(type $struct (struct_subtype (mut i32) data))
;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
(type $substruct (struct_subtype (mut i32) f64 $struct))
;; CHECK: (type $none_=>_none (func_subtype 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 $none_=>_none)
;; 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 (struct_subtype (field (mut i32)) data))
(type $struct (struct_subtype (mut i32) data))
;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
(type $substruct (struct_subtype (mut i32) f64 $struct))
;; CHECK: (type $none_=>_none (func_subtype 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 $none_=>_none)
;; 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)
)
(i32.const 11)
)
;; 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 (struct_subtype (field (mut i32)) data))
(type $struct (struct_subtype (mut i32) data))
;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct))
(type $substruct (struct_subtype (mut i32) f64 $struct))
;; CHECK: (type $none_=>_none (func_subtype 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 $none_=>_none)
;; 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)
)
(i32.const 10)
)
(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
;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
;; CHECK: (type $i1 (func_subtype (param i32) func))
(type $i1 (func (param i32)))
;; CHECK: (type $i2 (func_subtype (param i32) func))
(type $i2 (func (param i32)))
;; CHECK: (type $none_=>_none (func_subtype func))
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
;; 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 $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 $none_=>_none)
;; CHECK-NEXT: (call_ref
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (ref.func $reffed1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (ref.func $reffed1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (ref.func $reffed2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref
;; 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
(i32.const 42)
(ref.func $reffed1)
)
(call_ref
(i32.const 42)
(ref.func $reffed1)
)
(call_ref
(i32.const 1337)
(ref.func $reffed2)
)
(call_ref
(i32.const 99999)
(ref.func $reffed2)
)
)
)