;; 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-cast-all -S -o - | filecheck %s

(module
  ;; CHECK:      (type $none_=>_none (func))
  (type $none_=>_none (func))

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

  ;; CHECK:      (type $B (sub $A (struct)))
  (type $B (sub $A (struct)))

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

  ;; CHECK:      (import "a" "b" (func $import (type $3) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (elem declare func $func $funcs)

  ;; CHECK:      (export "export1" (func $ref))

  ;; CHECK:      (export "export2" (func $int))

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

  ;; CHECK:      (export "export4" (func $funcs))

  ;; CHECK:      (export "export5" (func $unreachable))

  ;; CHECK:      (func $ref (type $none_=>_none)
  ;; CHECK-NEXT:  (local $a (ref $A))
  ;; CHECK-NEXT:  (local.set $a
  ;; CHECK-NEXT:   (struct.new_default $B)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $B))
  ;; CHECK-NEXT:    (local.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref (export "export1")
    (local $a (ref $A))
    (local.set $a
      (struct.new $B)
    )
    (drop
      ;; We can infer that this contains B, and add a cast to that type.
      (local.get $a)
    )
  )

  ;; CHECK:      (func $int (type $none_=>_none)
  ;; CHECK-NEXT:  (local $a i32)
  ;; CHECK-NEXT:  (local.set $a
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $int (export "export2")
    (local $a i32)
    (local.set $a
      (i32.const 1)
    )
    (drop
      ;; We can infer that this contains 1, but there is nothing to do regarding
      ;; the type, which is not a reference.
      (local.get $a)
    )
  )

  ;; CHECK:      (func $func (type $none_=>_none)
  ;; CHECK-NEXT:  (local $a funcref)
  ;; CHECK-NEXT:  (local.set $a
  ;; CHECK-NEXT:   (ref.func $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "export3") (type $none_=>_none)
    (local $a funcref)
    (local.set $a
      (ref.func $func)
    )
    (drop
      ;; We can infer that this contains a ref to $func, which we can apply
      ;; here. We don't need to add a cast in addition to that, as the ref.func
      ;; we add has the refined type already.
      (local.get $a)
    )
  )

  ;; CHECK:      (func $funcs (type $none_=>_none)
  ;; CHECK-NEXT:  (local $a funcref)
  ;; CHECK-NEXT:  (local.set $a
  ;; CHECK-NEXT:   (select (result (ref (exact $none_=>_none)))
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:    (ref.func $funcs)
  ;; CHECK-NEXT:    (call $import)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $none_=>_none))
  ;; CHECK-NEXT:    (local.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $funcs (export "export4") (type $none_=>_none)
    (local $a funcref)
    (local.set $a
      (select
        (ref.func $func)
        (ref.func $funcs)
        (call $import)
      )
    )
    (drop
      ;; We can infer that this contains a ref to $func or $funcs, so all we
      ;; can infer is the type, and we add a cast to $none_=>_none.
      (local.get $a)
    )
  )

  ;; CHECK:      (func $unreachable (type $none_=>_none)
  ;; CHECK-NEXT:  (local $a (ref $A))
  ;; CHECK-NEXT:  (local.tee $a
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable (export "export5")
    (local $a (ref $A))
    (local.set $a
      (unreachable)
    )
    (drop
      ;; We can infer that the type here is unreachable, and emit that in the
      ;; IR. This checks we don't error on the inferred type not being a ref.
      (local.get $a)
    )
  )
)

;; Imported tags may be written to from places we do not see.
(module
  ;; CHECK:      (type $0 (func (param i32)))

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

  ;; CHECK:      (import "fuzzing-support" "throw" (func $throw (type $0) (param i32)))
  (import "fuzzing-support" "throw" (func $throw (param i32)))
  ;; CHECK:      (import "fuzzing-support" "tag" (tag $tag (type $0) (param i32)))
  (import "fuzzing-support" "tag" (tag $tag (param i32)))

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

  ;; CHECK:      (func $func (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $block (result i32)
  ;; CHECK-NEXT:    (try_table (catch $tag $block)
  ;; CHECK-NEXT:     (call $throw
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (return)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func")
    (drop
      ;; If we thought no i32 value could arrive here (if no exception were
      ;; created of this tag) then we'd put an unreachable after it. As it is
      ;; imported, a value might be there, so we do not.
      (block $block (result i32)
        (try_table (catch $tag $block)
          (call $throw
            (i32.const 1)
          )
        )
        (return)
      )
    )
  )
)

;; As above, but with an exported tag. Also test a tag with multiple params.
(module
  ;; CHECK:      (type $0 (func (param i32 f64)))

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

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

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

  ;; CHECK:      (import "fuzzing-support" "throw" (func $throw (type $1) (param i32)))
  (import "fuzzing-support" "throw" (func $throw (param i32)))

  ;; CHECK:      (tag $tag (type $0) (param i32 f64))
  (tag $tag (param i32 f64))

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

  ;; CHECK:      (export "tag" (tag $tag))
  (export "tag" (tag $tag))

  ;; CHECK:      (func $func (type $2)
  ;; CHECK-NEXT:  (tuple.drop 2
  ;; CHECK-NEXT:   (block $block (type $3) (result i32 f64)
  ;; CHECK-NEXT:    (try_table (catch $tag $block)
  ;; CHECK-NEXT:     (call $throw
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (return)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func")
    ;; Once more, we do not optimize to unreachable here.
    (tuple.drop 2
      (block $block (result i32 f64)
        (try_table (catch $tag $block)
          (call $throw
            (i32.const 1)
          )
        )
        (return)
      )
    )
  )
)

;; Private tags are optimizable.
(module
  ;; CHECK:      (type $0 (func (param i32)))

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

  ;; CHECK:      (import "fuzzing-support" "throw" (func $throw (type $0) (param i32)))
  (import "fuzzing-support" "throw" (func $throw (param i32)))

  ;; CHECK:      (tag $tag (type $0) (param i32))
  (tag $tag (param i32))

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

  ;; CHECK:      (func $func (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $block (result i32)
  ;; CHECK-NEXT:      (try_table (catch $tag $block)
  ;; CHECK-NEXT:       (call $throw
  ;; CHECK-NEXT:        (i32.const 1)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (return)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func")
    ;; The tag is neither imported nor exported, so we can optimize to
    ;; unreachable.
    (drop
      (block $block (result i32)
        (try_table (catch $tag $block)
          (call $throw
            (i32.const 1)
          )
        )
        (return)
      )
    )
  )
)

;; Test pre-filtering.
(module
  ;; CHECK:      (type $A (func))
  (type $A (func))

  ;; CHECK:      (import "a" "b" (global $global i32))
  (import "a" "b" (global $global i32))

  ;; CHECK:      (elem declare func $test)

  ;; CHECK:      (func $test (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $A)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $block (result (ref (exact $A)))
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (block (result (ref (exact $A)))
  ;; CHECK-NEXT:        (drop
  ;; CHECK-NEXT:         (br_if $block
  ;; CHECK-NEXT:          (ref.func $test)
  ;; CHECK-NEXT:          (global.get $global)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (ref.func $test)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (br_on_non_null $block
  ;; CHECK-NEXT:       (ref.null nofunc)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.func $test)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (type $A)
    ;; This block is declared as having type $A. Two values appear to reach it:
    ;; one from a br that sends a ref.func, and one from a br_on_non_null which
    ;; sends a null with the type (ref nofunc) (in practice that branch is not
    ;; taken, of course, but GUFA does see all branches; later optimizations
    ;; would optimize the branch away).
    ;;
    ;; We see the ref.func first, so the block $block begins with that content.
    ;; Then we see the null arrive. Immediately combining the null with a
    ;; ref.func would give a cone - the best shape we have that can allow both a
    ;; null and a ref.func. If we later filter the result to the block, which is
    ;; non-nullable, the cone becomes non-nullable too - but it is a cone now,
    ;; and not the original ref.func, preventing us from applying the constant
    ;; value of the ref.func in the output. Early filtering of the arriving
    ;; content fixes this: the null is immediately filtered into nothing, since
    ;; it is null and the location can only contain non-nullable contents. As a
    ;; result, we can optimize the block (and the br_if) to return a ref.func.
    (drop
      (block $block (result (ref $A))
        (drop
          (br_if $block
            (ref.func $test)
            (global.get $global)
          )
        )
        (br_on_non_null $block
          (ref.null nofunc)
        )
        (unreachable)
      )
    )
  )
)

;; Do not refine uncastable types.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $cont (cont $none))
    (type $cont (cont $none))
    ;; CHECK:       (type $none (func))
    (type $none (func))
  )

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

  ;; CHECK:      (elem declare func $suspend)

  ;; CHECK:      (func $suspend (type $none)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $suspend (type $none)
    (nop)
  )

  ;; CHECK:      (func $unrefine (type $2) (result contref)
  ;; CHECK-NEXT:  (block $label (result contref)
  ;; CHECK-NEXT:   (cont.new $cont
  ;; CHECK-NEXT:    (ref.func $suspend)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unrefine (result contref)
    ;; No cast should be added here.
    (block $label (result contref)
      (cont.new $cont
        (ref.func $suspend)
      )
    )
  )
)

