llvm/mlir/test/Dialect/Transform/expensive-checks.mlir

// RUN: mlir-opt --transform-interpreter --split-input-file --verify-diagnostics %s

// expected-note @below {{ancestor payload op}}
func.func @func() {
  // expected-note @below {{nested payload op}}
  return
}

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%root: !transform.any_op) {
    transform.with_pdl_patterns %root : !transform.any_op {
    ^bb0(%arg0: !transform.any_op):
      pdl.pattern @return : benefit(1) {
        %0 = operands
        %1 = types
        %2 = operation "func.return"(%0 : !pdl.range<value>) -> (%1 : !pdl.range<type>)
        rewrite %2 with "transform.dialect"
      }

      sequence %arg0 : !transform.any_op failures(propagate) {
      ^bb1(%arg1: !transform.any_op):
        // expected-note @below {{handle to invalidated ops}}
        %0 = pdl_match @return in %arg1 : (!transform.any_op) -> !transform.any_op
        %1 = get_parent_op %0 {isolated_from_above} : (!transform.any_op) -> !transform.any_op
        // expected-note @below {{invalidated by this transform op that consumes its operand #0}}
        test_consume_operand %1 : !transform.any_op
        // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
        transform.debug.emit_remark_at %0, "remark" : !transform.any_op
      }
    }
    transform.yield
  }
}

// -----

func.func @func1() {
  // expected-note @below {{repeated target op}}
  return
}
func.func private @func2()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%root: !transform.any_op) {
    transform.with_pdl_patterns %root : !transform.any_op {
    ^bb0(%arg0: !transform.any_op):
      pdl.pattern @func : benefit(1) {
        %0 = operands
        %1 = types
        %2 = operation "func.func"(%0 : !pdl.range<value>) -> (%1 : !pdl.range<type>)
        rewrite %2 with "transform.dialect"
      }
      pdl.pattern @return : benefit(1) {
        %0 = operands
        %1 = types
        %2 = operation "func.return"(%0 : !pdl.range<value>) -> (%1 : !pdl.range<type>)
        rewrite %2 with "transform.dialect"
      }

      sequence %arg0 : !transform.any_op failures(propagate) {
      ^bb1(%arg1: !transform.any_op):
        %0 = pdl_match @func in %arg1 : (!transform.any_op) -> !transform.any_op
        %1 = pdl_match @return in %arg1 : (!transform.any_op) -> !transform.any_op
        %2 = replicate num(%0) %1 : !transform.any_op, !transform.any_op
        // expected-error @below {{a handle passed as operand #0 and consumed by this operation points to a payload entity more than once}}
        test_consume_operand %2 : !transform.any_op
        transform.debug.emit_remark_at %0, "remark" : !transform.any_op
      }
    }
    transform.yield
  }
}


// -----

// expected-note @below {{ancestor payload op}}
// expected-note @below {{nested payload op}}
module attributes {transform.with_named_sequence} {

  transform.named_sequence @__transform_main(%0: !transform.any_op) {
    %1 = transform.test_copy_payload %0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.test_copy_payload %0 : (!transform.any_op) ->!transform.any_op
    // expected-note @below {{invalidated by this transform op that consumes its operand #0}}
    transform.test_consume_operand %1 : !transform.any_op
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %2 : !transform.any_op
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor payload op}}
// expected-note @below {{nested payload op}}
module attributes {transform.with_named_sequence} {

  transform.named_sequence @__transform_main(%0: !transform.any_op) {
    %1 = transform.test_copy_payload %0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.test_copy_payload %0 : (!transform.any_op) -> !transform.any_op
    // Consuming two handles in the same operation is invalid if they point
    // to overlapping sets of payload IR ops.
    //
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities}}
    transform.test_consume_operand %1, %2 : !transform.any_op, !transform.any_op
    transform.yield
  }
}

// -----

// Deduplication attribute allows "merge_handles" to take repeated operands.

module attributes {transform.with_named_sequence} {

  transform.named_sequence @__transform_main(%0: !transform.any_op) {
    %1 = transform.test_copy_payload %0 : (!transform.any_op) -> !transform.any_op
    %2 = transform.test_copy_payload %0 : (!transform.any_op) -> !transform.any_op
    transform.merge_handles %1, %2 { deduplicate } : !transform.any_op
    transform.yield
  }
}
// -----

// expected-note @below {{payload value}}
%0 = "test.match_anchor"() : () -> (i32)

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %2 = transform.structured.match ops{["test.match_anchor"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_result %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated handle}}
    %4 = transform.test_produce_value_handle_to_result %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates handles to the same values as associated with it}}
    transform.test_consume_operand %3 : !transform.any_value
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %4 : !transform.any_value
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor op associated with the consumed handle}}
// expected-note @below {{payload value}}
// expected-note @below {{op defining the value as result #0}}
%0 = "test.match_anchor"() : () -> (i32)

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %2 = transform.structured.match ops{["test.match_anchor"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{invalidated handle}}
    %3 = transform.test_produce_value_handle_to_result %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %2 : !transform.any_op
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %3 : !transform.any_value
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor op associated with the consumed handle}}
"test.match_anchor_1"() ({
^bb0:
  // expected-note @below {{op defining the value as result #0}}
  // expected-note @below {{payload value}}
  %0 = "test.match_anchor_2"() : () -> (i32)
  "test.region_terminator"() : () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{invalidated handle}}
    %3 = transform.test_produce_value_handle_to_result %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %1 : !transform.any_op
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %3 : !transform.any_value
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor op associated with the consumed handle}}
// expected-note @below {{op defining the value as block argument #0 of block #0 in region #0}}
"test.match_anchor_1"() ({
// expected-note @below {{payload value}}
^bb0(%arg0: i32):
  %0 = "test.match_anchor_2"() : () -> (i32)
  "test.region_terminator"() : () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{invalidated handle}}
    %3 = transform.test_produce_value_handle_to_argument_of_parent_block %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %1 : !transform.any_op
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %3 : !transform.any_value
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor op associated with the consumed handle}}
"test.match_anchor_1"() ({
^bb:
  // expected-note @below {{op defining the value as block argument #0 of block #0 in region #0}}
  "test.op_with_regions"() ({
  // expected-note @below {{payload value}}
  ^bb0(%arg0: i32):
    %0 = "test.match_anchor_2"() : () -> (i32)
    "test.region_terminator"() : () -> ()
  }): () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{invalidated handle}}
    %3 = transform.test_produce_value_handle_to_argument_of_parent_block %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %1 : !transform.any_op
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %3 : !transform.any_value
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor payload op}}
// expected-note @below {{nested payload op}}
// expected-note @below {{consumed handle points to this payload value}}
%0 = "test.match_anchor"() : () -> (i32)

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.structured.match ops{["test.match_anchor"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_result %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %3 : !transform.any_value
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %2 : !transform.any_op 
    transform.yield
  }
}

// -----

// expected-note @below {{ancestor payload op}}
// expected-note @below {{consumed handle points to this payload value}}
%0 = "test.match_anchor_1"() ({
^bb0:
  // expected-note @below {{nested payload op}}
  "test.match_anchor_2"() : () -> ()
  "test.region_terminator"() : () -> ()
}) : () -> (i32)

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_result %1, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %3 : !transform.any_value
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %2 : !transform.any_op
    transform.yield
  }
}


// -----

"test.match_anchor_1"() ({
// expected-note @below {{consumed handle points to this payload value}}
^bb0(%arg0: f32):
  // expected-note @below {{ancestor payload op}}
  // expected-note @below {{nested payload op}}
  "test.match_anchor_2"() : () -> ()
  "test.region_terminator"() : () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_argument_of_parent_block %2, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %3 : !transform.any_value
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %2 : !transform.any_op
    transform.yield
  }
}

// -----

"test.op_with_regions"() ({
// expected-note @below {{consumed handle points to this payload value}}
^bb(%arg0: i32):
  // expected-note @below {{ancestor payload op}}
  "test.op_with_regions"() ({
  ^bb0:
    // expected-note @below {{nested payload op}}
    "test.match_anchor_2"() : () -> ()
    "test.region_terminator"() : () -> ()
  }): () -> ()
  "test.match_anchor_1"() : () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    // expected-note @below {{handle to invalidated ops}}
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_argument_of_parent_block %1, 0 : (!transform.any_op) -> !transform.any_value
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %3 : !transform.any_value
    // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %2 : !transform.any_op
    transform.yield
  }
}

// -----

// Removing a block argument does not invalidate handles to operations in another block.
// Not expecting an error here.

"test.op_with_regions"() ({
^bb1(%arg0: i32):
  "test.match_anchor_1"() : () -> ()
^bb2:
  "test.match_anchor_2"() : () -> ()
}) : () -> ()

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %1 = transform.structured.match ops{["test.match_anchor_1"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %2 = transform.structured.match ops{["test.match_anchor_2"]} in %arg0 : (!transform.any_op) -> !transform.any_op
    %3 = transform.test_produce_value_handle_to_argument_of_parent_block %1, 0 : (!transform.any_op) -> !transform.any_value
    transform.test_consume_operand %3 : !transform.any_value
    transform.test_consume_operand %2 : !transform.any_op
    transform.yield
  }
}

// -----

module attributes {transform.with_named_sequence} {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %0 = transform.test_produce_empty_payload : !transform.any_op
    // expected-note @below {{invalidated by this transform op that consumes its operand #0}}
    transform.test_consume_operand %0 : !transform.any_op
    // expected-error @below {{uses a handle associated with empty payload and invalidated by a previously executed transform op}}
    transform.debug.emit_remark_at %0, "remark" : !transform.any_op
    transform.yield
  }
}

// -----

// Make sure we properly report a use-after-consume error when repeated handles
// are allowed in the consuming op. We still want to report handles consumed by
// _previous_ operations, just not by this one. To bypass the quick static check
// of repeated consumption, create a handle to the transform operation and
// invalidate the handle to the root module thus invalidating all other handles.

// expected-note @below {{ancestor payload op}}
module attributes {transform.with_named_sequence}  {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    // expected-note @below {{handle to invalidated ops}}
    // expected-note @below {{nested payload op}}
    %0 = transform.test_produce_self_handle_or_forward_operand : () -> !transform.any_op
    // expected-note @below {{invalidated by this transform op that consumes its operand #0 and invalidates all handles to payload IR entities associated with this operand and entities nested in them}}
    transform.test_consume_operand %arg0 : !transform.any_op
    // expected-error @below {{uses a handle invalidated by a previously executed transform op}}
    transform.test_consume_operand %0 { allow_repeated_handles } : !transform.any_op
    transform.yield
  }
}

// -----

// Re-entering the region should not trigger the consumption error from previous
// execution of the region.

module attributes {transform.with_named_sequence}  {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    transform.test_re_enter_region {
      %0 = transform.test_produce_self_handle_or_forward_operand : () -> !transform.any_op
      transform.test_consume_operand %0 : !transform.any_op
      transform.yield
    }
    transform.yield
  }
}

// -----

// Re-entering the region should not trigger the consumption error from previous
// execution of the region.

module attributes {transform.with_named_sequence}  {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    %0 = transform.test_produce_self_handle_or_forward_operand : () -> !transform.any_op
    transform.test_re_enter_region %0 : !transform.any_op {
    ^bb0(%arg1: !transform.any_op):
      transform.test_consume_operand %arg1 : !transform.any_op
      transform.yield
    }
    transform.yield
  }
}

// -----

// Consuming the same handle repeatedly in the region should trigger an error.
module attributes {transform.with_named_sequence}  {
  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    // expected-note @below {{payload op}}
    // expected-note @below {{handle to invalidated ops}}
    %0 = transform.test_produce_self_handle_or_forward_operand : () -> !transform.any_op
    transform.test_re_enter_region {
      // expected-error @below {{op uses a handle invalidated by a previously executed transform op}}
      // expected-note @below {{invalidated by this transform op}}
      transform.test_consume_operand %0 : !transform.any_op
      transform.yield
    }
    transform.yield
  }
}

// -----

module @named_inclusion_and_consumption attributes { transform.with_named_sequence } {

  transform.named_sequence @foo(%arg0: !transform.any_op {transform.consumed}) -> () {
    // Consuming this handle removes the mapping from the current stack frame
    // mapping and from the caller's stack frame mapping. (If this were not
    // be the case, the "expensive checks" caching mechanism for op names
    // would throw an error saying that an op is mapped but not in the cache.)
    transform.test_consume_operand %arg0 : !transform.any_op
    transform.yield
  }

  transform.named_sequence @__transform_main(%arg0: !transform.any_op) {
    transform.include @foo failures(propagate) (%arg0) : (!transform.any_op) -> ()
    transform.yield
  }
}