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

;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world -all -S -o - | filecheck %s
;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements                -all -S -o - | filecheck %s --check-prefix OPEN_WORLD

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $foo (func))
    ;; OPEN_WORLD:      (rec
    ;; OPEN_WORLD-NEXT:  (type $foo (func))
    (type $foo (func))
    ;; CHECK:       (type $bar (func))
    ;; OPEN_WORLD:       (type $bar (func))
    (type $bar (func))
  )

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

  ;; CHECK:      (table $table 10 funcref)
  ;; OPEN_WORLD:      (type $2 (func))

  ;; OPEN_WORLD:      (table $table 10 funcref)
  (table $table 10 funcref)
  ;; CHECK:      (elem $table (i32.const 0) $foo-in-table $bar)
  ;; OPEN_WORLD:      (elem $table (i32.const 0) $foo-in-table $bar)
  (elem $table (i32.const 0) $foo-in-table $bar)

  ;; CHECK:      (elem declare func $foo-not-in-table)

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

  ;; CHECK:      (func $export (type $2)
  ;; CHECK-NEXT:  (call_indirect $table (type $foo)
  ;; CHECK-NEXT:   (i32.const 5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $foo-not-in-table)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (elem declare func $foo-not-in-table)

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

  ;; OPEN_WORLD:      (func $export (type $2)
  ;; OPEN_WORLD-NEXT:  (call_indirect $table (type $foo)
  ;; OPEN_WORLD-NEXT:   (i32.const 5)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (ref.func $foo-not-in-table)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $export (export "export")
    ;; Call type $foo and nothing else.
    (call_indirect $table (type $foo)
      ;; TODO: we could track indexes in the table.
      (i32.const 5)
    )
    ;; Refer to $foo-not-in-table.
    (drop
      (ref.func $foo-not-in-table)
    )
  )

  ;; CHECK:      (func $foo-in-table (type $foo)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 10)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-in-table (type $foo)
    ;; This should not change: type $foo is called, and this is in the table.
    (drop (i32.const 10))
  )

  ;; CHECK:      (func $foo-not-in-table (type $foo)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-not-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 20)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-not-in-table (type $foo)
    ;; This can be made unreachable: its type is called, but it is not in the
    ;; table, and we can see the table's contents: no table.set etc. can put us
    ;; there, and it is not imported/exported. That we are referred to is not
    ;; enough to keep our contents alive, at least not in closed world (in open
    ;; world, our reference might escape and be called outside).
    (drop (i32.const 20))
  )

  ;; CHECK:      (func $bar (type $bar)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $bar (type $bar)
  ;; OPEN_WORLD-NEXT:  (unreachable)
  ;; OPEN_WORLD-NEXT: )
  (func $bar (type $bar)
    ;; This can be made unreachable: its type is not even called, even though it
    ;; is in the table.
    (drop (i32.const 30))
  )
)

;; As above, but now the table is exported. It might be written to from the
;; outside, preventing some opts.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $foo (func))
    ;; OPEN_WORLD:      (rec
    ;; OPEN_WORLD-NEXT:  (type $foo (func))
    (type $foo (func))
    ;; CHECK:       (type $bar (func))
    ;; OPEN_WORLD:       (type $bar (func))
    (type $bar (func))
  )

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

  ;; CHECK:      (table $table 10 funcref)
  ;; OPEN_WORLD:      (type $2 (func))

  ;; OPEN_WORLD:      (table $table 10 funcref)
  (table $table 10 funcref)
  ;; CHECK:      (elem $table (i32.const 0) $foo-in-table $bar)
  ;; OPEN_WORLD:      (elem $table (i32.const 0) $foo-in-table $bar)
  (elem $table (i32.const 0) $foo-in-table $bar)

  ;; CHECK:      (elem declare func $foo-not-in-table)

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

  ;; CHECK:      (export "table" (table $table))
  ;; OPEN_WORLD:      (elem declare func $foo-not-in-table)

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

  ;; OPEN_WORLD:      (export "table" (table $table))
  (export "table" (table $table)) ;; this was added

  ;; CHECK:      (func $export (type $2)
  ;; CHECK-NEXT:  (call_indirect $table (type $foo)
  ;; CHECK-NEXT:   (i32.const 5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $foo-not-in-table)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $export (type $2)
  ;; OPEN_WORLD-NEXT:  (call_indirect $table (type $foo)
  ;; OPEN_WORLD-NEXT:   (i32.const 5)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (ref.func $foo-not-in-table)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $export (export "export")
    (call_indirect $table (type $foo)
      (i32.const 5)
    )
    (drop
      (ref.func $foo-not-in-table)
    )
  )

  ;; CHECK:      (func $foo-in-table (type $foo)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 10)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-in-table (type $foo)
    ;; As above.
    (drop (i32.const 10))
  )

  ;; CHECK:      (func $foo-not-in-table (type $foo)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-not-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 20)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-not-in-table (type $foo)
    ;; Optimization changes here: the table is public, so we must assume this
    ;; could be in the table, written there from outside. Even in closed world,
    ;; we change nothing here.
    (drop (i32.const 20))
  )

  ;; CHECK:      (func $bar (type $bar)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $bar (type $bar)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 30)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $bar (type $bar)
    ;; This changes too: In open world, we cannot assume this is not called, and
    ;; leave it alone.
    (drop (i32.const 30))
  )
)

;; As above, but now the table has a table.set.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $foo (func))
    ;; OPEN_WORLD:      (rec
    ;; OPEN_WORLD-NEXT:  (type $foo (func))
    (type $foo (func))
    ;; CHECK:       (type $bar (func))
    ;; OPEN_WORLD:       (type $bar (func))
    (type $bar (func))
  )

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

  ;; CHECK:      (table $table 10 funcref)
  ;; OPEN_WORLD:      (type $2 (func))

  ;; OPEN_WORLD:      (table $table 10 funcref)
  (table $table 10 funcref)
  ;; CHECK:      (elem $table (i32.const 0) $foo-in-table $bar)
  ;; OPEN_WORLD:      (elem $table (i32.const 0) $foo-in-table $bar)
  (elem $table (i32.const 0) $foo-in-table $bar)

  ;; CHECK:      (elem declare func $foo-not-in-table)

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

  ;; CHECK:      (func $export (type $2)
  ;; CHECK-NEXT:  (call_indirect $table (type $foo)
  ;; CHECK-NEXT:   (i32.const 5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (table.set $table
  ;; CHECK-NEXT:   (i32.const 7)
  ;; CHECK-NEXT:   (ref.null nofunc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $foo-not-in-table)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (elem declare func $foo-not-in-table)

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

  ;; OPEN_WORLD:      (func $export (type $2)
  ;; OPEN_WORLD-NEXT:  (call_indirect $table (type $foo)
  ;; OPEN_WORLD-NEXT:   (i32.const 5)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT:  (table.set $table
  ;; OPEN_WORLD-NEXT:   (i32.const 7)
  ;; OPEN_WORLD-NEXT:   (ref.null nofunc)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (ref.func $foo-not-in-table)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $export (export "export")
    (call_indirect $table (type $foo)
      (i32.const 5)
    )
    (table.set $table
      (i32.const 7)
      (ref.null func)
    )
    ;; Take the reference of $foo-not-in-table, so that it is referred to but
    ;; not in the table. The table.set will make our analysis believe it might
    ;; be there (we do not track the flow of values precisely).
    (drop
      (ref.func $foo-not-in-table)
    )
  )

  ;; CHECK:      (func $foo-in-table (type $foo)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 10)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-in-table (type $foo)
    ;; As above.
    (drop (i32.const 10))
  )

  ;; CHECK:      (func $foo-not-in-table (type $foo)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $foo-not-in-table (type $foo)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 20)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $foo-not-in-table (type $foo)
    ;; The reference taken of this function might be table.set'ed, so we can do
    ;; nothing here.
    (drop (i32.const 20))
  )

  ;; CHECK:      (func $bar (type $bar)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  ;; OPEN_WORLD:      (func $bar (type $bar)
  ;; OPEN_WORLD-NEXT:  (drop
  ;; OPEN_WORLD-NEXT:   (i32.const 30)
  ;; OPEN_WORLD-NEXT:  )
  ;; OPEN_WORLD-NEXT: )
  (func $bar (type $bar)
    ;; As above.
    (drop (i32.const 30))
  )
)
