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