;; 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) ) ) ) ) )