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