1 //===- Shape.cpp - MLIR Shape Operations ----------------------------------===//
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 #include "mlir/Dialect/Shape/IR/Shape.h"
10 
11 #include "mlir/Dialect/Traits.h"
12 #include "mlir/IR/Builders.h"
13 #include "mlir/IR/DialectImplementation.h"
14 #include "mlir/IR/PatternMatch.h"
15 #include "mlir/IR/StandardTypes.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/Support/raw_ostream.h"
18 
19 using namespace mlir;
20 using namespace mlir::shape;
21 
22 namespace {
23 #include "ShapeCanonicalization.inc"
24 }
25 
26 ShapeDialect::ShapeDialect(MLIRContext *context)
27     : Dialect(getDialectNamespace(), context) {
28   addOperations<
29 #define GET_OP_LIST
30 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
31       >();
32   addTypes<ComponentType, ElementType, ShapeType, SizeType, ValueShapeType,
33            WitnessType>();
34   // Allow unknown operations during prototyping and testing. As the dialect is
35   // still evolving it makes it simple to start with an unregistered ops and
36   // try different variants before actually defining the op.
37   allowUnknownOperations();
38 }
39 
40 Operation *ShapeDialect::materializeConstant(OpBuilder &builder,
41                                              Attribute value, Type type,
42                                              Location loc) {
43   if (auto shapeType = type.dyn_cast<ShapeType>())
44     return builder.create<ConstShapeOp>(loc, type,
45                                         value.cast<DenseIntElementsAttr>());
46   if (auto sizeType = type.dyn_cast<SizeType>())
47     return builder.create<ConstSizeOp>(loc, type, value.cast<IntegerAttr>());
48   if (auto witnessType = type.dyn_cast<WitnessType>())
49     return builder.create<ConstWitnessOp>(loc, type, value.cast<BoolAttr>());
50   return nullptr;
51 }
52 
53 /// Parse a type registered to this dialect.
54 Type ShapeDialect::parseType(DialectAsmParser &parser) const {
55   StringRef keyword;
56   if (parser.parseKeyword(&keyword))
57     return Type();
58 
59   if (keyword == "component")
60     return ComponentType::get(getContext());
61   if (keyword == "element")
62     return ElementType::get(getContext());
63   if (keyword == "shape")
64     return ShapeType::get(getContext());
65   if (keyword == "size")
66     return SizeType::get(getContext());
67   if (keyword == "value_shape")
68     return ValueShapeType::get(getContext());
69   if (keyword == "witness")
70     return WitnessType::get(getContext());
71 
72   parser.emitError(parser.getNameLoc(), "unknown shape type: ") << keyword;
73   return Type();
74 }
75 
76 /// Print a type registered to this dialect.
77 void ShapeDialect::printType(Type type, DialectAsmPrinter &os) const {
78   switch (type.getKind()) {
79   case ShapeTypes::Component:
80     os << "component";
81     return;
82   case ShapeTypes::Element:
83     os << "element";
84     return;
85   case ShapeTypes::Size:
86     os << "size";
87     return;
88   case ShapeTypes::Shape:
89     os << "shape";
90     return;
91   case ShapeTypes::ValueShape:
92     os << "value_shape";
93     return;
94   case ShapeTypes::Witness:
95     os << "witness";
96     return;
97   default:
98     llvm_unreachable("unexpected 'shape' type kind");
99   }
100 }
101 
102 //===----------------------------------------------------------------------===//
103 // AnyOp
104 //===----------------------------------------------------------------------===//
105 
106 // TODO: Canonicalization should be implemented for shapes that can be
107 // determined through mixtures of the known dimensions of the inputs.
108 OpFoldResult AnyOp::fold(ArrayRef<Attribute> operands) {
109   // Only the last operand is checked because AnyOp is commutative.
110   if (operands.back())
111     return operands.back();
112 
113   return nullptr;
114 }
115 
116 //===----------------------------------------------------------------------===//
117 // AssumingOp
118 //===----------------------------------------------------------------------===//
119 
120 static ParseResult parseAssumingOp(OpAsmParser &parser,
121                                    OperationState &result) {
122   result.regions.reserve(1);
123   Region *doRegion = result.addRegion();
124 
125   auto &builder = parser.getBuilder();
126   OpAsmParser::OperandType cond;
127   if (parser.parseOperand(cond) ||
128       parser.resolveOperand(cond, builder.getType<WitnessType>(),
129                             result.operands))
130     return failure();
131 
132   // Parse optional results type list.
133   if (parser.parseOptionalArrowTypeList(result.types))
134     return failure();
135 
136   // Parse the region and add a terminator if elided.
137   if (parser.parseRegion(*doRegion, /*arguments=*/{}, /*argTypes=*/{}))
138     return failure();
139   AssumingOp::ensureTerminator(*doRegion, parser.getBuilder(), result.location);
140 
141   // Parse the optional attribute list.
142   if (parser.parseOptionalAttrDict(result.attributes))
143     return failure();
144   return success();
145 }
146 
147 static void print(OpAsmPrinter &p, AssumingOp op) {
148   bool yieldsResults = !op.results().empty();
149 
150   p << AssumingOp::getOperationName() << " " << op.witness();
151   if (yieldsResults) {
152     p << " -> (" << op.getResultTypes() << ")";
153   }
154   p.printRegion(op.doRegion(),
155                 /*printEntryBlockArgs=*/false,
156                 /*printBlockTerminators=*/yieldsResults);
157   p.printOptionalAttrDict(op.getAttrs());
158 }
159 
160 namespace {
161 // Removes AssumingOp with a passing witness and inlines the region.
162 struct AssumingWithTrue : public OpRewritePattern<AssumingOp> {
163   using OpRewritePattern<AssumingOp>::OpRewritePattern;
164 
165   LogicalResult matchAndRewrite(AssumingOp op,
166                                 PatternRewriter &rewriter) const override {
167     auto witness = op.witness().getDefiningOp<ConstWitnessOp>();
168     if (!witness || !witness.passingAttr())
169       return failure();
170 
171     AssumingOp::inlineRegionIntoParent(op, rewriter);
172     return success();
173   }
174 };
175 } // namespace
176 
177 void AssumingOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns,
178                                              MLIRContext *context) {
179   // If taking a passing witness, inline region.
180   patterns.insert<AssumingWithTrue>(context);
181 }
182 
183 void AssumingOp::inlineRegionIntoParent(AssumingOp &op,
184                                         PatternRewriter &rewriter) {
185   auto *blockBeforeAssuming = rewriter.getInsertionBlock();
186   auto *assumingBlock = op.getBody();
187   auto initPosition = rewriter.getInsertionPoint();
188   auto *blockAfterAssuming =
189       rewriter.splitBlock(blockBeforeAssuming, initPosition);
190 
191   // Remove the AssumingOp and AssumingYieldOp.
192   auto &yieldOp = assumingBlock->back();
193   rewriter.inlineRegionBefore(op.doRegion(), blockAfterAssuming);
194   rewriter.replaceOp(op, yieldOp.getOperands());
195   rewriter.eraseOp(&yieldOp);
196 
197   // Merge blocks together as there was no branching behavior from the
198   // AssumingOp.
199   rewriter.mergeBlocks(assumingBlock, blockBeforeAssuming);
200   rewriter.mergeBlocks(blockAfterAssuming, blockBeforeAssuming);
201 }
202 
203 //===----------------------------------------------------------------------===//
204 // AssumingAllOp
205 //===----------------------------------------------------------------------===//
206 OpFoldResult AssumingAllOp::fold(ArrayRef<Attribute> operands) {
207   // Iterate in reverse to first handle all constant operands. They are
208   // guaranteed to be the tail of the inputs because this is commutative.
209   for (int idx = operands.size() - 1; idx >= 0; idx--) {
210     Attribute a = operands[idx];
211     // Cannot fold if any inputs are not constant;
212     if (!a)
213       return nullptr;
214 
215     // We do not need to keep statically known values after handling them in
216     // this method.
217     getOperation()->eraseOperand(idx);
218 
219     // Always false if any input is statically known false
220     if (!a.cast<BoolAttr>().getValue())
221       return a;
222   }
223   // If this is reached, all inputs were statically known passing.
224   return BoolAttr::get(true, getContext());
225 }
226 
227 static LogicalResult verify(AssumingAllOp op) {
228   // Ensure that AssumingAllOp contains at least one operand
229   if (op.getNumOperands() == 0)
230     return op.emitOpError("no operands specified");
231 
232   return success();
233 }
234 
235 //===----------------------------------------------------------------------===//
236 // BroadcastOp
237 //===----------------------------------------------------------------------===//
238 
239 OpFoldResult BroadcastOp::fold(ArrayRef<Attribute> operands) {
240   if (!operands[1])
241     return nullptr;
242 
243   auto rhsShape = llvm::to_vector<6>(
244       operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>());
245   if (rhsShape.empty())
246     return lhs();
247 
248   if (!operands[0])
249     return nullptr;
250 
251   auto lhsShape = llvm::to_vector<6>(
252       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
253   if (lhsShape.empty())
254     return rhs();
255 
256   SmallVector<int64_t, 6> resultShape;
257   // If the shapes are not compatible, we can't fold it.
258   // TODO: Fold to an "error".
259   if (!OpTrait::util::getBroadcastedShape(lhsShape, rhsShape, resultShape))
260     return nullptr;
261   Builder builder(getContext());
262   return builder.getIndexTensorAttr(resultShape);
263 }
264 
265 //===----------------------------------------------------------------------===//
266 // ConcatOp
267 //===----------------------------------------------------------------------===//
268 
269 OpFoldResult ConcatOp::fold(ArrayRef<Attribute> operands) {
270   if (!operands[0] || !operands[1])
271     return nullptr;
272   auto lhsShape = llvm::to_vector<6>(
273       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
274   auto rhsShape = llvm::to_vector<6>(
275       operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>());
276   SmallVector<int64_t, 6> resultShape;
277   resultShape.append(lhsShape.begin(), lhsShape.end());
278   resultShape.append(rhsShape.begin(), rhsShape.end());
279   Builder builder(getContext());
280   return builder.getIndexTensorAttr(resultShape);
281 }
282 
283 //===----------------------------------------------------------------------===//
284 // ConstShapeOp
285 //===----------------------------------------------------------------------===//
286 
287 static void print(OpAsmPrinter &p, ConstShapeOp &op) {
288   p << "shape.const_shape ";
289   p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{"shape"});
290   p << "[";
291   interleaveComma(op.shape().getValues<int64_t>(), p,
292                   [&](int64_t i) { p << i; });
293   p << "]";
294 }
295 
296 static ParseResult parseConstShapeOp(OpAsmParser &parser,
297                                      OperationState &result) {
298   if (parser.parseOptionalAttrDict(result.attributes))
299     return failure();
300   // We piggy-back on ArrayAttr parsing, though we don't internally store the
301   // shape as an ArrayAttr.
302   // TODO: Implement custom parser and maybe make syntax a bit more concise.
303   Attribute extentsRaw;
304   NamedAttrList dummy;
305   if (parser.parseAttribute(extentsRaw, "dummy", dummy))
306     return failure();
307   auto extentsArray = extentsRaw.dyn_cast<ArrayAttr>();
308   if (!extentsArray)
309     return failure();
310   SmallVector<int64_t, 6> ints;
311   for (Attribute extent : extentsArray) {
312     IntegerAttr attr = extent.dyn_cast<IntegerAttr>();
313     if (!attr)
314       return failure();
315     ints.push_back(attr.getInt());
316   }
317   Builder &builder = parser.getBuilder();
318   result.addAttribute("shape", builder.getIndexTensorAttr(ints));
319 
320   result.types.push_back(ShapeType::get(builder.getContext()));
321   return success();
322 }
323 
324 OpFoldResult ConstShapeOp::fold(ArrayRef<Attribute>) { return shapeAttr(); }
325 
326 //===----------------------------------------------------------------------===//
327 // CstrBroadcastableOp
328 //===----------------------------------------------------------------------===//
329 
330 namespace {
331 // Given an input shape Value, try to obtain the shape's values.
332 LogicalResult getShapeVec(Value input, SmallVectorImpl<int64_t> &shapeValues) {
333   if (auto inputOp = input.getDefiningOp<ShapeOfOp>()) {
334     auto type = inputOp.arg().getType().dyn_cast<ShapedType>();
335     if (!type.hasRank())
336       return failure();
337     shapeValues = llvm::to_vector<6>(type.getShape());
338     return success();
339   } else if (auto inputOp = input.getDefiningOp<ConstShapeOp>()) {
340     shapeValues = llvm::to_vector<6>(inputOp.shape().getValues<int64_t>());
341     return success();
342   } else {
343     return failure();
344   }
345 }
346 
347 // For shapes that were created by some operations, we can obtain partial
348 // information on the shapes and sometimes determine if they will be
349 // broadcastable with that.
350 struct CstrBroadcastablePartialInfo
351     : public OpRewritePattern<CstrBroadcastableOp> {
352   using OpRewritePattern<CstrBroadcastableOp>::OpRewritePattern;
353 
354   LogicalResult matchAndRewrite(CstrBroadcastableOp op,
355                                 PatternRewriter &rewriter) const override {
356     SmallVector<int64_t, 6> lhsShape, rhsShape;
357     if (failed(getShapeVec(op.lhs(), lhsShape)))
358       return failure();
359     if (failed(getShapeVec(op.rhs(), rhsShape)))
360       return failure();
361     if (!OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape))
362       return failure();
363 
364     rewriter.replaceOpWithNewOp<ConstWitnessOp>(op.getOperation(), true);
365     return success();
366   }
367 };
368 
369 // Scalars are always broadcastable.
370 struct CstrBroadcastableScalar : public OpRewritePattern<CstrBroadcastableOp> {
371   using OpRewritePattern<CstrBroadcastableOp>::OpRewritePattern;
372 
373   LogicalResult matchAndRewrite(CstrBroadcastableOp op,
374                                 PatternRewriter &rewriter) const override {
375     SmallVector<int64_t, 6> shape;
376     if (failed(getShapeVec(op.lhs(), shape)) || shape.size() > 0)
377       return failure();
378     if (failed(getShapeVec(op.rhs(), shape)) || shape.size() > 0)
379       return failure();
380 
381     rewriter.replaceOpWithNewOp<ConstWitnessOp>(op.getOperation(), true);
382     return success();
383   }
384 };
385 
386 } // namespace
387 
388 void CstrBroadcastableOp::getCanonicalizationPatterns(
389     OwningRewritePatternList &patterns, MLIRContext *context) {
390   // Canonicalization patterns have overlap with the considerations during
391   // folding in case additional shape information is inferred at some point that
392   // does not result in folding.
393   patterns.insert<CstrBroadcastableEqOps, CstrBroadcastablePartialInfo,
394                   CstrBroadcastableScalar>(context);
395 }
396 
397 OpFoldResult CstrBroadcastableOp::fold(ArrayRef<Attribute> operands) {
398   // Both operands are not needed if one is a scalar.
399   if (operands[0] &&
400       operands[0].cast<DenseIntElementsAttr>().getNumElements() == 0)
401     return BoolAttr::get(true, getContext());
402   if (operands[1] &&
403       operands[1].cast<DenseIntElementsAttr>().getNumElements() == 0)
404     return BoolAttr::get(true, getContext());
405 
406   if (operands[0] && operands[1]) {
407     auto lhsShape = llvm::to_vector<6>(
408         operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
409     auto rhsShape = llvm::to_vector<6>(
410         operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>());
411     SmallVector<int64_t, 6> resultShape;
412     if (OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape))
413       return BoolAttr::get(true, getContext());
414   }
415 
416   // Lastly, see if folding can be completed based on what constraints are known
417   // on the input shapes.
418   SmallVector<int64_t, 6> lhsShape, rhsShape;
419   if (failed(getShapeVec(lhs(), lhsShape)))
420     return nullptr;
421   if (failed(getShapeVec(rhs(), rhsShape)))
422     return nullptr;
423 
424   if (OpTrait::util::staticallyKnownBroadcastable(lhsShape, rhsShape))
425     return BoolAttr::get(true, getContext());
426 
427   // Because a failing witness result here represents an eventual assertion
428   // failure, we do not replace it with a constant witness.
429   return nullptr;
430 }
431 
432 //===----------------------------------------------------------------------===//
433 // CstrEqOp
434 //===----------------------------------------------------------------------===//
435 
436 void CstrEqOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns,
437                                            MLIRContext *context) {
438   // If inputs are equal, return passing witness
439   patterns.insert<CstrEqEqOps>(context);
440 }
441 
442 OpFoldResult CstrEqOp::fold(ArrayRef<Attribute> operands) {
443   if (llvm::all_of(operands,
444                    [&](Attribute a) { return a && a == operands[0]; }))
445     return BoolAttr::get(true, getContext());
446 
447   // Because a failing witness result here represents an eventual assertion
448   // failure, we do not try to replace it with a constant witness. Similarly, we
449   // cannot if there are any non-const inputs.
450   return nullptr;
451 }
452 
453 //===----------------------------------------------------------------------===//
454 // ConstSizeOp
455 //===----------------------------------------------------------------------===//
456 
457 void ConstSizeOp::build(OpBuilder &builder, OperationState &result,
458                         int64_t value) {
459   build(builder, result, builder.getIndexAttr(value));
460 }
461 
462 OpFoldResult ConstSizeOp::fold(ArrayRef<Attribute>) { return valueAttr(); }
463 
464 void ConstSizeOp::getAsmResultNames(
465     llvm::function_ref<void(Value, StringRef)> setNameFn) {
466   SmallString<4> buffer;
467   llvm::raw_svector_ostream os(buffer);
468   os << "c" << value();
469   setNameFn(getResult(), os.str());
470 }
471 
472 //===----------------------------------------------------------------------===//
473 // ConstWitnessOp
474 //===----------------------------------------------------------------------===//
475 
476 OpFoldResult ConstWitnessOp::fold(ArrayRef<Attribute>) { return passingAttr(); }
477 
478 //===----------------------------------------------------------------------===//
479 // ShapeEqOp
480 //===----------------------------------------------------------------------===//
481 
482 OpFoldResult ShapeEqOp::fold(ArrayRef<Attribute> operands) {
483   auto lhs = operands[0].dyn_cast_or_null<DenseIntElementsAttr>();
484   if (lhs == nullptr)
485     return {};
486   auto rhs = operands[1].dyn_cast_or_null<DenseIntElementsAttr>();
487   if (rhs == nullptr)
488     return {};
489   return BoolAttr::get(lhs == rhs, getContext());
490 }
491 
492 //===----------------------------------------------------------------------===//
493 // IndexToSizeOp
494 //===----------------------------------------------------------------------===//
495 
496 OpFoldResult IndexToSizeOp::fold(ArrayRef<Attribute> operands) {
497   // Constant values of both types, `shape.size` and `index`, are represented as
498   // `IntegerAttr`s which makes constant folding simple.
499   if (Attribute arg = operands[0])
500     return arg;
501   return {};
502 }
503 
504 void IndexToSizeOp::getCanonicalizationPatterns(
505     OwningRewritePatternList &patterns, MLIRContext *context) {
506   patterns.insert<SizeToIndexToSizeCanonicalization>(context);
507 }
508 
509 //===----------------------------------------------------------------------===//
510 // FromExtentsOp
511 //===----------------------------------------------------------------------===//
512 
513 OpFoldResult FromExtentsOp::fold(ArrayRef<Attribute> operands) {
514   if (llvm::any_of(operands, [](Attribute a) { return !a; }))
515     return nullptr;
516   SmallVector<int64_t, 6> extents;
517   for (auto attr : operands)
518     extents.push_back(attr.cast<IntegerAttr>().getInt());
519   Builder builder(getContext());
520   return builder.getIndexTensorAttr(extents);
521 }
522 
523 //===----------------------------------------------------------------------===//
524 // GetExtentOp
525 //===----------------------------------------------------------------------===//
526 
527 Optional<int64_t> GetExtentOp::getConstantDim() {
528   if (auto constSizeOp = dim().getDefiningOp<ConstSizeOp>()) {
529     return constSizeOp.value().getLimitedValue();
530   }
531   return llvm::None;
532 }
533 
534 OpFoldResult GetExtentOp::fold(ArrayRef<Attribute> operands) {
535   auto elements = operands[0].dyn_cast_or_null<DenseIntElementsAttr>();
536   if (!elements)
537     return nullptr;
538   Optional<int64_t> dim = getConstantDim();
539   if (!dim.hasValue())
540     return nullptr;
541   if (dim.getValue() >= elements.getNumElements())
542     return nullptr;
543   return elements.getValue({(uint64_t)dim.getValue()});
544 }
545 
546 void GetExtentOp::build(OpBuilder &builder, OperationState &result, Value shape,
547                         int64_t dim) {
548   auto loc = result.location;
549   auto dimAttr = builder.getIndexAttr(dim);
550   Value dimValue = builder.create<ConstSizeOp>(loc, dimAttr);
551   build(builder, result, shape, dimValue);
552 }
553 
554 //===----------------------------------------------------------------------===//
555 // RankOp
556 //===----------------------------------------------------------------------===//
557 
558 OpFoldResult RankOp::fold(ArrayRef<Attribute> operands) {
559   auto shape = operands[0].dyn_cast_or_null<DenseIntElementsAttr>();
560   if (!shape)
561     return {};
562   int64_t rank = shape.getNumElements();
563   Builder builder(getContext());
564   return builder.getIndexAttr(rank);
565 }
566 
567 /// Evaluate the `rank` operation for shapes of ranked tensors at compile time.
568 /// Constant folding fails in cases where only the rank is constant, not the
569 /// shape itself.
570 /// This canonicalization matches `shape.rank(shape.shape_of(%ranked_tensor))`.
571 ///
572 /// Example:
573 ///
574 /// %shape = shape.shape_of %ranked_tensor : tensor<1x2x?xf32>
575 /// %rank = shape.rank %shape
576 ///
577 /// becomes
578 ///
579 /// %rank = shape.const_size 3
580 
581 namespace {
582 struct RankShapeOfCanonicalizationPattern : public OpRewritePattern<RankOp> {
583   using OpRewritePattern<RankOp>::OpRewritePattern;
584 
585   LogicalResult matchAndRewrite(RankOp op,
586                                 PatternRewriter &rewriter) const override {
587     auto shapeOfOp = op.shape().getDefiningOp<ShapeOfOp>();
588     if (!shapeOfOp)
589       return failure();
590     auto rankedTensorType =
591         shapeOfOp.arg().getType().dyn_cast<RankedTensorType>();
592     if (!rankedTensorType)
593       return failure();
594     int64_t rank = rankedTensorType.getRank();
595     rewriter.replaceOpWithNewOp<ConstSizeOp>(op.getOperation(), rank);
596     return success();
597   }
598 };
599 } // namespace
600 
601 void RankOp::getCanonicalizationPatterns(OwningRewritePatternList &patterns,
602                                          MLIRContext *context) {
603   patterns.insert<RankShapeOfCanonicalizationPattern>(context);
604 }
605 
606 //===----------------------------------------------------------------------===//
607 // NumElementsOp
608 //===----------------------------------------------------------------------===//
609 
610 OpFoldResult NumElementsOp::fold(ArrayRef<Attribute> operands) {
611 
612   // Fold only when argument constant.
613   Attribute shape = operands[0];
614   if (!shape)
615     return {};
616 
617   APInt product(64, 1);
618   for (auto value : shape.cast<DenseIntElementsAttr>())
619     product *= value;
620   Builder builder(getContext());
621   return builder.getIndexAttr(product.getLimitedValue());
622 }
623 
624 //===----------------------------------------------------------------------===//
625 // ShapeOfOp
626 //===----------------------------------------------------------------------===//
627 
628 OpFoldResult ShapeOfOp::fold(ArrayRef<Attribute>) {
629   auto type = getOperand().getType().dyn_cast<ShapedType>();
630   if (!type || !type.hasStaticShape())
631     return nullptr;
632   Builder builder(getContext());
633   return builder.getIndexTensorAttr(type.getShape());
634 }
635 
636 //===----------------------------------------------------------------------===//
637 // SizeToIndexOp
638 //===----------------------------------------------------------------------===//
639 
640 OpFoldResult SizeToIndexOp::fold(ArrayRef<Attribute> operands) {
641   // Constant values of both types, `shape.size` and `index`, are represented as
642   // `IntegerAttr`s which makes constant folding simple.
643   if (Attribute arg = operands[0])
644     return arg;
645   return {};
646 }
647 
648 void SizeToIndexOp::getCanonicalizationPatterns(
649     OwningRewritePatternList &patterns, MLIRContext *context) {
650   patterns.insert<IndexToSizeToIndexCanonicalization>(context);
651 }
652 
653 //===----------------------------------------------------------------------===//
654 // YieldOp
655 //===----------------------------------------------------------------------===//
656 
657 static LogicalResult verify(YieldOp op) {
658   auto *parentOp = op.getParentOp();
659   auto results = parentOp->getResults();
660   auto operands = op.getOperands();
661 
662   if (parentOp->getNumResults() != op.getNumOperands())
663     return op.emitOpError() << "number of operands does not match number of "
664                                "results of its parent";
665   for (auto e : llvm::zip(results, operands))
666     if (std::get<0>(e).getType() != std::get<1>(e).getType())
667       return op.emitOpError()
668              << "types mismatch between yield op and its parent";
669 
670   return success();
671 }
672 
673 //===----------------------------------------------------------------------===//
674 // SplitAtOp
675 //===----------------------------------------------------------------------===//
676 
677 LogicalResult SplitAtOp::fold(ArrayRef<Attribute> operands,
678                               SmallVectorImpl<OpFoldResult> &results) {
679   if (!operands[0] || !operands[1])
680     return failure();
681   auto shapeVec = llvm::to_vector<6>(
682       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
683   auto shape = llvm::makeArrayRef(shapeVec);
684   auto splitPoint = operands[1].cast<IntegerAttr>().getInt();
685   // Verify that the split point is in the correct range.
686   // TODO: Constant fold to an "error".
687   int64_t rank = shape.size();
688   if (!(-rank <= splitPoint && splitPoint <= rank))
689     return failure();
690   if (splitPoint < 0)
691     splitPoint += shape.size();
692   Builder builder(operands[0].getContext());
693   results.push_back(builder.getIndexTensorAttr(shape.take_front(splitPoint)));
694   results.push_back(builder.getIndexTensorAttr(shape.drop_front(splitPoint)));
695   return success();
696 }
697 
698 //===----------------------------------------------------------------------===//
699 // ToExtentTensorOp
700 //===----------------------------------------------------------------------===//
701 
702 OpFoldResult ToExtentTensorOp::fold(ArrayRef<Attribute> operands) {
703   if (!operands[0])
704     return nullptr;
705   Builder builder(getContext());
706   auto shape = llvm::to_vector<6>(
707       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
708   auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())},
709                                     builder.getIndexType());
710   return DenseIntElementsAttr::get(type, shape);
711 }
712 
713 //===----------------------------------------------------------------------===//
714 // ReduceOp
715 //===----------------------------------------------------------------------===//
716 
717 void ReduceOp::build(OpBuilder &builder, OperationState &result, Value shape,
718                      ValueRange initVals) {
719   result.addOperands(shape);
720   result.addOperands(initVals);
721 
722   Region *bodyRegion = result.addRegion();
723   bodyRegion->push_back(new Block);
724   Block &bodyBlock = bodyRegion->front();
725   bodyBlock.addArgument(builder.getIndexType());
726   bodyBlock.addArgument(SizeType::get(builder.getContext()));
727 
728   for (Type initValType : initVals.getTypes()) {
729     bodyBlock.addArgument(initValType);
730     result.addTypes(initValType);
731   }
732 }
733 
734 static LogicalResult verify(ReduceOp op) {
735   // Verify block arg types.
736   Block &block = op.region().front();
737 
738   // The block takes index, extent, and aggregated values as arguments.
739   auto blockArgsCount = op.initVals().size() + 2;
740   if (block.getNumArguments() != blockArgsCount)
741     return op.emitOpError() << "ReduceOp body is expected to have "
742                             << blockArgsCount << " arguments";
743 
744   // The first block argument is the index and must always be of type `index`.
745   if (!block.getArgument(0).getType().isa<IndexType>())
746     return op.emitOpError(
747         "argument 0 of ReduceOp body is expected to be of IndexType");
748 
749   // The second block argument is the extent and must be of type `size` or
750   // `index`, depending on whether the reduce operation is applied to a shape or
751   // to an extent tensor.
752   Type extentTy = block.getArgument(1).getType();
753   if (op.shape().getType().isa<ShapeType>()) {
754     if (!extentTy.isa<SizeType>())
755       return op.emitOpError("argument 1 of ReduceOp body is expected to be of "
756                             "SizeType if the ReduceOp operates on a ShapeType");
757   } else {
758     if (!extentTy.isa<IndexType>())
759       return op.emitOpError(
760           "argument 1 of ReduceOp body is expected to be of IndexType if the "
761           "ReduceOp operates on an extent tensor");
762   }
763 
764   for (auto type : llvm::enumerate(op.initVals()))
765     if (block.getArgument(type.index() + 2).getType() != type.value().getType())
766       return op.emitOpError()
767              << "type mismatch between argument " << type.index() + 2
768              << " of ReduceOp body and initial value " << type.index();
769   return success();
770 }
771 
772 static ParseResult parseReduceOp(OpAsmParser &parser, OperationState &result) {
773   // Parse operands.
774   SmallVector<OpAsmParser::OperandType, 3> operands;
775   Type shapeOrExtentTensorType;
776   if (parser.parseOperandList(operands, /*requiredOperandCount=*/-1,
777                               OpAsmParser::Delimiter::Paren) ||
778       parser.parseColonType(shapeOrExtentTensorType) ||
779       parser.parseOptionalArrowTypeList(result.types))
780     return failure();
781 
782   // Resolve operands.
783   auto initVals = llvm::makeArrayRef(operands).drop_front();
784   if (parser.resolveOperand(operands.front(), shapeOrExtentTensorType,
785                             result.operands) ||
786       parser.resolveOperands(initVals, result.types, parser.getNameLoc(),
787                              result.operands))
788     return failure();
789 
790   // Parse the body.
791   Region *body = result.addRegion();
792   if (parser.parseRegion(*body, /*args=*/{}, /*argTypes=*/{}))
793     return failure();
794 
795   // Parse attributes.
796   if (parser.parseOptionalAttrDict(result.attributes))
797     return failure();
798 
799   return success();
800 }
801 
802 static void print(OpAsmPrinter &p, ReduceOp op) {
803   p << op.getOperationName() << '(' << op.shape() << ", " << op.initVals()
804     << ") : " << op.shape().getType();
805   p.printOptionalArrowTypeList(op.getResultTypes());
806   p.printRegion(op.region());
807   p.printOptionalAttrDict(op.getAttrs());
808 }
809 
810 namespace mlir {
811 namespace shape {
812 
813 #define GET_OP_CLASSES
814 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
815 
816 } // namespace shape
817 } // namespace mlir
818