blob: 4f593aea34b619d4218301ad68be76adaabcfc14 [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 -all --gufa -tnh --closed-world -S -o - | filecheck %s
;; A CallRef with no non-trapping targets must be unreachable if traps never
;; happen, in a closed world, which is what this file tests.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $A (func))
(type $A (func))
;; CHECK: (type $B (func))
(type $B (func))
)
;; CHECK: (type $2 (func (param funcref)))
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $impossible (type $A)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $impossible (type $A)
;; This cannot be called if traps never happen.
(unreachable)
)
;; CHECK: (func $irrelevant (type $B)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $irrelevant (type $B)
;; This has a similar but different type, so it is irrelevant to this
;; optimization.
(drop
(i32.const 20)
)
)
;; CHECK: (func $caller (type $2) (param $x funcref)
;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
;; This call must trap: the only function of the right type will trap.
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
)
)
;; As above, but now there are no functions of type $A at all.
(module
(rec
;; CHECK: (type $0 (func (param funcref)))
;; CHECK: (rec
;; CHECK-NEXT: (type $A (func))
(type $A (func))
;; CHECK: (type $B (func))
(type $B (func))
)
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $irrelevant (type $B)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $irrelevant (type $B)
;; This has a similar but different type, so it is irrelevant to this
;; optimization.
(drop
(i32.const 20)
)
)
;; CHECK: (func $caller (type $0) (param $x funcref)
;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
;; No function exists of type $A, so this will trap.
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
)
)
;; As above, but now there is a function that can be called.
(module
;; CHECK: (type $A (func))
(type $A (func))
;; CHECK: (type $1 (func (param funcref)))
;; CHECK: (elem declare func $possible)
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $possible (type $A)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible (type $A)
(drop
(i32.const 10)
)
)
;; CHECK: (func $caller (type $1) (param $x funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.func $possible)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
;; This must call $possible.
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
)
)
;; As above, with another function of that type that traps.
(module
;; CHECK: (type $A (func))
(type $A (func))
;; CHECK: (type $1 (func (param funcref)))
;; CHECK: (elem declare func $possible)
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $possible (type $A)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible (type $A)
(drop
(i32.const 10)
)
)
;; CHECK: (func $impossible (type $A)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $impossible (type $A)
(unreachable)
)
;; CHECK: (func $caller (type $1) (param $x funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.func $possible)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
;; This must call $possible, as the trapping function won't be called.
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
)
)
;; As above, but now we have two possible functions. We cannot optimize here.
(module
;; CHECK: (type $A (func))
(type $A (func))
;; CHECK: (type $1 (func (param funcref)))
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $possible (type $A)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible (type $A)
(drop
(i32.const 10)
)
)
;; CHECK: (func $possible-2 (type $A)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-2 (type $A)
(drop
(i32.const 20)
)
)
;; CHECK: (func $caller (type $1) (param $x funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
)
)
;; As above, but now the second possible function is of a subtype. We still
;; cannot optimize a call to the parent, but we can for the child.
(module
;; CHECK: (type $A (sub (func)))
(type $A (sub (func)))
;; CHECK: (type $B (sub $A (func)))
(type $B (sub $A (func)))
;; CHECK: (type $2 (func (param funcref)))
;; CHECK: (elem declare func $possible-2)
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $possible (type $A)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible (type $A)
(drop
(i32.const 10)
)
)
;; CHECK: (func $possible-2 (type $B)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-2 (type $B)
(drop
(i32.const 20)
)
)
;; CHECK: (func $caller (type $2) (param $x funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $B
;; CHECK-NEXT: (ref.func $possible-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $x funcref)
;; A has one function, but it has a subtype with another, so we cannot
;; optimize
(call_ref $A
(ref.cast (ref $A)
(local.get $x)
)
)
;; The second call can be optimized, as B has just one function.
(call_ref $B
(ref.cast (ref $B)
(local.get $x)
)
)
)
)
;; Two possible functions, but one has a parameter that will trap.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $X (sub (struct)))
(type $X (sub (struct)))
;; CHECK: (type $Y1 (sub $X (struct)))
(type $Y1 (sub $X (struct)))
;; CHECK: (type $Y2 (sub $X (struct)))
(type $Y2 (sub $X (struct)))
;; CHECK: (type $A (func (param anyref)))
(type $A (func (param anyref)))
)
;; CHECK: (type $4 (func (param funcref funcref funcref structref)))
;; CHECK: (type $5 (func))
;; CHECK: (elem declare func $possible-Y1 $possible-Y2)
;; CHECK: (export "out" (func $caller))
;; CHECK: (export "out2" (func $reffer))
;; CHECK: (func $possible-Y1 (type $A) (param $ref anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-Y1 (type $A) (param $ref anyref)
(drop
(ref.cast (ref $Y1)
(local.get $ref)
)
)
)
;; CHECK: (func $possible-Y2 (type $A) (param $ref anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y2)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-Y2 (type $A) (param $ref anyref)
(drop
(ref.cast (ref $Y2)
(local.get $ref)
)
)
)
;; CHECK: (func $caller (type $4) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $struct structref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (ref.func $possible-Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (ref.func $possible-Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out")
(param $func1 funcref)
(param $func2 funcref)
(param $func3 funcref)
(param $struct structref)
;; This would trap if we called the function that casts to Y2, so we must be
;; calling possible-Y1.
(call_ref $A
(struct.new $Y1)
(ref.cast (ref $A)
(local.get $func1)
)
)
;; Inverse of the above: we must call possible-Y2.
(call_ref $A
(struct.new $Y2)
(ref.cast (ref $A)
(local.get $func2)
)
)
;; This can call either one, and cannot be optimized.
(call_ref $A
(local.get $struct)
(ref.cast (ref $A)
(local.get $func3)
)
)
)
;; CHECK: (func $reffer (type $5)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffer (export "out2")
;; Take references to the possible functions so that GUFA does not optimize
;; calls to them away.
(drop
(ref.func $possible-Y1)
)
(drop
(ref.func $possible-Y2)
)
)
)
;; As above, but now the target functions have two parameters, to check for
;; both of their casts being noticed. The second function still casts to Y2, but
;; it casts the second parameter to that.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $X (sub (struct)))
(type $X (sub (struct)))
;; CHECK: (type $Y1 (sub $X (struct)))
(type $Y1 (sub $X (struct)))
;; CHECK: (type $Y2 (sub $X (struct)))
(type $Y2 (sub $X (struct)))
;; CHECK: (type $A (func (param anyref anyref)))
(type $A (func (param anyref) (param anyref)))
)
;; CHECK: (type $4 (func (param funcref funcref funcref funcref)))
;; CHECK: (type $5 (func (param funcref funcref funcref funcref structref)))
;; CHECK: (type $6 (func))
;; CHECK: (elem declare func $possible-Y1 $possible-Y2)
;; CHECK: (export "out" (func $caller1))
;; CHECK: (export "out2" (func $caller2))
;; CHECK: (export "out3" (func $reffer))
;; CHECK: (func $possible-Y1 (type $A) (param $ref anyref) (param $ref2 anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-Y1 (type $A) (param $ref anyref) (param $ref2 anyref)
(drop
(ref.cast (ref $Y1)
(local.get $ref)
)
)
)
;; CHECK: (func $possible-Y2 (type $A) (param $ref anyref) (param $ref2 anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y2)
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-Y2 (type $A) (param $ref anyref) (param $ref2 anyref)
(drop
(ref.cast (ref $Y2)
(local.get $ref2) ;; this changed from ref to ref2.
)
)
)
;; CHECK: (func $caller1 (type $4) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $func4 funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (ref.func $possible-Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (ref.func $possible-Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller1 (export "out")
(param $func1 funcref)
(param $func2 funcref)
(param $func3 funcref)
(param $func4 funcref)
;; Both params are Y1. The cast to Y2 would fail, so this must call
;; possible-Y1.
(call_ref $A
(struct.new $Y1)
(struct.new $Y1)
(ref.cast (ref $A)
(local.get $func1)
)
)
;; Both params are Y2. The cast to Y1 would fail, so this must call
;; possible-Y2.
(call_ref $A
(struct.new $Y2)
(struct.new $Y2)
(ref.cast (ref $A)
(local.get $func2)
)
)
;; Cast the first to Y1 and the second to Y2. Both functions match here, so
;; we cannot optimize.
(call_ref $A
(struct.new $Y1)
(struct.new $Y2)
(ref.cast (ref $A)
(local.get $func3)
)
)
;; The inverse, which matches no functions at all, and we trap.
(call_ref $A
(struct.new $Y2)
(struct.new $Y1)
(ref.cast (ref $A)
(local.get $func4)
)
)
)
;; CHECK: (func $caller2 (type $5) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $func4 funcref) (param $struct structref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (ref.func $possible-Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (ref.func $possible-Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller2 (export "out2")
(param $func1 funcref)
(param $func2 funcref)
(param $func3 funcref)
(param $func4 funcref)
(param $struct structref)
;; Both are structref, so both functions are possible, and we cannot
;; optimize.
(call_ref $A
(local.get $struct)
(local.get $struct)
(ref.cast (ref $A)
(local.get $func3)
)
)
;; Y1 in the first param works in both functions; no optimization.
(call_ref $A
(struct.new $Y1)
(local.get $struct)
(ref.cast (ref $A)
(local.get $func3)
)
)
;; Y2 in the first param fails in the first, so we optimize to the second.
(call_ref $A
(struct.new $Y2)
(local.get $struct)
(ref.cast (ref $A)
(local.get $func3)
)
)
;; Y1 in the second param fails in the second, so we optimize to the first.
(call_ref $A
(local.get $struct)
(struct.new $Y1)
(ref.cast (ref $A)
(local.get $func3)
)
)
;; Y2 in the second param works in both functions; no optimization.
(call_ref $A
(local.get $struct)
(struct.new $Y2)
(ref.cast (ref $A)
(local.get $func3)
)
)
)
;; CHECK: (func $reffer (type $6)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-Y1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-Y2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffer (export "out3")
;; Take references to the possible functions so that GUFA does not optimize
;; calls to them away.
(drop
(ref.func $possible-Y1)
)
(drop
(ref.func $possible-Y2)
)
)
)
;; Casts in multiple call_ref possible targets.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $X (sub (struct)))
(type $X (sub (struct)))
;; CHECK: (type $Y1 (sub $X (struct)))
(type $Y1 (sub $X (struct)))
;; CHECK: (type $Y2 (sub $X (struct)))
(type $Y2 (sub $X (struct)))
;; CHECK: (type $A (func (param anyref anyref anyref anyref anyref anyref)))
(type $A (func (param anyref) (param anyref) (param anyref) (param anyref) (param anyref) (param anyref)))
)
;; CHECK: (type $4 (func (param anyref anyref anyref anyref anyref anyref funcref)))
;; CHECK: (type $5 (func))
;; CHECK: (elem declare func $possible-1 $possible-2)
;; CHECK: (export "out" (func $caller1))
;; CHECK: (export "out2" (func $reffer))
;; CHECK: (func $possible-1 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref5)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref6)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-1 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
(drop
(ref.cast (ref $X)
(local.get $ref)
)
)
(drop
(ref.cast (ref $X)
(local.get $ref2)
)
)
(drop
(ref.cast (ref $X)
(local.get $ref4)
)
)
(drop
(ref.cast (ref $Y1)
(local.get $ref5)
)
)
(drop
(ref.cast (ref $Y1)
(local.get $ref6)
)
)
)
;; CHECK: (func $possible-2 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref4)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref5)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y2)
;; CHECK-NEXT: (local.get $ref6)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-2 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
(drop
(ref.cast (ref $X)
(local.get $ref)
)
)
(drop
(ref.cast (ref $X)
(local.get $ref3)
)
)
(drop
(ref.cast (ref $Y1)
(local.get $ref4)
)
)
(drop
(ref.cast (ref $X)
(local.get $ref5)
)
)
(drop
(ref.cast (ref $Y2)
(local.get $ref6)
)
)
)
;; CHECK: (func $caller1 (type $4) (param $ref1 anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref) (param $func funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast anyref
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast anyref
;; CHECK-NEXT: (local.get $ref3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref5)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref6)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller1 (export "out")
(param $ref1 anyref)
(param $ref2 anyref)
(param $ref3 anyref)
(param $ref4 anyref)
(param $ref5 anyref)
(param $ref6 anyref)
(param $func funcref)
;; All the params have a cast, so we can potentially refine them depending
;; on the call targets.
(call_ref $A
;; The first param is cast to $X in both targets, so we can apply that.
(ref.cast anyref
(local.get $ref1)
)
;; The second and third are cast to $X just in one of them, so we do
;; nothing.
(ref.cast anyref
(local.get $ref2)
)
(ref.cast anyref
(local.get $ref3)
)
;; The fourth and fifth are cast to $X and $Y1, so we can apply the LUB,
;; which is $X.
(ref.cast anyref
(local.get $ref4)
)
(ref.cast anyref
(local.get $ref5)
)
;; The last is cast to $Y1 and $Y2, and the LUB is $X.
(ref.cast anyref
(local.get $ref6)
)
(ref.cast (ref $A)
(local.get $func)
)
)
)
;; CHECK: (func $reffer (type $5)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffer (export "out2")
;; Take references to the possible functions so that GUFA does not optimize
;; calls to them away.
(drop
(ref.func $possible-1)
)
(drop
(ref.func $possible-2)
)
)
)
;; As above, but now the declared type is $X and not anyref, so we need to
;; improve past that.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $X (sub (struct)))
(type $X (sub (struct)))
;; CHECK: (type $Y1 (sub $X (struct)))
(type $Y1 (sub $X (struct)))
;; CHECK: (type $Y2 (sub $X (struct)))
(type $Y2 (sub $X (struct)))
;; CHECK: (type $A (func (param (ref null $X) (ref null $X) (ref null $X))))
(type $A (func (param (ref null $X)) (param (ref null $X)) (param (ref null $X))))
)
;; CHECK: (type $4 (func (param anyref anyref anyref anyref anyref anyref funcref)))
;; CHECK: (type $5 (func))
;; CHECK: (elem declare func $possible-1 $possible-2)
;; CHECK: (export "out" (func $caller1))
;; CHECK: (export "out2" (func $reffer))
;; CHECK: (func $possible-1 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-1 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
(drop
(ref.cast (ref $Y1)
(local.get $ref2)
)
)
(drop
(ref.cast (ref $Y1)
(local.get $ref3)
)
)
)
;; CHECK: (func $possible-2 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $possible-2 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
(drop
(ref.cast (ref $Y1)
(local.get $ref)
)
)
(drop
(ref.cast (ref $X)
(local.get $ref2)
)
)
(drop
(ref.cast (ref $Y1)
(local.get $ref3)
)
)
)
;; CHECK: (func $caller1 (type $4) (param $ref1 anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref) (param $func funcref)
;; CHECK-NEXT: (call_ref $A
;; CHECK-NEXT: (ref.cast (ref null $X)
;; CHECK-NEXT: (local.get $ref1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $X)
;; CHECK-NEXT: (local.get $ref2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (local.get $ref3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller1 (export "out")
(param $ref1 anyref)
(param $ref2 anyref)
(param $ref3 anyref)
(param $ref4 anyref)
(param $ref5 anyref)
(param $ref6 anyref)
(param $func funcref)
;; All the params have a cast, so we can potentially refine them depending
;; on the call targets.
(call_ref $A
;; The first is cast to $Y1 in only one target, so we can do nothing.
(ref.cast (ref null $X)
(local.get $ref1)
)
;; The second is cast to $X in one $Y1 in the other, so we can refine the
;; nullability at least.
(ref.cast (ref null $X)
(local.get $ref2)
)
;; The third parameter is cast to $Y1 in both, so we can optimize to that.
(ref.cast (ref null $X)
(local.get $ref3)
)
(ref.cast (ref $A)
(local.get $func)
)
)
)
;; CHECK: (func $reffer (type $5)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $possible-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffer (export "out2")
;; Take references to the possible functions so that GUFA does not optimize
;; calls to them away.
(drop
(ref.func $possible-1)
)
(drop
(ref.func $possible-2)
)
)
)
;; Test for a cast with incompatible heap types but nullability allows the cast
;; to succeed at runtime.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $X (sub (struct)))
(type $X (sub (struct)))
;; CHECK: (type $Y1 (sub $X (struct)))
(type $Y1 (sub $X (struct)))
;; CHECK: (type $Y2 (sub $X (struct)))
(type $Y2 (sub $X (struct)))
;; CHECK: (type $A (func (param anyref)))
(type $A (func (param anyref)))
)
;; CHECK: (type $4 (func (param i32)))
;; CHECK: (export "out" (func $caller))
;; CHECK: (func $called (type $A) (param $ref anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref null $Y1)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $called (type $A) (param $ref anyref)
(drop
(ref.cast (ref null $Y1)
(local.get $ref)
)
)
)
;; CHECK: (func $caller (type $4) (param $i i32)
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (ref.cast (ref $Y1)
;; CHECK-NEXT: (select (result (ref $X))
;; CHECK-NEXT: (struct.new_default $Y1)
;; CHECK-NEXT: (struct.new_default $Y2)
;; CHECK-NEXT: (local.get $i)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out")
(param $i i32)
;; The param is either a null or a Y2, but only the null can pass the cast
;; in the called function, so we can infer a value of null here.
(call $called
(select (result (ref null $Y2))
(ref.null $Y2)
(struct.new $Y2)
(local.get $i)
)
)
;; If no null is possible, this must trap.
(call $called
(select (result (ref $Y2))
(struct.new $Y2)
(struct.new $Y2)
(local.get $i)
)
)
;; Only Y1 would succeed, so we can infer that the cast can be to $Y1.
(call $called
(ref.cast (ref $X)
(select (result (ref $X))
(struct.new $Y1)
(struct.new $Y2)
(local.get $i)
)
)
)
)
)