| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| |
| ;; (remove-unused-names allows the pass to see that blocks flow values) |
| ;; RUN: foreach %s %t wasm-opt -all --remove-unused-names --heap2local -S -o - | filecheck %s |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $described (descriptor $descriptor) (struct (field i32))) |
| (type $described (descriptor $descriptor) (struct (field i32))) |
| ;; CHECK: (type $descriptor (describes $described) (struct (field i64))) |
| (type $descriptor (describes $described) (struct (field i64))) |
| |
| ;; CHECK: (type $super (sub (descriptor $super.desc) (struct))) |
| (type $super (sub (descriptor $super.desc) (struct))) |
| ;; CHECK: (type $super.desc (sub (describes $super) (struct))) |
| (type $super.desc (sub (describes $super) (struct))) |
| |
| ;; CHECK: (type $sub (sub $super (descriptor $sub.desc) (struct))) |
| (type $sub (sub $super (descriptor $sub.desc) (struct))) |
| ;; CHECK: (type $sub.desc (sub $super.desc (describes $sub) (struct))) |
| (type $sub.desc (sub $super.desc (describes $sub) (struct))) |
| |
| ;; CHECK: (type $no-desc (struct)) |
| (type $no-desc (struct)) |
| |
| ;; CHECK: (type $chain-described (descriptor $chain-middle) (struct)) |
| (type $chain-described (descriptor $chain-middle) (struct)) |
| ;; CHECK: (type $chain-middle (describes $chain-described) (descriptor $chain-descriptor) (struct)) |
| (type $chain-middle (describes $chain-described) (descriptor $chain-descriptor) (struct)) |
| ;; CHECK: (type $chain-descriptor (describes $chain-middle) (struct)) |
| (type $chain-descriptor (describes $chain-middle) (struct)) |
| ) |
| |
| ;; CHECK: (type $10 (func)) |
| |
| ;; CHECK: (type $11 (func (param (ref null (exact $super.desc))))) |
| |
| ;; CHECK: (type $12 (func (param (ref null (exact $super))))) |
| |
| ;; CHECK: (type $13 (func (result (ref null $super.desc)))) |
| |
| ;; CHECK: (type $14 (func (param (ref null (exact $chain-descriptor))))) |
| |
| ;; CHECK: (type $15 (func (result (ref (exact $super))))) |
| |
| ;; CHECK: (type $16 (func (result (ref (exact $super.desc))))) |
| |
| ;; CHECK: (import "" "" (func $effect (type $10))) |
| (import "" "" (func $effect)) |
| |
| ;; CHECK: (global $desc (ref null (exact $descriptor)) (ref.null none)) |
| (global $desc (ref null (exact $descriptor)) (ref.null none)) |
| |
| ;; CHECK: (func $dropped (type $10) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (global.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dropped |
| (drop |
| (struct.new_desc $described |
| (i32.const 1) |
| (global.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $dropped-default (type $10) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (global.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dropped-default |
| (drop |
| (struct.new_default_desc $described |
| (global.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $dropped-alloc-desc (type $10) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (struct.new $descriptor |
| ;; CHECK-NEXT: (i64.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dropped-alloc-desc |
| (drop |
| (struct.new_desc $described |
| (i32.const 1) |
| (struct.new $descriptor |
| (i64.const 2) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $dropped-default-alloc-desc (type $10) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (struct.new $descriptor |
| ;; CHECK-NEXT: (i64.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dropped-default-alloc-desc |
| (drop |
| (struct.new_default_desc $described |
| (struct.new $descriptor |
| (i64.const 2) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-desc (type $13) (result (ref null $super.desc)) |
| ;; CHECK-NEXT: (local $0 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (struct.new_default $super.desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| (func $get-desc (result (ref null $super.desc)) |
| (ref.get_desc $super |
| (struct.new_desc $super |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-desc-refinalize (type $13) (result (ref null $super.desc)) |
| ;; CHECK-NEXT: (local $0 (ref (exact $sub.desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $sub.desc))) |
| ;; CHECK-NEXT: (block (result (ref (exact $sub.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (struct.new_default $sub.desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $get-desc-refinalize (result (ref null $super.desc)) |
| ;; This block should be refinalized. |
| (block (result (ref null $super.desc)) |
| (ref.get_desc $super |
| (block (result (ref null $super)) |
| (struct.new_desc $sub |
| (struct.new $sub.desc) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-success (type $10) |
| ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local.set $desc |
| ;; CHECK-NEXT: (struct.new_default $super.desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result nullref) |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-success |
| (local $desc (ref null (exact $super.desc))) |
| (local.set $desc |
| (struct.new $super.desc) |
| ) |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (struct.new_desc $super |
| (local.get $desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (struct.new_default $super.desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result nullref) |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail (param $desc (ref null (exact $super.desc))) |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (struct.new_desc $super |
| (struct.new $super.desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail-reverse (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result nullref) |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail-reverse (param $desc (ref null (exact $super.desc))) |
| ;; Same as above, but change where the parameter is used. |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (struct.new_desc $super |
| (local.get $desc) |
| ) |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail-param (type $12) (param $ref (ref null (exact $super))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail-param (param $ref (ref null (exact $super))) |
| ;; Now cast the parameter. We know it can't have the locally allocated |
| ;; descriptor, so the cast fails. |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (local.get $ref) |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail-param-effect (type $12) (param $ref (ref null (exact $super))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (exact $super))) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail-param-effect (param $ref (ref null (exact $super))) |
| ;; Same, but with effects we cannot drop. |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (block (result (ref null (exact $super))) |
| (call $effect) |
| (local.get $ref) |
| ) |
| (block (result (ref (exact $super.desc))) |
| (call $effect) |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail-param-nullable (type $12) (param $ref (ref null (exact $super))) |
| ;; CHECK-NEXT: (local $1 (ref null (exact $super))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail-param-nullable (param $ref (ref null (exact $super))) |
| ;; Now the cast admits nulls. |
| (drop |
| (ref.cast_desc_eq (ref null (exact $super)) |
| (local.get $ref) |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-fail-param-nullable-effect (type $12) (param $ref (ref null (exact $super))) |
| ;; CHECK-NEXT: (local $1 (ref null (exact $super))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (block (result (ref null (exact $super))) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-fail-param-nullable-effect (param $ref (ref null (exact $super))) |
| ;; Now the cast admits nulls and there are effects we cannot remove. |
| (drop |
| (ref.cast_desc_eq (ref null (exact $super)) |
| (block (result (ref null (exact $super))) |
| (call $effect) |
| (local.get $ref) |
| ) |
| (block (result (ref (exact $super.desc))) |
| (call $effect) |
| (struct.new $super.desc) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-no-desc (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-no-desc (param $desc (ref null (exact $super.desc))) |
| ;; The allocation does not have a descriptor, so we know the cast must fail. |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (struct.new $no-desc) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-no-desc-effect (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-no-desc-effect (param $desc (ref null (exact $super.desc))) |
| ;; Same, but with effects we cannot drop. |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (block (result (ref (exact $no-desc))) |
| (call $effect) |
| (struct.new $no-desc) |
| ) |
| (block (result (ref null (exact $super.desc))) |
| (call $effect) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-no-desc-nullable (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-no-desc-nullable (param $desc (ref null (exact $super.desc))) |
| ;; The allocation does not have a descriptor, so we know the cast must fail. |
| ;; Although the cast admits nulls, we know we don't have a null here, so we |
| ;; don't need to preserve a null cast. |
| (drop |
| (ref.cast_desc_eq (ref null (exact $super)) |
| (struct.new $no-desc) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-no-desc-nullable-effect (type $11) (param $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-no-desc-nullable-effect (param $desc (ref null (exact $super.desc))) |
| ;; Same, but with effects we cannot drop. |
| (drop |
| (ref.cast_desc_eq (ref null (exact $super)) |
| (block (result (ref (exact $no-desc))) |
| (call $effect) |
| (struct.new $no-desc) |
| ) |
| (block (result (ref null (exact $super.desc))) |
| (call $effect) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-and-ref (type $14) (param $desc (ref null (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (local $middle (ref null (exact $chain-middle))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (local $3 (ref (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-and-ref (param $desc (ref null (exact $chain-descriptor))) |
| ;; The same allocation flows into both the descriptor and the reference. The |
| ;; cast must fail because a value cannot be its own descriptor. We make sure |
| ;; the descriptor itself has a descriptor so it is not handled by the same |
| ;; logic as the previous test. |
| (local $middle (ref null (exact $chain-middle))) |
| (local.set $middle |
| (struct.new_desc $chain-middle |
| (local.get $desc) |
| ) |
| ) |
| (drop |
| (ref.cast_desc_eq (ref (exact $chain-described)) |
| (local.get $middle) |
| (local.get $middle) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-and-ref-nullable (type $14) (param $desc (ref null (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (local $middle (ref null (exact $chain-middle))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (local $3 (ref (exact $chain-descriptor))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-and-ref-nullable (param $desc (ref null (exact $chain-descriptor))) |
| ;; Same, but now the cast allows nulls. It should still trap. |
| (local $middle (ref null (exact $chain-middle))) |
| (local.set $middle |
| (struct.new_desc $chain-middle |
| (local.get $desc) |
| ) |
| ) |
| (drop |
| (ref.cast_desc_eq (ref null (exact $chain-described)) |
| (local.get $middle) |
| (local.get $middle) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-and-ref-tee (type $10) |
| ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-and-ref-tee |
| ;; The same allocation flows into both the descriptor and the reference |
| ;; again, but now it uses a tee. The allocation does not have a descriptor. |
| (local $desc (ref null (exact $super.desc))) |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| (local.tee $desc |
| (struct.new $super.desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-and-ref-tee-nullable (type $10) |
| ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-and-ref-tee-nullable |
| ;; Same, but the cast allows nulls. It should still trap. |
| (local $desc (ref null (exact $super.desc))) |
| (drop |
| (ref.cast_desc_eq (ref null (exact $super)) |
| (local.tee $desc |
| (struct.new $super.desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-stale-parent (type $15) (result (ref (exact $super))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $super.desc)) |
| ;; CHECK-NEXT: (call $effect) |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-stale-parent (result (ref (exact $super))) |
| (ref.cast_desc_eq (ref (exact $super)) |
| ;; We will optimize this allocation first, causing the parent |
| ;; ref.cast_desc_eq to be optimized out. The parent map will no longer be up |
| ;; to date when we optimize the second allocation, but we should sill be |
| ;; able to optimize successfully without crashing. |
| (struct.new_default $no-desc) |
| (block (result (ref (exact $super.desc))) |
| (call $effect) |
| (struct.new_default $super.desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-desc-eq-stale-parent-escape (type $16) (result (ref (exact $super.desc))) |
| ;; CHECK-NEXT: (local $desc (ref null (exact $super.desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-desc-eq-stale-parent-escape (result (ref (exact $super.desc))) |
| (local $desc (ref (exact $super.desc))) |
| (drop |
| (ref.cast_desc_eq (ref (exact $super)) |
| ;; Same as above, but now the second alloocation escapes. We should still |
| ;; optimize the first allocation and the cast, and we should still not |
| ;; crash. |
| (struct.new_default $no-desc) |
| (local.tee $desc |
| (struct.new_default $super.desc) |
| ) |
| ) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (sub (descriptor $desc) (struct))) |
| (type $struct (sub (descriptor $desc) (struct))) |
| ;; CHECK: (type $desc (describes $struct) (struct)) |
| (type $desc (describes $struct) (struct)) |
| ) |
| |
| ;; CHECK: (type $2 (func (result (ref (exact $desc))))) |
| |
| ;; CHECK: (type $3 (func (param (ref null (exact $desc))) (result (ref (exact $desc))))) |
| |
| ;; CHECK: (func $null (type $2) (result (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $0 (ref none)) |
| ;; CHECK-NEXT: (local $1 (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| (func $null (result (ref (exact $desc))) |
| ;; Read a null descriptor from a struct.new we can convert to locals. We do |
| ;; not end up with a (ref (exact $desc)) here, since this will trap, so we |
| ;; emit an unreachable. |
| (ref.get_desc $struct |
| (struct.new_default_desc $struct |
| (ref.null none) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nullable-param (type $3) (param $desc (ref null (exact $desc))) (result (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $nullable-param (param $desc (ref null (exact $desc))) (result (ref (exact $desc))) |
| ;; Read a null descriptor from a nullable param. |
| (ref.get_desc $struct |
| (struct.new_default_desc $struct |
| (local.get $desc) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nullable-local (type $2) (result (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $desc (ref null (exact $desc))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $nullable-local (result (ref (exact $desc))) |
| (local $desc (ref null (exact $desc))) |
| ;; Read a null descriptor from a nullable local. |
| (ref.get_desc $struct |
| (struct.new_default_desc $struct |
| (local.get $desc) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A definitely-failing descriptor cast. We have two pairs of descriptor/ |
| ;; describee, and create an $A2 that we try to cast to the unrelated $A. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $A (descriptor $B) (struct)) |
| (type $A (descriptor $B) (struct)) |
| ;; CHECK: (type $B (describes $A) (struct)) |
| (type $B (describes $A) (struct)) |
| |
| ;; CHECK: (type $A2 (descriptor $B2) (struct)) |
| (type $A2 (descriptor $B2) (struct)) |
| ;; CHECK: (type $B2 (describes $A2) (struct)) |
| (type $B2 (describes $A2) (struct)) |
| ) |
| |
| ;; CHECK: (type $4 (func (result (ref $A)))) |
| |
| ;; CHECK: (func $A (type $4) (result (ref $A)) |
| ;; CHECK-NEXT: (local $0 (ref (exact $B2))) |
| ;; CHECK-NEXT: (local $1 (ref (exact $B2))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (struct.new_default $B2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $A (result (ref $A)) |
| (ref.cast_desc_eq (ref $A) |
| (struct.new_default_desc $A2 |
| (struct.new_default $B2) |
| ) |
| (struct.new_default $B) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| (type $A (descriptor $B) (struct)) |
| (type $B (sub (describes $A) (struct))) |
| ) |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (func $test (type $0) |
| ;; CHECK-NEXT: (local $0 (ref none)) |
| ;; CHECK-NEXT: (local $1 (ref none)) |
| ;; CHECK-NEXT: (block $block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (br_on_null $block |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test |
| ;; After removing the ref.get_desc, the null descriptor falls through, and |
| ;; we must update the br_on_null's type, or internal validation errors. |
| (block $block |
| (drop |
| (br_on_null $block |
| (ref.get_desc $A |
| (struct.new_default_desc $A |
| (ref.null none) |
| ) |
| ) |
| ) |
| ) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $A (descriptor $B) (struct (field v128))) |
| (type $A (descriptor $B) (struct (field v128))) |
| ;; CHECK: (type $B (describes $A) (struct)) |
| (type $B (describes $A) (struct)) |
| ) |
| |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (func $test (type $2) |
| ;; CHECK-NEXT: (local $B (ref null $B)) |
| ;; CHECK-NEXT: (local $v v128) |
| ;; CHECK-NEXT: (local $2 v128) |
| ;; CHECK-NEXT: (local $3 (ref none)) |
| ;; CHECK-NEXT: (local $4 (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.tee $v |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (v128.const i32x4 0x00000000 0x00000000 0x00000000 0x00000000) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test |
| (local $B (ref null $B)) |
| (local $v v128) |
| |
| ;; We can optimize a few times here. As we do so, the local.set becomes |
| ;; unreachable, as its descriptor is null. Later work will replace the |
| ;; nested struct.get there, which was unreachable, with a local.get of a |
| ;; v128, a concrete type, causing an error as now the local.set is |
| ;; unreachable but the child is not. To avoid this problem, we should not |
| ;; modify unreachable code. |
| |
| (drop |
| (ref.as_non_null |
| (local.tee $B |
| (struct.new_default $B) |
| ) |
| ) |
| ) |
| (local.set $v |
| (struct.get $A 0 |
| (ref.cast_desc_eq (ref $A) |
| (struct.new_default_desc $A |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| ) |
| (ref.as_non_null |
| (local.get $B) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A chain of descriptors, where initial optimizations influence later ones. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $A (shared (descriptor $B) (struct))) |
| (type $A (shared (descriptor $B) (struct))) |
| ;; CHECK: (type $B (sub (shared (describes $A) (descriptor $C) (struct)))) |
| (type $B (sub (shared (describes $A) (descriptor $C) (struct)))) |
| ;; CHECK: (type $C (sub (shared (describes $B) (struct)))) |
| (type $C (sub (shared (describes $B) (struct)))) |
| ) |
| |
| ;; CHECK: (type $3 (func (result (ref (shared any))))) |
| |
| ;; CHECK: (func $test (type $3) (result (ref (shared any))) |
| ;; CHECK-NEXT: (local $temp (ref $C)) |
| ;; CHECK-NEXT: (local $1 (ref (shared none))) |
| ;; CHECK-NEXT: (local $2 (ref (shared none))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test (result (ref (shared any))) |
| (local $temp (ref $C)) |
| (local.set $temp |
| ;; We optimize this first, making the |local.get| below unreachable, and |
| ;; making that inner ref.cast_desc_eq unreachable, which leads to the |
| ;; |struct.new_default $B| being dropped, and in particular having a new |
| ;; parent (the drop). We should not get confused and error internally. |
| (struct.new_default $C) |
| ) |
| (ref.cast_desc_eq (ref $B) |
| (ref.cast_desc_eq (ref $B) |
| (struct.new_default_desc $B |
| (ref.null (shared none)) |
| ) |
| (local.get $temp) |
| ) |
| (struct.new_default $C) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (sub (descriptor $desc) (struct))) |
| (type $struct (sub (descriptor $desc) (struct))) |
| ;; CHECK: (type $desc (describes $struct) (struct)) |
| (type $desc (sub final (describes $struct) (struct))) |
| ) |
| |
| ;; CHECK: (type $2 (func (result i32))) |
| |
| ;; CHECK: (func $test (type $2) (result i32) |
| ;; CHECK-NEXT: (local $desc (ref $desc)) |
| ;; CHECK-NEXT: (local $1 (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $2 (ref (exact $desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (struct.new_default $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test (result i32) |
| (local $desc (ref $desc)) |
| (local.set $desc |
| (struct.new_default $desc) |
| ) |
| ;; After we optimize the struct.new above, the local.get below will get |
| ;; removed, and we will see that the ref.cast_desc_eq traps (the input |
| ;; descriptor differs from the one we test, the allocation we just |
| ;; removed). When optimizing that, we should not emit invalid IR for the |
| ;; ref.is_null: It has an i32 result normally, but in unreachable code it |
| ;; must remain unreachable. |
| (ref.is_null |
| (ref.cast_desc_eq (ref $struct) |
| (struct.new_default_desc $struct |
| (struct.new_default $desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) |
| (type $struct (descriptor $desc) (struct)) |
| ;; CHECK: (type $desc (sub (describes $struct) (struct (field funcref)))) |
| (type $desc (sub (describes $struct) (struct (field funcref)))) |
| ) |
| |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (func $test (type $2) |
| ;; CHECK-NEXT: (local $desc (ref $desc)) |
| ;; CHECK-NEXT: (local $func funcref) |
| ;; CHECK-NEXT: (local $2 funcref) |
| ;; CHECK-NEXT: (local $3 (ref (exact $desc))) |
| ;; CHECK-NEXT: (local $4 (ref (exact $desc))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.null nofunc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.tee $func |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (struct.new_default $desc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test |
| (local $desc (ref $desc)) |
| (local $func funcref) |
| |
| (local.set $desc |
| (struct.new_default $desc) |
| ) |
| |
| (local.set $func |
| (struct.get $desc 0 |
| ;; This ref.get_desc will become unreachable (as the desc does not |
| ;; match, after expanding locals). We should not generate invalid code |
| ;; from that point - it is unreachable, and we can leave it as is. |
| (ref.get_desc $struct |
| (ref.cast_desc_eq (ref $struct) |
| (struct.new_default_desc $struct |
| (struct.new_default $desc) |
| ) |
| (local.get $desc) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |