1//===- LinalgOps.td - Linalg dialect ops -------------------*- tablegen -*-===// 2// 3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information. 5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6// 7//===----------------------------------------------------------------------===// 8// 9// This is the operation definition file for linear algebra operations. 10// 11//===----------------------------------------------------------------------===// 12 13#ifndef LINALG_OPS 14#define LINALG_OPS 15 16include "mlir/Dialect/Linalg/IR/LinalgBase.td" 17include "mlir/Interfaces/ControlFlowInterfaces.td" 18include "mlir/Interfaces/SideEffectInterfaces.td" 19include "mlir/Interfaces/ViewLikeInterface.td" 20 21// Base class for Linalg dialect ops that do not correspond to library calls. 22class Linalg_Op<string mnemonic, list<OpTrait> traits = []> : 23 Op<Linalg_Dialect, mnemonic, traits> { 24 // For every linalg op, there needs to be a: 25 // * void print(OpAsmPrinter &p, ${C++ class of Op} op) 26 // * LogicalResult verify(${C++ class of Op} op) 27 // * ParseResult parse${C++ class of Op}(OpAsmParser &parser, 28 // OperationState &result) 29 // functions. 30 let printer = [{ return ::print(p, *this); }]; 31 let verifier = [{ return ::verify(*this); }]; 32 let parser = [{ return ::parse$cppClass(parser, result); }]; 33} 34 35def Linalg_RangeOp : 36 Linalg_Op<"range", [NoSideEffect]>, 37 Arguments<(ins Index:$min, Index:$max, Index:$step)>, 38 Results<(outs Range)> { 39 let summary = "Create a `range` type value, used to create `view`s"; 40 let description = [{ 41 The `linalg.range` op creates a `!linalg.range` from 3 values of type 42 `index` that represent the min, max and step values of the `range`. This 43 type does not pass function boundaries at the moment. 44 45 Example: 46 47 ```mlir 48 %3 = linalg.range %0:%1:%2 : !linalg.range 49 ```` 50 }]; 51 let builders = [ 52 OpBuilderDAG<(ins "Value":$min, "Value":$max, "Value":$step), 53 [{ 54 auto rangeType = RangeType::get($_builder.getContext()); 55 build($_builder, $_state, rangeType, min, max, step); 56 }]>]; 57 58 // Fully specified by traits. 59 let verifier = ?; 60 let assemblyFormat = "$min `:` $max `:` $step attr-dict `:` type(results)"; 61} 62 63class Linalg_ReshapeLikeOp<string mnemonic, list<OpTrait> traits = []> : 64 Linalg_Op<mnemonic, !listconcat(traits, [NoSideEffect])> { 65 let builders = [ 66 // Builders for a contracting reshape whose result type is computed from 67 // `src` and `reassociation`. 68 OpBuilderDAG<(ins "Value":$src, 69 "ArrayRef<ReassociationExprs>":$reassociation, 70 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 71 OpBuilderDAG<(ins "Value":$src, 72 "ArrayRef<ReassociationIndices>":$reassociation, 73 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 74 [{ 75 auto reassociationMaps = 76 convertReassociationIndicesToMaps($_builder, reassociation); 77 build($_builder, $_state, src, reassociationMaps, attrs); 78 }]>, 79 80 // Builders for a reshape whose result type is passed explicitly. This may 81 // be either a contracting or expanding reshape. 82 OpBuilderDAG<(ins "Type":$resultType, "Value":$src, 83 "ArrayRef<ReassociationExprs>":$reassociation, 84 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 85 OpBuilderDAG<(ins "Type":$resultType, "Value":$src, 86 "ArrayRef<ReassociationIndices>":$reassociation, 87 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 88 [{ 89 auto reassociationMaps = 90 convertReassociationIndicesToMaps($_builder, reassociation); 91 build($_builder, $_state, resultType, src, reassociationMaps, attrs); 92 }]> 93 ]; 94 95 code commonExtraClassDeclaration = [{ 96 static StringRef getReassociationAttrName() { return "reassociation"; } 97 SmallVector<AffineMap, 4> getReassociationMaps() { 98 return llvm::to_vector<4>(llvm::map_range(reassociation(), [ 99 ](Attribute a) { return a.cast<AffineMapAttr>().getValue(); })); 100 } 101 }]; 102 let assemblyFormat = [{ 103 $src $reassociation attr-dict `:` type($src) `into` type(results) 104 }]; 105} 106 107def Linalg_ReshapeOp : Linalg_ReshapeLikeOp<"reshape", 108 [DeclareOpInterfaceMethods<ViewLikeOpInterface>]>, 109 Arguments<(ins AnyStridedMemRef:$src, AffineMapArrayAttr:$reassociation)>, 110 Results<(outs AnyStridedMemRef:$result)> { 111 let summary = "linalg.reshape produces a new view into the operand view"; 112 let description = [{ 113 The `linalg.reshape` op produces a new view whose sizes are a reassociation 114 of the original `view`. Depending on whether or not the reassociated 115 MemRefType is contiguous, the resulting memref may require explicit alloc 116 and copies. 117 118 A reassociation is defined as a continuous grouping of dimensions and is 119 represented with an affine map array attribute. In the future, 120 non-continuous groupings may be allowed (i.e. permutations, reindexings 121 etc). 122 123 For now, it is assumed that either: 124 1. a reassociation produces and consumes contiguous MemRefType or, 125 2. the reshape op will be folded into its consumers (by changing the shape 126 of the computations). 127 All other cases are undefined behavior and a reshape op may not lower to 128 LLVM if it cannot be proven statically that it does not require alloc+copy. 129 130 A reshape may either collapse or expand dimensions, depending on the 131 relationship between source and target memref ranks. The verification rule 132 is that the reassociation maps are applied to the memref with the larger 133 rank to obtain the memref with the smaller rank. In the case of a dimension 134 expansion, the reassociation maps can be interpreted as inverse maps. 135 136 The result memref type of a reshape when dimensions are collapsed 137 (operand memref type when dimensions are expanded) can be 138 zero-ranked if the operand memref type (or the result memref type 139 when dimensions are expanded) is statically shaped with all 140 dimensions being unit extent. In such cases the reassociation map 141 is empty. 142 143 Examples: 144 145 ```mlir 146 // Dimension collapse (i, j) -> i' and k -> k' 147 %1 = linalg.reshape %0 [(i, j, k) -> (i, j), (i, j, k) -> (k)] : 148 memref<?x?x?xf32, stride_spec> into memref<?x?xf32, stride_spec_2> 149 ``` 150 151 ```mlir 152 // Dimension expansion i -> (i', j') and (k) -> (k') 153 %1 = linalg.reshape %0 [(i, j, k) -> (i, j), (i, j, k) -> (k)] : 154 memref<?x?xf32, stride_spec> into memref<?x?x?xf32, stride_spec_2> 155 ``` 156 }]; 157 let extraClassDeclaration = commonExtraClassDeclaration # [{ 158 MemRefType getSrcType() { return src().getType().cast<MemRefType>(); } 159 MemRefType getResultType() { return result().getType().cast<MemRefType>(); } 160 }]; 161 let hasFolder = 1; 162 let hasCanonicalizer = 1; 163} 164 165def Linalg_TensorReshapeOp : Linalg_ReshapeLikeOp<"tensor_reshape">, 166 Arguments<(ins AnyTensor:$src, 167 AffineMapArrayAttr:$reassociation)>, 168 Results<(outs AnyTensor:$result)> { 169 let summary = "linalg.tensor_reshape produces a new reshaped tensor."; 170 let description = [{ 171 The `linalg.reshape` op produces a new tensor whose sizes are a 172 reassociation of the original `src`. 173 174 A reassociation is defined as a continuous grouping of dimensions and is 175 represented with an affine map array attribute. In the future, 176 non-continuous groupings may be allowed (i.e. permutations, reindexings 177 etc). 178 179 A reshape may either collapse or expand dimensions, depending on the 180 relationship between source and target tensor ranks. The verification rule 181 is that the reassociation maps are applied to the tensor with the larger 182 rank to obtain the tensor with the smaller rank. In the case of a dimension 183 expansion, the reassociation maps can be interpreted as inverse maps. 184 185 The result tensor type of a reshape when dimensions are collapsed 186 (operand tensor type when dimensions are expanded) can be 187 zero-ranked if the operand tensor type (or the result tensor type 188 when dimensions are expanded) is statically shaped with all 189 dimensions being unit extent. In such cases the reassociation map 190 is empty. 191 192 Examples: 193 194 ```mlir 195 // Dimension collapse (i, j) -> i' and k -> k' 196 %b = linalg.tensor_reshape %a [(i, j, k) -> (i, j), (i, j, k) -> (k)] : 197 tensor<?x?x?xf32> into tensor<?x?xf32> 198 ``` 199 200 ```mlir 201 // Dimension expansion i -> (i', j') and (k) -> (k') 202 %b = linalg.tensor_reshape %a [(i, j, k) -> (i, j), (i, j, k) -> (k)] : 203 tensor<?x?xf32> into tensor<?x?x?xf32> 204 ``` 205 }]; 206 let extraClassDeclaration = commonExtraClassDeclaration # [{ 207 RankedTensorType getSrcType() { 208 return src().getType().cast<RankedTensorType>(); 209 } 210 RankedTensorType getResultType() { 211 return result().getType().cast<RankedTensorType>(); 212 } 213 }]; 214 let hasFolder = 1; 215 let hasCanonicalizer = 1; 216} 217 218def Linalg_SliceOp : Linalg_Op<"slice", [ 219 DeclareOpInterfaceMethods<ViewLikeOpInterface>, NoSideEffect]>, 220 Arguments<(ins AnyStridedMemRef:$view, 221 Variadic<AnyTypeOf<[Range, Index]>>:$indexings)>, 222 Results<(outs AnyStridedMemRef)> { 223 let summary = "Produce a rank-reduced `subview` of a base `view`."; 224 let description = [{ 225 The `linalg.slice` op allows defining a subregion of a smaller rank than the 226 operand `view` within the underlying buffer. 227 228 A `linalg.slice` op takes a view and a variadic number of indexings and 229 produces a `view` of the same elemental type. An indexing is either: 230 1. a `linalg.range`, in which case it does not reduce the rank of the 231 parent `view` along the corresponding dimension. 232 2. an `index`, in which case it reduces the rank of the parent view by 233 one. 234 235 If an indexing extends past the size of the `view`, this is undefined 236 behavior. Ideally the `linalg.slice` operation would automatically truncate 237 it to be within bounds but there are tradeoffs involved now that `std.view` 238 is a standard op. 239 240 Examples: 241 242 1. rank-preserving `slice`: 243 244 ```mlir 245 %4 = linalg.slice %0[%1, %2] : memref<?x?xf32, stride_spec>, 246 !linalg.range, !linalg.range, memref<?x?xf32, stride_spec> 247 ``` 248 249 2. rank-reducing `slice` (from 2-D to 1-D): 250 251 ```mlir 252 %4 = linalg.slice %0[%1, %2] : memref<?x?xf32, stride_spec>, 253 index, !linalg.range, memref<?x?xf32, stride_spec> 254 ``` 255 256 3. rank-reducing `slice` (from 2-D to 0-D): 257 258 ```mlir 259 %4 = linalg.slice %0[%1, %2] : memref<?x?xf32, stride_spec>, 260 index, index, memref<?x?xf32, stride_spec> 261 ``` 262 }]; 263 264 let builders = [OpBuilderDAG<(ins "Value":$base, "ValueRange":$indexings)>]; 265 266 let extraClassDeclaration = [{ 267 enum { FirstIndexingOperand = 1 }; 268 unsigned getRank() { return getShapedType().getRank(); } 269 Type getElementType() { return getShapedType().getElementType(); } 270 ShapedType getShapedType() { return getType().cast<ShapedType>(); } 271 unsigned getBaseViewRank() { return getBaseViewType().getRank(); } 272 ShapedType getBaseViewType() { return view().getType().cast<ShapedType>();} 273 274 // Get the underlying indexing at a given rank. 275 Value indexing(unsigned rank) { return *(indexings().begin() + rank); } 276 277 // Get the subset of indexings that are of RangeType. 278 SmallVector<Value, 8> getRanges() { 279 SmallVector<Value, 8> res; 280 for (auto operand : indexings()) 281 if (!operand.getType().isa<IndexType>()) 282 res.push_back(operand); 283 return res; 284 } 285 }]; 286 287 let hasFolder = 1; 288} 289 290def Linalg_YieldOp : Linalg_Op<"yield", [NoSideEffect, ReturnLike, Terminator]>, 291 Arguments<(ins Variadic<AnyType>:$values)> { 292 let summary = "Linalg yield operation"; 293 let description = [{ 294 `linalg.yield` is a special terminator operation for blocks inside regions 295 in `linalg` generic ops. It returns values to the immediately enclosing 296 `linalg` generic op. 297 298 Example: 299 300 ```mlir 301 linalg.yield %f0, %f1 : f32, f32 302 ``` 303 }]; 304} 305 306#endif // LINALG_OPS 307