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