blob: e5d92abc9c19b7a1846f09377d1f9667b3b43c29 [file] [log] [blame] [edit]
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --rse -all -S -o - | filecheck %s
(module
;; CHECK: (type $A (struct (field structref)))
(type $A (struct (field (ref null struct))))
;; $B is a subtype of $A, and its field has a more refined type (it is non-
;; nullable).
;; CHECK: (type $B (sub $A (struct (field (ref struct)))))
(type $B (struct_subtype (field (ref struct)) $A))
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (local $single (ref func))
;; CHECK-NEXT: (local $tuple ((ref any) (ref any)))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $test
;; A non-nullable local. The pass should ignore it (as we cannot optimize
;; anything here anyhow: the code must assign to the local before reading from
;; it, so no sets can be redundant in that sense).
(local $single (ref func))
;; A non-nullable tuple.
(local $tuple ((ref any) (ref any)))
)
;; CHECK: (func $needs-refinalize (type $ref|$B|_=>_anyref) (param $b (ref $B)) (result anyref)
;; CHECK-NEXT: (local $a (ref null $A))
;; CHECK-NEXT: (local.set $a
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.get $B 0
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $needs-refinalize (param $b (ref $B)) (result anyref)
(local $a (ref null $A))
;; Make $a contain $b.
(local.set $a
(local.get $b)
)
(struct.get $A 0
;; Once more, make $a contain $b. This set is redundant. After removing it,
;; the struct.get will be reading from type $B, which has a more refined
;; field, so we must refinalize to get the right type for the instruction.
(local.tee $a
(local.get $b)
)
)
)
;; CHECK: (func $pick-refined (type $ref?|$A|_i32_=>_none) (param $A (ref null $A)) (param $x i32)
;; CHECK-NEXT: (local $B (ref null $B))
;; CHECK-NEXT: (local.set $B
;; CHECK-NEXT: (ref.cast null $B
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $pick-refined (param $A (ref null $A)) (param $x i32)
(local $B (ref null $B))
(local.set $B
(ref.cast null $B
(local.get $A)
)
)
;; All these can refer to $B, the more refined type, even in branching and
;; merging control flow later.
(drop
(local.get $A)
)
(drop
(local.get $B)
)
(if
(local.get $x)
(drop
(local.get $A)
)
(drop
(local.get $B)
)
)
(drop
(local.get $A)
)
(drop
(local.get $B)
)
)
;; CHECK: (func $pick-refined-nn (type $ref|$A|_=>_none) (param $A (ref $A))
;; CHECK-NEXT: (local $B (ref $B))
;; CHECK-NEXT: (local.set $B
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $pick-refined-nn (param $A (ref $A))
(local $B (ref $B))
;; As above, but now the types are both non-nullable. We should still switch
;; to $B.
(local.set $B
(ref.cast $B
(local.get $A)
)
)
(drop
(local.get $A)
)
(drop
(local.get $B)
)
)
;; CHECK: (func $avoid-unrefined (type $ref|$A|_=>_none) (param $A (ref $A))
;; CHECK-NEXT: (local $B (ref null $B))
;; CHECK-NEXT: (local.set $B
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $avoid-unrefined (param $A (ref $A))
(local $B (ref null $B))
;; As above, but now the local is nullable. Since the parameter is non-
;; nullable, that means neither is a subtype of the other, and we will make
;; no changes.
(local.set $B
(ref.cast $B
(local.get $A)
)
)
(drop
(local.get $A)
)
(drop
(local.get $B)
)
)
;; CHECK: (func $pick-refined-earlier (type $ref|$A|_=>_none) (param $A (ref $A))
;; CHECK-NEXT: (local $A2 (ref null $A))
;; CHECK-NEXT: (local.set $A2
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $pick-refined-earlier (param $A (ref $A))
;; As above but now the local has the same heap type but is nullable. Now we
;; prefer the non-nullable parameter.
(local $A2 (ref null $A))
(local.set $A2
(local.get $A)
)
(drop
(local.get $A)
)
(drop
(local.get $A2)
)
)
;; CHECK: (func $different-choices (type $ref|$A|_=>_none) (param $non-nullable (ref $A))
;; CHECK-NEXT: (local $nullable (ref null $A))
;; CHECK-NEXT: (local.set $nullable
;; CHECK-NEXT: (local.get $non-nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $non-nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $nullable
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $nullable
;; CHECK-NEXT: (local.get $non-nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $non-nullable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $different-choices (param $non-nullable (ref $A))
(local $nullable (ref null $A))
(local.set $nullable
(local.get $non-nullable)
)
;; Here we can switch to the non-nullable one.
(drop
(local.get $nullable)
)
(local.set $nullable
(ref.null $A)
)
;; Here we cannot.
(drop
(local.get $nullable)
)
(local.set $nullable
(local.get $non-nullable)
)
;; Here we can switch once more.
(drop
(local.get $nullable)
)
)
;; CHECK: (func $string (type $none_=>_none)
;; CHECK-NEXT: (local $s stringref)
;; CHECK-NEXT: (local $t stringref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $s)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $s
;; CHECK-NEXT: (string.const "hello")
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $t
;; CHECK-NEXT: (local.get $s)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $s)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $t
;; CHECK-NEXT: (string.const "world!")
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $t
;; CHECK-NEXT: (local.get $s)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $string
(local $s stringref)
(local $t stringref)
;; This set is redundant (both are null).
(local.set $t
(local.get $s)
)
(local.set $s
(string.const "hello")
)
;; This set is not (one is not null).
(local.set $t
(local.get $s)
)
;; This set is redundant (both are "hello").
(local.set $t
(local.get $s)
)
(local.set $t
(string.const "world!")
)
;; This set is not (one is "world!").
(local.set $t
(local.get $s)
)
)
)