168ae0d78SAlex Zinenko# Chapter 2: Adding a Simple New Transformation Operation 268ae0d78SAlex Zinenko 368ae0d78SAlex Zinenko## Setting Up to Add New Transformations 468ae0d78SAlex Zinenko 568ae0d78SAlex ZinenkoBefore defining a new transform operation, we need to choose where its implementation should be located. While MLIR encourages upstream contributions, it is not always possible or even desirable to modify the main Transform dialect, for example, if the transformation is specific to some out-of-tree dialect that is not itself available upstream. 668ae0d78SAlex Zinenko 768ae0d78SAlex ZinenkoThe Transform dialect uses the dialect extension mechanism to allow additional operations to be injected without modifying the dialect itself. Dialect extensions are registered with the context and loaded when the dialect itself is loaded. Extension definition is straightforward: 868ae0d78SAlex Zinenko 968ae0d78SAlex Zinenko```cpp 1068ae0d78SAlex Zinenko// In MyExtension.cpp. 1168ae0d78SAlex Zinenko#include "mlir/Dialect/Transform/IR/TransformDialect.h" 1268ae0d78SAlex Zinenko 13b33b91a2SOleksandr "Alex" Zinenko// Define a new Transform dialect extension. This uses the CRTP idiom to 14b33b91a2SOleksandr "Alex" Zinenko// identify extensions. 1568ae0d78SAlex Zinenkoclass MyExtension : public ::mlir::transform::TransformDialectExtension<MyExtension> { 1668ae0d78SAlex Zinenkopublic: 1768ae0d78SAlex Zinenko // The extension must derive the base constructor. 1868ae0d78SAlex Zinenko using Base::Base; 1968ae0d78SAlex Zinenko 20b33b91a2SOleksandr "Alex" Zinenko // This function initializes the extension, similarly to `initialize` in 21b33b91a2SOleksandr "Alex" Zinenko // dialect definitions. List individual operations and dependent dialects 22b33b91a2SOleksandr "Alex" Zinenko // here. 2368ae0d78SAlex Zinenko void init(); 2468ae0d78SAlex Zinenko}; 2568ae0d78SAlex Zinenko 2668ae0d78SAlex Zinenkovoid MyExtension::init() { 27b33b91a2SOleksandr "Alex" Zinenko // Similarly to dialects, an extension can declare a dependent dialect. This 28b33b91a2SOleksandr "Alex" Zinenko // dialect will be loaded along with the extension and, therefore, along with 29b33b91a2SOleksandr "Alex" Zinenko // the Transform dialect. Only declare as dependent the dialects that contain 30b33b91a2SOleksandr "Alex" Zinenko // the attributes or types used by transform operations. Do NOT declare as 31b33b91a2SOleksandr "Alex" Zinenko // dependent the dialects produced during the transformation. 32b33b91a2SOleksandr "Alex" Zinenko // 3368ae0d78SAlex Zinenko // declareDependentDialect<MyDialect>(); 3468ae0d78SAlex Zinenko 35b33b91a2SOleksandr "Alex" Zinenko // When transformations are applied, they may produce new operations from 36b33b91a2SOleksandr "Alex" Zinenko // previously unloaded dialects. Typically, a pass would need to declare 37b33b91a2SOleksandr "Alex" Zinenko // itself dependent on the dialects containing such new operations. To avoid 38b33b91a2SOleksandr "Alex" Zinenko // confusion with the dialects the extension itself depends on, the Transform 39b33b91a2SOleksandr "Alex" Zinenko // dialects differentiates between: 4068ae0d78SAlex Zinenko // - dependent dialects, which are used by the transform operations, and 4168ae0d78SAlex Zinenko // - generated dialects, which contain the entities (attributes, operations, 42b33b91a2SOleksandr "Alex" Zinenko // types) that may be produced by applying the transformation even when 43b33b91a2SOleksandr "Alex" Zinenko // not present in the original payload IR. 44b33b91a2SOleksandr "Alex" Zinenko // In the following chapter, we will be add operations that generate function 45b33b91a2SOleksandr "Alex" Zinenko // calls and structured control flow operations, so let's declare the 46b33b91a2SOleksandr "Alex" Zinenko // corresponding dialects as generated. 4768ae0d78SAlex Zinenko declareGeneratedDialect<::mlir::scf::SCFDialect>(); 4868ae0d78SAlex Zinenko declareGeneratedDialect<::mlir::func::FuncDialect>(); 4968ae0d78SAlex Zinenko 5068ae0d78SAlex Zinenko // Finally, we register the additional transform operations with the dialect. 5168ae0d78SAlex Zinenko registerTransformOps< 5268ae0d78SAlex Zinenko // TODO: list the operation classes. 5368ae0d78SAlex Zinenko >(); 5468ae0d78SAlex Zinenko} 5568ae0d78SAlex Zinenko``` 5668ae0d78SAlex Zinenko 5768ae0d78SAlex ZinenkoThe operations themselves can be defined using ODS, exactly in the same way as regular operations in a dialect. 5868ae0d78SAlex Zinenko 5968ae0d78SAlex Zinenko```tablegen 6068ae0d78SAlex Zinenko// In MyExtension.td 6168ae0d78SAlex Zinenko#ifndef MY_EXTENSION 6268ae0d78SAlex Zinenko#define MY_EXTENSION 6368ae0d78SAlex Zinenko 6468ae0d78SAlex Zinenkoinclude "mlir/Dialect/Transform/IR/TransformDialect.td" 65*5a9bdd85SOleksandr "Alex" Zinenkoinclude "mlir/Dialect/Transform/Interfaces/TransformInterfaces.td" 6668ae0d78SAlex Zinenkoinclude "mlir/IR/OpBase.td" 6768ae0d78SAlex Zinenkoinclude "mlir/Interfaces/SideEffectInterfaces.td" 6868ae0d78SAlex Zinenko 6968ae0d78SAlex Zinenkodef MyOp : Op<Transform_Dialect, "transform.my.op", [ 7068ae0d78SAlex Zinenko // TODO: interfaces and traits here. 7168ae0d78SAlex Zinenko ]> { 7268ae0d78SAlex Zinenko let summary = "my transform op"; 7368ae0d78SAlex Zinenko // TODO: define the operation properties. 7468ae0d78SAlex Zinenko} 7568ae0d78SAlex Zinenko 7668ae0d78SAlex Zinenko#endif // MY_EXTENSION 7768ae0d78SAlex Zinenko``` 7868ae0d78SAlex Zinenko 7968ae0d78SAlex ZinenkoSimilarly to dialects, we must use Tablegen to generate the header and implementation of these operations. We can instruct CMake to do it as follows. 8068ae0d78SAlex Zinenko 8168ae0d78SAlex Zinenko 8268ae0d78SAlex Zinenko```sh 8368ae0d78SAlex Zinenko# In CMakeLists.txt next to MyExtension.td. 8468ae0d78SAlex Zinenko 8568ae0d78SAlex Zinenko# Tell Tablegen to use MyExtension.td as input. 8668ae0d78SAlex Zinenkoset(LLVM_TARGET_DEFINITIONS MyExtension.td) 8768ae0d78SAlex Zinenko 8868ae0d78SAlex Zinenko# Ask Tablegen to generate op declarations and definitions from ODS. 8968ae0d78SAlex Zinenkomlir_tablegen(MyExtension.h.inc -gen-op-decls) 9068ae0d78SAlex Zinenkomlir_tablegen(MyExtension.cpp.inc -gen-op-defs) 9168ae0d78SAlex Zinenko 9268ae0d78SAlex Zinenko# Add a CMakeTarget we can depend on to ensure the generation happens before the compilation. 9368ae0d78SAlex Zinenkoadd_public_tablegen_target(MyExtensionIncGen) 9468ae0d78SAlex Zinenko 9568ae0d78SAlex Zinenko# Don't forget to generate the documentation, this will produce a MyExtension.md under 9668ae0d78SAlex Zinenko# Dialects. 9768ae0d78SAlex Zinenkoadd_mlir_doc(MyExtension MyExtension Dialects/ -gen-op-doc) 9868ae0d78SAlex Zinenko``` 9968ae0d78SAlex Zinenko 10068ae0d78SAlex Zinenko```sh 10168ae0d78SAlex Zinenko# In CMakeLists.txt next to MyExtension.cpp 10268ae0d78SAlex Zinenkoadd_mlir_library( 10368ae0d78SAlex Zinenko # Library called MyExtension. 10468ae0d78SAlex Zinenko MyExtension 10568ae0d78SAlex Zinenko 10668ae0d78SAlex Zinenko # Built from the following source files. 10768ae0d78SAlex Zinenko MyExtension.cpp 10868ae0d78SAlex Zinenko 109b33b91a2SOleksandr "Alex" Zinenko # Make sure ODS declaration and definitions are generated before compiling 110b33b91a2SOleksandr "Alex" Zinenko # this. 11168ae0d78SAlex Zinenko DEPENDS 11268ae0d78SAlex Zinenko MyExtensionIncGen 11368ae0d78SAlex Zinenko 11468ae0d78SAlex Zinenko # Link in the transform dialect, and all generated dialects. 11568ae0d78SAlex Zinenko LINK_LIBS PUBLIC 11668ae0d78SAlex Zinenko MLIRTransformDialect 11768ae0d78SAlex Zinenko MLIRFuncDialect 11868ae0d78SAlex Zinenko MLIRSCFDialect 11968ae0d78SAlex Zinenko) 12068ae0d78SAlex Zinenko``` 12168ae0d78SAlex Zinenko 12268ae0d78SAlex ZinenkoThis will generate two files, `MyExtension.h.inc` and `MyExtension.cpp.inc`, that are supposed to be included into the declaration and definition of the transform operations, respectively. 12368ae0d78SAlex Zinenko 12468ae0d78SAlex Zinenko```c++ 12568ae0d78SAlex Zinenko// In MyExtension.h. 12668ae0d78SAlex Zinenko#include "mlir/Dialect/Transform/IR/TransformDialect.h" 127*5a9bdd85SOleksandr "Alex" Zinenko#include "mlir/Dialect/Transform/Interfaces/TransformInterfaces.h" 12868ae0d78SAlex Zinenko 12968ae0d78SAlex Zinenko#define GET_OP_CLASSES 13068ae0d78SAlex Zinenko#include "MyExtension.h.inc" 13168ae0d78SAlex Zinenko``` 13268ae0d78SAlex Zinenko 13368ae0d78SAlex Zinenko```c++ 13468ae0d78SAlex Zinenko// In MyExtension.cpp. 13568ae0d78SAlex Zinenko 13668ae0d78SAlex Zinenko#define GET_OP_CLASSES 13768ae0d78SAlex Zinenko#include "MyExtension.cpp.inc" 13868ae0d78SAlex Zinenko 13968ae0d78SAlex Zinenko// … 14068ae0d78SAlex Zinenkovoid MyExtension::init() { 14168ae0d78SAlex Zinenko // … 14268ae0d78SAlex Zinenko 143b33b91a2SOleksandr "Alex" Zinenko // Finally, we register the additional transform operations with the dialect. 144b33b91a2SOleksandr "Alex" Zinenko // List all operations generated from ODS. This call will perform additional 145b33b91a2SOleksandr "Alex" Zinenko // checks that the operations implement the transform and memory effect 146b33b91a2SOleksandr "Alex" Zinenko // interfaces required by the dialect interpreter and assert if they do not. 14768ae0d78SAlex Zinenko registerTransformOps< 14868ae0d78SAlex Zinenko#define GET_OP_LIST 14968ae0d78SAlex Zinenko#include "MyExtension.cpp.inc" 15068ae0d78SAlex Zinenko >(); 15168ae0d78SAlex Zinenko} 15268ae0d78SAlex Zinenko``` 15368ae0d78SAlex Zinenko 15468ae0d78SAlex Zinenko## Defining a Transform Operation 15568ae0d78SAlex Zinenko 15668ae0d78SAlex ZinenkoWith this setup, we are now ready to define the new transform operation to rewrite the function call. This is identical to defining a regular operation in a dialect. Note that the Transform dialect requires operations to implement the `TransformOpInterface` as well as `MemoryEffectsOpInterface` to indicate whether the operands are consumed or only read. Our operation can be defined along the following lines. 15768ae0d78SAlex Zinenko 15868ae0d78SAlex Zinenko```tablegen 15968ae0d78SAlex Zinenko// In MyExtension.td. 16068ae0d78SAlex Zinenko 161b33b91a2SOleksandr "Alex" Zinenko// Define the new operation. By convention, prefix its name with the name of the 162b33b91a2SOleksandr "Alex" Zinenko// dialect extension, "my.". The full operation name will be further prefixed 163b33b91a2SOleksandr "Alex" Zinenko// with "transform.". 16468ae0d78SAlex Zinenkodef ChangeCallTargetOp : Op<Transform_Dialect, "my.change_call_target", 165b33b91a2SOleksandr "Alex" Zinenko // Indicate that the operation implements the required TransformOpInterface 166b33b91a2SOleksandr "Alex" Zinenko // and MemoryEffectsOpInterface. 16768ae0d78SAlex Zinenko [DeclareOpInterfaceMethods<TransformOpInterface>, 16868ae0d78SAlex Zinenko DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> { 169b33b91a2SOleksandr "Alex" Zinenko // Provide a brief and a full description. It is recommended that the latter 170b33b91a2SOleksandr "Alex" Zinenko // describes the effects on the operands and how the operation processes 171b33b91a2SOleksandr "Alex" Zinenko // various failure modes. 17268ae0d78SAlex Zinenko let summary = "Changes the callee of a call operation to the specified one"; 17368ae0d78SAlex Zinenko let description = [{ 174b33b91a2SOleksandr "Alex" Zinenko For each `func.call` payload operation associated with the handle, changes 175b33b91a2SOleksandr "Alex" Zinenko its callee to be the symbol whose name is provided as an attribute to this operation. 17668ae0d78SAlex Zinenko 177b33b91a2SOleksandr "Alex" Zinenko Generates a silenceable failure if the operand is associated with payload operations that are not `func.call`. Only reads the operand. 17868ae0d78SAlex Zinenko }]; 17968ae0d78SAlex Zinenko 180b33b91a2SOleksandr "Alex" Zinenko // The arguments include the handle to the payload operations and the 181b33b91a2SOleksandr "Alex" Zinenko // attribute that specifies the new callee. The handle must implement 182b33b91a2SOleksandr "Alex" Zinenko // TransformHandleTypeInterface. 183b33b91a2SOleksandr "Alex" Zinenko // We use a string attribute as the symbol may not exist in the transform IR 184b33b91a2SOleksandr "Alex" Zinenko // so the verification may fail. 18568ae0d78SAlex Zinenko let arguments = (ins 18668ae0d78SAlex Zinenko TransformHandleTypeInterface:$call, 18768ae0d78SAlex Zinenko StrAttr:$new_target); 18868ae0d78SAlex Zinenko 189b33b91a2SOleksandr "Alex" Zinenko // The results are empty as the transformation does not produce any new 190b33b91a2SOleksandr "Alex" Zinenko // payload. 19168ae0d78SAlex Zinenko let results = (outs); 19268ae0d78SAlex Zinenko 19368ae0d78SAlex Zinenko // Provide nice syntax. 19468ae0d78SAlex Zinenko let assemblyFormat = "$call `,` $new_target attr-dict `:` type($call)"; 19568ae0d78SAlex Zinenko} 19668ae0d78SAlex Zinenko``` 19768ae0d78SAlex Zinenko 198c63d2b2cSMatthias SpringerTo finalize the definition of the transform operation, we need to implement the 199c63d2b2cSMatthias Springerinterface methods. The `TransformOpInterface` currently requires only one method 200c63d2b2cSMatthias Springer– `apply` – that performs the actual transformation. It is a good practice to 201c63d2b2cSMatthias Springerlimit the body of the method to manipulation of the Transform dialect constructs 202c63d2b2cSMatthias Springerand have the actual transformation implemented as a standalone function so it 203c63d2b2cSMatthias Springercan be used from other places in the code. Similar to rewrite patterns, all IR 204c63d2b2cSMatthias Springermust be modified with the provided rewriter. 20568ae0d78SAlex Zinenko 20668ae0d78SAlex Zinenko```c++ 20768ae0d78SAlex Zinenko// In MyExtension.cpp 20868ae0d78SAlex Zinenko 20939298b09SAndrzej Warzyński// Implementation of our Transform dialect operation. 21068ae0d78SAlex Zinenko// This operation returns a tri-state result that can be one of: 21168ae0d78SAlex Zinenko// - success when the transformation succeeded; 212c63d2b2cSMatthias Springer// - definite failure when the transformation failed in such a way that 213c63d2b2cSMatthias Springer// following transformations are impossible or undesirable, typically it could 214c63d2b2cSMatthias Springer// have left payload IR in an invalid state; it is expected that a diagnostic 215c63d2b2cSMatthias Springer// is emitted immediately before returning the definite error; 216c63d2b2cSMatthias Springer// - silenceable failure when the transformation failed but following 217c63d2b2cSMatthias Springer// transformations are still applicable, typically this means a precondition 218c63d2b2cSMatthias Springer// for the transformation is not satisfied and the payload IR has not been 219c63d2b2cSMatthias Springer// modified. The silenceable failure additionally carries a Diagnostic that 220c63d2b2cSMatthias Springer// can be emitted to the user. 221c63d2b2cSMatthias Springer::mlir::DiagnosedSilenceableFailure mlir::transform::ChangeCallTargetOp::apply( 222c63d2b2cSMatthias Springer // The rewriter that should be used when modifying IR. 223c63d2b2cSMatthias Springer ::mlir::transform::TransformRewriter &rewriter, 224c63d2b2cSMatthias Springer // The list of payload IR entities that will be associated with the 225c63d2b2cSMatthias Springer // transform IR values defined by this transform operation. In this case, it 226c63d2b2cSMatthias Springer // can remain empty as there are no results. 22768ae0d78SAlex Zinenko ::mlir::transform::TransformResults &results, 228c63d2b2cSMatthias Springer // The transform application state. This object can be used to query the 229c63d2b2cSMatthias Springer // current associations between transform IR values and payload IR entities. 230c63d2b2cSMatthias Springer // It can also carry additional user-defined state. 23168ae0d78SAlex Zinenko ::mlir::transform::TransformState &state) { 23268ae0d78SAlex Zinenko 233b33b91a2SOleksandr "Alex" Zinenko // First, we need to obtain the list of payload operations that are associated 234b33b91a2SOleksandr "Alex" Zinenko // with the operand handle. 23568ae0d78SAlex Zinenko auto payload = state.getPayloadOps(getCall()); 23668ae0d78SAlex Zinenko 23768ae0d78SAlex Zinenko // Then, we iterate over the list of operands and call the actual IR-mutating 23868ae0d78SAlex Zinenko // function. We also check the preconditions here. 23968ae0d78SAlex Zinenko for (Operation *payloadOp : payload) { 24068ae0d78SAlex Zinenko auto call = dyn_cast<::mlir::func::CallOp>(payloadOp); 24168ae0d78SAlex Zinenko if (!call) { 24268ae0d78SAlex Zinenko DiagnosedSilenceableFailure diag = emitSilenceableError() 24368ae0d78SAlex Zinenko << "only applies to func.call payloads"; 24468ae0d78SAlex Zinenko diag.attachNote(payloadOp->getLoc()) << "offending payload"; 24568ae0d78SAlex Zinenko return diag; 24668ae0d78SAlex Zinenko } 24768ae0d78SAlex Zinenko 24868ae0d78SAlex Zinenko updateCallee(call, getNewTarget()); 24968ae0d78SAlex Zinenko } 25068ae0d78SAlex Zinenko 25168ae0d78SAlex Zinenko // If everything went well, return success. 25268ae0d78SAlex Zinenko return DiagnosedSilenceableFailure::success(); 25368ae0d78SAlex Zinenko} 25468ae0d78SAlex Zinenko``` 25568ae0d78SAlex Zinenko 25668ae0d78SAlex ZinenkoThe implementation of the `MemoryEffectsOpInterface` must specify the effects this operation has on its operands (consumed or readonly) and on the payload IR (mutates or readonly). Transform dialect verifiers will check for side effects being present and assert in debug builds if they are not. 25768ae0d78SAlex Zinenko 25868ae0d78SAlex Zinenko```c++ 25968ae0d78SAlex Zinenko// In MyExtension.cpp 26068ae0d78SAlex Zinenko 26168ae0d78SAlex Zinenkovoid ChangeCallTargetOp::getEffects( 26268ae0d78SAlex Zinenko ::llvm::SmallVectorImpl<::mlir::MemoryEffects::EffectInstance> &effects) { 26368ae0d78SAlex Zinenko // Indicate that the `call` handle is only read by this operation because the 26468ae0d78SAlex Zinenko // associated operation is not erased but rather modified in-place, so the 26568ae0d78SAlex Zinenko // reference to it remains valid. 26668ae0d78SAlex Zinenko onlyReadsHandle(getCall(), effects); 26768ae0d78SAlex Zinenko 26868ae0d78SAlex Zinenko // Indicate that the payload is modified by this operation. 26968ae0d78SAlex Zinenko modifiesPayload(effects); 27068ae0d78SAlex Zinenko} 27168ae0d78SAlex Zinenko``` 27268ae0d78SAlex Zinenko 27368ae0d78SAlex Zinenko## Registration and Usage 27468ae0d78SAlex Zinenko 27568ae0d78SAlex ZinenkoThis is enough to define transform operations. The only remaining bit is providing the extension registration hook that can be called from the project’s `main`. 27668ae0d78SAlex Zinenko 27768ae0d78SAlex Zinenko 27868ae0d78SAlex Zinenko```c++ 27968ae0d78SAlex Zinenko// In TransformDialect.cpp (don't forget a declaration in TransformDialect.h); 28068ae0d78SAlex Zinenko 28168ae0d78SAlex Zinenkovoid registerMyExtension(::mlir::DialectRegistry ®istry) { 28268ae0d78SAlex Zinenko registry.addExtensions<MyExtension>(); 28368ae0d78SAlex Zinenko} 28468ae0d78SAlex Zinenko``` 28568ae0d78SAlex Zinenko 28639298b09SAndrzej WarzyńskiAfter registering the extension, it becomes possible to use our new operation in the Transform dialect interpreter. The upstream testing pass can be used as is. 28768ae0d78SAlex Zinenko 28868ae0d78SAlex Zinenko```mlir 289b33b91a2SOleksandr "Alex" Zinenkomodule attributes {transform.with_named_sequence} { 290b33b91a2SOleksandr "Alex" Zinenko transform.named_sequence @__transform_main( 291b33b91a2SOleksandr "Alex" Zinenko %arg0: !transform.any_op, 29268ae0d78SAlex Zinenko %arg1: !transform.op<"linalg.matmul">, 293b33b91a2SOleksandr "Alex" Zinenko %arg2: !transform.op<"linalg.elemwise_binary">) { 29468ae0d78SAlex Zinenko // Since the %arg2 handle is associated with both elementwise operations, 29568ae0d78SAlex Zinenko // we need to split it into two handles so we can target only the second 29668ae0d78SAlex Zinenko // elementwise operation. 297b33b91a2SOleksandr "Alex" Zinenko %add, %max = transform.split_handle %arg2 298b33b91a2SOleksandr "Alex" Zinenko : (!transform.op<"linalg.elemwise_binary">) 29968ae0d78SAlex Zinenko -> (!transform.any_op, !transform.any_op) 30068ae0d78SAlex Zinenko 301b33b91a2SOleksandr "Alex" Zinenko // The actual tiling transformation takes tile sizes as attributes. It 302b33b91a2SOleksandr "Alex" Zinenko // produces a handle to the loop generated during tiling. 303b33b91a2SOleksandr "Alex" Zinenko %loop, %tiled = transform.structured.tile_using_forall %max 304b33b91a2SOleksandr "Alex" Zinenko tile_sizes [8, 32] 30568ae0d78SAlex Zinenko : (!transform.any_op) -> (!transform.any_op, !transform.any_op) 30668ae0d78SAlex Zinenko 30768ae0d78SAlex Zinenko // We can now fuse the other operations into the loop. Here, we fuse 30868ae0d78SAlex Zinenko // operations one-by-one. This requires the operation that is being fused 30968ae0d78SAlex Zinenko // to define the value used within the loop, so the order of such fusions 31068ae0d78SAlex Zinenko // is important. We could also use "transform.merge_handles" to obtain 311b33b91a2SOleksandr "Alex" Zinenko // a single handle to all operations and give it to 312b33b91a2SOleksandr "Alex" Zinenko // `fuse_into_containing_op` that would take care of the ordering in this 313b33b91a2SOleksandr "Alex" Zinenko // case. 31468ae0d78SAlex Zinenko %add_fused = transform.structured.fuse_into_containing_op %add into %loop 31568ae0d78SAlex Zinenko : (!transform.any_op, !transform.any_op) -> !transform.any_op 316b33b91a2SOleksandr "Alex" Zinenko %matmul_fused = transform.structured.fuse_into_containing_op %arg1 317b33b91a2SOleksandr "Alex" Zinenko into %loop 318b33b91a2SOleksandr "Alex" Zinenko : (!transform.op<"linalg.matmul">, !transform.any_op) 319b33b91a2SOleksandr "Alex" Zinenko -> !transform.any_op 32068ae0d78SAlex Zinenko 32168ae0d78SAlex Zinenko // Tile again to get the desired size. Note that this time this tiles the 32268ae0d78SAlex Zinenko // "add" operation and fuses matmul into the loop, but doesn't affect the 323b33b91a2SOleksandr "Alex" Zinenko // "max" operation. This illustrates the precise targeting with the 324b33b91a2SOleksandr "Alex" Zinenko // transform dialect. Otherwise, it is difficult to differentiate "add" and 325b33b91a2SOleksandr "Alex" Zinenko // "max", both of which having the same kind. 326b33b91a2SOleksandr "Alex" Zinenko %loop_2, %tiled_2 = transform.structured.tile_using_forall %add_fused 327b33b91a2SOleksandr "Alex" Zinenko tile_sizes [4, 4] 32868ae0d78SAlex Zinenko : (!transform.any_op) -> (!transform.any_op, !transform.any_op) 329b33b91a2SOleksandr "Alex" Zinenko %matmul_fused_2 = transform.structured.fuse_into_containing_op %matmul_fused 330b33b91a2SOleksandr "Alex" Zinenko into %loop_2 33168ae0d78SAlex Zinenko : (!transform.any_op, !transform.any_op) -> !transform.any_op 33268ae0d78SAlex Zinenko 333b33b91a2SOleksandr "Alex" Zinenko // Since outlining is currently only implemented for region-holding 334b33b91a2SOleksandr "Alex" Zinenko // operations such as loops, use tiling to size 1 to materialize the outer 335b33b91a2SOleksandr "Alex" Zinenko // loop that is going to be outlined. 33696ff0255SOleksandr "Alex" Zinenko %outline_target, %_ = transform.structured.tile_using_forall %tiled_2 tile_sizes [1] 33768ae0d78SAlex Zinenko : (!transform.any_op) -> (!transform.any_op, !transform.any_op) 33868ae0d78SAlex Zinenko transform.structured.fuse_into_containing_op %matmul_fused_2 into %outline_target 33968ae0d78SAlex Zinenko : (!transform.any_op, !transform.any_op) -> !transform.any_op 340b33b91a2SOleksandr "Alex" Zinenko %func, %call = transform.loop.outline %outline_target 341b33b91a2SOleksandr "Alex" Zinenko {func_name = "outlined"} 34268ae0d78SAlex Zinenko : (!transform.any_op) -> (!transform.any_op, !transform.any_op) 34368ae0d78SAlex Zinenko 34468ae0d78SAlex Zinenko // Rewrite the call target. 34568ae0d78SAlex Zinenko transform.my.change_call_target %call, "microkernel" : !transform.any_op 34668ae0d78SAlex Zinenko 34768ae0d78SAlex Zinenko transform.yield 34868ae0d78SAlex Zinenko } 349b33b91a2SOleksandr "Alex" Zinenko} 35068ae0d78SAlex Zinenko``` 351d8c18e42SAlex Zinenko 352d8c18e42SAlex Zinenko## Appendix: Autogenerated Documentation 353d8c18e42SAlex Zinenko 3544921ff5bSKohei Yamaguchi[include "Tutorials/transform/MyExtensionCh2.md"] 355