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