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