diff options
Diffstat (limited to 'test/lit/passes')
-rw-r--r-- | test/lit/passes/catch-pop-fixup-eh.wast | 6 | ||||
-rw-r--r-- | test/lit/passes/coalesce-locals-gc.wast | 77 | ||||
-rw-r--r-- | test/lit/passes/dae-gc.wast | 18 | ||||
-rw-r--r-- | test/lit/passes/denan.wast | 1 | ||||
-rw-r--r-- | test/lit/passes/directize_all-features.wast | 23 | ||||
-rw-r--r-- | test/lit/passes/flatten.wast | 6 | ||||
-rw-r--r-- | test/lit/passes/flatten_all-features.wast | 6 | ||||
-rw-r--r-- | test/lit/passes/gto-removals.wast | 12 | ||||
-rw-r--r-- | test/lit/passes/heap2local.wast | 107 | ||||
-rw-r--r-- | test/lit/passes/inlining_all-features.wast | 12 | ||||
-rw-r--r-- | test/lit/passes/inlining_splitting.wast | 2 | ||||
-rw-r--r-- | test/lit/passes/local-cse_all-features.wast | 18 | ||||
-rw-r--r-- | test/lit/passes/local-subtyping.wast | 161 | ||||
-rw-r--r-- | test/lit/passes/opt_flatten.wast | 12 | ||||
-rw-r--r-- | test/lit/passes/precompute-gc.wast | 58 | ||||
-rw-r--r-- | test/lit/passes/roundtrip-gc.wast | 29 | ||||
-rw-r--r-- | test/lit/passes/simplify-locals-gc-nn.wast | 99 | ||||
-rw-r--r-- | test/lit/passes/simplify-locals-gc-validation.wast | 48 | ||||
-rw-r--r-- | test/lit/passes/simplify-locals-gc.wast | 139 | ||||
-rw-r--r-- | test/lit/passes/ssa.wast | 12 |
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)) |