;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt -all --closed-world --preserve-type-order \
;; RUN:     --unsubtyping --remove-unused-types -all -S -o - | filecheck %s

(module
  (rec
    ;; We must maintain this descriptor relationship so that when $struct flows
    ;; out to JS it still has the prototype configured on its descriptor.
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct-proto (descriptor $desc-proto) (struct))
    (type $struct-proto (descriptor $desc-proto) (struct))
    ;; CHECK:       (type $desc-proto (describes $struct-proto) (struct (field externref)))
    (type $desc-proto (describes $struct-proto) (struct (field externref)))

    ;; But this descriptor cannot have a configured prototype, so we do not need
    ;; to maintain the relationship.
    ;; CHECK:       (type $struct-no-proto (struct))
    (type $struct-no-proto (descriptor $desc-no-proto) (struct))
    (type $desc-no-proto (describes $struct-no-proto) (struct (field (mut externref))))

    ;; This descriptor has a valid prototype field, but is not sent to JS. It
    ;; can be optimized out.
    ;; CHECK:       (type $struct-not-sent (struct))
    (type $struct-not-sent (descriptor $desc-not-sent) (struct))
    (type $desc-not-sent (describes $struct-not-sent) (struct (field externref)))

    ;; Receiving a type from JS as means it is implicitly cast from any, which
    ;; can cause the subtype relationship to be kept.
    ;; CHECK:       (type $super-param (sub (struct)))
    (type $super-param (sub (struct)))
    ;; CHECK:       (type $sub-param (sub $super-param (struct)))
    (type $sub-param (sub $super-param (struct)))

    ;; Returning a type to JS means it flows into an anyref posistion, which
    ;; can cause the subytpe relationship to be kept.
    ;; CHECK:       (type $super-result (sub (struct)))
    (type $super-result (sub (struct)))
    ;; CHECK:       (type $sub-result (sub $super-result (struct)))
    (type $sub-result (sub $super-result (struct)))
  )

  ;; CHECK:       (type $8 (func))

  ;; CHECK:       (type $9 (func (result (ref $struct-proto))))

  ;; CHECK:       (type $10 (func (result (ref $struct-no-proto))))

  ;; CHECK:       (type $11 (func (param (ref $struct-not-sent))))

  ;; CHECK:       (type $12 (func (param (ref null $super-param))))

  ;; CHECK:       (type $13 (func (result (ref null $sub-result))))

  ;; CHECK:      (type $prototypes (array (mut externref)))
  (type $prototypes (array (mut externref)))
  ;; CHECK:      (type $funcs (array (mut funcref)))
  (type $funcs (array (mut funcref)))
  ;; CHECK:      (type $data (array (mut i8)))
  (type $data (array (mut i8)))
  ;; CHECK:      (type $configureAll (func (param (ref null $prototypes) (ref null $funcs) (ref null $data) externref)))
  (type $configureAll (func (param (ref null $prototypes)) (param (ref null $funcs)) (param (ref null $data)) (param externref)))

  ;; CHECK:      (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll) (param (ref null $prototypes) (ref null $funcs) (ref null $data) externref)))
  (import "wasm:js-prototypes" "configureAll" (func $configureAll (type $configureAll)))

  ;; CHECK:      (data $data "12345678")
  (data $data "12345678")

  ;; CHECK:      (elem $prototypes externref (item (ref.null noextern)))
  (elem $prototypes externref (ref.null extern))

  ;; CHECK:      (elem $funcs func $return-struct-proto $return-struct-no-proto $receive-proto $receive $return)
  (elem $funcs funcref (ref.func $return-struct-proto) (ref.func $return-struct-no-proto) (ref.func $receive-proto) (ref.func $receive) (ref.func $return))

  ;; CHECK:      (start $start)
  (start $start)

  ;; CHECK:      (func $start (type $8)
  ;; CHECK-NEXT:  (call $configureAll
  ;; CHECK-NEXT:   (array.new_elem $prototypes $prototypes
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (array.new_elem $funcs $funcs
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (array.new_data $data $data
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 8)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.null noextern)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $start
    (call $configureAll
      (array.new_elem $prototypes $prototypes (i32.const 0) (i32.const 1))
      (array.new_elem $funcs $funcs (i32.const 0) (i32.const 5))
      (array.new_data $data $data (i32.const 0) (i32.const 8))
      (ref.null extern)
    )
  )

  ;; CHECK:      (func $return-struct-proto (type $9) (result (ref $struct-proto))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $return-struct-proto (result (ref $struct-proto))
    ;; Looking at the function signature, $struct-proto flows out to JS. Since
    ;; its descriptor can have a configured prototype, it must be kept. It
    ;; doesn't matter that the function actually traps.
    (unreachable)
  )

  ;; CHECK:      (func $return-struct-no-proto (type $10) (result (ref $struct-no-proto))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $return-struct-no-proto (result (ref $struct-no-proto))
    ;; No need to keep a descriptor that cannot hold a configured prototype.
    (unreachable)
  )

  ;; CHECK:      (func $receive-proto (type $11) (param $0 (ref $struct-not-sent))
  ;; CHECK-NEXT: )
  (func $receive-proto (param (ref $struct-not-sent))
    ;; Since we are not passing $struct-not-sent to JS, we do not need to
    ;; preserve its descriptor, even though that descriptor could hold a
    ;; prototype.
  )

  ;; CHECK:      (func $receive (type $12) (param $0 (ref null $super-param))
  ;; CHECK-NEXT:  (local $any anyref)
  ;; CHECK-NEXT:  (local $sub-param (ref null $sub-param))
  ;; CHECK-NEXT:  (local.set $any
  ;; CHECK-NEXT:   (local.get $sub-param)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $receive (param (ref null $super-param))
    ;; There is an implicit cast from any to $super-param on the boundary. This
    ;; combined with $sub-param flowing into any here means that the subtyping
    ;; must be maintained.
    (local $any anyref)
    (local $sub-param (ref null $sub-param))
    (local.set $any
      (local.get $sub-param)
    )
  )

  ;; CHECK:      (func $return (type $13) (result (ref null $sub-result))
  ;; CHECK-NEXT:  (local $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref null $super-result)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $return (result (ref null $sub-result))
    ;; $sub-result implicitly flows into any on the boundary. This combined with
    ;; the cast from any to $super-result here means that the subtyping must be
    ;; maintained.
    (local $any anyref)
    (drop
      (ref.test (ref null $super-result)
        (local.get $any)
      )
    )
    (unreachable)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (struct))
    (type $struct (descriptor $desc) (struct))
    (type $desc (describes $struct) (struct (field externref)))
  )

  ;; CHECK:       (type $1 (func (result anyref)))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-any (type $1) (result anyref)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-any (result anyref)
    ;; Returning any to JS does not on its own require $struct to keep its
    ;; descriptor because $struct never flows into an any location.
    (local $struct (ref null $struct))
    (unreachable)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (descriptor $desc) (struct))
    (type $struct (descriptor $desc) (struct))
    ;; CHECK:       (type $desc (describes $struct) (struct (field externref)))
    (type $desc (describes $struct) (struct (field externref)))
  )

  ;; CHECK:       (type $2 (func (result anyref)))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-any (type $2) (result anyref)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (local.get $struct)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-any (result anyref)
    ;; Now we require $struct <: any, so exposing any requires keeping the
    ;; descriptor for $struct.
    (local $struct (ref null $struct))
    (local.get $struct)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (struct))
    (type $struct (descriptor $desc) (struct))
    (type $desc (describes $struct) (struct (field nullexternref)))
  )

  ;; CHECK:       (type $1 (func (result anyref)))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-any (type $1) (result anyref)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (local.get $struct)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-any (result anyref)
    ;; Same, but now the descriptor cannot configure a JS prototype, so we do
    ;; not need to keep it.
    (local $struct (ref null $struct))
    (local.get $struct)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (struct)))
    (type $super (sub (struct)))
    ;; CHECK:       (type $sub (sub (struct)))
    (type $sub (sub $super (descriptor $desc) (struct)))
    (type $desc (describes $sub) (struct (field externref)))
  )

  ;; CHECK:       (type $2 (func (result (ref null $super))))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-super (type $2) (result (ref null $super))
  ;; CHECK-NEXT:  (local $sub (ref null $sub))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-super (result (ref null $super))
    ;; Returning $super to JS does not on its own require $sub to keep its
    ;; descriptor because $sub never flows into a $sub location.
    (local $sub (ref null $sub))
    (unreachable)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (struct)))
    (type $super (sub (struct)))
    ;; CHECK:       (type $sub (sub $super (descriptor $desc) (struct)))
    (type $sub (sub $super (descriptor $desc) (struct)))
    ;; CHECK:       (type $desc (describes $sub) (struct (field externref)))
    (type $desc (describes $sub) (struct (field externref)))
  )

  ;; CHECK:       (type $3 (func (result (ref null $super))))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-super (type $3) (result (ref null $super))
  ;; CHECK-NEXT:  (local $sub (ref null $sub))
  ;; CHECK-NEXT:  (local.get $sub)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-super (result (ref null $super))
    ;; Now $sub is exposed to JS via $super, so it must keep its descriptor.
    (local $sub (ref null $sub))
    (local.get $sub)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (struct)))
    (type $super (sub (struct)))
    ;; CHECK:       (type $sub (sub $super (struct)))
    (type $sub (sub $super (descriptor $desc) (struct)))
    (type $desc (describes $sub) (struct (field nullexternref)))
  )

  ;; CHECK:       (type $2 (func (result (ref null $super))))

  ;; CHECK:      (@binaryen.js.called)
  ;; CHECK-NEXT: (func $expose-super (type $2) (result (ref null $super))
  ;; CHECK-NEXT:  (local $sub (ref null $sub))
  ;; CHECK-NEXT:  (local.get $sub)
  ;; CHECK-NEXT: )
  (@binaryen.js.called)
  (func $expose-super (result (ref null $super))
    ;; Now the descriptor cannot configure a JS prototype, so we can still
    ;; optimize it out.
    (local $sub (ref null $sub))
    (local.get $sub)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (descriptor $desc) (struct))
    (type $struct (descriptor $desc) (struct))
    ;; CHECK:       (type $desc (describes $struct) (struct (field externref)))
    (type $desc (describes $struct) (struct (field externref)))
  )

  ;; CHECK:       (type $2 (func))

  ;; CHECK:      (func $externalize (type $2)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $externalize
    ;; Expose $struct to JS by externalizing it, requiring us to keep its
    ;; descriptor.
    (local $struct (ref null $struct))
    (drop
      (extern.convert_any
        (local.get $struct)
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (struct))
    (type $struct (descriptor $desc) (struct))
    (type $desc (describes $struct) (struct (field externref)))
  )

  ;; CHECK:       (type $1 (func))

  ;; CHECK:      (func $externalize (type $1)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (local $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $externalize
    ;; Expose any to JS by externalizing it. This does not on its own require us
    ;; to keep $struct's descriptor.
    (local $struct (ref null $struct))
    (local $any anyref)
    (drop
      (extern.convert_any
        (local.get $any)
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (descriptor $desc) (struct))
    (type $struct (descriptor $desc) (struct))
    ;; CHECK:       (type $desc (describes $struct) (struct (field externref)))
    (type $desc (describes $struct) (struct (field externref)))
  )

  ;; CHECK:       (type $2 (func (result anyref)))

  ;; CHECK:      (func $externalize (type $2) (result anyref)
  ;; CHECK-NEXT:  (local $struct (ref null $struct))
  ;; CHECK-NEXT:  (local $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $struct)
  ;; CHECK-NEXT: )
  (func $externalize (result anyref)
    ;; Expose any to JS by externalizing it. Now we also require $struct <: any,
    ;; so we must keep $struct's descriptor.
    (local $struct (ref null $struct))
    (local $any anyref)
    (drop
      (extern.convert_any
        (local.get $any)
      )
    )
    (local.get $struct)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (struct)))
    (type $super (sub (struct)))
    ;; CHECK:       (type $sub (sub (struct)))
    (type $sub (sub $super (descriptor $desc) (struct)))
    (type $desc (describes $sub) (struct (field externref)))
  )

  ;; CHECK:       (type $2 (func))

  ;; CHECK:      (func $externalize (type $2)
  ;; CHECK-NEXT:  (local $super (ref null $super))
  ;; CHECK-NEXT:  (local $sub (ref null $sub))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $super)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $externalize
    ;; Expose $super to JS by externalizing it. This does not require keeping
    ;; $sub's descriptor because $sub does not remain a subtype of $super.
    (local $super (ref null $super))
    (local $sub (ref null $sub))
    (drop
      (extern.convert_any
        (local.get $super)
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (struct)))
    (type $super (sub (struct)))
    ;; CHECK:       (type $sub (sub $super (descriptor $desc) (struct)))
    (type $sub (sub $super (descriptor $desc) (struct)))
    ;; CHECK:       (type $desc (describes $sub) (struct (field externref)))
    (type $desc (describes $sub) (struct (field externref)))
  )

  ;; CHECK:       (type $3 (func (result (ref null $super))))

  ;; CHECK:      (func $externalize (type $3) (result (ref null $super))
  ;; CHECK-NEXT:  (local $super (ref null $super))
  ;; CHECK-NEXT:  (local $sub (ref null $sub))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (extern.convert_any
  ;; CHECK-NEXT:    (local.get $super)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $sub)
  ;; CHECK-NEXT: )
  (func $externalize (result (ref null $super))
    ;; Expose $super to JS by externalizing it. Now also require $sub <: $super,
    ;; we we must keep $sub's descriptor.
    (local $super (ref null $super))
    (local $sub (ref null $sub))
    (drop
      (extern.convert_any
        (local.get $super)
      )
    )
    (local.get $sub)
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $public-super (sub (struct)))
    (type $public-super (sub (struct)))
    ;; CHECK:       (type $private-sub (sub (struct)))
    (type $private-sub (sub $public-super (descriptor $desc) (struct)))
    (type $desc (describes $private-sub) (struct (field externref)))
  )

  ;; Since we assume with a closed world that the environment does not do
  ;; anything to observe differences between private subtypes and their public
  ;; supertypes, we remain free to optimize out descriptors on private subtypes
  ;; as long as the public supertypes do not have them, even if those
  ;; descriptors might configure prototypes.
  ;;
  ;; We might reasonably have chosen to consider public types and all their
  ;; subtypes JS-exposed instead, like we do for types returned by JS-called
  ;; functions. This test documents that we have not chosen this policy.

  ;; CHECK:      (global $public (ref null $public-super) (ref.null none))
  (global $public (ref null $public-super) (ref.null none))

  ;; CHECK:      (global $private (ref null $private-sub) (ref.null none))
  (global $private (ref null $private-sub) (ref.null none))
)
