;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.

;; We should optimize casts to desc casts only when the flag is set.

;; RUN: foreach %s %t wasm-opt --gsi           -all --closed-world -S -o - | filecheck %s
;; RUN: foreach %s %t wasm-opt --gsi-desc-cast -all --closed-world -S -o - | filecheck %s --check-prefix=DESCC

;; Two types with descriptors and subtyping between them.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    ;; DESCC:      (rec
    ;; DESCC-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    (type $A (sub (descriptor $A.desc) (struct)))
    ;; CHECK:       (type $A.desc (sub (describes $A) (struct)))
    ;; DESCC:       (type $A.desc (sub (describes $A) (struct)))
    (type $A.desc (sub (describes $A) (struct)))

    ;; CHECK:       (type $B (sub $A (descriptor $B.desc) (struct)))
    ;; DESCC:       (type $B (sub $A (descriptor $B.desc) (struct)))
    (type $B (sub $A (descriptor $B.desc) (struct)))
    ;; CHECK:       (type $B.desc (sub $A.desc (describes $B) (struct)))
    ;; DESCC:       (type $B.desc (sub $A.desc (describes $B) (struct)))
    (type $B.desc (sub $A.desc (describes $B) (struct)))
  )

  ;; CHECK:      (type $4 (func (param anyref)))

  ;; CHECK:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  ;; DESCC:      (type $4 (func (param anyref)))

  ;; DESCC:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  (global $A.desc (ref $A.desc) (struct.new $A.desc))

  ;; CHECK:      (global $B.desc (ref $B.desc) (struct.new_default $B.desc))
  ;; DESCC:      (global $B.desc (ref $B.desc) (struct.new_default $B.desc))
  (global $B.desc (ref $B.desc) (struct.new $B.desc))

  ;; CHECK:      (func $test (type $4) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $B)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test (type $4) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast (ref $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast_desc_eq (ref $B)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:    (global.get $B.desc)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; The second cast here is optimizable: it can only be to a single type with
    ;; no subtypes, so we can use ref.cast_desc_eq.
    (drop
      (ref.cast (ref $A)
        (local.get $any)
      )
    )
    (drop
      (ref.cast (ref $B)
        (local.get $any)
      )
    )
  )

  ;; CHECK:      (func $test-exact (type $4) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $A))
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $B))
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test-exact (type $4) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast (ref (exact $A))
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast_desc_eq (ref $B)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:    (global.get $B.desc)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test-exact (param $any anyref)
    ;; When using exact casts, we can optimize both. TODO: atm we do not
    ;; optimize $A, as we propagate on |typeGlobals|.
    (drop
      (ref.cast (ref (exact $A))
        (local.get $any)
      )
    )
    (drop
      (ref.cast (ref (exact $B))
        (local.get $any)
      )
    )
  )
)

;; As above, but without subtyping between $A.desc and $B.desc.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    ;; DESCC:      (rec
    ;; DESCC-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    (type $A (sub (descriptor $A.desc) (struct)))
    ;; CHECK:       (type $A.desc (sub (describes $A) (struct)))
    ;; DESCC:       (type $A.desc (sub (describes $A) (struct)))
    (type $A.desc (sub (describes $A) (struct)))

    ;; CHECK:       (type $B (sub (descriptor $B.desc) (struct)))
    ;; DESCC:       (type $B (sub (descriptor $B.desc) (struct)))
    (type $B (sub (descriptor $B.desc) (struct)))
    ;; CHECK:       (type $B.desc (sub (describes $B) (struct)))
    ;; DESCC:       (type $B.desc (sub (describes $B) (struct)))
    (type $B.desc (sub (describes $B) (struct)))
  )

  ;; CHECK:      (type $4 (func (param anyref)))

  ;; CHECK:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  ;; DESCC:      (type $4 (func (param anyref)))

  ;; DESCC:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  (global $A.desc (ref $A.desc) (struct.new $A.desc))

  ;; CHECK:      (global $B.desc (ref $B.desc) (struct.new_default $B.desc))
  ;; DESCC:      (global $B.desc (ref $B.desc) (struct.new_default $B.desc))
  (global $B.desc (ref $B.desc) (struct.new $B.desc))

  ;; CHECK:      (func $test (type $4) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $B)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test (type $4) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast_desc_eq (ref $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:    (global.get $A.desc)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast_desc_eq (ref $B)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:    (global.get $B.desc)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; We can fully optimize these two independent cases.
    (drop
      (ref.cast (ref $A)
        (local.get $any)
      )
    )
    (drop
      (ref.cast (ref $B)
        (local.get $any)
      )
    )
  )
)

;; Zero descriptor instances in globals.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    ;; DESCC:      (rec
    ;; DESCC-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    (type $A (sub (descriptor $A.desc) (struct)))
    ;; CHECK:       (type $A.desc (sub (describes $A) (struct)))
    ;; DESCC:       (type $A.desc (sub (describes $A) (struct)))
    (type $A.desc (sub (describes $A) (struct)))
  )

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

  ;; CHECK:      (func $test (type $2) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (type $2 (func (param anyref)))

  ;; DESCC:      (func $test (type $2) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast (ref $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; We do not optimize here. TODO: we could make this trap
    (drop
      (ref.cast (ref $A)
        (local.get $any)
      )
    )
  )
)

;; Two descriptor instances in globals.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    ;; DESCC:      (rec
    ;; DESCC-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    (type $A (sub (descriptor $A.desc) (struct)))
    ;; CHECK:       (type $A.desc (sub (describes $A) (struct)))
    ;; DESCC:       (type $A.desc (sub (describes $A) (struct)))
    (type $A.desc (sub (describes $A) (struct)))
  )

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

  ;; CHECK:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  ;; DESCC:      (type $2 (func (param anyref)))

  ;; DESCC:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  (global $A.desc (ref $A.desc) (struct.new $A.desc))

  ;; CHECK:      (global $A.desc2 (ref $A.desc) (struct.new_default $A.desc))
  ;; DESCC:      (global $A.desc2 (ref $A.desc) (struct.new_default $A.desc))
  (global $A.desc2 (ref $A.desc) (struct.new $A.desc))

  ;; CHECK:      (func $test (type $2) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block ;; (replaces unreachable RefCast we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test (type $2) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast (ref $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (block ;; (replaces unreachable RefCast we can't emit)
  ;; DESCC-NEXT:    (drop
  ;; DESCC-NEXT:     (unreachable)
  ;; DESCC-NEXT:    )
  ;; DESCC-NEXT:    (unreachable)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; We do not optimize here. TODO: we could with a select
    (drop
      (ref.cast (ref $A)
        (local.get $any)
      )
    )
    ;; We do not error on unreachable casts.
    (drop
      (ref.cast (ref $A)
        (unreachable)
      )
    )
  )
)

(module
  ;; CHECK:      (type $0 (func (param anyref)))

  ;; CHECK:      (type $A (sub (struct)))
  ;; DESCC:      (type $0 (func (param anyref)))

  ;; DESCC:      (type $A (sub (struct)))
  (type $A (sub (struct)))

  ;; CHECK:      (func $test (type $0) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test (type $0) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast (ref $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; We do not handle casts to types without descriptors.
    (drop
      (ref.cast (ref $A)
        (local.get $any)
      )
    )
  )
)

;; Nullable cast.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    ;; DESCC:      (rec
    ;; DESCC-NEXT:  (type $A (sub (descriptor $A.desc) (struct)))
    (type $A (sub (descriptor $A.desc) (struct)))
    ;; CHECK:       (type $A.desc (sub (describes $A) (struct)))
    ;; DESCC:       (type $A.desc (sub (describes $A) (struct)))
    (type $A.desc (sub (describes $A) (struct)))
  )

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

  ;; CHECK:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  ;; DESCC:      (type $2 (func (param anyref)))

  ;; DESCC:      (global $A.desc (ref $A.desc) (struct.new_default $A.desc))
  (global $A.desc (ref $A.desc) (struct.new $A.desc))

  ;; CHECK:      (func $test (type $2) (param $any anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $A)
  ;; CHECK-NEXT:    (local.get $any)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; DESCC:      (func $test (type $2) (param $any anyref)
  ;; DESCC-NEXT:  (drop
  ;; DESCC-NEXT:   (ref.cast_desc_eq (ref null $A)
  ;; DESCC-NEXT:    (local.get $any)
  ;; DESCC-NEXT:    (global.get $A.desc)
  ;; DESCC-NEXT:   )
  ;; DESCC-NEXT:  )
  ;; DESCC-NEXT: )
  (func $test (param $any anyref)
    ;; The cast is nullable, which we can still optimize: null will succeed as
    ;; expected.
    (drop
      (ref.cast (ref null $A)
        (local.get $any)
      )
    )
  )
)

