xref: /llvm-project/mlir/docs/Tutorials/transform/Ch3.md (revision b33b91a21788d439f49d6db4e7224c20f740f1a7)
168ae0d78SAlex Zinenko# Chapter 3: More than Simple Transform Operations
268ae0d78SAlex Zinenko
368ae0d78SAlex Zinenko## Type Constraints and ApplyEach Trait
468ae0d78SAlex Zinenko
5b7d462a7SIngo MüllerA transform operation that applies to each payload operation individually and requires it to be of a specific kind is a repeated pattern. One can use Transform dialect types to specify the preconditions of the type. Specifically, we can change the expected operand type from the wide `TransformHandleTypeInterface` to the more narrow `Transform_ConcreteOp<"func.call">`. Furthermore, we use the `TransformEachOpTrait` trait to provide the skeleton implementation of the `apply` method that performs verification, iteration over payloads and result concatenation. The improved ODS op definition is as follows.
668ae0d78SAlex Zinenko
768ae0d78SAlex Zinenko```tablegen
868ae0d78SAlex Zinenko// In MyExtension.td.
968ae0d78SAlex Zinenko
1068ae0d78SAlex Zinenko// Define the new operation. By convention, prefix its name with the name of the dialect extension, "my.". The full operation name will be further prefixed with "transform.".
1168ae0d78SAlex Zinenkodef ChangeCallTargetOp : Op<Transform_Dialect, "my.change_call_target",
1268ae0d78SAlex Zinenko    // Indicate that the operation implements the required TransformOpInterface and
1368ae0d78SAlex Zinenko    // MemoryEffectsOpInterface. Use the TransformEach trait to provide the
1468ae0d78SAlex Zinenko    // implementation for TransformOpInterface.
1568ae0d78SAlex Zinenko    [TransformOpInterface, TransformEachOpTrait,
1668ae0d78SAlex Zinenko     DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
1768ae0d78SAlex Zinenko  // Provide a brief and a full description. It is recommended that the latter describes
1868ae0d78SAlex Zinenko  // the effects on the operands and how the operation processes various failure modes.
1968ae0d78SAlex Zinenko  let summary = "Changes the callee of a call operation to the specified one";
2068ae0d78SAlex Zinenko  let description = [{
2168ae0d78SAlex Zinenko    For each `func.call` payload operation associated with the handle, changes its
2268ae0d78SAlex Zinenko    callee to be the symbol whose name is provided as an attribute to this operation.
2368ae0d78SAlex Zinenko
2468ae0d78SAlex Zinenko    Generates a silenceable failure if the operand is associated with payload operations
2568ae0d78SAlex Zinenko    that are not `func.call`.
2668ae0d78SAlex Zinenko    Only reads the operand.
2768ae0d78SAlex Zinenko  }];
2868ae0d78SAlex Zinenko
2968ae0d78SAlex Zinenko  // The arguments include the handle to the payload operations and the attribute that
3068ae0d78SAlex Zinenko  // specifies the new callee. The handle must implement TransformHandleTypeInterface.
3168ae0d78SAlex Zinenko  // We use a string attribute as the symbol may not exist in the transform IR so the
3268ae0d78SAlex Zinenko  // verification may fail.
3368ae0d78SAlex Zinenko  let arguments = (ins
3468ae0d78SAlex Zinenko    Transform_ConcreteOpType<"func.call">:$call,
3568ae0d78SAlex Zinenko    StrAttr:$new_target);
3668ae0d78SAlex Zinenko
3768ae0d78SAlex Zinenko  // The results are empty as the transformation does not produce any new payload.
3868ae0d78SAlex Zinenko  let results = (outs);
3968ae0d78SAlex Zinenko
4068ae0d78SAlex Zinenko  // Provide nice syntax.
4168ae0d78SAlex Zinenko  let assemblyFormat = "$call `,` $new_target attr-dict `:` type($call)";
4268ae0d78SAlex Zinenko
4368ae0d78SAlex Zinenko  // Declare the function implementing the interface for a single payload operation.
4468ae0d78SAlex Zinenko  let extraClassDeclaration = [{
4568ae0d78SAlex Zinenko    ::mlir::DiagnosedSilenceableFailure applyToOne(
46c63d2b2cSMatthias Springer        ::mlir::transform::TransformRewriter &rewriter,
4768ae0d78SAlex Zinenko        ::mlir::func::CallOp call,
4868ae0d78SAlex Zinenko        ::mlir::transform::ApplyToEachResultList &results,
4968ae0d78SAlex Zinenko        ::mlir::transform::TransformState &state);
5068ae0d78SAlex Zinenko  }];
5168ae0d78SAlex Zinenko}
5268ae0d78SAlex Zinenko```
5368ae0d78SAlex Zinenko
5468ae0d78SAlex ZinenkoNow, instead of defining the `apply` method with a loop, we can simply define a function that applies to an individual payload operation and the trait will take care of the rest.
5568ae0d78SAlex Zinenko
5668ae0d78SAlex Zinenko```c++
5768ae0d78SAlex Zinenko::mlir::DiagnosedSilenceableFailure ChangeCallTargetOp::applyToOne(
58c63d2b2cSMatthias Springer    ::mlir::transform::TransformRewriter &rewriter,
59c63d2b2cSMatthias Springer    ::mlir::func::CallOp call,
6068ae0d78SAlex Zinenko    ::mlir::transform::ApplyToEachResultList &results,
6168ae0d78SAlex Zinenko    ::mlir::transform::TransformState &state) {
6268ae0d78SAlex Zinenko  // Call the actual transformation function.
6368ae0d78SAlex Zinenko  updateCallee(call, getNewTarget());
6468ae0d78SAlex Zinenko  // Indicate success.
6568ae0d78SAlex Zinenko  return DiagnosedSilenceableFailure::success();
6668ae0d78SAlex Zinenko}
6768ae0d78SAlex Zinenko```
6868ae0d78SAlex Zinenko
6968ae0d78SAlex Zinenko## Defining a Transform Type
7068ae0d78SAlex Zinenko
7168ae0d78SAlex ZinenkoIn addition to operations, the Transform dialect allows extensions to define and inject additional attributes and types. As we have seen above, transform types are used to specify constraints on the payload operations. Our call rewriting operation currently applies only to `func.call`. We may want to generalize it to apply to any payload operation that implements `CallOpInterface`, but the Transform dialect currently doesn’t provide a type that checks if a payload operation implements this interface. Let’s define it in our extension.
7268ae0d78SAlex Zinenko
7368ae0d78SAlex ZinenkoType definition is again identical to defining a dialect type with ODS.
7468ae0d78SAlex Zinenko
7568ae0d78SAlex Zinenko```tablegen
7668ae0d78SAlex Zinenko// Transform dialect allows additional types to be defined and injected.
7768ae0d78SAlex Zinenkodef CallOpInterfaceHandle
7868ae0d78SAlex Zinenko  : TypeDef<Transform_Dialect, "CallOpInterfaceHandle",
7968ae0d78SAlex Zinenko      // The type must implement `TransformHandleTypeInterface`.
8068ae0d78SAlex Zinenko      [DeclareTypeInterfaceMethods<TransformHandleTypeInterface>]> {
8168ae0d78SAlex Zinenko
8268ae0d78SAlex Zinenko  // The usual components of a type such as description, mnemonic and assembly format
8368ae0d78SAlex Zinenko  // should be provided.
8468ae0d78SAlex Zinenko  let summary = "handle to payload operations implementing CallOpInterface";
8568ae0d78SAlex Zinenko  let mnemonic = "my.call_op_interface";
8668ae0d78SAlex Zinenko  let assemblyFormat = "";
8768ae0d78SAlex Zinenko}
8868ae0d78SAlex Zinenko```
8968ae0d78SAlex Zinenko
9068ae0d78SAlex ZinenkoWe will omit the generation of declaration and definitions using Tablegen for brevity as it is identical to the regular case.
9168ae0d78SAlex Zinenko
9268ae0d78SAlex ZinenkoTo finalize the definition of a transform type, one must implement the interface methods.
9368ae0d78SAlex Zinenko
9468ae0d78SAlex Zinenko```c++
9568ae0d78SAlex Zinenko// In MyExtension.cpp.
9668ae0d78SAlex Zinenko
9768ae0d78SAlex Zinenko// The interface declares this method to verify constraints this type has on
9868ae0d78SAlex Zinenko// payload operations. It returns the now familiar tri-state result.
9968ae0d78SAlex Zinenkomlir::DiagnosedSilenceableFailure
10068ae0d78SAlex Zinenkomlir::transform::CallOpInterfaceHandleType::checkPayload(
10168ae0d78SAlex Zinenko    // Location at which diagnostics should be emitted.
10268ae0d78SAlex Zinenko    mlir::Location loc,
10368ae0d78SAlex Zinenko    // List of payload operations that are about to be associated with the
10468ae0d78SAlex Zinenko    // handle that has this type.
10568ae0d78SAlex Zinenko    llvm::ArrayRef<mlir::Operation *> payload) const {
10668ae0d78SAlex Zinenko
10768ae0d78SAlex Zinenko  // All payload operations are expected to implement CallOpInterface, check this.
10868ae0d78SAlex Zinenko  for (Operation *op : payload) {
10968ae0d78SAlex Zinenko    if (llvm::isa<mlir::CallOpInterface>(op))
11068ae0d78SAlex Zinenko      continue;
11168ae0d78SAlex Zinenko
11268ae0d78SAlex Zinenko    // By convention, these verifiers always emit a silenceable failure since they are
11368ae0d78SAlex Zinenko    // checking a precondition.
11468ae0d78SAlex Zinenko    DiagnosedSilenceableFailure diag = emitSilenceableError(loc)
11568ae0d78SAlex Zinenko        << "expected the payload operation to implement CallOpInterface";
11668ae0d78SAlex Zinenko    diag.attachNote(op->getLoc()) << "offending operation";
11768ae0d78SAlex Zinenko    return diag;
11868ae0d78SAlex Zinenko  }
11968ae0d78SAlex Zinenko
12068ae0d78SAlex Zinenko  // If everything is okay, return success.
12168ae0d78SAlex Zinenko  return DiagnosedSilenceableFailure::success();
12268ae0d78SAlex Zinenko}
12368ae0d78SAlex Zinenko
12468ae0d78SAlex Zinenko```
12568ae0d78SAlex Zinenko
12668ae0d78SAlex ZinenkoAdditional attributes and types need to be registered in the extension, next to operations.
12768ae0d78SAlex Zinenko
12868ae0d78SAlex Zinenko```c++
12968ae0d78SAlex Zinenko// In MyExtension.cpp.
13068ae0d78SAlex Zinenko
13168ae0d78SAlex Zinenkovoid MyExtension::init() {
132*b33b91a2SOleksandr "Alex" Zinenko  // ...
13368ae0d78SAlex Zinenko
13468ae0d78SAlex Zinenko  registerTypes<
13568ae0d78SAlex Zinenko#define GET_TYPEDEF_LIST
13668ae0d78SAlex Zinenko#include "MyExtensionTypes.cpp.inc"
13768ae0d78SAlex Zinenko  >();
13868ae0d78SAlex Zinenko}
13968ae0d78SAlex Zinenko```
14068ae0d78SAlex Zinenko
14139298b09SAndrzej WarzyńskiThis type is now directly available in the Transform dialect and can be used in operations.
14268ae0d78SAlex Zinenko
14368ae0d78SAlex Zinenko
14468ae0d78SAlex Zinenko```mlir
14568ae0d78SAlex Zinenko  // Cast to our new type.
14668ae0d78SAlex Zinenko  %casted = transform.cast %call : !transform.any_op to !transform.my.call_op_interface
14768ae0d78SAlex Zinenko  // Using our new operation.
14868ae0d78SAlex Zinenko  transform.my.change_call_target %casted, "microkernel" : !transform.my.call_op_interface
14968ae0d78SAlex Zinenko```
15068ae0d78SAlex Zinenko
15168ae0d78SAlex Zinenko## Operand Consumption
15268ae0d78SAlex Zinenko
15368ae0d78SAlex ZinenkoAs an exercise, let us modify the rewriting operation to consume the operand. This would be necessary, for example, if the transformation were to rewrite the `func.call` operation into a custom operation `my.mm4`. Since the operand handle is now consumed, the operation can return a new handle to the newly produced payload operation. Otherwise, the ODS definition of the transform operation remains unchanged.
15468ae0d78SAlex Zinenko
15568ae0d78SAlex Zinenko```tablegen
15668ae0d78SAlex Zinenko// In MyExtension.td.
15768ae0d78SAlex Zinenko
15868ae0d78SAlex Zinenko// Define another transform operation.
15968ae0d78SAlex Zinenkodef CallToOp : Op<Transform_Dialect, "my.call_to_op",
16068ae0d78SAlex Zinenko     // Indicate that the operation implements the required TransformOpInterface and
16168ae0d78SAlex Zinenko     // MemoryEffectsOpInterface. Use the TransformEach trait to provide the
16268ae0d78SAlex Zinenko     // implementation for TransformOpInterface.
16368ae0d78SAlex Zinenko    [TransformOpInterface, TransformEachOpTrait,
16468ae0d78SAlex Zinenko     DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
16568ae0d78SAlex Zinenko  // Summary and description omitted for brevity.
16668ae0d78SAlex Zinenko
16768ae0d78SAlex Zinenko  // The argument is the handle to the payload operations.
16868ae0d78SAlex Zinenko  let arguments = (ins CallOpInterfaceHandle:$call);
16968ae0d78SAlex Zinenko
17068ae0d78SAlex Zinenko  // The result is the handle to the payload operations produced during the
17168ae0d78SAlex Zinenko  // transformation.
17268ae0d78SAlex Zinenko  let results = (outs TransformHandleTypeInterface:$transformed);
17368ae0d78SAlex Zinenko
17468ae0d78SAlex Zinenko  // Provide nice syntax.
17568ae0d78SAlex Zinenko  let assemblyFormat = "$call attr-dict `:` functional-type(inputs, outputs)";
17668ae0d78SAlex Zinenko
17768ae0d78SAlex Zinenko  // Declare the function implementing the interface for a single payload operation.
17868ae0d78SAlex Zinenko  let extraClassDeclaration = [{
17968ae0d78SAlex Zinenko    ::mlir::DiagnosedSilenceableFailure applyToOne(
180c63d2b2cSMatthias Springer        ::mlir::transform::TransformRewriter &rewriter,
18168ae0d78SAlex Zinenko        ::mlir::CallOpInterface call,
18268ae0d78SAlex Zinenko        ::mlir::transform::ApplyToEachResultList &results,
18368ae0d78SAlex Zinenko        ::mlir::transform::TransformState &state);
18468ae0d78SAlex Zinenko  }];
18568ae0d78SAlex Zinenko}
18668ae0d78SAlex Zinenko```
18768ae0d78SAlex Zinenko
18868ae0d78SAlex ZinenkoNow let’s look at the implementation of interface methods.
18968ae0d78SAlex Zinenko
19068ae0d78SAlex Zinenko```c++
19168ae0d78SAlex Zinenko// In MyExtension.cpp.
19268ae0d78SAlex Zinenko
19368ae0d78SAlex Zinenko::mlir::DiagnosedSilenceableFailure CallToOp::applyToOne(
194c63d2b2cSMatthias Springer    ::mlir::transform::TransformRewriter &rewriter,
19568ae0d78SAlex Zinenko    ::mlir::CallOpInterface call,
19668ae0d78SAlex Zinenko    ::mlir::transform::ApplyToEachResultList &results,
19768ae0d78SAlex Zinenko    ::mlir::transform::TransformState &state) {
19868ae0d78SAlex Zinenko  // Call the actual rewrite.
19968ae0d78SAlex Zinenko  Operation *rewritten = rewriteToOp(call);
20068ae0d78SAlex Zinenko
20168ae0d78SAlex Zinenko  // Report an error if the rewriter produced a null pointer. Note that it may have
20268ae0d78SAlex Zinenko  // irreversibly modified the payload IR, so we produce a definite failure.
20368ae0d78SAlex Zinenko  if (!rewritten) {
20468ae0d78SAlex Zinenko    return emitDefiniteError() << "failed to rewrite call to operation";
20568ae0d78SAlex Zinenko  }
20668ae0d78SAlex Zinenko
20768ae0d78SAlex Zinenko  // On success, push the resulting operation into the result list. The list is expected
20868ae0d78SAlex Zinenko  // to contain exactly one entity per result and per application. The handles will be
20968ae0d78SAlex Zinenko  // associated with lists of the respective values produced by each application.
21068ae0d78SAlex Zinenko  results.push_back(rewritten);
21168ae0d78SAlex Zinenko
21268ae0d78SAlex Zinenko  // If everything is fine, return success.
21368ae0d78SAlex Zinenko  return DiagnosedSilenceableFailure::success();
21468ae0d78SAlex Zinenko}
21568ae0d78SAlex Zinenko
21668ae0d78SAlex Zinenkovoid CallToOp::getEffects(
21768ae0d78SAlex Zinenko    ::llvm::SmallVectorImpl<::mlir::MemoryEffects::EffectInstance> &effects) {
21868ae0d78SAlex Zinenko  // Indicate using side effects that the operand handle is consumed, and the
21968ae0d78SAlex Zinenko  // result handle is produced.
22068ae0d78SAlex Zinenko  consumesHandle(getCall(), effects);
22168ae0d78SAlex Zinenko  producesHandle(getTransformed(), effects);
22268ae0d78SAlex Zinenko
22368ae0d78SAlex Zinenko  // Indicate that the payload IR is modified.
22468ae0d78SAlex Zinenko  modifiesPayload(effects);
22568ae0d78SAlex Zinenko}
22668ae0d78SAlex Zinenko```
22768ae0d78SAlex Zinenko
22868ae0d78SAlex ZinenkoThe overall flow of these implementations is similar to the previous one. The application also needs to specify the resulting entities that are going to be associated with the handles it produces. Operations are required to specify the entities to associate with _all_ results on success, even if the list is empty. An assertion will be triggered if it is not the case. In case of failure, the interpreter will automatically associate all results that are not yet defined with empty lists.
22968ae0d78SAlex Zinenko
23068ae0d78SAlex ZinenkoNote that since `applyToOne` always expects one payload entity to be associated with each result handle in each application, it cannot be used to return handles associated with empty lists for non-empty operand handles. Instead, one would use `apply` directly.
23168ae0d78SAlex Zinenko
23268ae0d78SAlex Zinenko```c++
23368ae0d78SAlex Zinenko::mlir::DiagnosedSilenceableFailure SomeOtherOp::apply(
234c63d2b2cSMatthias Springer    ::mlir::transform::TransformRewriter &rewriter,
23568ae0d78SAlex Zinenko    ::mlir::transform::TransformResults &results,
23668ae0d78SAlex Zinenko    ::mlir::transform::TransformState &state) {
23768ae0d78SAlex Zinenko  // ...
23868ae0d78SAlex Zinenko
23968ae0d78SAlex Zinenko  // Associate the result `transformed` with an empty list of payload operations.
24068ae0d78SAlex Zinenko  results.set(cast<OpResult>(getTransformed()), {});
24168ae0d78SAlex Zinenko  return DiagnosedSilenceableFailure::success();
24268ae0d78SAlex Zinenko}
24368ae0d78SAlex Zinenko```
24468ae0d78SAlex Zinenko
24568ae0d78SAlex Zinenko## Memory Effects Traits
24668ae0d78SAlex Zinenko
24768ae0d78SAlex ZinenkoSome common memory effect patterns are also available as traits to minimize the boilerplate.
24868ae0d78SAlex Zinenko
24968ae0d78SAlex Zinenko*   `FunctionalStyleTransformOpTrait` indicates that all handle-typed operands are consumed, all results are produced, and the payload IR is modified.
25068ae0d78SAlex Zinenko*   `NavigationTransformOpTrait` indicates that all handle-typed operands are only read, all results are produced, and the payload IR is only read.
25168ae0d78SAlex Zinenko
25268ae0d78SAlex ZinenkoUsing these traits removes the need to declare or define the methods of the `MemoryEffectsOpInterface`.
25368ae0d78SAlex Zinenko
25468ae0d78SAlex Zinenko```tablegen
25568ae0d78SAlex Zinenko// In MyExtension.td.
25668ae0d78SAlex Zinenko
25768ae0d78SAlex Zinenko// Define another transform operation.
25868ae0d78SAlex Zinenkodef CallToOp : Op<Transform_Dialect, "my.call_to_op",
25968ae0d78SAlex Zinenko     // Indicate that the operation implements the required TransformOpInterface.
26068ae0d78SAlex Zinenko     // Use the TransformEach trait to provide implementation of this interface.
26168ae0d78SAlex Zinenko    [TransformOpInterface, TransformEachOpTrait,
26268ae0d78SAlex Zinenko     // Indicate that the operation implements the required MemoryEffectsOpInterface.
26368ae0d78SAlex Zinenko     // Use the FunctionalStyle trait to provide the implementation for this interface.
26468ae0d78SAlex Zinenko     MemoryEffectsOpInterface, FunctionalStyleTransformOpTrait]> {
26568ae0d78SAlex Zinenko  // Summary and description omitted for brevity.
26668ae0d78SAlex Zinenko
26768ae0d78SAlex Zinenko  // The argument is the handle to the payload operations.
26868ae0d78SAlex Zinenko  let arguments = (ins CallOpInterfaceHandle:$call);
26968ae0d78SAlex Zinenko
27068ae0d78SAlex Zinenko  // The result is the handle to the payload operations produced during the
27168ae0d78SAlex Zinenko  // transformation.
27268ae0d78SAlex Zinenko  let results = (outs TransformHandleTypeInterface:$transformed);
27368ae0d78SAlex Zinenko
27468ae0d78SAlex Zinenko  // Provide nice syntax.
27568ae0d78SAlex Zinenko  let assemblyFormat = "$call attr-dict `:` functional-type(operands, results)";
27668ae0d78SAlex Zinenko
27768ae0d78SAlex Zinenko  // Declare the function implementing the interface for a single payload operation.
27868ae0d78SAlex Zinenko  let extraClassDeclaration = [{
27968ae0d78SAlex Zinenko    ::mlir::DiagnosedSilenceableFailure applyToOne(
280c63d2b2cSMatthias Springer        ::mlir::transform::TransformRewriter &rewriter,
28168ae0d78SAlex Zinenko        ::mlir::CallOpInterface call,
28268ae0d78SAlex Zinenko        ::mlir::transform::ApplyToEachResultList &results,
28368ae0d78SAlex Zinenko        ::mlir::transform::TransformState &state);
28468ae0d78SAlex Zinenko  }];
28568ae0d78SAlex Zinenko}
28668ae0d78SAlex Zinenko```
28768ae0d78SAlex Zinenko
288d8c18e42SAlex Zinenko## Appendix: Autogenerated Documentation
289d8c18e42SAlex Zinenko
2904921ff5bSKohei Yamaguchi[include "Tutorials/transform/MyExtensionCh3.md"]
29168ae0d78SAlex Zinenko
292