// RUN: mlir-opt %s --test-walk-pattern-rewrite-driver="dump-notifications=true" \
// RUN:   --allow-unregistered-dialect --split-input-file | FileCheck %s

// The following op is updated in-place and will not be added back to the worklist.
// CHECK-LABEL: func.func @inplace_update()
// CHECK: "test.any_attr_of_i32_str"() <{attr = 1 : i32}> : () -> ()
// CHECK: "test.any_attr_of_i32_str"() <{attr = 2 : i32}> : () -> ()
func.func @inplace_update() {
  "test.any_attr_of_i32_str"() {attr = 0 : i32} : () -> ()
  "test.any_attr_of_i32_str"() {attr = 1 : i32} : () -> ()
  return
}

// Check that the driver does not fold visited ops.
// CHECK-LABEL: func.func @add_no_fold()
// CHECK: arith.constant
// CHECK: arith.constant
// CHECK: %[[RES:.+]] = arith.addi
// CHECK: return %[[RES]]
func.func @add_no_fold() -> i32 {
  %c0 = arith.constant 0 : i32
  %c1 = arith.constant 1 : i32
  %res = arith.addi %c0, %c1 : i32
  return %res : i32
}

// Check that the driver handles rewriter.moveBefore.
// CHECK-LABEL: func.func @move_before(
// CHECK: "test.move_before_parent_op"
// CHECK: "test.any_attr_of_i32_str"() <{attr = 1 : i32}> : () -> ()
// CHECK: scf.if
// CHECK: return
func.func @move_before(%cond : i1) {
  scf.if %cond {
    "test.move_before_parent_op"() ({
      "test.any_attr_of_i32_str"() {attr = 0 : i32} : () -> ()
    }) : () -> ()
  }
  return
}

// Check that the driver handles rewriter.moveAfter. In this case, we expect
// the moved op to be visited twice.
// CHECK-LABEL: func.func @move_after(
// CHECK: scf.if
// CHECK: }
// CHECK: "test.move_after_parent_op"
// CHECK: "test.any_attr_of_i32_str"() <{attr = 2 : i32}> : () -> ()
// CHECK: return
func.func @move_after(%cond : i1) {
  scf.if %cond {
    "test.move_after_parent_op"() ({
      "test.any_attr_of_i32_str"() {attr = 0 : i32} : () -> ()
    }) : () -> ()
  }
  return
}

// Check that the driver handles rewriter.moveAfter. In this case, we expect
// the moved op to be visited twice since we advance its position to the next
// node after the parent.
// CHECK-LABEL: func.func @move_forward_and_revisit(
// CHECK: scf.if
// CHECK: }
// CHECK: arith.addi
// CHECK: "test.move_after_parent_op"
// CHECK: "test.any_attr_of_i32_str"() <{attr = 2 : i32}> : () -> ()
// CHECK: arith.addi
// CHECK: return
func.func @move_forward_and_revisit(%cond : i1) {
  scf.if %cond {
    "test.move_after_parent_op"() ({
      "test.any_attr_of_i32_str"() {attr = 0 : i32} : () -> ()
    }) {advance = 1 : i32} : () -> ()
  }
  %a = arith.addi %cond, %cond : i1
  %b = arith.addi %a, %cond : i1
  return
}

// Operation inserted just after the currently visited one won't be visited.
// CHECK-LABEL: func.func @insert_just_after
// CHECK: "test.clone_me"() ({
// CHECK:   "test.any_attr_of_i32_str"() <{attr = 1 : i32}> : () -> ()
// CHECK: }) {was_cloned} : () -> ()
// CHECK: "test.clone_me"() ({
// CHECK:   "test.any_attr_of_i32_str"() <{attr = 1 : i32}> : () -> ()
// CHECK: }) : () -> ()
// CHECK: return
func.func @insert_just_after(%cond : i1) {
  "test.clone_me"() ({
    "test.any_attr_of_i32_str"() {attr = 0 : i32} : () -> ()
  }) : () -> ()
  return
}

// Check that we can replace the current operation with a new one.
// Note that the new op won't be visited.
// CHECK-LABEL: func.func @replace_with_new_op
// CHECK: %[[NEW:.+]] = "test.new_op"
// CHECK: %[[RES:.+]] = arith.addi %[[NEW]], %[[NEW]]
// CHECK: return %[[RES]]
func.func @replace_with_new_op() -> i32 {
  %a = "test.replace_with_new_op"() : () -> (i32)
  %res = arith.addi %a, %a : i32
  return %res : i32
}

// Check that we can erase nested blocks.
// CHECK-LABEL: func.func @erase_nested_block
// CHECK:         %[[RES:.+]] = "test.erase_first_block"
// CHECK-NEXT:    foo.bar
// CHECK:         return %[[RES]]
func.func @erase_nested_block() -> i32 {
  %a = "test.erase_first_block"() ({
    "foo.foo"() : () -> ()
    ^bb1:
    "foo.bar"() : () -> ()
  }): () -> (i32)
  return %a : i32
}


// CHECK-LABEL: func.func @unreachable_replace_with_new_op
// CHECK: "test.new_op"
// CHECK: "test.replace_with_new_op"
// CHECK-SAME: unreachable
// CHECK: "test.new_op"
func.func @unreachable_replace_with_new_op() {
  "test.br"()[^bb1] : () -> ()
^bb1:
  %a = "test.replace_with_new_op"() : () -> (i32)
  "test.br"()[^end] : () -> () // Test jumping over the unreachable block is visited as well.
^unreachable:
  %b = "test.replace_with_new_op"() {test.unreachable} : () -> (i32)
  return
^end:
  %c = "test.replace_with_new_op"() : () -> (i32)
  return
}

