| ;; 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) |
| ) |
| ) |
| ) |