| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| ;; RUN: wasm-opt %s --optimize-casts -all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (type $A (struct )) |
| (type $A (struct_subtype data)) |
| |
| ;; CHECK: (type $B (struct_subtype $A)) |
| (type $B (struct_subtype $A)) |
| |
| ;; CHECK: (func $ref.as (type $ref?|$A|_=>_none) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.as (param $x (ref null $A)) |
| ;; After the first ref.as, we can use the cast value in later gets, which is |
| ;; more refined. |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ;; In this case we don't really need the last ref.as here, but we leave that |
| ;; for later opts. |
| (drop |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.as-no (type $ref|$A|_=>_none) (param $x (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.as-no (param $x (ref $A)) |
| ;; As above, but the param is now non-nullable anyhow, so we should do |
| ;; nothing. |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.cast (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.cast (param $x (ref struct)) |
| ;; As $ref.as but with ref.casts: we should use the cast value after it has |
| ;; been computed, in both gets. |
| (drop |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $not-past-set (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (call $get) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $not-past-set (param $x (ref struct)) |
| (drop |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ;; The local.set in the middle stops us from helping the last get. |
| (local.set $x |
| (call $get) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $best (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (local $2 (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $best (param $x (ref struct)) |
| (drop |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| ;; Here we should use $A. |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (ref.cast $B |
| (local.get $x) |
| ) |
| ) |
| ;; Here we should use $B, which is even better. |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $best-2 (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $best-2 (param $x (ref struct)) |
| ;; As above, but with the casts reversed. Now we should use $B in both |
| ;; gets. |
| (drop |
| (ref.cast $B |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (drop |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $fallthrough (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (block (result (ref struct)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (block (result (ref $A)) |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (call $get) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $fallthrough (param $x (ref struct)) |
| (drop |
| (ref.cast $A |
| ;; We look through the block, and optimize. |
| (block (result (ref struct)) |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (local.set $x |
| ;; Cannot look through for sets at the moment |
| (block (result (ref $A)) |
| (ref.cast $A |
| (call $get) |
| ) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $past-basic-block (type $ref|struct|_=>_none) (param $x (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $past-basic-block (param $x (ref struct)) |
| (drop |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| ;; The if means the later get is in another basic block. We do not handle |
| ;; this atm. |
| (if |
| (i32.const 0) |
| (return) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (local.set $x |
| (ref.cast $A |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ;; Same behaviour for sets. |
| (if |
| (i32.const 0) |
| (return) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $multiple (type $ref|struct|_ref|struct|_=>_none) (param $x (ref struct)) (param $y (ref struct)) |
| ;; CHECK-NEXT: (local $a (ref struct)) |
| ;; CHECK-NEXT: (local $b (ref struct)) |
| ;; CHECK-NEXT: (local $4 (ref $A)) |
| ;; CHECK-NEXT: (local $5 (ref $A)) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $b |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $4 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $5 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $b |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $multiple (param $x (ref struct)) (param $y (ref struct)) |
| (local $a (ref struct)) |
| (local $b (ref struct)) |
| ;; Two different locals, with overlapping lives. |
| (local.set $a |
| (local.get $x) |
| ) |
| (local.set $b |
| (local.get $y) |
| ) |
| (drop |
| (ref.cast $A |
| (local.get $a) |
| ) |
| ) |
| (drop |
| (ref.cast $A |
| (local.get $b) |
| ) |
| ) |
| ;; These two can be optimized. |
| (drop |
| (local.get $a) |
| ) |
| (drop |
| (local.get $b) |
| ) |
| (local.set $b |
| (local.get $x) |
| ) |
| ;; Now only the first can be, since $b changed. |
| (drop |
| (local.get $a) |
| ) |
| (drop |
| (local.get $b) |
| ) |
| ) |
| |
| ;; CHECK: (func $check-set-basic (type $ref|$A|_ref?|$A|_=>_none) (param $x (ref $A)) (param $y (ref null $A)) |
| ;; CHECK-NEXT: (local $a (ref struct)) |
| ;; CHECK-NEXT: (local $b structref) |
| ;; CHECK-NEXT: (local $4 (ref $A)) |
| ;; CHECK-NEXT: (local $5 (ref $B)) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $b |
| ;; CHECK-NEXT: (local.tee $4 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (local.tee $5 |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $check-set-basic (param $x (ref $A)) (param $y (ref null $A)) |
| (local $a (ref struct)) |
| (local $b (ref null struct)) |
| ;; Param is already non-nullable, so set won't do anything |
| (local.set $a |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (local.get $x) |
| ) |
| (local.set $b |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| (drop |
| (local.get $b) |
| ) |
| (drop |
| (local.tee $a |
| (ref.cast $B |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (local.get $a) |
| ) |
| ) |
| |
| ;; CHECK: (func $check-set-uses-most-casted (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a (ref struct)) |
| ;; CHECK-NEXT: (local $1 (ref $B)) |
| ;; CHECK-NEXT: (local $2 (ref $A)) |
| ;; CHECK-NEXT: (local $3 (ref $B)) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (call $get) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (ref.cast $A |
| ;; CHECK-NEXT: (call $get) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $3 |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast $B |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $check-set-uses-most-casted |
| (local $a (ref struct)) |
| (local.set $a |
| (ref.cast $B |
| (call $get) |
| ) |
| ) |
| (drop |
| (local.get $a) |
| ) |
| (drop |
| ;; This will use the value from the cast in the above local.set |
| ;; since both casts are equally specific |
| (ref.cast $B |
| (local.get $a) |
| ) |
| ) |
| (drop |
| (ref.cast $A |
| (local.get $a) |
| ) |
| ) |
| (local.set $a |
| (ref.cast $A |
| (call $get) |
| ) |
| ) |
| (drop |
| (local.get $a) |
| ) |
| (drop |
| ;; This cast is more specific than the one in the set, so it will be used henceforth |
| (ref.cast $B |
| (local.get $a) |
| ) |
| ) |
| (drop |
| (ref.cast $A |
| (local.get $a) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get (type $none_=>_ref|struct|) (result (ref struct)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $get (result (ref struct)) |
| ;; Helper for the above. |
| (unreachable) |
| ) |
| ) |