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

;; RUN: foreach %s %t wasm-opt --cfp --closed-world -all -S -o - | filecheck %s

(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))
  )

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

  ;; CHECK:      (func $impossible-get (type $2)
  ;; CHECK-NEXT:  (local $A (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $impossible-get (local $A (ref null $A))
    (drop
      ;; This type is never created, so a get is impossible, and we can trap.
      (ref.get_desc $A
        (local.get $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 $2 (func (param (ref null $A))))

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

  ;; CHECK:      (func $test (type $2) (param $A (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $B)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A))
    ;; Only one creation of $A, so we can infer the descriptor that is read
    ;; below.
    (drop
      (struct.new_desc $A
        (global.get $B)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $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 $2 (func (param (ref null $A))))

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

  ;; CHECK:      (func $test (type $2) (param $A (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (struct.new_default $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $A
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A))
    ;; As above, but with another struct.new here, with another value, so we
    ;; cannot infer.
    (drop
      (struct.new_desc $A
        (global.get $B)
      )
    )
    (drop
      (struct.new_desc $A
        (struct.new $B)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $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 $2 (func (param (ref null $A))))

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

  ;; CHECK:      (func $test (type $2) (param $A (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $B)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (global.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A))
    ;; As above, but both struct.news agree, so we can infer.
    (drop
      (struct.new_desc $A
        (global.get $B)
      )
    )
    (drop
      (struct.new_desc $A
        (global.get $B)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $A)
      )
    )
  )
)

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

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

  ;; CHECK:      (type $4 (func (param (ref null $A) (ref null $B))))

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

  ;; CHECK:      (func $test (type $4) (param $A (ref null $A)) (param $B (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $A.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $B
  ;; CHECK-NEXT:    (struct.new_default $B.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $A.desc)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $A)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (global.get $A.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $B
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A)) (param $B (ref null $B))
    ;; We can optimize $A's read below, but not $B's.
    (drop
      (struct.new_desc $A
        (global.get $A.desc)
      )
    )
    (drop
      (struct.new_desc $B
        (struct.new $B.desc)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $A)
      )
    )
    (drop
      (ref.get_desc $B
        (local.get $B)
      )
    )
  )
)

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

    ;; CHECK:       (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)))
    (type $B.desc (sub $A.desc (describes $B) (struct)))
  )

  ;; CHECK:      (type $4 (func (param (ref null $A) (ref null $B))))

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

  ;; CHECK:      (func $test (type $4) (param $A (ref null $A)) (param $B (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (global.get $A.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $B
  ;; CHECK-NEXT:    (struct.new_default $B.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $A
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $B
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A)) (param $B (ref null $B))
    ;; As above, but now $B is a subtype of $A, preventing $A's optimization.
    (drop
      (struct.new_desc $A
        (global.get $A.desc)
      )
    )
    (drop
      (struct.new_desc $B
        (struct.new $B.desc)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $A)
      )
    )
    (drop
      (ref.get_desc $B
        (local.get $B)
      )
    )
  )
)

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

    ;; CHECK:       (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)))
    (type $B.desc (sub $A.desc (describes $B) (struct)))
  )

  ;; CHECK:      (type $4 (func (param (ref null $A) (ref null $B))))

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

  ;; CHECK:      (func $test (type $4) (param $A (ref null $A)) (param $B (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $A
  ;; CHECK-NEXT:    (struct.new_default $A.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default_desc $B
  ;; CHECK-NEXT:    (global.get $B.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $A
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $B.desc)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $B)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (global.get $B.desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $A (ref null $A)) (param $B (ref null $B))
    ;; As above, but the roles of $A and $B are reversed: now the subtype $B
    ;; can be optimized but not the supertype. The problem with $A does not stop
    ;; $B from being optimized.
    (drop
      (struct.new_desc $A
        (struct.new $A.desc)
      )
    )
    (drop
      (struct.new_desc $B
        (global.get $B.desc)
      )
    )
    (drop
      (ref.get_desc $A
        (local.get $A)
      )
    )
    (drop
      (ref.get_desc $B
        (local.get $B)
      )
    )
  )
)

(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 $2 (func (result (ref (exact $B)))))

  ;; CHECK:      (func $test (type $2) (result (ref (exact $B)))
  ;; CHECK-NEXT:  (ref.as_non_null
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (struct.new_default_desc $A
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (result (ref (exact $B)))
    ;; We need to add a ref.as_non_null on the descriptor that is read, as the
    ;; function result is non-nullable.
    (ref.get_desc $A
      (struct.new_default_desc $A
        (ref.null none)
      )
    )
  )
)

