;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt -all --gufa --closed-world -S -o - | filecheck %s

;; $struct is our main test class. $desc is $struct's descriptor.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (sub (descriptor $desc) (struct (field i32) (field i32) (field i32))))
    (type $struct (sub (descriptor $desc) (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $desc (sub (describes $struct) (struct (field funcref))))
    (type $desc (sub (describes $struct) (struct (field funcref))))
  )

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

  ;; CHECK:      (type $3 (func (result i32)))

  ;; CHECK:      (global $desc (ref (exact $desc)) (struct.new $desc
  ;; CHECK-NEXT:  (ref.func $func)
  ;; CHECK-NEXT: ))
  (global $desc (ref (exact $desc)) (struct.new $desc
    (ref.func $func)
  ))

  ;; CHECK:      (global $struct (ref $struct) (struct.new_desc $struct
  ;; CHECK-NEXT:  (i32.const 100)
  ;; CHECK-NEXT:  (i32.const 200)
  ;; CHECK-NEXT:  (i32.const 300)
  ;; CHECK-NEXT:  (global.get $desc)
  ;; CHECK-NEXT: ))
  (global $struct (ref $struct) (struct.new_desc $struct
    (i32.const 100)
    (i32.const 200)
    (i32.const 300)
    (global.get $desc)
  ))

  ;; CHECK:      (elem declare func $func)

  ;; CHECK:      (export "desc" (func $desc))

  ;; CHECK:      (export "struct" (func $struct))

  ;; CHECK:      (func $func (type $3) (result i32)
  ;; CHECK-NEXT:  (i32.const -1)
  ;; CHECK-NEXT: )
  (func $func (result i32)
    (i32.const -1)
  )

  ;; CHECK:      (func $desc (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (global.get $desc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $desc (export "desc")
    ;; Show we can infer the descriptor.
    (drop
      (ref.get_desc $struct
        (global.get $struct)
      )
    )
    ;; Show we can read from the descriptor.
    (drop
      (struct.get $desc 0
        (ref.get_desc $struct
          (global.get $struct)
        )
      )
    )
  )

  ;; CHECK:      (func $struct (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 100)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 200)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 300)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct (export "struct")
    ;; Show we don't disrupt normal struct field inference. Field 1 is
    ;; particuarly interesting as we represent descriptors as field -1
    ;; internally.
    (drop
      (struct.get $struct 0
        (global.get $struct)
      )
    )
    (drop
      (struct.get $struct 1
        (global.get $struct)
      )
    )
    (drop
      (struct.get $struct 2
        (global.get $struct)
      )
    )
  )
)

;; As above, but now we have a parent and subtype of $struct. The parent has no
;; descriptor, while the subtype does.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $parent (sub (struct (field i32) (field i32) (field i32))))
    (type $parent (sub (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $struct (sub $parent (descriptor $desc) (struct (field i32) (field i32) (field i32))))
    (type $struct (sub $parent (descriptor $desc) (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $desc (sub (describes $struct) (struct (field funcref))))
    (type $desc (sub (describes $struct) (struct (field funcref))))

    ;; CHECK:       (type $sub (sub $struct (descriptor $subdesc) (struct (field i32) (field i32) (field i32))))
    (type $sub (sub $struct (descriptor $subdesc) (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $subdesc (sub $desc (describes $sub) (struct (field funcref))))
    (type $subdesc (sub $desc (describes $sub) (struct (field funcref))))
  )


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

  ;; CHECK:      (type $6 (func (result i32)))

  ;; CHECK:      (global $parent (ref $parent) (struct.new $parent
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT:  (i32.const 200)
  ;; CHECK-NEXT:  (i32.const 300)
  ;; CHECK-NEXT: ))
  (global $parent (ref $parent) (struct.new $parent
    (i32.const 10)  ;; disagrees with the child
    (i32.const 200) ;; agrees with the child
    (i32.const 300) ;; agrees with the child
  ))

  ;; CHECK:      (global $desc (ref (exact $desc)) (struct.new $desc
  ;; CHECK-NEXT:  (ref.func $func)
  ;; CHECK-NEXT: ))
  (global $desc (ref (exact $desc)) (struct.new $desc
    (ref.func $func)
  ))

  ;; CHECK:      (global $struct (ref $struct) (struct.new_desc $struct
  ;; CHECK-NEXT:  (i32.const 100)
  ;; CHECK-NEXT:  (i32.const 200)
  ;; CHECK-NEXT:  (i32.const 300)
  ;; CHECK-NEXT:  (global.get $desc)
  ;; CHECK-NEXT: ))
  (global $struct (ref $struct) (struct.new_desc $struct
    (i32.const 100)
    (i32.const 200)
    (i32.const 300)
    (global.get $desc)
  ))

  ;; CHECK:      (global $subdesc (ref (exact $subdesc)) (struct.new $subdesc
  ;; CHECK-NEXT:  (ref.func $func)
  ;; CHECK-NEXT: ))
  (global $subdesc (ref (exact $subdesc)) (struct.new $subdesc
    (ref.func $func) ;; agrees with parent
  ))

  ;; CHECK:      (elem declare func $func)

  ;; CHECK:      (export "desc" (func $desc))

  ;; CHECK:      (export "parent" (func $parent))

  ;; CHECK:      (export "sub" (func $sub))

  ;; CHECK:      (export "struct" (func $struct))

  ;; CHECK:      (func $func (type $6) (result i32)
  ;; CHECK-NEXT:  (i32.const -1)
  ;; CHECK-NEXT: )
  (func $func (result i32)
    (i32.const -1)
  )

  ;; CHECK:      (func $desc (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $struct
  ;; CHECK-NEXT:    (global.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $desc (export "desc")
    ;; We cannot infer the descriptor: the subtype has a different one.
    (drop
      (ref.get_desc $struct
        (global.get $struct)
      )
    )
    ;; While we can't infer the descriptor, we *can* infer the value in the
    ;; descriptor, as all descriptors have the same value.
    (drop
      (struct.get $desc 0
        (ref.get_desc $struct
          (global.get $struct)
        )
      )
    )
  )

  ;; CHECK:      (func $parent (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (global.get $parent)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 200)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 2
  ;; CHECK-NEXT:    (global.get $parent)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $parent (export "parent")
    ;; Show we don't disrupt the parent's inference. We can infer field 1, which
    ;; is in agreement among all possible subtypes.
    (drop
      (struct.get $parent 0
        (global.get $parent)
      )
    )
    (drop
      (struct.get $parent 1
        (global.get $parent)
      )
    )
    (drop
      (struct.get $parent 2
        (global.get $parent)
      )
    )
  )

  ;; CHECK:      (func $sub (type $5)
  ;; CHECK-NEXT:  (local $temp (ref $sub))
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (struct.new_desc $sub
  ;; CHECK-NEXT:    (i32.const 100)
  ;; CHECK-NEXT:    (i32.const 200)
  ;; CHECK-NEXT:    (i32.const 333)
  ;; CHECK-NEXT:    (global.get $subdesc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (global.get $subdesc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 100)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 200)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 333)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $sub (export "sub")
    (local $temp (ref $sub))
    ;; Test $sub, and for variety, not using globals.
    (local.set $temp
      (struct.new_desc $sub
        (i32.const 100) ;; agrees with parent
        (i32.const 200) ;; agrees with parent
        (i32.const 333) ;; disagrees with parent
        (global.get $subdesc)
      )
    )
    ;; We can infer the descriptor.
    (drop
      (ref.get_desc $sub
        (local.get $temp)
      )
    )
    ;; We can infer all these.
    (drop
      (struct.get $sub 0
        (local.get $temp)
      )
    )
    (drop
      (struct.get $sub 1
        (local.get $temp)
      )
    )
    (drop
      (struct.get $sub 2
        (local.get $temp)
      )
    )
  )

  ;; CHECK:      (func $struct (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 100)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 200)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 2
  ;; CHECK-NEXT:    (global.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct (export "struct")
    ;; We can infer the first two.
    (drop
      (struct.get $struct 0
        (global.get $struct)
      )
    )
    (drop
      (struct.get $struct 1
        (global.get $struct)
      )
    )
    (drop
      (struct.get $struct 2
        (global.get $struct)
      )
    )
  )
)

;; As above, but now the descriptor's funcref fields do not agree, so we cannot
;; infer the value there.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (sub (descriptor $desc) (struct (field i32) (field i32) (field i32))))
    (type $struct (sub (descriptor $desc) (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $desc (sub (describes $struct) (struct (field funcref))))
    (type $desc (sub (describes $struct) (struct (field funcref))))

    ;; CHECK:       (type $sub (sub $struct (descriptor $subdesc) (struct (field i32) (field i32) (field i32))))
    (type $sub (sub $struct (descriptor $subdesc) (struct (field i32) (field i32) (field i32))))

    ;; CHECK:       (type $subdesc (sub $desc (describes $sub) (struct (field funcref))))
    (type $subdesc (sub $desc (describes $sub) (struct (field funcref))))
  )

  ;; CHECK:      (type $4 (func (result i32)))

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

  ;; CHECK:      (global $desc (ref (exact $desc)) (struct.new $desc
  ;; CHECK-NEXT:  (ref.func $func)
  ;; CHECK-NEXT: ))
  (global $desc (ref (exact $desc)) (struct.new $desc
    (ref.func $func)
  ))

  ;; CHECK:      (global $struct (ref $struct) (struct.new_desc $struct
  ;; CHECK-NEXT:  (i32.const 100)
  ;; CHECK-NEXT:  (i32.const 200)
  ;; CHECK-NEXT:  (i32.const 300)
  ;; CHECK-NEXT:  (global.get $desc)
  ;; CHECK-NEXT: ))
  (global $struct (ref $struct) (struct.new_desc $struct
    (i32.const 100)
    (i32.const 200)
    (i32.const 300)
    (global.get $desc)
  ))

  ;; CHECK:      (global $subdesc (ref (exact $subdesc)) (struct.new $subdesc
  ;; CHECK-NEXT:  (ref.func $test)
  ;; CHECK-NEXT: ))
  (global $subdesc (ref (exact $subdesc)) (struct.new $subdesc
    (ref.func $test) ;; disagrees with parent
  ))

  ;; CHECK:      (elem declare func $func)

  ;; CHECK:      (export "test" (func $test))

  ;; CHECK:      (func $func (type $4) (result i32)
  ;; CHECK-NEXT:  (i32.const -1)
  ;; CHECK-NEXT: )
  (func $func (result i32)
    (i32.const -1)
  )

  ;; CHECK:      (func $test (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_desc $sub
  ;; CHECK-NEXT:    (i32.const 100)
  ;; CHECK-NEXT:    (i32.const 200)
  ;; CHECK-NEXT:    (i32.const 333)
  ;; CHECK-NEXT:    (global.get $subdesc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.get_desc $struct
  ;; CHECK-NEXT:    (global.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $desc)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref (exact $struct))
  ;; CHECK-NEXT:      (global.get $struct)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (global.get $desc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $desc 0
  ;; CHECK-NEXT:    (ref.get_desc $struct
  ;; CHECK-NEXT:     (global.get $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $4)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result (ref (exact $desc)))
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (ref.cast (ref (exact $struct))
  ;; CHECK-NEXT:        (global.get $struct)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (global.get $desc)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (export "test")
    (drop
      (struct.new_desc $sub
        (i32.const 100) ;; agrees with parent
        (i32.const 200) ;; agrees with parent
        (i32.const 333) ;; disagrees with parent
        (global.get $subdesc)
      )
    )
    ;; We cannot infer the descriptor due to the subtype.
    (drop
      (ref.get_desc $struct
        (global.get $struct)
      )
    )
    ;; Using an exact $struct, we can.
    (drop
      (ref.get_desc $struct
        (ref.cast (ref (exact $struct))
          (global.get $struct)
        )
      )
    )
    ;; We cannot infer from the descriptor due to the different descriptor
    ;; subtypes.
    (drop
      (struct.get $desc 0
        (ref.get_desc $struct
          (global.get $struct)
        )
      )
    )
    ;; Using an exact $struct, we can.
    (drop
      (struct.get $desc 0
        (ref.get_desc $struct
          (ref.cast (ref (exact $struct))
            (global.get $struct)
          )
        )
      )
    )
  )
)

;; Descriptor chain.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (descriptor $B) (struct (field i32)))
    (type $A (descriptor $B) (struct (field i32)))

    ;; CHECK:       (type $B (describes $A) (descriptor $C) (struct (field i32)))
    (type $B (describes $A) (descriptor $C) (struct (field i32)))

    ;; CHECK:       (type $C (describes $B) (struct (field i32)))
    (type $C (describes $B) (struct (field i32)))
  )

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

  ;; CHECK:      (global $C (ref (exact $C)) (struct.new $C
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT: ))
  (global $C (ref (exact $C)) (struct.new $C
    (i32.const 10)
  ))

  ;; CHECK:      (global $B (ref (exact $B)) (struct.new_desc $B
  ;; CHECK-NEXT:  (i32.const 20)
  ;; CHECK-NEXT:  (global.get $C)
  ;; CHECK-NEXT: ))
  (global $B (ref (exact $B)) (struct.new_desc $B
    (i32.const 20)
    (global.get $C)
  ))

  ;; CHECK:      (global $A (ref (exact $A)) (struct.new_desc $A
  ;; CHECK-NEXT:  (i32.const 30)
  ;; CHECK-NEXT:  (global.get $B)
  ;; CHECK-NEXT: ))
  (global $A (ref (exact $A)) (struct.new_desc $A
    (i32.const 30)
    (global.get $B)
  ))

  ;; CHECK:      (export "test" (func $test))

  ;; CHECK:      (func $test (type $3)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (global.get $B)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (global.get $C)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (export "test")
    ;; We can infer these.
    (drop
      (ref.get_desc $A
        (global.get $A)
      )
    )
    (drop
      (ref.get_desc $B
        (global.get $B)
      )
    )
    ;; We can do so all at once too.
    (drop
      (struct.get $C 0
        (ref.get_desc $B
          (ref.get_desc $A
            (global.get $A)
          )
        )
      )
    )
  )
)
