MLIR 24.0.0git
Shape.cpp
Go to the documentation of this file.
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
10
15#include "mlir/Dialect/Traits.h"
17#include "mlir/IR/Builders.h"
20#include "mlir/IR/Matchers.h"
25#include "llvm/ADT/SetOperations.h"
26#include "llvm/ADT/SmallVectorExtras.h"
27#include "llvm/ADT/TypeSwitch.h"
28#include "llvm/Support/raw_ostream.h"
29#include <utility>
30
31using namespace mlir;
32using namespace mlir::shape;
33
34#include "mlir/Dialect/Shape/IR/ShapeOpsDialect.cpp.inc"
35
36namespace {
37#include "ShapeCanonicalization.inc"
38} // namespace
39
40RankedTensorType shape::getExtentTensorType(MLIRContext *ctx, int64_t rank) {
41 return RankedTensorType::get({rank}, IndexType::get(ctx));
42}
43
45 auto ranked = llvm::dyn_cast<RankedTensorType>(type);
46 return ranked && ranked.getRank() == 1 && ranked.getElementType().isIndex();
47}
48
49LogicalResult shape::getShapeVec(Value input,
50 SmallVectorImpl<int64_t> &shapeValues) {
51 if (auto inputOp = input.getDefiningOp<ShapeOfOp>()) {
52 auto type = llvm::cast<ShapedType>(inputOp.getArg().getType());
53 if (!type.hasRank())
54 return failure();
55 llvm::append_range(shapeValues, type.getShape());
56 return success();
57 }
59 if (matchPattern(input, m_Constant(&attr))) {
60 llvm::append_range(shapeValues, attr.getValues<int64_t>());
61 return success();
62 }
63 return failure();
64}
65
66static bool isErrorPropagationPossible(TypeRange operandTypes) {
67 return llvm::any_of(operandTypes,
68 llvm::IsaPred<SizeType, ShapeType, ValueShapeType>);
69}
70
71static LogicalResult verifySizeOrIndexOp(Operation *op) {
72 assert(op != nullptr && op->getNumResults() == 1);
73 Type resultTy = op->getResultTypes().front();
75 if (!llvm::isa<SizeType>(resultTy))
76 return op->emitOpError()
77 << "if at least one of the operands can hold error values then "
78 "the result must be of type `size` to propagate them";
79 }
80 return success();
81}
82
83static LogicalResult verifyShapeOrExtentTensorOp(Operation *op) {
84 assert(op != nullptr && op->getNumResults() == 1);
85 Type resultTy = op->getResultTypes().front();
87 if (!llvm::isa<ShapeType>(resultTy))
88 return op->emitOpError()
89 << "if at least one of the operands can hold error values then "
90 "the result must be of type `shape` to propagate them";
91 }
92 return success();
93}
94
95template <typename... Ty>
96static bool eachHasOnlyOneOfTypes(TypeRange typeRange) {
97 return typeRange.size() == 1 && llvm::isa<Ty...>(typeRange.front());
98}
99
100template <typename... Ty, typename... ranges>
101static bool eachHasOnlyOneOfTypes(TypeRange l, ranges... rs) {
102 return eachHasOnlyOneOfTypes<Ty...>(l) && eachHasOnlyOneOfTypes<Ty...>(rs...);
103}
104
105//===----------------------------------------------------------------------===//
106// InlinerInterface
107//===----------------------------------------------------------------------===//
108
109namespace {
110/// This class defines the interface for inlining shape dialect ops.
111struct ShapeInlinerInterface : public DialectInlinerInterface {
112 using DialectInlinerInterface::DialectInlinerInterface;
113
114 // Returns true if the given region 'src' can be inlined into the region
115 // 'dest' that is attached to an operation registered to the current dialect.
116 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
117 IRMapping &) const final {
118 return true;
119 }
120
121 // Returns true if the given operation 'op', that is registered to this
122 // dialect, can be inlined into the region 'dest' that is attached to an
123 // operation registered to the current dialect.
124 bool isLegalToInline(Operation *op, Region *dest, bool wouldBeCloned,
125 IRMapping &) const final {
126 return true;
127 }
128};
129} // namespace
130
131void ShapeDialect::initialize() {
132 addOperations<
133#define GET_OP_LIST
134#include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
135 >();
136 addTypes<
137#define GET_TYPEDEF_LIST
138#include "mlir/Dialect/Shape/IR/ShapeOpsTypes.cpp.inc"
139 >();
140 addInterfaces<ShapeInlinerInterface>();
141 // Allow unknown operations during prototyping and testing. As the dialect is
142 // still evolving it makes it simple to start with an unregistered ops and
143 // try different variants before actually defining the op.
144 allowUnknownOperations();
145 declarePromisedInterfaces<bufferization::BufferizableOpInterface, AssumingOp,
146 AssumingYieldOp>();
147}
148
149Operation *ShapeDialect::materializeConstant(OpBuilder &builder,
150 Attribute value, Type type,
151 Location loc) {
152 if (auto poison = dyn_cast<ub::PoisonAttr>(value))
153 return ub::PoisonOp::create(builder, loc, type, poison);
154
155 if (llvm::isa<ShapeType>(type) || isExtentTensorType(type))
156 return ConstShapeOp::create(builder, loc, type,
157 llvm::cast<DenseIntElementsAttr>(value));
158 if (llvm::isa<SizeType>(type))
159 return ConstSizeOp::create(builder, loc, type,
160 llvm::cast<IntegerAttr>(value));
161 if (llvm::isa<WitnessType>(type))
162 return ConstWitnessOp::create(builder, loc, type,
163 llvm::cast<BoolAttr>(value));
164
165 return arith::ConstantOp::materialize(builder, value, type, loc);
166}
167
168LogicalResult ShapeDialect::verifyOperationAttribute(Operation *op,
169 NamedAttribute attribute) {
170 // Verify shape.lib attribute.
171 if (attribute.getName() == "shape.lib") {
172 if (!op->hasTrait<OpTrait::SymbolTable>())
173 return op->emitError(
174 "shape.lib attribute may only be on op implementing SymbolTable");
175
176 if (auto symbolRef = llvm::dyn_cast<SymbolRefAttr>(attribute.getValue())) {
177 auto *symbol = SymbolTable::lookupSymbolIn(op, symbolRef);
178 if (!symbol)
179 return op->emitError("shape function library ")
180 << symbolRef << " not found";
181 return isa<shape::FunctionLibraryOp>(symbol)
182 ? success()
183 : op->emitError()
184 << symbolRef << " required to be shape function library";
185 }
186
187 if (auto arr = llvm::dyn_cast<ArrayAttr>(attribute.getValue())) {
188 // Verify all entries are function libraries and mappings in libraries
189 // refer to unique ops.
191 for (auto it : arr) {
192 if (!llvm::isa<SymbolRefAttr>(it))
193 return op->emitError(
194 "only SymbolRefAttr allowed in shape.lib attribute array");
195
196 auto shapeFnLib = dyn_cast_or_null<shape::FunctionLibraryOp>(
197 SymbolTable::lookupSymbolIn(op, llvm::cast<SymbolRefAttr>(it)));
198 if (!shapeFnLib)
199 return op->emitError()
200 << it << " does not refer to FunctionLibraryOp";
201 for (auto mapping : shapeFnLib.getMapping()) {
202 if (!key.insert(mapping.getName()).second) {
203 return op->emitError("only one op to shape mapping allowed, found "
204 "multiple for `")
205 << mapping.getName() << "`";
206 }
207 }
208 }
209 return success();
210 }
211
212 return op->emitError("only SymbolRefAttr or array of SymbolRefAttrs "
213 "allowed as shape.lib attribute");
214 }
215 return success();
216}
217
218//===----------------------------------------------------------------------===//
219// AnyOp
220//===----------------------------------------------------------------------===//
221
222// TODO: Canonicalization should be implemented for shapes that can be
223// determined through mixtures of the known dimensions of the inputs.
224OpFoldResult AnyOp::fold(FoldAdaptor adaptor) {
225 // Only the last operand is checked because AnyOp is commutative.
226 if (adaptor.getInputs().back())
227 return adaptor.getInputs().back();
228
229 return nullptr;
230}
231
232//===----------------------------------------------------------------------===//
233// AssumingOp
234//===----------------------------------------------------------------------===//
235
236ParseResult AssumingOp::parse(OpAsmParser &parser, OperationState &result) {
237 result.regions.reserve(1);
238 Region *doRegion = result.addRegion();
239
240 auto &builder = parser.getBuilder();
242 if (parser.parseOperand(cond) ||
243 parser.resolveOperand(cond, builder.getType<WitnessType>(),
244 result.operands))
245 return failure();
246
247 // Parse optional results type list.
248 if (parser.parseOptionalArrowTypeList(result.types))
249 return failure();
250
251 // Parse the region and add a terminator if elided.
252 if (parser.parseRegion(*doRegion, /*arguments=*/{}, /*argTypes=*/{}))
253 return failure();
254 AssumingOp::ensureTerminator(*doRegion, parser.getBuilder(), result.location);
255
256 // Parse the optional attribute list.
257 if (parser.parseOptionalAttrDict(result.attributes))
258 return failure();
259 return success();
260}
261
262void AssumingOp::print(OpAsmPrinter &p) {
263 bool yieldsResults = !getResults().empty();
264
265 p << " " << getWitness();
266 if (yieldsResults)
267 p << " -> (" << getResultTypes() << ")";
268 p << ' ';
269 p.printRegion(getDoRegion(),
270 /*printEntryBlockArgs=*/false,
271 /*printBlockTerminators=*/yieldsResults);
272 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
273}
274
275namespace {
276// Removes AssumingOp with a passing witness and inlines the region.
277struct AssumingWithTrue : public OpRewritePattern<AssumingOp> {
278 using OpRewritePattern<AssumingOp>::OpRewritePattern;
279
280 LogicalResult matchAndRewrite(AssumingOp op,
281 PatternRewriter &rewriter) const override {
282 auto witness = op.getWitness().getDefiningOp<ConstWitnessOp>();
283 if (!witness || !witness.getPassingAttr())
284 return failure();
285
286 AssumingOp::inlineRegionIntoParent(op, rewriter);
287 return success();
288 }
289};
290
291struct AssumingOpRemoveUnusedResults : public OpRewritePattern<AssumingOp> {
292 using OpRewritePattern<AssumingOp>::OpRewritePattern;
293
294 LogicalResult matchAndRewrite(AssumingOp op,
295 PatternRewriter &rewriter) const override {
296 Block *body = op.getBody();
297 auto yieldOp = llvm::cast<AssumingYieldOp>(body->getTerminator());
298
299 // Find used values.
300 SmallVector<Value, 4> newYieldOperands;
301 for (auto [opResult, yieldOperand] :
302 llvm::zip(op.getResults(), yieldOp.getOperands())) {
303 if (!opResult.getUses().empty()) {
304 newYieldOperands.push_back(yieldOperand);
305 }
306 }
307
308 // Rewrite only if redundant results exist.
309 if (newYieldOperands.size() == yieldOp->getNumOperands())
310 return failure();
311
312 // Replace yield op in the old assuming op's body and move the entire region
313 // to the new assuming op.
314 rewriter.setInsertionPointToEnd(body);
315 auto newYieldOp =
316 rewriter.replaceOpWithNewOp<AssumingYieldOp>(yieldOp, newYieldOperands);
317 rewriter.setInsertionPoint(op);
318 auto newOp = AssumingOp::create(
319 rewriter, op.getLoc(), newYieldOp->getOperandTypes(), op.getWitness());
320 newOp.getDoRegion().takeBody(op.getDoRegion());
321
322 // Use the new results to replace the previously used ones.
323 SmallVector<Value, 4> replacementValues;
324 auto src = newOp.getResults().begin();
325 for (auto it : op.getResults()) {
326 if (it.getUses().empty())
327 replacementValues.push_back(nullptr);
328 else
329 replacementValues.push_back(*src++);
330 }
331 rewriter.replaceOp(op, replacementValues);
332 return success();
333 }
334};
335} // namespace
336
337void AssumingOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
338 MLIRContext *context) {
339 patterns.add<AssumingOpRemoveUnusedResults, AssumingWithTrue>(context);
340}
341
342// See RegionBranchOpInterface in Interfaces/ControlFlowInterfaces.td
343void AssumingOp::getSuccessorRegions(
345 // AssumingOp has unconditional control flow into the region and back to the
346 // parent, so return the correct RegionSuccessor purely based on the index
347 // being None or 0.
348 if (!point.isParent()) {
349 regions.push_back(RegionSuccessor(getOperation()));
350 return;
351 }
352
353 regions.push_back(RegionSuccessor(&getDoRegion()));
354}
355
356ValueRange AssumingOp::getSuccessorInputs(RegionSuccessor successor) {
357 return successor.isOperation() ? ValueRange(getResults()) : ValueRange();
358}
359
360void AssumingOp::inlineRegionIntoParent(AssumingOp &op,
361 PatternRewriter &rewriter) {
362 auto *blockBeforeAssuming = rewriter.getInsertionBlock();
363 auto *assumingBlock = op.getBody();
364 auto initPosition = rewriter.getInsertionPoint();
365 auto *blockAfterAssuming =
366 rewriter.splitBlock(blockBeforeAssuming, initPosition);
367
368 // Remove the AssumingOp and AssumingYieldOp.
369 auto &yieldOp = assumingBlock->back();
370 rewriter.inlineRegionBefore(op.getDoRegion(), blockAfterAssuming);
371 rewriter.replaceOp(op, yieldOp.getOperands());
372 rewriter.eraseOp(&yieldOp);
373
374 // Merge blocks together as there was no branching behavior from the
375 // AssumingOp.
376 rewriter.mergeBlocks(assumingBlock, blockBeforeAssuming);
377 rewriter.mergeBlocks(blockAfterAssuming, blockBeforeAssuming);
378}
379
380void AssumingOp::build(
381 OpBuilder &builder, OperationState &result, Value witness,
383 OpBuilder::InsertionGuard g(builder);
384
385 result.addOperands(witness);
386 Region *bodyRegion = result.addRegion();
387 builder.createBlock(bodyRegion);
388
389 // Build body.
390 SmallVector<Value, 2> yieldValues = bodyBuilder(builder, result.location);
391 AssumingYieldOp::create(builder, result.location, yieldValues);
392
393 SmallVector<Type, 2> assumingTypes;
394 for (Value v : yieldValues)
395 assumingTypes.push_back(v.getType());
396 result.addTypes(assumingTypes);
397}
398
399//===----------------------------------------------------------------------===//
400// AddOp
401//===----------------------------------------------------------------------===//
402
403LogicalResult mlir::shape::AddOp::inferReturnTypes(
404 MLIRContext *context, std::optional<Location> location,
405 AddOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
406 if (llvm::isa<SizeType>(adaptor.getLhs().getType()) ||
407 llvm::isa<SizeType>(adaptor.getRhs().getType()))
408 inferredReturnTypes.assign({SizeType::get(context)});
409 else
410 inferredReturnTypes.assign({IndexType::get(context)});
411 return success();
412}
413
414bool mlir::shape::AddOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
415 // SizeType is compatible with IndexType.
417}
418
419OpFoldResult mlir::shape::AddOp::fold(FoldAdaptor adaptor) {
420 // add(x, 0) -> x
421 if (matchPattern(getRhs(), m_Zero()))
422 return getLhs();
423
425 adaptor.getOperands(),
426 [](APInt a, const APInt &b) { return std::move(a) + b; });
427}
428
429LogicalResult shape::AddOp::verify() { return verifySizeOrIndexOp(*this); }
430
431//===----------------------------------------------------------------------===//
432// AssumingAllOp
433//===----------------------------------------------------------------------===//
434
435namespace {
436
437// Merge multiple `shape.assuming_all` operations together.
438//
439// %0 = shape.assuming_all %w0, %w1
440// %1 = shape.assuming_all %w2, %0
441//
442// to:
443//
444// %0 = shape.assuming_all %w0, %w2, %w2
445struct MergeAssumingAllOps : public OpRewritePattern<AssumingAllOp> {
446 using OpRewritePattern<AssumingAllOp>::OpRewritePattern;
447
448 LogicalResult matchAndRewrite(AssumingAllOp op,
449 PatternRewriter &rewriter) const override {
450 SmallVector<Value> operands;
451
452 for (Value operand : op.getInputs()) {
453 if (auto assumeAll = operand.getDefiningOp<AssumingAllOp>())
454 operands.append(assumeAll.operand_begin(), assumeAll->operand_end());
455 else
456 operands.push_back(operand);
457 }
458
459 // We didn't find any other `assuming_all` ops to merge with.
460 if (operands.size() == op.getNumOperands())
461 return failure();
462
463 // Replace with a new `assuming_all` operation with merged constraints.
464 rewriter.replaceOpWithNewOp<AssumingAllOp>(op, operands);
465 return success();
466 }
467};
468
469// Eliminate `cstr_broadcastable` operands from `assuming_all` operation that
470// are subsumed by others.
471//
472// %0 = shape.cstr_broadcastable %shape0, %shape1
473// %1 = shape.cstr_broadcastable %shape0, %shape1, %shape2
474//
475// %2 = shape.cstr_broadcastable %shape3, %shape4
476// %3 = shape.cstr_broadcastable %shape3, %shape4, %shape5
477//
478// %4 = shape.assuming_all %0, %1, %2, %3
479//
480// to:
481//
482// %0 = shape.cstr_broadcastable %shape0, %shape1, %shape2
483// %1 = shape.cstr_broadcastable %shape3, %shape4, %shape5
484// %2 = shape.assuming_all %0, %1
485//
486// In this example if shapes [0, 1, 2] are broadcastable, then it means that
487// shapes [0, 1] are broadcastable too, and can be removed from the list of
488// constraints. If shapes [0, 1, 2] are not broadcastable, then it doesn't
489// matter if shapes [0, 1] are broadcastable (same for shapes [3, 4, 5]).
490struct AssumingAllOfCstrBroadcastable : public OpRewritePattern<AssumingAllOp> {
491 using OpRewritePattern<AssumingAllOp>::OpRewritePattern;
492
493 LogicalResult matchAndRewrite(AssumingAllOp op,
494 PatternRewriter &rewriter) const override {
495 // Collect all `CstrBroadcastableOp` operands first.
497 for (Value operand : op.getInputs()) {
498 // TODO: Apply this optimization if some of the witnesses are not
499 // produced by the `cstr_broadcastable`.
500 auto broadcastable = operand.getDefiningOp<CstrBroadcastableOp>();
501 if (!broadcastable)
502 return failure();
503
504 operands.insert(broadcastable);
505 }
506
507 // Skip trivial `assuming_all` operations.
508 if (operands.size() <= 1)
509 return failure();
510
511 // Collect shapes checked by `cstr_broadcastable` operands.
512 SmallVector<std::pair<CstrBroadcastableOp, DenseSet<Value>>> shapes;
513 for (auto cstr : operands) {
514 DenseSet<Value> shapesSet(cstr->operand_begin(), cstr->operand_end());
515 shapes.emplace_back(cstr, std::move(shapesSet));
516 }
517
518 // Sort by the number of shape operands (larger to smaller).
519 llvm::sort(shapes, [](auto a, auto b) {
520 return a.first.getNumOperands() > b.first.getNumOperands();
521 });
522
523 // We start from the `cst_broadcastable` operations with largest number of
524 // shape operands, and remove redundant `cst_broadcastable` operations. We
525 // do this until we find a set of `cst_broadcastable` operations with
526 // non-overlapping constraints.
527 SmallVector<CstrBroadcastableOp> markedForErase;
528
529 for (unsigned i = 0; i < shapes.size(); ++i) {
530 auto isSubset = [&](auto pair) {
531 return llvm::set_is_subset(pair.second, shapes[i].second);
532 };
533
534 // Keep redundant `cstr_broadcastable` operations to be erased.
535 auto *it = std::remove_if(shapes.begin() + i + 1, shapes.end(), isSubset);
536 for (auto *it0 = it; it0 < shapes.end(); ++it0)
537 markedForErase.push_back(it0->first);
538 shapes.erase(it, shapes.end());
539 }
540
541 // We didn't find any operands that could be removed.
542 if (markedForErase.empty())
543 return failure();
544
545 // Collect non-overlapping `cst_broadcastable` constraints.
546 SmallVector<Value> uniqueConstraints;
547 for (auto &shape : shapes)
548 uniqueConstraints.push_back(shape.first.getResult());
549
550 // Replace with a new `assuming_all` operation ...
551 rewriter.replaceOpWithNewOp<AssumingAllOp>(op, uniqueConstraints);
552
553 // ... and maybe erase `cstr_broadcastable` ops without uses.
554 for (auto &op : markedForErase)
555 if (op->use_empty())
556 rewriter.eraseOp(op);
557
558 return success();
559 }
560};
561
562struct AssumingAllToCstrEqCanonicalization
563 : public OpRewritePattern<AssumingAllOp> {
564 using OpRewritePattern<AssumingAllOp>::OpRewritePattern;
565
566 LogicalResult matchAndRewrite(AssumingAllOp op,
567 PatternRewriter &rewriter) const override {
568 SmallVector<Value, 8> shapes;
569 for (Value w : op.getInputs()) {
570 auto cstrEqOp = w.getDefiningOp<CstrEqOp>();
571 if (!cstrEqOp)
572 return failure();
573 bool disjointShapes = llvm::none_of(cstrEqOp.getShapes(), [&](Value s) {
574 return llvm::is_contained(shapes, s);
575 });
576 if (!shapes.empty() && !cstrEqOp.getShapes().empty() && disjointShapes)
577 return failure();
578 shapes.append(cstrEqOp.getShapes().begin(), cstrEqOp.getShapes().end());
579 }
580 rewriter.replaceOpWithNewOp<CstrEqOp>(op, shapes);
581 return success();
582 }
583};
584
585template <typename OpTy>
586struct RemoveDuplicateOperandsPattern : public OpRewritePattern<OpTy> {
587 using OpRewritePattern<OpTy>::OpRewritePattern;
588
589 LogicalResult matchAndRewrite(OpTy op,
590 PatternRewriter &rewriter) const override {
591 // Find unique operands.
592 SetVector<Value> unique(op.operand_begin(), op.operand_end());
593
594 // Reduce op to equivalent with unique operands.
595 if (unique.size() < op.getNumOperands()) {
596 rewriter.replaceOpWithNewOp<OpTy>(
597 op, op->getResultTypes(), unique.takeVector(), op.getProperties(),
598 op->getDiscardableAttrDictionary().getValue());
599 return success();
600 }
601
602 return failure();
603 }
604};
605} // namespace
606
607void AssumingAllOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
608 MLIRContext *context) {
609 patterns
610 .add<MergeAssumingAllOps, AssumingAllOneOp,
611 AssumingAllOfCstrBroadcastable, AssumingAllToCstrEqCanonicalization,
612 RemoveDuplicateOperandsPattern<AssumingAllOp>>(context);
613}
614
615OpFoldResult AssumingAllOp::fold(FoldAdaptor adaptor) {
616 // Iterate in reverse to first handle all constant operands. They are
617 // guaranteed to be the tail of the inputs because this is commutative.
618 for (int idx = adaptor.getInputs().size() - 1; idx >= 0; idx--) {
619 Attribute a = adaptor.getInputs()[idx];
620 // Cannot fold if any inputs are not constant;
621 if (!a)
622 return nullptr;
623
624 // We do not need to keep statically known values after handling them in
625 // this method.
626 getOperation()->eraseOperand(idx);
627
628 // Always false if any input is statically known false
629 if (!llvm::cast<BoolAttr>(a).getValue())
630 return a;
631 }
632 // If this is reached, all inputs were statically known passing.
633 return BoolAttr::get(getContext(), true);
634}
635
636LogicalResult AssumingAllOp::verify() {
637 // Ensure that AssumingAllOp contains at least one operand
638 if (getNumOperands() == 0)
639 return emitOpError("no operands specified");
640
641 return success();
642}
643
644//===----------------------------------------------------------------------===//
645// BroadcastOp
646//===----------------------------------------------------------------------===//
647
648OpFoldResult BroadcastOp::fold(FoldAdaptor adaptor) {
649 if (getShapes().size() == 1) {
650 // Otherwise, we need a cast which would be a canonicalization, not folding.
651 if (getShapes().front().getType() != getType())
652 return nullptr;
653 return getShapes().front();
654 }
655
656 auto firstAttr =
657 dyn_cast_or_null<DenseIntElementsAttr>(adaptor.getShapes().front());
658 if (!firstAttr)
659 return nullptr;
660
661 SmallVector<int64_t, 6> resultShape(firstAttr.getValues<int64_t>());
662
663 for (auto next : adaptor.getShapes().drop_front()) {
664 auto nextAttr = dyn_cast_or_null<DenseIntElementsAttr>(next);
665 if (!nextAttr)
666 return nullptr;
667 auto nextShape = llvm::to_vector<6>(nextAttr.getValues<int64_t>());
668
670 // If the shapes are not compatible, we can't fold it.
671 // TODO: Fold to an "error".
672 if (!OpTrait::util::getBroadcastedShape(resultShape, nextShape, tmpShape))
673 return nullptr;
674
675 resultShape.clear();
676 std::copy(tmpShape.begin(), tmpShape.end(),
677 std::back_inserter(resultShape));
678 }
679
680 Builder builder(getContext());
681 return builder.getIndexTensorAttr(resultShape);
682}
683
684LogicalResult BroadcastOp::verify() {
685 return verifyShapeOrExtentTensorOp(*this);
686}
687
688namespace {
689template <typename OpTy>
690struct RemoveEmptyShapeOperandsPattern : public OpRewritePattern<OpTy> {
691 using OpRewritePattern<OpTy>::OpRewritePattern;
692
693 LogicalResult matchAndRewrite(OpTy op,
694 PatternRewriter &rewriter) const override {
695 auto isPotentiallyNonEmptyShape = [](Value shape) {
696 if (auto extentTensorTy =
697 llvm::dyn_cast<RankedTensorType>(shape.getType())) {
698 if (extentTensorTy.getDimSize(0) == 0)
699 return false;
700 }
701 if (auto constShape = shape.getDefiningOp<ConstShapeOp>()) {
702 if (constShape.getShape().empty())
703 return false;
704 }
705 return true;
706 };
707 auto newOperands = llvm::filter_to_vector<8>(op->getOperands(),
708 isPotentiallyNonEmptyShape);
709
710 // Replace the op with empty shape constant if all operants are reduced to
711 // be empty.
712 if (newOperands.empty()) {
713 rewriter.replaceOpWithNewOp<ConstShapeOp>(
714 op, op->getResultTypes().front(), rewriter.getIndexTensorAttr({}));
715 return success();
716 }
717
718 // Reduce op to equivalent without empty shape operands.
719 if (newOperands.size() < op.getNumOperands()) {
720 rewriter.replaceOpWithNewOp<OpTy>(
721 op, op->getResultTypes(), newOperands, op.getProperties(),
722 op->getDiscardableAttrDictionary().getValue());
723 return success();
724 }
725
726 return failure();
727 }
728};
729
730struct BroadcastForwardSingleOperandPattern
731 : public OpRewritePattern<BroadcastOp> {
732 using OpRewritePattern<BroadcastOp>::OpRewritePattern;
733
734 LogicalResult matchAndRewrite(BroadcastOp op,
735 PatternRewriter &rewriter) const override {
736 if (op.getNumOperands() != 1)
737 return failure();
738 Value replacement = op.getShapes().front();
739
740 // Insert cast if needed.
741 if (replacement.getType() != op.getType()) {
742 auto loc = op.getLoc();
743 if (llvm::isa<ShapeType>(op.getType())) {
744 replacement = FromExtentTensorOp::create(rewriter, loc, replacement);
745 } else {
746 assert(!llvm::isa<ShapeType>(op.getType()) &&
747 !llvm::isa<ShapeType>(replacement.getType()) &&
748 "expect extent tensor cast");
750 tensor::CastOp::create(rewriter, loc, op.getType(), replacement);
751 }
752 }
753
754 rewriter.replaceOp(op, replacement);
755 return success();
756 }
757};
758
759struct BroadcastFoldConstantOperandsPattern
760 : public OpRewritePattern<BroadcastOp> {
761 using OpRewritePattern<BroadcastOp>::OpRewritePattern;
762
763 LogicalResult matchAndRewrite(BroadcastOp op,
764 PatternRewriter &rewriter) const override {
765 SmallVector<int64_t, 8> foldedConstantShape;
766 SmallVector<Value, 8> newShapeOperands;
767 for (Value shape : op.getShapes()) {
768 if (auto constShape = shape.getDefiningOp<ConstShapeOp>()) {
769 SmallVector<int64_t, 8> newFoldedConstantShape;
771 foldedConstantShape,
772 llvm::to_vector<8>(constShape.getShape().getValues<int64_t>()),
773 newFoldedConstantShape)) {
774 foldedConstantShape = newFoldedConstantShape;
775 continue;
776 }
777 }
778 newShapeOperands.push_back(shape);
779 }
780
781 // Need at least two constant operands to fold anything.
782 if (op.getNumOperands() - newShapeOperands.size() < 2)
783 return failure();
784
785 auto foldedConstantOperandsTy = RankedTensorType::get(
786 {static_cast<int64_t>(foldedConstantShape.size())},
787 rewriter.getIndexType());
788 newShapeOperands.push_back(
789 ConstShapeOp::create(rewriter, op.getLoc(), foldedConstantOperandsTy,
790 rewriter.getIndexTensorAttr(foldedConstantShape)));
791 rewriter.replaceOpWithNewOp<BroadcastOp>(
792 op, TypeRange{op.getType()}, newShapeOperands, op.getProperties(),
793 op->getDiscardableAttrDictionary().getValue());
794 return success();
795 }
796};
797
798template <typename OpTy>
799struct CanonicalizeCastExtentTensorOperandsPattern
800 : public OpRewritePattern<OpTy> {
801 using OpRewritePattern<OpTy>::OpRewritePattern;
802
803 LogicalResult matchAndRewrite(OpTy op,
804 PatternRewriter &rewriter) const override {
805 // Canonicalize operands.
806 bool anyChange = false;
807 auto canonicalizeOperand = [&](Value operand) -> Value {
808 if (auto castOp = operand.getDefiningOp<tensor::CastOp>()) {
809 // Only eliminate the cast if it holds no shape information.
810 bool isInformationLoosingCast =
811 llvm::cast<RankedTensorType>(castOp.getType()).isDynamicDim(0);
812 if (isInformationLoosingCast) {
813 anyChange = true;
814 return castOp.getSource();
815 }
816 }
817 return operand;
818 };
819 auto newOperands =
820 llvm::map_to_vector<8>(op.getOperands(), canonicalizeOperand);
821
822 // Rewrite op if any change required.
823 if (!anyChange)
824 return failure();
825 rewriter.replaceOpWithNewOp<OpTy>(
826 op, op->getResultTypes(), newOperands, op.getProperties(),
827 op->getDiscardableAttrDictionary().getValue());
828 return success();
829 }
830};
831
832struct BroadcastConcretizeResultTypePattern
833 : public OpRewritePattern<BroadcastOp> {
834 using OpRewritePattern<BroadcastOp>::OpRewritePattern;
835
836 LogicalResult matchAndRewrite(BroadcastOp op,
837 PatternRewriter &rewriter) const override {
838 // Only concretize dynamic extent tensor result types.
839 auto resultTy = llvm::dyn_cast<RankedTensorType>(op.getType());
840 if (!resultTy || !resultTy.isDynamicDim(0))
841 return failure();
842
843 // Infer resulting shape rank if possible.
844 int64_t maxRank = 0;
845 for (Value shape : op.getShapes()) {
846 if (auto extentTensorTy =
847 llvm::dyn_cast<RankedTensorType>(shape.getType())) {
848 // Cannot infer resulting shape rank if any operand is dynamically
849 // ranked.
850 if (extentTensorTy.isDynamicDim(0))
851 return failure();
852 maxRank = std::max(maxRank, extentTensorTy.getDimSize(0));
853 }
854 }
855
856 auto newOp = BroadcastOp::create(rewriter, op.getLoc(),
858 op.getShapes(), /*error=*/nullptr);
859 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp);
860 return success();
861 }
862};
863} // namespace
864
865void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
866 MLIRContext *context) {
867 patterns.add<BroadcastConcretizeResultTypePattern,
868 BroadcastFoldConstantOperandsPattern,
869 BroadcastForwardSingleOperandPattern,
870 CanonicalizeCastExtentTensorOperandsPattern<BroadcastOp>,
871 RemoveDuplicateOperandsPattern<BroadcastOp>,
872 RemoveEmptyShapeOperandsPattern<BroadcastOp>>(context);
873}
874
875//===----------------------------------------------------------------------===//
876// ConcatOp
877//===----------------------------------------------------------------------===//
878
879OpFoldResult ConcatOp::fold(FoldAdaptor adaptor) {
880 if (!adaptor.getLhs() || !adaptor.getRhs())
881 return nullptr;
882 auto lhsShape = llvm::to_vector<6>(
883 llvm::cast<DenseIntElementsAttr>(adaptor.getLhs()).getValues<int64_t>());
884 auto rhsShape = llvm::to_vector<6>(
885 llvm::cast<DenseIntElementsAttr>(adaptor.getRhs()).getValues<int64_t>());
886 SmallVector<int64_t, 6> resultShape;
887 resultShape.append(lhsShape.begin(), lhsShape.end());
888 resultShape.append(rhsShape.begin(), rhsShape.end());
889 Builder builder(getContext());
890 return builder.getIndexTensorAttr(resultShape);
891}
892
893//===----------------------------------------------------------------------===//
894// ConstShapeOp
895//===----------------------------------------------------------------------===//
896
897void ConstShapeOp::print(OpAsmPrinter &p) {
898 p << " ";
899 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
900 p << "[";
901 interleaveComma(getShape().getValues<int64_t>(), p);
902 p << "] : ";
903 p.printType(getType());
904}
905
906ParseResult ConstShapeOp::parse(OpAsmParser &parser, OperationState &result) {
907 if (parser.parseOptionalAttrDict(result.attributes))
908 return failure();
909 // We piggy-back on ArrayAttr parsing, though we don't internally store the
910 // shape as an ArrayAttr.
911 // TODO: Implement custom parser and maybe make syntax a bit more concise.
912 Attribute extentsRaw;
913 NamedAttrList dummy;
914 if (parser.parseAttribute(extentsRaw, "dummy", dummy))
915 return failure();
916 auto extentsArray = llvm::dyn_cast<ArrayAttr>(extentsRaw);
917 if (!extentsArray)
918 return failure();
920 for (Attribute extent : extentsArray) {
921 IntegerAttr attr = llvm::dyn_cast<IntegerAttr>(extent);
922 if (!attr)
923 return failure();
924 ints.push_back(attr.getInt());
925 }
926 Builder &builder = parser.getBuilder();
927 result.addAttribute("shape", builder.getIndexTensorAttr(ints));
928 Type resultTy;
929 if (parser.parseColonType(resultTy))
930 return failure();
931 result.types.push_back(resultTy);
932 return success();
933}
934
935OpFoldResult ConstShapeOp::fold(FoldAdaptor) { return getShapeAttr(); }
936
937void ConstShapeOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
938 MLIRContext *context) {
939 patterns.add<TensorCastConstShape>(context);
940}
941
942LogicalResult mlir::shape::ConstShapeOp::inferReturnTypes(
943 MLIRContext *context, std::optional<Location> location,
944 ConstShapeOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
945 Builder b(context);
946 const Properties prop = adaptor.getProperties();
947 inferredReturnTypes.assign({RankedTensorType::get(
948 {static_cast<int64_t>(prop.shape.size())}, b.getIndexType())});
949 return success();
950}
951
952bool mlir::shape::ConstShapeOp::isCompatibleReturnTypes(TypeRange l,
953 TypeRange r) {
954 if (l.size() != 1 || r.size() != 1)
955 return false;
956
957 Type lhs = l.front();
958 Type rhs = r.front();
959
960 if (llvm::isa<ShapeType>(lhs) || llvm::isa<ShapeType>(rhs))
961 // Shape type is compatible with all other valid return types.
962 return true;
963 return lhs == rhs;
964}
965
966//===----------------------------------------------------------------------===//
967// CstrBroadcastableOp
968//===----------------------------------------------------------------------===//
969
970void CstrBroadcastableOp::getCanonicalizationPatterns(
971 RewritePatternSet &patterns, MLIRContext *context) {
972 // Canonicalization patterns have overlap with the considerations during
973 // folding in case additional shape information is inferred at some point that
974 // does not result in folding.
975 patterns.add<CanonicalizeCastExtentTensorOperandsPattern<CstrBroadcastableOp>,
976 CstrBroadcastableEqOps,
977 RemoveDuplicateOperandsPattern<CstrBroadcastableOp>,
978 RemoveEmptyShapeOperandsPattern<CstrBroadcastableOp>>(context);
979}
980
981// Return true if there is exactly one attribute not representing a scalar
982// broadcast.
984 bool nonScalarSeen = false;
985 for (Attribute a : attributes) {
986 if (!a || llvm::cast<DenseIntElementsAttr>(a).getNumElements() != 0) {
987 if (nonScalarSeen)
988 return false;
989 nonScalarSeen = true;
990 }
991 }
992 return true;
993}
994
995OpFoldResult CstrBroadcastableOp::fold(FoldAdaptor adaptor) {
996 // No broadcasting is needed if all operands but one are scalar.
997 if (hasAtMostSingleNonScalar(adaptor.getShapes()))
998 return BoolAttr::get(getContext(), true);
999
1000 if ([&] {
1002 for (const auto &operand : adaptor.getShapes()) {
1003 if (!operand)
1004 return false;
1005 extents.push_back(llvm::to_vector<6>(
1006 llvm::cast<DenseIntElementsAttr>(operand).getValues<int64_t>()));
1007 }
1009 }())
1010 return BoolAttr::get(getContext(), true);
1011
1012 // Lastly, see if folding can be completed based on what constraints are known
1013 // on the input shapes.
1014 if ([&] {
1016 for (auto shapeValue : getShapes()) {
1017 extents.emplace_back();
1018 if (failed(getShapeVec(shapeValue, extents.back())))
1019 return false;
1020 }
1022 }())
1023 return BoolAttr::get(getContext(), true);
1024
1025 // Because a failing witness result here represents an eventual assertion
1026 // failure, we do not replace it with a constant witness.
1027 return nullptr;
1028}
1029
1030LogicalResult CstrBroadcastableOp::verify() {
1031 // Ensure that CstrBroadcastableOp contains at least two operands
1032 if (getNumOperands() < 2)
1033 return emitOpError("required at least 2 input shapes");
1034 return success();
1035}
1036
1037//===----------------------------------------------------------------------===//
1038// CstrEqOp
1039//===----------------------------------------------------------------------===//
1040
1041void CstrEqOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1042 MLIRContext *context) {
1043 // If inputs are equal, return passing witness
1044 patterns.add<CstrEqEqOps>(context);
1045}
1046
1047OpFoldResult CstrEqOp::fold(FoldAdaptor adaptor) {
1048 if (llvm::all_of(adaptor.getShapes(), [&](Attribute a) {
1049 return a && a == adaptor.getShapes().front();
1050 }))
1051 return BoolAttr::get(getContext(), true);
1052
1053 // Because a failing witness result here represents an eventual assertion
1054 // failure, we do not try to replace it with a constant witness. Similarly, we
1055 // cannot if there are any non-const inputs.
1056 return nullptr;
1057}
1058
1059//===----------------------------------------------------------------------===//
1060// ConstSizeOp
1061//===----------------------------------------------------------------------===//
1062
1063void ConstSizeOp::build(OpBuilder &builder, OperationState &result,
1064 int64_t value) {
1065 build(builder, result, builder.getIndexAttr(value));
1066}
1067
1068OpFoldResult ConstSizeOp::fold(FoldAdaptor) { return getValueAttr(); }
1069
1070void ConstSizeOp::getAsmResultNames(
1071 llvm::function_ref<void(Value, StringRef)> setNameFn) {
1072 SmallString<4> buffer;
1073 llvm::raw_svector_ostream os(buffer);
1074 os << "c" << getValue();
1075 setNameFn(getResult(), os.str());
1076}
1077
1078//===----------------------------------------------------------------------===//
1079// ConstWitnessOp
1080//===----------------------------------------------------------------------===//
1081
1082OpFoldResult ConstWitnessOp::fold(FoldAdaptor) { return getPassingAttr(); }
1083
1084//===----------------------------------------------------------------------===//
1085// CstrRequireOp
1086//===----------------------------------------------------------------------===//
1087
1088OpFoldResult CstrRequireOp::fold(FoldAdaptor adaptor) {
1089 return adaptor.getPred();
1090}
1091
1092//===----------------------------------------------------------------------===//
1093// DimOp
1094//===----------------------------------------------------------------------===//
1095
1096std::optional<int64_t> DimOp::getConstantIndex() {
1097 if (auto constSizeOp = getIndex().getDefiningOp<ConstSizeOp>())
1098 return constSizeOp.getValue().getLimitedValue();
1099 if (auto constantOp = getIndex().getDefiningOp<arith::ConstantOp>())
1100 return llvm::cast<IntegerAttr>(constantOp.getValue()).getInt();
1101 return std::nullopt;
1102}
1103
1104OpFoldResult DimOp::fold(FoldAdaptor adaptor) {
1105 Type valType = getValue().getType();
1106 auto valShapedType = llvm::dyn_cast<ShapedType>(valType);
1107 if (!valShapedType || !valShapedType.hasRank())
1108 return nullptr;
1109 std::optional<int64_t> index = getConstantIndex();
1110 if (!index.has_value())
1111 return nullptr;
1112 if (index.value() < 0 || index.value() >= valShapedType.getRank())
1113 return nullptr;
1114 auto extent = valShapedType.getDimSize(*index);
1115 if (ShapedType::isDynamic(extent))
1116 return nullptr;
1117 return IntegerAttr::get(IndexType::get(getContext()), extent);
1118}
1119
1120LogicalResult mlir::shape::DimOp::inferReturnTypes(
1121 MLIRContext *context, std::optional<Location> location,
1122 DimOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1123 inferredReturnTypes.assign({adaptor.getIndex().getType()});
1124 return success();
1125}
1126
1127bool mlir::shape::DimOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1129}
1130
1131//===----------------------------------------------------------------------===//
1132// DivOp
1133//===----------------------------------------------------------------------===//
1134
1135OpFoldResult DivOp::fold(FoldAdaptor adaptor) {
1136 auto lhs = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getLhs());
1137 if (!lhs)
1138 return nullptr;
1139 auto rhs = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getRhs());
1140 if (!rhs || rhs.getValue().isZero())
1141 return nullptr;
1142
1143 // Division in APInt does not follow floor(lhs, rhs) when the result is
1144 // negative. Rather, APInt rounds toward zero.
1145 APInt quotient, remainder;
1146 APInt::sdivrem(lhs.getValue(), rhs.getValue(), quotient, remainder);
1147 if (quotient.isNegative() && !remainder.isZero()) {
1148 quotient -= 1;
1149 }
1150
1151 Type indexTy = IndexType::get(getContext());
1152 return IntegerAttr::get(indexTy, quotient);
1153}
1154
1155LogicalResult mlir::shape::DivOp::inferReturnTypes(
1156 MLIRContext *context, std::optional<Location> location,
1157 DivOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1158 if (llvm::isa<SizeType>(adaptor.getLhs().getType()) ||
1159 llvm::isa<SizeType>(adaptor.getRhs().getType()))
1160 inferredReturnTypes.assign({SizeType::get(context)});
1161 else
1162 inferredReturnTypes.assign({IndexType::get(context)});
1163 return success();
1164}
1165
1166bool mlir::shape::DivOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1167 // SizeType is compatible with IndexType.
1169}
1170
1171LogicalResult DivOp::verify() { return verifySizeOrIndexOp(*this); }
1172
1173//===----------------------------------------------------------------------===//
1174// ShapeEqOp
1175//===----------------------------------------------------------------------===//
1176
1177OpFoldResult ShapeEqOp::fold(FoldAdaptor adaptor) {
1178 bool allSame = true;
1179 if (!adaptor.getShapes().empty() && !adaptor.getShapes().front())
1180 return {};
1181 for (Attribute operand : adaptor.getShapes().drop_front()) {
1182 if (!operand)
1183 return {};
1184 allSame = allSame && operand == adaptor.getShapes().front();
1185 }
1186 return BoolAttr::get(getContext(), allSame);
1187}
1188
1189//===----------------------------------------------------------------------===//
1190// IndexToSizeOp
1191//===----------------------------------------------------------------------===//
1192
1193OpFoldResult IndexToSizeOp::fold(FoldAdaptor adaptor) {
1194 // Constant values of both types, `shape.size` and `index`, are represented as
1195 // `IntegerAttr`s which makes constant folding simple.
1196 if (Attribute arg = adaptor.getArg())
1197 return arg;
1198 return {};
1199}
1200
1201void IndexToSizeOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1202 MLIRContext *context) {
1203 patterns.add<SizeToIndexToSizeCanonicalization>(context);
1204}
1205
1206//===----------------------------------------------------------------------===//
1207// FromExtentsOp
1208//===----------------------------------------------------------------------===//
1209
1210OpFoldResult FromExtentsOp::fold(FoldAdaptor adaptor) {
1212 for (Attribute attr : adaptor.getExtents()) {
1213 auto intAttr = llvm::dyn_cast_if_present<IntegerAttr>(attr);
1214 if (!intAttr)
1215 return nullptr;
1216 extents.push_back(intAttr.getInt());
1217 }
1218 Builder builder(getContext());
1219 return builder.getIndexTensorAttr(extents);
1220}
1221
1222//===----------------------------------------------------------------------===//
1223// FunctionLibraryOp
1224//===----------------------------------------------------------------------===//
1225
1226void FunctionLibraryOp::build(OpBuilder &builder, OperationState &result,
1227 StringRef name) {
1228 result.getOrAddProperties<Properties>().sym_name =
1229 builder.getStringAttr(name);
1230}
1231
1232FuncOp FunctionLibraryOp::getShapeFunction(Operation *op) {
1233 auto attr = llvm::dyn_cast_or_null<FlatSymbolRefAttr>(
1234 getMapping().get(op->getName().getIdentifier()));
1235 if (!attr)
1236 return nullptr;
1237 return lookupSymbol<FuncOp>(attr);
1238}
1239
1240ParseResult FunctionLibraryOp::parse(OpAsmParser &parser,
1242 // Parse the op name.
1243 StringAttr nameAttr;
1244 if (parser.parseSymbolName(nameAttr, getSymNameAttrName(result.name),
1245 result.attributes))
1246 return failure();
1247
1248 if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
1249 return failure();
1250
1251 auto *bodyRegion = result.addRegion();
1252 if (parser.parseRegion(*bodyRegion))
1253 return failure();
1254
1255 if (parser.parseKeyword("mapping"))
1256 return failure();
1257
1258 DictionaryAttr mappingAttr;
1259 if (parser.parseAttribute(mappingAttr,
1260 parser.getBuilder().getType<NoneType>(), "mapping",
1261 result.attributes))
1262 return failure();
1263 return success();
1264}
1265
1266void FunctionLibraryOp::print(OpAsmPrinter &p) {
1267 p << ' ';
1268 p.printSymbolName(getName());
1270 (*this)->getDiscardableAttrDictionary().getValue());
1271 p << ' ';
1272 p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
1273 /*printBlockTerminators=*/false);
1274 p << " mapping ";
1275 p.printAttributeWithoutType(getMappingAttr());
1276}
1277
1278//===----------------------------------------------------------------------===//
1279// FuncOp
1280//===----------------------------------------------------------------------===//
1281
1282FuncOp FuncOp::create(Location location, StringRef name, FunctionType type,
1284 OpBuilder builder(location->getContext());
1285 OperationState state(location, getOperationName());
1286 FuncOp::build(builder, state, name, type, attrs);
1287 return cast<FuncOp>(Operation::create(state));
1288}
1289FuncOp FuncOp::create(Location location, StringRef name, FunctionType type,
1291 SmallVector<NamedAttribute, 8> attrRef(attrs);
1292 return create(location, name, type, llvm::ArrayRef(attrRef));
1293}
1294FuncOp FuncOp::create(Location location, StringRef name, FunctionType type,
1296 ArrayRef<DictionaryAttr> argAttrs) {
1297 FuncOp func = create(location, name, type, attrs);
1298 func.setAllArgAttrs(argAttrs);
1299 return func;
1300}
1301
1302void FuncOp::build(OpBuilder &builder, OperationState &state, StringRef name,
1303 FunctionType type, ArrayRef<NamedAttribute> attrs,
1304 ArrayRef<DictionaryAttr> argAttrs) {
1305 state.addAttribute(FuncOp::getSymNameAttrName(state.name),
1306 builder.getStringAttr(name));
1307 state.addAttribute(FuncOp::getFunctionTypeAttrName(state.name),
1308 TypeAttr::get(type));
1309 state.attributes.append(attrs.begin(), attrs.end());
1310 state.addRegion();
1311
1312 if (argAttrs.empty())
1313 return;
1314 assert(type.getNumInputs() == argAttrs.size());
1316 builder, state, argAttrs, /*resultAttrs=*/{},
1317 getArgAttrsAttrName(state.name), getResAttrsAttrName(state.name));
1318}
1319
1320ParseResult FuncOp::parse(OpAsmParser &parser, OperationState &result) {
1321 auto buildFuncType =
1322 [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
1324 std::string &) { return builder.getFunctionType(argTypes, results); };
1325
1327 parser, result, /*allowVariadic=*/false,
1328 getFunctionTypeAttrName(result.name), buildFuncType,
1329 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
1330}
1331
1332void FuncOp::print(OpAsmPrinter &p) {
1334 p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(),
1335 getArgAttrsAttrName(), getResAttrsAttrName());
1336}
1337
1338//===----------------------------------------------------------------------===//
1339// GetExtentOp
1340//===----------------------------------------------------------------------===//
1341
1342std::optional<int64_t> GetExtentOp::getConstantDim() {
1343 if (auto constSizeOp = getDim().getDefiningOp<ConstSizeOp>())
1344 return constSizeOp.getValue().getLimitedValue();
1345 if (auto constantOp = getDim().getDefiningOp<arith::ConstantOp>())
1346 return llvm::cast<IntegerAttr>(constantOp.getValue()).getInt();
1347 return std::nullopt;
1348}
1349
1350OpFoldResult GetExtentOp::fold(FoldAdaptor adaptor) {
1351 auto elements =
1352 llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getShape());
1353 if (!elements)
1354 return nullptr;
1355 std::optional<int64_t> dim = getConstantDim();
1356 if (!dim.has_value())
1357 return nullptr;
1358 if (dim.value() >= elements.getNumElements())
1359 return nullptr;
1360 return elements.getValues<Attribute>()[(uint64_t)dim.value()];
1361}
1362
1363void GetExtentOp::build(OpBuilder &builder, OperationState &result, Value shape,
1364 int64_t dim) {
1365 auto loc = result.location;
1366 auto dimAttr = builder.getIndexAttr(dim);
1367 if (llvm::isa<ShapeType>(shape.getType())) {
1368 Value dim = ConstSizeOp::create(builder, loc, dimAttr);
1369 build(builder, result, builder.getType<SizeType>(), shape, dim);
1370 } else {
1371 Value dim = arith::ConstantOp::create(builder, loc, builder.getIndexType(),
1372 dimAttr);
1373 build(builder, result, builder.getIndexType(), shape, dim);
1374 }
1375}
1376
1377LogicalResult mlir::shape::GetExtentOp::inferReturnTypes(
1378 MLIRContext *context, std::optional<Location> location,
1379 GetExtentOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1380 inferredReturnTypes.assign({IndexType::get(context)});
1381 return success();
1382}
1383
1384bool mlir::shape::GetExtentOp::isCompatibleReturnTypes(TypeRange l,
1385 TypeRange r) {
1386 // SizeType is compatible with IndexType.
1388}
1389
1390LogicalResult GetExtentOp::verify() { return verifySizeOrIndexOp(*this); }
1391
1392//===----------------------------------------------------------------------===//
1393// IsBroadcastableOp
1394//===----------------------------------------------------------------------===//
1395
1396void IsBroadcastableOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1397 MLIRContext *context) {
1398 patterns.add<RemoveDuplicateOperandsPattern<IsBroadcastableOp>>(context);
1399}
1400
1401OpFoldResult IsBroadcastableOp::fold(FoldAdaptor adaptor) {
1402 // Can always broadcast fewer than two shapes.
1403 if (adaptor.getShapes().size() < 2) {
1404 return BoolAttr::get(getContext(), true);
1405 }
1406
1407 return nullptr;
1408}
1409
1410//===----------------------------------------------------------------------===//
1411// MeetOp
1412//===----------------------------------------------------------------------===//
1413
1414LogicalResult mlir::shape::MeetOp::inferReturnTypes(
1415 MLIRContext *context, std::optional<Location> location,
1416 MeetOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1417 if (adaptor.getOperands().empty())
1418 return failure();
1419
1420 auto isShapeType = [](Type arg) {
1421 if (llvm::isa<ShapeType>(arg))
1422 return true;
1423 return isExtentTensorType(arg);
1424 };
1425
1426 ValueRange::type_range types = adaptor.getOperands().getTypes();
1427 Type acc = types.front();
1428 for (auto t : drop_begin(types)) {
1429 Type l = acc, r = t;
1430 if (!llvm::isa<ShapeType, SizeType>(l))
1431 std::swap(l, r);
1432
1433 // Handle sizes, propagate error type if present.
1434 if (llvm::isa<SizeType>(l)) {
1435 if (llvm::isa<SizeType, IndexType>(r))
1436 acc = l;
1437 else
1438 return emitOptionalError(location, "requires all sizes or shapes");
1439 } else if (llvm::isa<IndexType>(l)) {
1440 if (llvm::isa<IndexType>(r))
1441 acc = r;
1442 else
1443 return emitOptionalError(location, "requires all sizes or shapes");
1444 } else if (llvm::isa<ShapeType>(l)) {
1445 // Handle shapes, propagate error type if present.
1446 if (isShapeType(r))
1447 acc = l;
1448 else
1449 return emitOptionalError(location, "requires all sizes or shapes");
1450 } else if (isExtentTensorType(l)) {
1451 auto rank1 = llvm::cast<RankedTensorType>(l).getShape()[0];
1452 auto rank2 = llvm::cast<RankedTensorType>(r).getShape()[0];
1453 if (ShapedType::isDynamic(rank1))
1454 acc = l;
1455 else if (ShapedType::isDynamic(rank2))
1456 acc = r;
1457 else if (rank1 != rank2)
1458 return emitOptionalError(location, "unequal shape cardinality");
1459 else
1460 acc = l;
1461 }
1462 }
1463 inferredReturnTypes.assign({acc});
1464 return success();
1465}
1466
1467bool mlir::shape::MeetOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1468 if (l.size() != 1 || r.size() != 1)
1469 return false;
1470 if (l == r)
1471 return true;
1472
1473 Type lhs = l.front();
1474 Type rhs = r.front();
1475
1476 if (!llvm::isa<ShapeType, SizeType>(lhs))
1477 std::swap(lhs, rhs);
1478
1479 if (llvm::isa<SizeType>(lhs))
1480 return llvm::isa<SizeType, IndexType>(rhs);
1481 if (llvm::isa<ShapeType>(lhs))
1482 return llvm::isa<ShapeType, TensorType>(rhs);
1483
1484 if (succeeded(verifyCompatibleShapes({lhs, rhs})))
1485 return true;
1486 return false;
1487}
1488
1489//===----------------------------------------------------------------------===//
1490// RankOp
1491//===----------------------------------------------------------------------===//
1492
1493OpFoldResult shape::RankOp::fold(FoldAdaptor adaptor) {
1494 auto shape =
1495 llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getShape());
1496 if (!shape)
1497 return {};
1498 int64_t rank = shape.getNumElements();
1499 Builder builder(getContext());
1500 return builder.getIndexAttr(rank);
1501}
1502
1503/// Evaluate the `rank` operation for shapes of ranked tensors at compile time.
1504/// Constant folding fails in cases where only the rank is constant, not the
1505/// shape itself.
1506/// This canonicalization matches `shape.rank(shape.shape_of(%ranked_tensor))`.
1507///
1508/// Example:
1509///
1510/// %shape = shape.shape_of %ranked_tensor : tensor<1x2x?xf32>
1511/// %rank = shape.rank %shape
1512///
1513/// becomes
1514///
1515/// %rank = shape.const_size 3
1516
1517namespace {
1518struct RankShapeOfCanonicalizationPattern
1519 : public OpRewritePattern<shape::RankOp> {
1520 using OpRewritePattern<shape::RankOp>::OpRewritePattern;
1521
1522 LogicalResult matchAndRewrite(shape::RankOp op,
1523 PatternRewriter &rewriter) const override {
1524 auto shapeOfOp = op.getShape().getDefiningOp<ShapeOfOp>();
1525 if (!shapeOfOp)
1526 return failure();
1527 auto rankedTensorType =
1528 llvm::dyn_cast<RankedTensorType>(shapeOfOp.getArg().getType());
1529 if (!rankedTensorType)
1530 return failure();
1531 int64_t rank = rankedTensorType.getRank();
1532 if (llvm::isa<IndexType>(op.getType())) {
1533 rewriter.replaceOpWithNewOp<arith::ConstantIndexOp>(op.getOperation(),
1534 rank);
1535 } else if (llvm::isa<shape::SizeType>(op.getType())) {
1536 rewriter.replaceOpWithNewOp<shape::ConstSizeOp>(op.getOperation(), rank);
1537 } else {
1538 return failure();
1539 }
1540 return success();
1541 }
1542};
1543} // namespace
1544
1545void shape::RankOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1546 MLIRContext *context) {
1547 patterns.add<RankShapeOfCanonicalizationPattern>(context);
1548}
1549
1550LogicalResult mlir::shape::RankOp::inferReturnTypes(
1551 MLIRContext *context, std::optional<Location> location,
1552 RankOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1553 if (llvm::isa<ShapeType>(adaptor.getShape().getType()))
1554 inferredReturnTypes.assign({SizeType::get(context)});
1555 else
1556 inferredReturnTypes.assign({IndexType::get(context)});
1557 return success();
1558}
1559
1560bool mlir::shape::RankOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1561 // SizeType is compatible with IndexType.
1563}
1564
1565LogicalResult shape::RankOp::verify() { return verifySizeOrIndexOp(*this); }
1566
1567//===----------------------------------------------------------------------===//
1568// NumElementsOp
1569//===----------------------------------------------------------------------===//
1570
1571OpFoldResult NumElementsOp::fold(FoldAdaptor adaptor) {
1572
1573 // Fold only when argument constant.
1574 Attribute shape = adaptor.getShape();
1575 if (!shape)
1576 return {};
1577
1578 APInt product(64, 1);
1579 for (auto value : llvm::cast<DenseIntElementsAttr>(shape))
1580 product *= value;
1581 Builder builder(getContext());
1582 return builder.getIndexAttr(product.getLimitedValue());
1583}
1584
1585LogicalResult mlir::shape::NumElementsOp::inferReturnTypes(
1586 MLIRContext *context, std::optional<Location> location,
1587 NumElementsOp::Adaptor adaptor,
1588 SmallVectorImpl<Type> &inferredReturnTypes) {
1589 if (llvm::isa<ShapeType>(adaptor.getShape().getType()))
1590 inferredReturnTypes.assign({SizeType::get(context)});
1591 else
1592 inferredReturnTypes.assign({IndexType::get(context)});
1593 return success();
1594}
1595
1596bool mlir::shape::NumElementsOp::isCompatibleReturnTypes(TypeRange l,
1597 TypeRange r) {
1598 // SizeType is compatible with IndexType.
1600}
1601
1602LogicalResult shape::NumElementsOp::verify() {
1603 return verifySizeOrIndexOp(*this);
1604}
1605
1606//===----------------------------------------------------------------------===//
1607// MaxOp
1608//===----------------------------------------------------------------------===//
1609
1610OpFoldResult MaxOp::fold(FoldAdaptor adaptor) {
1611 // If operands are equal, just propagate one.
1612 if (getLhs() == getRhs())
1613 return getLhs();
1614 return nullptr;
1615}
1616
1617LogicalResult mlir::shape::MaxOp::inferReturnTypes(
1618 MLIRContext *context, std::optional<Location> location,
1619 MaxOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1620 if (adaptor.getLhs().getType() == adaptor.getRhs().getType())
1621 inferredReturnTypes.assign({adaptor.getLhs().getType()});
1622 else
1623 inferredReturnTypes.assign({SizeType::get(context)});
1624 return success();
1625}
1626
1627bool mlir::shape::MaxOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1628 if (l.size() != 1 || r.size() != 1)
1629 return false;
1630 if (llvm::isa<ShapeType>(l.front()) && llvm::isa<ShapeType>(r.front()))
1631 return true;
1632 if (llvm::isa<SizeType>(l.front()) && llvm::isa<SizeType>(r.front()))
1633 return true;
1634 return false;
1635}
1636
1637//===----------------------------------------------------------------------===//
1638// MinOp
1639//===----------------------------------------------------------------------===//
1640
1641OpFoldResult MinOp::fold(FoldAdaptor adaptor) {
1642 // If operands are equal, just propagate one.
1643 if (getLhs() == getRhs())
1644 return getLhs();
1645 return nullptr;
1646}
1647
1648LogicalResult mlir::shape::MinOp::inferReturnTypes(
1649 MLIRContext *context, std::optional<Location> location,
1650 MinOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1651 if (adaptor.getLhs().getType() == adaptor.getRhs().getType())
1652 inferredReturnTypes.assign({adaptor.getLhs().getType()});
1653 else
1654 inferredReturnTypes.assign({SizeType::get(context)});
1655 return success();
1656}
1657
1658bool mlir::shape::MinOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1659 if (l.size() != 1 || r.size() != 1)
1660 return false;
1661 if (llvm::isa<ShapeType>(l.front()) && llvm::isa<ShapeType>(r.front()))
1662 return true;
1663 if (llvm::isa<SizeType>(l.front()) && llvm::isa<SizeType>(r.front()))
1664 return true;
1665 return false;
1666}
1667
1668//===----------------------------------------------------------------------===//
1669// MulOp
1670//===----------------------------------------------------------------------===//
1671
1672OpFoldResult MulOp::fold(FoldAdaptor adaptor) {
1673 auto lhs = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getLhs());
1674 if (!lhs)
1675 return nullptr;
1676 auto rhs = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getRhs());
1677 if (!rhs)
1678 return nullptr;
1679 APInt folded = lhs.getValue() * rhs.getValue();
1680 Type indexTy = IndexType::get(getContext());
1681 return IntegerAttr::get(indexTy, folded);
1682}
1683
1684LogicalResult mlir::shape::MulOp::inferReturnTypes(
1685 MLIRContext *context, std::optional<Location> location,
1686 MulOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1687 if (llvm::isa<SizeType>(adaptor.getLhs().getType()) ||
1688 llvm::isa<SizeType>(adaptor.getRhs().getType()))
1689 inferredReturnTypes.assign({SizeType::get(context)});
1690 else
1691 inferredReturnTypes.assign({IndexType::get(context)});
1692 return success();
1693}
1694
1695bool mlir::shape::MulOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1696 // SizeType is compatible with IndexType.
1698}
1699
1700LogicalResult shape::MulOp::verify() { return verifySizeOrIndexOp(*this); }
1701
1702//===----------------------------------------------------------------------===//
1703// ShapeOfOp
1704//===----------------------------------------------------------------------===//
1705
1706namespace {
1707/// Replace shape_of(x) where x has a constant shape with a const_shape op.
1708struct ShapeOfOpToConstShapeOp : public OpRewritePattern<shape::ShapeOfOp> {
1709 using OpRewritePattern<shape::ShapeOfOp>::OpRewritePattern;
1710
1711 LogicalResult matchAndRewrite(shape::ShapeOfOp op,
1712 PatternRewriter &rewriter) const override {
1713 auto type = llvm::dyn_cast<ShapedType>(op.getArg().getType());
1714 if (!type || !type.hasStaticShape())
1715 return failure();
1716
1717 Type resultType = op.getResult().getType();
1718 Location loc = op.getLoc();
1719 Type constResType =
1720 isa<ShapeType>(resultType)
1721 ? resultType
1722 : RankedTensorType::get({type.getRank()}, rewriter.getIndexType());
1723 Value constShape =
1724 ConstShapeOp::create(rewriter, loc, constResType,
1725 rewriter.getIndexTensorAttr(type.getShape()))
1726 .getResult();
1727 if (constShape.getType() != resultType)
1728 constShape =
1729 tensor::CastOp::create(rewriter, loc, resultType, constShape);
1730 rewriter.replaceOp(op, constShape);
1731 return success();
1732 }
1733};
1734
1735// Canonicalize
1736//
1737// %0 = tensor.reshape %input(%shape) : (tensor<*xf32>, tensor<?xindex>) ->
1738// tensor<*xf32>
1739// %1 = shape.shape_of %0 : tensor<*xf32> -> tensor<?xindex>
1740//
1741// to
1742//
1743// %0 = tensor.reshape %input(%shape) : (tensor<*xf32>, tensor<?xindex>) ->
1744// tensor<*xf32>
1745// %1 = %shape
1746//
1747struct ShapeOfFromReshape : public OpRewritePattern<shape::ShapeOfOp> {
1748 using OpRewritePattern<shape::ShapeOfOp>::OpRewritePattern;
1749
1750 LogicalResult matchAndRewrite(shape::ShapeOfOp op,
1751 PatternRewriter &rewriter) const override {
1752 auto tensorReshapeOp = op.getArg().getDefiningOp<tensor::ReshapeOp>();
1753 if (!tensorReshapeOp)
1754 return rewriter.notifyMatchFailure(op, "producer is not tensor.reshape");
1755 if (!isa<TensorType>(op.getType()))
1756 return rewriter.notifyMatchFailure(op, "result is not a tensor");
1757
1758 // Operand 'shape' of 'tensor.reshape' may now be used as the result of
1759 // 'shape.shape_of'. While its type is guaranteed to be compatible in well-
1760 // formed IR, it may not be identical (dynamically vs statically shaped),
1761 // in which case it needs to be cast first using 'tensor.cast'.
1762 // Additionally, it may not have identical element type (i32 vs index)
1763 // while it has identical shaped type (dynamic vs static), in which case it
1764 // needs to be cast first using 'arith.index_cast'. Note: 'shape.shape_of'
1765 // op result must be shape or extent tensor.
1766 Value shape = tensorReshapeOp.getShape();
1767
1768 auto opTensorTy = cast<RankedTensorType>(op.getType());
1769 auto shapeTensorTy = cast<RankedTensorType>(shape.getType());
1770
1771 if (opTensorTy != shapeTensorTy) {
1772 if (opTensorTy.getElementType() == shapeTensorTy.getElementType())
1773 shape =
1774 tensor::CastOp::create(rewriter, op.getLoc(), opTensorTy, shape);
1775 else if (!isExtentTensorType(shapeTensorTy))
1776 shape = arith::IndexCastOp::create(rewriter, op.getLoc(), opTensorTy,
1777 shape);
1778 }
1779
1780 rewriter.replaceOp(op, shape);
1781 return success();
1782 }
1783};
1784
1785// Canonicalize
1786// ```
1787// %0 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<3xindex>
1788// %1 = tensor.cast %0 : tensor<3xindex> to tensor<?xindex>
1789// ```
1790// to
1791// ```
1792// %1 = shape.shape_of %arg : tensor<?x?x?xf32> -> tensor<?xindex>
1793// ```
1794struct ShapeOfCastExtentTensor : public OpRewritePattern<tensor::CastOp> {
1795 using OpRewritePattern<tensor::CastOp>::OpRewritePattern;
1796
1797 LogicalResult matchAndRewrite(tensor::CastOp op,
1798 PatternRewriter &rewriter) const override {
1799 auto ty = llvm::dyn_cast<RankedTensorType>(op.getType());
1800 if (!ty || ty.getRank() != 1)
1801 return failure();
1802
1803 auto shapeOfOp = op.getSource().getDefiningOp<ShapeOfOp>();
1804 if (!shapeOfOp)
1805 return failure();
1806
1807 // Argument type must be ranked and must not conflict.
1808 auto argTy = llvm::dyn_cast<RankedTensorType>(shapeOfOp.getArg().getType());
1809 if (!argTy || (!ty.isDynamicDim(0) && ty.getDimSize(0) != argTy.getRank()))
1810 return failure();
1811
1812 rewriter.replaceOpWithNewOp<ShapeOfOp>(op, ty, shapeOfOp.getArg());
1813 return success();
1814 }
1815};
1816} // namespace
1817
1818void ShapeOfOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1819 MLIRContext *context) {
1820 patterns.add<ShapeOfCastExtentTensor, ShapeOfFromReshape,
1821 ExtractFromShapeOfExtentTensor, ShapeOfOpToConstShapeOp>(
1822 context);
1823}
1824
1825LogicalResult mlir::shape::ShapeOfOp::inferReturnTypes(
1826 MLIRContext *context, std::optional<Location> location,
1827 ShapeOfOp::Adaptor adaptor, SmallVectorImpl<Type> &inferredReturnTypes) {
1828 if (llvm::isa<ValueShapeType>(adaptor.getArg().getType()))
1829 inferredReturnTypes.assign({ShapeType::get(context)});
1830 else {
1831 auto shapedTy = llvm::cast<ShapedType>(adaptor.getArg().getType());
1832 int64_t rank =
1833 shapedTy.hasRank() ? shapedTy.getRank() : ShapedType::kDynamic;
1834 Type indexTy = IndexType::get(context);
1835 Type extentTensorTy = RankedTensorType::get({rank}, indexTy);
1836 inferredReturnTypes.assign({extentTensorTy});
1837 }
1838 return success();
1839}
1840
1841bool mlir::shape::ShapeOfOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1842 if (l.size() != 1 || r.size() != 1)
1843 return false;
1844 if (l == r)
1845 return true;
1846
1847 Type lhs = l.front();
1848 Type rhs = r.front();
1849
1850 if (!llvm::isa<ShapeType, ShapedType>(lhs) ||
1851 !llvm::isa<ShapeType, ShapedType>(rhs))
1852 return false;
1853
1854 if (llvm::isa<ShapeType>(lhs) || llvm::isa<ShapeType>(rhs))
1855 // Shape type is compatible with all other valid return types.
1856 return true;
1857
1858 if (succeeded(verifyCompatibleShapes({lhs, rhs})))
1859 return true;
1860 return false;
1861}
1862
1863LogicalResult shape::ShapeOfOp::verify() {
1864 return verifyShapeOrExtentTensorOp(*this);
1865}
1866
1867//===----------------------------------------------------------------------===//
1868// SizeToIndexOp
1869//===----------------------------------------------------------------------===//
1870
1871OpFoldResult SizeToIndexOp::fold(FoldAdaptor adaptor) {
1872 // Constant values of both types, `shape.size` and `index`, are represented as
1873 // `IntegerAttr`s which makes constant folding simple.
1874 if (Attribute arg = adaptor.getArg())
1875 return arg;
1876 return OpFoldResult();
1877}
1878
1879void SizeToIndexOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
1880 MLIRContext *context) {
1881 patterns.add<IndexToSizeToIndexCanonicalization>(context);
1882}
1883
1884bool SizeToIndexOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1885 if (inputs.size() != 1 || outputs.size() != 1)
1886 return false;
1887 return llvm::isa<IndexType, SizeType>(inputs[0]) &&
1888 llvm::isa<IndexType>(outputs[0]);
1889}
1890
1891//===----------------------------------------------------------------------===//
1892// YieldOp
1893//===----------------------------------------------------------------------===//
1894
1895LogicalResult shape::YieldOp::verify() {
1896 auto *parentOp = (*this)->getParentOp();
1897 auto results = parentOp->getResults();
1898 auto operands = getOperands();
1899
1900 if (parentOp->getNumResults() != getNumOperands())
1901 return emitOpError() << "number of operands does not match number of "
1902 "results of its parent";
1903 for (auto e : llvm::zip(results, operands))
1904 if (std::get<0>(e).getType() != std::get<1>(e).getType())
1905 return emitOpError() << "types mismatch between yield op and its parent";
1906
1907 return success();
1908}
1909
1910//===----------------------------------------------------------------------===//
1911// SplitAtOp
1912//===----------------------------------------------------------------------===//
1913
1914LogicalResult SplitAtOp::fold(FoldAdaptor adaptor,
1916 if (!adaptor.getOperand() || !adaptor.getIndex())
1917 return failure();
1918 auto shapeVec =
1919 llvm::to_vector<6>(llvm::cast<DenseIntElementsAttr>(adaptor.getOperand())
1920 .getValues<int64_t>());
1921 auto shape = llvm::ArrayRef(shapeVec);
1922 auto splitPoint = llvm::cast<IntegerAttr>(adaptor.getIndex()).getInt();
1923 // Verify that the split point is in the correct range.
1924 // TODO: Constant fold to an "error".
1925 int64_t rank = shape.size();
1926 if (-rank > splitPoint || splitPoint > rank)
1927 return failure();
1928 if (splitPoint < 0)
1929 splitPoint += shape.size();
1930 Builder builder(adaptor.getOperand().getContext());
1931 results.push_back(builder.getIndexTensorAttr(shape.take_front(splitPoint)));
1932 results.push_back(builder.getIndexTensorAttr(shape.drop_front(splitPoint)));
1933 return success();
1934}
1935
1936//===----------------------------------------------------------------------===//
1937// ToExtentTensorOp
1938//===----------------------------------------------------------------------===//
1939
1940OpFoldResult ToExtentTensorOp::fold(FoldAdaptor adaptor) {
1941 if (!adaptor.getInput())
1942 return OpFoldResult();
1943 Builder builder(getContext());
1944 auto shape =
1945 llvm::to_vector<6>(llvm::cast<DenseIntElementsAttr>(adaptor.getInput())
1946 .getValues<int64_t>());
1947 auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())},
1948 builder.getIndexType());
1949 return DenseIntElementsAttr::get(type, shape);
1950}
1951
1952bool ToExtentTensorOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
1953 if (inputs.size() != 1 || outputs.size() != 1)
1954 return false;
1955 if (auto inputTensor = llvm::dyn_cast<RankedTensorType>(inputs[0])) {
1956 if (!llvm::isa<IndexType>(inputTensor.getElementType()) ||
1957 inputTensor.getRank() != 1)
1958 return false;
1959 } else if (!llvm::isa<ShapeType>(inputs[0])) {
1960 return false;
1961 }
1962
1963 TensorType outputTensor = llvm::dyn_cast<TensorType>(outputs[0]);
1964 return outputTensor && llvm::isa<IndexType>(outputTensor.getElementType());
1965}
1966
1967//===----------------------------------------------------------------------===//
1968// ReduceOp
1969//===----------------------------------------------------------------------===//
1970
1971void ReduceOp::build(OpBuilder &builder, OperationState &result, Value shape,
1972 ValueRange initVals) {
1973 OpBuilder::InsertionGuard g(builder);
1974 result.addOperands(shape);
1975 result.addOperands(initVals);
1976
1977 Region *bodyRegion = result.addRegion();
1978 Block *bodyBlock = builder.createBlock(
1979 bodyRegion, /*insertPt=*/{}, builder.getIndexType(), result.location);
1980
1981 Type elementType;
1982 if (auto tensorType = llvm::dyn_cast<TensorType>(shape.getType()))
1983 elementType = tensorType.getElementType();
1984 else
1985 elementType = SizeType::get(builder.getContext());
1986 bodyBlock->addArgument(elementType, shape.getLoc());
1987
1988 for (Value initVal : initVals) {
1989 bodyBlock->addArgument(initVal.getType(), initVal.getLoc());
1990 result.addTypes(initVal.getType());
1991 }
1992}
1993
1994LogicalResult ReduceOp::verify() {
1995 // Verify block arg types.
1996 Block &block = getRegion().front();
1997
1998 // The block takes index, extent, and aggregated values as arguments.
1999 auto blockArgsCount = getInitVals().size() + 2;
2000 if (block.getNumArguments() != blockArgsCount)
2001 return emitOpError() << "ReduceOp body is expected to have "
2002 << blockArgsCount << " arguments";
2003
2004 // The first block argument is the index and must always be of type `index`.
2005 if (!llvm::isa<IndexType>(block.getArgument(0).getType()))
2006 return emitOpError(
2007 "argument 0 of ReduceOp body is expected to be of IndexType");
2008
2009 // The second block argument is the extent and must be of type `size` or
2010 // `index`, depending on whether the reduce operation is applied to a shape or
2011 // to an extent tensor.
2012 Type extentTy = block.getArgument(1).getType();
2013 if (llvm::isa<ShapeType>(getShape().getType())) {
2014 if (!llvm::isa<SizeType>(extentTy))
2015 return emitOpError("argument 1 of ReduceOp body is expected to be of "
2016 "SizeType if the ReduceOp operates on a ShapeType");
2017 } else {
2018 if (!llvm::isa<IndexType>(extentTy))
2019 return emitOpError(
2020 "argument 1 of ReduceOp body is expected to be of IndexType if the "
2021 "ReduceOp operates on an extent tensor");
2022 }
2023
2024 for (const auto &type : llvm::enumerate(getInitVals()))
2025 if (block.getArgument(type.index() + 2).getType() != type.value().getType())
2026 return emitOpError() << "type mismatch between argument "
2027 << type.index() + 2
2028 << " of ReduceOp body and initial value "
2029 << type.index();
2030 return success();
2031}
2032
2033ParseResult ReduceOp::parse(OpAsmParser &parser, OperationState &result) {
2034 // Parse operands.
2036 Type shapeOrExtentTensorType;
2037 if (parser.parseOperandList(operands, /*requiredOperandCount=*/-1,
2039 parser.parseColonType(shapeOrExtentTensorType) ||
2040 parser.parseOptionalArrowTypeList(result.types))
2041 return failure();
2042
2043 // Resolve operands.
2044 auto initVals = llvm::ArrayRef(operands).drop_front();
2045 if (parser.resolveOperand(operands.front(), shapeOrExtentTensorType,
2046 result.operands) ||
2047 parser.resolveOperands(initVals, result.types, parser.getNameLoc(),
2048 result.operands))
2049 return failure();
2050
2051 // Parse the body.
2052 Region *body = result.addRegion();
2053 if (parser.parseRegion(*body, /*args=*/{}, /*argTypes=*/{}))
2054 return failure();
2055
2056 // Parse attributes.
2057 if (parser.parseOptionalAttrDict(result.attributes))
2058 return failure();
2059
2060 return success();
2061}
2062
2063void ReduceOp::print(OpAsmPrinter &p) {
2064 p << '(' << getShape() << ", " << getInitVals()
2065 << ") : " << getShape().getType();
2066 p.printOptionalArrowTypeList(getResultTypes());
2067 p << ' ';
2068 p.printRegion(getRegion());
2069 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
2070}
2071
2072#define GET_OP_CLASSES
2073#include "mlir/Dialect/Shape/IR/ShapeOps.cpp.inc"
2074
2075#define GET_TYPEDEF_CLASSES
2076#include "mlir/Dialect/Shape/IR/ShapeOpsTypes.cpp.inc"
return success()
getNumOperands() - 1))) return failure()
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static bool isErrorPropagationPossible(TypeRange operandTypes)
Definition Shape.cpp:66
static bool hasAtMostSingleNonScalar(ArrayRef< Attribute > attributes)
Definition Shape.cpp:983
static LogicalResult verifyShapeOrExtentTensorOp(Operation *op)
Definition Shape.cpp:83
static bool eachHasOnlyOneOfTypes(TypeRange typeRange)
Definition Shape.cpp:96
static LogicalResult verifySizeOrIndexOp(Operation *op)
Definition Shape.cpp:71
static int64_t product(ArrayRef< int64_t > vals)
static bool isLegalToInline(InlinerInterface &interface, Region *src, Region *insertRegion, bool shouldCloneInlinedRegion, IRMapping &valueMapping)
Utility to check that all of the operations within 'src' can be inlined.
static int64_t getNumElements(Type t)
Compute the total number of elements in the given type, also taking into account nested types.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
ParseResult parseSymbolName(StringAttr &result)
Parse an -identifier and store it (without the '@' symbol) in a string attribute.
@ Paren
Parens surrounding zero or more operands.
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseOptionalAttrDictWithKeyword(NamedAttrList &result)=0
Parse a named dictionary into 'result' if the attributes keyword is present.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
virtual void printAttributeWithoutType(Attribute attr)
Print the given attribute without its type.
virtual void printType(Type type)
virtual void printSymbolName(StringRef symbolRef)
Print the given string as a symbol reference, i.e.
void printOptionalArrowTypeList(TypeRange &&types)
Print an optional arrow followed by a type list.
Attributes are known-constant values of operations.
Definition Attributes.h:25
MLIRContext * getContext() const
Return the context this attribute belongs to.
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
Operation & front()
Definition Block.h:177
Operation & back()
Definition Block.h:176
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
static BoolAttr get(MLIRContext *context, bool value)
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
FunctionType getFunctionType(TypeRange inputs, TypeRange results)
Definition Builders.cpp:84
Ty getType(Args &&...args)
Get or construct an instance of the type Ty with provided arguments.
Definition Builders.h:94
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
DenseIntElementsAttr getIndexTensorAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:201
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
An attribute that represents a reference to a dense integer vector or tensor object.
static DenseIntElementsAttr get(const ShapedType &type, Arg &&arg)
Get an instance of a DenseIntElementsAttr with the given arguments.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDictWithKeyword(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary prefixed with 'attribute...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
virtual void printRegion(Region &blocks, bool printEntryBlockArgs=true, bool printBlockTerminators=true, bool printEmptyBlock=false)=0
Prints a region.
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
Block::iterator getInsertionPoint() const
Returns the current insertion point of the builder.
Definition Builders.h:448
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void setInsertionPointToEnd(Block *block)
Sets the insertion point to the end of the specified block.
Definition Builders.h:439
Block * getInsertionBlock() const
Return the block the current insertion point belongs to.
Definition Builders.h:445
This class represents a single result from folding an operation.
A trait used to provide symbol table functionalities to a region operation.
StringAttr getIdentifier() const
Return the name of this operation as a StringAttr.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:801
iterator_range< dialect_attr_iterator > dialect_attr_range
Definition Operation.h:686
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
operand_type_range getOperandTypes()
Definition Operation.h:422
static Operation * create(Location location, OperationName name, TypeRange resultTypes, ValueRange operands, NamedAttrList &&attributes, PropertyRef properties, BlockRange successors, unsigned numRegions)
Create a new Operation with the specific fields.
Definition Operation.cpp:65
result_type_range getResultTypes()
Definition Operation.h:453
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
bool isParent() const
Returns true if branching from the parent op.
This class represents a successor of a region.
bool isOperation() const
Return true if the successor is an operation.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Block * splitBlock(Block *block, Block::iterator before)
Split the operations starting at "before" (inclusive) out of the given block into a new block,...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
Type getElementType() const
Returns the element type of this tensor type.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
ValueTypeRange< ValueRange > type_range
Definition ValueRange.h:424
Type front()
Return first type in the range.
Definition TypeRange.h:164
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A named class for passing around the variadic flag.
bool staticallyKnownBroadcastable(ArrayRef< SmallVector< int64_t, 6 > > shapes)
Returns true if a broadcast between n shapes is guaranteed to be successful and not result in an erro...
Definition Traits.cpp:24
bool getBroadcastedShape(ArrayRef< int64_t > shape1, ArrayRef< int64_t > shape2, SmallVectorImpl< int64_t > &resultShape)
Returns true and sets resultShape to the broadcasted shape from the two given shapes if they are broa...
Definition Traits.cpp:59
void addArgAndResultAttrs(Builder &builder, OperationState &result, ArrayRef< DictionaryAttr > argAttrs, ArrayRef< DictionaryAttr > resultAttrs, StringAttr argAttrsName, StringAttr resAttrsName)
Adds argument and result attributes, provided as argAttrs and resultAttrs arguments,...
void printFunctionOp(OpAsmPrinter &p, FunctionOpInterface op, bool isVariadic, StringRef typeAttrName, StringAttr argAttrsName, StringAttr resAttrsName)
Printer implementation for function-like operations.
ParseResult parseFunctionOp(OpAsmParser &parser, OperationState &result, bool allowVariadic, StringAttr typeAttrName, FuncTypeBuilder funcTypeBuilder, StringAttr argAttrsName, StringAttr resAttrsName)
Parser implementation for function-like operations.
DynamicAPInt getIndex(const ConeV &cone)
Get the index of a cone, i.e., the volume of the parallelepiped spanned by its generators,...
Definition Barvinok.cpp:63
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
bool isExtentTensorType(Type)
Definition Shape.cpp:44
LogicalResult getShapeVec(Value input, SmallVectorImpl< int64_t > &shapeValues)
Definition Shape.cpp:49
RankedTensorType getExtentTensorType(MLIRContext *ctx, int64_t rank=ShapedType::kDynamic)
Alias type for extent tensors.
Definition Shape.cpp:40
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
LogicalResult verifyCompatibleShapes(TypeRange types1, TypeRange types2)
Returns success if the given two arrays have the same number of elements and each pair wise entries h...
Attribute constFoldBinaryOp(ArrayRef< Attribute > operands, Type resultType, CalculationT &&calculate)
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
LogicalResult emitOptionalError(std::optional< Location > loc, Args &&...args)
Overloads of the above emission functions that take an optionally null location.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
detail::constant_int_predicate_matcher m_Zero()
Matches a constant scalar / vector splat / tensor splat integer zero.
Definition Matchers.h:442
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
This is the representation of an operand reference.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
This represents an operation in an abstracted form, suitable for use with the builder APIs.
void addAttribute(StringRef name, Attribute attr)
Add an attribute with the specified name.
Region * addRegion()
Create a region that should be attached to the operation.