summaryrefslogtreecommitdiff
path: root/test/lit/passes
diff options
context:
space:
mode:
Diffstat (limited to 'test/lit/passes')
-rw-r--r--test/lit/passes/catch-pop-fixup-eh.wast6
-rw-r--r--test/lit/passes/coalesce-locals-gc.wast77
-rw-r--r--test/lit/passes/dae-gc.wast18
-rw-r--r--test/lit/passes/denan.wast1
-rw-r--r--test/lit/passes/directize_all-features.wast23
-rw-r--r--test/lit/passes/flatten.wast6
-rw-r--r--test/lit/passes/flatten_all-features.wast6
-rw-r--r--test/lit/passes/gto-removals.wast12
-rw-r--r--test/lit/passes/heap2local.wast107
-rw-r--r--test/lit/passes/inlining_all-features.wast12
-rw-r--r--test/lit/passes/inlining_splitting.wast2
-rw-r--r--test/lit/passes/local-cse_all-features.wast18
-rw-r--r--test/lit/passes/local-subtyping.wast161
-rw-r--r--test/lit/passes/opt_flatten.wast12
-rw-r--r--test/lit/passes/precompute-gc.wast58
-rw-r--r--test/lit/passes/roundtrip-gc.wast29
-rw-r--r--test/lit/passes/simplify-locals-gc-nn.wast99
-rw-r--r--test/lit/passes/simplify-locals-gc-validation.wast48
-rw-r--r--test/lit/passes/simplify-locals-gc.wast139
-rw-r--r--test/lit/passes/ssa.wast12
20 files changed, 619 insertions, 227 deletions
diff --git a/test/lit/passes/catch-pop-fixup-eh.wast b/test/lit/passes/catch-pop-fixup-eh.wast
index 51a703945..813bb6899 100644
--- a/test/lit/passes/catch-pop-fixup-eh.wast
+++ b/test/lit/passes/catch-pop-fixup-eh.wast
@@ -355,7 +355,7 @@
)
;; CHECK: (func $pop-non-defaultable-type-within-block
- ;; CHECK-NEXT: (local $0 (ref null $struct.A))
+ ;; CHECK-NEXT: (local $0 (ref $struct.A))
;; CHECK-NEXT: (try $try
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
@@ -366,9 +366,7 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: (throw $e-struct.A
;; CHECK-NEXT: (block (result (ref $struct.A))
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/coalesce-locals-gc.wast b/test/lit/passes/coalesce-locals-gc.wast
index 074c9caf0..0113af23c 100644
--- a/test/lit/passes/coalesce-locals-gc.wast
+++ b/test/lit/passes/coalesce-locals-gc.wast
@@ -1,7 +1,12 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --coalesce-locals -all -S -o - \
+;; RUN: wasm-opt %s --remove-unused-names --coalesce-locals -all -S -o - \
;; RUN: | filecheck %s
+;; --remove-unused-names is run to avoid adding names to blocks. Block names
+;; can prevent non-nullable local validation (we emit named blocks in the binary
+;; format, if we need them, but never emit unnamed ones), which affects some
+;; testcases.
+
(module
;; CHECK: (type $array (array (mut i8)))
(type $array (array (mut i8)))
@@ -61,4 +66,74 @@
(local.get $1)
)
)
+
+ ;; CHECK: (func $nn-dead
+ ;; CHECK-NEXT: (local $0 funcref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $nn-dead)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block $inner
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (ref.func $nn-dead)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $inner
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nn-dead
+ (local $x (ref func))
+ (local.set $x
+ (ref.func $nn-dead) ;; this will be removed, as it is not needed.
+ )
+ (block $inner
+ (local.set $x
+ (ref.func $nn-dead) ;; this is not enough for validation of the get, so we
+ ;; will end up making the local nullable.
+ )
+ ;; refer to $inner to keep the name alive (see the next testcase)
+ (br_if $inner
+ (i32.const 1)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $nn-dead-nameless
+ ;; CHECK-NEXT: (local $0 (ref func))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.func $nn-dead)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (ref.func $nn-dead)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $nn-dead-nameless
+ (local $x (ref func))
+ (local.set $x
+ (ref.func $nn-dead)
+ )
+ ;; As above, but now the block has no name. Nameless blocks do not interfere
+ ;; with validation, so we can keep the local non-nullable.
+ (block
+ (local.set $x
+ (ref.func $nn-dead)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
)
diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast
index 5ec272ea4..e6c83f0d0 100644
--- a/test/lit/passes/dae-gc.wast
+++ b/test/lit/passes/dae-gc.wast
@@ -66,13 +66,13 @@
)
)
;; A function that gets a non-nullable reference that is never used. We can
- ;; still create a nullable local for that parameter.
+ ;; still create a non-nullable local for that parameter.
;; CHECK: (func $get-nonnull
- ;; CHECK-NEXT: (local $0 (ref null ${}))
+ ;; CHECK-NEXT: (local $0 (ref ${}))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; NOMNL: (func $get-nonnull (type $none_=>_none)
- ;; NOMNL-NEXT: (local $0 (ref null ${}))
+ ;; NOMNL-NEXT: (local $0 (ref ${}))
;; NOMNL-NEXT: (nop)
;; NOMNL-NEXT: )
(func $get-nonnull (param $0 (ref ${}))
@@ -94,15 +94,13 @@
;; Test ref.func and ref.null optimization of constant parameter values.
(module
;; CHECK: (func $foo (param $0 (ref $none_=>_none))
- ;; CHECK-NEXT: (local $1 (ref null $none_=>_none))
+ ;; CHECK-NEXT: (local $1 (ref $none_=>_none))
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (ref.func $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $0)
@@ -110,15 +108,13 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $foo (type $ref|none_->_none|_=>_none) (param $0 (ref $none_=>_none))
- ;; NOMNL-NEXT: (local $1 (ref null $none_=>_none))
+ ;; NOMNL-NEXT: (local $1 (ref $none_=>_none))
;; NOMNL-NEXT: (local.set $1
;; NOMNL-NEXT: (ref.func $a)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (block
;; NOMNL-NEXT: (drop
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $1)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $1)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (local.get $0)
diff --git a/test/lit/passes/denan.wast b/test/lit/passes/denan.wast
index 47912bb1f..f92570f43 100644
--- a/test/lit/passes/denan.wast
+++ b/test/lit/passes/denan.wast
@@ -248,6 +248,7 @@
)
)
+
;; CHECK: (func $deNan32_0 (param $0 f32) (result f32)
;; CHECK-NEXT: (if (result f32)
;; CHECK-NEXT: (f32.eq
diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast
index 5e42e271e..491a7e064 100644
--- a/test/lit/passes/directize_all-features.wast
+++ b/test/lit/passes/directize_all-features.wast
@@ -1166,46 +1166,39 @@
)
;; CHECK: (func $select-non-nullable (param $x i32)
- ;; CHECK-NEXT: (local $1 (ref null $i32_=>_none))
+ ;; CHECK-NEXT: (local $1 (ref $i32_=>_none))
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (ref.func $select-non-nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (call $foo-ref
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $foo-ref
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; IMMUT: (func $select-non-nullable (param $x i32)
- ;; IMMUT-NEXT: (local $1 (ref null $i32_=>_none))
+ ;; IMMUT-NEXT: (local $1 (ref $i32_=>_none))
;; IMMUT-NEXT: (local.set $1
;; IMMUT-NEXT: (ref.func $select-non-nullable)
;; IMMUT-NEXT: )
;; IMMUT-NEXT: (if
;; IMMUT-NEXT: (local.get $x)
;; IMMUT-NEXT: (call $foo-ref
- ;; IMMUT-NEXT: (ref.as_non_null
- ;; IMMUT-NEXT: (local.get $1)
- ;; IMMUT-NEXT: )
+ ;; IMMUT-NEXT: (local.get $1)
;; IMMUT-NEXT: )
;; IMMUT-NEXT: (call $foo-ref
- ;; IMMUT-NEXT: (ref.as_non_null
- ;; IMMUT-NEXT: (local.get $1)
- ;; IMMUT-NEXT: )
+ ;; IMMUT-NEXT: (local.get $1)
;; IMMUT-NEXT: )
;; IMMUT-NEXT: )
;; IMMUT-NEXT: )
(func $select-non-nullable (param $x i32)
;; Test we can handle a non-nullable value when optimizing a select, during
- ;; which we place values in locals.
+ ;; which we place values in locals. The local can remain non-nullable since it
+ ;; dominates the uses.
(call_indirect (type $F)
(ref.func $select-non-nullable)
(select
diff --git a/test/lit/passes/flatten.wast b/test/lit/passes/flatten.wast
index e07c8fafb..0446f3ce1 100644
--- a/test/lit/passes/flatten.wast
+++ b/test/lit/passes/flatten.wast
@@ -5,14 +5,12 @@
;; CHECK: (type $simplefunc (func))
(type $simplefunc (func))
;; CHECK: (func $0 (param $0 (ref $simplefunc)) (result (ref $simplefunc))
- ;; CHECK-NEXT: (local $1 (ref null $simplefunc))
+ ;; CHECK-NEXT: (local $1 (ref $simplefunc))
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $0 (param $0 (ref $simplefunc)) (result (ref $simplefunc))
diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast
index c6512deeb..934706419 100644
--- a/test/lit/passes/flatten_all-features.wast
+++ b/test/lit/passes/flatten_all-features.wast
@@ -3518,16 +3518,14 @@
;; CHECK: (type $none_=>_funcref (func (result funcref)))
;; CHECK: (func $0 (result funcref)
- ;; CHECK-NEXT: (local $0 (ref null $none_=>_none))
+ ;; CHECK-NEXT: (local $0 (ref $none_=>_none))
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null $none_=>_none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $0 (result funcref)
diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast
index 73756fc7f..f75eb7483 100644
--- a/test/lit/passes/gto-removals.wast
+++ b/test/lit/passes/gto-removals.wast
@@ -438,7 +438,7 @@
;; CHECK: (func $new-side-effect (type $none_=>_none)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 f64)
- ;; CHECK-NEXT: (local $2 anyref)
+ ;; CHECK-NEXT: (local $2 (ref any))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct))
;; CHECK-NEXT: (local.set $0
@@ -478,7 +478,7 @@
;; CHECK: (func $new-side-effect-global-imm (type $none_=>_none)
;; CHECK-NEXT: (local $0 f64)
- ;; CHECK-NEXT: (local $1 anyref)
+ ;; CHECK-NEXT: (local $1 (ref any))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct))
;; CHECK-NEXT: (local.set $0
@@ -514,7 +514,7 @@
;; CHECK: (func $new-side-effect-global-mut (type $none_=>_none)
;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (local $1 f64)
- ;; CHECK-NEXT: (local $2 anyref)
+ ;; CHECK-NEXT: (local $2 (ref any))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct))
;; CHECK-NEXT: (local.set $0
@@ -837,7 +837,7 @@
)
;; CHECK: (func $unreachable-set-3 (type $none_=>_none)
- ;; CHECK-NEXT: (local $0 (ref null ${mut:i8}))
+ ;; CHECK-NEXT: (local $0 (ref ${mut:i8}))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (block (result (ref ${mut:i8}))
@@ -847,9 +847,7 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $helper-i32)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast
index b7ed9bd0f..6fd744278 100644
--- a/test/lit/passes/heap2local.wast
+++ b/test/lit/passes/heap2local.wast
@@ -392,8 +392,8 @@
)
;; CHECK: (func $nondefaultable
- ;; CHECK-NEXT: (local $0 (ref null $struct.A))
- ;; CHECK-NEXT: (local $1 (ref null $struct.A))
+ ;; CHECK-NEXT: (local $0 (ref $struct.A))
+ ;; CHECK-NEXT: (local $1 (ref $struct.A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct.A))
;; CHECK-NEXT: (drop
@@ -402,22 +402,18 @@
;; CHECK-NEXT: (struct.new_default $struct.A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $0
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct.nondefaultable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $nondefaultable (type $none_=>_none)
- ;; NOMNL-NEXT: (local $0 (ref null $struct.A))
- ;; NOMNL-NEXT: (local $1 (ref null $struct.A))
+ ;; NOMNL-NEXT: (local $0 (ref $struct.A))
+ ;; NOMNL-NEXT: (local $1 (ref $struct.A))
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result (ref $struct.A))
;; NOMNL-NEXT: (drop
@@ -426,23 +422,17 @@
;; NOMNL-NEXT: (struct.new_default $struct.A)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (local.set $0
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $1)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $1)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (ref.null $struct.nondefaultable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $0)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $0)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $nondefaultable
- ;; We do not optimize structs with nondefaultable types that we cannot
- ;; handle.
- ;; TODO: We should be able to handle this after #4824 is resolved.
+ ;; The non-nullable types here can fit in locals.
(drop
(struct.get $struct.nondefaultable 0
(struct.new $struct.nondefaultable
@@ -1459,8 +1449,8 @@
)
;; CHECK: (func $non-nullable (param $a (ref $struct.A))
- ;; CHECK-NEXT: (local $1 (ref null $struct.A))
- ;; CHECK-NEXT: (local $2 (ref null $struct.A))
+ ;; CHECK-NEXT: (local $1 (ref $struct.A))
+ ;; CHECK-NEXT: (local $2 (ref $struct.A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref $struct.A))
;; CHECK-NEXT: (drop
@@ -1469,22 +1459,18 @@
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $1
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $2)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null $struct.nondefaultable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $non-nullable (type $ref|$struct.A|_=>_none) (param $a (ref $struct.A))
- ;; NOMNL-NEXT: (local $1 (ref null $struct.A))
- ;; NOMNL-NEXT: (local $2 (ref null $struct.A))
+ ;; NOMNL-NEXT: (local $1 (ref $struct.A))
+ ;; NOMNL-NEXT: (local $2 (ref $struct.A))
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block (result (ref $struct.A))
;; NOMNL-NEXT: (drop
@@ -1493,16 +1479,12 @@
;; NOMNL-NEXT: (local.get $a)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (local.set $1
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $2)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $2)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (ref.null $struct.nonnullable)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $1)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $1)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
@@ -2826,4 +2808,59 @@
)
)
)
+
+ ;; CHECK: (func $non-nullable-local (result anyref)
+ ;; CHECK-NEXT: (local $0 (ref null $struct.A))
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result (ref null $struct.A))
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (f64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null $struct.A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $non-nullable-local (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT: (local $0 (ref null $struct.A))
+ ;; NOMNL-NEXT: (local $1 i32)
+ ;; NOMNL-NEXT: (local $2 f64)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (block (result (ref null $struct.A))
+ ;; NOMNL-NEXT: (local.set $1
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.set $2
+ ;; NOMNL-NEXT: (f64.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (ref.null $struct.A)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (unreachable)
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $non-nullable-local (result anyref)
+ (local $0 (ref $struct.A))
+ ;; The local.get here is in unreachable code, which means we won't do
+ ;; anything to it. But when we remove the local.set during optimization (we
+ ;; can replace it with new locals for the fields of $struct.A), we must make
+ ;; sure that validation still passes, that is, since the local.get is
+ ;; around we must have a local.set for it, or it must become nullable (which
+ ;; is what the fixup will do).
+ (local.set $0
+ (struct.new_default $struct.A)
+ )
+ (unreachable)
+ (local.get $0)
+ )
)
diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast
index 3b2f3a458..f820d1826 100644
--- a/test/lit/passes/inlining_all-features.wast
+++ b/test/lit/passes/inlining_all-features.wast
@@ -177,14 +177,12 @@
;; CHECK: (elem declare func $1)
;; CHECK: (func $1 (result (ref func))
- ;; CHECK-NEXT: (local $0 funcref)
+ ;; CHECK-NEXT: (local $0 (ref func))
;; CHECK-NEXT: (block $__inlined_func$0 (result (ref func))
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (ref.func $1)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (type $none_=>_ref|func| (func_subtype (result (ref func)) func))
@@ -192,14 +190,12 @@
;; NOMNL: (elem declare func $1)
;; NOMNL: (func $1 (type $none_=>_ref|func|) (result (ref func))
- ;; NOMNL-NEXT: (local $0 funcref)
+ ;; NOMNL-NEXT: (local $0 (ref func))
;; NOMNL-NEXT: (block $__inlined_func$0 (result (ref func))
;; NOMNL-NEXT: (local.set $0
;; NOMNL-NEXT: (ref.func $1)
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.as_non_null
- ;; NOMNL-NEXT: (local.get $0)
- ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (local.get $0)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $1 (result (ref func))
diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast
index 76062f0c0..aca20f8e8 100644
--- a/test/lit/passes/inlining_splitting.wast
+++ b/test/lit/passes/inlining_splitting.wast
@@ -213,7 +213,7 @@
;; CHECK: (func $call-nondefaultable-param
;; CHECK-NEXT: (local $0 i32)
- ;; CHECK-NEXT: (local $1 (ref null $struct))
+ ;; CHECK-NEXT: (local $1 (ref $struct))
;; CHECK-NEXT: (block $__inlined_func$nondefaultable-param
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (i32.const 0)
diff --git a/test/lit/passes/local-cse_all-features.wast b/test/lit/passes/local-cse_all-features.wast
index 5d9184e8e..fd1af725b 100644
--- a/test/lit/passes/local-cse_all-features.wast
+++ b/test/lit/passes/local-cse_all-features.wast
@@ -148,22 +148,18 @@
)
;; CHECK: (func $non-nullable-value (param $ref (ref $A))
- ;; CHECK-NEXT: (local $1 (ref null $A))
+ ;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.tee $1
- ;; CHECK-NEXT: (select (result (ref $A))
- ;; CHECK-NEXT: (local.get $ref)
- ;; CHECK-NEXT: (local.get $ref)
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.tee $1
+ ;; CHECK-NEXT: (select (result (ref $A))
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: (local.get $ref)
+ ;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $non-nullable-value (param $ref (ref $A))
diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast
index 37990ad24..a2a5780da 100644
--- a/test/lit/passes/local-subtyping.wast
+++ b/test/lit/passes/local-subtyping.wast
@@ -1,7 +1,12 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --local-subtyping -all -S -o - \
+;; RUN: wasm-opt %s --remove-unused-names --local-subtyping -all -S -o - \
;; RUN: | filecheck %s
+;; --remove-unused-names is run to avoid adding names to blocks. Block names
+;; can prevent non-nullable local validation (we emit named blocks in the binary
+;; format, if we need them, but never emit unnamed ones), which affects some
+;; testcases.
+
(module
;; CHECK: (type ${} (struct ))
(type ${} (struct_subtype data))
@@ -65,7 +70,7 @@
;; more specific type. A similar thing with a parameter, however, is not a
;; thing we can optimize. Also, ignore a local with zero assignments.
;; CHECK: (func $simple-local-but-not-param (param $x funcref)
- ;; CHECK-NEXT: (local $y (ref null $none_=>_i32))
+ ;; CHECK-NEXT: (local $y (ref $none_=>_i32))
;; CHECK-NEXT: (local $unused funcref)
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (ref.func $i32)
@@ -87,9 +92,9 @@
;; CHECK: (func $locals-with-multiple-assignments (param $data dataref)
;; CHECK-NEXT: (local $x eqref)
- ;; CHECK-NEXT: (local $y i31ref)
+ ;; CHECK-NEXT: (local $y (ref i31))
;; CHECK-NEXT: (local $z dataref)
- ;; CHECK-NEXT: (local $w funcref)
+ ;; CHECK-NEXT: (local $w (ref func))
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (i31.new
;; CHECK-NEXT: (i32.const 0)
@@ -146,8 +151,9 @@
(local.set $z
(local.get $data)
)
- ;; w is assigned two different types *without* a new LUB possible, as it
- ;; already had the optimal LUB
+ ;; w is assigned two different types *without* a new LUB heap type possible,
+ ;; as it already had the optimal LUB heap type (but it can become non-
+ ;; nullable).
(local.set $w
(ref.func $i32)
)
@@ -159,9 +165,9 @@
;; In some cases multiple iterations are necessary, as one inferred new type
;; applies to a get which then allows another inference.
;; CHECK: (func $multiple-iterations
- ;; CHECK-NEXT: (local $x (ref null $none_=>_i32))
- ;; CHECK-NEXT: (local $y (ref null $none_=>_i32))
- ;; CHECK-NEXT: (local $z (ref null $none_=>_i32))
+ ;; CHECK-NEXT: (local $x (ref $none_=>_i32))
+ ;; CHECK-NEXT: (local $y (ref $none_=>_i32))
+ ;; CHECK-NEXT: (local $z (ref $none_=>_i32))
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (ref.func $i32)
;; CHECK-NEXT: )
@@ -189,9 +195,9 @@
;; Sometimes a refinalize is necessary in between the iterations.
;; CHECK: (func $multiple-iterations-refinalize (param $i i32)
- ;; CHECK-NEXT: (local $x (ref null $none_=>_i32))
- ;; CHECK-NEXT: (local $y (ref null $none_=>_i64))
- ;; CHECK-NEXT: (local $z funcref)
+ ;; CHECK-NEXT: (local $x (ref $none_=>_i32))
+ ;; CHECK-NEXT: (local $y (ref $none_=>_i64))
+ ;; CHECK-NEXT: (local $z (ref func))
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (ref.func $i32)
;; CHECK-NEXT: )
@@ -199,7 +205,7 @@
;; CHECK-NEXT: (ref.func $i64)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $z
- ;; CHECK-NEXT: (select (result funcref)
+ ;; CHECK-NEXT: (select (result (ref func))
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: (local.get $i)
@@ -273,13 +279,13 @@
)
;; CHECK: (func $unreachables (result funcref)
- ;; CHECK-NEXT: (local $temp (ref null $none_=>_funcref))
+ ;; CHECK-NEXT: (local $temp (ref $none_=>_funcref))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (ref.func $unreachables)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (block (result (ref null $none_=>_funcref))
+ ;; CHECK-NEXT: (block (result (ref $none_=>_funcref))
;; CHECK-NEXT: (local.tee $temp
;; CHECK-NEXT: (ref.func $unreachables)
;; CHECK-NEXT: )
@@ -308,7 +314,7 @@
)
;; CHECK: (func $incompatible-sets (result i32)
- ;; CHECK-NEXT: (local $temp (ref null $none_=>_i32))
+ ;; CHECK-NEXT: (local $temp (ref $none_=>_i32))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (ref.func $incompatible-sets)
;; CHECK-NEXT: )
@@ -398,4 +404,127 @@
;; as there is no point to making something less specific in type.
(local.set $x (ref.null ${i32}))
)
+
+ ;; CHECK: (func $become-non-nullable
+ ;; CHECK-NEXT: (local $x (ref $none_=>_none))
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $become-non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $become-non-nullable
+ (local $x (ref null func))
+ (local.set $x
+ (ref.func $become-non-nullable)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $already-non-nullable
+ ;; CHECK-NEXT: (local $x (ref $none_=>_none))
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $already-non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $already-non-nullable
+ (local $x (ref func))
+ (local.set $x
+ (ref.func $already-non-nullable)
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $cannot-become-non-nullable
+ ;; CHECK-NEXT: (local $x (ref null $none_=>_none))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $become-non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cannot-become-non-nullable
+ (local $x (ref null func))
+ ;; The set is in a nested scope, so we should not make the local non-
+ ;; nullable, as it would not validate. (We can refine the heap type,
+ ;; though.)
+ (if
+ (i32.const 1)
+ (local.set $x
+ (ref.func $become-non-nullable)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $cannot-become-non-nullable-block
+ ;; CHECK-NEXT: (local $x (ref null $none_=>_none))
+ ;; CHECK-NEXT: (block $name
+ ;; CHECK-NEXT: (br_if $name
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $become-non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $cannot-become-non-nullable-block
+ (local $x (ref null func))
+ ;; A named block prevents us from optimizing here, the same as above.
+ (block $name
+ ;; Add a br_if to avoid the name being removed.
+ (br_if $name
+ (i32.const 1)
+ )
+ (local.set $x
+ (ref.func $become-non-nullable)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $become-non-nullable-block-unnamed
+ ;; CHECK-NEXT: (local $x (ref $none_=>_none))
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $become-non-nullable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $become-non-nullable-block-unnamed
+ (local $x (ref null func))
+ ;; An named block does *not* prevent us from optimizing here. Unlike above,
+ ;; an unnamed block is never emitted in the binary format, so it does not
+ ;; prevent validation.
+ (block
+ (local.set $x
+ (ref.func $become-non-nullable)
+ )
+ )
+ (drop
+ (local.get $x)
+ )
+ )
)
diff --git a/test/lit/passes/opt_flatten.wast b/test/lit/passes/opt_flatten.wast
index 048ccd60d..0e7ab9a41 100644
--- a/test/lit/passes/opt_flatten.wast
+++ b/test/lit/passes/opt_flatten.wast
@@ -9,8 +9,8 @@
(export "foo" (func $foo))
;; CHECK: (func $foo (result funcref)
;; CHECK-NEXT: (local $0 funcref)
- ;; CHECK-NEXT: (local $1 (ref null $none_=>_funcref))
- ;; CHECK-NEXT: (local $2 (ref null $none_=>_funcref))
+ ;; CHECK-NEXT: (local $1 (ref $none_=>_funcref))
+ ;; CHECK-NEXT: (local $2 (ref $none_=>_funcref))
;; CHECK-NEXT: (local $3 i32)
;; CHECK-NEXT: (block
;; CHECK-NEXT: (local.set $0
@@ -23,14 +23,10 @@
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $2
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $2)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast
index 161b1fe07..dd864eee1 100644
--- a/test/lit/passes/precompute-gc.wast
+++ b/test/lit/passes/precompute-gc.wast
@@ -1355,4 +1355,62 @@
)
)
)
+
+ ;; CHECK: (func $remove-set (result (ref func))
+ ;; CHECK-NEXT: (local $nn funcref)
+ ;; CHECK-NEXT: (local $i i32)
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (local.set $i
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $loop)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $remove-set (type $none_=>_ref|func|) (result (ref func))
+ ;; NOMNL-NEXT: (local $nn funcref)
+ ;; NOMNL-NEXT: (local $i i32)
+ ;; NOMNL-NEXT: (loop $loop
+ ;; NOMNL-NEXT: (local.set $i
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (br $loop)
+ ;; NOMNL-NEXT: (return
+ ;; NOMNL-NEXT: (ref.as_non_null
+ ;; NOMNL-NEXT: (local.get $nn)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $remove-set (result (ref func))
+ (local $nn (ref func))
+ (local $i i32)
+ (loop $loop
+ ;; Add a local.set here in the loop, just so the entire loop is not optimized
+ ;; out.
+ (local.set $i
+ (i32.const 0)
+ )
+ ;; This entire block can be precomputed into an unconditional br. That
+ ;; removes the local.set, which means the local no longer validates since
+ ;; there is a get without a set (the get is never reached, but the validator
+ ;; does not take that into account). Fixups will turn the local nullable to
+ ;; avoid that problem.
+ (block
+ (br_if $loop
+ (i32.const 1)
+ )
+ (local.set $nn
+ (ref.func $remove-set)
+ )
+ )
+ (return
+ (local.get $nn)
+ )
+ )
+ )
)
diff --git a/test/lit/passes/roundtrip-gc.wast b/test/lit/passes/roundtrip-gc.wast
index e0b59fe41..2692b7b99 100644
--- a/test/lit/passes/roundtrip-gc.wast
+++ b/test/lit/passes/roundtrip-gc.wast
@@ -8,33 +8,38 @@
;; NOMNL: (export "export" (func $test))
(export "export" (func $test))
;; CHECK: (func $test
+ ;; CHECK-NEXT: (local $0 (ref $\7bi32\7d))
;; CHECK-NEXT: (call $help
- ;; CHECK-NEXT: (struct.new_default $\7bi32\7d)
- ;; CHECK-NEXT: (block $label$1 (result i32)
+ ;; CHECK-NEXT: (block (result (ref $\7bi32\7d))
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (struct.new_default $\7bi32\7d)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
- ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $test (type $none_=>_none)
+ ;; NOMNL-NEXT: (local $0 (ref $\7bi32\7d))
;; NOMNL-NEXT: (call $help
- ;; NOMNL-NEXT: (struct.new_default $\7bi32\7d)
- ;; NOMNL-NEXT: (block $label$1 (result i32)
+ ;; NOMNL-NEXT: (block (result (ref $\7bi32\7d))
+ ;; NOMNL-NEXT: (local.set $0
+ ;; NOMNL-NEXT: (struct.new_default $\7bi32\7d)
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: (nop)
- ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: (local.get $0)
;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $test
(call $help
(struct.new_default ${i32})
;; Stack IR optimizations can remove this block, leaving a nop in an odd
- ;; "stacky" location. On load, we would normally use a local to work around
- ;; that, creating a block to contain the non-nullable reference before us and
- ;; the nop, and then returning the local. But we can't use a local for a
- ;; non-nullable reference, so we should not optimize this sort of thing in
- ;; stack IR.
- ;; TODO: This shouldn't be true after #4824 is resolved.
+ ;; "stacky" location. On load, we will use a local to work around that. It
+ ;; is fine for the local to be non-nullable since the get is later in that
+ ;; same block.
(block $block (result i32)
(nop)
(i32.const 1)
diff --git a/test/lit/passes/simplify-locals-gc-nn.wast b/test/lit/passes/simplify-locals-gc-nn.wast
index 814350fc0..e5941cc0e 100644
--- a/test/lit/passes/simplify-locals-gc-nn.wast
+++ b/test/lit/passes/simplify-locals-gc-nn.wast
@@ -1,33 +1,34 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --simplify-locals -all --enable-gc-nn-locals -S -o - | filecheck %s
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s
(module
;; CHECK: (func $test-nn
- ;; CHECK-NEXT: (local $nn (ref any))
- ;; CHECK-NEXT: (local.set $nn
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.null any)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local $nn anyref)
+ ;; CHECK-NEXT: (nop)
;; CHECK-NEXT: (try $try
;; CHECK-NEXT: (do
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: (local.set $nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch_all
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-nn
(local $nn (ref any))
- ;; We should not sink this set into the try, as the spec does not allow it
- ;; even though we are not changing dominance (we are not changing it,
- ;; because there is nothing that can throw in the try's body that can reach
- ;; the catch_all before the local.set that we move there). See
+ ;; We can sink this set into the try, but the spec does not allow it to
+ ;; remain non-nullable. Even though we are not changing dominance (we are
+ ;; not changing it, because there is nothing that can throw in the try's
+ ;; body that can reach the catch_all before the local.set that we move
+ ;; there). See
;; https://github.com/WebAssembly/function-references/issues/44#issuecomment-1083146887
(local.set $nn
(ref.as_non_null
@@ -67,8 +68,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test-nullable
- ;; As above, but now the local is nullable. Here we *can* optimize the set
- ;; into the try.
+ ;; As above, but now the local is nullable. Here we can optimize the set
+ ;; into the try, with no other necessary changes.
(local $nullable (ref null any))
(local.set $nullable
(ref.as_non_null
@@ -88,70 +89,4 @@
)
)
)
-
- ;; CHECK: (func $test-nested-nn
- ;; CHECK-NEXT: (local $nullable anyref)
- ;; CHECK-NEXT: (local $nn (ref any))
- ;; CHECK-NEXT: (local.set $nullable
- ;; CHECK-NEXT: (block (result (ref any))
- ;; CHECK-NEXT: (local.set $nn
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.null any)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (ref.null any)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (try $try
- ;; CHECK-NEXT: (do
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $nullable)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (catch_all
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $nullable)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.get $nn)
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- ;; CHECK-NEXT: )
- (func $test-nested-nn
- ;; As above, the set we want to move is nullable, but now it has a nested
- ;; value that is a non-nullable set. That should also prevent us from
- ;; moving it.
- (local $nullable (ref null any))
- (local $nn (ref any))
- (local.set $nullable
- (block (result (ref any))
- (local.set $nn
- (ref.as_non_null
- (ref.null any)
- )
- )
- (ref.as_non_null
- (ref.null any)
- )
- )
- )
- (try
- (do
- (drop
- (local.get $nullable)
- )
- )
- (catch_all
- (drop
- (local.get $nullable)
- )
- (drop
- (local.get $nn)
- )
- )
- )
- )
)
diff --git a/test/lit/passes/simplify-locals-gc-validation.wast b/test/lit/passes/simplify-locals-gc-validation.wast
new file mode 100644
index 000000000..bda59882b
--- /dev/null
+++ b/test/lit/passes/simplify-locals-gc-validation.wast
@@ -0,0 +1,48 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
+;; RUN: wasm-opt %s --simplify-locals -all -S -o - | filecheck %s
+
+;; Tests for validation of non-nullable locals. In this form a local.set allows
+;; a local.get until the end of the current block.
+
+(module
+ ;; CHECK: (func $test-nn (param $x (ref any))
+ ;; CHECK-NEXT: (local $nn anyref)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (block $inner
+ ;; CHECK-NEXT: (call $test-nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.tee $nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $test-nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test-nn (param $x (ref any))
+ (local $nn (ref any))
+ ;; We can sink this set into the block, but we should then update things so
+ ;; that we still validate, as then the final local.get is not structurally
+ ;; dominated. (Note that we end up with several ref.as_non_nulls here, but
+ ;; later passes could remove them.)
+ (local.set $nn
+ (ref.as_non_null
+ (ref.null any)
+ )
+ )
+ (block $inner
+ (call $test-nn
+ (local.get $nn)
+ )
+ )
+ (call $test-nn
+ (local.get $nn)
+ )
+ )
+)
diff --git a/test/lit/passes/simplify-locals-gc.wast b/test/lit/passes/simplify-locals-gc.wast
index 821f0a862..8d15c213e 100644
--- a/test/lit/passes/simplify-locals-gc.wast
+++ b/test/lit/passes/simplify-locals-gc.wast
@@ -226,6 +226,145 @@
)
)
+ ;; CHECK: (func $if-nnl
+ ;; CHECK-NEXT: (local $x (ref func))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $if-nnl)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (local.tee $x
+ ;; CHECK-NEXT: (ref.func $if-nnl)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $if-nnl (type $none_=>_none)
+ ;; NOMNL-NEXT: (local $x (ref func))
+ ;; NOMNL-NEXT: (if
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: (local.set $x
+ ;; NOMNL-NEXT: (ref.func $if-nnl)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $helper
+ ;; NOMNL-NEXT: (local.tee $x
+ ;; NOMNL-NEXT: (ref.func $if-nnl)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $helper
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $if-nnl
+ (local $x (ref func))
+ ;; We want to turn this if into an if-else with a set on the outside:
+ ;;
+ ;; (local.set $x
+ ;; (if
+ ;; (i32.const 1)
+ ;; (ref.func $if-nnl)
+ ;; (local.get $x)))
+ ;;
+ ;; That will not validate, however (no set dominates the get), so we'll get
+ ;; fixed up by adding a ref.as_non_null. But that may be dangerous - if no
+ ;; set exists before us, then that new instruction will trap, in fact. So we
+ ;; do not optimize here.
+ (if
+ (i32.const 1)
+ (local.set $x
+ (ref.func $if-nnl)
+ )
+ )
+ ;; An exta set + gets, just to avoid other optimizations kicking in
+ ;; (without them, the function only has a set and nothing else, and will
+ ;; remove the set entirely). Nothing should change here.
+ (call $helper
+ (local.tee $x
+ (ref.func $if-nnl)
+ )
+ )
+ (call $helper
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $if-nnl-previous-set
+ ;; CHECK-NEXT: (local $x (ref func))
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $if-nnl)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.func $if-nnl)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (local.tee $x
+ ;; CHECK-NEXT: (ref.func $if-nnl)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $helper
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $if-nnl-previous-set (type $none_=>_none)
+ ;; NOMNL-NEXT: (local $x (ref func))
+ ;; NOMNL-NEXT: (local.set $x
+ ;; NOMNL-NEXT: (ref.func $if-nnl)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (if
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: (local.set $x
+ ;; NOMNL-NEXT: (ref.func $if-nnl)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $helper
+ ;; NOMNL-NEXT: (local.tee $x
+ ;; NOMNL-NEXT: (ref.func $if-nnl)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $helper
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $if-nnl-previous-set
+ (local $x (ref func))
+ ;; As the above testcase, but now there is a set before the if. We could
+ ;; optimize in this case, but don't atm. TODO
+ (local.set $x
+ (ref.func $if-nnl)
+ )
+ (if
+ (i32.const 1)
+ (local.set $x
+ (ref.func $if-nnl)
+ )
+ )
+ (call $helper
+ (local.tee $x
+ (ref.func $if-nnl)
+ )
+ )
+ (call $helper
+ (local.get $x)
+ )
+ )
+
+ ;; CHECK: (func $helper (param $ref (ref func))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $helper (type $ref|func|_=>_none) (param $ref (ref func))
+ ;; NOMNL-NEXT: (nop)
+ ;; NOMNL-NEXT: )
+ (func $helper (param $ref (ref func))
+ )
+
;; CHECK: (func $needs-refinalize (param $b (ref $B)) (result anyref)
;; CHECK-NEXT: (local $a (ref null $A))
;; CHECK-NEXT: (nop)
diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast
index 30640a803..420761e40 100644
--- a/test/lit/passes/ssa.wast
+++ b/test/lit/passes/ssa.wast
@@ -8,23 +8,19 @@
(func $foo)
;; CHECK: (func $bar (param $x (ref func))
- ;; CHECK-NEXT: (local $1 funcref)
- ;; CHECK-NEXT: (local $2 funcref)
+ ;; CHECK-NEXT: (local $1 (ref func))
+ ;; CHECK-NEXT: (local $2 (ref func))
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $2
;; CHECK-NEXT: (ref.func $bar)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (ref.as_non_null
- ;; CHECK-NEXT: (local.get $2)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $bar (param $x (ref func))