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

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

  ;; CHECK:      (type $1 (func (result i32 exnref)))

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

  ;; CHECK:      (tag $e-i32 (type $2) (param i32))
  (tag $e-i32 (param i32))
  ;; CHECK:      (tag $e-empty (type $0))
  (tag $e-empty)

  ;; CHECK:      (func $foo (type $0)
  ;; CHECK-NEXT: )
  (func $foo)

  ;; CHECK:      (func $try_table1 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (block $outer
  ;; CHECK-NEXT:   (block $catch_all
  ;; CHECK-NEXT:    (try_table (catch_all $catch_all)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (br $outer)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table1
    (local $x i32)
    (block $outer
      (block $catch_all
        (try_table (catch_all $catch_all)
        )
        (br $outer)
      )
      (local.set $x (i32.const 1))
    )
    ;; try_table will not throw. So this should NOT be dropped
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $try_table2 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (block $catch_all
  ;; CHECK-NEXT:   (try_table (catch_all $catch_all)
  ;; CHECK-NEXT:    (throw $e-i32
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table2
    (local $x i32)
    (block $catch_all
      (try_table (catch_all $catch_all)
        (throw $e-i32 (i32.const 0))
        (local.set $x (i32.const 1))
      )
    )
    ;; local.set is after 'throw' so it will not run. This should NOT be
    ;; dropped.
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $try_table3 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (block $outer
  ;; CHECK-NEXT:   (block $catch_all
  ;; CHECK-NEXT:    (try_table (catch_all $catch_all)
  ;; CHECK-NEXT:     (call $foo)
  ;; CHECK-NEXT:     (local.set $x
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (br $outer)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table3
    (local $x i32)
    (block $outer
      (block $catch_all
        (try_table (catch_all $catch_all)
          (call $foo)
          (local.set $x (i32.const 1))
        )
        (br $outer)
      )
    )
    ;; (call $foo) may throw and the local.set may not run, so this should NOT
    ;; be dropped
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $try_table4 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (block $outer
  ;; CHECK-NEXT:   (block $catch_all
  ;; CHECK-NEXT:    (try_table (catch_all $catch_all)
  ;; CHECK-NEXT:     (local.set $x
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (call $foo)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (br $outer)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table4
    (local $x i32)
    (block $outer
      (block $catch_all
        (try_table (catch_all $catch_all)
          (local.set $x (i32.const 1))
          (call $foo)
        )
        (br $outer)
      )
    )
    ;; Even if (call $foo) throws, local.set runs before it, so this should be
    ;; dropped
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $nested-try_table1 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (local $exn exnref)
  ;; CHECK-NEXT:  (block $catch_all0
  ;; CHECK-NEXT:   (try_table (catch_all $catch_all0)
  ;; CHECK-NEXT:    (local.set $exn
  ;; CHECK-NEXT:     (block $catch_all_ref1 (result exnref)
  ;; CHECK-NEXT:      (try_table (catch_all_ref $catch_all_ref1)
  ;; CHECK-NEXT:       (throw $e-i32
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (throw_ref
  ;; CHECK-NEXT:     (local.get $exn)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-try_table1
    (local $x i32)
    (local $exn exnref)
    (block $catch_all0
      (try_table (catch_all $catch_all0)
        (local.set $exn
          (block $catch_all_ref1 (result exnref)
            (try_table (catch_all_ref $catch_all_ref1)
              (throw $e-i32 (i32.const 0))
            )
          )
        )
        (local.set $x (i32.const 1))
        (throw_ref (local.get $exn))
      )
    )
    ;; The exception is caught by the inner catch_all_ref, which runs the
    ;; local.set, so this should be dropped
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $nested-try_table2 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (local $exn exnref)
  ;; CHECK-NEXT:  (local $pair (tuple i32 exnref))
  ;; CHECK-NEXT:  (block $catch_all0
  ;; CHECK-NEXT:   (try_table (catch_all $catch_all0)
  ;; CHECK-NEXT:    (local.set $pair
  ;; CHECK-NEXT:     (block $catch1 (type $1) (result i32 exnref)
  ;; CHECK-NEXT:      (try_table (catch_ref $e-i32 $catch1)
  ;; CHECK-NEXT:       (throw $e-i32
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $exn
  ;; CHECK-NEXT:     (tuple.extract 2 1
  ;; CHECK-NEXT:      (local.get $pair)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (throw_ref
  ;; CHECK-NEXT:     (local.get $exn)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-try_table2
    (local $x i32)
    (local $exn exnref)
    (local $pair (tuple i32 exnref))
    (block $catch_all0
      (try_table (catch_all $catch_all0)
        (local.set $pair
          (block $catch1 (result i32 exnref)
            (try_table (catch_ref $e-i32 $catch1)
              (throw $e-i32 (i32.const 0))
            )
          )
        )
        (local.set $exn
          (tuple.extract 2 1 (local.get $pair))
        )
        (local.set $x (i32.const 1))
        (throw_ref (local.get $exn))
      )
    )
    ;; Unlike nested-try_table1, the exception may not be caught by the inner
    ;; catch, so the local.set may not run. So this should NOT be dropped.
    ;; TODO This actually can be removed if we analyze tags in CFGWalker,
    ;; because we throw an i32 and catch an i32 too in the inner try_table. Add
    ;; this to the analysis.
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $nested-try_table3 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (local $exn exnref)
  ;; CHECK-NEXT:  (local $pair (tuple i32 exnref))
  ;; CHECK-NEXT:  (block $catch_all0
  ;; CHECK-NEXT:   (try_table (catch_all $catch_all0)
  ;; CHECK-NEXT:    (block $outer1
  ;; CHECK-NEXT:     (local.set $pair
  ;; CHECK-NEXT:      (block $catch1 (type $1) (result i32 exnref)
  ;; CHECK-NEXT:       (try_table (catch_ref $e-i32 $catch1)
  ;; CHECK-NEXT:        (call $foo)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (br $outer1)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.set $exn
  ;; CHECK-NEXT:      (tuple.extract 2 1
  ;; CHECK-NEXT:       (local.get $pair)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.set $x
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (throw_ref
  ;; CHECK-NEXT:      (local.get $exn)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-try_table3
    (local $x i32)
    (local $exn exnref)
    (local $pair (tuple i32 exnref))
    (block $catch_all0
      (try_table (catch_all $catch_all0)
        (block $outer1
          (local.set $pair
            (block $catch1 (result i32 exnref)
              (try_table (catch_ref $e-i32 $catch1)
                (call $foo)
              )
              (br $outer1)
            )
          )
          (local.set $exn
            (tuple.extract 2 1 (local.get $pair))
          )
          (local.set $x (i32.const 1))
          (throw_ref (local.get $exn))
        )
      )
    )
    ;; Unlike nested-try_table1, the exception may not be caught by the inner
    ;; catch, so the local.set may not run. So this should NOT be dropped.
    ;; Unlike nested-try_table2, In this case we don't know what (call $foo)
    ;; will throw, so we can't drop this even if we analyze tags.
    (local.set $x (i32.const 1))
  )

  ;; CHECK:      (func $catchless-try_table (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (try_table
  ;; CHECK-NEXT:   (call $foo)
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $catchless-try_table
    (local $x i32)
    (try_table
      (call $foo)
      (local.set $x (i32.const 1))
    )
    ;; The only way we end up here is when (call $foo) does not throw, because
    ;; if (call $foo) throws, it will throw to the caller because it is within
    ;; a catchless try_table. In that case the local.set after (call $foo) would
    ;; have run before this, so this can be dropped.
    (local.set $x (i32.const 1))
  )
)
