| //===- LinalgOps.td - Linalg dialect ops -------------------*- tablegen -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This is the operation definition file for linear algebra operations. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LINALG_OPS |
| #define LINALG_OPS |
| |
| include "mlir/Dialect/Linalg/IR/LinalgBase.td" |
| include "mlir/Interfaces/ControlFlowInterfaces.td" |
| include "mlir/Interfaces/InferTypeOpInterface.td" |
| include "mlir/Interfaces/LoopLikeInterface.td" |
| include "mlir/Interfaces/SideEffectInterfaces.td" |
| include "mlir/Interfaces/TilingInterface.td" |
| include "mlir/Interfaces/ViewLikeInterface.td" |
| |
| // Base class for Linalg dialect ops that do not correspond to library calls. |
| class Linalg_Op<string mnemonic, list<OpTrait> traits = []> : |
| Op<Linalg_Dialect, mnemonic, traits> { |
| // For every linalg op, there needs to be a: |
| // * void print(OpAsmPrinter &p, ${C++ class of Op} op) |
| // * LogicalResult verify(${C++ class of Op} op) |
| // * ParseResult parse${C++ class of Op}(OpAsmParser &parser, |
| // OperationState &result) |
| // functions. |
| let printer = [{ return ::print(p, *this); }]; |
| let verifier = [{ return ::verify(*this); }]; |
| let parser = [{ return ::parse$cppClass(parser, result); }]; |
| } |
| |
| def Linalg_InitTensorOp : Linalg_Op<"init_tensor", |
| [NoSideEffect, |
| DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>]> { |
| let summary = "operation to define a tensor of particular value"; |
| |
| let description = [{ |
| `linalg.init_tensor` is an operation that materializes a tensor of |
| a given shape. The shape could be dynamic or static. |
| }]; |
| |
| let arguments = |
| (ins Variadic<Index>:$sizes, I64ArrayAttr:$static_sizes); |
| |
| let results = (outs AnyTensor:$result); |
| |
| let assemblyFormat = [{ |
| custom<OperandsOrIntegersSizesList>($sizes, $static_sizes) attr-dict |
| `:` type($result) |
| }]; |
| |
| let verifier = [{ return ::verify(*this); }]; |
| |
| let extraClassDeclaration = [{ |
| static StringRef getStaticSizesAttrName() { |
| return "static_sizes"; |
| } |
| |
| RankedTensorType getType() { |
| return getResult().getType().cast<RankedTensorType>(); } |
| |
| // Infer the shape of the result tensor given the static shapes |
| // and element type of the result tensor. |
| static Type inferResultType(ArrayRef<int64_t> staticSizes, Type elementType); |
| |
| // Return true if the size of the tensor is dynamic at `idx` |
| bool isDynamicSize(unsigned idx) { |
| APInt v = *(static_sizes().getAsValueRange<IntegerAttr>().begin() + idx); |
| return ShapedType::isDynamic(v.getSExtValue()); |
| } |
| |
| // Assert that the size of the result tensor is static at `idx` |
| // and return the shape. |
| int64_t getStaticSize(unsigned idx) { |
| assert(!isDynamicSize(idx) && "expected static size"); |
| APInt v = *(static_sizes(). |
| template getAsValueRange<IntegerAttr>().begin() + idx); |
| return v.getSExtValue(); |
| } |
| |
| // Return the argument position that contains the dynamic size of |
| // the tensor at dimension `idx`. Asserts that the shape is |
| // dynamic at that `idx`. |
| unsigned getIndexOfDynamicSize(unsigned idx) { |
| assert(isDynamicSize(idx) && "expected dynamic size"); |
| return std::count_if( |
| static_sizes().getValue().begin(), |
| static_sizes().getValue().begin() + idx, |
| [&](Attribute attr) { |
| return ShapedType::isDynamic(attr.cast<IntegerAttr>().getInt()); |
| }); |
| } |
| |
| // Return the Value of the dynamic size of the tensor at dimension |
| // `idx`. Asserts that the shape is dynamic at that `idx. |
| Value getDynamicSize(unsigned idx) { |
| return getOperand(getIndexOfDynamicSize(idx)); |
| } |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins "ValueRange":$shape, |
| "ArrayRef<int64_t>":$staticShape, "Type":$elementType), |
| [{ |
| build($_builder, $_state, |
| InitTensorOp::inferResultType(staticShape, elementType), |
| shape, $_builder.getI64ArrayAttr(staticShape)); |
| }]>, |
| OpBuilder<(ins "ValueRange":$shape, "Type":$elementType), |
| [{ |
| SmallVector<int64_t, 4> staticShape( |
| shape.size(), ShapedType::kDynamicSize); |
| build($_builder, $_state, shape, staticShape, elementType); |
| }]>, |
| OpBuilder<(ins "ArrayRef<int64_t>":$staticShape, "Type":$elementType), |
| [{ |
| build($_builder, $_state, ValueRange{}, staticShape, elementType); |
| }]>, |
| OpBuilder<(ins "ArrayRef<OpFoldResult>":$sizes, "Type":$elementType, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)> |
| ]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Linalg_PadTensorOp : Linalg_Op<"pad_tensor", |
| [AttrSizedOperandSegments, NoSideEffect, |
| DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>, |
| DeclareOpInterfaceMethods<TilingInterface, |
| ["getDestinationOperands", "getLoopIteratorTypes", "getLoopBounds", |
| "getTiledImplementation"]>]> { |
| let summary = "tensor pad operation"; |
| let description = [{ |
| `linalg.pad_tensor` is an operation that pads the `source` tensor |
| with given `low` and `high` padding config. |
| |
| The PadTensor operation supports the following arguments: |
| |
| * source: the "base" tensor on which to pad. |
| * low: A list contains the padding along the start of each |
| dimension, i.e `low`. |
| * high: A list contains the padding along the end of each |
| dimension, i.e. `high`. |
| * nofold: indicates that the operation should not be folded when source and |
| result types are equal. |
| |
| The result tensor dimensions are `low` + `dim` + `high` along that |
| dimension. The number of elements of `low` and `high` must match |
| the rank of the input tensor. They can be either a constant or a |
| dynamic value. |
| |
| The region of the `pad_tensor` operation returns the value to use |
| for the padding. The arguments of the region represent the index |
| of the source being accessed. There should be as many arguments as |
| the rank of the `source` tensor. The value `yield`-ed by the |
| region is used as the value of the view at the given position. |
| |
| If `nofold` is set, the padding operation will not be folded away even |
| if the source type and the padded type have the same static shape. This can |
| be used, e.g., for packing or promotion to faster memory. |
| |
| Example 1: |
| |
| ```mlir |
| %pad_value = ... : f32 |
| %0 = linalg.pad_tensor %0 low[1, 2] high[2, 3] { |
| ^bb0(%arg0 : index, %arg1 : index): |
| linalg.yield %pad_value : f32 |
| } : tensor<?x?xf32> to tensor<?x?xf32> |
| ``` |
| |
| Example 2: |
| |
| ```mlir |
| %pad_value = ... : f32 |
| %0 = linalg.pad_tensor %arg0 low[2, %arg1, 3, 3] high[3, 3, %arg1, 2] { |
| ^bb0(%arg2: index, %arg3: index, %arg4: index, %arg5: index): |
| linalg.yield %pad_value : f32 |
| } : tensor<1x2x2x?xf32> to tensor<6x?x?x?xf32> |
| ``` |
| |
| Example 3: |
| |
| ```mlir |
| %pad_value = ... : f32 |
| %0 = linalg.pad_tensor %arg0 low[0, 0] high[%ub0, %ub1] { |
| ^bb0(%arg1: index, %arg2: index): |
| linalg.yield %pad_value : f32 |
| } : tensor<2x3xf32> to tensor<?x?xf32> |
| ``` |
| |
| Example 4: |
| |
| ```mlir |
| // Force a padded value to be always exist with `nofold`. |
| %pad_value = ... : f32 |
| %0 = linalg.pad_tensor %arg0 nofold low[0, 0] high[0, 0] { |
| ^bb0(%arg1: index, %arg2: index): |
| linalg.yield %pad_value : f32 |
| } : tensor<2x3xf32> to tensor<2x3xf32> |
| ``` |
| }]; |
| |
| let arguments = (ins |
| AnyTensor:$source, |
| Variadic<Index>:$low, |
| Variadic<Index>:$high, |
| I64ArrayAttr:$static_low, |
| I64ArrayAttr:$static_high, |
| UnitAttr:$nofold); |
| |
| let regions = (region SizedRegion<1>:$region); |
| |
| let results = (outs AnyTensor:$result); |
| |
| // TODO: Remove custom<InferType> when AllTypesMatch supports opt. operands. |
| let assemblyFormat = [{ |
| $source |
| (`nofold` $nofold^)? |
| `low` `` custom<OperandsOrIntegersSizesList>($low, $static_low) |
| `high` `` custom<OperandsOrIntegersSizesList>($high, $static_high) |
| $region attr-dict `:` type($source) `to` type($result) |
| }]; |
| |
| let extraClassDeclaration = [{ |
| static StringRef getStaticLowAttrName() { |
| return "static_low"; |
| } |
| |
| static StringRef getStaticHighAttrName() { |
| return "static_high"; |
| } |
| |
| RankedTensorType getSourceType() { |
| return source().getType().cast<RankedTensorType>(); |
| } |
| RankedTensorType getResultType() { |
| return getResult().getType().cast<RankedTensorType>(); |
| } |
| |
| // Infer the shape of the result tensor given the type of the source tensor |
| // and paddings. Known result dimensions that cannot necessarily be inferred |
| // from low/high padding sizes can be optionally specified. Those will be |
| // considered when computing the result type. |
| static RankedTensorType inferResultType( |
| RankedTensorType sourceType, |
| ArrayRef<int64_t> staticLow, |
| ArrayRef<int64_t> staticHigh, |
| ArrayRef<int64_t> resultShape = {}); |
| |
| // Return a PadTensorOp that pads `source` to `type` size where the static |
| // sizes are assumed to be greater than the dynamic sizes. The op performs |
| // "high" padding (i.e. it adds trailing padding values until the desired |
| // size is met). |
| static linalg::PadTensorOp createPadHighOp( |
| Type type, Value source, Value pad, bool nofold, Location loc, |
| OpBuilder & builder); |
| |
| // Return a PadTensorOp that pads `source to `type` size with `pad` value. |
| // I.e., a block will be created and the `pad` value will be yielded |
| // directly. If the type passed is nullptr, it is inferred. |
| static linalg::PadTensorOp createPadScalarOp( |
| Type type, Value source, Value pad, ArrayRef<OpFoldResult> low, |
| ArrayRef<OpFoldResult> high, bool nofold, Location loc, |
| OpBuilder & builder); |
| |
| // Return the pad value if it is a constant. Return null value otherwise. |
| Value getConstantPaddingValue(); |
| |
| // Return a vector of all the static or dynamic values (low/high padding) of |
| // the op. |
| inline SmallVector<OpFoldResult> getMixedPadImpl(ArrayAttr staticAttrs, |
| ValueRange values) { |
| SmallVector<OpFoldResult> res; |
| unsigned numDynamic = 0; |
| unsigned count = staticAttrs.size(); |
| for (unsigned idx = 0; idx < count; ++idx) { |
| if (ShapedType::isDynamic(staticAttrs[idx].cast<IntegerAttr>().getInt())) |
| res.push_back(values[numDynamic++]); |
| else |
| res.push_back(staticAttrs[idx]); |
| } |
| return res; |
| } |
| SmallVector<OpFoldResult> getMixedLowPad() { |
| return getMixedPadImpl(static_low(), low()); |
| } |
| SmallVector<OpFoldResult> getMixedHighPad() { |
| return getMixedPadImpl(static_high(), high()); |
| } |
| // Return true if low padding is guaranteed to be 0. |
| bool hasZeroLowPad() { |
| return llvm::all_of(getMixedLowPad(), [](OpFoldResult ofr) { |
| return getConstantIntValue(ofr) == static_cast<int64_t>(0); |
| }); |
| } |
| // Return true if high padding is guaranteed to be 0. |
| bool hasZeroHighPad() { |
| return llvm::all_of(getMixedHighPad(), [](OpFoldResult ofr) { |
| return getConstantIntValue(ofr) == static_cast<int64_t>(0); |
| }); |
| } |
| }]; |
| |
| let builders = [ |
| // Build a PadTensorOp with mixed static and dynamic entries. |
| OpBuilder<(ins "Value":$source, "ArrayRef<int64_t>":$staticLow, |
| "ArrayRef<int64_t>":$staticHigh, "ValueRange":$low, "ValueRange":$high, |
| CArg<"bool", "false">:$nofold, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, |
| // Build a PadTensorOp with all dynamic entries. |
| OpBuilder<(ins "Value":$source, "ValueRange":$low, "ValueRange":$high, |
| CArg<"bool", "false">:$nofold, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, |
| // Build a PadTensorOp with mixed static and dynamic entries and custom |
| // result type. If the type passed is nullptr, it is inferred. |
| OpBuilder<(ins "Type":$resultType, "Value":$source, |
| "ArrayRef<OpFoldResult>":$low, "ArrayRef<OpFoldResult>":$high, |
| CArg<"bool", "false">:$nofold, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, |
| ]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Linalg_RangeOp : |
| Linalg_Op<"range", [NoSideEffect]>, |
| Arguments<(ins Index:$min, Index:$max, Index:$step)>, |
| Results<(outs Range)> { |
| let summary = "Create a `range` type value, used to create `view`s"; |
| let description = [{ |
| The `linalg.range` op creates a `!linalg.range` from 3 values of type |
| `index` that represent the min, max and step values of the `range`. This |
| type does not pass function boundaries at the moment. |
| |
| Example: |
| |
| ```mlir |
| %3 = linalg.range %0:%1:%2 : !linalg.range |
| ```` |
| }]; |
| let builders = [ |
| OpBuilder<(ins "Value":$min, "Value":$max, "Value":$step), |
| [{ |
| auto rangeType = RangeType::get($_builder.getContext()); |
| build($_builder, $_state, rangeType, min, max, step); |
| }]>]; |
| |
| // Fully specified by traits. |
| let verifier = ?; |
| let assemblyFormat = "$min `:` $max `:` $step attr-dict `:` type(results)"; |
| } |
| |
| class Linalg_ReshapeLikeOp<string mnemonic, list<OpTrait> traits = []> : |
| Linalg_Op<mnemonic, !listconcat(traits, [NoSideEffect])> { |
| let builders = [ |
| // Builders for a contracting reshape whose result type is computed from |
| // `src` and `reassociation`. |
| OpBuilder<(ins "Value":$src, |
| "ArrayRef<ReassociationIndices>":$reassociation, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, |
| OpBuilder<(ins "Value":$src, |
| "ArrayRef<ReassociationExprs>":$reassociation, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), |
| [{ |
| auto reassociationMaps = |
| convertReassociationMapsToIndices($_builder, reassociation); |
| build($_builder, $_state, src, reassociationMaps, attrs); |
| }]>, |
| |
| // Builders for a reshape whose result type is passed explicitly. This may |
| // be either a contracting or expanding reshape. |
| OpBuilder<(ins "Type":$resultType, "Value":$src, |
| "ArrayRef<ReassociationIndices>":$reassociation, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), |
| [{ |
| build($_builder, $_state, resultType, src, attrs); |
| $_state.addAttribute("reassociation", |
| getReassociationIndicesAttribute($_builder, reassociation)); |
| }]>, |
| OpBuilder<(ins "Type":$resultType, "Value":$src, |
| "ArrayRef<ReassociationExprs>":$reassociation, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), |
| [{ |
| auto reassociationMaps = |
| convertReassociationMapsToIndices($_builder, reassociation); |
| build($_builder, $_state, resultType, src, reassociationMaps, attrs); |
| }]> |
| ]; |
| |
| code commonExtraClassDeclaration = [{ |
| static StringRef getReassociationAttrName() { return "reassociation"; } |
| SmallVector<AffineMap, 4> getReassociationMaps(); |
| SmallVector<ReassociationExprs, 4> getReassociationExprs(); |
| SmallVector<ReassociationIndices, 4> getReassociationIndices() { |
| SmallVector<ReassociationIndices, 4> reassociationIndices; |
| for (auto attr : reassociation()) |
| reassociationIndices.push_back(llvm::to_vector<2>( |
| llvm::map_range(attr.cast<ArrayAttr>(), [&](Attribute indexAttr) { |
| return indexAttr.cast<IntegerAttr>().getInt(); |
| }))); |
| return reassociationIndices; |
| }; |
| }]; |
| |
| let parser = [{ return ::parseReshapeLikeOp(parser, result); }]; |
| } |
| |
| def IndexListArrayAttr : |
| TypedArrayAttrBase<I64ArrayAttr, "Array of 64-bit integer array attributes">; |
| |
| class Linalg_TensorReshapeOp<string mnemonic> : Linalg_ReshapeLikeOp< |
| mnemonic, |
| [DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>]>, |
| Arguments<(ins AnyTensor:$src, |
| IndexListArrayAttr:$reassociation)>, |
| Results<(outs AnyTensor:$result)> { |
| let extraClassDeclaration = commonExtraClassDeclaration # [{ |
| RankedTensorType getSrcType() { |
| return src().getType().cast<RankedTensorType>(); |
| } |
| RankedTensorType getResultType() { |
| return result().getType().cast<RankedTensorType>(); |
| } |
| }]; |
| let hasFolder = 1; |
| let hasCanonicalizer = 1; |
| let printer = [{ return ::print(p, *this); }]; |
| let parser = [{ return ::parseReshapeLikeOp(parser, result); }]; |
| } |
| |
| def Linalg_TensorExpandShapeOp : Linalg_TensorReshapeOp<"tensor_expand_shape"> { |
| let summary = "operation to produce a tensor with a higher rank"; |
| let description = [{ |
| The `linalg.tensor_expand_shape` op produces a new tensor with a higher |
| rank whose sizes are a reassociation of the original `src`. |
| |
| A reassociation is defined as a continuous grouping of dimensions and is |
| represented with an array of I64ArrayAttr attribute. |
| |
| The verification rule is that the reassociation maps are applied to the |
| result tensor with the higher rank to obtain the operand tensor with the |
| smaller rank. |
| |
| The operand tensor type of a reshape can be zero-ranked if the result |
| tensor type is statically shaped with all dimensions being unit extent. In |
| such cases the reassociation map is empty. |
| |
| Examples: |
| |
| ```mlir |
| // Dimension expansion i -> (i', j') and (k) -> (k') |
| %b = linalg.tensor_expand_shape %a [[0, 1], [2]] |
| : tensor<?x?xf32> into tensor<?x?x?xf32> |
| ``` |
| }]; |
| } |
| |
| def Linalg_TensorCollapseShapeOp : Linalg_TensorReshapeOp<"tensor_collapse_shape"> { |
| let summary = "operation to produce a tensor with a smaller rank"; |
| let description = [{ |
| The `linalg.tensor_collapse_shape` op produces a new tensor with a smaller |
| rank whose sizes are a reassociation of the original `src`. |
| |
| A reassociation is defined as a continuous grouping of dimensions and is |
| represented with an array of I64ArrayAttr attribute. |
| |
| The verification rule is that the reassociation maps are applied to the |
| operand tensor with the higher rank to obtain the result tensor with the |
| smaller rank. |
| |
| The result tensor type of a reshape can be zero-ranked if the operand |
| tensor type is statically shaped with all dimensions being unit extent. In |
| such case the reassociation map is empty. |
| |
| Examples: |
| |
| ```mlir |
| // Dimension collapse (i, j) -> i' and k -> k' |
| %b = linalg.tensor_collapse_shape %a [[0, 1], [2]] |
| : tensor<?x?x?xf32> into tensor<?x?xf32> |
| ``` |
| }]; |
| } |
| |
| def Linalg_YieldOp : Linalg_Op<"yield", [NoSideEffect, ReturnLike, Terminator]>, |
| Arguments<(ins Variadic<AnyType>:$values)> { |
| let summary = "Linalg yield operation"; |
| let description = [{ |
| `linalg.yield` is a special terminator operation for blocks inside regions |
| in `linalg` generic ops. It returns values to the immediately enclosing |
| `linalg` generic op. |
| |
| Example: |
| |
| ```mlir |
| linalg.yield %f0, %f1 : f32, f32 |
| ``` |
| }]; |
| let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; |
| } |
| |
| def Linalg_TiledLoopOp : Linalg_Op<"tiled_loop", [ |
| AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<LoopLikeOpInterface>, |
| RecursiveSideEffects, |
| SingleBlockImplicitTerminator<"linalg::YieldOp"> |
| ]> { |
| let summary = "Linalg tiled loop operation"; |
| let description = [{ |
| This is a loop-like operation with additional properties. The arguments |
| also include the input and the output tensors or memrefs and the attributes |
| to specify the iterator types. |
| |
| Parsing TiledLoopOp will set all elements of the `iterator_types` attribute |
| to "parallel" type, when it is absent from the custom format. |
| |
| Tensor-based version: |
| |
| The body region of the loop contains `extract_slice` operations applied to |
| every tensor argument of TiledLoopOp. |
| |
| The body region must contain exactly one block that terminates with |
| `linalg.yield` with the operands resulting from `insert_slice` operations. |
| |
| Example: |
| |
| ```mlir |
| %0 = linalg.tiled_loop (%i) = (%c0) to (%c24) step (%c4) |
| ins(%lhs, %rhs : tensor<24x64xi8>, tensor<24x64xi8>) |
| outs(%out : tensor<24x64xi8>) |
| iterators("parallel") |
| distribution("block_x") { |
| %lhs_sub = tensor.extract_slice %lhs[%i, 0] [%c4, %c64] [1, 1] |
| : tensor<24x64xi8> to tensor<?x?xi8> |
| %rhs_sub = tensor.extract_slice %rhs[%i, 0] [%c4, %c64] [1, 1] |
| : tensor<24x64xi8> to tensor<?x?xi8> |
| %out_sub = tensor.extract_slice %out[%i, 0] [%c4, %c64] [1, 1] |
| : tensor<24x64xi8> to tensor<?x?xi8> |
| |
| %result_sub = linalg.generic ... |
| |
| %result = tensor.insert_slice %result_sub into %out[%i, 0][%c4, %c64][1, 1] |
| : tensor<?x?xi8> into tensor<24x64xi8> |
| linalg.yield %result : tensor<24x64xi8> |
| } |
| ``` |
| |
| MemRef-based version: |
| |
| The body region of the loop contains `subview` operations applied to |
| every memref argument of TiledLoopOp. |
| |
| The body region must contain exactly one block that terminates with |
| `linalg.yield` with no operands. |
| |
| Example: |
| |
| ```mlir |
| linalg.tiled_loop (%i) = (%c0) to (%c24) step (%c4) |
| ins(%lhs, %rhs : memref<24x64xi8>, memref<24x64xi8>) |
| outs(%out : memref<24x64xi8>) |
| iterators("parallel") |
| distribution("block_x") { |
| %lhs_sub = subview %lhs[%i, 0] [%c4, %c64] [1, 1] |
| : memref<24x64xi8> to memref<?x?xi8> |
| %rhs_sub = subview %rhs[%i, 0] [%c4, %c64] [1, 1] |
| : memref<24x64xi8> to memref<?x?xi8> |
| %out_sub = subview %out[%i, 0] [%c4, %c64] [1, 1] |
| : memref<24x64xi8> to memref<?x?xi8> |
| |
| %result_sub = linalg.generic ... |
| linalg.yield |
| } |
| ``` |
| }]; |
| |
| let arguments = (ins Variadic<Index>:$lowerBound, |
| Variadic<Index>:$upperBound, |
| Variadic<Index>:$step, |
| Variadic<AnyType>:$inputs, |
| Variadic<AnyShaped>:$outputs, |
| ArrayAttr:$iterator_types, |
| OptionalAttr<ArrayAttr>:$distribution_types); |
| let results = (outs Variadic<AnyRankedTensor>:$results); |
| let regions = (region SizedRegion<1>:$region); |
| |
| let builders = [ |
| OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds, |
| "ValueRange":$steps, "ValueRange":$inputs, "ValueRange":$outputs, |
| "ArrayAttr":$iteratorTypes, "Optional<ArrayAttr>":$distributionTypes, |
| CArg<"function_ref<void (OpBuilder &, Location, /*ivs=*/ValueRange," |
| "/*inputs=*/ValueRange, /*outputs=*/ValueRange)>", |
| "nullptr">:$bodyBuilderFn)>, |
| OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds, |
| "ValueRange":$steps, "ValueRange":$inputs, "ValueRange":$outputs, |
| "ArrayAttr":$iteratorTypes, |
| CArg<"function_ref<void (OpBuilder &, Location, /*ivs=*/ValueRange," |
| "/*inputs=*/ValueRange, /*outputs=*/ValueRange)>", |
| "nullptr">:$bodyBuilderFn)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| /// Number of loops |
| unsigned getNumLoops() { return step().size(); } |
| |
| /// Number of input operands |
| unsigned getNumInputs() { return inputs().size(); } |
| |
| /// Number of output operands |
| unsigned getNumOutputs() { return outputs().size(); } |
| |
| /// Number of operands controlling the loop: lbs, ubs, steps |
| unsigned getNumControlOperands() { return 3 * getNumLoops(); } |
| |
| ValueRange getInductionVars() { |
| return getBody()->getArguments().take_front(getNumLoops()); |
| } |
| ValueRange getRegionInputArgs() { |
| return getBody()->getArguments().slice(getNumLoops(), inputs().size()); |
| } |
| ValueRange getRegionOutputArgs() { |
| return getBody()->getArguments().take_back(outputs().size()); |
| } |
| |
| void setDistributionTypes(Builder& b, ArrayRef<StringRef> types) { |
| assert(types.size() == getNumLoops() && |
| "expected distribution type for every dimension"); |
| distribution_typesAttr(b.getStrArrayAttr(types)); |
| } |
| |
| void setLowerBounds(ValueRange lowerBounds) { |
| unsigned numLoops = getNumLoops(); |
| assert(lowerBounds.size() == numLoops && |
| "expected lower bounds for every loop dimension"); |
| for (unsigned i = 0; i < numLoops; ++i) |
| setOperand(i, lowerBounds[i]); |
| } |
| |
| void setUpperBounds(ValueRange upperBounds) { |
| unsigned numLoops = getNumLoops(); |
| assert(upperBounds.size() == numLoops && |
| "expected upper bounds for every loop dimension"); |
| for (unsigned i = 0, pos = numLoops; i < numLoops; ++i, ++pos) |
| setOperand(pos, upperBounds[i]); |
| } |
| |
| void setSteps(ValueRange steps) { |
| unsigned numLoops = getNumLoops(); |
| assert(steps.size() == numLoops && |
| "expected upper bounds for every loop dimension"); |
| for (unsigned i = 0, pos = 2 * numLoops; i < numLoops; ++i, ++pos) |
| setOperand(pos, steps[i]); |
| } |
| |
| /// Block argument that corresponds to the `input` or `output` operand. |
| BlockArgument getTiedBlockArgument(OpOperand& operand) { |
| auto operandIndex = operand.getOperandNumber(); |
| assert( |
| operandIndex >= getNumControlOperands() && |
| operandIndex < getNumOperands() && |
| "tied block arg is defined only for `input` and `output` arguments"); |
| return getBody()->getArgument(operandIndex - 2 * getNumLoops()); |
| } |
| |
| /// Result that corresponds to the `outputs` argument of tensor type. |
| OpResult getTiedOpResult(OpOperand& opOperand) { |
| // No result can correspond to a memref argument. |
| if (opOperand.get().getType().isa<MemRefType>()) return OpResult(); |
| |
| // Check whether the operand index is in bounds of `outputs()` arg. |
| int operandIndex = opOperand.getOperandNumber(); |
| int outputIndexStart = |
| getNumControlOperands() + inputs().size(); |
| int outputIndexEnd = outputIndexStart + outputs().size(); |
| if (operandIndex < outputIndexStart || operandIndex >= outputIndexEnd) |
| return OpResult(); |
| |
| // Count tensor arguments in `outputs` to compute the result index. |
| int tensorId = -1; |
| for (int i = outputIndexStart; i <= operandIndex; ++i) |
| tensorId += getOperand(i).getType().isa<RankedTensorType>(); |
| return getOperation()->getResult(tensorId); |
| } |
| |
| /// Append `operand` to the `input` arguments. |
| OpOperand& appendInputOperand(OpBuilder& builder, Value operand) { |
| int numLoops = getNumLoops(); |
| int numInputs = getNumInputs(); |
| int numOutputs = getNumOutputs(); |
| |
| getOperation()->insertOperands(getNumControlOperands() + numInputs, |
| operand); |
| getBody()->insertArgument(numLoops + numInputs, operand.getType()); |
| getOperation()->setAttr( |
| TiledLoopOp::getOperandSegmentSizeAttr(), |
| builder.getI32VectorAttr( |
| {numLoops, numLoops, numLoops, numInputs + 1, numOutputs})); |
| return getOperation()->getOpOperand(getNumControlOperands() + numInputs); |
| } |
| |
| /// Append `operand` to the `output` arguments. |
| OpOperand& appendOutputOperand(OpBuilder& builder, Value operand) { |
| int numLoops = getNumLoops(); |
| int numInputs = getNumInputs(); |
| int numOutputs = getNumOutputs(); |
| |
| getOperation()->insertOperands( |
| getNumControlOperands() + numInputs + numOutputs, operand); |
| getBody()->insertArgument(numLoops + numInputs + numOutputs, |
| operand.getType()); |
| getOperation()->setAttr( |
| TiledLoopOp::getOperandSegmentSizeAttr(), |
| builder.getI32VectorAttr( |
| {numLoops, numLoops, numLoops, numInputs, numOutputs + 1})); |
| return getOperation()->getOpOperand(getNumControlOperands() + numInputs + |
| numOutputs); |
| } |
| |
| /// Erase `operand` from the `input` or `output` arguments. |
| void eraseOperand(OpBuilder& builder, OpOperand& operand) { |
| int numInputs = getNumInputs(); |
| int numLoops = getNumLoops(); |
| int numOutputs = getNumOutputs(); |
| int numControlOperands = getNumControlOperands(); |
| |
| int operandIndex = operand.getOperandNumber(); |
| assert(operandIndex >= numControlOperands && |
| operandIndex < static_cast<int>(getNumOperands()) && |
| "Can erase only `input` or `output` operand"); |
| |
| if (operandIndex >= numControlOperands + numInputs) |
| --numOutputs; |
| else |
| --numInputs; |
| |
| getOperation()->eraseOperand(operandIndex); |
| getBody()->eraseArgument(operandIndex - 2 * numLoops); |
| getOperation()->setAttr( |
| TiledLoopOp::getOperandSegmentSizeAttr(), |
| builder.getI32VectorAttr( |
| {numLoops, numLoops, numLoops, numInputs, numOutputs})); |
| } |
| |
| OpOperand* findInputOperand(Value value) { |
| OperandRange::iterator it = llvm::find(inputs(), value); |
| if (it == inputs().end()) return nullptr; |
| return it.getBase(); |
| } |
| |
| OpOperand* findOutputOperand(Value value) { |
| OperandRange::iterator it = llvm::find(outputs(), value); |
| if (it == outputs().end()) return nullptr; |
| return it.getBase(); |
| } |
| |
| /// Return whether the op has only MemRef input and outputs. |
| bool hasBufferSemantics() { |
| Operation* op = this->getOperation(); |
| return op->getNumResults() == 0 && |
| llvm::all_of(op->getOpOperands(), [&](OpOperand & operand) { |
| return !operand.get().getType().template isa<ShapedType>() || |
| operand.get().getType().template isa<MemRefType>(); |
| }); |
| } |
| |
| /// Return whether the loop dimension is parallel or not. |
| bool isParallelDimension(unsigned dim) { |
| StringAttr attr = this->iterator_types()[dim].cast<StringAttr>(); |
| return attr.getValue() == getParallelIteratorTypeName(); |
| } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Linalg_IndexOp : Linalg_Op<"index", [NoSideEffect]>, |
| Arguments<(ins Confined<I64Attr, [IntMinValue<0>]>:$dim)>, |
| Results<(outs Index:$result)> { |
| let summary = "linalg index operation"; |
| let description = [{ |
| The `linalg.index` operation returns the iteration index of the immediately |
| enclosing linalg structured operation for the iteration dimension `dim`. The |
| `dim` attribute specifies the position of the accessed dimension in the |
| indexing map domain. |
| |
| Example: |
| |
| ```mlir |
| #map = affine_map<(i, j) -> (i, j)> |
| linalg.generic {indexing_maps = [#map, #map], |
| iterator_types = ["parallel", "parallel"]} |
| outs(%I, %J : memref<?x?xindex>, memref<?x?xindex>) { |
| ^bb0(%arg0 : index, %arg1 : index): |
| // Access the outer iteration dimension i |
| %i = linalg.index 0 : index |
| // Access the inner iteration dimension j |
| %j = linalg.index 1 : index |
| linalg.yield %i, %j : index, index |
| } |
| ``` |
| |
| This may lower to IR resembling: |
| |
| ```mlir |
| %0 = dim %I, %c0 : memref<?x?xindex> |
| %1 = dim %I, %c1 : memref<?x?xindex> |
| scf.for %i = %c0 to %0 step %c1 { |
| scf.for %j = %c0 to %1 step %c1 { |
| store %i, %I[%i, %j] : memref<?x?xindex> |
| store %j, %J[%i, %j] : memref<?x?xindex> |
| } |
| } |
| ``` |
| }]; |
| |
| let assemblyFormat = [{ $dim attr-dict `:` type($result) }]; |
| } |
| |
| #endif // LINALG_OPS |