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