;; 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 (sub (struct (field structref)))) (type $A (sub (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 (sub $A (struct (field (ref struct))))) ;; CHECK: (func $test (type $3) ;; CHECK-NEXT: (local $single (ref func)) ;; CHECK-NEXT: (local $tuple (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 (tuple (ref any) (ref any))) ) ;; CHECK: (func $needs-refinalize (type $4) (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 $5) (param $A (ref null $A)) (param $x i32) ;; CHECK-NEXT: (local $B (ref null $B)) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (ref.cast (ref 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: (then ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $B) ;; CHECK-NEXT: ) ;; 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 (ref 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) (then (drop (local.get $A) ) ) (else (drop (local.get $B) ) ) ) (drop (local.get $A) ) (drop (local.get $B) ) ) ;; CHECK: (func $pick-refined-nn (type $2) (param $A (ref $A)) ;; CHECK-NEXT: (local $B (ref $B)) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (ref.cast (ref $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 (ref $B) (local.get $A) ) ) (drop (local.get $A) ) (drop (local.get $B) ) ) ;; CHECK: (func $avoid-unrefined (type $2) (param $A (ref $A)) ;; CHECK-NEXT: (local $B (ref null $B)) ;; CHECK-NEXT: (local.set $B ;; CHECK-NEXT: (ref.cast (ref $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 (ref $B) (local.get $A) ) ) (drop (local.get $A) ) (drop (local.get $B) ) ) ;; CHECK: (func $pick-refined-earlier (type $2) (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 $2) (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 $3) ;; 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) ) ) )