diff options
author | Alon Zakai <azakai@google.com> | 2022-07-22 08:21:42 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-22 08:21:42 -0700 |
commit | ed704448a6883e9ee0b2f6284f6b5a7b5e7b4aa9 (patch) | |
tree | 2db1f5d38f4c86264549366a499c4db6588c585f /test | |
parent | af7c69795668cad3e25a8f80e2e7a9741df97f29 (diff) | |
download | binaryen-ed704448a6883e9ee0b2f6284f6b5a7b5e7b4aa9.tar.gz binaryen-ed704448a6883e9ee0b2f6284f6b5a7b5e7b4aa9.tar.bz2 binaryen-ed704448a6883e9ee0b2f6284f6b5a7b5e7b4aa9.zip |
Grand Unified Flow Analysis (GUFA) (#4598)
This tracks the possible contents in the entire program all at once using a single IR.
That is in contrast to say DeadArgumentElimination of LocalRefining etc., all of whom
look at one particular aspect of the program (function params and returns in DAE,
locals in LocalRefining). The cost is to build up an entire new IR, which takes a lot
of new code (mostly in the already-landed PossibleContents). Another cost
is this new IR is very big and requires a lot of time and memory to process.
The benefit is that this can find opportunities that are only obvious when looking
at the entire program, and also it can track information that is more specialized
than the normal type system in the IR - in particular, this can track an ExactType,
which is the case where we know the value is of a particular type exactly and not
a subtype.
Diffstat (limited to 'test')
-rw-r--r-- | test/gtest/possible-contents.cpp | 4 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 9 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 9 | ||||
-rw-r--r-- | test/lit/passes/gufa-optimizing.wast | 62 | ||||
-rw-r--r-- | test/lit/passes/gufa-refs.wast | 3616 | ||||
-rw-r--r-- | test/lit/passes/gufa-tags.wast | 111 | ||||
-rw-r--r-- | test/lit/passes/gufa-vs-cfp.wast | 2753 | ||||
-rw-r--r-- | test/lit/passes/gufa.wast | 922 |
8 files changed, 7486 insertions, 0 deletions
diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 2bed9a882..e4e6bc045 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -27,10 +27,14 @@ void assertCombination(const T& a, const T& b, const T& c) { T temp1 = a; temp1.combine(b); assertEqualSymmetric(temp1, c); + // Also check the type, as nulls will compare equal even if their types + // differ. We want to make sure even the types are identical. + assertEqualSymmetric(temp1.getType(), c.getType()); T temp2 = b; temp2.combine(a); assertEqualSymmetric(temp2, c); + assertEqualSymmetric(temp2.getType(), c.getType()); } // Parse a module from text and return it. diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index e0444ae01..70decf453 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -173,6 +173,15 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --gto globally optimize GC types ;; CHECK-NEXT: +;; CHECK-NEXT: --gufa Grand Unified Flow Analysis: +;; CHECK-NEXT: optimize the entire program +;; CHECK-NEXT: using information about what +;; CHECK-NEXT: content can actually appear in +;; CHECK-NEXT: each location +;; CHECK-NEXT: +;; CHECK-NEXT: --gufa-optimizing GUFA plus local optimizations in +;; CHECK-NEXT: functions we modified +;; CHECK-NEXT: ;; CHECK-NEXT: --heap2local replace GC allocations with ;; CHECK-NEXT: locals ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 5d2d29e87..54010cec5 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -132,6 +132,15 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --gto globally optimize GC types ;; CHECK-NEXT: +;; CHECK-NEXT: --gufa Grand Unified Flow Analysis: +;; CHECK-NEXT: optimize the entire program +;; CHECK-NEXT: using information about what +;; CHECK-NEXT: content can actually appear in +;; CHECK-NEXT: each location +;; CHECK-NEXT: +;; CHECK-NEXT: --gufa-optimizing GUFA plus local optimizations in +;; CHECK-NEXT: functions we modified +;; CHECK-NEXT: ;; CHECK-NEXT: --heap2local replace GC allocations with ;; CHECK-NEXT: locals ;; CHECK-NEXT: diff --git a/test/lit/passes/gufa-optimizing.wast b/test/lit/passes/gufa-optimizing.wast new file mode 100644 index 000000000..7ccd6ae34 --- /dev/null +++ b/test/lit/passes/gufa-optimizing.wast @@ -0,0 +1,62 @@ +;; 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 --check-prefix NO_OPT +;; RUN: foreach %s %t wasm-opt -all --gufa-optimizing -S -o - | filecheck %s --check-prefix DO_OPT + +;; Compare the results of gufa and gufa-optimizing. The optimizing variant will +;; remove unneeded extra code that gufa might introduce, like dropped unneeded +;; things. + +(module + ;; NO_OPT: (type $none_=>_i32 (func (result i32))) + + ;; NO_OPT: (func $foo (result i32) + ;; NO_OPT-NEXT: (i32.const 1) + ;; NO_OPT-NEXT: ) + ;; DO_OPT: (type $none_=>_i32 (func (result i32))) + + ;; DO_OPT: (func $foo (result i32) + ;; DO_OPT-NEXT: (i32.const 1) + ;; DO_OPT-NEXT: ) + (func $foo (result i32) + ;; Helper function. + (i32.const 1) + ) + + ;; NO_OPT: (func $bar (result i32) + ;; NO_OPT-NEXT: (drop + ;; NO_OPT-NEXT: (block $out (result i32) + ;; NO_OPT-NEXT: (block (result i32) + ;; NO_OPT-NEXT: (drop + ;; NO_OPT-NEXT: (block $in (result i32) + ;; NO_OPT-NEXT: (block (result i32) + ;; NO_OPT-NEXT: (drop + ;; NO_OPT-NEXT: (call $foo) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: (i32.const 1) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: (i32.const 1) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: ) + ;; NO_OPT-NEXT: (i32.const 1) + ;; NO_OPT-NEXT: ) + ;; DO_OPT: (func $bar (result i32) + ;; DO_OPT-NEXT: (drop + ;; DO_OPT-NEXT: (call $foo) + ;; DO_OPT-NEXT: ) + ;; DO_OPT-NEXT: (i32.const 1) + ;; DO_OPT-NEXT: ) + (func $bar (result i32) + ;; GUFA infers a constant value for each block here, adding multiple + ;; constants of 1 and dropped earlier values. The optimizing variant of this + ;; pass will avoid all that and just emit minimal code here (a drop of the + ;; call followed by the value we inferred for it, 1). + (block $out (result i32) + (block $in (result i32) + (call $foo) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast new file mode 100644 index 000000000..19ca922f4 --- /dev/null +++ b/test/lit/passes/gufa-refs.wast @@ -0,0 +1,3616 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt -all --gufa --nominal -S -o - | filecheck %s + +(module + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|any| (func_subtype (result (ref any)) func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $no-non-null (type $none_=>_ref|any|) (result (ref any)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $no-non-null (result (ref any)) + ;; The only possible value at the location of this ref.as_non_null is a + ;; null - but that value does not have a compatible type (null is nullable). + ;; Therefore we can infer that nothing is possible here, and the code must + ;; trap, and we'll optimize this to an unreachable. + (ref.as_non_null + (ref.null any) + ) + ) + + ;; CHECK: (func $nested (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (loop $loop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nested (result i32) + ;; As above, but add other instructions on the outside, which can also be + ;; replaced with an unreachable (except for the loop, which as a control + ;; flow structure with a name we keep it around and just add an unreachable + ;; after it; and for now we don't optimize ref.is* so that stays). + (ref.is_null + (loop $loop (result (ref func)) + (nop) + (ref.as_func + (ref.as_non_null + (ref.null any) + ) + ) + ) + ) + ) + + ;; CHECK: (func $yes-non-null (type $none_=>_ref|any|) (result (ref any)) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $yes-non-null (result (ref any)) + ;; Similar to the above but now there *is* an non-null value here, so there + ;; is nothing for us to optimize or change here. + (ref.as_non_null + (struct.new $struct) + ) + ) + + ;; CHECK: (func $breaks (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref $struct)) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $breaks + ;; Check that we notice values sent along breaks. We should optimize + ;; nothing in the first block here. + (drop + (block $block (result (ref any)) + (br $block + (struct.new $struct) + ) + ) + ) + ;; But here we send a null so we can optimize to an unreachable. + (drop + (ref.as_non_null + (block $block2 (result (ref null any)) + (br $block2 + (ref.null $struct) + ) + ) + ) + ) + ) + + ;; CHECK: (func $get-nothing (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $get-nothing (result (ref $struct)) + ;; This function returns a non-nullable struct by type, but does not + ;; actually return a value in practice, and our whole-program analysis + ;; should pick that up in optimizing the callers (but nothing changes here). + (unreachable) + ) + + ;; CHECK: (func $get-nothing-calls (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-nothing-calls + ;; This can be optimized since the call does not actually return any + ;; possible content (it has an unreachable), which means we can optimize + ;; away the call's value - we must keep it around in a drop, since it can + ;; have side effects, but the drop ignores the value which we do not need. + (drop + (call $get-nothing) + ) + ;; As above, add another instruction in the middle. We can optimize it to + ;; an unreachable, like the call. + (drop + (ref.as_non_null + (call $get-nothing) + ) + ) + ;; As above, but we do not optimize ref.is_null yet so nothing happens for + ;; it (but the call still gets optimized as before). + (drop + (ref.is_null + (call $get-nothing) + ) + ) + ) + + ;; CHECK: (func $two-inputs (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $two-inputs + ;; As above, but now the outer instruction is a select, and some of the arms + ;; may have a possible type - we check all 4 permutations. Only in the + ;; case where both inputs are nothing can we optimize away the select (that + ;; is, drop it and ignore its value), as only then will the select never + ;; have any contents. + ;; (Note: we are not fully optimal here since we could notice that the + ;; select executes both arms unconditionally, so if one traps then it will + ;; all trap.) + (drop + (select (result (ref any)) + (struct.new $struct) + (call $get-nothing) + (call $import) + ) + ) + (drop + (select (result (ref any)) + (call $get-nothing) + (struct.new $struct) + (call $import) + ) + ) + (drop + (select (result (ref any)) + (struct.new $struct) + (struct.new $struct) + (call $import) + ) + ) + (drop + (select (result (ref any)) + (call $get-nothing) + (call $get-nothing) + (call $import) + ) + ) + ) + + ;; CHECK: (func $get-something-flow (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + (func $get-something-flow (result (ref $struct)) + ;; Return a value by flowing it out. Helper for later code. + (struct.new $struct) + ) + + ;; CHECK: (func $get-something-return (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-something-return (result (ref $struct)) + ;; Return a value using an explicit return. Helper for later code. + (return + (struct.new $struct) + ) + ) + + ;; CHECK: (func $call-get-something (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-something-flow) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-something-return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-get-something + ;; In both of these cases a value is actually returned and there is nothing + ;; to optimize, unlike get-nothing from above. + (drop + (call $get-something-flow) + ) + (drop + (call $get-something-return) + ) + ) + + ;; CHECK: (func $locals (type $none_=>_none) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local $y anyref) + ;; CHECK-NEXT: (local $z anyref) + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $get-nothing) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $z + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $locals + (local $x (ref null any)) + (local $y (ref null any)) + (local $z (ref null any)) + ;; Assign to x from a call that actually will not return anything. We will + ;; be able to optimize away the call's return value (drop it) and append an + ;; unreachable. + (local.set $x + (call $get-nothing) + ) + ;; Never assign to y. + ;; Assign to z an actual value. + (local.set $z + (struct.new $struct) + ) + ;; Get the 3 locals, to check that we optimize. We can replace x and y with + ;; a null constant. (x will not actually contain null since the call will + ;; trap, but the only value we see x can contain is the default value, and + ;; we don't use SSA yet, so all values written to x anywhere are considered + ;; possible at all local.gets) + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + (drop + (local.get $z) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $null anyref (ref.null any)) + (global $null (ref null any) (ref.null any)) + ;; CHECK: (global $something anyref (struct.new_default $struct)) + (global $something (ref null any) (struct.new $struct)) + + ;; CHECK: (global $mut-null (mut anyref) (ref.null any)) + (global $mut-null (mut (ref null any)) (ref.null any)) + ;; CHECK: (global $mut-something (mut anyref) (ref.null any)) + (global $mut-something (mut (ref null any)) (ref.null any)) + + ;; CHECK: (func $read-globals (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $mut-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $read-globals + ;; This global has no possible contents aside from a null, which we can + ;; infer and place here. + (drop + (global.get $null) + ) + ;; This global has no possible contents aside from a null, so the + ;; ref.as_non_null can be optimized to an unreachable (since a null is not + ;; compatible with its non-nullable type). + (drop + (ref.as_non_null + (global.get $null) + ) + ) + ;; This global has a possible non-null value (in the initializer), so there + ;; is nothing to do. + (drop + (ref.as_non_null + (global.get $something) + ) + ) + ;; This mutable global has a write aside from the initializer, but it is + ;; also of a null, so we can optimize here. + (drop + (ref.as_non_null + (global.get $mut-null) + ) + ) + ;; This one also has a later write, of a non-null value, so there is nothing + ;; to do. + (drop + (ref.as_non_null + (global.get $mut-something) + ) + ) + ) + + ;; CHECK: (func $write-globals (type $none_=>_none) + ;; CHECK-NEXT: (global.set $mut-null + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $mut-something + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $write-globals + (global.set $mut-null + (ref.null $struct) + ) + (global.set $mut-something + (struct.new $struct) + ) + ) +) + +;; As above, but now with a chain of globals: A starts with a value, which is +;; copied to B, and then C, and then C is read. We will be able to optimize +;; away *-null (which is where A-null starts with null) but not *-something +;; (which is where A-something starts with a value). +(module + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (global $A-null anyref (ref.null any)) + (global $A-null (ref null any) (ref.null any)) + ;; CHECK: (global $A-something anyref (struct.new_default $struct)) + (global $A-something (ref null any) (struct.new $struct)) + + ;; CHECK: (global $B-null (mut anyref) (ref.null any)) + (global $B-null (mut (ref null any)) (ref.null any)) + ;; CHECK: (global $B-something (mut anyref) (ref.null any)) + (global $B-something (mut (ref null any)) (ref.null any)) + + ;; CHECK: (global $C-null (mut anyref) (ref.null any)) + (global $C-null (mut (ref null any)) (ref.null any)) + ;; CHECK: (global $C-something (mut anyref) (ref.null any)) + (global $C-something (mut (ref null any)) (ref.null any)) + + ;; CHECK: (func $read-globals (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $A-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $B-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $C-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $read-globals + (drop + (ref.as_non_null + (global.get $A-null) + ) + ) + (drop + (ref.as_non_null + (global.get $A-something) + ) + ) + (drop + (ref.as_non_null + (global.get $B-null) + ) + ) + (drop + (ref.as_non_null + (global.get $B-something) + ) + ) + (drop + (ref.as_non_null + (global.get $C-null) + ) + ) + (drop + (ref.as_non_null + (global.get $C-something) + ) + ) + ) + + ;; CHECK: (func $write-globals (type $none_=>_none) + ;; CHECK-NEXT: (global.set $B-null + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $C-null + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $B-something + ;; CHECK-NEXT: (global.get $A-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $C-something + ;; CHECK-NEXT: (global.get $B-something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $write-globals + (global.set $B-null + (global.get $A-null) + ) + (global.set $C-null + (global.get $B-null) + ) + (global.set $B-something + (global.get $A-something) + ) + (global.set $C-something + (global.get $B-something) + ) + ) +) + +(module + ;; CHECK: (type $ref|any|_=>_ref|any| (func_subtype (param (ref any)) (result (ref any)) func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (type $i32_=>_i32 (func_subtype (param i32) (result i32) func)) + + ;; CHECK: (type $ref|any|_ref|any|_ref|any|_=>_none (func_subtype (param (ref any) (ref any) (ref any)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $never-called (type $i32_=>_i32) (param $x i32) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $never-called (param $x i32) (result i32) + ;; This function is never called, so the parameter has no possible contents, + ;; and we can optimize to an unreachable. + (local.get $x) + ) + + ;; CHECK: (func $never-called-ref (type $ref|any|_=>_ref|any|) (param $x (ref any)) (result (ref any)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $never-called-ref (param $x (ref any)) (result (ref any)) + ;; As above but with a reference type. Again, we can apply an unreachable. + (local.get $x) + ) + + ;; CHECK: (func $recursion (type $ref|any|_=>_ref|any|) (param $x (ref any)) (result (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $recursion + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $recursion (param $x (ref any)) (result (ref any)) + ;; This function calls itself recursively. That forms a loop, but still, + ;; nothing reaches here, so we can optimize to an unreachable (we cannot + ;; remove the call though, as it has effects, so we drop it). + (call $recursion + (local.get $x) + ) + ) + + ;; CHECK: (func $called (type $ref|any|_ref|any|_ref|any|_=>_none) (param $x (ref any)) (param $y (ref any)) (param $z (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $called (param $x (ref any)) (param $y (ref any)) (param $z (ref any)) + ;; This function is called, with possible (non-null) values in the 1st & 3rd + ;; params, but nothing can arrive in the 2nd, which we can optimize to an + ;; unreachable. + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + (drop + (local.get $z) + ) + ) + + ;; CHECK: (func $call-called (type $none_=>_none) + ;; CHECK-NEXT: (call $called + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $called + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-called + ;; Call the above function as described there: Nothing can arrive in the + ;; second param (since we cast a null to non-null there), while the others + ;; have both a null and a non-null (different in the 2 calls here). (With + ;; more precise analysis we could see that the ref.as must trap, and we + ;; could optimize even more here.) + (call $called + (struct.new $struct) + (ref.as_non_null + (ref.null any) + ) + (ref.as_non_null + (ref.null any) + ) + ) + (call $called + (ref.as_non_null + (ref.null any) + ) + (ref.as_non_null + (ref.null any) + ) + (struct.new $struct) + ) + ) +) + +;; As above, but using indirect calls. +(module + ;; CHECK: (type $struct (struct_subtype data)) + + ;; CHECK: (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func)) + (type $two-params (func (param (ref $struct)) (param (ref $struct)))) + + ;; CHECK: (type $three-params (func_subtype (param (ref $struct) (ref $struct) (ref $struct)) func)) + (type $three-params (func (param (ref $struct)) (param (ref $struct)) (param (ref $struct)))) + + (type $struct (struct)) + + (table 10 funcref) + + (elem (i32.const 0) funcref + (ref.func $func-2params-a) + (ref.func $func-2params-b) + (ref.func $func-3params) + ) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $func-2params-a $func-2params-b $func-3params) + + ;; CHECK: (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $two-params) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + ;; Only null is possible for the first, so we can optimize it to an + ;; unreachable. + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + ;; Send a value only to the second param. + (call_indirect (type $two-params) + (ref.as_non_null + (ref.null $struct) + ) + (struct.new $struct) + (i32.const 0) + ) + ) + + ;; CHECK: (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + ;; Another function with the same signature as before, which we should + ;; optimize in the same way: the indirect call can go to either. + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + ) + + ;; CHECK: (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $three-params) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $three-params) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct)) + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + (drop + (local.get $z) + ) + ;; Send a non-null value only to the first and third param. Do so in two + ;; separate calls. The second param, $y, can be optimized. + (call_indirect (type $three-params) + (struct.new $struct) + (ref.as_non_null + (ref.null $struct) + ) + (ref.as_non_null + (ref.null $struct) + ) + (i32.const 0) + ) + (call_indirect (type $three-params) + (ref.as_non_null + (ref.null $struct) + ) + (ref.as_non_null + (ref.null $struct) + ) + (struct.new $struct) + (i32.const 0) + ) + ) +) + +;; As above, but using call_ref. +(module + ;; CHECK: (type $struct (struct_subtype data)) + + ;; CHECK: (type $two-params (func_subtype (param (ref $struct) (ref $struct)) func)) + (type $two-params (func (param (ref $struct)) (param (ref $struct)))) + + (type $struct (struct)) + + ;; CHECK: (elem declare func $func-2params-a) + + ;; CHECK: (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.func $func-2params-a) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct)) + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + ;; Send a non-null value only to the second param. + (call_ref + (ref.as_non_null + (ref.null $struct) + ) + (struct.new $struct) + (ref.func $func-2params-a) + ) + ) +) + +;; Array creation. +(module + ;; CHECK: (type $vector (array_subtype (mut f64) data)) + (type $vector (array (mut f64))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $arrays (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (array.new $vector + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (array.new_default $vector + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (array.init_static $vector + ;; CHECK-NEXT: (f64.const 1.1) + ;; CHECK-NEXT: (f64.const 2.2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $arrays + (drop + (ref.as_non_null + (array.new $vector + (f64.const 3.14159) + (i32.const 1) + ) + ) + ) + (drop + (ref.as_non_null + (array.new_default $vector + (i32.const 100) + ) + ) + ) + (drop + (ref.as_non_null + (array.init_static $vector + (f64.const 1.1) + (f64.const 2.2) + ) + ) + ) + ;; In the last case we have no possible non-null value and can optimize to + ;; an unreachable. + (drop + (ref.as_non_null + (ref.null $vector) + ) + ) + ) +) + +;; Struct fields. +(module + ;; CHECK: (type $parent (struct_subtype (field (mut (ref null $struct))) data)) + + ;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + + (type $parent (struct_subtype (field (mut (ref null $struct))) data)) + (type $child (struct_subtype (field (mut (ref null $struct))) (field (mut (ref null $struct))) $parent)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $child (ref null $child)) + ;; CHECK-NEXT: (local $parent (ref null $parent)) + ;; CHECK-NEXT: (local.set $child + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 1 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $parent 0 + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (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) + ) + ) + ) + + ;; CHECK: (func $nulls (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result anyref) + ;; CHECK-NEXT: (br $block + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block0 (result (ref null $child)) + ;; CHECK-NEXT: (br $block0 + ;; CHECK-NEXT: (ref.null $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block1 (result (ref null $child)) + ;; CHECK-NEXT: (br $block1 + ;; CHECK-NEXT: (block (result (ref null $child)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $child + ;; CHECK-NEXT: (ref.null $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $nulls + ;; Leave null constants alone. + (drop + (ref.null $parent) + ) + ;; Reading from a null reference is easy to optimize - it will trap. + (drop + (struct.get $parent 0 + (ref.null $parent) + ) + ) + ;; Send a null to the block, which is the only value exiting, so we can + ;; optimize here. + (drop + (block $block (result (ref null any)) + (br $block + (ref.null any) + ) + (unreachable) + ) + ) + ;; Send a more specific type. We should emit a valid null constant (but in + ;; this case, a null of either $parent or $child would be ok). + (drop + (block $block (result (ref null $parent)) + (br $block + (ref.null $child) + ) + (unreachable) + ) + ) + ;; Send a less specific type, via a cast. But all nulls are identical and + ;; ref.cast passes nulls through, so this is ok, but we must be careful to + ;; emit a ref.null $child on the outside (to not change the outer type to a + ;; less refined one). + (drop + (block $block (result (ref null $child)) + (br $block + (ref.cast_static $child + (ref.null $parent) + ) + ) + (unreachable) + ) + ) + ) +) + +;; Default values in struct fields. +(module + (type $A (struct_subtype (field i32) data)) + (type $B (struct_subtype (field i32) data)) + (type $C (struct_subtype (field i32) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; Create a struct with default values. We can propagate a 0 to the get. + (drop + (struct.get $A 0 + (struct.new_default $A) + ) + ) + ;; Allocate with a non-default value, that can also be propagated. + (drop + (struct.get $B 0 + (struct.new $B + (i32.const 1) + ) + ) + ) + ) +) + +;; Exact types: Writes to the parent class do not confuse us. +(module + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct_subtype data)) + ;; CHECK: (type $parent (struct_subtype (field (mut (ref null $struct))) data)) + (type $parent (struct_subtype (field (mut (ref null $struct))) data)) + ;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent)) + (type $child (struct_subtype (field (mut (ref null $struct))) (field i32) $parent)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $child (ref null $child)) + ;; CHECK-NEXT: (local $parent (ref null $parent)) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.get $parent 0 + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $child + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (local $child (ref null $child)) + (local $parent (ref null $parent)) + ;; Allocate when writing to the parent's field. + (local.set $parent + (struct.new $parent + (struct.new $struct) + ) + ) + ;; This cannot be optimized in any way. + (drop + (ref.as_non_null + (struct.get $parent 0 + (local.get $parent) + ) + ) + ) + ;; The child writes a null to the first field. + (local.set $child + (struct.new $child + (ref.null $struct) + (i32.const 0) + ) + ) + ;; The parent wrote to the shared field, but that does not prevent us from + ;; seeing that the child must have a null there, and so this will trap. + (drop + (ref.as_non_null + (struct.get $child 0 + (local.get $child) + ) + ) + ) + ) +) + +;; Write values to the parent *and* the child and read from the child. +(module + ;; CHECK: (type $parent (struct_subtype (field (mut i32)) data)) + (type $parent (struct_subtype (field (mut i32)) data)) + ;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $child (ref null $child)) + ;; CHECK-NEXT: (local $parent (ref null $parent)) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $parent 0 + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $child + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 1 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (local $child (ref null $child)) + (local $parent (ref null $parent)) + (local.set $parent + (struct.new $parent + (i32.const 10) + ) + ) + ;; This can be optimized to 10. The child also sets this field, but the + ;; reference in the local $parent can only be a $parent and nothing else. + (drop + (struct.get $parent 0 + (local.get $parent) + ) + ) + (local.set $child + (struct.new $child + ;; The value here conflicts with the parent's for this field, but the + ;; local $child can only contain a $child and nothing else, so we can + ;; optimize the get below us. + (i32.const 20) + (i32.const 30) + ) + ) + (drop + (struct.get $child 0 + (local.get $child) + ) + ) + ;; This get aliases nothing but 30, so we can optimize. + (drop + (struct.get $child 1 + (local.get $child) + ) + ) + ) +) + +;; As above, but the $parent local can now contain a child too. +(module + ;; CHECK: (type $parent (struct_subtype (field (mut i32)) data)) + (type $parent (struct_subtype (field (mut i32)) data)) + ;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $child (ref null $child)) + ;; CHECK-NEXT: (local $parent (ref null $parent)) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $parent 0 + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (local.tee $child + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (local $child (ref null $child)) + (local $parent (ref null $parent)) + (local.set $parent + (struct.new $parent + (i32.const 10) + ) + ) + ;; This get cannot be optimized because later down the local is written a + ;; child as well. So the local $parent can refer to either type, and they + ;; disagree on the aliased value. + (drop + (struct.get $parent 0 + (local.get $parent) + ) + ) + ;; This extra local.set to $parent is added here. + (local.set $parent + (local.tee $child + (struct.new $child + (i32.const 20) + (i32.const 30) + ) + ) + ) + ;; But this one can be optimized as $child can only contain a child. + (drop + (struct.get $child 0 + (local.get $child) + ) + ) + ) +) + +;; As above, but now the parent and child happen to agree on the aliased value. +(module + ;; CHECK: (type $parent (struct_subtype (field (mut i32)) data)) + (type $parent (struct_subtype (field (mut i32)) data)) + ;; CHECK: (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + (type $child (struct_subtype (field (mut i32)) (field i32) $parent)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $child (ref null $child)) + ;; CHECK-NEXT: (local $parent (ref null $parent)) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (struct.new $parent + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $parent 0 + ;; CHECK-NEXT: (local.get $parent) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $parent + ;; CHECK-NEXT: (local.tee $child + ;; CHECK-NEXT: (struct.new $child + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child 0 + ;; CHECK-NEXT: (local.get $child) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (local $child (ref null $child)) + (local $parent (ref null $parent)) + (local.set $parent + (struct.new $parent + (i32.const 10) + ) + ) + (drop + (struct.get $parent 0 + (local.get $parent) + ) + ) + (local.set $parent + (local.tee $child + (struct.new $child + (i32.const 10) ;; This is 10, like above, so we can optimize the get + ;; before us. + (i32.const 30) + ) + ) + ) + (drop + (struct.get $child 0 + (local.get $child) + ) + ) + ) +) + +;; Arrays get/set +(module + (type $nothing (array_subtype (mut (ref null any)) data)) + + ;; CHECK: (type $null (array_subtype (mut anyref) data)) + (type $null (array_subtype (mut (ref null any)) data)) + + ;; CHECK: (type $something (array_subtype (mut anyref) data)) + (type $something (array_subtype (mut (ref null any)) data)) + + (type $something-child (array_subtype (mut (ref null any)) $something)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.set $null + ;; CHECK-NEXT: (array.new_default $null + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $null + ;; CHECK-NEXT: (array.new_default $null + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.set $something + ;; CHECK-NEXT: (array.new_default $something + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (array.get $something + ;; CHECK-NEXT: (array.new_default $something + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; Reading from a null will trap, and we can optimize to an unreachable. + (drop + (array.get $nothing + (ref.null $nothing) + (i32.const 0) + ) + ) + ;; Write a null to this array. + (array.set $null + (array.new_default $null + (i32.const 10) + ) + (i32.const 0) + (ref.null any) + ) + ;; We can only read a null here, so this will trap and can be optimized. + (drop + (ref.as_non_null + (array.get $null + (array.new_default $null + (i32.const 10) + ) + (i32.const 0) + ) + ) + ) + ;; In $something we do actually write a non-null value, so we cannot add + ;; unreachables here. + (array.set $something + (array.new_default $something + (i32.const 10) + ) + (i32.const 0) + (struct.new $struct) + ) + (drop + (ref.as_non_null + (array.get $something + (array.new_default $something + (i32.const 10) + ) + (i32.const 0) + ) + ) + ) + ;; $something-child has nothing written to it, but its parent does. Still, + ;; with exact type info that does not confuse us, and we can optimize to an + ;; unreachable. + (drop + (ref.as_non_null + (array.get $something-child + (ref.cast_static $something-child + (array.new_default $something + (i32.const 10) + ) + ) + (i32.const 0) + ) + ) + ) + ) +) + +;; A big chain, from an allocation that passes through many locations along the +;; way before it is used. Nothing here can be optimized. +(module + ;; CHECK: (type $storage (struct_subtype (field (mut anyref)) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + (type $storage (struct (field (mut (ref null any))))) + + ;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func)) + + ;; CHECK: (global $x (mut anyref) (ref.null any)) + (global $x (mut (ref null any)) (ref.null any)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $x + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (struct.get $storage 0 + ;; CHECK-NEXT: (struct.new $storage + ;; CHECK-NEXT: (call $pass-through + ;; CHECK-NEXT: (global.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + (local $x (ref null any)) + ;; Allocate a non-null value and pass it through a local. + (local.set $x + (struct.new $struct) + ) + ;; Pass it through a global. + (global.set $x + (local.get $x) + ) + ;; Pass it through a call, then write it to a struct, then read it from + ;; there, and coerce to non-null which we would optimize if the value were + ;; only a null. But it is not a null, and no optimizations happen here. + (drop + (ref.as_non_null + (struct.get $storage 0 + (struct.new $storage + (call $pass-through + (global.get $x) + ) + ) + ) + ) + ) + ) + + ;; CHECK: (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $pass-through (param $x (ref null any)) (result (ref null any)) + (local.get $x) + ) +) + +;; As above, but the chain is turned into a loop, replacing the initial +;; allocation with a get from the end. We can optimize such cycles. +(module + (type $struct (struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $storage (struct_subtype (field (mut anyref)) data)) + (type $storage (struct (field (mut (ref null any))))) + + ;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func)) + + ;; CHECK: (global $x (mut anyref) (ref.null any)) + (global $x (mut (ref null any)) (ref.null any)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $x + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $storage + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $pass-through + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + (local $x (ref null any)) + ;; Replace the initial allocation with a read from the global. That is + ;; written to lower down, forming a loop - a loop with no actual allocation + ;; anywhere, so we can infer the possible values are only a null. + (local.set $x + (global.get $x) + ) + (global.set $x + (struct.get $storage 0 + (struct.new $storage + (call $pass-through + (local.get $x) + ) + ) + ) + ) + (drop + (ref.as_non_null + (global.get $x) + ) + ) + ) + + ;; CHECK: (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + (func $pass-through (param $x (ref null any)) (result (ref null any)) + (local.get $x) + ) +) + +;; A single long chain as above, but now we break the chain in the middle by +;; adding a non-null value. +(module + ;; CHECK: (type $storage (struct_subtype (field (mut anyref)) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + (type $storage (struct (field (mut (ref null any))))) + + ;; CHECK: (type $anyref_=>_anyref (func_subtype (param anyref) (result anyref) func)) + + ;; CHECK: (global $x (mut anyref) (ref.null any)) + (global $x (mut (ref null any)) (ref.null any)) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (global.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $x + ;; CHECK-NEXT: (struct.get $storage 0 + ;; CHECK-NEXT: (struct.new $storage + ;; CHECK-NEXT: (call $pass-through + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (global.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + (local $x (ref null any)) + (local.set $x + (global.get $x) + ) + (global.set $x + (struct.get $storage 0 + (struct.new $storage + (call $pass-through + ;; The only change is to allocate here instead of reading the local + ;; $x. This causes us to not optimize anything in this function. + (struct.new $struct) + ) + ) + ) + ) + (drop + (ref.as_non_null + (global.get $x) + ) + ) + ) + + ;; CHECK: (func $pass-through (type $anyref_=>_anyref) (param $x anyref) (result anyref) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $pass-through (param $x (ref null any)) (result (ref null any)) + (local.get $x) + ) +) + +;; Exceptions. +(module + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (type $anyref_=>_none (func_subtype (param anyref) func)) + + ;; CHECK: (tag $nothing (param anyref)) + (tag $nothing (param (ref null any))) + + ;; CHECK: (tag $something (param anyref)) + (tag $something (param (ref null any))) + + ;; CHECK: (tag $empty (param)) + (tag $empty (param)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $0 anyref) + ;; CHECK-NEXT: (throw $nothing + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $nothing + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop anyref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (throw $something + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $try0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $something + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (pop anyref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; This tag receives no non-null value, so we can optimize the pop of it, + ;; in the next try-catch, to an unreachable. + (throw $nothing + (ref.null $struct) + ) + (try + (do) + (catch $nothing + (drop + (ref.as_non_null + (pop (ref null any)) + ) + ) + ) + ) + ;; This tag cannot be optimized as we send it something. + (throw $something + (struct.new $struct) + ) + (try + (do) + (catch $something + (drop + (ref.as_non_null + (pop (ref null any)) + ) + ) + ) + ) + ) + + ;; CHECK: (func $empty-tag (type $none_=>_none) + ;; CHECK-NEXT: (try $label$3 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $empty-tag + ;; Check we do not error on catching an empty tag. + (try $label$3 + (do + (nop) + ) + (catch $empty + (nop) + ) + ) + ) + + ;; CHECK: (func $try-results (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try $try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try $try1 (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try $try2 (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try $try3 (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $empty + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $try-results + ;; If all values flowing out are identical, we can optimize. That is only + ;; the case in the very first try. + (drop + (try (result i32) + (do + (i32.const 0) + ) + (catch $empty + (i32.const 0) + ) + (catch_all + (i32.const 0) + ) + ) + ) + ;; If any of the values is changed, we cannot. + (drop + (try (result i32) + (do + (i32.const 42) + ) + (catch $empty + (i32.const 0) + ) + (catch_all + (i32.const 0) + ) + ) + ) + (drop + (try (result i32) + (do + (i32.const 0) + ) + (catch $empty + (i32.const 42) + ) + (catch_all + (i32.const 0) + ) + ) + ) + (drop + (try (result i32) + (do + (i32.const 0) + ) + (catch $empty + (i32.const 0) + ) + (catch_all + (i32.const 42) + ) + ) + ) + ) +) + +;; Exceptions with a tuple +(module + ;; CHECK: (type $struct (struct_subtype data)) + (type $struct (struct)) + + ;; CHECK: (type $anyref_anyref_=>_none (func_subtype (param anyref anyref) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (tag $tag (param anyref anyref)) + (tag $tag (param (ref null any)) (param (ref null any))) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (local $0 (anyref anyref)) + ;; CHECK-NEXT: (throw $tag + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop anyref anyref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try $try0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 1 + ;; CHECK-NEXT: (pop anyref anyref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + ;; This tag receives a null in the first parameter. + (throw $tag + (ref.null $struct) + (struct.new $struct) + ) + ;; Catch the first, which we can optimize to a null. + (try + (do) + (catch $tag + (drop + (tuple.extract 0 + (pop (ref null any) (ref null any)) + ) + ) + ) + ) + ;; Catch the second, which we cannot optimize. + (try + (do) + (catch $tag + (drop + (tuple.extract 1 + (pop (ref null any) (ref null any)) + ) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type ${} (struct_subtype data)) + (type ${} (struct_subtype data)) + + ;; CHECK: (type $none_=>_ref|${}| (func_subtype (result (ref ${})) func)) + + ;; CHECK: (func $func (type $none_=>_ref|${}|) (result (ref ${})) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block (result (ref ${})) + ;; CHECK-NEXT: (br_on_non_null $block + ;; CHECK-NEXT: (ref.null ${}) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $func (result (ref ${})) + ;; This block can only return a null in theory (in practice, not even that - + ;; the br will not be taken, but this pass is not smart enough to see that). + ;; We can optimize to an unreachable here, but must be careful - we cannot + ;; remove the block as the wasm would not validate (not unless we also + ;; removed the br, which we don't do atm). All we will do is add an + ;; unreachable after the block, on the outside of it (which would help other + ;; passes do more work). + (block $block (result (ref ${})) + (br_on_non_null $block + (ref.null ${}) + ) + (unreachable) + ) + ) +) + +(module + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $A (struct_subtype (field i32) data)) + (type $A (struct_subtype (field i32) data)) + ;; CHECK: (type $B (struct_subtype (field i64) data)) + (type $B (struct_subtype (field i64) data)) + ;; CHECK: (type $C (struct_subtype (field f32) data)) + (type $C (struct_subtype (field f32) data)) + ;; CHECK: (type $D (struct_subtype (field f64) data)) + (type $D (struct_subtype (field f64) data)) + + ;; CHECK: (func $many-types (type $none_=>_none) + ;; CHECK-NEXT: (local $x anyref) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (f32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (struct.new $D + ;; CHECK-NEXT: (f64.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $many-types + (local $x (ref null any)) + ;; Write 4 different types into $x. That should not confuse us, and we + ;; should not make any changes in this function. + (local.set $x + (struct.new $A + (i32.const 0) + ) + ) + (local.set $x + (struct.new $B + (i64.const 1) + ) + ) + (local.set $x + (struct.new $C + (f32.const 2) + ) + ) + (local.set $x + (struct.new $D + (f64.const 3) + ) + ) + (drop + (ref.as_non_null + (local.get $x) + ) + ) + ) +) + +;; Test a vtable-like pattern. This tests ref.func values flowing into struct +;; locations being properly noticed, both from global locations (the global's +;; init) and a function ($create). +(module + ;; CHECK: (type $vtable-A (struct_subtype (field funcref) (field funcref) (field funcref) data)) + (type $vtable-A (struct_subtype (field (ref null func)) (field (ref null func)) (field (ref null func)) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global-A (ref $vtable-A) (struct.new $vtable-A + ;; CHECK-NEXT: (ref.func $foo) + ;; CHECK-NEXT: (ref.null func) + ;; CHECK-NEXT: (ref.func $foo) + ;; CHECK-NEXT: )) + (global $global-A (ref $vtable-A) + (struct.new $vtable-A + (ref.func $foo) + (ref.null func) + (ref.func $foo) + ) + ) + + ;; CHECK: (elem declare func $foo $test) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable-A 2 + ;; CHECK-NEXT: (global.get $global-A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The first item here contains a fixed value (ref.func $foo) in both the + ;; global init and in the function $create, which we can apply. + (drop + (struct.get $vtable-A 0 + (global.get $global-A) + ) + ) + ;; The second item here contains a null in all cases, which we can also + ;; apply. + (drop + (struct.get $vtable-A 1 + (global.get $global-A) + ) + ) + ;; The third item has more than one possible value, due to the function + ;; $create later down, so we cannot optimize. + (drop + (struct.get $vtable-A 2 + (global.get $global-A) + ) + ) + ) + + ;; CHECK: (func $create (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $vtable-A + ;; CHECK-NEXT: (ref.func $foo) + ;; CHECK-NEXT: (ref.null func) + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create + (drop + (struct.new $vtable-A + (ref.func $foo) + (ref.null func) + (ref.func $test) + ) + ) + ) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $foo) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct_subtype (field i32) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (block $block (result (ref $struct)) + ;; CHECK-NEXT: (block $block0 (result (ref $struct)) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $ref (ref null $struct)) + ;; Regression test for an assertion firing in this case. We should properly + ;; handle the multiple intermediate blocks here, allowing us to optimize the + ;; get below to a 42. + (local.set $ref + (block (result (ref $struct)) + (block (result (ref $struct)) + (struct.new $struct + (i32.const 42) + ) + ) + ) + ) + (drop + (struct.get $struct 0 + (local.get $ref) + ) + ) + ) +) + +;; Casts. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct_subtype (field i32) data)) + ;; CHECK: (type $substruct (struct_subtype (field i32) (field i32) $struct)) + (type $substruct (struct_subtype (field i32) (field i32) $struct)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct)) + (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (struct.new $subsubstruct + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The cast here will fail, and the ref.cast allows nothing through, so we + ;; can emit an unreachable here. + (drop + (ref.cast_static $substruct + (struct.new $struct + (i32.const 0) + ) + ) + ) + ;; This cast of a type to itself can succeed (in fact, it will), so we make + ;; no changes here. + (drop + (ref.cast_static $substruct + (struct.new $substruct + (i32.const 1) + (i32.const 2) + ) + ) + ) + ;; This cast of a subtype will also succeed. As above, we make no changes. + (drop + (ref.cast_static $substruct + (struct.new $subsubstruct + (i32.const 3) + (i32.const 4) + (i32.const 5) + ) + ) + ) + ) + + ;; CHECK: (func $test-nulls (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $struct + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $struct + ;; CHECK-NEXT: (select (result anyref) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $struct + ;; CHECK-NEXT: (select (result (ref null $struct)) + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-nulls + ;; Only a null can flow through the cast, which we can infer for the value + ;; of the cast. + (drop + (ref.cast_static $struct + (select + (ref.null $struct) + (ref.null $struct) + (call $import) + ) + ) + ) + ;; A null or a func will reach the cast; only the null can actually pass + ;; through (a func would fail the cast). Given that, we can infer a null for + ;; the value of the cast. + (drop + (ref.cast_static $struct + (select + (ref.null $struct) + (ref.func $test) + (call $import) + ) + ) + ) + ;; A null or a $struct may arrive, and so we cannot do anything here. + (drop + (ref.cast_static $struct + (select + (ref.null $struct) + (struct.new $struct + (i32.const 6) + ) + (call $import) + ) + ) + ) + ) +) + +(module + (type $A (struct_subtype (field i32) data)) + (type $B (struct_subtype (ref $A) data)) + (type $C (struct_subtype (ref $B) data)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; Test nested struct.get operations. We can optimize all this into the + ;; constant 42. + (drop + (struct.get $A 0 + (struct.get $B 0 + (struct.get $C 0 + (struct.new $C + (struct.new $B + (struct.new $A + (i32.const 42) + ) + ) + ) + ) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $A (struct_subtype (field i32) data)) + (type $A (struct_subtype (field i32) data)) + ;; CHECK: (type $B (struct_subtype (field (ref $A)) data)) + (type $B (struct_subtype (ref $A) data)) + ;; CHECK: (type $C (struct_subtype (field (ref $B)) data)) + (type $C (struct_subtype (ref $B) data)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.get $B 0 + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (struct.new $C + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but now call an import for the i32; we cannot optimize. + (drop + (struct.get $A 0 + (struct.get $B 0 + (struct.get $C 0 + (struct.new $C + (struct.new $B + (struct.new $A + (call $import) + ) + ) + ) + ) + ) + ) + ) + ) +) + +;; ref.as* test. +(module + ;; CHECK: (type $A (struct_subtype (field i32) data)) + (type $A (struct_subtype (field i32) data)) + ;; CHECK: (type $B (struct_subtype (field i32) (field f64) $A)) + (type $B (struct_subtype (field i32) (field f64) $A)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|$B| (func_subtype (result (ref $B)) func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $foo (type $none_=>_ref|$B|) (result (ref $B)) + ;; CHECK-NEXT: (local $A (ref null $A)) + ;; CHECK-NEXT: (ref.cast_static $B + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.tee $A + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (f64.const 13.37) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo (result (ref $B)) + (local $A (ref null $A)) + + ;; Read the following from the most nested comment first. + + (ref.cast_static $B ;; if we mistakenly think this contains content of + ;; type $A, it would trap, but it should not, and we + ;; have nothing to optimize here + (ref.as_non_null ;; also $B, based on the child's *contents* (not type!) + (local.tee $A ;; flows out a $B, but has type $A + (struct.new $B ;; returns a $B + (i32.const 42) + (f64.const 13.37) + ) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $A (struct_subtype (field i32) data)) + (type $A (struct_subtype (field i32) data)) + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $B (struct_subtype (field i32) (field i32) $A)) + (type $B (struct_subtype (field i32) (field i32) $A)) + ;; CHECK: (func $0 (type $none_=>_i32) (result i32) + ;; CHECK-NEXT: (local $ref (ref null $A)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $B + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $0 (result i32) + (local $ref (ref null $A)) + (local.set $ref + (struct.new $B + (i32.const 0) + (i32.const 1) + ) + ) + ;; This struct.get has a reference of type $A, but we can infer the type + ;; present in the reference must actually be a $B, and $B precisely - no + ;; sub or supertypes. So we can infer a value of 0. + ;; + ;; A possible bug that this is a regression test for is a confusion between + ;; the type of the content and the declared type. If we mixed them up and + ;; thought this must be precisely an $A and not a $B then we'd emit an + ;; unreachable here (since no $A is ever allocated). + (struct.get $A 0 + (local.get $ref) + ) + ) +) + +;; array.copy between types. +(module + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $bytes (array_subtype (mut anyref) data)) + (type $bytes (array (mut anyref))) + ;; CHECK: (type $chars (array_subtype (mut anyref) data)) + (type $chars (array (mut anyref))) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $bytes (ref null $bytes)) + ;; CHECK-NEXT: (local $chars (ref null $chars)) + ;; CHECK-NEXT: (local.set $bytes + ;; CHECK-NEXT: (array.init_static $bytes + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $chars + ;; CHECK-NEXT: (array.init_static $chars + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.copy $chars $bytes + ;; CHECK-NEXT: (local.get $chars) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $bytes) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $none_=>_none)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $bytes + ;; CHECK-NEXT: (local.get $bytes) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $chars + ;; CHECK-NEXT: (local.get $chars) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $bytes (ref null $bytes)) + (local $chars (ref null $chars)) + + ;; Write something to $bytes, but just a null to $chars. But then do a copy + ;; which means two things are possible in $chars, and we can't optimize + ;; there. + (local.set $bytes + (array.init_static $bytes + (ref.func $test) + ) + ) + (local.set $chars + (array.init_static $chars + (ref.null any) + ) + ) + (array.copy $chars $bytes + (local.get $chars) + (i32.const 0) + (local.get $bytes) + (i32.const 0) + (i32.const 1) + ) + (drop + (array.get $bytes + (local.get $bytes) + (i32.const 0) + ) + ) + (drop + (array.get $chars + (local.get $chars) + (i32.const 0) + ) + ) + ) +) + +;; As above, but with a copy in the opposite direction. Now $chars has a single +;; value (a null) which we can optimize, but $bytes has two values and we +;; cannot optimize there. +(module + ;; CHECK: (type $bytes (array_subtype (mut anyref) data)) + (type $bytes (array (mut anyref))) + ;; CHECK: (type $chars (array_subtype (mut anyref) data)) + (type $chars (array (mut anyref))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $bytes (ref null $bytes)) + ;; CHECK-NEXT: (local $chars (ref null $chars)) + ;; CHECK-NEXT: (local.set $bytes + ;; CHECK-NEXT: (array.init_static $bytes + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $chars + ;; CHECK-NEXT: (array.init_static $chars + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (array.copy $bytes $chars + ;; CHECK-NEXT: (local.get $bytes) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $chars) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $bytes + ;; CHECK-NEXT: (local.get $bytes) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.get $chars + ;; CHECK-NEXT: (local.get $chars) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $bytes (ref null $bytes)) + (local $chars (ref null $chars)) + (local.set $bytes + (array.init_static $bytes + (ref.func $test) + ) + ) + (local.set $chars + (array.init_static $chars + (ref.null any) + ) + ) + (array.copy $bytes $chars + (local.get $bytes) + (i32.const 0) + (local.get $chars) + (i32.const 0) + (i32.const 1) + ) + (drop + (array.get $bytes + (local.get $bytes) + (i32.const 0) + ) + ) + (drop + (array.get $chars + (local.get $chars) + (i32.const 0) + ) + ) + ) +) + +;; Basic tests for all instructions appearing in possible-contents.cpp but not +;; already shown above. If we forgot to add the proper links to any of them, +;; they might appear as if no content were possible there, and we'd emit an +;; unreachable. That should not happen anywhere here. +(module + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + + ;; CHECK: (type $i32_=>_none (func_subtype (param i32) func)) + + ;; CHECK: (type $B (array_subtype (mut anyref) data)) + (type $B (array (mut anyref))) + + ;; CHECK: (memory $0 10) + + ;; CHECK: (table $t 0 anyref) + + ;; CHECK: (tag $e-i32 (param i32)) + (tag $e-i32 (param i32)) + + (memory $0 10) + + (table $t 0 externref) + + ;; CHECK: (func $br_table (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $A (result i32) + ;; CHECK-NEXT: (br_table $A $A + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_table + (drop + ;; The value 1 can be inferred here. + (block $A (result i32) + (br_table $A $A + (i32.const 1) + (i32.const 2) + ) + ) + ) + ) + + ;; CHECK: (func $memory (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.atomic.rmw.add + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.atomic.rmw.cmpxchg + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.atomic.wait32 + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i64.const 15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.atomic.notify + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $memory + (drop + (i32.load + (i32.const 5) + ) + ) + (drop + (i32.atomic.rmw.add + (i32.const 5) + (i32.const 10) + ) + ) + (drop + (i32.atomic.rmw.cmpxchg + (i32.const 5) + (i32.const 10) + (i32.const 15) + ) + ) + (drop + (memory.atomic.wait32 + (i32.const 5) + (i32.const 10) + (i64.const 15) + ) + ) + (drop + (memory.atomic.notify + (i32.const 5) + (i32.const 10) + ) + ) + (drop + (memory.size) + ) + (drop + (memory.grow + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $simd (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i8x16.extract_lane_s 0 + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i8x16.replace_lane 0 + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.bitselect + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000) + ;; CHECK-NEXT: (v128.const i32x4 0x00000005 0x00000000 0x00000006 0x00000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i8x16.shr_s + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.load8_splat + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.load8_lane 0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $simd + (drop + (i8x16.extract_lane_s 0 + (v128.const i64x2 1 2) + ) + ) + (drop + (i8x16.replace_lane 0 + (v128.const i64x2 1 2) + (i32.const 3) + ) + ) + (drop + (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31 + (v128.const i64x2 1 2) + (v128.const i64x2 3 4) + ) + ) + (drop + (v128.bitselect + (v128.const i64x2 1 2) + (v128.const i64x2 3 4) + (v128.const i64x2 5 6) + ) + ) + (drop + (i8x16.shr_s + (v128.const i64x2 1 2) + (i32.const 3) + ) + ) + (drop + (v128.load8_splat + (i32.const 0) + ) + ) + (drop + (v128.load8_lane 0 + (i32.const 0) + (v128.const i64x2 1 2) + ) + ) + ) + + ;; CHECK: (func $unary (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary + (drop + (i32.eqz + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $binary (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $binary + (drop + (i32.add + (i32.const 1) + (i32.const 2) + ) + ) + ) + + ;; CHECK: (func $refs-rtts (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (ref.null data) + ;; CHECK-NEXT: (ref.null data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast + ;; CHECK-NEXT: (ref.null $A) + ;; CHECK-NEXT: (rtt.sub $A + ;; CHECK-NEXT: (rtt.canon $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refs-rtts + (drop + (ref.eq + (ref.null data) + (ref.null data) + ) + ) + (drop + (ref.cast + (ref.null $A) + (rtt.sub $A + (rtt.canon $A) + ) + ) + ) + ) + + ;; CHECK: (func $table (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.get $t + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.size $t) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $t + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $table + (drop + (table.get $t + (i32.const 1) + ) + ) + (drop + (table.size $t) + ) + (drop + (table.grow $t + (ref.null extern) + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $i31 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i31.get_s + ;; CHECK-NEXT: (i31.new + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $i31 + (drop + (i31.get_s + (i31.new + (i32.const 0) + ) + ) + ) + ) + + ;; CHECK: (func $arrays (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (array.len $B + ;; CHECK-NEXT: (ref.null $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $arrays + (drop + (array.len $B + (ref.null $B) + ) + ) + ) + + ;; CHECK: (func $rethrow (type $none_=>_none) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (try $l0 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (throw $e-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $e-i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rethrow $l0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $rethrow + (try $l0 + (do + (throw $e-i32 + (i32.const 0) + ) + ) + (catch $e-i32 + (drop + (pop i32) + ) + (rethrow $l0) + ) + ) + ) + + ;; CHECK: (func $tuples (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.make + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tuples + (drop + (tuple.make + (i32.const 1) + (i32.const 2) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct_subtype (mut i32) data)) + + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct)) + (type $substruct (struct_subtype (mut i32) f64 $struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: )) + (global $something (mut (ref $struct)) (struct.new $struct + (i32.const 10) + )) + + ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: )) + (global $subsomething (mut (ref $substruct)) (struct.new $substruct + (i32.const 22) + (f64.const 3.14159) + )) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (global.set $something + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + ;; The global $something has an initial value and this later value, and they + ;; are both of type $struct, so we can infer an exact type for the global. + (global.set $something + (struct.new $struct + (i32.const 10) + ) + ) + ;; Write to that global here. This can only affect $struct, and *not* + ;; $substruct, thanks to the exact type. + (struct.set $struct 0 + (global.get $something) + (i32.const 12) + ) + ;; We cannot optimize the first get here, as it might be 10 or 11. + (drop + (struct.get $struct 0 + (global.get $something) + ) + ) + ;; We can optimize this get, however, as nothing aliased it and 22 is the + ;; only possibility. + (drop + (struct.get $substruct 0 + (global.get $subsomething) + ) + ) + ) +) + +;; As above, but we can no longer infer an exact type for the struct.set on the +;; global $something. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct_subtype (mut i32) data)) + + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct)) + (type $substruct (struct_subtype (mut i32) f64 $struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: )) + (global $something (mut (ref $struct)) (struct.new $struct + (i32.const 10) + )) + + ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: )) + (global $subsomething (mut (ref $substruct)) (struct.new $substruct + (i32.const 22) + (f64.const 3.14159) + )) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (global.set $something + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 22) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: (i32.const 12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $substruct 0 + ;; CHECK-NEXT: (global.get $subsomething) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + ;; Write a $substruct to $something, so that the global might contain either + ;; of the two types. + (global.set $something + (struct.new $substruct + (i32.const 22) + (f64.const 3.14159) + ) + (i32.const 11) + ) + ;; This write might alias both types now. + (struct.set $struct 0 + (global.get $something) + (i32.const 12) + ) + ;; As a result, we can optimize neither of these gets. + (drop + (struct.get $struct 0 + (global.get $something) + ) + ) + (drop + (struct.get $substruct 0 + (global.get $subsomething) + ) + ) + ) +) + +;; As above, but change the constants in the first field in all cases to 10. Now +;; we can optimize. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct_subtype (mut i32) data)) + + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct)) + (type $substruct (struct_subtype (mut i32) f64 $struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $something (mut (ref $struct)) (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: )) + (global $something (mut (ref $struct)) (struct.new $struct + (i32.const 10) + )) + + ;; CHECK: (global $subsomething (mut (ref $substruct)) (struct.new $substruct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: )) + (global $subsomething (mut (ref $substruct)) (struct.new $substruct + (i32.const 10) + (f64.const 3.14159) + )) + + ;; CHECK: (func $foo (type $none_=>_none) + ;; CHECK-NEXT: (global.set $something + ;; CHECK-NEXT: (struct.new $substruct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (global.get $something) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $foo + (global.set $something + (struct.new $substruct + (i32.const 10) + (f64.const 3.14159) + ) + (i32.const 10) + ) + (struct.set $struct 0 + (global.get $something) + (i32.const 10) + ) + (drop + (struct.get $struct 0 + (global.get $something) + ) + ) + (drop + (struct.get $substruct 0 + (global.get $subsomething) + ) + ) + ) +) + +;; call_ref types +(module + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $i1 (func_subtype (param i32) func)) + (type $i1 (func (param i32))) + ;; CHECK: (type $i2 (func_subtype (param i32) func)) + (type $i2 (func (param i32))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (elem declare func $reffed1 $reffed2) + + ;; CHECK: (func $reffed1 (type $i1) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed1 (type $i1) (param $x i32) + ;; This is called with one possible value, 42, which we can optimize the + ;; param to. + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $reffed2 (type $i2) (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed2 (type $i2) (param $x i32) + ;; This is called with two possible values, so we cannot optimize. + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls (type $none_=>_none) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (ref.func $reffed1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (ref.func $reffed1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (ref.func $reffed2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_ref + ;; CHECK-NEXT: (i32.const 99999) + ;; CHECK-NEXT: (ref.func $reffed2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + ;; Call $i1 twice with the same value, and $i2 twice with different values. + ;; Note that structurally the types are identical, but we still + ;; differentiate them, allowing us to optimize. + (call_ref + (i32.const 42) + (ref.func $reffed1) + ) + (call_ref + (i32.const 42) + (ref.func $reffed1) + ) + (call_ref + (i32.const 1337) + (ref.func $reffed2) + ) + (call_ref + (i32.const 99999) + (ref.func $reffed2) + ) + ) +) diff --git a/test/lit/passes/gufa-tags.wast b/test/lit/passes/gufa-tags.wast new file mode 100644 index 000000000..8e43dc932 --- /dev/null +++ b/test/lit/passes/gufa-tags.wast @@ -0,0 +1,111 @@ +;; 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 + +;; Two tags with different values. +(module + ;; CHECK: (type $i32_=>_none (func (param i32))) + + ;; CHECK: (type $f32_=>_none (func (param f32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (tag $tag$i32 (param i32)) + (tag $tag$i32 (param i32)) + ;; CHECK: (tag $tag$f32 (param f32)) + (tag $tag$f32 (param f32)) + + ;; CHECK: (func $test + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f32) + ;; CHECK-NEXT: (try $try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (throw $tag$i32 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag$i32 + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag$f32 + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop f32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (try + (do + (throw $tag$i32 + (i32.const 42) + ) + ) + (catch $tag$i32 + ;; We always throw a 42 to this tag, so we can optimize this. + (drop + (pop i32) + ) + ) + (catch $tag$f32 + ;; We never actually throw this, so it can be turned into an + ;; unreachable. + (drop + (pop f32) + ) + ) + ) + ) + + ;; CHECK: (func $bar (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try $try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag$i32 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $bar (result i32) + ;; Like the first case, we can optimize the pop here. The pop and the try + ;; body agree on the value, 42, so we can replace the entire try in theory, + ;; but we should not - removing the try would leave a pop without a proper + ;; parent (that is a problem even though the try does not have a name). We + ;; can still emit a 42 for the try, but must leave the try right before it, + ;; dropped. + (try (result i32) + (do + (i32.const 42) + ) + (catch $tag$i32 + (pop i32) + ) + ) + ) +) diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast new file mode 100644 index 000000000..38dbf72fd --- /dev/null +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -0,0 +1,2753 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --nominal --remove-unused-names --gufa -all -S -o - | filecheck %s +;; (remove-unused-names is added to test fallthrough values without a block +;; name getting in the way) + +;; This is almost identical to cfp.wast, and is meant to facilitate comparisons +;; between the passes - in particular, gufa should do everything cfp can do, +;; although it may do it differently. Changes include: +;; +;; * Tests must avoid things gufa optimizes away that would make the test +;; irrelevant. In particular, parameters to functions that are never called +;; will be turned to unreachable by gufa, so instead make those calls to +;; imports. Gufa will also realize that passing ref.null as the reference of +;; a struct.get/set will trap, so we must actually allocate something. +;; * Gufa optimizes in a more general way. Cfp will turn a struct.get whose +;; value it infers into a ref.as_non_null (to preserve the trap if the ref is +;; null) followed by the constant. Gufa has no special handling for +;; struct.get, so it will use its normal pattern there, of a drop of the +;; struct.get followed by the constant. (Other passes can remove the +;; dropped operation, like vacuum in trapsNeverHappen mode). +;; * Gufa's more general optimizations can remove more unreachable code, as it +;; checks for effects (and removes effectless code). +;; +;; This file could also run cfp in addition to gufa, but the aforementioned +;; changes cause cfp to behave differently in some cases, which could lead to +;; more confusion than benefit - the reader would not be able to compare the two +;; outputs and see cfp as "correct" which gufa should match. +;; +;; Note that there is some overlap with gufa-refs.wast in some places, but +;; intentionally no tests are removed here compared to cfp.wast, to make it +;; simple to map the original cfp tests to their ported versions here. + +(module + (type $struct (struct i32)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $impossible-get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $impossible-get + (drop + ;; This type is never created, so a get is impossible, and we will trap + ;; anyhow. So we can turn this into an unreachable. + (struct.get $struct 0 + (ref.null $struct) + ) + ) + ) +) + +(module + (type $struct (struct i64)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The only place this type is created is with a default value, and so we + ;; can optimize the get into a constant (note that no drop of the + ;; ref is needed: the optimizer can see that the struct.get cannot trap, as + ;; its reference is non-nullable). + (drop + (struct.get $struct 0 + (struct.new_default_with_rtt $struct + (rtt.canon $struct) + ) + ) + ) + ) +) + +(module + (type $struct (struct f32)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The only place this type is created is with a constant value, and so we + ;; can optimize to a constant, the same as above (but the constant was + ;; passed in, as opposed to being a default value as in the last testcase). + (drop + (struct.get $struct 0 + (struct.new_with_rtt $struct + (f32.const 42) + (rtt.canon $struct) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field f32) data)) + (type $struct (struct f32)) + + ;; CHECK: (type $none_=>_f32 (func_subtype (result f32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result f32))) + (import "a" "b" (func $import (result f32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The value given is not a constant, and so we cannot optimize. + (drop + (struct.get $struct 0 + (struct.new_with_rtt $struct + (call $import) + (rtt.canon $struct) + ) + ) + ) + ) +) + +;; Create in one function, get in another. The 10 should be forwarded to the +;; get. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + ;; The reference will be dropped here, and not removed entirely, because + ;; the optimizer thinks it might have side effects (since it has a call). + ;; But the forwarded value, 10, is applied after that drop. + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +;; As before, but with the order of functions reversed to check for any ordering +;; issues. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + ) +) + +;; Different values assigned in the same function, in different struct.news, +;; so we cannot optimize the struct.get away. +(module + ;; CHECK: (type $struct (struct_subtype (field f32) data)) + (type $struct (struct f32)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (f32.const 1337) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.new_with_rtt $struct + (f32.const 42) + (rtt.canon $struct) + ) + ) + ;; (A better analysis could see that the first struct.new is dropped and its + ;; value cannot reach this struct.get.) + (drop + (struct.get $struct 0 + (struct.new_with_rtt $struct + (f32.const 1337) + (rtt.canon $struct) + ) + ) + ) + ) +) + +;; Different values assigned in different functions, and one is a struct.set. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut f32)) data)) + (type $struct (struct (mut f32))) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (f32.const 42) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $set (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (f32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (struct.set $struct 0 + (call $create) + (f32.const 1337) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + ;; (A better analysis could see that only $create's value can reach here.) + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +;; As the last testcase, but the values happen to coincide, so we can optimize +;; the get into a constant. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut f32)) data)) + (type $struct (struct (mut f32))) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (f32.const 42) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $set (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (struct.set $struct 0 + (call $create) + (f32.const 42) ;; The last testcase had 1337 here. + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +;; Check that we look into the fallthrough value that is assigned. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut f32)) data)) + (type $struct (struct (mut f32))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + ;; Fall though a 42. The block can be optimized to a constant. + (block $named (result f32) + (nop) + (f32.const 42) + ) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $set (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (block (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (if (result f32) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (struct.set $struct 0 + (call $create) + ;; Fall though a 42 via an if. + (if (result f32) + (call $import) + (unreachable) + (f32.const 42) + ) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + ;; This can be inferred to be 42 since both the new and the set write that + ;; value. + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +;; Test a function reference instead of a number. +(module + (type $struct (struct funcref)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (elem declare func $test) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $struct 0 + (struct.new_with_rtt $struct + (ref.func $test) + (rtt.canon $struct) + ) + ) + ) + ) +) + +;; Test for unreachable creations, sets, and gets. +(module + (type $struct (struct (mut i32))) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.new_with_rtt $struct + (i32.const 10) + (unreachable) + ) + ) + (struct.set $struct 0 + (struct.get $struct 0 + (unreachable) + ) + (i32.const 20) + ) + ) +) + +;; Subtyping: Create a supertype and get a subtype. As we never create a +;; subtype, the get must trap anyhow (the reference it receives can +;; only be null in this closed world). +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + (type $substruct (struct_subtype i32 $struct)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + ;; As the get must trap, we can optimize to an unreachable here. + (drop + (struct.get $substruct 0 + (ref.cast_static $substruct + (call $create) + ) + ) + ) + ) +) + +;; As above, but in addition to a new of $struct also add a set. The set, +;; however, cannot write to the subtype, so we still know that any reads from +;; the subtype must trap. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + (type $substruct (struct_subtype (mut i32) $struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + ) + + ;; CHECK: (func $set (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + (struct.set $struct 0 + (call $create) + (i32.const 10) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (drop + (struct.get $substruct 0 + (ref.cast_static $substruct + (call $create) + ) + ) + ) + ) +) + +;; As above, pass the created supertype through a local and a cast on the way +;; to a read of the subtype. Still, no actual instance of the subtype can +;; appear in the get, so we can optimize to an unreachable. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) $struct)) + (type $substruct (struct_subtype (mut i32) $struct)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref null $substruct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $ref (ref null $struct)) + (local.set $ref + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (i32.const 10) + ) + (drop + ;; This must trap, so we can add an unreachable. + (struct.get $substruct 0 + ;; Only a null can pass through here, as the cast would not allow a ref + ;; to $struct. A null looks possible to the pass due to the default + ;; value of the local $ref - an SSA analysis would remove that. For now, + ;; we'll optimize the ref.cast to have a null after it. + (ref.cast_static $substruct + (local.get $ref) + ) + ) + ) + ) +) + +;; Subtyping: Create a subtype and get a supertype. The get must receive a +;; reference to the subtype and so we can infer the value of the get. +(module + (type $substruct (struct_subtype i32 f64 $struct)) + + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $struct 0 + (struct.new_with_rtt $substruct + (i32.const 10) + (f64.const 3.14159) + (rtt.canon $substruct) + ) + ) + ) + ) +) + +;; Subtyping: Create both a subtype and a supertype, with identical constants +;; for the shared field, and get the supertype. +(module + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + ;; CHECK: (type $substruct (struct_subtype (field i32) (field f64) $struct)) + (type $substruct (struct_subtype i32 f64 $struct)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $substruct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (rtt.canon $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; We can infer the value here must be 10. + (drop + (struct.get $struct 0 + (select + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + (struct.new_with_rtt $substruct + (i32.const 10) + (f64.const 3.14159) + (rtt.canon $substruct) + ) + (call $import) + ) + ) + ) + ) +) + +;; Subtyping: Create both a subtype and a supertype, with different constants +;; for the shared field, preventing optimization, as a get of the +;; supertype may receive an instance of the subtype. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $substruct (struct_subtype (field i32) (field f64) $struct)) + (type $substruct (struct_subtype i32 f64 $struct)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (rtt.canon $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $struct 0 + (select + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + (struct.new_with_rtt $substruct + (i32.const 20) ;; this constant changed + (f64.const 3.14159) + (rtt.canon $substruct) + ) + (call $import) + ) + ) + ) + ) +) + +;; Subtyping: Create both a subtype and a supertype, with different constants +;; for the shared field, but get from the subtype. The field is +;; shared between the types, but we only create the subtype with +;; one value, so we can optimize. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + + ;; CHECK: (type $substruct (struct_subtype (field i32) (field f64) $struct)) + (type $substruct (struct_subtype i32 f64 $struct)) + + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (rtt.canon $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $struct 0 + ;; This cast is added, ensuring only a $substruct can reach the get. + (ref.cast_static $substruct + (select + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + (struct.new_with_rtt $substruct + (i32.const 20) + (f64.const 3.14159) + (rtt.canon $substruct) + ) + (call $import) + ) + ) + ) + ) + ) +) + +;; As above, but add a set of $struct. The set prevents the optimization. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct)) + (type $substruct (struct_subtype (mut i32) f64 $struct)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (rtt.canon $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $substruct 0 + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $ref (ref null $struct)) + (local.set $ref + (select + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + (struct.new_with_rtt $substruct + (i32.const 20) + (f64.const 3.14159) + (rtt.canon $substruct) + ) + (call $import) + ) + ) + ;; This set is added. Even though the type is the super, this may write to + ;; the child, and so we cannot optimize. + (struct.set $struct 0 + (local.get $ref) + (i32.const 10) + ) + (drop + (struct.get $substruct 0 + (ref.cast_static $substruct + (local.get $ref) + ) + ) + ) + ) +) + +;; As above, but now the constant in the set agrees with the substruct value, +;; so we can optimize. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $substruct (struct_subtype (field (mut i32)) (field f64) $struct)) + (type $substruct (struct_subtype (mut i32) f64 $struct)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new_with_rtt $substruct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (rtt.canon $substruct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $substruct 0 + ;; CHECK-NEXT: (ref.cast_static $substruct + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (local $ref (ref null $struct)) + (local.set $ref + (select + (struct.new_with_rtt $struct + (i32.const 10) + (rtt.canon $struct) + ) + (struct.new_with_rtt $substruct + (i32.const 20) + (f64.const 3.14159) + (rtt.canon $substruct) + ) + (call $import) + ) + ) + (struct.set $struct 0 + (local.get $ref) + ;; This now writes the same value as in the $substruct already has, 20, so + ;; we can optimize the get below (which must contain a $substruct). + (i32.const 20) + ) + (drop + (struct.get $substruct 0 + (ref.cast_static $substruct + (local.get $ref) + ) + ) + ) + ) +) + +;; Multi-level subtyping, check that we propagate not just to the immediate +;; supertype but all the way as needed. +(module + ;; CHECK: (type $struct1 (struct_subtype (field i32) data)) + (type $struct1 (struct_subtype i32 data)) + + ;; CHECK: (type $struct2 (struct_subtype (field i32) (field f64) $struct1)) + (type $struct2 (struct_subtype i32 f64 $struct1)) + + ;; CHECK: (type $struct3 (struct_subtype (field i32) (field f64) (field anyref) $struct2)) + (type $struct3 (struct_subtype i32 f64 anyref $struct2)) + + ;; CHECK: (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct3|) (result (ref $struct3)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct3 + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (rtt.canon $struct3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct3)) + (struct.new_with_rtt $struct3 + (i32.const 20) + (f64.const 3.14159) + (ref.null any) + (rtt.canon $struct3) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (local $ref (ref null $struct3)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 0 + ;; CHECK-NEXT: (local.get $ref) + ;; 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 $struct3 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 1 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (local $ref (ref null $struct3)) + (local.set $ref + (call $create) + ) + ;; Get field 0 from $struct1. This can be optimized to a constant since + ;; we only ever created an instance of struct3 with a constant there - the + ;; reference must point to a $struct3. The same happens in all the other + ;; gets below as well, all optimize to constants. + (drop + (struct.get $struct1 0 + (local.get $ref) + ) + ) + ;; Get both fields of $struct2. + (drop + (struct.get $struct2 0 + (local.get $ref) + ) + ) + (drop + (struct.get $struct2 1 + (local.get $ref) + ) + ) + ;; Get all 3 fields of $struct3 + (drop + (struct.get $struct3 0 + (local.get $ref) + ) + ) + (drop + (struct.get $struct3 1 + (local.get $ref) + ) + ) + (drop + (struct.get $struct3 2 + (local.get $ref) + ) + ) + ) +) + +;; Multi-level subtyping with conflicts. The even-numbered fields will get +;; different values in the sub-most type. Create the top and bottom types, but +;; not the middle one. +(module + ;; CHECK: (type $struct1 (struct_subtype (field i32) (field i32) data)) + (type $struct1 (struct i32 i32)) + + ;; CHECK: (type $struct2 (struct_subtype (field i32) (field i32) (field f64) (field f64) $struct1)) + (type $struct2 (struct_subtype i32 i32 f64 f64 $struct1)) + + ;; CHECK: (type $struct3 (struct_subtype (field i32) (field i32) (field f64) (field f64) (field anyref) (field anyref) $struct2)) + (type $struct3 (struct_subtype i32 i32 f64 f64 anyref anyref $struct2)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_anyref (func_subtype (result anyref) func)) + + ;; CHECK: (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func)) + + ;; CHECK: (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func)) + + ;; CHECK: (import "a" "b" (func $import (result anyref))) + (import "a" "b" (func $import (result anyref))) + + ;; CHECK: (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (rtt.canon $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create1 (result (ref $struct1)) + (struct.new_with_rtt $struct1 + (i32.const 10) + (i32.const 20) + (rtt.canon $struct1) + ) + ) + + ;; CHECK: (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 999) + ;; CHECK-NEXT: (f64.const 2.71828) + ;; CHECK-NEXT: (f64.const 9.9999999) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (rtt.canon $struct3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create3 (result (ref $struct3)) + (struct.new_with_rtt $struct3 + (i32.const 10) + (i32.const 999) ;; use a different value here + (f64.const 2.71828) + (f64.const 9.9999999) + (ref.null any) + (call $import) ;; use an unknown value here, which can never be + ;; optimized. + (rtt.canon $struct3) + ) + ) + + ;; CHECK: (func $get-1 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-1 + ;; Get all the fields of all the structs. First, create $struct1 and get + ;; its fields. Even though there are subtypes with different fields for some + ;; of them, we can optimize these using exact type info, as this must be a + ;; $struct1 and nothing else. + (drop + (struct.get $struct1 0 + (call $create1) + ) + ) + (drop + (struct.get $struct1 1 + (call $create1) + ) + ) + ) + + ;; CHECK: (func $get-2 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 2.71828) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 9.9999999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-2 + ;; $struct2 is never created, instead create a $struct3. We can optimize, + ;; since $struct1's values are not relevant and cannot confuse us. + ;; trap. + (drop + (struct.get $struct2 0 + (call $create3) + ) + ) + (drop + (struct.get $struct2 1 + (call $create3) + ) + ) + (drop + (struct.get $struct2 2 + (call $create3) + ) + ) + (drop + (struct.get $struct2 3 + (call $create3) + ) + ) + ) + + ;; CHECK: (func $get-3 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 2.71828) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 9.9999999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct3 5 + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-3 + ;; We can optimize all these (where the field is constant). + (drop + (struct.get $struct3 0 + (call $create3) + ) + ) + (drop + (struct.get $struct3 1 + (call $create3) + ) + ) + (drop + (struct.get $struct3 2 + (call $create3) + ) + ) + (drop + (struct.get $struct3 3 + (call $create3) + ) + ) + (drop + (struct.get $struct3 4 + (call $create3) + ) + ) + (drop + (struct.get $struct3 5 + (call $create3) + ) + ) + ) +) + +;; Multi-level subtyping with a different value in the middle of the chain. +(module + ;; CHECK: (type $struct1 (struct_subtype (field (mut i32)) data)) + (type $struct1 (struct (mut i32))) + ;; CHECK: (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1)) + (type $struct2 (struct_subtype (mut i32) f64 $struct1)) + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2)) + (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func)) + + ;; CHECK: (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func)) + + ;; CHECK: (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create1 (result (ref $struct1)) + (struct.new_with_rtt $struct1 + (i32.const 10) + (rtt.canon $struct1) + ) + ) + + ;; CHECK: (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct2 + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (rtt.canon $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create2 (result (ref $struct2)) + (struct.new_with_rtt $struct2 + (i32.const 9999) ;; use a different value here + (f64.const 0) + (rtt.canon $struct2) + ) + ) + + ;; CHECK: (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (rtt.canon $struct3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create3 (result (ref $struct3)) + (struct.new_with_rtt $struct3 + (i32.const 10) + (f64.const 0) + (ref.null any) + (rtt.canon $struct3) + ) + ) + + ;; CHECK: (func $get-precise (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-precise + ;; Get field 0 in all the types. We know precisely what the type is in each + ;; case here, so we can optimize all of these. + (drop + (struct.get $struct1 0 + (call $create1) + ) + ) + (drop + (struct.get $struct2 0 + (call $create2) + ) + ) + (drop + (struct.get $struct3 0 + (call $create3) + ) + ) + ) + + ;; CHECK: (func $get-imprecise-1 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $struct1)) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct1 0 + ;; CHECK-NEXT: (select (result (ref $struct1)) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct1 0 + ;; CHECK-NEXT: (select (result (ref $struct1)) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-imprecise-1 + ;; Check the results of reading from a ref that can be one of two things. + ;; We check all permutations in the arms of the select in this function and + ;; the next two. + ;; + ;; Atm we can only optimize when the ref is the same in both arms, since + ;; even if two different types agree on the value (like $struct1 and + ;; $struct3 do), once we see two different types we already see the type as + ;; imprecise, and $struct2 in the middle has a different value, so imprecise + ;; info is not enough. + (drop + (struct.get $struct1 0 + (select + (call $create1) + (call $create1) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create1) + (call $create2) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create1) + (call $create3) + (call $import) + ) + ) + ) + ) + + ;; CHECK: (func $get-imprecise-2 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct1 0 + ;; CHECK-NEXT: (select (result (ref $struct1)) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $struct2)) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct2 0 + ;; CHECK-NEXT: (select (result (ref $struct2)) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-imprecise-2 + (drop + (struct.get $struct1 0 + (select + (call $create2) + (call $create1) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create2) + (call $create2) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create2) + (call $create3) + (call $import) + ) + ) + ) + ) + + ;; CHECK: (func $get-imprecise-3 (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct1 0 + ;; CHECK-NEXT: (select (result (ref $struct1)) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct2 0 + ;; CHECK-NEXT: (select (result (ref $struct2)) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select (result (ref $struct3)) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-imprecise-3 + (drop + (struct.get $struct1 0 + (select + (call $create3) + (call $create1) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create3) + (call $create2) + (call $import) + ) + ) + ) + (drop + (struct.get $struct1 0 + (select + (call $create3) + (call $create3) + (call $import) + ) + ) + ) + ) +) + +;; As above, but add not just a new of the middle class with a different value +;; but also a set. We can see that the set just affects the middle class, +;; though, so it is not a problem. +(module + ;; CHECK: (type $struct1 (struct_subtype (field (mut i32)) data)) + (type $struct1 (struct (mut i32))) + ;; CHECK: (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1)) + (type $struct2 (struct_subtype (mut i32) f64 $struct1)) + ;; CHECK: (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2)) + (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func)) + + ;; CHECK: (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func)) + + ;; CHECK: (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create1 (result (ref $struct1)) + (struct.new_with_rtt $struct1 + (i32.const 10) + (rtt.canon $struct1) + ) + ) + + ;; CHECK: (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct2 + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (rtt.canon $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create2 (result (ref $struct2)) + (struct.new_with_rtt $struct2 + (i32.const 9999) ;; use a different value here + (f64.const 0) + (rtt.canon $struct2) + ) + ) + + ;; CHECK: (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (rtt.canon $struct3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create3 (result (ref $struct3)) + (struct.new_with_rtt $struct3 + (i32.const 10) + (f64.const 0) + (ref.null any) + (rtt.canon $struct3) + ) + ) + + ;; CHECK: (func $get-precise (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct2 0 + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-precise + ;; The set only affects $struct2, exactly it and nothing else, so we can + ;; optimize all the gets in this function. + (drop + (struct.get $struct1 0 + (call $create1) + ) + ) + (struct.set $struct2 0 + (call $create2) + (i32.const 9999) + ) + (drop + (struct.get $struct2 0 + (call $create2) + ) + ) + (drop + (struct.get $struct3 0 + (call $create3) + ) + ) + ) +) + +;; As above, but the set is of a different value. +(module + ;; CHECK: (type $struct1 (struct_subtype (field (mut i32)) data)) + (type $struct1 (struct (mut i32))) + ;; CHECK: (type $struct2 (struct_subtype (field (mut i32)) (field f64) $struct1)) + (type $struct2 (struct_subtype (mut i32) f64 $struct1)) + ;; CHECK: (type $struct3 (struct_subtype (field (mut i32)) (field f64) (field anyref) $struct2)) + (type $struct3 (struct_subtype (mut i32) f64 anyref $struct2)) + + ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func)) + + ;; CHECK: (type $none_=>_ref|$struct1| (func_subtype (result (ref $struct1)) func)) + + ;; CHECK: (type $none_=>_ref|$struct2| (func_subtype (result (ref $struct2)) func)) + + ;; CHECK: (type $none_=>_ref|$struct3| (func_subtype (result (ref $struct3)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $create1 (type $none_=>_ref|$struct1|) (result (ref $struct1)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct1 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create1 (result (ref $struct1)) + (struct.new_with_rtt $struct1 + (i32.const 10) + (rtt.canon $struct1) + ) + ) + + ;; CHECK: (func $create2 (type $none_=>_ref|$struct2|) (result (ref $struct2)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct2 + ;; CHECK-NEXT: (i32.const 9999) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (rtt.canon $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create2 (result (ref $struct2)) + (struct.new_with_rtt $struct2 + (i32.const 9999) ;; use a different value here + (f64.const 0) + (rtt.canon $struct2) + ) + ) + + ;; CHECK: (func $create3 (type $none_=>_ref|$struct3|) (result (ref $struct3)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct3 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: (rtt.canon $struct3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create3 (result (ref $struct3)) + (struct.new_with_rtt $struct3 + (i32.const 10) + (f64.const 0) + (ref.null any) + (rtt.canon $struct3) + ) + ) + + ;; CHECK: (func $get-precise (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct2 0 + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: (i32.const 1234) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct2 0 + ;; CHECK-NEXT: (call $create2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-precise + (drop + (struct.get $struct1 0 + (call $create1) + ) + ) + ;; This set of a different value limits our ability to optimize the get + ;; after us. But the get before us and the one at the very end remain + ;; optimized - changes to $struct2 do not confuse the other types. + (struct.set $struct2 0 + (call $create2) + (i32.const 1234) + ) + (drop + (struct.get $struct2 0 + (call $create2) + ) + ) + (drop + (struct.get $struct3 0 + (call $create3) + ) + ) + ) +) + +;; Test for a struct with multiple fields, some of which are constant and hence +;; optimizable, and some not. Also test that some have the same type. +(module + ;; CHECK: (type $struct (struct_subtype (field i32) (field f64) (field i32) (field f64) (field i32) data)) + (type $struct (struct i32 f64 i32 f64 i32)) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_with_rtt $struct + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (f64.abs + ;; CHECK-NEXT: (f64.const 2.71828) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_with_rtt $struct + (i32.eqz (i32.const 10)) ;; not a constant (as far as this pass knows) + (f64.const 3.14159) + (i32.const 20) + (f64.abs (f64.const 2.71828)) ;; not a constant + (i32.const 30) + (rtt.canon $struct) + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f64) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (f64.const 3.14159) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 3 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (drop + (struct.get $struct 0 + (call $create) + ) + ) + (drop + (struct.get $struct 1 + (call $create) + ) + ) + (drop + (struct.get $struct 2 + (call $create) + ) + ) + (drop + (struct.get $struct 3 + (call $create) + ) + ) + (drop + (struct.get $struct 4 + (call $create) + ) + ) + ;; Also test for multiple gets of the same field. + (drop + (struct.get $struct 4 + (call $create) + ) + ) + ) +) + +;; Never create A, but have a set to its field. A subtype B has no creates nor +;; sets, and the final subtype C has a create and a get. The set to A should +;; apply to it, preventing optimization. +(module + ;; CHECK: (type $A (struct_subtype (field (mut i32)) data)) + + ;; CHECK: (type $B (struct_subtype (field (mut i32)) $A)) + + ;; CHECK: (type $C (struct_subtype (field (mut i32)) $B)) + (type $C (struct_subtype (mut i32) $B)) + + (type $A (struct (mut i32))) + + (type $B (struct_subtype (mut i32) $A)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $none_=>_ref|$C| (func_subtype (result (ref $C)) func)) + + ;; CHECK: (func $create-C (type $none_=>_ref|$C|) (result (ref $C)) + ;; CHECK-NEXT: (struct.new_with_rtt $C + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (rtt.canon $C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create-C (result (ref $C)) + (struct.new_with_rtt $C + (i32.const 10) + (rtt.canon $C) + ) + ) + ;; CHECK: (func $set (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $A 0 + ;; CHECK-NEXT: (ref.cast_static $A + ;; CHECK-NEXT: (call $create-C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $set + ;; Set of $A, but the reference is actually a $C. We add a cast to make sure + ;; the type is $A, which should not confuse us: this set does alias the data + ;; in $C, which means we cannot optimize in the function $get below. + (struct.set $A 0 + (ref.cast_static $A + (call $create-C) + ) + (i32.const 20) ;; different value than in $create + ) + ) + ;; CHECK: (func $get (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $C 0 + ;; CHECK-NEXT: (call $create-C) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get + (drop + (struct.get $C 0 + (call $create-C) + ) + ) + ) +) + +;; Copies of a field to itself can be ignored. As a result, we can optimize both +;; of the gets here. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new_default_with_rtt $struct + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new_default_with_rtt $struct + (rtt.canon $struct) + ) + ) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; This copy does not actually introduce any new possible values, and so it + ;; remains true that the only possible value is the default 0, so we can + ;; optimize the get below to a 0 (and also the get in the set). + (struct.set $struct 0 + (call $create) + (struct.get $struct 0 + (call $create) + ) + ) + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +;; Test of a near-copy, of a similar looking field (same index, and same field +;; type) but in a different struct. The value in both structs is the same, so +;; we can optimize. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut f32)) (field (mut i32)) data)) + (type $struct (struct (mut f32) (mut i32))) + ;; CHECK: (type $other (struct_subtype (field (mut f64)) (field (mut i32)) data)) + (type $other (struct (mut f64) (mut i32))) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_ref|$other| (func_subtype (result (ref $other)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create-struct (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create-struct (result (ref $struct)) + (struct.new $struct + (f32.const 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $create-other (type $none_=>_ref|$other|) (result (ref $other)) + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create-other (result (ref $other)) + (struct.new $other + (f64.const 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 1 + ;; CHECK-NEXT: (call $create-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create-other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; We copy data between the types, but the possible values of their fields + ;; are the same anyhow, so we can optimize all the gets to 42. + (struct.set $struct 1 + (call $create-struct) + (struct.get $other 1 + (call $create-other) + ) + ) + (drop + (struct.get $struct 1 + (call $create-struct) + ) + ) + ) +) + +;; As above, but each struct has a different value, so copying between them +;; inhibits one optimization. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut f32)) (field (mut i32)) data)) + (type $struct (struct (mut f32) (mut i32))) + ;; CHECK: (type $other (struct_subtype (field (mut f64)) (field (mut i32)) data)) + (type $other (struct (mut f64) (mut i32))) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_ref|$other| (func_subtype (result (ref $other)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create-struct (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (f32.const 0) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create-struct (result (ref $struct)) + (struct.new $struct + (f32.const 0) + (i32.const 42) + ) + ) + + ;; CHECK: (func $create-other (type $none_=>_ref|$other|) (result (ref $other)) + ;; CHECK-NEXT: (struct.new $other + ;; CHECK-NEXT: (f64.const 0) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create-other (result (ref $other)) + (struct.new $other + (f64.const 0) + (i32.const 1337) ;; this changed + ) + ) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 1 + ;; CHECK-NEXT: (call $create-struct) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create-other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 1 + ;; CHECK-NEXT: (call $create-struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As this is not a copy between a struct and itself, we cannot optimize + ;; the last get lower down: $struct has both 42 and 1337 written to it. + (struct.set $struct 1 + (call $create-struct) + (struct.get $other 1 + (call $create-other) + ) + ) + (drop + (struct.get $struct 1 + (call $create-struct) + ) + ) + ) +) + +;; Similar to the above, but different fields within the same struct. +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) (field (mut i32)) data)) + (type $struct (struct (mut i32) (mut i32))) + + ;; CHECK: (type $none_=>_ref|$struct| (func_subtype (result (ref $struct)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (func $create (type $none_=>_ref|$struct|) (result (ref $struct)) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $create (result (ref $struct)) + (struct.new $struct + (i32.const 42) + (i32.const 1337) + ) + ) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (call $create) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; The get from field 1 can be optimized to 1337, but field 0 has this + ;; write to it, which means it can contain 42 or 1337, so we cannot + ;; optimize. + (struct.set $struct 0 + (call $create) + (struct.get $struct 1 + (call $create) + ) + ) + (drop + (struct.get $struct 0 + (call $create) + ) + ) + ) +) + +(module + ;; CHECK: (type $A (struct_subtype data)) + (type $A (struct)) + (type $B (struct (ref $A))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global (ref $A) (struct.new_default $A)) + (global $global (ref $A) (struct.new $A)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; An immutable global is the only thing written to this field, so we can + ;; propagate the value to the struct.get and replace it with a global.get. + (drop + (struct.get $B 0 + (struct.new $B + (global.get $global) + ) + ) + ) + ) +) + +;; As above, but with an imported global, which we can also optimize (since it +;; is still immutable). +(module + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (import "a" "b" (global $global i32)) + (import "a" "b" (global $global i32)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global i32 (i32.const 42)) + (global $global i32 (i32.const 42)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; An immutable global is the only thing written to this field, so we can + ;; propagate the value to the struct.get to get 42 here (even better than a + ;; global.get as in the last examples). + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global (mut i32) (i32.const 42)) + (global $global (mut i32) (i32.const 42)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but the global is *not* immutable. Still, it has no writes, so + ;; we can optimize. + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field i32) data)) + (type $struct (struct i32)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global (mut i32) (i32.const 42)) + (global $global (mut i32) (i32.const 42)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (global.set $global + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but the global does have another write of another value, which + ;; prevents optimization. + (global.set $global + (i32.const 1337) + ) + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global i32 (i32.const 42)) + (global $global i32 (i32.const 42)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but there is another set of the field. It writes the same + ;; value, though, so that is fine. Also, the struct's field is now mutable + ;; as well to allow that, and that also does not prevent optimization. + (struct.set $struct 0 + (struct.new $struct + (global.get $global) + ) + (i32.const 42) + ) + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global i32 (i32.const 42)) + (global $global i32 (i32.const 42)) + ;; CHECK: (global $global-2 i32 (i32.const 1337)) + (global $global-2 i32 (i32.const 1337)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but set a different global, which prevents optimization of the + ;; struct.get below. + (struct.set $struct 0 + (struct.new $struct + (global.get $global) + ) + (global.get $global-2) + ) + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + ;; CHECK: (type $struct (struct_subtype (field (mut i32)) data)) + (type $struct (struct (mut i32))) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (global $global i32 (i32.const 42)) + (global $global i32 (i32.const 42)) + + ;; CHECK: (func $test (type $none_=>_none) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; As above, but set a constant, which means we are mixing constants with + ;; globals, which prevents the optimization of the struct.get. + (struct.set $struct 0 + (struct.new $struct + (global.get $global) + ) + (i32.const 1337) + ) + (drop + (struct.get $struct 0 + (struct.new $struct + (global.get $global) + ) + ) + ) + ) +) + +(module + ;; Test a global type other than i32. Arrays of structs are a realistic case + ;; as they are used to implement itables. + + ;; CHECK: (type $vtable (struct_subtype (field funcref) data)) + (type $vtable (struct funcref)) + + ;; CHECK: (type $itable (array_subtype (ref $vtable) data)) + (type $itable (array (ref $vtable))) + + (type $object (struct (field $itable (ref $itable)))) + + ;; CHECK: (type $none_=>_funcref (func_subtype (result funcref) func)) + + ;; CHECK: (global $global (ref $itable) (array.init_static $itable + ;; CHECK-NEXT: (struct.new $vtable + ;; CHECK-NEXT: (ref.null func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $vtable + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $global (ref $itable) (array.init_static $itable + (struct.new $vtable + (ref.null func) + ) + (struct.new $vtable + (ref.func $test) + ) + )) + + ;; CHECK: (func $test (type $none_=>_funcref) (result funcref) + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (array.get $itable + ;; CHECK-NEXT: (global.get $global) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result funcref) + ;; Realistic usage of an itable: read an item from it, then a func from + ;; that, and return the value (all verifying that the types are correct + ;; after optimization). + ;; + ;; We optimize some of this, but stop at reading from the immutable global. + ;; To continue we'd need to track the fields of allocated objects, or look + ;; at immutable globals directly, neither of which we do yet. TODO + (struct.get $vtable 0 + (array.get $itable + (struct.get $object $itable + (struct.new $object + (global.get $global) + ) + ) + (i32.const 1) + ) + ) + ) +) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast new file mode 100644 index 000000000..01b83ce06 --- /dev/null +++ b/test/lit/passes/gufa.wast @@ -0,0 +1,922 @@ +;; 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 $none_=>_i32 (func (result i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) + + ;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + + ;; CHECK: (export "param-no" (func $param-no)) + + ;; CHECK: (func $never-called (param $param i32) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $never-called (param $param i32) (result i32) + ;; This function is never called, so no content is possible in $param, and + ;; we know this must be unreachable code that can be removed (replaced with + ;; an unreachable). + (local.get $param) + ) + + ;; CHECK: (func $foo (result i32) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $foo (result i32) + (i32.const 1) + ) + + ;; CHECK: (func $bar + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $bar + ;; Both arms of the select have identical values, 1. Inlining + + ;; OptimizeInstructions could of course discover that in this case, but + ;; GUFA can do so even without inlining. As a result the select will be + ;; dropped (due to the call which may have effects, we keep it), and at the + ;; end we emit the constant 1 for the value. + (drop + (select + (call $foo) + (i32.const 1) + (call $import) + ) + ) + ) + + ;; CHECK: (func $baz + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $foo) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $baz + (drop + (select + (call $foo) + ;; As above, but replace 1 with eqz(eqz(1)).This pass assumes any eqz + ;; etc is a new value, and so here we do not optimize the select (we do + ;; still optimize the call's result, though). + (i32.eqz + (i32.eqz + (i32.const 1) + ) + ) + (call $import) + ) + ) + ) + + ;; CHECK: (func $return (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + (func $return (result i32) + ;; Helper function that returns one result in a return and flows another + ;; out. There is nothing to optimize in this function, but see the caller + ;; below. + (if + (i32.const 0) + (return + (i32.const 1) + ) + ) + (i32.const 2) + ) + + ;; CHECK: (func $call-return + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-return + ;; The called function has two possible return values, so we cannot optimize + ;; anything here. + (drop + (call $return) + ) + ) + + ;; CHECK: (func $return-same (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $return-same (result i32) + ;; Helper function that returns the same result in a return as it flows out. + ;; This is the same as above, but now the values are identical, and the + ;; function must return 1. There is nothing to optimize in this function, + ;; but see the caller below. + (if + (i32.const 0) + (return + (i32.const 1) + ) + ) + (i32.const 1) + ) + + ;; CHECK: (func $call-return-same + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $return-same) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $call-return-same + ;; Unlike in $call-return, now we can optimize here. + (drop + (call $return-same) + ) + ) + + ;; CHECK: (func $local-no (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $local-no (result i32) + (local $x i32) + (if + (call $import) + (local.set $x + (i32.const 1) + ) + ) + ;; $x has two possible values, 1 and the default 0, so we cannot optimize + ;; anything here. + (local.get $x) + ) + + ;; CHECK: (func $local-yes (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $local-yes (result i32) + (local $x i32) + (if + (call $import) + (local.set $x + ;; As above, but now we set 0 here. We can optimize the local.get to 0 + ;; in this case. + (i32.const 0) + ) + ) + (local.get $x) + ) + + ;; CHECK: (func $param-no (param $param i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: (local.set $param + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $param) + ;; CHECK-NEXT: ) + (func $param-no (export "param-no") (param $param i32) (result i32) + (if + (local.get $param) + (local.set $param + (i32.const 1) + ) + ) + ;; $x has two possible values, the incoming param value and 1, so we cannot + ;; optimize, since the function is exported - anything on the outside could + ;; call it with values we are not aware of during the optimization. + (local.get $param) + ) + + ;; CHECK: (func $param-yes (param $param i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (local.set $param + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + (func $param-yes (param $param i32) (result i32) + (if + (local.get $param) + (local.set $param + (i32.const 1) + ) + ) + ;; As above, but now the function is not exported. That means it has no + ;; callers, so the only possible contents for $param are the local.set here, + ;; as this code is unreachable. We will infer a constant of 1 for all values + ;; of $param here. (With an SSA analysis, we could see that the first + ;; local.get must be unreachable, and optimize even better; as things are, + ;; we see the local.set and it is the only thing that sends values to the + ;; local.) + (local.get $param) + ) + + ;; CHECK: (func $cycle (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $cycle (param $x i32) (param $y i32) (result i32) + ;; Return 42, or else the result from a recursive call. The only possible + ;; value is 42, which we can optimize to. + ;; (Nothing else calls $cycle, so this is dead code in actuality, but this + ;; pass leaves such things to other passes.) + ;; Note that the first call passes constants for $x and $y which lets us + ;; optimize them too (as we see no other contents arrive to them). + (drop + (call $cycle + (i32.const 42) + (i32.const 1) + ) + ) + (select + (i32.const 42) + (call $cycle + (local.get $x) + (local.get $y) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $cycle-2 (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-2 + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $cycle-2 (param $x i32) (param $y i32) (result i32) + (drop + (call $cycle-2 + (i32.const 42) + (i32.const 1) + ) + ) + ;; As above, but flip one $x and $y on the first and last local.gets. We + ;; can see that $y must contain 1, and we cannot infer a value for $x (it + ;; is sent both 42 and $y which is 1). Even without $x, however, we can see + ;; the value leaving the select is 42, which means the call returns 42. + (select + (i32.const 42) + (call $cycle-2 + (local.get $y) + (local.get $y) + ) + (local.get $x) + ) + ) + + ;; CHECK: (func $cycle-3 (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-3 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-3 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $cycle-3 (param $x i32) (param $y i32) (result i32) + ;; Even adding a caller with a different value for $x does not prevent us + ;; from optimizing here. + (drop + (call $cycle-3 + (i32.const 1337) + (i32.const 1) + ) + ) + ;; As $cycle, but add an i32.eqz on $x. We can still optimize this, as + ;; while the eqz is a new value arriving in $x, we do not actually return + ;; $x, and again the only possible value flowing in the graph is 42. + (select + (i32.const 42) + (call $cycle-3 + (i32.eqz + (local.get $x) + ) + (local.get $y) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $cycle-4 (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-4 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (call $cycle-4 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cycle-4 (param $x i32) (param $y i32) (result i32) + (drop + (call $cycle-4 + (i32.const 1337) + (i32.const 1) + ) + ) + (select + ;; As above, but we have no constant here, but $x. We may now return $x or + ;; $eqz of $x, which means we cannot infer the result of the call. (But we + ;; can still infer the value of $y, which is 1.) + (local.get $x) + (call $cycle-4 + (i32.eqz + (local.get $x) + ) + (local.get $y) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $cycle-5 (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-5 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $cycle-5 + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: ) + (func $cycle-5 (param $x i32) (param $y i32) (result i32) + (drop + (call $cycle-5 + (i32.const 1337) + (i32.const 1) + ) + ) + (select + (local.get $x) + (call $cycle-5 + ;; As above, but now we return $x in both cases, so we can optimize, and + ;; infer the result is the 1337 which is passed in the earlier call. + (local.get $x) + (local.get $y) + ) + (local.get $y) + ) + ) + + ;; CHECK: (func $blocks + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $named (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (br $named + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $named0 (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (br $named0 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $blocks + ;; A block with a branch to it, which we can infer a constant for. + (drop + (block $named (result i32) + (if + (i32.const 0) + (br $named + (i32.const 1) + ) + ) + (i32.const 1) + ) + ) + ;; As above, but the two values reaching the block do not agree, so we + ;; should not optimize. + (drop + (block $named (result i32) + (if + (i32.const 0) + (br $named + (i32.const 2) ;; this changed + ) + ) + (i32.const 1) + ) + ) + ) +) + +(module + ;; CHECK: (type $i (func (param i32))) + (type $i (func (param i32))) + + (table 10 funcref) + (elem (i32.const 0) funcref + (ref.func $reffed) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $reffed) + + ;; CHECK: (export "table" (table $0)) + (export "table" (table 0)) + + ;; CHECK: (func $reffed (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed (param $x i32) + ;; This function is in the table, and the table is exported, so we cannot + ;; see all callers, and cannot infer the value here. + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls + ;; CHECK-NEXT: (call $reffed + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + (call $reffed + (i32.const 42) + ) + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + ) +) + +;; As above, but the table is not exported. We have a direct and an indirect +;; call with the same value, so we can optimize inside $reffed. +(module + ;; CHECK: (type $i (func (param i32))) + (type $i (func (param i32))) + + (table 10 funcref) + (elem (i32.const 0) funcref + (ref.func $reffed) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $reffed) + + ;; CHECK: (func $reffed (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed (param $x i32) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls + ;; CHECK-NEXT: (call $reffed + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + (call $reffed + (i32.const 42) + ) + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + ) +) + +;; As above but the only calls are indirect. +(module + ;; CHECK: (type $i (func (param i32))) + (type $i (func (param i32))) + + (table 10 funcref) + (elem (i32.const 0) funcref + (ref.func $reffed) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $reffed) + + ;; CHECK: (func $reffed (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed (param $x i32) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + ) +) + +;; As above but the indirect calls have different parameters, so we do not +;; optimize. +(module + ;; CHECK: (type $i (func (param i32))) + (type $i (func (param i32))) + + (table 10 funcref) + (elem (i32.const 0) funcref + (ref.func $reffed) + ) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $reffed) + + ;; CHECK: (func $reffed (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed (param $x i32) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 1337) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + (call_indirect (type $i) + (i32.const 1337) + (i32.const 0) + ) + ) +) + +;; As above but the second call is of another function type, so it does not +;; prevent us from optimizing even though it has a different value. +(module + ;; CHECK: (type $i (func (param i32))) + (type $i (func (param i32))) + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (type $f (func (param f32))) + (type $f (func (param f32))) + + (table 10 funcref) + (elem (i32.const 0) funcref + (ref.func $reffed) + ) + + ;; CHECK: (table $0 10 funcref) + + ;; CHECK: (elem (i32.const 0) $reffed) + + ;; CHECK: (func $reffed (param $x i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $reffed (param $x i32) + (drop + (local.get $x) + ) + ) + + ;; CHECK: (func $do-calls + ;; CHECK-NEXT: (call_indirect $0 (type $i) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $0 (type $f) + ;; CHECK-NEXT: (f32.const 1337) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $do-calls + (call_indirect (type $i) + (i32.const 42) + (i32.const 0) + ) + (call_indirect (type $f) + (f32.const 1337) + (i32.const 0) + ) + ) +) + +(module + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (func $const (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $const (result i32) + ;; Return a const to the caller below. + (i32.const 42) + ) + + ;; CHECK: (func $retcall (result i32) + ;; CHECK-NEXT: (return_call $const) + ;; CHECK-NEXT: ) + (func $retcall (result i32) + ;; Do a return call. This tests that we pass its value out as a result. + (return_call $const) + ) + + ;; CHECK: (func $caller + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $retcall) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller + ;; Call the return caller. We can optimize this value to 42. + (drop + (call $retcall) + ) + ) +) + +;; Imports have unknown values. +(module + ;; CHECK: (type $none_=>_i32 (func (result i32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (import "a" "b" (func $import (result i32))) + (import "a" "b" (func $import (result i32))) + + ;; CHECK: (func $internal (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $internal (result i32) + (i32.const 42) + ) + + ;; CHECK: (func $calls + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $internal) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls + (drop + (call $import) + ) + ;; For comparison, we can optimize this call to an internal function. + (drop + (call $internal) + ) + ) +) + +;; Test for nontrivial code in a global init. We need to process such code +;; normally and not hit any internal asserts (nothing can be optimized here). +(module + ;; CHECK: (global $global$0 i32 (i32.add + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: )) + (global $global$0 i32 + (i32.add + (i32.const 10) + (i32.const 20) + ) + ) +) |