blob: 7a5020e29e07c4b298e9132edf4b8849070adb21 [file] [log] [blame] [edit]
;; 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 $A (sub (struct)))
(type $A (sub (struct)))
;; CHECK: (type $1 (func (param (ref $B))))
;; 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 $1) (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).
)
)