blob: a6673463504249bb1fd81645323f1e03a6c01eb8 [file] [edit]
;; 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)
)
)
)
)
)
)