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/StandardOps/IR/Ops.h"
12 #include "mlir/Dialect/Tensor/IR/Tensor.h"
13 #include "mlir/Dialect/Traits.h"
14 #include "mlir/IR/Builders.h"
15 #include "mlir/IR/BuiltinTypes.h"
16 #include "mlir/IR/DialectImplementation.h"
17 #include "mlir/IR/PatternMatch.h"
18 #include "mlir/Transforms/InliningUtils.h"
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/TypeSwitch.h"
21 #include "llvm/Support/raw_ostream.h"
22 
23 using namespace mlir;
24 using namespace mlir::shape;
25 
26 namespace {
27 #include "ShapeCanonicalization.inc"
28 }
29 
30 RankedTensorType shape::getExtentTensorType(MLIRContext *ctx) {
31   return RankedTensorType::get({ShapedType::kDynamicSize}, IndexType::get(ctx));
32 }
33 
34 bool shape::isExtentTensorType(Type type) {
35   auto ranked = type.dyn_cast<RankedTensorType>();
36   return ranked && ranked.getRank() == 1 && ranked.getElementType().isIndex();
37 }
38 
39 LogicalResult shape::getShapeVec(Value input,
40                                  SmallVectorImpl<int64_t> &shapeValues) {
41   if (auto inputOp = input.getDefiningOp<ShapeOfOp>()) {
42     auto type = inputOp.arg().getType().dyn_cast<ShapedType>();
43     if (!type.hasRank())
44       return failure();
45     shapeValues = llvm::to_vector<6>(type.getShape());
46     return success();
47   } else if (auto inputOp = input.getDefiningOp<ConstShapeOp>()) {
48     shapeValues = llvm::to_vector<6>(inputOp.shape().getValues<int64_t>());
49     return success();
50   } else if (auto inputOp = input.getDefiningOp<ConstantOp>()) {
51     shapeValues = llvm::to_vector<6>(
52         inputOp.value().cast<DenseIntElementsAttr>().getValues<int64_t>());
53     return success();
54   } else {
55     return failure();
56   }
57 }
58 
59 static bool isErrorPropagationPossible(TypeRange operandTypes) {
60   return llvm::any_of(operandTypes, [](Type ty) {
61     return ty.isa<SizeType, ShapeType, ValueShapeType>();
62   });
63 }
64 
65 static LogicalResult verifySizeOrIndexOp(Operation *op) {
66   assert(op != nullptr && op->getNumResults() == 1);
67   Type resultTy = op->getResultTypes().front();
68   if (isErrorPropagationPossible(op->getOperandTypes())) {
69     if (!resultTy.isa<SizeType>())
70       return op->emitOpError()
71              << "if at least one of the operands can hold error values then "
72                 "the result must be of type `size` to propagate them";
73   }
74   return success();
75 }
76 
77 static LogicalResult verifyShapeOrExtentTensorOp(Operation *op) {
78   assert(op != nullptr && op->getNumResults() == 1);
79   Type resultTy = op->getResultTypes().front();
80   if (isErrorPropagationPossible(op->getOperandTypes())) {
81     if (!resultTy.isa<ShapeType>())
82       return op->emitOpError()
83              << "if at least one of the operands can hold error values then "
84                 "the result must be of type `shape` to propagate them";
85   }
86   return success();
87 }
88 
89 //===----------------------------------------------------------------------===//
90 // InlinerInterface
91 //===----------------------------------------------------------------------===//
92 
93 namespace {
94 /// This class defines the interface for inlining shape dialect ops.
95 struct ShapeInlinerInterface : public DialectInlinerInterface {
96   using DialectInlinerInterface::DialectInlinerInterface;
97 
98   // Returns true if the given region 'src' can be inlined into the region
99   // 'dest' that is attached to an operation registered to the current dialect.
100   bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
101                        BlockAndValueMapping &) const final {
102     return true;
103   }
104 
105   // Returns true if the given operation 'op', that is registered to this
106   // dialect, can be inlined into the region 'dest' that is attached to an
107   // operation registered to the current dialect.
108   bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned,
109                        BlockAndValueMapping &) const final {
110     return true;
111   }
112 };
113 } // namespace
114 
115 void ShapeDialect::initialize() {
116   addOperations<
117 #define GET_OP_LIST
118 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
119       >();
120   addTypes<ShapeType, SizeType, ValueShapeType, WitnessType>();
121   addInterfaces<ShapeInlinerInterface>();
122   // Allow unknown operations during prototyping and testing. As the dialect is
123   // still evolving it makes it simple to start with an unregistered ops and
124   // try different variants before actually defining the op.
125   allowUnknownOperations();
126 }
127 
128 Operation *ShapeDialect::materializeConstant(OpBuilder &builder,
129                                              Attribute value, Type type,
130                                              Location loc) {
131   if (type.isa<ShapeType>() || isExtentTensorType(type))
132     return builder.create<ConstShapeOp>(loc, type,
133                                         value.cast<DenseIntElementsAttr>());
134   if (type.isa<SizeType>())
135     return builder.create<ConstSizeOp>(loc, type, value.cast<IntegerAttr>());
136   if (type.isa<WitnessType>())
137     return builder.create<ConstWitnessOp>(loc, type, value.cast<BoolAttr>());
138   if (ConstantOp::isBuildableWith(value, type))
139     return builder.create<ConstantOp>(loc, type, value);
140   return nullptr;
141 }
142 
143 /// Parse a type registered to this dialect.
144 Type ShapeDialect::parseType(DialectAsmParser &parser) const {
145   StringRef keyword;
146   if (parser.parseKeyword(&keyword))
147     return Type();
148 
149   if (keyword == "shape")
150     return ShapeType::get(getContext());
151   if (keyword == "size")
152     return SizeType::get(getContext());
153   if (keyword == "value_shape")
154     return ValueShapeType::get(getContext());
155   if (keyword == "witness")
156     return WitnessType::get(getContext());
157 
158   parser.emitError(parser.getNameLoc(), "unknown shape type: ") << keyword;
159   return Type();
160 }
161 
162 /// Print a type registered to this dialect.
163 void ShapeDialect::printType(Type type, DialectAsmPrinter &os) const {
164   TypeSwitch<Type>(type)
165       .Case<ShapeType>([&](Type) { os << "shape"; })
166       .Case<SizeType>([&](Type) { os << "size"; })
167       .Case<ValueShapeType>([&](Type) { os << "value_shape"; })
168       .Case<WitnessType>([&](Type) { os << "witness"; })
169       .Default([](Type) { llvm_unreachable("unexpected 'shape' type kind"); });
170 }
171 
172 LogicalResult ShapeDialect::verifyOperationAttribute(Operation *op,
173                                                      NamedAttribute attribute) {
174   // Verify shape.lib attribute.
175   if (attribute.first == "shape.lib") {
176     if (!op->hasTrait<OpTrait::SymbolTable>())
177       return op->emitError(
178           "shape.lib attribute may only be on op implementing SymbolTable");
179 
180     if (auto symbolRef = attribute.second.dyn_cast<SymbolRefAttr>()) {
181       auto *symbol = SymbolTable::lookupSymbolIn(op, symbolRef);
182       if (!symbol)
183         return op->emitError("shape function library ")
184                << symbolRef << " not found";
185       return isa<shape::FunctionLibraryOp>(symbol)
186                  ? success()
187                  : op->emitError()
188                        << symbolRef << " required to be shape function library";
189     }
190 
191     if (auto arr = attribute.second.dyn_cast<ArrayAttr>()) {
192       // Verify all entries are function libraries and mappings in libraries
193       // refer to unique ops.
194       DenseSet<Identifier> key;
195       for (auto it : arr) {
196         if (!it.isa<SymbolRefAttr>())
197           return op->emitError(
198               "only SymbolRefAttr allowed in shape.lib attribute array");
199 
200         auto shapeFnLib = dyn_cast<shape::FunctionLibraryOp>(
201             SymbolTable::lookupSymbolIn(op, it.cast<SymbolRefAttr>()));
202         if (!shapeFnLib)
203           return op->emitError()
204                  << it << " does not refer to FunctionLibraryOp";
205         for (auto mapping : shapeFnLib.mapping()) {
206           if (!key.insert(mapping.first).second) {
207             return op->emitError("only one op to shape mapping allowed, found "
208                                  "multiple for `")
209                    << mapping.first << "`";
210           }
211         }
212       }
213       return success();
214     }
215 
216     return op->emitError("only SymbolRefAttr or array of SymbolRefAttrs "
217                          "allowed as shape.lib attribute");
218   }
219   return success();
220 }
221 
222 //===----------------------------------------------------------------------===//
223 // AnyOp
224 //===----------------------------------------------------------------------===//
225 
226 // TODO: Canonicalization should be implemented for shapes that can be
227 // determined through mixtures of the known dimensions of the inputs.
228 OpFoldResult AnyOp::fold(ArrayRef<Attribute> operands) {
229   // Only the last operand is checked because AnyOp is commutative.
230   if (operands.back())
231     return operands.back();
232 
233   return nullptr;
234 }
235 
236 //===----------------------------------------------------------------------===//
237 // AssumingOp
238 //===----------------------------------------------------------------------===//
239 
240 static ParseResult parseAssumingOp(OpAsmParser &parser,
241                                    OperationState &result) {
242   result.regions.reserve(1);
243   Region *doRegion = result.addRegion();
244 
245   auto &builder = parser.getBuilder();
246   OpAsmParser::OperandType cond;
247   if (parser.parseOperand(cond) ||
248       parser.resolveOperand(cond, builder.getType<WitnessType>(),
249                             result.operands))
250     return failure();
251 
252   // Parse optional results type list.
253   if (parser.parseOptionalArrowTypeList(result.types))
254     return failure();
255 
256   // Parse the region and add a terminator if elided.
257   if (parser.parseRegion(*doRegion, /*arguments=*/{}, /*argTypes=*/{}))
258     return failure();
259   AssumingOp::ensureTerminator(*doRegion, parser.getBuilder(), result.location);
260 
261   // Parse the optional attribute list.
262   if (parser.parseOptionalAttrDict(result.attributes))
263     return failure();
264   return success();
265 }
266 
267 static void print(OpAsmPrinter &p, AssumingOp op) {
268   bool yieldsResults = !op.results().empty();
269 
270   p << AssumingOp::getOperationName() << " " << op.witness();
271   if (yieldsResults) {
272     p << " -> (" << op.getResultTypes() << ")";
273   }
274   p.printRegion(op.doRegion(),
275                 /*printEntryBlockArgs=*/false,
276                 /*printBlockTerminators=*/yieldsResults);
277   p.printOptionalAttrDict(op->getAttrs());
278 }
279 
280 namespace {
281 // Removes AssumingOp with a passing witness and inlines the region.
282 struct AssumingWithTrue : public OpRewritePattern<AssumingOp> {
283   using OpRewritePattern<AssumingOp>::OpRewritePattern;
284 
285   LogicalResult matchAndRewrite(AssumingOp op,
286                                 PatternRewriter &rewriter) const override {
287     auto witness = op.witness().getDefiningOp<ConstWitnessOp>();
288     if (!witness || !witness.passingAttr())
289       return failure();
290 
291     AssumingOp::inlineRegionIntoParent(op, rewriter);
292     return success();
293   }
294 };
295 
296 struct AssumingOpRemoveUnusedResults : public OpRewritePattern<AssumingOp> {
297   using OpRewritePattern<AssumingOp>::OpRewritePattern;
298 
299   LogicalResult matchAndRewrite(AssumingOp op,
300                                 PatternRewriter &rewriter) const override {
301     Block *body = op.getBody();
302     auto yieldOp = llvm::cast<AssumingYieldOp>(body->getTerminator());
303 
304     // Find used values.
305     SmallVector<Value, 4> newYieldOperands;
306     Value opResult, yieldOperand;
307     for (auto it : llvm::zip(op.getResults(), yieldOp.operands())) {
308       std::tie(opResult, yieldOperand) = it;
309       if (!opResult.getUses().empty()) {
310         newYieldOperands.push_back(yieldOperand);
311       }
312     }
313 
314     // Rewrite only if redundant results exist.
315     if (newYieldOperands.size() == yieldOp->getNumOperands())
316       return failure();
317 
318     // Replace yield op in the old assuming op's body and move the entire region
319     // to the new assuming op.
320     rewriter.setInsertionPointToEnd(body);
321     auto newYieldOp =
322         rewriter.replaceOpWithNewOp<AssumingYieldOp>(yieldOp, newYieldOperands);
323     rewriter.setInsertionPoint(op);
324     auto newOp = rewriter.create<AssumingOp>(
325         op.getLoc(), newYieldOp->getOperandTypes(), op.witness());
326     newOp.doRegion().takeBody(op.doRegion());
327 
328     // Use the new results to replace the previously used ones.
329     SmallVector<Value, 4> replacementValues;
330     auto src = newOp.getResults().begin();
331     for (auto it : op.getResults()) {
332       if (it.getUses().empty())
333         replacementValues.push_back(nullptr);
334       else
335         replacementValues.push_back(*src++);
336     }
337     rewriter.replaceOp(op, replacementValues);
338     return success();
339   }
340 };
341 } // namespace
342 
343 void AssumingOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
344                                              MLIRContext *context) {
345   patterns.add<AssumingOpRemoveUnusedResults, AssumingWithTrue>(context);
346 }
347 
348 // See RegionBranchOpInterface in Interfaces/ControlFlowInterfaces.td
349 void AssumingOp::getSuccessorRegions(
350     Optional<unsigned> index, ArrayRef<Attribute> operands,
351     SmallVectorImpl<RegionSuccessor> &regions) {
352   // AssumingOp has unconditional control flow into the region and back to the
353   // parent, so return the correct RegionSuccessor purely based on the index
354   // being None or 0.
355   if (index.hasValue()) {
356     regions.push_back(RegionSuccessor(getResults()));
357     return;
358   }
359 
360   regions.push_back(RegionSuccessor(&doRegion()));
361 }
362 
363 void AssumingOp::inlineRegionIntoParent(AssumingOp &op,
364                                         PatternRewriter &rewriter) {
365   auto *blockBeforeAssuming = rewriter.getInsertionBlock();
366   auto *assumingBlock = op.getBody();
367   auto initPosition = rewriter.getInsertionPoint();
368   auto *blockAfterAssuming =
369       rewriter.splitBlock(blockBeforeAssuming, initPosition);
370 
371   // Remove the AssumingOp and AssumingYieldOp.
372   auto &yieldOp = assumingBlock->back();
373   rewriter.inlineRegionBefore(op.doRegion(), blockAfterAssuming);
374   rewriter.replaceOp(op, yieldOp.getOperands());
375   rewriter.eraseOp(&yieldOp);
376 
377   // Merge blocks together as there was no branching behavior from the
378   // AssumingOp.
379   rewriter.mergeBlocks(assumingBlock, blockBeforeAssuming);
380   rewriter.mergeBlocks(blockAfterAssuming, blockBeforeAssuming);
381 }
382 
383 void AssumingOp::build(
384     OpBuilder &builder, OperationState &result, Value witness,
385     function_ref<SmallVector<Value, 2>(OpBuilder &, Location)> bodyBuilder) {
386 
387   result.addOperands(witness);
388   Region *bodyRegion = result.addRegion();
389   bodyRegion->push_back(new Block);
390   Block &bodyBlock = bodyRegion->front();
391 
392   // Build body.
393   OpBuilder::InsertionGuard guard(builder);
394   builder.setInsertionPointToStart(&bodyBlock);
395   SmallVector<Value, 2> yieldValues = bodyBuilder(builder, result.location);
396   builder.create<AssumingYieldOp>(result.location, yieldValues);
397 
398   SmallVector<Type, 2> assumingTypes;
399   for (Value v : yieldValues)
400     assumingTypes.push_back(v.getType());
401   result.addTypes(assumingTypes);
402 }
403 
404 //===----------------------------------------------------------------------===//
405 // AssumingAllOp
406 //===----------------------------------------------------------------------===//
407 
408 namespace {
409 struct AssumingAllToCstrEqCanonicalization
410     : public OpRewritePattern<AssumingAllOp> {
411   using OpRewritePattern<AssumingAllOp>::OpRewritePattern;
412 
413   LogicalResult matchAndRewrite(AssumingAllOp op,
414                                 PatternRewriter &rewriter) const override {
415     SmallVector<Value, 8> shapes;
416     for (Value w : op.inputs()) {
417       auto cstrEqOp = w.getDefiningOp<CstrEqOp>();
418       if (!cstrEqOp)
419         return failure();
420       bool disjointShapes = llvm::none_of(cstrEqOp.shapes(), [&](Value s) {
421         return llvm::is_contained(shapes, s);
422       });
423       if (!shapes.empty() && !cstrEqOp.shapes().empty() && disjointShapes)
424         return failure();
425       shapes.append(cstrEqOp.shapes().begin(), cstrEqOp.shapes().end());
426     }
427     rewriter.replaceOpWithNewOp<CstrEqOp>(op, shapes);
428     return success();
429   }
430 };
431 } // namespace
432 
433 void AssumingAllOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
434                                                 MLIRContext *context) {
435   patterns.add<AssumingAllOneOp, AssumingAllToCstrEqCanonicalization>(context);
436 }
437 
438 OpFoldResult AssumingAllOp::fold(ArrayRef<Attribute> operands) {
439   // Iterate in reverse to first handle all constant operands. They are
440   // guaranteed to be the tail of the inputs because this is commutative.
441   for (int idx = operands.size() - 1; idx >= 0; idx--) {
442     Attribute a = operands[idx];
443     // Cannot fold if any inputs are not constant;
444     if (!a)
445       return nullptr;
446 
447     // We do not need to keep statically known values after handling them in
448     // this method.
449     getOperation()->eraseOperand(idx);
450 
451     // Always false if any input is statically known false
452     if (!a.cast<BoolAttr>().getValue())
453       return a;
454   }
455   // If this is reached, all inputs were statically known passing.
456   return BoolAttr::get(getContext(), true);
457 }
458 
459 static LogicalResult verify(AssumingAllOp op) {
460   // Ensure that AssumingAllOp contains at least one operand
461   if (op.getNumOperands() == 0)
462     return op.emitOpError("no operands specified");
463 
464   return success();
465 }
466 
467 void AssumingAllOp::build(OpBuilder &b, OperationState &state,
468                           ValueRange inputs) {
469   build(b, state, b.getType<WitnessType>(), inputs);
470 }
471 
472 //===----------------------------------------------------------------------===//
473 // BroadcastOp
474 //===----------------------------------------------------------------------===//
475 
476 OpFoldResult BroadcastOp::fold(ArrayRef<Attribute> operands) {
477   if (shapes().size() == 1) {
478     // Otherwise, we need a cast which would be a canonicalization, not folding.
479     if (shapes().front().getType() != getType())
480       return nullptr;
481     return shapes().front();
482   }
483 
484   // TODO: Support folding with more than 2 input shapes
485   if (shapes().size() > 2)
486     return nullptr;
487 
488   if (!operands[0] || !operands[1])
489     return nullptr;
490   auto lhsShape = llvm::to_vector<6>(
491       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
492   auto rhsShape = llvm::to_vector<6>(
493       operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>());
494   SmallVector<int64_t, 6> resultShape;
495 
496   // If the shapes are not compatible, we can't fold it.
497   // TODO: Fold to an "error".
498   if (!OpTrait::util::getBroadcastedShape(lhsShape, rhsShape, resultShape))
499     return nullptr;
500 
501   Builder builder(getContext());
502   return builder.getIndexTensorAttr(resultShape);
503 }
504 
505 static LogicalResult verify(BroadcastOp op) {
506   return verifyShapeOrExtentTensorOp(op);
507 }
508 
509 namespace {
510 template <typename OpTy>
511 struct RemoveDuplicateOperandsPattern : public OpRewritePattern<OpTy> {
512   using OpRewritePattern<OpTy>::OpRewritePattern;
513 
514   LogicalResult matchAndRewrite(OpTy op,
515                                 PatternRewriter &rewriter) const override {
516     // Find unique operands.
517     SmallVector<Value, 2> unique;
518     for (Value v : op.getOperands()) {
519       if (!llvm::is_contained(unique, v))
520         unique.push_back(v);
521     }
522 
523     // Reduce op to equivalent with unique operands.
524     if (unique.size() < op.getNumOperands()) {
525       rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(), unique,
526                                         op->getAttrs());
527       return success();
528     }
529 
530     return failure();
531   }
532 };
533 
534 template <typename OpTy>
535 struct RemoveEmptyShapeOperandsPattern : public OpRewritePattern<OpTy> {
536   using OpRewritePattern<OpTy>::OpRewritePattern;
537 
538   LogicalResult matchAndRewrite(OpTy op,
539                                 PatternRewriter &rewriter) const override {
540     auto isPotentiallyNonEmptyShape = [](Value shape) {
541       if (auto extentTensorTy = shape.getType().dyn_cast<RankedTensorType>()) {
542         if (extentTensorTy.getDimSize(0) == 0)
543           return false;
544       }
545       if (auto constShape = shape.getDefiningOp<ConstShapeOp>()) {
546         if (constShape.shape().empty())
547           return false;
548       }
549       return true;
550     };
551     auto newOperands = llvm::to_vector<8>(
552         llvm::make_filter_range(op->getOperands(), isPotentiallyNonEmptyShape));
553 
554     // Reduce op to equivalent without empty shape operands.
555     if (newOperands.size() < op.getNumOperands()) {
556       rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(), newOperands,
557                                         op->getAttrs());
558       return success();
559     }
560 
561     return failure();
562   }
563 };
564 
565 struct BroadcastForwardSingleOperandPattern
566     : public OpRewritePattern<BroadcastOp> {
567   using OpRewritePattern<BroadcastOp>::OpRewritePattern;
568 
569   LogicalResult matchAndRewrite(BroadcastOp op,
570                                 PatternRewriter &rewriter) const override {
571     if (op.getNumOperands() != 1)
572       return failure();
573     Value replacement = op.shapes().front();
574 
575     // Insert cast if needed.
576     if (replacement.getType() != op.getType()) {
577       auto loc = op.getLoc();
578       if (op.getType().isa<ShapeType>()) {
579         replacement = rewriter.create<FromExtentTensorOp>(loc, replacement);
580       } else {
581         assert(!op.getType().isa<ShapeType>() &&
582                !replacement.getType().isa<ShapeType>() &&
583                "expect extent tensor cast");
584         replacement =
585             rewriter.create<tensor::CastOp>(loc, op.getType(), replacement);
586       }
587     }
588 
589     rewriter.replaceOp(op, replacement);
590     return success();
591   }
592 };
593 
594 struct BroadcastFoldConstantOperandsPattern
595     : public OpRewritePattern<BroadcastOp> {
596   using OpRewritePattern<BroadcastOp>::OpRewritePattern;
597 
598   LogicalResult matchAndRewrite(BroadcastOp op,
599                                 PatternRewriter &rewriter) const override {
600     SmallVector<int64_t, 8> foldedConstantShape;
601     SmallVector<Value, 8> newShapeOperands;
602     for (Value shape : op.shapes()) {
603       if (auto constShape = shape.getDefiningOp<ConstShapeOp>()) {
604         SmallVector<int64_t, 8> newFoldedConstantShape;
605         if (OpTrait::util::getBroadcastedShape(
606                 foldedConstantShape,
607                 llvm::to_vector<8>(constShape.shape().getValues<int64_t>()),
608                 newFoldedConstantShape)) {
609           foldedConstantShape = newFoldedConstantShape;
610           continue;
611         }
612       }
613       newShapeOperands.push_back(shape);
614     }
615 
616     // Need at least two constant operands to fold anything.
617     if (op.getNumOperands() - newShapeOperands.size() < 2)
618       return failure();
619 
620     auto foldedConstantOperandsTy = RankedTensorType::get(
621         {static_cast<int64_t>(foldedConstantShape.size())},
622         rewriter.getIndexType());
623     newShapeOperands.push_back(rewriter.create<ConstShapeOp>(
624         op.getLoc(), foldedConstantOperandsTy,
625         rewriter.getIndexTensorAttr(foldedConstantShape)));
626     rewriter.replaceOpWithNewOp<BroadcastOp>(op, op.getType(),
627                                              newShapeOperands);
628     return success();
629   }
630 };
631 
632 template <typename OpTy>
633 struct CanonicalizeCastExtentTensorOperandsPattern
634     : public OpRewritePattern<OpTy> {
635   using OpRewritePattern<OpTy>::OpRewritePattern;
636 
637   LogicalResult matchAndRewrite(OpTy op,
638                                 PatternRewriter &rewriter) const override {
639     // Canonicalize operands.
640     bool anyChange = false;
641     auto canonicalizeOperand = [&](Value operand) {
642       if (auto castOp = operand.getDefiningOp<tensor::CastOp>()) {
643         // Only eliminate the cast if it holds no shape information.
644         bool isInformationLoosingCast =
645             castOp.getType().cast<RankedTensorType>().isDynamicDim(0);
646         if (isInformationLoosingCast) {
647           anyChange = true;
648           return castOp.source();
649         }
650       }
651       return operand;
652     };
653     auto newOperands = llvm::to_vector<8>(
654         llvm::map_range(op.getOperands(), canonicalizeOperand));
655 
656     // Rewrite op if any change required.
657     if (!anyChange)
658       return failure();
659     rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(), newOperands);
660     return success();
661   }
662 };
663 } // namespace
664 
665 void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
666                                               MLIRContext *context) {
667   patterns.add<BroadcastFoldConstantOperandsPattern,
668                BroadcastForwardSingleOperandPattern,
669                CanonicalizeCastExtentTensorOperandsPattern<BroadcastOp>,
670                RemoveDuplicateOperandsPattern<BroadcastOp>,
671                RemoveEmptyShapeOperandsPattern<BroadcastOp>>(context);
672 }
673 
674 //===----------------------------------------------------------------------===//
675 // ConcatOp
676 //===----------------------------------------------------------------------===//
677 
678 OpFoldResult ConcatOp::fold(ArrayRef<Attribute> operands) {
679   if (!operands[0] || !operands[1])
680     return nullptr;
681   auto lhsShape = llvm::to_vector<6>(
682       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
683   auto rhsShape = llvm::to_vector<6>(
684       operands[1].cast<DenseIntElementsAttr>().getValues<int64_t>());
685   SmallVector<int64_t, 6> resultShape;
686   resultShape.append(lhsShape.begin(), lhsShape.end());
687   resultShape.append(rhsShape.begin(), rhsShape.end());
688   Builder builder(getContext());
689   return builder.getIndexTensorAttr(resultShape);
690 }
691 
692 //===----------------------------------------------------------------------===//
693 // ConstShapeOp
694 //===----------------------------------------------------------------------===//
695 
696 static void print(OpAsmPrinter &p, ConstShapeOp &op) {
697   p << "shape.const_shape ";
698   p.printOptionalAttrDict(op->getAttrs(), /*elidedAttrs=*/{"shape"});
699   p << "[";
700   interleaveComma(op.shape().getValues<int64_t>(), p,
701                   [&](int64_t i) { p << i; });
702   p << "] : ";
703   p.printType(op.getType());
704 }
705 
706 static ParseResult parseConstShapeOp(OpAsmParser &parser,
707                                      OperationState &result) {
708   if (parser.parseOptionalAttrDict(result.attributes))
709     return failure();
710   // We piggy-back on ArrayAttr parsing, though we don't internally store the
711   // shape as an ArrayAttr.
712   // TODO: Implement custom parser and maybe make syntax a bit more concise.
713   Attribute extentsRaw;
714   NamedAttrList dummy;
715   if (parser.parseAttribute(extentsRaw, "dummy", dummy))
716     return failure();
717   auto extentsArray = extentsRaw.dyn_cast<ArrayAttr>();
718   if (!extentsArray)
719     return failure();
720   SmallVector<int64_t, 6> ints;
721   for (Attribute extent : extentsArray) {
722     IntegerAttr attr = extent.dyn_cast<IntegerAttr>();
723     if (!attr)
724       return failure();
725     ints.push_back(attr.getInt());
726   }
727   Builder &builder = parser.getBuilder();
728   result.addAttribute("shape", builder.getIndexTensorAttr(ints));
729   Type resultTy;
730   if (parser.parseColonType(resultTy))
731     return failure();
732   result.types.push_back(resultTy);
733   return success();
734 }
735 
736 OpFoldResult ConstShapeOp::fold(ArrayRef<Attribute>) { return shapeAttr(); }
737 
738 void ConstShapeOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
739                                                MLIRContext *context) {
740   patterns.add<TensorCastConstShape>(context);
741 }
742 
743 //===----------------------------------------------------------------------===//
744 // CstrBroadcastableOp
745 //===----------------------------------------------------------------------===//
746 
747 void CstrBroadcastableOp::getCanonicalizationPatterns(
748     RewritePatternSet &patterns, MLIRContext *context) {
749   // Canonicalization patterns have overlap with the considerations during
750   // folding in case additional shape information is inferred at some point that
751   // does not result in folding.
752   patterns.add<CanonicalizeCastExtentTensorOperandsPattern<CstrBroadcastableOp>,
753                CstrBroadcastableEqOps,
754                RemoveDuplicateOperandsPattern<CstrBroadcastableOp>,
755                RemoveEmptyShapeOperandsPattern<CstrBroadcastableOp>>(context);
756 }
757 
758 // Return true if there is exactly one attribute not representing a scalar
759 // broadcast.
760 static bool hasAtMostSingleNonScalar(ArrayRef<Attribute> attributes) {
761   bool nonScalarSeen = false;
762   for (Attribute a : attributes) {
763     if (!a || a.cast<DenseIntElementsAttr>().getNumElements() != 0) {
764       if (nonScalarSeen)
765         return false;
766       nonScalarSeen = true;
767     }
768   }
769   return true;
770 }
771 
772 OpFoldResult CstrBroadcastableOp::fold(ArrayRef<Attribute> operands) {
773   // No broadcasting is needed if all operands but one are scalar.
774   if (hasAtMostSingleNonScalar(operands))
775     return BoolAttr::get(getContext(), true);
776 
777   if ([&] {
778         SmallVector<SmallVector<int64_t, 6>, 6> extents;
779         for (const auto &operand : operands) {
780           if (!operand)
781             return false;
782           extents.push_back(llvm::to_vector<6>(
783               operand.cast<DenseIntElementsAttr>().getValues<int64_t>()));
784         }
785         return OpTrait::util::staticallyKnownBroadcastable(extents);
786       }())
787     return BoolAttr::get(getContext(), true);
788 
789   // Lastly, see if folding can be completed based on what constraints are known
790   // on the input shapes.
791   if ([&] {
792         SmallVector<SmallVector<int64_t, 6>, 6> extents;
793         for (auto shapeValue : shapes()) {
794           extents.emplace_back();
795           if (failed(getShapeVec(shapeValue, extents.back())))
796             return false;
797         }
798         return OpTrait::util::staticallyKnownBroadcastable(extents);
799       }())
800     return BoolAttr::get(getContext(), true);
801 
802   // Because a failing witness result here represents an eventual assertion
803   // failure, we do not replace it with a constant witness.
804   return nullptr;
805 }
806 
807 static LogicalResult verify(CstrBroadcastableOp op) {
808   // Ensure that AssumingAllOp contains at least one operand
809   if (op.getNumOperands() < 2)
810     return op.emitOpError("required at least 2 input shapes");
811   return success();
812 }
813 
814 //===----------------------------------------------------------------------===//
815 // CstrEqOp
816 //===----------------------------------------------------------------------===//
817 
818 void CstrEqOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
819                                            MLIRContext *context) {
820   // If inputs are equal, return passing witness
821   patterns.add<CstrEqEqOps>(context);
822 }
823 
824 OpFoldResult CstrEqOp::fold(ArrayRef<Attribute> operands) {
825   if (llvm::all_of(operands,
826                    [&](Attribute a) { return a && a == operands[0]; }))
827     return BoolAttr::get(getContext(), true);
828 
829   // Because a failing witness result here represents an eventual assertion
830   // failure, we do not try to replace it with a constant witness. Similarly, we
831   // cannot if there are any non-const inputs.
832   return nullptr;
833 }
834 
835 //===----------------------------------------------------------------------===//
836 // ConstSizeOp
837 //===----------------------------------------------------------------------===//
838 
839 void ConstSizeOp::build(OpBuilder &builder, OperationState &result,
840                         int64_t value) {
841   build(builder, result, builder.getIndexAttr(value));
842 }
843 
844 OpFoldResult ConstSizeOp::fold(ArrayRef<Attribute>) { return valueAttr(); }
845 
846 void ConstSizeOp::getAsmResultNames(
847     llvm::function_ref<void(Value, StringRef)> setNameFn) {
848   SmallString<4> buffer;
849   llvm::raw_svector_ostream os(buffer);
850   os << "c" << value();
851   setNameFn(getResult(), os.str());
852 }
853 
854 //===----------------------------------------------------------------------===//
855 // ConstWitnessOp
856 //===----------------------------------------------------------------------===//
857 
858 OpFoldResult ConstWitnessOp::fold(ArrayRef<Attribute>) { return passingAttr(); }
859 
860 //===----------------------------------------------------------------------===//
861 // CstrRequireOp
862 //===----------------------------------------------------------------------===//
863 
864 OpFoldResult CstrRequireOp::fold(ArrayRef<Attribute> operands) {
865   return operands[0];
866 }
867 
868 //===----------------------------------------------------------------------===//
869 // DivOp
870 //===----------------------------------------------------------------------===//
871 
872 OpFoldResult DivOp::fold(ArrayRef<Attribute> operands) {
873   auto lhs = operands[0].dyn_cast_or_null<IntegerAttr>();
874   if (!lhs)
875     return nullptr;
876   auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>();
877   if (!rhs)
878     return nullptr;
879 
880   // Division in APInt does not follow floor(lhs, rhs) when the result is
881   // negative. Rather, APInt rounds toward zero.
882   APInt quotient, remainder;
883   APInt::sdivrem(lhs.getValue(), rhs.getValue(), quotient, remainder);
884   if (quotient.isNegative() && !remainder.isNullValue()) {
885     quotient -= 1;
886   }
887 
888   Type indexTy = IndexType::get(getContext());
889   return IntegerAttr::get(indexTy, quotient);
890 }
891 
892 //===----------------------------------------------------------------------===//
893 // ShapeEqOp
894 //===----------------------------------------------------------------------===//
895 
896 OpFoldResult ShapeEqOp::fold(ArrayRef<Attribute> operands) {
897   bool allSame = true;
898   if (!operands.empty() && !operands[0])
899     return {};
900   for (Attribute operand : operands.drop_front(1)) {
901     if (!operand)
902       return {};
903     allSame = allSame && operand == operands[0];
904   }
905   return BoolAttr::get(getContext(), allSame);
906 }
907 
908 //===----------------------------------------------------------------------===//
909 // IndexToSizeOp
910 //===----------------------------------------------------------------------===//
911 
912 OpFoldResult IndexToSizeOp::fold(ArrayRef<Attribute> operands) {
913   // Constant values of both types, `shape.size` and `index`, are represented as
914   // `IntegerAttr`s which makes constant folding simple.
915   if (Attribute arg = operands[0])
916     return arg;
917   return {};
918 }
919 
920 void IndexToSizeOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
921                                                 MLIRContext *context) {
922   patterns.add<SizeToIndexToSizeCanonicalization>(context);
923 }
924 
925 //===----------------------------------------------------------------------===//
926 // FromExtentsOp
927 //===----------------------------------------------------------------------===//
928 
929 OpFoldResult FromExtentsOp::fold(ArrayRef<Attribute> operands) {
930   if (llvm::any_of(operands, [](Attribute a) { return !a; }))
931     return nullptr;
932   SmallVector<int64_t, 6> extents;
933   for (auto attr : operands)
934     extents.push_back(attr.cast<IntegerAttr>().getInt());
935   Builder builder(getContext());
936   return builder.getIndexTensorAttr(extents);
937 }
938 
939 //===----------------------------------------------------------------------===//
940 // FunctionLibraryOp
941 //===----------------------------------------------------------------------===//
942 
943 void FunctionLibraryOp::build(OpBuilder &builder, OperationState &result,
944                               StringRef name) {
945   result.attributes.push_back(builder.getNamedAttr(
946       ::mlir::SymbolTable::getSymbolAttrName(), builder.getStringAttr(name)));
947 }
948 
949 FuncOp FunctionLibraryOp::getShapeFunction(Operation *op) {
950   auto attr = mapping()
951                   .get(op->getName().getIdentifier())
952                   .dyn_cast_or_null<FlatSymbolRefAttr>();
953   if (!attr)
954     return nullptr;
955   return lookupSymbol<FuncOp>(attr);
956 }
957 
958 ParseResult parseFunctionLibraryOp(OpAsmParser &parser,
959                                    OperationState &result) {
960   // Parse the op name.
961   StringAttr nameAttr;
962   if (parser.parseSymbolName(nameAttr, ::mlir::SymbolTable::getSymbolAttrName(),
963                              result.attributes))
964     return failure();
965 
966   if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
967     return failure();
968 
969   auto *bodyRegion = result.addRegion();
970   if (parser.parseRegion(*bodyRegion))
971     return failure();
972 
973   if (parser.parseKeyword("mapping"))
974     return failure();
975 
976   DictionaryAttr mappingAttr;
977   if (parser.parseAttribute(mappingAttr,
978                             parser.getBuilder().getType<NoneType>(), "mapping",
979                             result.attributes))
980     return failure();
981   return success();
982 }
983 
984 void print(OpAsmPrinter &p, FunctionLibraryOp op) {
985   p << op.getOperationName() << ' ';
986   p.printSymbolName(op.getName());
987   p.printOptionalAttrDictWithKeyword(
988       op->getAttrs(), {SymbolTable::getSymbolAttrName(), "mapping"});
989   p.printRegion(op.getOperation()->getRegion(0), /*printEntryBlockArgs=*/false,
990                 /*printBlockTerminators=*/false);
991   p << " mapping ";
992   p.printAttributeWithoutType(op.mappingAttr());
993 }
994 
995 //===----------------------------------------------------------------------===//
996 // GetExtentOp
997 //===----------------------------------------------------------------------===//
998 
999 Optional<int64_t> GetExtentOp::getConstantDim() {
1000   if (auto constSizeOp = dim().getDefiningOp<ConstSizeOp>())
1001     return constSizeOp.value().getLimitedValue();
1002   if (auto constantOp = dim().getDefiningOp<ConstantOp>())
1003     return constantOp.value().cast<IntegerAttr>().getInt();
1004   return llvm::None;
1005 }
1006 
1007 OpFoldResult GetExtentOp::fold(ArrayRef<Attribute> operands) {
1008   auto elements = operands[0].dyn_cast_or_null<DenseIntElementsAttr>();
1009   if (!elements)
1010     return nullptr;
1011   Optional<int64_t> dim = getConstantDim();
1012   if (!dim.hasValue())
1013     return nullptr;
1014   if (dim.getValue() >= elements.getNumElements())
1015     return nullptr;
1016   return elements.getValue({(uint64_t)dim.getValue()});
1017 }
1018 
1019 void GetExtentOp::build(OpBuilder &builder, OperationState &result, Value shape,
1020                         int64_t dim) {
1021   auto loc = result.location;
1022   auto dimAttr = builder.getIndexAttr(dim);
1023   if (shape.getType().isa<ShapeType>()) {
1024     Value dim = builder.create<ConstSizeOp>(loc, dimAttr);
1025     build(builder, result, builder.getType<SizeType>(), shape, dim);
1026   } else {
1027     Value dim =
1028         builder.create<ConstantOp>(loc, builder.getIndexType(), dimAttr);
1029     build(builder, result, builder.getIndexType(), shape, dim);
1030   }
1031 }
1032 
1033 //===----------------------------------------------------------------------===//
1034 // IsBroadcastableOp
1035 //===----------------------------------------------------------------------===//
1036 
1037 void IsBroadcastableOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1038                                                     MLIRContext *context) {
1039   patterns.add<RemoveDuplicateOperandsPattern<IsBroadcastableOp>>(context);
1040 }
1041 
1042 OpFoldResult IsBroadcastableOp::fold(ArrayRef<Attribute> operands) {
1043   // Can always broadcast fewer than two shapes.
1044   if (operands.size() < 2) {
1045     return BoolAttr::get(getContext(), true);
1046   }
1047 
1048   return nullptr;
1049 }
1050 
1051 //===----------------------------------------------------------------------===//
1052 // RankOp
1053 //===----------------------------------------------------------------------===//
1054 
1055 OpFoldResult shape::RankOp::fold(ArrayRef<Attribute> operands) {
1056   auto shape = operands[0].dyn_cast_or_null<DenseIntElementsAttr>();
1057   if (!shape)
1058     return {};
1059   int64_t rank = shape.getNumElements();
1060   Builder builder(getContext());
1061   return builder.getIndexAttr(rank);
1062 }
1063 
1064 /// Evaluate the `rank` operation for shapes of ranked tensors at compile time.
1065 /// Constant folding fails in cases where only the rank is constant, not the
1066 /// shape itself.
1067 /// This canonicalization matches `shape.rank(shape.shape_of(%ranked_tensor))`.
1068 ///
1069 /// Example:
1070 ///
1071 /// %shape = shape.shape_of %ranked_tensor : tensor<1x2x?xf32>
1072 /// %rank = shape.rank %shape
1073 ///
1074 /// becomes
1075 ///
1076 /// %rank = shape.const_size 3
1077 
1078 namespace {
1079 struct RankShapeOfCanonicalizationPattern
1080     : public OpRewritePattern<shape::RankOp> {
1081   using OpRewritePattern<shape::RankOp>::OpRewritePattern;
1082 
1083   LogicalResult matchAndRewrite(shape::RankOp op,
1084                                 PatternRewriter &rewriter) const override {
1085     auto shapeOfOp = op.shape().getDefiningOp<ShapeOfOp>();
1086     if (!shapeOfOp)
1087       return failure();
1088     auto rankedTensorType =
1089         shapeOfOp.arg().getType().dyn_cast<RankedTensorType>();
1090     if (!rankedTensorType)
1091       return failure();
1092     int64_t rank = rankedTensorType.getRank();
1093     if (op.getType().isa<IndexType>()) {
1094       rewriter.replaceOpWithNewOp<ConstantIndexOp>(op.getOperation(), rank);
1095     } else if (op.getType().isa<shape::SizeType>()) {
1096       rewriter.replaceOpWithNewOp<shape::ConstSizeOp>(op.getOperation(), rank);
1097     } else {
1098       return failure();
1099     }
1100     return success();
1101   }
1102 };
1103 } // namespace
1104 
1105 void shape::RankOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1106                                                 MLIRContext *context) {
1107   patterns.add<RankShapeOfCanonicalizationPattern>(context);
1108 }
1109 
1110 //===----------------------------------------------------------------------===//
1111 // NumElementsOp
1112 //===----------------------------------------------------------------------===//
1113 
1114 OpFoldResult NumElementsOp::fold(ArrayRef<Attribute> operands) {
1115 
1116   // Fold only when argument constant.
1117   Attribute shape = operands[0];
1118   if (!shape)
1119     return {};
1120 
1121   APInt product(64, 1);
1122   for (auto value : shape.cast<DenseIntElementsAttr>())
1123     product *= value;
1124   Builder builder(getContext());
1125   return builder.getIndexAttr(product.getLimitedValue());
1126 }
1127 
1128 void NumElementsOp::build(OpBuilder &builder, OperationState &result,
1129                           Value shape) {
1130   if (shape.getType().isa<ShapedType>()) {
1131     auto type = builder.getIndexType();
1132     return build(builder, result, type, shape);
1133   }
1134   auto type = SizeType::get(builder.getContext());
1135   return build(builder, result, type, shape);
1136 }
1137 
1138 //===----------------------------------------------------------------------===//
1139 // MaxOp
1140 //===----------------------------------------------------------------------===//
1141 
1142 OpFoldResult MaxOp::fold(llvm::ArrayRef<mlir::Attribute> operands) {
1143   // If operands are equal, just propagate one.
1144   if (lhs() == rhs())
1145     return lhs();
1146   return nullptr;
1147 }
1148 
1149 //===----------------------------------------------------------------------===//
1150 // MinOp
1151 //===----------------------------------------------------------------------===//
1152 
1153 OpFoldResult MinOp::fold(llvm::ArrayRef<mlir::Attribute> operands) {
1154   // If operands are equal, just propagate one.
1155   if (lhs() == rhs())
1156     return lhs();
1157   return nullptr;
1158 }
1159 
1160 //===----------------------------------------------------------------------===//
1161 // MulOp
1162 //===----------------------------------------------------------------------===//
1163 
1164 OpFoldResult MulOp::fold(ArrayRef<Attribute> operands) {
1165   auto lhs = operands[0].dyn_cast_or_null<IntegerAttr>();
1166   if (!lhs)
1167     return nullptr;
1168   auto rhs = operands[1].dyn_cast_or_null<IntegerAttr>();
1169   if (!rhs)
1170     return nullptr;
1171   APInt folded = lhs.getValue() * rhs.getValue();
1172   Type indexTy = IndexType::get(getContext());
1173   return IntegerAttr::get(indexTy, folded);
1174 }
1175 
1176 //===----------------------------------------------------------------------===//
1177 // ShapeOfOp
1178 //===----------------------------------------------------------------------===//
1179 
1180 OpFoldResult ShapeOfOp::fold(ArrayRef<Attribute>) {
1181   auto type = getOperand().getType().dyn_cast<ShapedType>();
1182   if (!type || !type.hasStaticShape())
1183     return nullptr;
1184   Builder builder(getContext());
1185   return builder.getIndexTensorAttr(type.getShape());
1186 }
1187 
1188 void ShapeOfOp::build(OpBuilder &builder, OperationState &result, Value arg) {
1189   if (auto shapedTy = arg.getType().dyn_cast<ShapedType>()) {
1190     int64_t rank =
1191         shapedTy.hasRank() ? shapedTy.getRank() : ShapedType::kDynamicSize;
1192     Type indexTy = builder.getIndexType();
1193     Type extentTensorTy = RankedTensorType::get({rank}, indexTy);
1194     return ShapeOfOp::build(builder, result, extentTensorTy, arg);
1195   }
1196   Type shapeTy = builder.getType<ShapeType>();
1197   return ShapeOfOp::build(builder, result, shapeTy, arg);
1198 }
1199 
1200 namespace {
1201 struct ShapeOfWithTensor : public OpRewritePattern<shape::ShapeOfOp> {
1202   using OpRewritePattern<shape::ShapeOfOp>::OpRewritePattern;
1203 
1204   LogicalResult matchAndRewrite(shape::ShapeOfOp op,
1205                                 PatternRewriter &rewriter) const override {
1206     if (!op.arg().getType().isa<ShapedType>())
1207       return failure();
1208     if (op.getType().isa<ShapedType>())
1209       return failure();
1210 
1211     rewriter.replaceOpWithNewOp<shape::ShapeOfOp>(op.getOperation(), op.arg());
1212     return success();
1213   }
1214 };
1215 
1216 // Canonicalize
1217 // ```
1218 // %0 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<3xindex>
1219 // %1 = tensor.cast %0 : tensor<3xindex> to tensor<?xindex>
1220 // ```
1221 // to
1222 // ```
1223 // %1 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<?xindex>
1224 // ```
1225 struct ShapeOfCastExtentTensor : public OpRewritePattern<tensor::CastOp> {
1226   using OpRewritePattern<tensor::CastOp>::OpRewritePattern;
1227 
1228   LogicalResult matchAndRewrite(tensor::CastOp op,
1229                                 PatternRewriter &rewriter) const override {
1230     auto ty = op.getType().dyn_cast<RankedTensorType>();
1231     if (!ty || ty.getRank() != 1)
1232       return failure();
1233 
1234     auto shapeOfOp = op.source().getDefiningOp<ShapeOfOp>();
1235     if (!shapeOfOp)
1236       return failure();
1237 
1238     // Argument type must be ranked and must not conflict.
1239     auto argTy = shapeOfOp.arg().getType().dyn_cast<RankedTensorType>();
1240     if (!argTy || (!ty.isDynamicDim(0) && ty.getDimSize(0) != argTy.getRank()))
1241       return failure();
1242 
1243     rewriter.replaceOpWithNewOp<ShapeOfOp>(op, ty, shapeOfOp.arg());
1244     return success();
1245   }
1246 };
1247 } // namespace
1248 
1249 void ShapeOfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1250                                             MLIRContext *context) {
1251   patterns.add<ShapeOfCastExtentTensor, ShapeOfWithTensor>(context);
1252 }
1253 
1254 //===----------------------------------------------------------------------===//
1255 // SizeToIndexOp
1256 //===----------------------------------------------------------------------===//
1257 
1258 OpFoldResult SizeToIndexOp::fold(ArrayRef<Attribute> operands) {
1259   // Constant values of both types, `shape.size` and `index`, are represented as
1260   // `IntegerAttr`s which makes constant folding simple.
1261   if (Attribute arg = operands[0])
1262     return arg;
1263   return impl::foldCastOp(*this);
1264 }
1265 
1266 void SizeToIndexOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1267                                                 MLIRContext *context) {
1268   patterns.add<IndexToSizeToIndexCanonicalization>(context);
1269 }
1270 
1271 //===----------------------------------------------------------------------===//
1272 // YieldOp
1273 //===----------------------------------------------------------------------===//
1274 
1275 static LogicalResult verify(shape::YieldOp op) {
1276   auto *parentOp = op->getParentOp();
1277   auto results = parentOp->getResults();
1278   auto operands = op.getOperands();
1279 
1280   if (parentOp->getNumResults() != op.getNumOperands())
1281     return op.emitOpError() << "number of operands does not match number of "
1282                                "results of its parent";
1283   for (auto e : llvm::zip(results, operands))
1284     if (std::get<0>(e).getType() != std::get<1>(e).getType())
1285       return op.emitOpError()
1286              << "types mismatch between yield op and its parent";
1287 
1288   return success();
1289 }
1290 
1291 //===----------------------------------------------------------------------===//
1292 // SplitAtOp
1293 //===----------------------------------------------------------------------===//
1294 
1295 LogicalResult SplitAtOp::fold(ArrayRef<Attribute> operands,
1296                               SmallVectorImpl<OpFoldResult> &results) {
1297   if (!operands[0] || !operands[1])
1298     return failure();
1299   auto shapeVec = llvm::to_vector<6>(
1300       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
1301   auto shape = llvm::makeArrayRef(shapeVec);
1302   auto splitPoint = operands[1].cast<IntegerAttr>().getInt();
1303   // Verify that the split point is in the correct range.
1304   // TODO: Constant fold to an "error".
1305   int64_t rank = shape.size();
1306   if (!(-rank <= splitPoint && splitPoint <= rank))
1307     return failure();
1308   if (splitPoint < 0)
1309     splitPoint += shape.size();
1310   Builder builder(operands[0].getContext());
1311   results.push_back(builder.getIndexTensorAttr(shape.take_front(splitPoint)));
1312   results.push_back(builder.getIndexTensorAttr(shape.drop_front(splitPoint)));
1313   return success();
1314 }
1315 
1316 //===----------------------------------------------------------------------===//
1317 // ToExtentTensorOp
1318 //===----------------------------------------------------------------------===//
1319 
1320 OpFoldResult ToExtentTensorOp::fold(ArrayRef<Attribute> operands) {
1321   if (!operands[0])
1322     return impl::foldCastOp(*this);
1323   Builder builder(getContext());
1324   auto shape = llvm::to_vector<6>(
1325       operands[0].cast<DenseIntElementsAttr>().getValues<int64_t>());
1326   auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())},
1327                                     builder.getIndexType());
1328   return DenseIntElementsAttr::get(type, shape);
1329 }
1330 
1331 //===----------------------------------------------------------------------===//
1332 // ReduceOp
1333 //===----------------------------------------------------------------------===//
1334 
1335 void ReduceOp::build(OpBuilder &builder, OperationState &result, Value shape,
1336                      ValueRange initVals) {
1337   result.addOperands(shape);
1338   result.addOperands(initVals);
1339 
1340   Region *bodyRegion = result.addRegion();
1341   bodyRegion->push_back(new Block);
1342   Block &bodyBlock = bodyRegion->front();
1343   bodyBlock.addArgument(builder.getIndexType());
1344 
1345   Type elementType;
1346   if (auto tensorType = shape.getType().dyn_cast<TensorType>())
1347     elementType = tensorType.getElementType();
1348   else
1349     elementType = SizeType::get(builder.getContext());
1350   bodyBlock.addArgument(elementType);
1351 
1352   for (Type initValType : initVals.getTypes()) {
1353     bodyBlock.addArgument(initValType);
1354     result.addTypes(initValType);
1355   }
1356 }
1357 
1358 static LogicalResult verify(ReduceOp op) {
1359   // Verify block arg types.
1360   Block &block = op.region().front();
1361 
1362   // The block takes index, extent, and aggregated values as arguments.
1363   auto blockArgsCount = op.initVals().size() + 2;
1364   if (block.getNumArguments() != blockArgsCount)
1365     return op.emitOpError() << "ReduceOp body is expected to have "
1366                             << blockArgsCount << " arguments";
1367 
1368   // The first block argument is the index and must always be of type `index`.
1369   if (!block.getArgument(0).getType().isa<IndexType>())
1370     return op.emitOpError(
1371         "argument 0 of ReduceOp body is expected to be of IndexType");
1372 
1373   // The second block argument is the extent and must be of type `size` or
1374   // `index`, depending on whether the reduce operation is applied to a shape or
1375   // to an extent tensor.
1376   Type extentTy = block.getArgument(1).getType();
1377   if (op.shape().getType().isa<ShapeType>()) {
1378     if (!extentTy.isa<SizeType>())
1379       return op.emitOpError("argument 1 of ReduceOp body is expected to be of "
1380                             "SizeType if the ReduceOp operates on a ShapeType");
1381   } else {
1382     if (!extentTy.isa<IndexType>())
1383       return op.emitOpError(
1384           "argument 1 of ReduceOp body is expected to be of IndexType if the "
1385           "ReduceOp operates on an extent tensor");
1386   }
1387 
1388   for (auto type : llvm::enumerate(op.initVals()))
1389     if (block.getArgument(type.index() + 2).getType() != type.value().getType())
1390       return op.emitOpError()
1391              << "type mismatch between argument " << type.index() + 2
1392              << " of ReduceOp body and initial value " << type.index();
1393   return success();
1394 }
1395 
1396 static ParseResult parseReduceOp(OpAsmParser &parser, OperationState &result) {
1397   // Parse operands.
1398   SmallVector<OpAsmParser::OperandType, 3> operands;
1399   Type shapeOrExtentTensorType;
1400   if (parser.parseOperandList(operands, /*requiredOperandCount=*/-1,
1401                               OpAsmParser::Delimiter::Paren) ||
1402       parser.parseColonType(shapeOrExtentTensorType) ||
1403       parser.parseOptionalArrowTypeList(result.types))
1404     return failure();
1405 
1406   // Resolve operands.
1407   auto initVals = llvm::makeArrayRef(operands).drop_front();
1408   if (parser.resolveOperand(operands.front(), shapeOrExtentTensorType,
1409                             result.operands) ||
1410       parser.resolveOperands(initVals, result.types, parser.getNameLoc(),
1411                              result.operands))
1412     return failure();
1413 
1414   // Parse the body.
1415   Region *body = result.addRegion();
1416   if (parser.parseRegion(*body, /*args=*/{}, /*argTypes=*/{}))
1417     return failure();
1418 
1419   // Parse attributes.
1420   if (parser.parseOptionalAttrDict(result.attributes))
1421     return failure();
1422 
1423   return success();
1424 }
1425 
1426 static void print(OpAsmPrinter &p, ReduceOp op) {
1427   p << op.getOperationName() << '(' << op.shape() << ", " << op.initVals()
1428     << ") : " << op.shape().getType();
1429   p.printOptionalArrowTypeList(op.getResultTypes());
1430   p.printRegion(op.region());
1431   p.printOptionalAttrDict(op->getAttrs());
1432 }
1433 
1434 #define GET_OP_CLASSES
1435 #include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
1436