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