summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/gtest/possible-contents.cpp4
-rw-r--r--test/lit/help/wasm-opt.test9
-rw-r--r--test/lit/help/wasm2js.test9
-rw-r--r--test/lit/passes/gufa-optimizing.wast62
-rw-r--r--test/lit/passes/gufa-refs.wast3616
-rw-r--r--test/lit/passes/gufa-tags.wast111
-rw-r--r--test/lit/passes/gufa-vs-cfp.wast2753
-rw-r--r--test/lit/passes/gufa.wast922
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)
+ )
+ )
+)