1//===- TensorOps.td - Tensor op definitions ----------------*- 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#ifndef TENSOR_OPS 10#define TENSOR_OPS 11 12include "mlir/Dialect/Tensor/IR/TensorBase.td" 13include "mlir/Interfaces/CastInterfaces.td" 14include "mlir/Interfaces/ControlFlowInterfaces.td" 15include "mlir/Interfaces/InferTypeOpInterface.td" 16include "mlir/Interfaces/ParallelCombiningOpInterface.td" 17include "mlir/Interfaces/SideEffectInterfaces.td" 18include "mlir/Interfaces/TilingInterface.td" 19include "mlir/Interfaces/ViewLikeInterface.td" 20 21class Tensor_Op<string mnemonic, list<Trait> traits = []> 22 : Op<Tensor_Dialect, mnemonic, traits>; 23 24// Base class for ops with static/dynamic offset, sizes and strides 25// attributes/arguments. 26class Tensor_OpWithOffsetSizesAndStrides<string mnemonic, 27 list<Trait> traits = []> 28 : Tensor_Op<mnemonic, traits> { 29 code extraBaseClassDeclaration = [{ 30 /// Returns the dynamic sizes for this subview operation if specified. 31 ::mlir::Operation::operand_range getDynamicSizes() { return getSizes(); } 32 33 /// Return the list of Range (i.e. offset, size, stride). Each 34 /// Range entry contains either the dynamic value or a ConstantIndexOp 35 /// constructed with `b` at location `loc`. 36 ::mlir::SmallVector<::mlir::Range, 8> getOrCreateRanges( 37 ::mlir::OpBuilder &b, ::mlir::Location loc) { 38 return ::mlir::getOrCreateRanges(*this, b, loc); 39 } 40 }]; 41} 42 43//===----------------------------------------------------------------------===// 44// CastOp 45//===----------------------------------------------------------------------===// 46 47def Tensor_CastOp : Tensor_Op<"cast", [ 48 DeclareOpInterfaceMethods<CastOpInterface>, NoSideEffect 49 ]> { 50 let summary = "tensor cast operation"; 51 let description = [{ 52 Convert a tensor from one type to an equivalent type without changing any 53 data elements. The source and destination types must both be tensor types 54 with the same element type. If both are ranked, then the rank should be the 55 same and static dimensions should match. The operation is invalid if 56 converting to a mismatching constant dimension. 57 58 Example: 59 60 ```mlir 61 // Convert from unknown rank to rank 2 with unknown dimension sizes. 62 %2 = tensor.cast %1 : tensor<*xf32> to tensor<?x?xf32> 63 64 // Convert to a type with more known dimensions. 65 %3 = tensor.cast %2 : tensor<?x?xf32> to tensor<4x?xf32> 66 67 // Discard static dimension and rank information. 68 %4 = tensor.cast %3 : tensor<4x?xf32> to tensor<?x?xf32> 69 %5 = tensor.cast %4 : tensor<?x?xf32> to tensor<*xf32> 70 ``` 71 }]; 72 73 let arguments = (ins AnyTensor:$source); 74 let results = (outs AnyTensor:$dest); 75 let assemblyFormat = "$source attr-dict `:` type($source) `to` type($dest)"; 76 77 let hasCanonicalizer = 1; 78} 79 80//===----------------------------------------------------------------------===// 81// DimOp 82//===----------------------------------------------------------------------===// 83 84def Tensor_DimOp : Tensor_Op<"dim", [NoSideEffect]> { 85 let summary = "dimension index operation"; 86 let description = [{ 87 The `tensor.dim` operation takes a tensor and a dimension operand of type 88 `index`. It returns the size of the requested dimension of the given 89 tensor. If the dimension index is out of bounds, the behavior is undefined. 90 91 The specified tensor type is that of the first operand. 92 93 Example: 94 95 ```mlir 96 // Always returns 4, can be constant folded: 97 %c0 = arith.constant 0 : index 98 %x = tensor.dim %A, %c0 : tensor<4x?xf32> 99 100 // Returns the dynamic dimension of %A. 101 %c1 = arith.constant 1 : index 102 %y = tensor.dim %A, %c1 : memref<4x?xf32> 103 104 // Equivalent generic form: 105 %x = "tensor.dim"(%A, %c0) : (memref<4x?xf32>, index) -> index 106 %y = "tensor.dim"(%A, %c1) : (memref<4x?xf32>, index) -> index 107 ``` 108 }]; 109 110 let arguments = (ins AnyTensor:$source, 111 Index:$index); 112 let results = (outs Index:$result); 113 114 let assemblyFormat = [{ 115 attr-dict $source `,` $index `:` type($source) 116 }]; 117 118 let builders = [ 119 OpBuilder<(ins "Value":$source, "int64_t":$index)> 120 ]; 121 122 let extraClassDeclaration = [{ 123 /// Helper function to get the index as a simple integer if it is constant. 124 Optional<int64_t> getConstantIndex(); 125 }]; 126 127 let hasCanonicalizer = 1; 128 let hasFolder = 1; 129 let hasVerifier = 1; 130} 131 132//===----------------------------------------------------------------------===// 133// ExtractOp 134//===----------------------------------------------------------------------===// 135 136def Tensor_ExtractOp : Tensor_Op<"extract", 137 [NoSideEffect, 138 TypesMatchWith<"result type matches element type of tensor", 139 "tensor", "result", 140 "$_self.cast<ShapedType>().getElementType()">]> { 141 let summary = "element extraction operation"; 142 let description = [{ 143 The `tensor.extract` op reads a tensor and returns one 144 element from it specified by an index list. The output of the op is a 145 new value with the same type as the elements of the tensor. The 146 arity of indices must match the rank of the accessed value (i.e., if a 147 tensor is of rank 3, then 3 indices are required for the extract. The 148 indices should all be of `index` type. 149 150 Example: 151 152 ```mlir 153 %4 = tensor.extract %t[%1, %2] : tensor<4x4xi32> 154 %5 = tensor.extract %rt[%1, %2] : tensor<?x?xi32> 155 %6 = tensor.extract %ut[%1, %2] : tensor<*xi32> 156 ``` 157 }]; 158 159 let arguments = (ins AnyTensor:$tensor, Variadic<Index>:$indices); 160 let results = (outs AnyType:$result); 161 let assemblyFormat = "$tensor `[` $indices `]` attr-dict `:` type($tensor)"; 162 163 let builders = [ 164 OpBuilder<(ins "Value":$tensor, CArg<"ValueRange", "{}">:$indices), [{ 165 auto resType = tensor.getType().cast<ShapedType>().getElementType(); 166 build($_builder, $_state, resType, tensor, indices); 167 }]>]; 168 169 let hasFolder = 1; 170 let hasVerifier = 1; 171} 172 173 174//===----------------------------------------------------------------------===// 175// ExtractSliceOp 176//===----------------------------------------------------------------------===// 177 178def Tensor_ExtractSliceOp : Tensor_OpWithOffsetSizesAndStrides<"extract_slice", [ 179 NoSideEffect, AttrSizedOperandSegments, 180 DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>, 181 OffsetSizeAndStrideOpInterface 182 ]> { 183 let summary = "extract slice operation"; 184 let description = [{ 185 The "extract_slice" operation extract a tensor from another tensor as 186 specified by the operation's offsets, sizes and strides arguments. 187 188 The extract_slice operation supports the following arguments: 189 190 * source: the "base" tensor from which to extract a slice. 191 * offsets: tensor-rank number of offsets into the "base" tensor from which 192 to extract the slice. 193 * sizes: tensor-rank number of sizes which specify the sizes of the result 194 tensor type. 195 * strides: tensor-rank number of strides specifying subsampling in each 196 dimension. 197 198 The representation based on offsets, sizes and strides support a 199 partially-static specification via attributes specified through the 200 `static_offsets`, `static_sizes` and `static_strides` arguments. A special 201 sentinel value ShapedType::kDynamicSize and 202 ShapedType::kDynamicStrideOrOffset encodes that the corresponding entry has 203 a dynamic value. 204 205 After buffer allocation, the "extract_slice" op is expected to lower into a 206 memref.subview op. 207 208 An extract_slice operation may additionally reduce the rank of the resulting 209 tensor by removing dimensions that are statically known to be of size 1. 210 This rank-reduction behavior is not required by the op semantics: this 211 flexibility allows to progressively drop unit dimensions while lowering 212 between different flavors of ops on that operate on tensors. 213 214 Verification vs Inference in the rank-reduced case: 215 =================================================== 216 Note that there may be multiple ways to infer a resulting rank-reduced type. 217 e.g. 1x6x1 could potentially rank-reduce to either 1x6 or 6x1 2-D shapes. 218 219 To disambiguate, the inference helpers `inferCanonicalRankReducedResultType` 220 only drop the first unit dimensions, in order: 221 e.g. 1x6x1 rank-reduced to 2-D will infer the 6x1 2-D shape, but not 1x6. 222 223 Verification however has access to result type and does not need to infer. 224 The verifier calls `isRankReducedType(getSource(), getResult())` to 225 determine whether the result type is rank-reduced from the source type. 226 This computes a so-called rank-reduction mask, consisting of dropped unit 227 dims, to map the rank-reduced type to the source type by dropping ones: 228 e.g. 1x6 is a rank-reduced version of 1x6x1 by mask {2} 229 6x1 is a rank-reduced version of 1x6x1 by mask {0} 230 1x2x1x4 is a rank-reduced version of 1x1x2x1x1x4x1 by mask {1, 4, 6} 231 (remaining common 1 dimensions are matched eagerly) 232 233 Example: 234 235 ``` 236 // Rank-reducing extract_slice. 237 %1 = tensor.extract_slice %0[0, 0, 0][1, 16, 4][1, 1, 1] : 238 tensor<8x16x4xf32> to tensor<16x4xf32> 239 %3 = tensor.extract_slice %2[%o0, 4, %o2][1, %sz1, 1][1, %st1, 1] : 240 tensor<8x16x4xf32> to tensor<1x?xf32> 241 ``` 242 }]; 243 244 let arguments = (ins 245 AnyRankedTensor:$source, 246 Variadic<Index>:$offsets, 247 Variadic<Index>:$sizes, 248 Variadic<Index>:$strides, 249 I64ArrayAttr:$static_offsets, 250 I64ArrayAttr:$static_sizes, 251 I64ArrayAttr:$static_strides 252 ); 253 let results = (outs AnyRankedTensor:$result); 254 255 let assemblyFormat = [{ 256 $source `` 257 custom<OperandsOrIntegersOffsetsOrStridesList>($offsets, $static_offsets) 258 custom<OperandsOrIntegersSizesList>($sizes, $static_sizes) 259 custom<OperandsOrIntegersOffsetsOrStridesList>($strides, $static_strides) 260 attr-dict `:` type($source) `to` type($result) 261 }]; 262 263 let builders = [ 264 // Build an ExtractSliceOp with mixed static and dynamic entries and 265 // inferred result type. 266 OpBuilder<(ins "Value":$source, "ArrayRef<OpFoldResult>":$offsets, 267 "ArrayRef<OpFoldResult>":$sizes, "ArrayRef<OpFoldResult>":$strides, 268 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 269 // Build an ExtractSliceOp with mixed static and dynamic entries and custom 270 // result type. If the type passed is nullptr, it is inferred. 271 OpBuilder<(ins "RankedTensorType":$resultType, "Value":$source, 272 "ArrayRef<OpFoldResult>":$offsets, "ArrayRef<OpFoldResult>":$sizes, 273 "ArrayRef<OpFoldResult>":$strides, 274 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 275 // Build an ExtractSliceOp with dynamic entries and custom result type. If 276 // the type passed is nullptr, it is inferred. 277 OpBuilder<(ins "Value":$source, "ValueRange":$offsets, 278 "ValueRange":$sizes, "ValueRange":$strides, 279 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 280 // Build an ExtractSliceOp with dynamic entries and inferred result type. 281 OpBuilder<(ins "RankedTensorType":$resultType, "Value":$source, 282 "ValueRange":$offsets, "ValueRange":$sizes, "ValueRange":$strides, 283 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)> 284 ]; 285 286 let extraClassDeclaration = extraBaseClassDeclaration # [{ 287 /// Returns the type of the base tensor operand. 288 RankedTensorType getSourceType() { 289 return getSource().getType().cast<RankedTensorType>(); 290 } 291 292 /// The result of an extract_slice is always a tensor. 293 RankedTensorType getType() { 294 return getResult().getType().cast<RankedTensorType>(); 295 } 296 297 /// Compute the rank-reduction mask that can be applied to map the source 298 /// tensor type to the result tensor type by dropping unit dims. 299 llvm::Optional<llvm::SmallDenseSet<unsigned>> 300 computeRankReductionMask() { 301 return ::mlir::computeRankReductionMask(getSourceType().getShape(), 302 getType().getShape()); 303 }; 304 305 /// An extract_slice result type can be inferred, when it is not 306 /// rank-reduced, from the source type and the static representation of 307 /// offsets, sizes and strides. Special sentinels encode the dynamic case. 308 static RankedTensorType inferResultType( 309 ShapedType sourceShapedTensorType, 310 ArrayRef<int64_t> staticOffsets, 311 ArrayRef<int64_t> staticSizes, 312 ArrayRef<int64_t> staticStrides); 313 static RankedTensorType inferResultType( 314 ShapedType sourceShapedTensorType, 315 ArrayRef<OpFoldResult> staticOffsets, 316 ArrayRef<OpFoldResult> staticSizes, 317 ArrayRef<OpFoldResult> staticStrides); 318 319 /// If the rank is reduced (i.e. the desiredResultRank is smaller than the 320 /// number of sizes), drop as many size 1 as needed to produce an inferred type 321 /// with the desired rank. 322 /// 323 /// Note that there may be multiple ways to compute this rank-reduced type: 324 /// e.g. 1x6x1 can rank-reduce to either 1x6 or 6x1 2-D tensors. 325 /// 326 /// To disambiguate, this function always drops the first 1 sizes occurrences. 327 static RankedTensorType inferCanonicalRankReducedResultType( 328 unsigned resultRank, 329 RankedTensorType sourceRankedTensorType, 330 ArrayRef<int64_t> staticOffsets, 331 ArrayRef<int64_t> staticSizes, 332 ArrayRef<int64_t> staticStrides); 333 static RankedTensorType inferCanonicalRankReducedResultType( 334 unsigned resultRank, 335 RankedTensorType sourceRankedTensorType, 336 ArrayRef<OpFoldResult> staticOffsets, 337 ArrayRef<OpFoldResult> staticSizes, 338 ArrayRef<OpFoldResult> staticStrides); 339 340 /// Return the expected rank of each of the`static_offsets`, `static_sizes` 341 /// and `static_strides` attributes. 342 std::array<unsigned, 3> getArrayAttrMaxRanks() { 343 unsigned rank = getSourceType().getRank(); 344 return {rank, rank, rank}; 345 } 346 347 /// Return the number of leading operands before the `offsets`, `sizes` and 348 /// and `strides` operands. 349 static unsigned getOffsetSizeAndStrideStartOperandIndex() { return 1; } 350 351 /// Return the dimensions of the source that are dropped in the 352 /// result when the result is rank-reduced. 353 llvm::SmallBitVector getDroppedDims(); 354 }]; 355 356 let hasCanonicalizer = 1; 357 let hasFolder = 1; 358 let hasVerifier = 1; 359} 360 361//===----------------------------------------------------------------------===// 362// FromElementsOp 363//===----------------------------------------------------------------------===// 364 365def Tensor_FromElementsOp : Tensor_Op<"from_elements", [ 366 NoSideEffect, 367 TypesMatchWith<"operand types match result element type", 368 "result", "elements", "SmallVector<Type, 2>(" 369 "$_self.cast<ShapedType>().getNumElements(), " 370 "$_self.cast<ShapedType>().getElementType())"> 371 ]> { 372 string summary = "tensor from elements operation."; 373 string description = [{ 374 Create a N-D tensor from a range of same-type arguments. The number of 375 provided `elements` should equal to the number of the elements in the 376 result type. The `elements` correspond to a flattened tensor. 377 378 Example: 379 380 ```mlir 381 tensor.from_elements %a, %b, %c, %d, %e, %f : tensor<2x3xindex> 382 ``` 383 384 will result in a tensor 385 386 [[%a, %b, %c] 387 [%d, %e, %f]] 388 }]; 389 390 let arguments = (ins Variadic<AnyType>:$elements); 391 let results = (outs AnyStaticShapeTensor:$result); 392 393 let assemblyFormat = "$elements attr-dict `:` type($result)"; 394 395 let skipDefaultBuilders = 1; 396 let builders = [ 397 OpBuilder<(ins "Type":$resultType, "ValueRange":$elements)>, 398 // Special case builder for when `elements` has size >=1. 399 OpBuilder<(ins "ValueRange":$elements)> 400 ]; 401 402 let hasCanonicalizer = 1; 403 let hasFolder = 1; 404} 405 406//===----------------------------------------------------------------------===// 407// GenerateOp 408//===----------------------------------------------------------------------===// 409 410def Tensor_GenerateOp : Tensor_Op<"generate", 411 [RecursiveSideEffects, 412 DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>, 413 SingleBlockImplicitTerminator<"mlir::tensor::YieldOp">]> { 414 string summary = "Creates a dynamically sized tensor from elements"; 415 string description = [{ 416 This operation creates a dynamically sized tensor with elements of any type. 417 It expects one index operand per dynamic extent of the result tensor. 418 419 The body region defines the tensor's elements. It takes index operands as 420 its region arguments that span the index space. The element at the given 421 position is yielded with the `yield` operation (see `YieldOp`). There is 422 no defined ordering to the invocations of the body. It is conceptually 423 a "parallel map" operation. 424 425 Example: 426 427 ```mlir 428 %tnsr = tensor.generate %m, %n { 429 ^bb0(%i : index, %j : index, %k : index): 430 ... 431 yield %elem : f32 432 } : tensor<?x3x?f32> 433 ``` 434 }]; 435 436 let arguments = (ins Variadic<Index>:$dynamicExtents); 437 let results = (outs AnyRankedTensor:$result); 438 let regions = (region SizedRegion<1>:$body); 439 let assemblyFormat = "$dynamicExtents $body attr-dict `:` type($result)"; 440 441 let builders = [ 442 // Build op and populate its body per callback function. 443 OpBuilder<(ins "Type":$resultTy, "ValueRange":$dynamicExtents, 444 "function_ref<void(OpBuilder &, Location, ValueRange)>")>, 445 ]; 446 447 let hasCanonicalizer = 1; 448 let hasVerifier = 1; 449 let hasRegionVerifier = 1; 450} 451 452//===----------------------------------------------------------------------===// 453// InsertOp 454//===----------------------------------------------------------------------===// 455 456def Tensor_InsertOp : Tensor_Op<"insert", 457 [NoSideEffect, 458 TypesMatchWith<"result type matches type of dest", 459 "dest", "result", 460 "$_self.cast<ShapedType>()">, 461 TypesMatchWith<"scalar type matches element type of dest", 462 "dest", "scalar", 463 "$_self.cast<ShapedType>().getElementType()">]> { 464 let summary = "element insertion operation"; 465 let description = [{ 466 The `tensor.insert` op writes a tensor into a tensor `dest`as specified by 467 the operation's indices. 468 469 It returns a copy of `dest` with the proper slice updated with the value 470 of `scalar`. 471 472 The arity of indices must match the rank of the tensor `dest` (i.e., if a 473 tensor is of rank 3, then 3 indices are required for the extract. The 474 indices should all be of `index` type. 475 476 Example: 477 478 ```mlir 479 %4 = tensor.insert %t into %dest[%1, %2] : tensor<4x4xi32> 480 %5 = tensor.insert %rt into %dest[%1, %2] : tensor<?x?xi32> 481 %6 = tensor.insert %ut into %dest[%1, %2] : tensor<*xi32> 482 ``` 483 }]; 484 485 let arguments = (ins AnyType:$scalar, 486 AnyTensor:$dest, 487 Variadic<Index>:$indices); 488 let results = (outs AnyTensor:$result); 489 let assemblyFormat = [{ 490 $scalar `into` $dest `[` $indices `]` attr-dict `:` type($dest) 491 }]; 492 493 let builders = [ 494 OpBuilder<(ins "Value":$scalar, "Value":$dest, 495 CArg<"ValueRange", "{}">:$indices), [{ 496 auto resType = dest.getType(); 497 build($_builder, $_state, resType, scalar, dest, indices); 498 }]>]; 499 500 let hasFolder = 1; 501 let hasVerifier = 1; 502} 503 504//===----------------------------------------------------------------------===// 505// InsertSliceOp 506//===----------------------------------------------------------------------===// 507 508def Tensor_InsertSliceOp : Tensor_OpWithOffsetSizesAndStrides<"insert_slice", [ 509 NoSideEffect, AttrSizedOperandSegments, OffsetSizeAndStrideOpInterface, 510 DeclareOpInterfaceMethods<ReifyRankedShapedTypeOpInterface>, 511 TypesMatchWith<"expected result type to match dest type", 512 "dest", "result", "$_self"> 513 ]> { 514 let summary = "insert_slice operation"; 515 let description = [{ 516 The "insert_slice" operation insert a tensor `source` into another 517 tensor `dest` as specified by the operation's offsets, sizes and strides 518 arguments. 519 520 It returns a copy of `dest` with the proper slice updated with the value 521 of `source`. 522 523 The insert_slice operation supports the following arguments: 524 525 * source: the tensor that is inserted. 526 * dest: the tensor into which the source tensor is inserted. 527 * offsets: tensor-rank number of offsets into the `dest` tensor into which 528 the slice is inserted. 529 * sizes: tensor-rank number of sizes which specify the sizes of the source 530 tensor type. 531 * strides: tensor-rank number of strides that specify subsampling in each 532 dimension. 533 534 The representation based on offsets, sizes and strides support a 535 partially-static specification via attributes specified through the 536 `static_offsets`, `static_sizes` and `static_strides` arguments. A special 537 sentinel value ShapedType::kDynamicSize and 538 ShapedType::kDynamicStrideOrOffset encodes that the corresponding entry has 539 a dynamic value. 540 541 After buffer allocation, the "insert_slice" op is expected to lower into a 542 memref.subview op. 543 544 An insert_slice operation may additionally specify insertion into a tensor 545 of higher rank than the source tensor, along dimensions that are statically 546 known to be of size 1. 547 This rank-altering behavior is not required by the op semantics: this 548 flexibility allows to progressively drop unit dimensions while lowering 549 between different flavors of ops on that operate on tensors. 550 The rank-altering behavior of tensor.insert_slice matches the rank-reducing 551 behavior of tensor.extract_slice. 552 553 Verification in the rank-reduced case: 554 ====================================== 555 The same verification discussion and mechanisms apply as for ExtractSliceOp. 556 Unlike ExtractSliceOp however, there is no need for a specific inference. 557 558 Example: 559 560 ``` 561 // Rank-altering insert_slice. 562 %1 = tensor.insert_slice %t into %0[0, 0, 0][1, 16, 4][1, 1, 1] : 563 tensor<16x4xf32> into tensor<8x16x4xf32> 564 %3 = tensor.insert_slice %tt into %2[%o0, 4, %o2][1, %sz1, 1][1, %st1, 1] : 565 tensor<1x?xf32> into tensor<8x16x4xf32> 566 ``` 567 }]; 568 569 let arguments = (ins 570 AnyRankedTensor:$source, 571 AnyRankedTensor:$dest, 572 Variadic<Index>:$offsets, 573 Variadic<Index>:$sizes, 574 Variadic<Index>:$strides, 575 I64ArrayAttr:$static_offsets, 576 I64ArrayAttr:$static_sizes, 577 I64ArrayAttr:$static_strides 578 ); 579 let results = (outs AnyRankedTensor:$result); 580 581 let assemblyFormat = [{ 582 $source `into` $dest `` 583 custom<OperandsOrIntegersOffsetsOrStridesList>($offsets, $static_offsets) 584 custom<OperandsOrIntegersSizesList>($sizes, $static_sizes) 585 custom<OperandsOrIntegersOffsetsOrStridesList>($strides, $static_strides) 586 attr-dict `:` type($source) `into` type($dest) 587 }]; 588 589 let builders = [ 590 // Build a InsertSliceOp with mixed static and dynamic entries. 591 OpBuilder<(ins "Value":$source, "Value":$dest, 592 "ArrayRef<OpFoldResult>":$offsets, "ArrayRef<OpFoldResult>":$sizes, 593 "ArrayRef<OpFoldResult>":$strides, 594 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 595 // Build a InsertSliceOp with dynamic entries. 596 OpBuilder<(ins "Value":$source, "Value":$dest, 597 "ValueRange":$offsets, "ValueRange":$sizes, "ValueRange":$strides, 598 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)> 599 ]; 600 601 let extraClassDeclaration = extraBaseClassDeclaration # [{ 602 /// Returns the type of the base tensor operand. 603 RankedTensorType getSourceType() { 604 return getSource().getType().cast<RankedTensorType>(); 605 } 606 607 /// The result of a insert_slice is always a tensor. 608 RankedTensorType getType() { 609 return getResult().getType().cast<RankedTensorType>(); 610 } 611 612 /// The `dest` type is the same as the result type. 613 RankedTensorType getDestType() { 614 return getType(); 615 } 616 617 /// Return the expected rank of each of the`static_offsets`, `static_sizes` 618 /// and `static_strides` attributes. 619 std::array<unsigned, 3> getArrayAttrMaxRanks() { 620 unsigned rank = getType().getRank(); 621 return {rank, rank, rank}; 622 } 623 624 /// Return the number of leading operands before the `offsets`, `sizes` and 625 /// and `strides` operands. 626 static unsigned getOffsetSizeAndStrideStartOperandIndex() { return 2; } 627 }]; 628 629 let hasCanonicalizer = 1; 630 let hasFolder = 1; 631 let hasVerifier = 1; 632} 633 634//===----------------------------------------------------------------------===// 635// RankOp 636//===----------------------------------------------------------------------===// 637 638def Tensor_RankOp : Tensor_Op<"rank", [NoSideEffect]> { 639 let summary = "rank operation"; 640 let description = [{ 641 The `tensor.rank` operation takes a tensor operand and returns its rank. 642 643 Example: 644 645 ```mlir 646 %0 = tensor.rank %arg0 : tensor<*xf32> 647 %1 = tensor.rank %arg1 : tensor<?x?xf32> 648 ``` 649 }]; 650 651 let arguments = (ins AnyTensor:$tensor); 652 let results = (outs Index); 653 654 let hasFolder = 1; 655 let assemblyFormat = "$tensor attr-dict `:` type($tensor)"; 656} 657 658//===----------------------------------------------------------------------===// 659// ReshapeOp 660//===----------------------------------------------------------------------===// 661 662def Tensor_ReshapeOp: Tensor_Op<"reshape", [NoSideEffect]> { 663 let summary = "tensor reshape operation"; 664 let description = [{ 665 The `reshape` operation converts a tensor from one type to an equivalent 666 type with a provided shape. The source and destination types are compatible 667 if both have the same element type, same number of elements. The following 668 combinations are possible: 669 670 a. Source type is ranked or unranked. Shape argument has static size. 671 Result type is ranked. 672 673 ```mlir 674 // Reshape statically-shaped tensor. 675 %dst = tensor.reshape %src(%shape) 676 : (tensor<4x1xf32>, tensor<1xi32>) -> tensor<4xf32> 677 %dst0 = tensor.reshape %src(%shape0) 678 : (tensor<4x1xf32>, tensor<2xi32>) -> tensor<2x2xf32> 679 // Flatten unranked tensor. 680 %dst = tensor.reshape %src(%shape) 681 : (tensor<*xf32>, tensor<1xi32>) -> tensor<?xf32> 682 ``` 683 684 b. Source type is ranked or unranked. Shape argument has dynamic size. 685 Result type is unranked. 686 687 ```mlir 688 // Reshape dynamically-shaped 1D tensor. 689 %dst = tensor.reshape %src(%shape) 690 : (tensor<?xf32>, tensor<?xi32>) -> tensor<*xf32> 691 // Reshape unranked tensor. 692 %dst = tensor.reshape %src(%shape) 693 : (tensor<*xf32>, tensor<?xi32>) -> tensor<*xf32> 694 ``` 695 }]; 696 697 let arguments = (ins 698 AnyTensor:$source, 699 TensorRankOf<[AnySignlessInteger, Index], [1]>:$shape 700 ); 701 let results = (outs AnyTensor:$result); 702 703 let builders = [OpBuilder< 704 (ins "TensorType":$resultType, "Value":$operand, "Value":$shape), [{ 705 $_state.addOperands(operand); 706 $_state.addOperands(shape); 707 $_state.addTypes(resultType); 708 }]>]; 709 710 let extraClassDeclaration = [{ 711 TensorType getResultType() { return getResult().getType().cast<TensorType>(); } 712 }]; 713 714 let assemblyFormat = [{ 715 $source `(` $shape `)` attr-dict `:` functional-type(operands, results) 716 }]; 717 let hasVerifier = 1; 718} 719 720//===----------------------------------------------------------------------===// 721// ExpandShapeOp / CollapseShapeOp 722//===----------------------------------------------------------------------===// 723 724class Tensor_ReassociativeReshapeOp<string mnemonic, list<Trait> traits = []> : 725 Tensor_Op<mnemonic, !listconcat(traits, [NoSideEffect])>, 726 Arguments<(ins AnyTensor:$src, IndexListArrayAttr:$reassociation)>, 727 Results<(outs AnyTensor:$result)> { 728 729 code commonExtraClassDeclaration = [{ 730 static StringRef getReassociationAttrStrName() { return "reassociation"; } 731 SmallVector<AffineMap, 4> getReassociationMaps(); 732 SmallVector<ReassociationExprs, 4> getReassociationExprs(); 733 SmallVector<ReassociationIndices, 4> getReassociationIndices() { 734 SmallVector<ReassociationIndices, 4> reassociationIndices; 735 for (auto attr : getReassociation()) 736 reassociationIndices.push_back(llvm::to_vector<2>( 737 llvm::map_range(attr.cast<ArrayAttr>(), [&](Attribute indexAttr) { 738 return indexAttr.cast<IntegerAttr>().getInt(); 739 }))); 740 return reassociationIndices; 741 }; 742 RankedTensorType getSrcType() { 743 return getSrc().getType().cast<RankedTensorType>(); 744 } 745 RankedTensorType getResultType() { 746 return getResult().getType().cast<RankedTensorType>(); 747 } 748 }]; 749 750 let assemblyFormat = [{ 751 $src $reassociation attr-dict `:` type($src) `into` type($result) 752 }]; 753 754 let hasFolder = 1; 755 let hasCanonicalizer = 1; 756 let hasVerifier = 1; 757} 758 759def Tensor_ExpandShapeOp : Tensor_ReassociativeReshapeOp<"expand_shape"> { 760 let summary = "operation to produce a tensor with a higher rank"; 761 let description = [{ 762 The `tensor.expand_shape` op produces a new tensor with a higher 763 rank whose sizes are a reassociation of the original `src`. 764 765 A reassociation is defined as a continuous grouping of dimensions and is 766 represented with an array of I64ArrayAttr attribute. 767 768 The verification rule is that the reassociation maps are applied to the 769 result tensor with the higher rank to obtain the operand tensor with the 770 smaller rank. 771 772 The operand tensor type of a reshape can be zero-ranked if the result 773 tensor type is statically shaped with all dimensions being unit extent. In 774 such cases the reassociation map is empty. 775 776 Examples: 777 778 ```mlir 779 // Dimension expansion i -> (i', j') and (k) -> (k') 780 %b = tensor.expand_shape %a [[0, 1], [2]] 781 : tensor<?x?xf32> into tensor<?x?x?xf32> 782 ``` 783 }]; 784 let builders = [ 785 // Builders using ReassociationIndices. 786 OpBuilder<(ins "Type":$resultType, "Value":$src, 787 "ArrayRef<ReassociationIndices>":$reassociation, 788 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 789 [{ 790 build($_builder, $_state, resultType, src, attrs); 791 $_state.addAttribute("reassociation", 792 getReassociationIndicesAttribute($_builder, reassociation)); 793 }]>, 794 OpBuilder<(ins "Type":$resultType, "Value":$src, 795 "ArrayRef<ReassociationExprs>":$reassociation, 796 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 797 [{ 798 auto reassociationMaps = 799 convertReassociationMapsToIndices($_builder, reassociation); 800 build($_builder, $_state, resultType, src, reassociationMaps, attrs); 801 }]> 802 ]; 803 804 let extraClassDeclaration = commonExtraClassDeclaration; 805 let hasVerifier = 1; 806} 807 808def Tensor_CollapseShapeOp : Tensor_ReassociativeReshapeOp<"collapse_shape"> { 809 let summary = "operation to produce a tensor with a smaller rank"; 810 let description = [{ 811 The `tensor.collapse_shape` op produces a new tensor with a smaller 812 rank whose sizes are a reassociation of the original `src`. 813 814 A reassociation is defined as a continuous grouping of dimensions and is 815 represented with an array of I64ArrayAttr attribute. 816 817 The verification rule is that the reassociation maps are applied to the 818 operand tensor with the higher rank to obtain the result tensor with the 819 smaller rank. 820 821 The result tensor type of a reshape can be zero-ranked if the operand 822 tensor type is statically shaped with all dimensions being unit extent. In 823 such case the reassociation map is empty. 824 825 Examples: 826 827 ```mlir 828 // Dimension collapse (i, j) -> i' and k -> k' 829 %b = tensor.collapse_shape %a [[0, 1], [2]] 830 : tensor<?x?x?xf32> into tensor<?x?xf32> 831 ``` 832 }]; 833 let builders = [ 834 // Builders for a contracting reshape whose result type is computed from 835 // `src` and `reassociation`. 836 OpBuilder<(ins "Value":$src, 837 "ArrayRef<ReassociationIndices>":$reassociation, 838 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 839 OpBuilder<(ins "Value":$src, 840 "ArrayRef<ReassociationExprs>":$reassociation, 841 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 842 [{ 843 auto reassociationMaps = 844 convertReassociationMapsToIndices($_builder, reassociation); 845 build($_builder, $_state, src, reassociationMaps, attrs); 846 }]>, 847 848 // Builders for a reshape whose result type is passed explicitly. 849 OpBuilder<(ins "Type":$resultType, "Value":$src, 850 "ArrayRef<ReassociationIndices>":$reassociation, 851 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 852 [{ 853 build($_builder, $_state, resultType, src, attrs); 854 $_state.addAttribute("reassociation", 855 getReassociationIndicesAttribute($_builder, reassociation)); 856 }]>, 857 OpBuilder<(ins "Type":$resultType, "Value":$src, 858 "ArrayRef<ReassociationExprs>":$reassociation, 859 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs), 860 [{ 861 auto reassociationMaps = 862 convertReassociationMapsToIndices($_builder, reassociation); 863 build($_builder, $_state, resultType, src, reassociationMaps, attrs); 864 }]> 865 ]; 866 867 let extraClassDeclaration = commonExtraClassDeclaration; 868 let hasVerifier = 1; 869} 870 871//===----------------------------------------------------------------------===// 872// PadOp 873//===----------------------------------------------------------------------===// 874 875def Tensor_PadOp : Tensor_Op<"pad", [AttrSizedOperandSegments, NoSideEffect, 876 SingleBlockImplicitTerminator<"mlir::tensor::YieldOp">]> { 877 let summary = "tensor pad operation"; 878 let description = [{ 879 `tensor.pad` is an operation that pads the `source` tensor 880 with given `low` and `high` padding config. 881 882 The PadOp operation supports the following arguments: 883 884 * source: the "base" tensor on which to pad. 885 * low: A list contains the padding along the start of each 886 dimension, i.e `low`. 887 * high: A list contains the padding along the end of each 888 dimension, i.e. `high`. 889 * nofold: indicates that the operation should not be folded when source and 890 result types are equal. 891 892 The result tensor dimensions are `low` + `dim` + `high` along that 893 dimension. The number of elements of `low` and `high` must match 894 the rank of the input tensor. They can be either a constant or a 895 dynamic value. 896 897 The region of the `tensor.pad` operation returns the value to use 898 for the padding. The arguments of the region represent the index 899 of the source being accessed. There should be as many arguments as 900 the rank of the `source` tensor. The value `yield`-ed by the 901 region is used as the value of the view at the given position. 902 903 If `nofold` is set, the padding operation will not be folded away even 904 if the source type and the padded type have the same static shape. This can 905 be used, e.g., for packing or promotion to faster memory. 906 907 Example 1: 908 909 ```mlir 910 %pad_value = ... : f32 911 %0 = tensor.pad %0 low[1, 2] high[2, 3] { 912 ^bb0(%arg0 : index, %arg1 : index): 913 tensor.yield %pad_value : f32 914 } : tensor<?x?xf32> to tensor<?x?xf32> 915 ``` 916 917 Example 2: 918 919 ```mlir 920 %pad_value = ... : f32 921 %0 = tensor.pad %arg0 low[2, %arg1, 3, 3] high[3, 3, %arg1, 2] { 922 ^bb0(%arg2: index, %arg3: index, %arg4: index, %arg5: index): 923 tensor.yield %pad_value : f32 924 } : tensor<1x2x2x?xf32> to tensor<6x?x?x?xf32> 925 ``` 926 927 Example 3: 928 929 ```mlir 930 %pad_value = ... : f32 931 %0 = tensor.pad %arg0 low[0, 0] high[%ub0, %ub1] { 932 ^bb0(%arg1: index, %arg2: index): 933 tensor.yield %pad_value : f32 934 } : tensor<2x3xf32> to tensor<?x?xf32> 935 ``` 936 937 Example 4: 938 939 ```mlir 940 // Force a padded value to be always exist with `nofold`. 941 %pad_value = ... : f32 942 %0 = tensor.pad %arg0 nofold low[0, 0] high[0, 0] { 943 ^bb0(%arg1: index, %arg2: index): 944 tensor.yield %pad_value : f32 945 } : tensor<2x3xf32> to tensor<2x3xf32> 946 ``` 947 }]; 948 949 let arguments = (ins 950 AnyTensor:$source, 951 Variadic<Index>:$low, 952 Variadic<Index>:$high, 953 I64ArrayAttr:$static_low, 954 I64ArrayAttr:$static_high, 955 UnitAttr:$nofold); 956 957 let regions = (region SizedRegion<1>:$region); 958 959 let results = (outs AnyTensor:$result); 960 961 // TODO: Remove custom<InferType> when AllTypesMatch supports opt. operands. 962 let assemblyFormat = [{ 963 $source 964 (`nofold` $nofold^)? 965 `low` `` custom<OperandsOrIntegersSizesList>($low, $static_low) 966 `high` `` custom<OperandsOrIntegersSizesList>($high, $static_high) 967 $region attr-dict `:` type($source) `to` type($result) 968 }]; 969 970 let extraClassDeclaration = [{ 971 static StringRef getStaticLowAttrStrName() { 972 return "static_low"; 973 } 974 975 static StringRef getStaticHighAttrStrName() { 976 return "static_high"; 977 } 978 979 RankedTensorType getSourceType() { 980 return getSource().getType().cast<RankedTensorType>(); 981 } 982 RankedTensorType getResultType() { 983 return getResult().getType().cast<RankedTensorType>(); 984 } 985 986 // Infer the shape of the result tensor given the type of the source tensor 987 // and paddings. Known result dimensions that cannot necessarily be inferred 988 // from low/high padding sizes can be optionally specified. Those will be 989 // considered when computing the result type. 990 static RankedTensorType inferResultType( 991 RankedTensorType sourceType, 992 ArrayRef<int64_t> staticLow, 993 ArrayRef<int64_t> staticHigh, 994 ArrayRef<int64_t> resultShape = {}); 995 996 // Return the pad value if it is a constant. Return null value otherwise. 997 Value getConstantPaddingValue(); 998 999 // Return a vector of all the static or dynamic values (low/high padding) of 1000 // the op. 1001 inline SmallVector<OpFoldResult> getMixedPadImpl(ArrayAttr staticAttrs, 1002 ValueRange values) { 1003 SmallVector<OpFoldResult> res; 1004 unsigned numDynamic = 0; 1005 unsigned count = staticAttrs.size(); 1006 for (unsigned idx = 0; idx < count; ++idx) { 1007 if (ShapedType::isDynamic(staticAttrs[idx].cast<IntegerAttr>().getInt())) 1008 res.push_back(values[numDynamic++]); 1009 else 1010 res.push_back(staticAttrs[idx]); 1011 } 1012 return res; 1013 } 1014 SmallVector<OpFoldResult> getMixedLowPad() { 1015 return getMixedPadImpl(getStaticLow(), getLow()); 1016 } 1017 SmallVector<OpFoldResult> getMixedHighPad() { 1018 return getMixedPadImpl(getStaticHigh(), getHigh()); 1019 } 1020 // Return true if low padding is guaranteed to be 0. 1021 bool hasZeroLowPad() { 1022 return llvm::all_of(getMixedLowPad(), [](OpFoldResult ofr) { 1023 return getConstantIntValue(ofr) == static_cast<int64_t>(0); 1024 }); 1025 } 1026 // Return true if high padding is guaranteed to be 0. 1027 bool hasZeroHighPad() { 1028 return llvm::all_of(getMixedHighPad(), [](OpFoldResult ofr) { 1029 return getConstantIntValue(ofr) == static_cast<int64_t>(0); 1030 }); 1031 } 1032 /// Return the dimensions with a non-zero low or high padding. 1033 llvm::SmallBitVector getPaddedDims(); 1034 }]; 1035 1036 let builders = [ 1037 // Build a PadOp with mixed static and dynamic entries. 1038 OpBuilder<(ins "Value":$source, "ArrayRef<int64_t>":$staticLow, 1039 "ArrayRef<int64_t>":$staticHigh, "ValueRange":$low, "ValueRange":$high, 1040 CArg<"bool", "false">:$nofold, 1041 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 1042 // Build a PadOp with all dynamic entries. 1043 OpBuilder<(ins "Value":$source, "ValueRange":$low, "ValueRange":$high, 1044 CArg<"bool", "false">:$nofold, 1045 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 1046 // Build a PadOp with mixed static and dynamic entries and custom 1047 // result type. If the type passed is nullptr, it is inferred. 1048 OpBuilder<(ins "Type":$resultType, "Value":$source, 1049 "ArrayRef<OpFoldResult>":$low, "ArrayRef<OpFoldResult>":$high, 1050 CArg<"bool", "false">:$nofold, 1051 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 1052 ]; 1053 1054 let hasCanonicalizer = 1; 1055 let hasFolder = 1; 1056 let hasVerifier = 1; 1057 let hasRegionVerifier = 1; 1058} 1059 1060//===----------------------------------------------------------------------===// 1061// ParallelInsertSliceOp 1062//===----------------------------------------------------------------------===// 1063 1064// TODO: Implement PerformConcurrentlyOpInterface. 1065def Tensor_ParallelInsertSliceOp : Tensor_Op<"parallel_insert_slice", [ 1066 AttrSizedOperandSegments, 1067 OffsetSizeAndStrideOpInterface, 1068 // TODO: Cannot use an interface here atm, verify this manually for now. 1069 // HasParent<"ParallelCombiningOpInterface"> 1070 ]> { 1071 let summary = [{ 1072 Specify the tensor slice update of a single thread of a parent 1073 ParallelCombiningOpInterface op. 1074 }]; 1075 let description = [{ 1076 The `parallel_insert_slice` yields a subset tensor value to its parent 1077 ParallelCombiningOpInterface. These subset tensor values are aggregated to 1078 in some unspecified order into a full tensor value returned by the parent 1079 parallel iterating op. 1080 The `parallel_insert_slice` is one such op allowed in the 1081 ParallelCombiningOpInterface op. 1082 1083 Conflicting writes result in undefined semantics, in that the indices written 1084 to by multiple parallel updates might contain data from any of the updates, 1085 or even a malformed bit pattern. 1086 1087 If an index is updated exactly once, the value contained at that index 1088 in the resulting tensor will be equal to the value at a corresponding index 1089 of a slice that was used for the updated. If an index is not updated at all, 1090 its value will be equal to the one in the original tensor. 1091 1092 This op does not create a new value, which allows maintaining a clean 1093 separation between the subset and full tensor. 1094 1095 Note that we cannot mark this operation as pure (NoSideEffects), even 1096 though it has no side effects, because it will get DCEd during 1097 canonicalization. 1098 1099 The parallel_insert_slice operation supports the following arguments: 1100 1101 * source: the tensor that is inserted. 1102 * dest: the tensor into which the source tensor is inserted. 1103 * offsets: tensor-rank number of offsets into the `dest` tensor into which 1104 the slice is inserted. 1105 * sizes: tensor-rank number of sizes which specify the sizes of the source 1106 tensor type. 1107 * strides: tensor-rank number of strides that specify subsampling in each 1108 dimension. 1109 1110 The representation based on offsets, sizes and strides support a 1111 partially-static specification via attributes specified through the 1112 `static_offsets`, `static_sizes` and `static_strides` arguments. A special 1113 sentinel value ShapedType::kDynamicSize and 1114 ShapedType::kDynamicStrideOrOffset encodes that the corresponding entry has 1115 a dynamic value. 1116 1117 After buffer allocation, the "parallel_insert_slice" op is expected to lower 1118 into a memref.subview op. 1119 1120 A parallel_insert_slice operation may additionally specify insertion into a 1121 tensor of higher rank than the source tensor, along dimensions that are 1122 statically known to be of size 1. 1123 This rank-altering behavior is not required by the op semantics: this 1124 flexibility allows to progressively drop unit dimensions while lowering 1125 between different flavors of ops on that operate on tensors. 1126 The rank-altering behavior of tensor.parallel_insert_slice matches the 1127 rank-reducing behavior of tensor.insert_slice and tensor.extract_slice. 1128 1129 Verification in the rank-reduced case: 1130 ====================================== 1131 The same verification discussion and mechanisms apply as for ExtractSliceOp. 1132 Unlike ExtractSliceOp however, there is no need for a specific inference. 1133 }]; 1134 1135 let arguments = (ins 1136 AnyRankedTensor:$source, 1137 AnyRankedTensor:$dest, 1138 Variadic<Index>:$offsets, 1139 Variadic<Index>:$sizes, 1140 Variadic<Index>:$strides, 1141 I64ArrayAttr:$static_offsets, 1142 I64ArrayAttr:$static_sizes, 1143 I64ArrayAttr:$static_strides 1144 ); 1145 let assemblyFormat = [{ 1146 $source `into` $dest `` 1147 custom<OperandsOrIntegersOffsetsOrStridesList>($offsets, $static_offsets) 1148 custom<OperandsOrIntegersSizesList>($sizes, $static_sizes) 1149 custom<OperandsOrIntegersOffsetsOrStridesList>($strides, $static_strides) 1150 attr-dict `:` type($source) `into` type($dest) 1151 }]; 1152 1153 let extraClassDeclaration = [{ 1154 Type yieldedType() { return getDest().getType(); } 1155 1156 RankedTensorType getSourceType() { 1157 return getSource().getType().cast<RankedTensorType>(); 1158 } 1159 1160 RankedTensorType getDestType() { 1161 return getDest().getType().cast<RankedTensorType>(); 1162 } 1163 1164 ParallelCombiningOpInterface getParallelCombiningParent() { 1165 return dyn_cast<ParallelCombiningOpInterface>( 1166 getOperation()->getParentOp()); 1167 } 1168 1169 /// Return the expected rank of each of the `static_offsets`, `static_sizes` 1170 /// and `static_strides` attributes. 1171 std::array<unsigned, 3> getArrayAttrMaxRanks() { 1172 unsigned rank = getDestType().getRank(); 1173 return {rank, rank, rank}; 1174 } 1175 1176 /// Return the number of leading operands before `offsets`, `sizes` and 1177 /// `strides` operands. 1178 static unsigned getOffsetSizeAndStrideStartOperandIndex() { return 1; } 1179 1180 /// Return the OpResult of the enclosing ForeachThreadOp that is 1181 /// corresponding to this ParallelInsertSliceOp. 1182 OpResult getTiedOpResult(); 1183 }]; 1184 1185 let builders = [ 1186 // Build a ParallelInsertSliceOp with mixed static and dynamic entries. 1187 OpBuilder<(ins "Value":$source, "Value":$dest, 1188 "ArrayRef<OpFoldResult>":$offsets, "ArrayRef<OpFoldResult>":$sizes, 1189 "ArrayRef<OpFoldResult>":$strides, 1190 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>, 1191 1192 // Build a ParallelInsertSliceOp with dynamic entries. 1193 OpBuilder<(ins "Value":$source, "Value":$dest, 1194 "ValueRange":$offsets, "ValueRange":$sizes, "ValueRange":$strides, 1195 CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)> 1196 ]; 1197 1198 let hasCanonicalizer = 1; 1199 let hasFolder = 1; 1200 let hasVerifier = 1; 1201} 1202 1203//===----------------------------------------------------------------------===// 1204// SplatOp 1205//===----------------------------------------------------------------------===// 1206 1207def Tensor_SplatOp : Tensor_Op<"splat", [ 1208 NoSideEffect, 1209 TypesMatchWith<"operand type matches element type of result", 1210 "aggregate", "input", 1211 "$_self.cast<TensorType>().getElementType()"> 1212 ]> { 1213 let summary = "tensor splat or broadcast operation"; 1214 let description = [{ 1215 Broadcast the operand to all elements of the result tensor. The operand is 1216 required to be of integer/index/float type, and the result tensor must be 1217 statically shaped. 1218 1219 Example: 1220 1221 ```mlir 1222 %s = arith.constant 10.1 : f32 1223 %t = tensor.splat %s : tensor<8x16xf32> 1224 ``` 1225 1226 TODO: This operation is easy to extend to broadcast to dynamically shaped 1227 tensors: 1228 1229 ```mlir 1230 // Broadcasts %s to a 2-d dynamically shaped tensor, with %m, %n binding 1231 // to the sizes of the two dynamic dimensions. 1232 %m = "foo"() : () -> (index) 1233 %n = "bar"() : () -> (index) 1234 %t = tensor.splat %s [%m, %n] : tensor<?x?xf32> 1235 ``` 1236 }]; 1237 1238 let arguments = (ins AnyTypeOf<[AnySignlessInteger, Index, AnyFloat], 1239 "integer/index/float type">:$input); 1240 let results = (outs AnyStaticShapeTensor:$aggregate); 1241 1242 let builders = [ 1243 OpBuilder<(ins "Value":$element, "Type":$aggregateType), 1244 [{ build($_builder, $_state, aggregateType, element); }]>]; 1245 let assemblyFormat = "$input attr-dict `:` type($aggregate)"; 1246 1247 let hasFolder = 1; 1248} 1249 1250//===----------------------------------------------------------------------===// 1251// YieldOp 1252//===----------------------------------------------------------------------===// 1253 1254def Tensor_YieldOp : Tensor_Op<"yield", 1255 [NoSideEffect, ReturnLike, Terminator, 1256 HasParent<"::mlir::tensor::GenerateOp, ::mlir::tensor::PadOp">]> { 1257 let summary = "Yield a value from a region"; 1258 let description = [{ 1259 This operation is used to yield a single value from a within a region. It 1260 is used to create dynamically sized tensors 1261 (see `tensor.generate` and `tensor.pad` ops). 1262 }]; 1263 1264 let arguments = (ins AnyType:$value); 1265 let assemblyFormat = "$value attr-dict `:` type($value)"; 1266 1267 // Dummy builder to appease code in templated ensureTerminator that 1268 // GenerateOp's auto-generated parser calls. 1269 let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; 1270} 1271 1272#endif // TENSOR_OPS 1273