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