diff options
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) + ) + ) +) |