;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: foreach %s %t wasm-opt --signature-refining -all -S -o - | filecheck %s (module ;; $func is defined with an anyref parameter but always called with a $struct, ;; and we can specialize the heap type to that. That will both update the ;; heap type's definition as well as the types of the parameters as printed ;; on the function (which are derived from the heap type). ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct))))) (type $sig (sub (func (param anyref)))) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct) ) ) ) (module ;; As above, but the call is via call_ref. ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct))))) (type $sig (sub (func (param anyref)))) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call_ref $sig ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call_ref $sig (struct.new $struct) (ref.func $func) ) ) ) (module ;; A combination of call types, and the LUB is affected by all of them: one ;; call uses a nullable $struct, the other a non-nullable i31, so the LUB ;; is a nullable eqref. ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param eqref)))) (type $sig (sub (func (param anyref)))) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x eqref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (local $struct (ref null $struct)) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $sig ;; CHECK-NEXT: (ref.i31 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (local $struct (ref null $struct)) (call $func ;; Use a local to avoid a bottom type. (local.get $struct) ) (call_ref $sig (ref.i31 (i32.const 0)) (ref.func $func) ) ) ) (module ;; Multiple functions with the same heap type. Again, the LUB is in the ;; middle, this time the parent $struct and not a subtype. (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (sub (struct ))) ;; CHECK: (type $struct-sub2 (sub $struct (struct ))) ;; CHECK: (type $struct-sub1 (sub $struct (struct ))) ;; CHECK: (type $3 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct))))) (type $sig (sub (func (param anyref)))) (type $struct (sub (struct))) (type $struct-sub1 (sub $struct (struct))) (type $struct-sub2 (sub $struct (struct))) ) ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (param $x anyref) ) ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $3) ;; CHECK-NEXT: (call $func-1 ;; CHECK-NEXT: (struct.new_default $struct-sub1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $func-2 ;; CHECK-NEXT: (struct.new_default $struct-sub2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func-1 (struct.new $struct-sub1) ) (call $func-2 (struct.new $struct-sub2) ) ) ) (module ;; As above, but now only one of the functions is called. The other is still ;; updated, though, as they share a heap type. ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct))))) (type $sig (sub (func (param anyref)))) (type $struct (struct)) ;; CHECK: (func $func-1 (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (param $x anyref) ) ;; CHECK: (func $func-2 (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func-1 ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func-1 (struct.new $struct) ) ) ) (module ;; Define a field in the struct of the signature type that will be updated, ;; to check for proper validation after the update. ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (sub (struct (field (ref $sig))))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct) (ref $sig))))) (type $sig (sub (func (param anyref funcref)))) (type $struct (sub (struct (field (ref $sig))))) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) (param $f (ref $sig)) ;; CHECK-NEXT: (local $temp (ref null $sig)) ;; CHECK-NEXT: (local $3 funcref) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) (param $f funcref) ;; Define a local of the signature type that is updated. (local $temp (ref null $sig)) ;; Do a local.get of the param, to verify its type is valid. (drop (local.get $x) ) ;; Copy from a funcref local to the formerly funcref param to verify their ;; types are still compatible after the update. Note that we will need to ;; add a fixup local here, as $f's new type becomes too specific to be ;; assigned the value here. (local.set $f (local.get $temp) ) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct (ref.func $func) ) (ref.func $func) ) ) ) (module ;; An unreachable value does not prevent optimization: we will update the ;; param to be $struct. ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref $struct))))) (type $sig (sub (func (param anyref)))) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $sig ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct) ) (call_ref $sig (unreachable) (ref.func $func) ) ) ) (module ;; When we have only unreachable values, there is nothing to optimize, and we ;; should not crash. (type $struct (struct)) ;; CHECK: (type $sig (sub (func (param anyref)))) (type $sig (sub (func (param anyref)))) ;; CHECK: (type $1 (func)) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $sig) (param $x anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call_ref $sig ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call_ref $sig (unreachable) (ref.func $func) ) ) ) (module ;; When we have no calls, there is nothing to optimize, and we should not ;; crash. (type $struct (struct)) ;; CHECK: (type $sig (sub (func (param anyref)))) (type $sig (sub (func (param anyref)))) ;; CHECK: (func $func (type $sig) (param $x anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ) (module ;; Test multiple fields in multiple types. (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig-2 (sub (func (param eqref (ref $struct))))) ;; CHECK: (type $sig-1 (sub (func (param structref anyref)))) (type $sig-1 (sub (func (param anyref) (param anyref)))) (type $sig-2 (sub (func (param anyref) (param anyref)))) ) ;; CHECK: (elem declare func $func-2) ;; CHECK: (func $func-1 (type $sig-1) (param $x structref) (param $y anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-1 (type $sig-1) (param $x anyref) (param $y anyref) ) ;; CHECK: (func $func-2 (type $sig-2) (param $x eqref) (param $y (ref $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func-2 (type $sig-2) (param $x anyref) (param $y anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (local $any anyref) ;; CHECK-NEXT: (local $struct structref) ;; CHECK-NEXT: (local $i31 i31ref) ;; CHECK-NEXT: (call $func-1 ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $func-1 ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $func-2 ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $sig-2 ;; CHECK-NEXT: (local.get $i31) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: (ref.func $func-2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (local $any (ref null any)) (local $struct (ref null struct)) (local $i31 (ref null i31)) (call $func-1 (struct.new $struct) (local.get $struct) ) (call $func-1 (local.get $struct) (local.get $any) ) (call $func-2 (struct.new $struct) (struct.new $struct) ) (call_ref $sig-2 (local.get $i31) (struct.new $struct) (ref.func $func-2) ) ) ) (module ;; The presence of a table prevents us from doing any optimizations. ;; CHECK: (type $sig (sub (func (param anyref)))) (type $sig (sub (func (param anyref)))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) (table 1 1 anyref) ;; CHECK: (table $0 1 1 anyref) ;; CHECK: (func $func (type $sig) (param $x anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct) ) ) ) (module ;; Pass a null in one call to the function. The null can be updated which ;; allows us to refine (but the new type must be nullable). ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) ;; CHECK: (type $1 (func)) ;; CHECK: (type $sig (sub (func (param (ref null $struct))))) (type $sig (sub (func (param anyref)))) (type $struct (struct)) ;; CHECK: (func $func (type $sig) (param $x (ref null $struct)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct) ) (call $func (ref.null none) ) ) ) (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $0 (func)) ;; CHECK: (type $sig-unreachable (sub (func (result anyref)))) ;; CHECK: (type $sig-cannot-refine (sub (func (result (ref func))))) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; This signature has a single function using it, which returns a more ;; refined type, and we can refine to that. ;; CHECK: (type $sig-can-refine (sub (func (result (ref $struct))))) (type $sig-can-refine (sub (func (result anyref)))) ;; Also a single function, but no refinement is possible. (type $sig-cannot-refine (sub (func (result (ref func))))) ;; The single function never returns, so no refinement is possible. (type $sig-unreachable (sub (func (result anyref)))) ) ;; CHECK: (elem declare func $func-can-refine $func-cannot-refine) ;; CHECK: (func $func-can-refine (type $sig-can-refine) (result (ref $struct)) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) (func $func-can-refine (type $sig-can-refine) (result anyref) (struct.new $struct) ) ;; CHECK: (func $func-cannot-refine (type $sig-cannot-refine) (result (ref func)) ;; CHECK-NEXT: (select (result (ref func)) ;; CHECK-NEXT: (ref.func $func-can-refine) ;; CHECK-NEXT: (ref.func $func-cannot-refine) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func-cannot-refine (type $sig-cannot-refine) (result (ref func)) (select (ref.func $func-can-refine) (ref.func $func-cannot-refine) (i32.const 0) ) ) ;; CHECK: (func $func-unreachable (type $sig-unreachable) (result anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $func-unreachable (type $sig-unreachable) (result anyref) (unreachable) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result (ref $struct)) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $func-can-refine) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (if (result (ref $struct)) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call_ref $sig-can-refine ;; CHECK-NEXT: (ref.func $func-can-refine) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller ;; Add a call to see that we update call types properly. ;; Put the call in an if so the refinalize will update the if type and get ;; printed out conveniently. (drop (if (result anyref) (i32.const 1) (then (call $func-can-refine) ) (else (unreachable) ) ) ) ;; The same with a call_ref. (drop (if (result anyref) (i32.const 1) (then (call_ref $sig-can-refine (ref.func $func-can-refine) ) ) (else (unreachable) ) ) ) ) ) (module ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (struct )) (type $struct (struct)) ;; This signature has multiple functions using it, and some of them have nulls ;; which should be updated when we refine. ;; CHECK: (type $sig (sub (func (result (ref null $struct))))) (type $sig (sub (func (result anyref)))) ;; CHECK: (func $func-1 (type $sig) (result (ref null $struct)) ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) (func $func-1 (type $sig) (result anyref) (struct.new $struct) ) ;; CHECK: (func $func-2 (type $sig) (result (ref null $struct)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $func-2 (type $sig) (result anyref) (ref.null any) ) ;; CHECK: (func $func-3 (type $sig) (result (ref null $struct)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) (func $func-3 (type $sig) (result anyref) (ref.null eq) ) ;; CHECK: (func $func-4 (type $sig) (result (ref null $struct)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $func-4 (type $sig) (result anyref) (if (i32.const 1) (then (return (ref.null any) ) ) ) (unreachable) ) ) ;; Exports prevent optimization, so $func's type will not change here. (module ;; CHECK: (type $sig (sub (func (param anyref)))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) (type $sig (sub (func (param anyref)))) ;; CHECK: (export "prevent-opts" (func $func)) ;; CHECK: (func $func (type $sig) (param $x anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $func (export "prevent-opts") (type $sig) (param $x anyref) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (struct.new $struct) ) ) ) (module ;; CHECK: (type $A (sub (func (param i32)))) (type $A (sub (func (param i32)))) ;; CHECK: (type $B (sub $A (func (param i32)))) (type $B (sub $A (func (param i32)))) ;; CHECK: (func $bar (type $B) (param $x i32) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $bar (type $B) (param $x i32) ;; The parameter to this function can be pruned. But while doing so we must ;; properly preserve the subtyping of $B from $A, which means we cannot just ;; remove it - we'd need to remove it from $A as well, which we don't ;; attempt to do in the pass atm. So we do not optimize here. (nop) ) ) (module ;; CHECK: (type $"{}" (struct )) (type $"{}" (struct)) ;; CHECK: (type $1 (func (param (ref $"{}") i32))) ;; CHECK: (func $foo (type $1) (param $ref (ref $"{}")) (param $i32 i32) ;; CHECK-NEXT: (local $2 eqref) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (call $foo ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $foo (param $ref eqref) (param $i32 i32) (call $foo ;; The only reference to the $"{}" type is in this block signature. Even ;; this will go away in the internal ReFinalize (which makes the block ;; type unreachable). (block (result (ref $"{}")) (unreachable) ) (i32.const 0) ) ;; Write something of type eqref into $ref. When we refine the type of the ;; parameter from eqref to $"{}" we must do something here, as we can no ;; longer just write this (ref.null eq) into a parameter of the more ;; refined type. While doing so, we must not be confused by the fact that ;; the only mention of $"{}" in the original module gets removed during our ;; processing, as mentioned in the earlier comment. This is a regression ;; test for a crash because of that. (local.set $ref (ref.null eq) ) ) ) ;; Do not modify the types used on imported functions (until the spec and VM ;; support becomes stable). (module ;; CHECK: (type $0 (func (param structref))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $struct (struct )) (type $struct (struct)) ;; CHECK: (import "a" "b" (func $import (type $0) (param structref))) (import "a" "b" (func $import (param (ref null struct)))) ;; CHECK: (func $test (type $1) ;; CHECK-NEXT: (call $import ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (call $import (struct.new $struct) ) ) ) ;; If we refine a type, that may require changes to its subtypes. For now, we ;; skip such optimizations. TODO (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (func (param (ref null $B)) (result (ref null $A))))) (type $A (sub (func (param (ref null $B)) (result (ref null $A))))) ;; CHECK: (type $B (sub $A (func (param (ref null $A)) (result (ref null $B))))) (type $B (sub $A (func (param (ref null $A)) (result (ref null $B))))) ) ;; CHECK: (elem declare func $func) ;; CHECK: (func $func (type $A) (param $0 (ref null $B)) (result (ref null $A)) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) (func $func (type $A) (param $0 (ref null $B)) (result (ref null $A)) ;; This result is non-nullable, and we could refine type $A accordingly. But ;; if we did that, we'd need to refine $B as well. (ref.func $func) ) ) ;; Until we handle contravariance, do not try to optimize a type that has a ;; supertype. In this example, refining the child's anyref to nullref would ;; cause an error. (module ;; CHECK: (type $parent (sub (func (param anyref)))) (type $parent (sub (func (param anyref)))) ;; CHECK: (type $child (sub $parent (func (param anyref)))) (type $child (sub $parent (func (param anyref)))) ;; CHECK: (type $2 (func)) ;; CHECK: (func $func (type $child) (param $0 anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $func (type $child) (param anyref) (unreachable) ) ;; CHECK: (func $caller (type $2) ;; CHECK-NEXT: (call $func ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $func (ref.null eq) ) ) ) (module ;; CHECK: (type $F (func)) (type $F (func)) ;; CHECK: (func $func (type $F) ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; We should not error on a call_ref to a bottom type. (call_ref $F (ref.null nofunc) ) ) ) (module ;; CHECK: (rec ;; CHECK-NEXT: (type $0 (func (param (ref $"[i8]")))) ;; CHECK: (type $"[i8]" (array i8)) (type $"[i8]" (array i8)) ;; CHECK: (type $2 (func)) ;; CHECK: (func $0 (type $2) ;; CHECK-NEXT: (call $1 ;; CHECK-NEXT: (array.new_fixed $"[i8]" 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $0 (call $1 (array.new_fixed $"[i8]" 0) ) ) ;; CHECK: (func $1 (type $0) (param $2 (ref $"[i8]")) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref none) ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $1 (param $2 anyref) ;; The param will become non-nullable after we refine. We must refinalize ;; after doing so, so the cast becomes non-nullable as well. (drop (ref.cast structref (local.get $2) ) ) ) ) ;; Test the call.without.effects intrinsic, which may require additional work. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct ))) (type $A (sub (struct))) ;; CHECK: (type $B (sub $A (struct ))) (type $B (sub $A (struct))) ;; CHECK: (type $C (sub $B (struct ))) (type $C (sub $B (struct))) ;; CHECK: (type $return_A_2 (func (result (ref $C)))) ;; CHECK: (type $return_A (func (result (ref $B)))) (type $return_A (func (result (ref null $A)))) (type $return_A_2 (func (result (ref null $A)))) ) ;; CHECK: (type $5 (func)) ;; CHECK: (type $6 (func (param funcref) (result (ref null $A)))) ;; CHECK: (type $7 (func (param funcref) (result (ref $B)))) ;; CHECK: (type $8 (func (param funcref) (result (ref $C)))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (type $6) (param funcref) (result (ref null $A)))) (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (param funcref) (result (ref null $A)) )) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_4 (type $7) (param funcref) (result (ref $B)))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects_5 (type $8) (param funcref) (result (ref $C)))) ;; CHECK: (elem declare func $other $other2) ;; CHECK: (func $func (type $5) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $no.side.effects_4 ;; CHECK-NEXT: (ref.func $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $no.side.effects_4 ;; CHECK-NEXT: (ref.func $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $no.side.effects_5 ;; CHECK-NEXT: (ref.func $other2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $func ;; After $other's result is refined, this will need to use a new import that ;; has the refined result type. (drop (call $no.side.effects (ref.func $other) ) ) ;; A second call of the same one. This should call the same new import (that ;; is, we shouldn't create unnecessary copies of the new imports). (drop (call $no.side.effects (ref.func $other) ) ) ;; A call of another function with a different refining, that will need ;; another import. (drop (call $no.side.effects (ref.func $other2) ) ) ) ;; CHECK: (func $other (type $return_A) (result (ref $B)) ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) (func $other (type $return_A) (result (ref null $A)) (struct.new $B) ;; this will allow this function's result to be refined to $B ) ;; CHECK: (func $other2 (type $return_A_2) (result (ref $C)) ;; CHECK-NEXT: (struct.new_default $C) ;; CHECK-NEXT: ) (func $other2 (type $return_A_2) (result (ref null $A)) (struct.new $C) ;; this will allow this function's result to be refined to $C ) ) ;; Test we consider call.without.effects when deciding what to refine. $A has ;; two subtypes, B1 and B2, and a call.without.effects sends in one while a ;; normal call sends in the other. As a result, we cannot refine the parameter ;; at all. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (struct ))) (type $A (sub (struct))) ;; CHECK: (type $B1 (sub $A (struct ))) (type $B1 (sub $A (struct))) ;; CHECK: (type $B2 (sub $A (struct ))) (type $B2 (sub $A (struct))) ) ;; CHECK: (type $3 (func (param (ref $A) funcref))) ;; CHECK: (type $4 (func)) ;; CHECK: (type $5 (func (param (ref $A)))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (type $3) (param (ref $A) funcref))) (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (param (ref $A)) (param funcref) )) ;; CHECK: (elem declare func $target) ;; CHECK: (func $calls (type $4) ;; CHECK-NEXT: (call $no.side.effects ;; CHECK-NEXT: (struct.new_default $B1) ;; CHECK-NEXT: (ref.func $target) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $target ;; CHECK-NEXT: (struct.new_default $B2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls (call $no.side.effects (struct.new $B1) (ref.func $target) ) (call $target (struct.new $B2) ) ) ;; CHECK: (func $target (type $5) (param $x (ref $A)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) ;; Because of the two calls above, this cannot be refined. ) ) ;; As above, but now we can refine the parameter to the called function. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $0 (func (param (ref $B)))) ;; CHECK: (type $A (sub (struct ))) (type $A (sub (struct))) ;; CHECK: (type $B (sub $A (struct ))) (type $B (sub $A (struct))) ) ;; CHECK: (type $3 (func)) ;; CHECK: (type $4 (func (param (ref $A) funcref))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (type $4) (param (ref $A) funcref))) (import "binaryen-intrinsics" "call.without.effects" (func $no.side.effects (param (ref $A)) (param funcref) )) ;; CHECK: (elem declare func $target) ;; CHECK: (func $calls (type $3) ;; CHECK-NEXT: (call $no.side.effects ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: (ref.func $target) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $target ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $calls (call $no.side.effects (struct.new $B) ;; this changed to $B (ref.func $target) ) (call $target (struct.new $B) ;; this also changed to $B ) ) ;; CHECK: (func $target (type $0) (param $x (ref $B)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $target (param $x (ref $A)) ;; The two calls above both send $B, so we can refine the parameter to $B. ;; ;; Note that the signature of the import $no.side.effects does *not* change; ;; the refined values sent are valid to send to the old parameter types there ;; (see tests above for how we handle refining of return values). ) )