xref: /llvm-project/mlir/docs/Tutorials/transform/Ch4.md (revision 73fa6685c43ef61f5f5babb14f734097af6dc702)
14cb2ef4fSOleksandr "Alex" Zinenko# Chapter 4: Matching Payload with Transform Operations
24cb2ef4fSOleksandr "Alex" Zinenko
34cb2ef4fSOleksandr "Alex" Zinenko**Check the continuously-tested version of MLIR files under
44cb2ef4fSOleksandr "Alex" Zinenko[mlir/test/Examples/transform/Ch4](https://github.com/llvm/llvm-project/tree/main/mlir/test/Examples/transform/Ch4).**
54cb2ef4fSOleksandr "Alex" Zinenko
64cb2ef4fSOleksandr "Alex" ZinenkoUp until now, we were applying transform dialect scripts under the assumption
74cb2ef4fSOleksandr "Alex" Zinenkothat specific payload operations are identified by the caller when the transform
84cb2ef4fSOleksandr "Alex" Zinenkodialect interpreter is invoked. This may be seen as contrary to the idea of
94cb2ef4fSOleksandr "Alex" Zinenkodriving transformations from a dialect since the transformation targets must be
104cb2ef4fSOleksandr "Alex" Zinenkoidentified through mechanisms external to the transform dialect interpreter, for
114cb2ef4fSOleksandr "Alex" Zinenkoexample, when invoking the interpreter programmatically in C++ or through pass
124cb2ef4fSOleksandr "Alex" Zinenkoarguments as seen in previous chapters. It also adds practical overhead due to
134cb2ef4fSOleksandr "Alex" Zinenkoincreased interaction with the interpreter in C++, and cognitive overhead of
144cb2ef4fSOleksandr "Alex" Zinenkomanipulating two interfaces at once. To remedy this, Transform dialect proposes
154cb2ef4fSOleksandr "Alex" Zinenkoa subset of operations for _matching_ payload operations that need to be
164cb2ef4fSOleksandr "Alex" Zinenkotransformed.
174cb2ef4fSOleksandr "Alex" Zinenko
184cb2ef4fSOleksandr "Alex" Zinenko_Match_ operations are simply transform operations with some additional
194cb2ef4fSOleksandr "Alex" Zinenkoguarantees. In particular, they are not expected to modify the payload IR and
204cb2ef4fSOleksandr "Alex" Zinenkoare expected to fail if their operands (typically payload operation handles) are
214cb2ef4fSOleksandr "Alex" Zinenkonot associated with payload IR objects having desired properties, such as
224cb2ef4fSOleksandr "Alex" Zinenkooperation names or kinds of arguments. Using simple combinator operations, it
234cb2ef4fSOleksandr "Alex" Zinenkobecomes possible to set up a higher-level match and rewrite infrastructure
244cb2ef4fSOleksandr "Alex" Zinenkodirectly within the transform dialect.
254cb2ef4fSOleksandr "Alex" Zinenko
264cb2ef4fSOleksandr "Alex" Zinenko
274cb2ef4fSOleksandr "Alex" Zinenko## Simple match
284cb2ef4fSOleksandr "Alex" Zinenko
294cb2ef4fSOleksandr "Alex" ZinenkoLet us reconsider the “fully connected layer” example from [Chapter
30*73fa6685Smlevesquedion1](Ch1.md/#chaining-transformations-with-handles), reproduced below for
314cb2ef4fSOleksandr "Alex" Zinenkoconvenience.
324cb2ef4fSOleksandr "Alex" Zinenko
334cb2ef4fSOleksandr "Alex" Zinenko
344cb2ef4fSOleksandr "Alex" Zinenko```mlir
354cb2ef4fSOleksandr "Alex" Zinenko// Original function to optimize.
364cb2ef4fSOleksandr "Alex" Zinenkofunc.func @fc_relu(%lhs: tensor<512x512xf32>, %rhs: tensor<512x512xf32>,
374cb2ef4fSOleksandr "Alex" Zinenko                   %bias: tensor<512x512xf32>, %output: tensor<512x512xf32>)
384cb2ef4fSOleksandr "Alex" Zinenko                   -> tensor<512x512xf32> {
394cb2ef4fSOleksandr "Alex" Zinenko  // Matrix-matrix multiplication.
404cb2ef4fSOleksandr "Alex" Zinenko  %matmul = linalg.matmul
414cb2ef4fSOleksandr "Alex" Zinenko            ins(%lhs, %rhs: tensor<512x512xf32>, tensor<512x512xf32>)
424cb2ef4fSOleksandr "Alex" Zinenko            outs(%output: tensor<512x512xf32>) -> tensor<512x512xf32>
434cb2ef4fSOleksandr "Alex" Zinenko
444cb2ef4fSOleksandr "Alex" Zinenko  // Elementwise addition.
454cb2ef4fSOleksandr "Alex" Zinenko  %biased = linalg.elemwise_binary { fun = #linalg.binary_fn<add> }
464cb2ef4fSOleksandr "Alex" Zinenko    ins(%matmul, %bias : tensor<512x512xf32>, tensor<512x512xf32>)
474cb2ef4fSOleksandr "Alex" Zinenko    outs(%output : tensor<512x512xf32>) -> tensor<512x512xf32>
484cb2ef4fSOleksandr "Alex" Zinenko
494cb2ef4fSOleksandr "Alex" Zinenko  // Elementwise max with 0 (ReLU).
504cb2ef4fSOleksandr "Alex" Zinenko  %c0f = arith.constant 0.0 : f32
514cb2ef4fSOleksandr "Alex" Zinenko  %relued = linalg.elemwise_binary { fun = #linalg.binary_fn<max_signed> }
524cb2ef4fSOleksandr "Alex" Zinenko    ins(%biased, %c0f : tensor<512x512xf32>, f32)
534cb2ef4fSOleksandr "Alex" Zinenko    outs(%output : tensor<512x512xf32>) -> tensor<512x512xf32>
544cb2ef4fSOleksandr "Alex" Zinenko  func.return %relued : tensor<512x512xf32>
554cb2ef4fSOleksandr "Alex" Zinenko}
564cb2ef4fSOleksandr "Alex" Zinenko
574cb2ef4fSOleksandr "Alex" Zinenko```
584cb2ef4fSOleksandr "Alex" Zinenko
594cb2ef4fSOleksandr "Alex" Zinenko
604cb2ef4fSOleksandr "Alex" ZinenkoIn Chapter 1, we were calling the test transform interpreter pass with
614cb2ef4fSOleksandr "Alex" Zinenkoadditional arguments, `bind-first-extra-to-ops=linalg.matmul
624cb2ef4fSOleksandr "Alex" Zinenkobind-second-extra-to-ops=linalg.elemwise_binary`, to provide initial
634cb2ef4fSOleksandr "Alex" Zinenkoassociations for operation handles. Instead, we can use match operations to
644cb2ef4fSOleksandr "Alex" Zinenkodiscover relevant operations in the payload IR. Match operations can be combined
654cb2ef4fSOleksandr "Alex" Zinenkowith “regular” transform operations using, e.g., the
664cb2ef4fSOleksandr "Alex" Zinenko`transform.collect_matching` combinator operation that leverages the concept of
674cb2ef4fSOleksandr "Alex" Zinenkonamed sequences to organize matchers.
684cb2ef4fSOleksandr "Alex" Zinenko
694cb2ef4fSOleksandr "Alex" Zinenko
704cb2ef4fSOleksandr "Alex" Zinenko```mlir
714cb2ef4fSOleksandr "Alex" Zinenko// The module containing named sequences must have an attribute allowing them
724cb2ef4fSOleksandr "Alex" Zinenko// to enable verification.
734cb2ef4fSOleksandr "Alex" Zinenkomodule @transforms attributes { transform.with_named_sequence } {
744cb2ef4fSOleksandr "Alex" Zinenko  // Entry point. This takes as the only argument the root operation (typically
754cb2ef4fSOleksandr "Alex" Zinenko  // pass root) given to the transform interpreter.
764cb2ef4fSOleksandr "Alex" Zinenko  transform.named_sequence @__transform_main(
774cb2ef4fSOleksandr "Alex" Zinenko      %root: !transform.any_op {transform.readonly}) {
784cb2ef4fSOleksandr "Alex" Zinenko    // Collect operations that match the criteria specified in named sequence.
794cb2ef4fSOleksandr "Alex" Zinenko    // If the named sequence fails with a silenceable failure, silences it (the
804cb2ef4fSOleksandr "Alex" Zinenko    // message is forwarded to the debug stream). If the named sequence
814cb2ef4fSOleksandr "Alex" Zinenko    // succeeds, appends its results to the results of this operation.
824cb2ef4fSOleksandr "Alex" Zinenko    %elemwise = transform.collect_matching @match_elemwise in %root
834cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> !transform.any_op
844cb2ef4fSOleksandr "Alex" Zinenko    %matmul = transform.collect_matching @match_matmul in %root
854cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> !transform.any_op
864cb2ef4fSOleksandr "Alex" Zinenko    transform.include @print_elemwise failures(propagate)  (%elemwise)
874cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> ()
884cb2ef4fSOleksandr "Alex" Zinenko    transform.include @print_matmul failures(propagate)  (%matmul)
894cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> ()
904cb2ef4fSOleksandr "Alex" Zinenko
914cb2ef4fSOleksandr "Alex" Zinenko    transform.yield
924cb2ef4fSOleksandr "Alex" Zinenko  }
934cb2ef4fSOleksandr "Alex" Zinenko
944cb2ef4fSOleksandr "Alex" Zinenko  // This is a matcher sequence. It is given an operation to match and the
954cb2ef4fSOleksandr "Alex" Zinenko  // match is considered successful unless any nested operation produces a
964cb2ef4fSOleksandr "Alex" Zinenko  // failure. The values yielded by this operation will be forwarded to the
974cb2ef4fSOleksandr "Alex" Zinenko  // rewriter sequence on success.
984cb2ef4fSOleksandr "Alex" Zinenko  transform.named_sequence @match_elemwise(
994cb2ef4fSOleksandr "Alex" Zinenko      %entry: !transform.any_op {transform.readonly}) -> !transform.any_op {
1004cb2ef4fSOleksandr "Alex" Zinenko    transform.match.operation_name %entry ["linalg.elemwise_binary"]
1014cb2ef4fSOleksandr "Alex" Zinenko      : !transform.any_op
1024cb2ef4fSOleksandr "Alex" Zinenko    transform.yield %entry : !transform.any_op
1034cb2ef4fSOleksandr "Alex" Zinenko  }
1044cb2ef4fSOleksandr "Alex" Zinenko  transform.named_sequence @match_matmul(
1054cb2ef4fSOleksandr "Alex" Zinenko      %entry: !transform.any_op {transform.readonly}) -> !transform.any_op {
1064cb2ef4fSOleksandr "Alex" Zinenko    transform.match.operation_name %entry ["linalg.matmul"] : !transform.any_op
1074cb2ef4fSOleksandr "Alex" Zinenko    transform.yield %entry : !transform.any_op
1084cb2ef4fSOleksandr "Alex" Zinenko  }
1094cb2ef4fSOleksandr "Alex" Zinenko
1104cb2ef4fSOleksandr "Alex" Zinenko  // This is a rewriter sequence.
1114cb2ef4fSOleksandr "Alex" Zinenko  transform.named_sequence @print_elemwise(
1124cb2ef4fSOleksandr "Alex" Zinenko      %elemwise_binary: !transform.any_op {transform.readonly}) {
1132798b72aSOleksandr "Alex" Zinenko    transform.debug.emit_remark_at
1144cb2ef4fSOleksandr "Alex" Zinenko      %elemwise_binary, "elementwise binary" : !transform.any_op
1154cb2ef4fSOleksandr "Alex" Zinenko    transform.yield
1164cb2ef4fSOleksandr "Alex" Zinenko  }
1174cb2ef4fSOleksandr "Alex" Zinenko  transform.named_sequence @print_matmul(
1184cb2ef4fSOleksandr "Alex" Zinenko      %matmul: !transform.any_op {transform.readonly}) {
1192798b72aSOleksandr "Alex" Zinenko    transform.debug.emit_remark_at %matmul, "matmul" : !transform.any_op
1204cb2ef4fSOleksandr "Alex" Zinenko    transform.yield
1214cb2ef4fSOleksandr "Alex" Zinenko  }
1224cb2ef4fSOleksandr "Alex" Zinenko}
1234cb2ef4fSOleksandr "Alex" Zinenko
1244cb2ef4fSOleksandr "Alex" Zinenko```
1254cb2ef4fSOleksandr "Alex" Zinenko
1264cb2ef4fSOleksandr "Alex" Zinenko
1274cb2ef4fSOleksandr "Alex" ZinenkoThis script can be executed using the non-test interpreter pass running on the
1284cb2ef4fSOleksandr "Alex" Zinenkoroot operation of the translation unit without additional flags: `mlir-opt
1294cb2ef4fSOleksandr "Alex" Zinenko--transform-interpreter`. It will emit corresponding remarks at
1304cb2ef4fSOleksandr "Alex" Zinenko`linalg.elemwise_binary` and `linalg.matmul` operations. In debug builds, the
1314cb2ef4fSOleksandr "Alex" Zinenkoinfrastructure provides a convenient method to understand the matching process
1324cb2ef4fSOleksandr "Alex" Zinenkoby passing `-debug-only=transform-matcher` to `mlir-opt` or a derived tool. It
1334cb2ef4fSOleksandr "Alex" Zinenkowill print the silenceable failure messages produced by the match operations
1344cb2ef4fSOleksandr "Alex" Zinenkointo the debug stream, for example:
1354cb2ef4fSOleksandr "Alex" Zinenko
1364cb2ef4fSOleksandr "Alex" Zinenko
1374cb2ef4fSOleksandr "Alex" Zinenko```
1384cb2ef4fSOleksandr "Alex" Zinenko<...>
1394cb2ef4fSOleksandr "Alex" Zinenko[transform-matcher] matching %0 = linalg.matmul ins(%arg0, %arg1 : tensor<512x512xf32>, tensor<512x512xf32>) outs(%arg3 : tensor<512x512xf32>) -> tensor<512x512xf32> @0x5622eee08410
1404cb2ef4fSOleksandr "Alex" Zinenko[transform-matcher] matcher match_elemwise failed: wrong operation name
1414cb2ef4fSOleksandr "Alex" Zinenko<...>
1424cb2ef4fSOleksandr "Alex" Zinenko```
1434cb2ef4fSOleksandr "Alex" Zinenko
1444cb2ef4fSOleksandr "Alex" Zinenko
1454cb2ef4fSOleksandr "Alex" ZinenkoThis is now sufficient to run the rest of the transform script from Chapter 1,
1464cb2ef4fSOleksandr "Alex" Zinenkosubstituting `%arg1` with `%matmul` and `%arg2` with `%elemwise`.
1474cb2ef4fSOleksandr "Alex" Zinenko
1484cb2ef4fSOleksandr "Alex" Zinenko
1494cb2ef4fSOleksandr "Alex" Zinenko## Matching Chains of Operations
1504cb2ef4fSOleksandr "Alex" Zinenko
1514cb2ef4fSOleksandr "Alex" ZinenkoThe matcher above remains naive as it matches _all_ operations of the certain
1524cb2ef4fSOleksandr "Alex" Zinenkokind under the payload root. These operations may or may not be related, and
1534cb2ef4fSOleksandr "Alex" Zinenkomay, for example, belong to different functions. Even if they are in a single
1544cb2ef4fSOleksandr "Alex" Zinenkofunction, if there are multiple groups of such operations, we wouldn’t be able
1554cb2ef4fSOleksandr "Alex" Zinenkoto differentiate them with this approach. In reality, we want to match a
1564cb2ef4fSOleksandr "Alex" Zinenkospecific group of operations where a `matmul` operation produces a result that
1574cb2ef4fSOleksandr "Alex" Zinenkois used by an elementwise operation, which in turn feeds another elementwise
1584cb2ef4fSOleksandr "Alex" Zinenkooperation in a similar way.
1594cb2ef4fSOleksandr "Alex" Zinenko
1604cb2ef4fSOleksandr "Alex" ZinenkoThis can be achieved using the following matcher sequence.
1614cb2ef4fSOleksandr "Alex" Zinenko
1624cb2ef4fSOleksandr "Alex" Zinenko
1634cb2ef4fSOleksandr "Alex" Zinenko```mlir
1644cb2ef4fSOleksandr "Alex" Zinenko// This is also a matcher sequence. It is similarly given an operation to
1654cb2ef4fSOleksandr "Alex" Zinenko// match and nested operations must succeed in order for a match to be deemed
1664cb2ef4fSOleksandr "Alex" Zinenko// successful. It starts matching from the last operation in the use-def chain
1674cb2ef4fSOleksandr "Alex" Zinenko// and goes back because each operand (use) has exactly one definition.
1684cb2ef4fSOleksandr "Alex" Zinenkotransform.named_sequence @match_matmul_elemwise(
1694cb2ef4fSOleksandr "Alex" Zinenko    %last: !transform.any_op {transform.readonly})
1704cb2ef4fSOleksandr "Alex" Zinenko    -> (!transform.any_op, !transform.any_op, !transform.any_op) {
1714cb2ef4fSOleksandr "Alex" Zinenko  // The last operation must be an elementwise binary.
1724cb2ef4fSOleksandr "Alex" Zinenko  transform.match.operation_name %last ["linalg.elemwise_binary"]
1734cb2ef4fSOleksandr "Alex" Zinenko    : !transform.any_op
1744cb2ef4fSOleksandr "Alex" Zinenko  // Its first operand must be defined by another operation, to which we
1754cb2ef4fSOleksandr "Alex" Zinenko  // will get a handle here. We are guaranteed that the first operand exists
1764cb2ef4fSOleksandr "Alex" Zinenko  // because we know the operation is binary, but even in absence of such a
1774cb2ef4fSOleksandr "Alex" Zinenko  // guarantee, this operation would have produced a silenceable failure when
1784cb2ef4fSOleksandr "Alex" Zinenko  // `%last` does not have enough operands.
1794cb2ef4fSOleksandr "Alex" Zinenko  %middle = transform.get_producer_of_operand %last[0]
1804cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> !transform.any_op
1814cb2ef4fSOleksandr "Alex" Zinenko  // The defining operation must itself be an elementwise binary.
1824cb2ef4fSOleksandr "Alex" Zinenko  transform.match.operation_name %middle ["linalg.elemwise_binary"]
1834cb2ef4fSOleksandr "Alex" Zinenko    : !transform.any_op
1844cb2ef4fSOleksandr "Alex" Zinenko  // And the first operand of that operation must be defined by yet another
1854cb2ef4fSOleksandr "Alex" Zinenko  // operation.
1864cb2ef4fSOleksandr "Alex" Zinenko  %matmul = transform.get_producer_of_operand %middle[0]
1874cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> !transform.any_op
1884cb2ef4fSOleksandr "Alex" Zinenko  // And that operation is a matmul.
1894cb2ef4fSOleksandr "Alex" Zinenko  transform.match.operation_name %matmul ["linalg.matmul"] : !transform.any_op
1904cb2ef4fSOleksandr "Alex" Zinenko  // We will yield the handles to the matmul and the two elementwise
1914cb2ef4fSOleksandr "Alex" Zinenko  // operations separately.
1924cb2ef4fSOleksandr "Alex" Zinenko  transform.yield %matmul, %middle, %last
1934cb2ef4fSOleksandr "Alex" Zinenko    : !transform.any_op, !transform.any_op, !transform.any_op
1944cb2ef4fSOleksandr "Alex" Zinenko}
1954cb2ef4fSOleksandr "Alex" Zinenko```
1964cb2ef4fSOleksandr "Alex" Zinenko
1974cb2ef4fSOleksandr "Alex" ZinenkoThis matcher is applicable in presence of other `elemwise` and `matmul`
1984cb2ef4fSOleksandr "Alex" Zinenkooperations and will return the triple of _related_ operations rather than
1994cb2ef4fSOleksandr "Alex" Zinenkooperations in the order in which they are found. It can be exercised similarly
2004cb2ef4fSOleksandr "Alex" Zinenkoto the previous incarnation, as follows.
2014cb2ef4fSOleksandr "Alex" Zinenko
2024cb2ef4fSOleksandr "Alex" Zinenko```mlir
2034cb2ef4fSOleksandr "Alex" Zinenko// Alternative entry point.
2044cb2ef4fSOleksandr "Alex" Zinenkotransform.named_sequence @__transform_main(
2054cb2ef4fSOleksandr "Alex" Zinenko    %root: !transform.any_op {transform.readonly}) {
2064cb2ef4fSOleksandr "Alex" Zinenko  // Collect groups of operations that match the criteria specified in the
2074cb2ef4fSOleksandr "Alex" Zinenko  // named sequence.
2084cb2ef4fSOleksandr "Alex" Zinenko  %matmul, %el1, %el2 = transform.collect_matching @match_matmul_elemwise in %root
2094cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> (!transform.any_op, !transform.any_op, !transform.any_op)
2104cb2ef4fSOleksandr "Alex" Zinenko  %elemwise = transform.merge_handles %el1, %el2 : !transform.any_op
2114cb2ef4fSOleksandr "Alex" Zinenko
2124cb2ef4fSOleksandr "Alex" Zinenko  transform.include @print_elemwise failures(propagate)  (%elemwise)
2134cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> ()
2144cb2ef4fSOleksandr "Alex" Zinenko  transform.include @print_matmul failures(propagate)  (%matmul)
2154cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> ()
2164cb2ef4fSOleksandr "Alex" Zinenko
2174cb2ef4fSOleksandr "Alex" Zinenko  transform.yield
2184cb2ef4fSOleksandr "Alex" Zinenko}
2194cb2ef4fSOleksandr "Alex" Zinenko```
2204cb2ef4fSOleksandr "Alex" Zinenko
2214cb2ef4fSOleksandr "Alex" Zinenko
2224cb2ef4fSOleksandr "Alex" Zinenko## Defining Match Operations
2234cb2ef4fSOleksandr "Alex" Zinenko
2244cb2ef4fSOleksandr "Alex" ZinenkoThe matcher of a chain of operations is correct in presence of other operations,
2254cb2ef4fSOleksandr "Alex" Zinenkobut is still insufficiently robust for many cases of interest. In particular,
2264cb2ef4fSOleksandr "Alex" Zinenkousing `transform.get_producer_of_operand %last[0]` requires that the _first_
2274cb2ef4fSOleksandr "Alex" Zinenkooperand of elementwise operations is produced by another operation. The same
2284cb2ef4fSOleksandr "Alex" Zinenkotransformation strategy may however apply regardless of the operand position:
2294cb2ef4fSOleksandr "Alex" Zinenkomany binary operations are associative. Let us use this opportunity to introduce
2304cb2ef4fSOleksandr "Alex" Zinenkoa new match operation. Specifically, we would like this operation to succeed if
2314cb2ef4fSOleksandr "Alex" Zinenko_any_ of the operands satisfies certain conditions that can be expressed as
2324cb2ef4fSOleksandr "Alex" Zinenkoother match operations. We also want it to return some of the state and the
2334cb2ef4fSOleksandr "Alex" Zinenkoposition of the matched operand in the operand list.
2344cb2ef4fSOleksandr "Alex" Zinenko
2354cb2ef4fSOleksandr "Alex" ZinenkoMatch operations are defined similarly to other transform operations, with the
2364cb2ef4fSOleksandr "Alex" Zinenkoonly difference of additionally implementing the `MatchOpInterface`. Note that
2374cb2ef4fSOleksandr "Alex" Zinenkothis interface has _no additional methods_ (though it may add some eventually)
2384cb2ef4fSOleksandr "Alex" Zinenkoand is only used as a verification contract that the operation is intended for
2394cb2ef4fSOleksandr "Alex" Zinenkomatching and will not attempt to transform the payload. The minimal definition
2404cb2ef4fSOleksandr "Alex" Zinenkoof our operation is as follows.
2414cb2ef4fSOleksandr "Alex" Zinenko
2424cb2ef4fSOleksandr "Alex" Zinenko
2434cb2ef4fSOleksandr "Alex" Zinenko```tablegen
2444cb2ef4fSOleksandr "Alex" Zinenko// Define the new operation. By convention, prefix its name with `match`
2454cb2ef4fSOleksandr "Alex" Zinenko// followed by the name of the dialect extension.
2464cb2ef4fSOleksandr "Alex" Zinenkodef HasOperandSatisfyingOp : TransformDialectOp<"match.my.has_operand_satisfying",
2474cb2ef4fSOleksandr "Alex" Zinenko    [DeclareOpInterfaceMethods<MemoryEffectsOpInterface>,
2484cb2ef4fSOleksandr "Alex" Zinenko     DeclareOpInterfaceMethods<TransformOpInterface>,
2494cb2ef4fSOleksandr "Alex" Zinenko     // Indicate that the operation implements MatchOpInterface in addition to
2504cb2ef4fSOleksandr "Alex" Zinenko     // the TransformOpInterface. This interface is only used as a tag at this
2514cb2ef4fSOleksandr "Alex" Zinenko     // point and has no methods that are mandatory to implement.
2524cb2ef4fSOleksandr "Alex" Zinenko     MatchOpInterface,
2534cb2ef4fSOleksandr "Alex" Zinenko     SingleBlockImplicitTerminator<"::mlir::transform::YieldOp">]> {
2544cb2ef4fSOleksandr "Alex" Zinenko  let summary = "Succeed if any of the operands matches all nested criteria";
2554cb2ef4fSOleksandr "Alex" Zinenko  let arguments = (ins TransformHandleTypeInterface:$op);
2564cb2ef4fSOleksandr "Alex" Zinenko  let results = (outs TransformParamTypeInterface:$position,
2574cb2ef4fSOleksandr "Alex" Zinenko                      Variadic<Transform_AnyHandleOrParamType>:$results);
2584cb2ef4fSOleksandr "Alex" Zinenko
2594cb2ef4fSOleksandr "Alex" Zinenko  // Match operations can be arbitrarily complex, e.g., containing regions.
2604cb2ef4fSOleksandr "Alex" Zinenko  let regions = (region SizedRegion<1>:$body);
2614cb2ef4fSOleksandr "Alex" Zinenko  let hasVerifier = 1;
2624cb2ef4fSOleksandr "Alex" Zinenko  let assemblyFormat = [{
2634cb2ef4fSOleksandr "Alex" Zinenko    $op `:` functional-type($op, results) attr-dict-with-keyword $body
2644cb2ef4fSOleksandr "Alex" Zinenko  }];
2654cb2ef4fSOleksandr "Alex" Zinenko}
2664cb2ef4fSOleksandr "Alex" Zinenko```
2674cb2ef4fSOleksandr "Alex" Zinenko
2684cb2ef4fSOleksandr "Alex" Zinenko
2694cb2ef4fSOleksandr "Alex" ZinenkoIt takes as argument the handle associated with the payload operations whose
2704cb2ef4fSOleksandr "Alex" Zinenkooperands it will match, has an associated single-block region containing the
2714cb2ef4fSOleksandr "Alex" Zinenkomatch criteria, and returns the position of the matched operand as well as any
2724cb2ef4fSOleksandr "Alex" Zinenkoother transform value yielded from the body on the successful match.
2734cb2ef4fSOleksandr "Alex" Zinenko
2744cb2ef4fSOleksandr "Alex" ZinenkoThe matching logic is implemented in the `apply` method of the
2754cb2ef4fSOleksandr "Alex" Zinenko`TransformOpInterface` and is easily composable with other transform operations.
2764cb2ef4fSOleksandr "Alex" ZinenkoAll facilities for managing the interpreter state and recursively entering the
2774cb2ef4fSOleksandr "Alex" Zinenkoblocks are available in the same way as they are for “regular” transform
2784cb2ef4fSOleksandr "Alex" Zinenkooperations. Match operations are expected to return a silenceable failure to
2794cb2ef4fSOleksandr "Alex" Zinenkoindicate failure to match, and to immediately propagate definite failures. If
2804cb2ef4fSOleksandr "Alex" Zinenkothey have nested operations, they are expected to handle and, in most cases,
2814cb2ef4fSOleksandr "Alex" Zinenkosilence the silenceable failures produced when applying those operations. For
2824cb2ef4fSOleksandr "Alex" Zinenkoour operation, the matching is essentially a loop iterating over all operands of
2834cb2ef4fSOleksandr "Alex" Zinenkothe (single) payload operation and applying nested transform ops until they all
2844cb2ef4fSOleksandr "Alex" Zinenkosucceed for one of the operands.
2854cb2ef4fSOleksandr "Alex" Zinenko
2864cb2ef4fSOleksandr "Alex" Zinenko
2874cb2ef4fSOleksandr "Alex" Zinenko```cpp
2884cb2ef4fSOleksandr "Alex" Zinenko// Matcher ops implement `apply` similarly to other transform ops. They are not
2894cb2ef4fSOleksandr "Alex" Zinenko// expected to modify payload, but use the tri-state result to signal failure or
2904cb2ef4fSOleksandr "Alex" Zinenko// success to match, as well as potential irrecoverable errors.
2914cb2ef4fSOleksandr "Alex" Zinenkomlir::DiagnosedSilenceableFailure
2924cb2ef4fSOleksandr "Alex" Zinenkomlir::transform::HasOperandSatisfyingOp::apply(
2934cb2ef4fSOleksandr "Alex" Zinenko    mlir::transform::TransformRewriter &rewriter,
2944cb2ef4fSOleksandr "Alex" Zinenko    mlir::transform::TransformResults &results,
2954cb2ef4fSOleksandr "Alex" Zinenko    mlir::transform::TransformState &state) {
2964cb2ef4fSOleksandr "Alex" Zinenko  // For simplicity, only handle a single payload op. Actual implementations
2974cb2ef4fSOleksandr "Alex" Zinenko  // can use `SingleOpMatcher` trait to simplify implementation and document
2984cb2ef4fSOleksandr "Alex" Zinenko  // this expectation.
2994cb2ef4fSOleksandr "Alex" Zinenko  auto payloadOps = state.getPayloadOps(getOp());
3004cb2ef4fSOleksandr "Alex" Zinenko  if (!llvm::hasSingleElement(payloadOps))
3014cb2ef4fSOleksandr "Alex" Zinenko    return emitSilenceableError() << "expected single payload";
3024cb2ef4fSOleksandr "Alex" Zinenko
3034cb2ef4fSOleksandr "Alex" Zinenko  // Iterate over all operands of the payload op to see if they can be matched
3044cb2ef4fSOleksandr "Alex" Zinenko  // using the body of this op.
3054cb2ef4fSOleksandr "Alex" Zinenko  Operation *payload = *payloadOps.begin();
3064cb2ef4fSOleksandr "Alex" Zinenko  for (OpOperand &operand : payload->getOpOperands()) {
3074cb2ef4fSOleksandr "Alex" Zinenko    // Create a scope for transform values defined in the body. This corresponds
3084cb2ef4fSOleksandr "Alex" Zinenko    // to the syntactic scope of the region attached to this op. Any values
3094cb2ef4fSOleksandr "Alex" Zinenko    // associated with payloads from now on will be automatically dissociated
3104cb2ef4fSOleksandr "Alex" Zinenko    // when this object is destroyed, i.e. at the end of the iteration.
3114cb2ef4fSOleksandr "Alex" Zinenko    // Associate the block argument handle with the operand.
3124cb2ef4fSOleksandr "Alex" Zinenko    auto matchScope = state.make_region_scope(getBody());
3134cb2ef4fSOleksandr "Alex" Zinenko    if (failed(state.mapBlockArgument(getBody().getArgument(0),
3144cb2ef4fSOleksandr "Alex" Zinenko                                      {operand.get()}))) {
3154cb2ef4fSOleksandr "Alex" Zinenko      return DiagnosedSilenceableFailure::definiteFailure();
3164cb2ef4fSOleksandr "Alex" Zinenko    }
3174cb2ef4fSOleksandr "Alex" Zinenko
3184cb2ef4fSOleksandr "Alex" Zinenko    // Iterate over all nested matchers with the current mapping and see if they
3194cb2ef4fSOleksandr "Alex" Zinenko    // succeed.
3204cb2ef4fSOleksandr "Alex" Zinenko    bool matchSucceeded = true;
3214cb2ef4fSOleksandr "Alex" Zinenko    for (Operation &matcher : getBody().front().without_terminator()) {
3224cb2ef4fSOleksandr "Alex" Zinenko      // Matcher ops are applied similarly to any other transform op.
3234cb2ef4fSOleksandr "Alex" Zinenko      DiagnosedSilenceableFailure diag =
3244cb2ef4fSOleksandr "Alex" Zinenko          state.applyTransform(cast<TransformOpInterface>(matcher));
3254cb2ef4fSOleksandr "Alex" Zinenko
3264cb2ef4fSOleksandr "Alex" Zinenko      // Definite failures are immediately propagated as they are irrecoverable.
3274cb2ef4fSOleksandr "Alex" Zinenko      if (diag.isDefiniteFailure())
3284cb2ef4fSOleksandr "Alex" Zinenko        return diag;
3294cb2ef4fSOleksandr "Alex" Zinenko
3304cb2ef4fSOleksandr "Alex" Zinenko      // On success, keep checking the remaining conditions.
3314cb2ef4fSOleksandr "Alex" Zinenko      if (diag.succeeded())
3324cb2ef4fSOleksandr "Alex" Zinenko        continue;
3334cb2ef4fSOleksandr "Alex" Zinenko
3344cb2ef4fSOleksandr "Alex" Zinenko      // Report failure-to-match for debugging purposes and stop matching this
3354cb2ef4fSOleksandr "Alex" Zinenko      // operand.
3364cb2ef4fSOleksandr "Alex" Zinenko      assert(diag.isSilenceableFailure());
3374cb2ef4fSOleksandr "Alex" Zinenko      DEBUG_MATCHER(DBGS_MATCHER()
3384cb2ef4fSOleksandr "Alex" Zinenko                    << "failed to match operand #" << operand.getOperandNumber()
3394cb2ef4fSOleksandr "Alex" Zinenko                    << ": " << diag.getMessage());
3404cb2ef4fSOleksandr "Alex" Zinenko      (void)diag.silence();
3414cb2ef4fSOleksandr "Alex" Zinenko      matchSucceeded = false;
3424cb2ef4fSOleksandr "Alex" Zinenko      break;
3434cb2ef4fSOleksandr "Alex" Zinenko    }
3444cb2ef4fSOleksandr "Alex" Zinenko    // If failed to match this operand, try other operands.
3454cb2ef4fSOleksandr "Alex" Zinenko    if (!matchSucceeded)
3464cb2ef4fSOleksandr "Alex" Zinenko      continue;
3474cb2ef4fSOleksandr "Alex" Zinenko
3484cb2ef4fSOleksandr "Alex" Zinenko    // If we reached this point, the matching succeeded for the current operand.
3494cb2ef4fSOleksandr "Alex" Zinenko    // Remap the values associated with terminator operands to be associated
3504cb2ef4fSOleksandr "Alex" Zinenko    // with op results, and also map the parameter result to the operand's
3514cb2ef4fSOleksandr "Alex" Zinenko    // position. Note that it is safe to do here despite the end of the scope
3524cb2ef4fSOleksandr "Alex" Zinenko    // as `results` are integrated into `state` by the interpreter after `apply`
3534cb2ef4fSOleksandr "Alex" Zinenko    // returns rather than immediately.
3544cb2ef4fSOleksandr "Alex" Zinenko    SmallVector<SmallVector<MappedValue>> yieldedMappings;
3554cb2ef4fSOleksandr "Alex" Zinenko    transform::detail::prepareValueMappings(
3564cb2ef4fSOleksandr "Alex" Zinenko        yieldedMappings, getBody().front().getTerminator()->getOperands(),
3574cb2ef4fSOleksandr "Alex" Zinenko        state);
3584cb2ef4fSOleksandr "Alex" Zinenko    results.setParams(getPosition().cast<OpResult>(),
3594cb2ef4fSOleksandr "Alex" Zinenko                      {rewriter.getI32IntegerAttr(operand.getOperandNumber())});
3604cb2ef4fSOleksandr "Alex" Zinenko    for (auto &&[result, mapping] : llvm::zip(getResults(), yieldedMappings))
3614cb2ef4fSOleksandr "Alex" Zinenko      results.setMappedValues(result, mapping);
3624cb2ef4fSOleksandr "Alex" Zinenko    return DiagnosedSilenceableFailure::success();
3634cb2ef4fSOleksandr "Alex" Zinenko  }
3644cb2ef4fSOleksandr "Alex" Zinenko
3654cb2ef4fSOleksandr "Alex" Zinenko  // If we reached this point, none of the operands succeeded the match.
3664cb2ef4fSOleksandr "Alex" Zinenko  return emitSilenceableError()
3674cb2ef4fSOleksandr "Alex" Zinenko         << "none of the operands satisfied the conditions";
3684cb2ef4fSOleksandr "Alex" Zinenko}
3694cb2ef4fSOleksandr "Alex" Zinenko
3704cb2ef4fSOleksandr "Alex" Zinenko```
3714cb2ef4fSOleksandr "Alex" Zinenko
3724cb2ef4fSOleksandr "Alex" Zinenko
3734cb2ef4fSOleksandr "Alex" ZinenkoBy convention, operations implementing `MatchOpInterface` must not modify
3744cb2ef4fSOleksandr "Alex" Zinenkopayload IR and must therefore specify that they only read operand handles and
3754cb2ef4fSOleksandr "Alex" Zinenkopayload as their effects.
3764cb2ef4fSOleksandr "Alex" Zinenko
3774cb2ef4fSOleksandr "Alex" Zinenko
3784cb2ef4fSOleksandr "Alex" Zinenko```cpp
3794cb2ef4fSOleksandr "Alex" Zinenkovoid transform::CollectMatchingOp::getEffects(
3804cb2ef4fSOleksandr "Alex" Zinenko    SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
3814cb2ef4fSOleksandr "Alex" Zinenko  onlyReadsHandle(getRoot(), effects);
3824cb2ef4fSOleksandr "Alex" Zinenko  producesHandle(getResults(), effects);
3834cb2ef4fSOleksandr "Alex" Zinenko  onlyReadsPayload(effects);
3844cb2ef4fSOleksandr "Alex" Zinenko}
3854cb2ef4fSOleksandr "Alex" Zinenko```
3864cb2ef4fSOleksandr "Alex" Zinenko
3874cb2ef4fSOleksandr "Alex" Zinenko
3884cb2ef4fSOleksandr "Alex" ZinenkoThis operation can now be included in a transform dialect extension, loaded and
3894cb2ef4fSOleksandr "Alex" Zinenkoused in our matcher. Specifically, we will use it to indicate that either of the
3904cb2ef4fSOleksandr "Alex" Zinenkooperands of the “max” elementwise operation in our example can be produced by
3914cb2ef4fSOleksandr "Alex" Zinenkothe previous elementwise operation. The previous operation will still require
3924cb2ef4fSOleksandr "Alex" Zinenkothe matmul to produce the first operand for simplicity. The updated matcher
3934cb2ef4fSOleksandr "Alex" Zinenkosequence looks as follows.
3944cb2ef4fSOleksandr "Alex" Zinenko
3954cb2ef4fSOleksandr "Alex" Zinenko
3964cb2ef4fSOleksandr "Alex" Zinenko```mlir
3974cb2ef4fSOleksandr "Alex" Zinenkotransform.named_sequence @match_matmul_elemwise(
3984cb2ef4fSOleksandr "Alex" Zinenko    %last: !transform.any_op {transform.readonly})
3994cb2ef4fSOleksandr "Alex" Zinenko    -> (!transform.any_op, !transform.any_op, !transform.any_op,
4004cb2ef4fSOleksandr "Alex" Zinenko        !transform.param<i32>) {
4014cb2ef4fSOleksandr "Alex" Zinenko  // The last operation must be an elementwise binary.
4024cb2ef4fSOleksandr "Alex" Zinenko  transform.match.operation_name %last ["linalg.elemwise_binary"]
4034cb2ef4fSOleksandr "Alex" Zinenko    : !transform.any_op
4044cb2ef4fSOleksandr "Alex" Zinenko
4054cb2ef4fSOleksandr "Alex" Zinenko  // One of its operands must be defined by another operation, to which we
4064cb2ef4fSOleksandr "Alex" Zinenko  // will get a handle here. This is achieved thanks to a newly defined
4074cb2ef4fSOleksandr "Alex" Zinenko  // operation that tries to match operands one by one using the match
4084cb2ef4fSOleksandr "Alex" Zinenko  // operations nested in its region.
4094cb2ef4fSOleksandr "Alex" Zinenko  %pos, %middle = transform.match.my.has_operand_satisfying %last
4104cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> (!transform.param<i32>, !transform.any_op) {
4114cb2ef4fSOleksandr "Alex" Zinenko  ^bb0(%operand: !transform.any_value):
4124cb2ef4fSOleksandr "Alex" Zinenko    // The operand must be defined by an operation.
4134cb2ef4fSOleksandr "Alex" Zinenko    %def = transform.get_defining_op %operand
4144cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_value) -> !transform.any_op
4154cb2ef4fSOleksandr "Alex" Zinenko    // The defining operation must itself be an elementwise binary.
4164cb2ef4fSOleksandr "Alex" Zinenko    transform.match.operation_name %def ["linalg.elemwise_binary"]
4174cb2ef4fSOleksandr "Alex" Zinenko      : !transform.any_op
4184cb2ef4fSOleksandr "Alex" Zinenko    transform.yield %def : !transform.any_op
4194cb2ef4fSOleksandr "Alex" Zinenko  }
4204cb2ef4fSOleksandr "Alex" Zinenko
4214cb2ef4fSOleksandr "Alex" Zinenko  // And the first operand of that operation must be defined by yet another
4224cb2ef4fSOleksandr "Alex" Zinenko  // operation.
4234cb2ef4fSOleksandr "Alex" Zinenko  %matmul = transform.get_producer_of_operand %middle[0]
4244cb2ef4fSOleksandr "Alex" Zinenko    : (!transform.any_op) -> !transform.any_op
4254cb2ef4fSOleksandr "Alex" Zinenko  // And that operation is a matmul.
4264cb2ef4fSOleksandr "Alex" Zinenko  transform.match.operation_name %matmul ["linalg.matmul"] : !transform.any_op
4274cb2ef4fSOleksandr "Alex" Zinenko  // We will yield the handles to the matmul and the two elementwise
4284cb2ef4fSOleksandr "Alex" Zinenko  // operations separately.
4294cb2ef4fSOleksandr "Alex" Zinenko  transform.yield %matmul, %middle, %last, %pos
4304cb2ef4fSOleksandr "Alex" Zinenko    : !transform.any_op, !transform.any_op, !transform.any_op,
4314cb2ef4fSOleksandr "Alex" Zinenko      !transform.param<i32>
4324cb2ef4fSOleksandr "Alex" Zinenko}
4334cb2ef4fSOleksandr "Alex" Zinenko```
4344cb2ef4fSOleksandr "Alex" Zinenko
4354cb2ef4fSOleksandr "Alex" Zinenko
4364cb2ef4fSOleksandr "Alex" ZinenkoThis achieves the desired effect and matches both `max(add(matmul(...), bias),
4374cb2ef4fSOleksandr "Alex" Zinenko0)` and `max(0, add(matmul(...), bias))` in the same values. The `%pos` value is
4384cb2ef4fSOleksandr "Alex" Zinenkoa transform dialect _parameter_, which is used to store lists of entities known
4394cb2ef4fSOleksandr "Alex" Zinenkoto be constant throughout the transform application. Most often, parameters are
4404cb2ef4fSOleksandr "Alex" Zinenkonumeric values, but they can generally be any MLIR attributes.
4414cb2ef4fSOleksandr "Alex" Zinenko
4424cb2ef4fSOleksandr "Alex" ZinenkoIn order to demonstrate that groups of operations are matched independently of
4434cb2ef4fSOleksandr "Alex" Zinenkoeach other, let us use the `transform.foreach_match` operation that allows one
4444cb2ef4fSOleksandr "Alex" Zinenkoto implement a simple high-level pattern rewriting approach within the transform
4454cb2ef4fSOleksandr "Alex" Zinenkodialect (for advanced or lower-level pattern rewriting, consider PDL(L) or C++
4464cb2ef4fSOleksandr "Alex" Zinenkorewriting APIs). It maps a matcher named sequence to an action named sequence,
4474cb2ef4fSOleksandr "Alex" Zinenkoand the latter gets invoked whenever the former succeeds.
4484cb2ef4fSOleksandr "Alex" Zinenko
4494cb2ef4fSOleksandr "Alex" Zinenko
4504cb2ef4fSOleksandr "Alex" Zinenko```mlir
4514cb2ef4fSOleksandr "Alex" Zinenko// Traverses the payload IR associated with the operand handle, invoking
4524cb2ef4fSOleksandr "Alex" Zinenko// @match_matmul_elemwise on each of the operations. If the named sequence
4534cb2ef4fSOleksandr "Alex" Zinenko// succeeds, i.e., if none of the nested match (transform) operations
4544cb2ef4fSOleksandr "Alex" Zinenko// produced a silenceable failure, invokes @print_matmul_elemwise and
4554cb2ef4fSOleksandr "Alex" Zinenko// forwards the values yielded as arguments of the new invocation. If the
4564cb2ef4fSOleksandr "Alex" Zinenko// named sequence fails with a silenceable failure, silences it (the message
4574cb2ef4fSOleksandr "Alex" Zinenko// is forwarded to the debug stream). Definite failures are propagated
4584cb2ef4fSOleksandr "Alex" Zinenko// immediately and unconditionally, as usual.
4594cb2ef4fSOleksandr "Alex" Zinenkotransform.foreach_match in %root
4604cb2ef4fSOleksandr "Alex" Zinenko  @match_matmul_elemwise -> @print_matmul_elemwise
4614cb2ef4fSOleksandr "Alex" Zinenko  : (!transform.any_op) -> !transform.any_op
4624cb2ef4fSOleksandr "Alex" Zinenko```
4634cb2ef4fSOleksandr "Alex" Zinenko
4644cb2ef4fSOleksandr "Alex" Zinenko
4654cb2ef4fSOleksandr "Alex" ZinenkoThe `@print_matmul_elemwise` named sequence, available in `multiple.mlir`, will
4664cb2ef4fSOleksandr "Alex" Zinenkouse the parameter with the position of the operand to differentiate the two
4674cb2ef4fSOleksandr "Alex" Zinenkogroups.
4684cb2ef4fSOleksandr "Alex" Zinenko
4694cb2ef4fSOleksandr "Alex" Zinenko
4704cb2ef4fSOleksandr "Alex" Zinenko## Matchers for Inferred Features
4714cb2ef4fSOleksandr "Alex" Zinenko
4724cb2ef4fSOleksandr "Alex" ZinenkoThe matcher sequences described above, although useful to drive transformations
4734cb2ef4fSOleksandr "Alex" Zinenkofrom within the transform dialect interpreter, are rather basic since they
4744cb2ef4fSOleksandr "Alex" Zinenkomostly rely on operation names and use-def chains. Alternative implementations
4754cb2ef4fSOleksandr "Alex" Zinenkousing APIs or various declarative rewrite rules are barely less expressive and
4764cb2ef4fSOleksandr "Alex" Zinenkosometimes more concise. The real power of transform dialect matcher ops lies in
4774cb2ef4fSOleksandr "Alex" Zinenkothe possibility to define matchers of _inferred properties_ of payloads, i.e.,
4784cb2ef4fSOleksandr "Alex" Zinenkoproperties that are not directly accessible as an attribute of an operation or
4794cb2ef4fSOleksandr "Alex" Zinenkoany straightforward relation between IR components.
4804cb2ef4fSOleksandr "Alex" Zinenko
4814cb2ef4fSOleksandr "Alex" ZinenkoThe utility of such matchers can be easily demonstrated by slightly modifying
4824cb2ef4fSOleksandr "Alex" Zinenkoour original example. If matrix multiplication is expressed as a special case of
4834cb2ef4fSOleksandr "Alex" Zinenkotensor contraction using `linalg.generic` instead of `linalg.matmul`, the
4844cb2ef4fSOleksandr "Alex" Zinenkooperation name-based matcher no longer applies. Yet such a representation is
4854cb2ef4fSOleksandr "Alex" Zinenkovery common and can appear both in the original input and during the course of
4864cb2ef4fSOleksandr "Alex" Zinenkotransformation, e.g., where a higher-dimensional contraction is decomposed into
4874cb2ef4fSOleksandr "Alex" Zinenkoloops around a matrix multiplication.
4884cb2ef4fSOleksandr "Alex" Zinenko
4894cb2ef4fSOleksandr "Alex" ZinenkoIn order to be a (potentially transposed) matrix multiplication, the
4904cb2ef4fSOleksandr "Alex" Zinenko`linalg.generic` operation must have the following features:
4914cb2ef4fSOleksandr "Alex" Zinenko
4924cb2ef4fSOleksandr "Alex" Zinenko
4934cb2ef4fSOleksandr "Alex" Zinenko
4944cb2ef4fSOleksandr "Alex" Zinenko*   Total rank of 3.
4954cb2ef4fSOleksandr "Alex" Zinenko*   Two inputs accessed as projected permutation of iteration dimensions.
4964cb2ef4fSOleksandr "Alex" Zinenko*   One output accessed as projected permutation of iteration dimensions.
4974cb2ef4fSOleksandr "Alex" Zinenko*   Iteration dimensions can be subdivided into LHS parallel, RHS parallel and reduction dimensions.
4984cb2ef4fSOleksandr "Alex" Zinenko*   The body block consists of a multiplication and an addition.
4994cb2ef4fSOleksandr "Alex" Zinenko
5004cb2ef4fSOleksandr "Alex" ZinenkoMost of these features can be derived from the properties of the operation,
5014cb2ef4fSOleksandr "Alex" Zinenkoe.g., the total rank corresponds to the number of entries in the `iterators`
5024cb2ef4fSOleksandr "Alex" Zinenkoattribute, but almost none of them are immediately accessible in the IR or in
5034cb2ef4fSOleksandr "Alex" Zinenkoany declarative form, which is usually limited to checking the presence or the
5044cb2ef4fSOleksandr "Alex" Zinenkoexact match of an attribute or a type.  The transform dialect allows these
5054cb2ef4fSOleksandr "Alex" Zinenkofeatures to be implemented in the `apply` method of a matcher op and reused
5064cb2ef4fSOleksandr "Alex" Zinenkoacross multiple matching cases. For structured linear algebra payload
5074cb2ef4fSOleksandr "Alex" Zinenkooperations, many such match operations are readily available in the `structured`
5084cb2ef4fSOleksandr "Alex" Zinenkoextension. They are sufficient to implement a matrix multiplication matcher
5094cb2ef4fSOleksandr "Alex" Zinenkousing the features listed above almost verbatim.
5104cb2ef4fSOleksandr "Alex" Zinenko
5114cb2ef4fSOleksandr "Alex" Zinenko
5124cb2ef4fSOleksandr "Alex" Zinenko```mlir
5134cb2ef4fSOleksandr "Alex" Zinenkotransform.named_sequence @match_generic_matmul(
5144cb2ef4fSOleksandr "Alex" Zinenko    %candidate: !transform.any_op {transform.readonly}) -> !transform.any_op {
5154cb2ef4fSOleksandr "Alex" Zinenko  // Match a structured linear algebra operation.
5164cb2ef4fSOleksandr "Alex" Zinenko  transform.match.structured %candidate : !transform.any_op {
5174cb2ef4fSOleksandr "Alex" Zinenko  ^bb0(%c: !transform.any_op):
5184cb2ef4fSOleksandr "Alex" Zinenko    // With a rank equal to 3.
5194cb2ef4fSOleksandr "Alex" Zinenko    %rank = transform.match.structured.rank %c
5204cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> !transform.param<i64>
5214cb2ef4fSOleksandr "Alex" Zinenko    %c3 = transform.param.constant 3 : i64 -> !transform.param<i64>
5224cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %rank, %c3 : !transform.param<i64>
5234cb2ef4fSOleksandr "Alex" Zinenko
5244cb2ef4fSOleksandr "Alex" Zinenko    // With 2 inputs.
5254cb2ef4fSOleksandr "Alex" Zinenko    %n_ins = transform.match.structured.num_inputs %c
5264cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> !transform.param<i64>
5274cb2ef4fSOleksandr "Alex" Zinenko    %c2 = transform.param.constant 2 : i64 -> !transform.param<i64>
5284cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_ins, %c2 : !transform.param<i64>
5294cb2ef4fSOleksandr "Alex" Zinenko
5304cb2ef4fSOleksandr "Alex" Zinenko    // With 1 output (note that structured ops in destination passing style
5314cb2ef4fSOleksandr "Alex" Zinenko    // has as many inits as outputs).
5324cb2ef4fSOleksandr "Alex" Zinenko    %n_inits = transform.match.structured.num_inits %c
5334cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op) -> !transform.param<i64>
5344cb2ef4fSOleksandr "Alex" Zinenko    %c1 = transform.param.constant 1 : i64 -> !transform.param<i64>
5354cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_inits, %c1 : !transform.param<i64>
5364cb2ef4fSOleksandr "Alex" Zinenko
5374cb2ef4fSOleksandr "Alex" Zinenko    // All inputs and inits are accessed with a projected permutation.
5384cb2ef4fSOleksandr "Alex" Zinenko    transform.match.structured.input %c[all] {projected_permutation}
5394cb2ef4fSOleksandr "Alex" Zinenko      : !transform.any_op
5404cb2ef4fSOleksandr "Alex" Zinenko    transform.match.structured.init %c[0] {projected_permutation}
5414cb2ef4fSOleksandr "Alex" Zinenko      : !transform.any_op
5424cb2ef4fSOleksandr "Alex" Zinenko
5434cb2ef4fSOleksandr "Alex" Zinenko    // The body is a mulf/addf contraction with appropriate dimensions.
5444cb2ef4fSOleksandr "Alex" Zinenko    transform.match.structured.body %c
5454cb2ef4fSOleksandr "Alex" Zinenko      { contraction = ["arith.mulf", "arith.addf"] } : !transform.any_op
5464cb2ef4fSOleksandr "Alex" Zinenko    %batch, %lhs, %rhs, %reduction =
5474cb2ef4fSOleksandr "Alex" Zinenko    transform.match.structured.classify_contraction_dims %c
5484cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.any_op)
5494cb2ef4fSOleksandr "Alex" Zinenko      -> (!transform.param<i64>, !transform.param<i64>, !transform.param<i64>,
5504cb2ef4fSOleksandr "Alex" Zinenko          !transform.param<i64>)
5514cb2ef4fSOleksandr "Alex" Zinenko
5524cb2ef4fSOleksandr "Alex" Zinenko
5534cb2ef4fSOleksandr "Alex" Zinenko    // There is one of lhs, rhs and reduction dimensions and zero batch
5544cb2ef4fSOleksandr "Alex" Zinenko    // dimensions.
5554cb2ef4fSOleksandr "Alex" Zinenko    %n_batch = transform.num_associations %batch
5564cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.param<i64>) -> !transform.param<i64>
5574cb2ef4fSOleksandr "Alex" Zinenko    %n_lhs = transform.num_associations %lhs
5584cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.param<i64>) -> !transform.param<i64>
5594cb2ef4fSOleksandr "Alex" Zinenko    %n_rhs = transform.num_associations %rhs
5604cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.param<i64>) -> !transform.param<i64>
5614cb2ef4fSOleksandr "Alex" Zinenko    %n_reduction = transform.num_associations %reduction
5624cb2ef4fSOleksandr "Alex" Zinenko      : (!transform.param<i64>) -> !transform.param<i64>
5634cb2ef4fSOleksandr "Alex" Zinenko    %c0 = transform.param.constant 0 : i64 -> !transform.param<i64>
5644cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_batch, %c0 : !transform.param<i64>
5654cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_lhs, %c1 : !transform.param<i64>
5664cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_rhs, %c1 : !transform.param<i64>
5674cb2ef4fSOleksandr "Alex" Zinenko    transform.match.param.cmpi eq %n_reduction, %c1 : !transform.param<i64>
5684cb2ef4fSOleksandr "Alex" Zinenko  }
5694cb2ef4fSOleksandr "Alex" Zinenko  transform.yield %candidate : !transform.any_op
5704cb2ef4fSOleksandr "Alex" Zinenko}
5714cb2ef4fSOleksandr "Alex" Zinenko```
5724cb2ef4fSOleksandr "Alex" Zinenko
5734cb2ef4fSOleksandr "Alex" Zinenko
5744cb2ef4fSOleksandr "Alex" ZinenkoWhile this example leverages the contraction-specific matchers that have a
5754cb2ef4fSOleksandr "Alex" Zinenkorather non-trivial C++ implementation, the transform dialect is sufficiently
5764cb2ef4fSOleksandr "Alex" Zinenkoflexible to implement this reasoning directly if desired. One could, for
5774cb2ef4fSOleksandr "Alex" Zinenkoexample, obtain the access map of each input as a parameter and extract the
5784cb2ef4fSOleksandr "Alex" Zinenkoaccessed dimensions as other parameters that can be compared with each other to
5794cb2ef4fSOleksandr "Alex" Zinenkoensure the subscripts are `m,k` for LHS, `k,n` for RHS and `m,n` for the
5804cb2ef4fSOleksandr "Alex" Zinenkoinit/result given the `m,n,k` notation for loops.
5814cb2ef4fSOleksandr "Alex" Zinenko
5820c13a896SKohei Yamaguchi## Appendix: Autogenerated Documentation
5830c13a896SKohei Yamaguchi
5840c13a896SKohei Yamaguchi[include "Tutorials/transform/MyExtensionCh4.md"]
5850c13a896SKohei Yamaguchi
586