MLIR 24.0.0git
SCF.cpp
Go to the documentation of this file.
1//===- SCF.cpp - Structured Control Flow 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
21#include "mlir/IR/IRMapping.h"
22#include "mlir/IR/Matchers.h"
23#include "mlir/IR/Operation.h"
31#include "llvm/ADT/MapVector.h"
32#include "llvm/ADT/STLExtras.h"
33#include "llvm/ADT/SmallPtrSet.h"
34#include "llvm/ADT/TypeSwitch.h"
35#include "llvm/Support/Casting.h"
36#include "llvm/Support/DebugLog.h"
37#include <optional>
38
39using namespace mlir;
40using namespace mlir::scf;
41
42#include "mlir/Dialect/SCF/IR/SCFOpsDialect.cpp.inc"
43
44//===----------------------------------------------------------------------===//
45// SCFDialect Dialect Interfaces
46//===----------------------------------------------------------------------===//
47
48namespace {
49struct SCFInlinerInterface : public DialectInlinerInterface {
50 using DialectInlinerInterface::DialectInlinerInterface;
51 // We don't have any special restrictions on what can be inlined into
52 // destination regions (e.g. while/conditional bodies). Always allow it.
53 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
54 IRMapping &valueMapping) const final {
55 return true;
56 }
57 // Operations in scf dialect are always legal to inline since they are
58 // pure.
59 bool isLegalToInline(Operation *, Region *, bool, IRMapping &) const final {
60 return true;
61 }
62 // Handle the given inlined terminator by replacing it with a new operation
63 // as necessary. Required when the region has only one block.
64 void handleTerminator(Operation *op, ValueRange valuesToRepl) const final {
65 auto retValOp = dyn_cast<scf::YieldOp>(op);
66 if (!retValOp)
67 return;
68
69 for (auto retValue : llvm::zip(valuesToRepl, retValOp.getOperands())) {
70 std::get<0>(retValue).replaceAllUsesWith(std::get<1>(retValue));
71 }
72 }
73};
74} // namespace
75
76//===----------------------------------------------------------------------===//
77// SCFDialect
78//===----------------------------------------------------------------------===//
79
80void SCFDialect::initialize() {
81 addOperations<
82#define GET_OP_LIST
83#include "mlir/Dialect/SCF/IR/SCFOps.cpp.inc"
84 >();
85 addInterfaces<SCFInlinerInterface>();
86 declarePromisedInterface<ConvertToEmitCPatternInterface, SCFDialect>();
87 declarePromisedInterfaces<bufferization::BufferDeallocationOpInterface,
88 InParallelOp, ReduceReturnOp>();
89 declarePromisedInterfaces<bufferization::BufferizableOpInterface, ConditionOp,
90 ExecuteRegionOp, ForOp, IfOp, IndexSwitchOp,
91 ForallOp, InParallelOp, WhileOp, YieldOp>();
92 declarePromisedInterface<ValueBoundsOpInterface, ForOp>();
93}
94
95/// Default callback for IfOp builders. Inserts a yield without arguments.
97 scf::YieldOp::create(builder, loc);
98}
99
100/// Verifies that the first block of the given `region` is terminated by a
101/// TerminatorTy. Reports errors on the given operation if it is not the case.
102template <typename TerminatorTy>
103static TerminatorTy verifyAndGetTerminator(Operation *op, Region &region,
104 StringRef errorMessage) {
105 Operation *terminatorOperation = nullptr;
106 if (!region.empty() && !region.front().empty()) {
107 terminatorOperation = &region.front().back();
108 if (auto yield = dyn_cast_or_null<TerminatorTy>(terminatorOperation))
109 return yield;
110 }
111 auto diag = op->emitOpError(errorMessage);
112 if (terminatorOperation)
113 diag.attachNote(terminatorOperation->getLoc()) << "terminator here";
114 return nullptr;
115}
116
117std::optional<llvm::APSInt> mlir::scf::computeUbMinusLb(Value lb, Value ub,
118 bool isSigned) {
119 llvm::APSInt diff;
120 auto addOp = ub.getDefiningOp<arith::AddIOp>();
121 if (!addOp)
122 return std::nullopt;
123 if ((isSigned && !addOp.hasNoSignedWrap()) ||
124 (!isSigned && !addOp.hasNoUnsignedWrap()))
125 return std::nullopt;
126
127 if (addOp.getLhs() != lb ||
128 !matchPattern(addOp.getRhs(), m_ConstantInt(&diff)))
129 return std::nullopt;
130 return diff;
131}
132
133//===----------------------------------------------------------------------===//
134// ExecuteRegionOp
135//===----------------------------------------------------------------------===//
136
137///
138/// (ssa-id `=`)? `execute_region` `->` function-result-type `{`
139/// block+
140/// `}`
141///
142/// Example:
143/// scf.execute_region -> i32 {
144/// %idx = load %rI[%i] : memref<128xi32>
145/// return %idx : i32
146/// }
147///
148ParseResult ExecuteRegionOp::parse(OpAsmParser &parser,
150 if (parser.parseOptionalArrowTypeList(result.types))
151 return failure();
152
153 if (succeeded(parser.parseOptionalKeyword("no_inline")))
154 result.addAttribute("no_inline", parser.getBuilder().getUnitAttr());
155
156 // Introduce the body region and parse it.
157 Region *body = result.addRegion();
158 if (parser.parseRegion(*body, /*arguments=*/{}, /*argTypes=*/{}) ||
159 parser.parseOptionalAttrDict(result.attributes))
160 return failure();
161
162 return success();
163}
164
165void ExecuteRegionOp::print(OpAsmPrinter &p) {
166 p.printOptionalArrowTypeList(getResultTypes());
167 p << ' ';
168 if (getNoInline())
169 p << "no_inline ";
170 p.printRegion(getRegion(),
171 /*printEntryBlockArgs=*/false,
172 /*printBlockTerminators=*/true);
173 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
174}
175
176LogicalResult ExecuteRegionOp::verify() {
177 if (getRegion().empty())
178 return emitOpError("region needs to have at least one block");
179 if (getRegion().front().getNumArguments() > 0)
180 return emitOpError("region cannot have any arguments");
181 return success();
182}
183
184// Inline an ExecuteRegionOp if its parent can contain multiple blocks.
185// TODO generalize the conditions for operations which can be inlined into.
186// func @func_execute_region_elim() {
187// "test.foo"() : () -> ()
188// %v = scf.execute_region -> i64 {
189// %c = "test.cmp"() : () -> i1
190// cf.cond_br %c, ^bb2, ^bb3
191// ^bb2:
192// %x = "test.val1"() : () -> i64
193// cf.br ^bb4(%x : i64)
194// ^bb3:
195// %y = "test.val2"() : () -> i64
196// cf.br ^bb4(%y : i64)
197// ^bb4(%z : i64):
198// scf.yield %z : i64
199// }
200// "test.bar"(%v) : (i64) -> ()
201// return
202// }
203//
204// becomes
205//
206// func @func_execute_region_elim() {
207// "test.foo"() : () -> ()
208// %c = "test.cmp"() : () -> i1
209// cf.cond_br %c, ^bb1, ^bb2
210// ^bb1: // pred: ^bb0
211// %x = "test.val1"() : () -> i64
212// cf.br ^bb3(%x : i64)
213// ^bb2: // pred: ^bb0
214// %y = "test.val2"() : () -> i64
215// cf.br ^bb3(%y : i64)
216// ^bb3(%z: i64): // 2 preds: ^bb1, ^bb2
217// "test.bar"(%z) : (i64) -> ()
218// return
219// }
220//
221struct MultiBlockExecuteInliner : public OpRewritePattern<ExecuteRegionOp> {
222 using OpRewritePattern<ExecuteRegionOp>::OpRewritePattern;
223
224 LogicalResult matchAndRewrite(ExecuteRegionOp op,
225 PatternRewriter &rewriter) const override {
226 if (op.getNoInline())
227 return failure();
228 if (!isa<FunctionOpInterface, ExecuteRegionOp>(op->getParentOp()))
229 return failure();
230
231 Block *prevBlock = op->getBlock();
232 Block *postBlock = rewriter.splitBlock(prevBlock, op->getIterator());
233 rewriter.setInsertionPointToEnd(prevBlock);
234
235 cf::BranchOp::create(rewriter, op.getLoc(), &op.getRegion().front());
236
237 for (Block &blk : op.getRegion()) {
238 if (YieldOp yieldOp = dyn_cast<YieldOp>(blk.getTerminator())) {
239 rewriter.setInsertionPoint(yieldOp);
240 cf::BranchOp::create(rewriter, yieldOp.getLoc(), postBlock,
241 yieldOp.getResults());
242 rewriter.eraseOp(yieldOp);
243 }
244 }
245
246 rewriter.inlineRegionBefore(op.getRegion(), postBlock);
247 SmallVector<Value> blockArgs;
248
249 for (auto res : op.getResults())
250 blockArgs.push_back(postBlock->addArgument(res.getType(), res.getLoc()));
251
252 rewriter.replaceOp(op, blockArgs);
253 return success();
254 }
255};
256
257void ExecuteRegionOp::getCanonicalizationPatterns(RewritePatternSet &results,
258 MLIRContext *context) {
259 results.add<MultiBlockExecuteInliner>(context);
261 results, ExecuteRegionOp::getOperationName());
262 // Inline ops with a single block that are not marked as "no_inline".
264 results, ExecuteRegionOp::getOperationName(),
266 return failure(cast<ExecuteRegionOp>(op).getNoInline());
267 });
268}
269
270void ExecuteRegionOp::getSuccessorRegions(
272 // If the predecessor is the ExecuteRegionOp, branch into the body.
273 if (point.isParent()) {
274 regions.push_back(RegionSuccessor(&getRegion()));
275 return;
276 }
277
278 // Otherwise, the region branches back to the parent operation.
279 regions.push_back(RegionSuccessor(getOperation()));
280}
281
282void ExecuteRegionOp::getRegionInvocationBounds(
284 bounds.emplace_back(/*lb=*/1, /*ub=*/1);
285}
286
287ValueRange ExecuteRegionOp::getSuccessorInputs(RegionSuccessor successor) {
288 return successor.isOperation() ? ValueRange(getOperation()->getResults())
289 : ValueRange();
290}
291
292//===----------------------------------------------------------------------===//
293// ConditionOp
294//===----------------------------------------------------------------------===//
295
297ConditionOp::getMutableSuccessorOperands(RegionSuccessor point) {
298 assert((point.isOperation() ||
299 point.getSuccessor() == &getParentOp().getAfter()) &&
300 "condition op can only exit the loop or branch to the after"
301 "region");
302 // Pass all operands except the condition to the successor region.
303 return getArgsMutable();
304}
305
306void ConditionOp::getSuccessorRegions(
308 FoldAdaptor adaptor(operands, *this);
309
310 WhileOp whileOp = getParentOp();
311
312 // Condition can either lead to the after region or back to the parent op
313 // depending on whether the condition is true or not.
314 auto boolAttr = dyn_cast_or_null<BoolAttr>(adaptor.getCondition());
315 if (!boolAttr || boolAttr.getValue())
316 regions.emplace_back(&whileOp.getAfter());
317 if (!boolAttr || !boolAttr.getValue())
318 regions.push_back(RegionSuccessor(whileOp.getOperation()));
319}
320
321//===----------------------------------------------------------------------===//
322// ForOp
323//===----------------------------------------------------------------------===//
324
325void ForOp::build(OpBuilder &builder, OperationState &result, Value lb,
326 Value ub, Value step, ValueRange initArgs,
327 BodyBuilderFn bodyBuilder, bool unsignedCmp) {
328 OpBuilder::InsertionGuard guard(builder);
329
330 if (unsignedCmp)
331 result.addAttribute(getUnsignedCmpAttrName(result.name),
332 builder.getUnitAttr());
333 result.addOperands({lb, ub, step});
334 result.addOperands(initArgs);
335 for (Value v : initArgs)
336 result.addTypes(v.getType());
337 Type t = lb.getType();
338 Region *bodyRegion = result.addRegion();
339 Block *bodyBlock = builder.createBlock(bodyRegion);
340 bodyBlock->addArgument(t, result.location);
341 for (Value v : initArgs)
342 bodyBlock->addArgument(v.getType(), v.getLoc());
343
344 // Create the default terminator if the builder is not provided and if the
345 // iteration arguments are not provided. Otherwise, leave this to the caller
346 // because we don't know which values to return from the loop.
347 if (initArgs.empty() && !bodyBuilder) {
348 ForOp::ensureTerminator(*bodyRegion, builder, result.location);
349 } else if (bodyBuilder) {
350 OpBuilder::InsertionGuard guard(builder);
351 builder.setInsertionPointToStart(bodyBlock);
352 bodyBuilder(builder, result.location, bodyBlock->getArgument(0),
353 bodyBlock->getArguments().drop_front());
354 }
355}
356
357LogicalResult ForOp::verify() {
358 // Check that the body block has at least the induction variable argument.
359 // This must be checked before verifyRegions() and before any region trait
360 // verifiers (e.g. LoopLikeOpInterface) that call getRegionIterArgs(), to
361 // avoid crashing with an out-of-bounds drop_front on an empty arg list.
362 if (getBody()->getNumArguments() < getNumInductionVars())
363 return emitOpError("expected body to have at least ")
364 << getNumInductionVars()
365 << " argument(s) for the induction variable, but got "
366 << getBody()->getNumArguments();
367
368 // Check that the number of init args and op results is the same.
369 if (getInitArgs().size() != getNumResults())
370 return emitOpError(
371 "mismatch in number of loop-carried values and defined values");
372
373 return success();
374}
375
376LogicalResult ForOp::verifyRegions() {
377 // Check that the body block has at least the induction variable argument.
378 if (getBody()->getNumArguments() < getNumInductionVars())
379 return emitOpError("expected body to have at least ")
380 << getNumInductionVars() << " argument(s) for the induction "
381 << "variable, but got " << getBody()->getNumArguments();
382
383 // Check that the body defines as single block argument for the induction
384 // variable.
385 if (getInductionVar().getType() != getLowerBound().getType())
386 return emitOpError(
387 "expected induction variable to be same type as bounds and step");
388
389 if (getNumRegionIterArgs() != getNumResults())
390 return emitOpError(
391 "mismatch in number of basic block args and defined values");
392
393 auto initArgs = getInitArgs();
394 auto iterArgs = getRegionIterArgs();
395 auto opResults = getResults();
396 unsigned i = 0;
397 for (auto e : llvm::zip(initArgs, iterArgs, opResults)) {
398 if (std::get<0>(e).getType() != std::get<2>(e).getType())
399 return emitOpError() << "types mismatch between " << i
400 << "th iter operand and defined value";
401 if (std::get<1>(e).getType() != std::get<2>(e).getType())
402 return emitOpError() << "types mismatch between " << i
403 << "th iter region arg and defined value";
404
405 ++i;
406 }
407 return success();
408}
409
410std::optional<SmallVector<Value>> ForOp::getLoopInductionVars() {
411 return SmallVector<Value>{getInductionVar()};
412}
413
414std::optional<SmallVector<OpFoldResult>> ForOp::getLoopLowerBounds() {
416}
417
418std::optional<SmallVector<OpFoldResult>> ForOp::getLoopSteps() {
419 return SmallVector<OpFoldResult>{OpFoldResult(getStep())};
420}
421
422std::optional<SmallVector<OpFoldResult>> ForOp::getLoopUpperBounds() {
424}
425
426bool ForOp::isValidInductionVarType(Type type) {
427 return type.isIndex() || type.isSignlessInteger();
428}
429
430LogicalResult ForOp::setLoopLowerBounds(ArrayRef<OpFoldResult> bounds) {
431 if (bounds.size() != 1)
432 return failure();
433 if (auto val = dyn_cast<Value>(bounds[0])) {
434 setLowerBound(val);
435 return success();
436 }
437 return failure();
438}
439
440LogicalResult ForOp::setLoopUpperBounds(ArrayRef<OpFoldResult> bounds) {
441 if (bounds.size() != 1)
442 return failure();
443 if (auto val = dyn_cast<Value>(bounds[0])) {
444 setUpperBound(val);
445 return success();
446 }
447 return failure();
448}
449
450LogicalResult ForOp::setLoopSteps(ArrayRef<OpFoldResult> steps) {
451 if (steps.size() != 1)
452 return failure();
453 if (auto val = dyn_cast<Value>(steps[0])) {
454 setStep(val);
455 return success();
456 }
457 return failure();
458}
459
460std::optional<ResultRange> ForOp::getLoopResults() { return getResults(); }
461
462/// Promotes the loop body of a forOp to its containing block if the forOp
463/// it can be determined that the loop has a single iteration.
464LogicalResult ForOp::promoteIfSingleIteration(RewriterBase &rewriter) {
465 std::optional<APInt> tripCount = getStaticTripCount();
466 LDBG() << "promoteIfSingleIteration tripCount is " << tripCount
467 << " for loop "
468 << OpWithFlags(getOperation(), OpPrintingFlags().skipRegions());
469 if (!tripCount.has_value() || tripCount->getZExtValue() > 1)
470 return failure();
471
472 if (*tripCount == 0) {
473 rewriter.replaceAllUsesWith(getResults(), getInitArgs());
474 rewriter.eraseOp(*this);
475 return success();
476 }
477
478 // Replace all results with the yielded values.
479 auto yieldOp = cast<scf::YieldOp>(getBody()->getTerminator());
480 rewriter.replaceAllUsesWith(getResults(), getYieldedValues());
481
482 // Replace block arguments with lower bound (replacement for IV) and
483 // iter_args.
484 SmallVector<Value> bbArgReplacements;
485 bbArgReplacements.push_back(getLowerBound());
486 llvm::append_range(bbArgReplacements, getInitArgs());
487
488 // Move the loop body operations to the loop's containing block.
489 rewriter.inlineBlockBefore(getBody(), getOperation()->getBlock(),
490 getOperation()->getIterator(), bbArgReplacements);
491
492 // Erase the old terminator and the loop.
493 rewriter.eraseOp(yieldOp);
494 rewriter.eraseOp(*this);
495
496 return success();
497}
498
499/// Prints the initialization list in the form of
500/// <prefix>(%inner = %outer, %inner2 = %outer2, <...>)
501/// where 'inner' values are assumed to be region arguments and 'outer' values
502/// are regular SSA values.
504 Block::BlockArgListType blocksArgs,
505 ValueRange initializers,
506 StringRef prefix = "") {
507 assert(blocksArgs.size() == initializers.size() &&
508 "expected same length of arguments and initializers");
509 if (initializers.empty())
510 return;
511
512 p << prefix << '(';
513 llvm::interleaveComma(llvm::zip(blocksArgs, initializers), p, [&](auto it) {
514 p << std::get<0>(it) << " = " << std::get<1>(it);
515 });
516 p << ")";
517}
518
519void ForOp::print(OpAsmPrinter &p) {
520 if (getUnsignedCmp())
521 p << " unsigned";
522
523 p << " " << getInductionVar() << " = " << getLowerBound() << " to "
524 << getUpperBound() << " step " << getStep();
525
526 printInitializationList(p, getRegionIterArgs(), getInitArgs(), " iter_args");
527 if (!getInitArgs().empty())
528 p << " -> (" << getInitArgs().getTypes() << ')';
529 p << ' ';
530 if (Type t = getInductionVar().getType(); !t.isIndex())
531 p << " : " << t << ' ';
532 p.printRegion(getRegion(),
533 /*printEntryBlockArgs=*/false,
534 /*printBlockTerminators=*/!getInitArgs().empty());
535 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
536}
537
538ParseResult ForOp::parse(OpAsmParser &parser, OperationState &result) {
539 auto &builder = parser.getBuilder();
540 Type type;
541
542 OpAsmParser::Argument inductionVariable;
544
545 if (succeeded(parser.parseOptionalKeyword("unsigned")))
546 result.addAttribute(getUnsignedCmpAttrName(result.name),
547 builder.getUnitAttr());
548
549 // Parse the induction variable followed by '='.
550 if (parser.parseOperand(inductionVariable.ssaName) || parser.parseEqual() ||
551 // Parse loop bounds.
552 parser.parseOperand(lb) || parser.parseKeyword("to") ||
553 parser.parseOperand(ub) || parser.parseKeyword("step") ||
554 parser.parseOperand(step))
555 return failure();
556
557 // Parse the optional initial iteration arguments.
560 regionArgs.push_back(inductionVariable);
561
562 bool hasIterArgs = succeeded(parser.parseOptionalKeyword("iter_args"));
563 if (hasIterArgs) {
564 // Parse assignment list and results type list.
565 if (parser.parseAssignmentList(regionArgs, operands) ||
566 parser.parseArrowTypeList(result.types))
567 return failure();
568 }
569
570 if (regionArgs.size() != result.types.size() + 1)
571 return parser.emitError(
572 parser.getNameLoc(),
573 "mismatch in number of loop-carried values and defined values");
574
575 // Parse optional type, else assume Index.
576 if (parser.parseOptionalColon())
577 type = builder.getIndexType();
578 else if (parser.parseType(type))
579 return failure();
580
581 // Set block argument types, so that they are known when parsing the region.
582 regionArgs.front().type = type;
583 for (auto [iterArg, type] :
584 llvm::zip_equal(llvm::drop_begin(regionArgs), result.types))
585 iterArg.type = type;
586
587 // Parse the body region.
588 Region *body = result.addRegion();
589 if (parser.parseRegion(*body, regionArgs))
590 return failure();
591 ForOp::ensureTerminator(*body, builder, result.location);
592
593 // Resolve input operands. This should be done after parsing the region to
594 // catch invalid IR where operands were defined inside of the region.
595 if (parser.resolveOperand(lb, type, result.operands) ||
596 parser.resolveOperand(ub, type, result.operands) ||
597 parser.resolveOperand(step, type, result.operands))
598 return failure();
599 if (hasIterArgs) {
600 for (auto argOperandType : llvm::zip_equal(llvm::drop_begin(regionArgs),
601 operands, result.types)) {
602 Type type = std::get<2>(argOperandType);
603 std::get<0>(argOperandType).type = type;
604 if (parser.resolveOperand(std::get<1>(argOperandType), type,
605 result.operands))
606 return failure();
607 }
608 }
609
610 // Parse the optional attribute list.
611 if (parser.parseOptionalAttrDict(result.attributes))
612 return failure();
613
614 return success();
615}
616
617SmallVector<Region *> ForOp::getLoopRegions() { return {&getRegion()}; }
618
619Block::BlockArgListType ForOp::getRegionIterArgs() {
620 return getBody()->getArguments().drop_front(getNumInductionVars());
621}
622
623MutableArrayRef<OpOperand> ForOp::getInitsMutable() {
624 return getInitArgsMutable();
625}
626
627FailureOr<LoopLikeOpInterface>
628ForOp::replaceWithAdditionalYields(RewriterBase &rewriter,
629 ValueRange newInitOperands,
630 bool replaceInitOperandUsesInLoop,
631 const NewYieldValuesFn &newYieldValuesFn) {
632 // Create a new loop before the existing one, with the extra operands.
633 OpBuilder::InsertionGuard g(rewriter);
634 rewriter.setInsertionPoint(getOperation());
635 auto inits = llvm::to_vector(getInitArgs());
636 inits.append(newInitOperands.begin(), newInitOperands.end());
637 scf::ForOp newLoop = scf::ForOp::create(
638 rewriter, getLoc(), getLowerBound(), getUpperBound(), getStep(), inits,
639 [](OpBuilder &, Location, Value, ValueRange) {}, getUnsignedCmp());
640 newLoop->setDiscardableAttrs(getOperation()->getDiscardableAttrDictionary());
641
642 // Generate the new yield values and append them to the scf.yield operation.
643 auto yieldOp = cast<scf::YieldOp>(getBody()->getTerminator());
644 ArrayRef<BlockArgument> newIterArgs =
645 newLoop.getBody()->getArguments().take_back(newInitOperands.size());
646 {
647 OpBuilder::InsertionGuard g(rewriter);
648 rewriter.setInsertionPoint(yieldOp);
649 SmallVector<Value> newYieldedValues =
650 newYieldValuesFn(rewriter, getLoc(), newIterArgs);
651 assert(newInitOperands.size() == newYieldedValues.size() &&
652 "expected as many new yield values as new iter operands");
653 rewriter.modifyOpInPlace(yieldOp, [&]() {
654 yieldOp.getResultsMutable().append(newYieldedValues);
655 });
656 }
657
658 // Move the loop body to the new op.
659 rewriter.mergeBlocks(getBody(), newLoop.getBody(),
660 newLoop.getBody()->getArguments().take_front(
661 getBody()->getNumArguments()));
662
663 if (replaceInitOperandUsesInLoop) {
664 // Replace all uses of `newInitOperands` with the corresponding basic block
665 // arguments.
666 for (auto it : llvm::zip(newInitOperands, newIterArgs)) {
667 rewriter.replaceUsesWithIf(std::get<0>(it), std::get<1>(it),
668 [&](OpOperand &use) {
669 Operation *user = use.getOwner();
670 return newLoop->isProperAncestor(user);
671 });
672 }
673 }
674
675 // Replace the old loop.
676 rewriter.replaceOp(getOperation(),
677 newLoop->getResults().take_front(getNumResults()));
678 return cast<LoopLikeOpInterface>(newLoop.getOperation());
679}
680
682 auto ivArg = llvm::dyn_cast<BlockArgument>(val);
683 if (!ivArg)
684 return ForOp();
685 assert(ivArg.getOwner() && "unlinked block argument");
686 auto *containingOp = ivArg.getOwner()->getParentOp();
687 return dyn_cast_or_null<ForOp>(containingOp);
688}
689
690OperandRange ForOp::getEntrySuccessorOperands(RegionSuccessor successor) {
691 return getInitArgs();
692}
693
694void ForOp::getSuccessorRegions(RegionBranchPoint point,
696 if (std::optional<APInt> tripCount = getStaticTripCount()) {
697 // The loop has a known static trip count.
698 if (point.isParent()) {
699 if (*tripCount == 0) {
700 // The loop has zero iterations. It branches directly back to the
701 // parent.
702 regions.push_back(RegionSuccessor(getOperation()));
703 } else {
704 // The loop has at least one iteration. It branches into the body.
705 regions.push_back(RegionSuccessor(&getRegion()));
706 }
707 return;
708 } else if (*tripCount == 1) {
709 // The loop has exactly 1 iteration. Therefore, it branches from the
710 // region to the parent. (No further iteration.)
711 regions.push_back(RegionSuccessor(getOperation()));
712 return;
713 }
714 }
715
716 // Both the operation itself and the region may be branching into the body or
717 // back into the operation itself. It is possible for loop not to enter the
718 // body.
719 regions.push_back(RegionSuccessor(&getRegion()));
720 regions.push_back(RegionSuccessor(getOperation()));
721}
722
723ValueRange ForOp::getSuccessorInputs(RegionSuccessor successor) {
724 return successor.isOperation() ? ValueRange(getResults())
725 : ValueRange(getRegionIterArgs());
726}
727
728SmallVector<Region *> ForallOp::getLoopRegions() { return {&getRegion()}; }
729
730/// Promotes the loop body of a forallOp to its containing block if it can be
731/// determined that the loop has a single iteration.
732LogicalResult scf::ForallOp::promoteIfSingleIteration(RewriterBase &rewriter) {
733 for (auto [lb, ub, step] :
734 llvm::zip(getMixedLowerBound(), getMixedUpperBound(), getMixedStep())) {
735 auto tripCount =
736 constantTripCount(lb, ub, step, /*isSigned=*/true, computeUbMinusLb);
737 if (!tripCount.has_value() || *tripCount != 1)
738 return failure();
739 }
740
741 return promote(rewriter, *this);
742}
743
744Block::BlockArgListType ForallOp::getRegionIterArgs() {
745 return getBody()->getArguments().drop_front(getRank());
746}
747
748MutableArrayRef<OpOperand> ForallOp::getInitsMutable() {
749 return getOutputsMutable();
750}
751
752/// Promotes the loop body of a scf::ForallOp to its containing block.
753LogicalResult mlir::scf::promote(RewriterBase &rewriter,
754 scf::ForallOp forallOp) {
755 OpBuilder::InsertionGuard g(rewriter);
756 scf::InParallelOp terminator = forallOp.getTerminator();
757
758 // Check that all combining ops can be promoted before modifying the IR,
759 // including materializing the lower bounds below.
760 for (Operation &op : terminator.getYieldingOps()) {
761 if (!isa<tensor::ParallelInsertSliceOp>(op))
762 return rewriter.notifyMatchFailure(forallOp, [&](Diagnostic &diag) {
763 diag << "unsupported parallel combining op: " << op.getName();
764 });
765 }
766
767 // Replace block arguments with lower bounds (replacements for IVs) and
768 // outputs.
769 SmallVector<Value> bbArgReplacements = forallOp.getLowerBound(rewriter);
770 bbArgReplacements.append(forallOp.getOutputs().begin(),
771 forallOp.getOutputs().end());
772
773 // Collect combining ops in shared output order before inlining replaces the
774 // output block arguments. Keep a separate list for each output.
776 yieldingOps.reserve(forallOp.getNumResults());
777
778 for (BlockArgument outputArg : forallOp.getRegionIterArgs()) {
779 auto &outputOps = yieldingOps.emplace_back();
780
781 for (Operation *op : forallOp.getCombiningOps(outputArg)) {
782 auto combiningOp = cast<ParallelCombiningOpInterface>(op);
783 // Supported tensor.parallel_insert_slice ops have a single destination.
784 if (combiningOp->getParentOp() != terminator.getOperation() ||
785 combiningOp.getUpdatedDestinations()[0].get() != outputArg)
786 continue;
787
788 outputOps.push_back(combiningOp);
789 }
790 }
791
792 // Move the loop body operations to the loop's containing block.
793 rewriter.inlineBlockBefore(forallOp.getBody(), forallOp->getBlock(),
794 forallOp->getIterator(), bbArgReplacements);
795
796 // Replace the terminator with tensor.insert_slice ops.
797 rewriter.setInsertionPointAfter(forallOp);
798 SmallVector<Value> results;
799 results.reserve(forallOp.getResults().size());
800 for (auto [output, combiningOps] :
801 llvm::zip_equal(forallOp.getOutputs(), yieldingOps)) {
802 Value result = output;
803
804 for (ParallelCombiningOpInterface combiningOp : combiningOps) {
805 auto insertOp = cast<tensor::ParallelInsertSliceOp>(combiningOp);
806 result = tensor::InsertSliceOp::create(
807 rewriter, forallOp.getLoc(), result.getType(), insertOp.getSource(),
808 result, insertOp.getOffsets(), insertOp.getSizes(),
809 insertOp.getStrides(), insertOp.getStaticOffsets(),
810 insertOp.getStaticSizes(), insertOp.getStaticStrides());
811 }
812
813 results.push_back(result);
814 }
815
816 rewriter.replaceAllUsesWith(forallOp.getResults(), results);
817
818 // Erase the old terminator and the loop.
819 rewriter.eraseOp(terminator);
820 rewriter.eraseOp(forallOp);
821 return success();
822}
823
825 OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs,
826 ValueRange steps, ValueRange iterArgs,
828 bodyBuilder) {
829 assert(lbs.size() == ubs.size() &&
830 "expected the same number of lower and upper bounds");
831 assert(lbs.size() == steps.size() &&
832 "expected the same number of lower bounds and steps");
833
834 // If there are no bounds, call the body-building function and return early.
835 if (lbs.empty()) {
836 ValueVector results =
837 bodyBuilder ? bodyBuilder(builder, loc, ValueRange(), iterArgs)
838 : ValueVector();
839 assert(results.size() == iterArgs.size() &&
840 "loop nest body must return as many values as loop has iteration "
841 "arguments");
842 return LoopNest{{}, std::move(results)};
843 }
844
845 // First, create the loop structure iteratively using the body-builder
846 // callback of `ForOp::build`. Do not create `YieldOp`s yet.
847 OpBuilder::InsertionGuard guard(builder);
850 loops.reserve(lbs.size());
851 ivs.reserve(lbs.size());
852 ValueRange currentIterArgs = iterArgs;
853 Location currentLoc = loc;
854 for (unsigned i = 0, e = lbs.size(); i < e; ++i) {
855 auto loop = scf::ForOp::create(
856 builder, currentLoc, lbs[i], ubs[i], steps[i], currentIterArgs,
857 [&](OpBuilder &nestedBuilder, Location nestedLoc, Value iv,
858 ValueRange args) {
859 ivs.push_back(iv);
860 // It is safe to store ValueRange args because it points to block
861 // arguments of a loop operation that we also own.
862 currentIterArgs = args;
863 currentLoc = nestedLoc;
864 });
865 // Set the builder to point to the body of the newly created loop. We don't
866 // do this in the callback because the builder is reset when the callback
867 // returns.
868 builder.setInsertionPointToStart(loop.getBody());
869 loops.push_back(loop);
870 }
871
872 // For all loops but the innermost, yield the results of the nested loop.
873 for (unsigned i = 0, e = loops.size() - 1; i < e; ++i) {
874 builder.setInsertionPointToEnd(loops[i].getBody());
875 scf::YieldOp::create(builder, loc, loops[i + 1].getResults());
876 }
877
878 // In the body of the innermost loop, call the body building function if any
879 // and yield its results.
880 builder.setInsertionPointToStart(loops.back().getBody());
881 ValueVector results = bodyBuilder
882 ? bodyBuilder(builder, currentLoc, ivs,
883 loops.back().getRegionIterArgs())
884 : ValueVector();
885 assert(results.size() == iterArgs.size() &&
886 "loop nest body must return as many values as loop has iteration "
887 "arguments");
888 builder.setInsertionPointToEnd(loops.back().getBody());
889 scf::YieldOp::create(builder, loc, results);
890
891 // Return the loops.
892 ValueVector nestResults;
893 llvm::append_range(nestResults, loops.front().getResults());
894 return LoopNest{std::move(loops), std::move(nestResults)};
895}
896
898 OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs,
899 ValueRange steps,
900 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilder) {
901 // Delegate to the main function by wrapping the body builder.
902 return buildLoopNest(builder, loc, lbs, ubs, steps, {},
903 [&bodyBuilder](OpBuilder &nestedBuilder,
904 Location nestedLoc, ValueRange ivs,
906 if (bodyBuilder)
907 bodyBuilder(nestedBuilder, nestedLoc, ivs);
908 return {};
909 });
910}
911
914 OpOperand &operand, Value replacement,
915 const ValueTypeCastFnTy &castFn) {
916 assert(operand.getOwner() == forOp);
917 Type oldType = operand.get().getType(), newType = replacement.getType();
918
919 // 1. Create new iter operands, exactly 1 is replaced.
920 assert(operand.getOperandNumber() >= forOp.getNumControlOperands() &&
921 "expected an iter OpOperand");
922 assert(operand.get().getType() != replacement.getType() &&
923 "Expected a different type");
924 SmallVector<Value> newIterOperands;
925 for (OpOperand &opOperand : forOp.getInitArgsMutable()) {
926 if (opOperand.getOperandNumber() == operand.getOperandNumber()) {
927 newIterOperands.push_back(replacement);
928 continue;
929 }
930 newIterOperands.push_back(opOperand.get());
931 }
932
933 // 2. Create the new forOp shell.
934 scf::ForOp newForOp = scf::ForOp::create(
935 rewriter, forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(),
936 forOp.getStep(), newIterOperands, /*bodyBuilder=*/nullptr,
937 forOp.getUnsignedCmp());
938 newForOp->setDiscardableAttrs(
939 forOp->getDiscardableAttrDictionary().getValue());
940 Block &newBlock = newForOp.getRegion().front();
941 SmallVector<Value, 4> newBlockTransferArgs(newBlock.getArguments().begin(),
942 newBlock.getArguments().end());
943
944 // 3. Inject an incoming cast op at the beginning of the block for the bbArg
945 // corresponding to the `replacement` value.
946 OpBuilder::InsertionGuard g(rewriter);
947 rewriter.setInsertionPointToStart(&newBlock);
948 BlockArgument newRegionIterArg = newForOp.getTiedLoopRegionIterArg(
949 &newForOp->getOpOperand(operand.getOperandNumber()));
950 Value castIn = castFn(rewriter, newForOp.getLoc(), oldType, newRegionIterArg);
951 newBlockTransferArgs[newRegionIterArg.getArgNumber()] = castIn;
952
953 // 4. Steal the old block ops, mapping to the newBlockTransferArgs.
954 Block &oldBlock = forOp.getRegion().front();
955 rewriter.mergeBlocks(&oldBlock, &newBlock, newBlockTransferArgs);
956
957 // 5. Inject an outgoing cast op at the end of the block and yield it instead.
958 auto clonedYieldOp = cast<scf::YieldOp>(newBlock.getTerminator());
959 rewriter.setInsertionPoint(clonedYieldOp);
960 unsigned yieldIdx =
961 newRegionIterArg.getArgNumber() - forOp.getNumInductionVars();
962 Value castOut = castFn(rewriter, newForOp.getLoc(), newType,
963 clonedYieldOp.getOperand(yieldIdx));
964 SmallVector<Value> newYieldOperands = clonedYieldOp.getOperands();
965 newYieldOperands[yieldIdx] = castOut;
966 scf::YieldOp::create(rewriter, newForOp.getLoc(), newYieldOperands);
967 rewriter.eraseOp(clonedYieldOp);
968
969 // 6. Inject an outgoing cast op after the forOp.
970 rewriter.setInsertionPointAfter(newForOp);
971 SmallVector<Value> newResults = newForOp.getResults();
972 newResults[yieldIdx] =
973 castFn(rewriter, newForOp.getLoc(), oldType, newResults[yieldIdx]);
974
975 return newResults;
976}
977
978bool mlir::scf::isShapePreserving(ForOp forOp, int64_t arg) {
979 assert(arg < static_cast<int64_t>(forOp.getNumResults()) &&
980 "arg is out of bounds");
981 Value value = forOp.getYieldedValues()[arg];
982 while (value) {
983 if (value == forOp.getRegionIterArgs()[arg])
984 return true;
985 OpResult opResult = dyn_cast<OpResult>(value);
986 if (!opResult)
987 return false;
988
990 .Case([&](DestinationStyleOpInterface op) {
991 return op.getTiedOpOperand(opResult)->get();
992 })
993 .Case([&](ForOp forOp) {
994 return isShapePreserving(forOp, opResult.getResultNumber())
995 ? forOp.getInitArgs()[opResult.getResultNumber()]
996 : Value();
997 })
998 .Default(nullptr);
999 }
1000 return false;
1001}
1002
1003namespace {
1004/// Fold scf.for iter_arg/result pairs that go through incoming/ougoing
1005/// a tensor.cast op pair so as to pull the tensor.cast inside the scf.for:
1006///
1007/// ```
1008/// %0 = tensor.cast %t0 : tensor<32x1024xf32> to tensor<?x?xf32>
1009/// %1 = scf.for %i = %c0 to %c1024 step %c32 iter_args(%iter_t0 = %0)
1010/// -> (tensor<?x?xf32>) {
1011/// %2 = call @do(%iter_t0) : (tensor<?x?xf32>) -> tensor<32x1024xf32>
1012/// %3 = tensor.cast %2 : tensor<32x1024xf32> to tensor<?x?xf32>
1013/// scf.yield %3 : tensor<?x?xf32>
1014/// }
1015/// use_of(%1)
1016/// ```
1017///
1018/// folds into:
1019///
1020/// ```
1021/// %0 = scf.for %arg2 = %c0 to %c1024 step %c32 iter_args(%arg3 = %arg0)
1022/// -> (tensor<32x1024xf32>) {
1023/// %2 = tensor.cast %arg3 : tensor<32x1024xf32> to tensor<?x?xf32>
1024/// %3 = call @do(%2) : (tensor<?x?xf32>) -> tensor<32x1024xf32>
1025/// scf.yield %3 : tensor<32x1024xf32>
1026/// }
1027/// %1 = tensor.cast %0 : tensor<32x1024xf32> to tensor<?x?xf32>
1028/// use_of(%1)
1029/// ```
1030struct ForOpTensorCastFolder : public OpRewritePattern<ForOp> {
1032
1033 LogicalResult matchAndRewrite(ForOp op,
1034 PatternRewriter &rewriter) const override {
1035 for (auto it : llvm::zip(op.getInitArgsMutable(), op.getResults())) {
1036 OpOperand &iterOpOperand = std::get<0>(it);
1037 auto incomingCast = iterOpOperand.get().getDefiningOp<tensor::CastOp>();
1038 if (!incomingCast ||
1039 incomingCast.getSource().getType() == incomingCast.getType())
1040 continue;
1041 // If the dest type of the cast does not preserve static information in
1042 // the source type.
1044 incomingCast.getDest().getType(),
1045 incomingCast.getSource().getType()))
1046 continue;
1047 if (!std::get<1>(it).hasOneUse())
1048 continue;
1049 // The fold casts the yielded value to the cast's source type, which is
1050 // only valid if every iteration yields a value of that type. A dynamic
1051 // dimension of that type stays dynamic, so its size may still change
1052 // between iterations; only the static sizes are asserted.
1053 int64_t resultNumber = std::get<1>(it).getResultNumber();
1054 auto yieldedCast =
1055 op.getYieldedValues()[resultNumber].getDefiningOp<tensor::CastOp>();
1056 bool yieldsStaticType =
1057 yieldedCast && yieldedCast.getSource().getType() ==
1058 incomingCast.getSource().getType();
1059 if (!yieldsStaticType && !isShapePreserving(op, resultNumber))
1060 continue;
1061
1062 // Create a new ForOp with that iter operand replaced.
1063 rewriter.replaceOp(
1065 rewriter, op, iterOpOperand, incomingCast.getSource(),
1066 [](OpBuilder &b, Location loc, Type type, Value source) {
1067 return tensor::CastOp::create(b, loc, type, source);
1068 }));
1069 return success();
1070 }
1071 return failure();
1072 }
1073};
1074} // namespace
1075
1076void ForOp::getCanonicalizationPatterns(RewritePatternSet &results,
1077 MLIRContext *context) {
1078 results.add<ForOpTensorCastFolder>(context);
1080 results, ForOp::getOperationName());
1081 // Inline single-iteration loops before applying the generic region branch op
1082 // canonicalizations, which may otherwise remove tied iter_args and results
1083 // independently.
1085 results, ForOp::getOperationName(),
1086 /*replBuilderFn=*/
1087 [](OpBuilder &builder, Location loc, Value value) {
1088 // scf.for has only one non-successor input value: the loop induction
1089 // variable. In case of a single acyclic path through the op, the IV can
1090 // be safely replaced with the lower bound.
1091 auto blockArg = cast<BlockArgument>(value);
1092 assert(blockArg.getArgNumber() == 0 && "expected induction variable");
1093 auto forOp = cast<ForOp>(blockArg.getOwner()->getParentOp());
1094 return forOp.getLowerBound();
1095 },
1097 /*benefit=*/2);
1098}
1099
1100std::optional<APInt> ForOp::getConstantStep() {
1101 IntegerAttr step;
1102 if (matchPattern(getStep(), m_Constant(&step)))
1103 return step.getValue();
1104 return {};
1105}
1106
1107std::optional<MutableArrayRef<OpOperand>> ForOp::getYieldedValuesMutable() {
1108 return cast<scf::YieldOp>(getBody()->getTerminator()).getResultsMutable();
1109}
1110
1111Speculation::Speculatability ForOp::getSpeculatability() {
1112 // `scf.for (I = Start; I < End; I += 1)` terminates for all values of Start
1113 // and End.
1114 if (auto constantStep = getConstantStep())
1115 if (*constantStep == 1)
1117
1118 // For Step != 1, the loop may not terminate. We can add more smarts here if
1119 // needed.
1121}
1122
1123std::optional<APInt> ForOp::getStaticTripCount() {
1124 return constantTripCount(getLowerBound(), getUpperBound(), getStep(),
1125 /*isSigned=*/!getUnsignedCmp(), computeUbMinusLb);
1126}
1127
1128//===----------------------------------------------------------------------===//
1129// ForallOp
1130//===----------------------------------------------------------------------===//
1131
1132LogicalResult ForallOp::verify() {
1133 unsigned numLoops = getRank();
1134 // Check number of outputs.
1135 if (getNumResults() != getOutputs().size())
1136 return emitOpError("produces ")
1137 << getNumResults() << " results, but has only "
1138 << getOutputs().size() << " outputs";
1139
1140 // Check that the body defines block arguments for thread indices and outputs.
1141 auto *body = getBody();
1142 if (body->getNumArguments() != numLoops + getOutputs().size())
1143 return emitOpError("region expects ") << numLoops << " arguments";
1144 for (int64_t i = 0; i < numLoops; ++i)
1145 if (!body->getArgument(i).getType().isIndex())
1146 return emitOpError("expects ")
1147 << i << "-th block argument to be an index";
1148 for (unsigned i = 0; i < getOutputs().size(); ++i)
1149 if (body->getArgument(i + numLoops).getType() != getOutputs()[i].getType())
1150 return emitOpError("type mismatch between ")
1151 << i << "-th output and corresponding block argument";
1152 if (getMapping().has_value() && !getMapping()->empty()) {
1153 if (getDeviceMappingAttrs().size() != numLoops)
1154 return emitOpError() << "mapping attribute size must match op rank";
1155 if (failed(getDeviceMaskingAttr()))
1156 return emitOpError() << getMappingAttrName()
1157 << " supports at most one device masking attribute";
1158 }
1159
1160 // Verify mixed static/dynamic control variables.
1161 Operation *op = getOperation();
1162 if (failed(verifyListOfOperandsOrIntegers(op, "lower bound", numLoops,
1163 getStaticLowerBound(),
1164 getDynamicLowerBound())))
1165 return failure();
1166 if (failed(verifyListOfOperandsOrIntegers(op, "upper bound", numLoops,
1167 getStaticUpperBound(),
1168 getDynamicUpperBound())))
1169 return failure();
1170 if (failed(verifyListOfOperandsOrIntegers(op, "step", numLoops,
1171 getStaticStep(), getDynamicStep())))
1172 return failure();
1173
1174 return success();
1175}
1176
1177void ForallOp::print(OpAsmPrinter &p) {
1178 Operation *op = getOperation();
1179 p << " (" << getInductionVars();
1180 if (isNormalized()) {
1181 p << ") in ";
1182 printDynamicIndexList(p, op, getDynamicUpperBound(), getStaticUpperBound(),
1183 /*valueTypes=*/{}, /*scalables=*/{},
1185 } else {
1186 p << ") = ";
1187 printDynamicIndexList(p, op, getDynamicLowerBound(), getStaticLowerBound(),
1188 /*valueTypes=*/{}, /*scalables=*/{},
1190 p << " to ";
1191 printDynamicIndexList(p, op, getDynamicUpperBound(), getStaticUpperBound(),
1192 /*valueTypes=*/{}, /*scalables=*/{},
1194 p << " step ";
1195 printDynamicIndexList(p, op, getDynamicStep(), getStaticStep(),
1196 /*valueTypes=*/{}, /*scalables=*/{},
1198 }
1199 printInitializationList(p, getRegionOutArgs(), getOutputs(), " shared_outs");
1200 p << " ";
1201 if (!getRegionOutArgs().empty())
1202 p << "-> (" << getResultTypes() << ") ";
1203 p.printRegion(getRegion(),
1204 /*printEntryBlockArgs=*/false,
1205 /*printBlockTerminators=*/getNumResults() > 0);
1206 SmallVector<NamedAttribute> attrs(op->getDiscardableAttrs());
1207 if (ArrayAttr mapping = getMappingAttr())
1208 attrs.emplace_back(getMappingAttrName(), mapping);
1209 llvm::sort(attrs);
1210 p.printOptionalAttrDict(attrs);
1211}
1212
1213ParseResult ForallOp::parse(OpAsmParser &parser, OperationState &result) {
1214 OpBuilder b(parser.getContext());
1215 auto indexType = b.getIndexType();
1216
1217 // Parse an opening `(` followed by thread index variables followed by `)`
1218 // TODO: when we can refer to such "induction variable"-like handles from the
1219 // declarative assembly format, we can implement the parser as a custom hook.
1220 SmallVector<OpAsmParser::Argument, 4> ivs;
1222 return failure();
1223
1224 DenseI64ArrayAttr staticLbs, staticUbs, staticSteps;
1225 SmallVector<OpAsmParser::UnresolvedOperand> dynamicLbs, dynamicUbs,
1226 dynamicSteps;
1227 if (succeeded(parser.parseOptionalKeyword("in"))) {
1228 // Parse upper bounds.
1229 if (parseDynamicIndexList(parser, dynamicUbs, staticUbs,
1230 /*valueTypes=*/nullptr,
1232 parser.resolveOperands(dynamicUbs, indexType, result.operands))
1233 return failure();
1234
1235 unsigned numLoops = ivs.size();
1236 staticLbs = b.getDenseI64ArrayAttr(SmallVector<int64_t>(numLoops, 0));
1237 staticSteps = b.getDenseI64ArrayAttr(SmallVector<int64_t>(numLoops, 1));
1238 } else {
1239 // Parse lower bounds.
1240 if (parser.parseEqual() ||
1241 parseDynamicIndexList(parser, dynamicLbs, staticLbs,
1242 /*valueTypes=*/nullptr,
1244
1245 parser.resolveOperands(dynamicLbs, indexType, result.operands))
1246 return failure();
1247
1248 // Parse upper bounds.
1249 if (parser.parseKeyword("to") ||
1250 parseDynamicIndexList(parser, dynamicUbs, staticUbs,
1251 /*valueTypes=*/nullptr,
1253 parser.resolveOperands(dynamicUbs, indexType, result.operands))
1254 return failure();
1255
1256 // Parse step values.
1257 if (parser.parseKeyword("step") ||
1258 parseDynamicIndexList(parser, dynamicSteps, staticSteps,
1259 /*valueTypes=*/nullptr,
1261 parser.resolveOperands(dynamicSteps, indexType, result.operands))
1262 return failure();
1263 }
1264
1265 // Parse out operands and results.
1266 SmallVector<OpAsmParser::Argument, 4> regionOutArgs;
1267 SmallVector<OpAsmParser::UnresolvedOperand, 4> outOperands;
1268 SMLoc outOperandsLoc = parser.getCurrentLocation();
1269 if (succeeded(parser.parseOptionalKeyword("shared_outs"))) {
1270 if (outOperands.size() != result.types.size())
1271 return parser.emitError(outOperandsLoc,
1272 "mismatch between out operands and types");
1273 if (parser.parseAssignmentList(regionOutArgs, outOperands) ||
1274 parser.parseOptionalArrowTypeList(result.types) ||
1275 parser.resolveOperands(outOperands, result.types, outOperandsLoc,
1276 result.operands))
1277 return failure();
1278 }
1279
1280 // Parse region.
1281 SmallVector<OpAsmParser::Argument, 4> regionArgs;
1282 std::unique_ptr<Region> region = std::make_unique<Region>();
1283 for (auto &iv : ivs) {
1284 iv.type = b.getIndexType();
1285 regionArgs.push_back(iv);
1286 }
1287 for (const auto &it : llvm::enumerate(regionOutArgs)) {
1288 auto &out = it.value();
1289 out.type = result.types[it.index()];
1290 regionArgs.push_back(out);
1291 }
1292 if (parser.parseRegion(*region, regionArgs))
1293 return failure();
1294
1295 // Ensure terminator and move region.
1296 ForallOp::ensureTerminator(*region, b, result.location);
1297 result.addRegion(std::move(region));
1298
1299 // Parse the optional attribute list.
1300 if (parser.parseOptionalAttrDict(result.attributes))
1301 return failure();
1302
1303 result.addAttribute("staticLowerBound", staticLbs);
1304 result.addAttribute("staticUpperBound", staticUbs);
1305 result.addAttribute("staticStep", staticSteps);
1306 result.addAttribute("operandSegmentSizes",
1308 {static_cast<int32_t>(dynamicLbs.size()),
1309 static_cast<int32_t>(dynamicUbs.size()),
1310 static_cast<int32_t>(dynamicSteps.size()),
1311 static_cast<int32_t>(outOperands.size())}));
1312 return success();
1313}
1314
1315// Builder that takes loop bounds.
1316void ForallOp::build(
1317 mlir::OpBuilder &b, mlir::OperationState &result,
1318 ArrayRef<OpFoldResult> lbs, ArrayRef<OpFoldResult> ubs,
1319 ArrayRef<OpFoldResult> steps, ValueRange outputs,
1320 std::optional<ArrayAttr> mapping,
1321 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn) {
1322 SmallVector<int64_t> staticLbs, staticUbs, staticSteps;
1323 SmallVector<Value> dynamicLbs, dynamicUbs, dynamicSteps;
1324 dispatchIndexOpFoldResults(lbs, dynamicLbs, staticLbs);
1325 dispatchIndexOpFoldResults(ubs, dynamicUbs, staticUbs);
1326 dispatchIndexOpFoldResults(steps, dynamicSteps, staticSteps);
1327
1328 result.addOperands(dynamicLbs);
1329 result.addOperands(dynamicUbs);
1330 result.addOperands(dynamicSteps);
1331 result.addOperands(outputs);
1332 result.addTypes(TypeRange(outputs));
1333
1334 result.addAttribute(getStaticLowerBoundAttrName(result.name),
1335 b.getDenseI64ArrayAttr(staticLbs));
1336 result.addAttribute(getStaticUpperBoundAttrName(result.name),
1337 b.getDenseI64ArrayAttr(staticUbs));
1338 result.addAttribute(getStaticStepAttrName(result.name),
1339 b.getDenseI64ArrayAttr(staticSteps));
1340 result.addAttribute(
1341 "operandSegmentSizes",
1342 b.getDenseI32ArrayAttr({static_cast<int32_t>(dynamicLbs.size()),
1343 static_cast<int32_t>(dynamicUbs.size()),
1344 static_cast<int32_t>(dynamicSteps.size()),
1345 static_cast<int32_t>(outputs.size())}));
1346 if (mapping.has_value()) {
1347 result.addAttribute(ForallOp::getMappingAttrName(result.name),
1348 mapping.value());
1349 }
1350
1351 Region *bodyRegion = result.addRegion();
1352 OpBuilder::InsertionGuard g(b);
1353 b.createBlock(bodyRegion);
1354 Block &bodyBlock = bodyRegion->front();
1355
1356 // Add block arguments for indices and outputs.
1357 bodyBlock.addArguments(
1358 SmallVector<Type>(lbs.size(), b.getIndexType()),
1359 SmallVector<Location>(staticLbs.size(), result.location));
1360 bodyBlock.addArguments(
1361 TypeRange(outputs),
1362 SmallVector<Location>(outputs.size(), result.location));
1363
1364 b.setInsertionPointToStart(&bodyBlock);
1365 if (!bodyBuilderFn) {
1366 ForallOp::ensureTerminator(*bodyRegion, b, result.location);
1367 return;
1368 }
1369 bodyBuilderFn(b, result.location, bodyBlock.getArguments());
1370}
1371
1372// Builder that takes loop bounds.
1373void ForallOp::build(
1374 mlir::OpBuilder &b, mlir::OperationState &result,
1375 ArrayRef<OpFoldResult> ubs, ValueRange outputs,
1376 std::optional<ArrayAttr> mapping,
1377 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn) {
1378 unsigned numLoops = ubs.size();
1379 SmallVector<OpFoldResult> lbs(numLoops, b.getIndexAttr(0));
1380 SmallVector<OpFoldResult> steps(numLoops, b.getIndexAttr(1));
1381 build(b, result, lbs, ubs, steps, outputs, mapping, bodyBuilderFn);
1382}
1383
1384// Checks if the lbs are zeros and steps are ones.
1385bool ForallOp::isNormalized() {
1386 auto allEqual = [](ArrayRef<OpFoldResult> results, int64_t val) {
1387 return llvm::all_of(results, [&](OpFoldResult ofr) {
1388 auto intValue = getConstantIntValue(ofr);
1389 return intValue.has_value() && intValue == val;
1390 });
1391 };
1392 return allEqual(getMixedLowerBound(), 0) && allEqual(getMixedStep(), 1);
1393}
1394
1395InParallelOp ForallOp::getTerminator() {
1396 return cast<InParallelOp>(getBody()->getTerminator());
1397}
1398
1399SmallVector<Operation *> ForallOp::getCombiningOps(BlockArgument bbArg) {
1400 SmallVector<Operation *> storeOps;
1401 for (Operation *user : bbArg.getUsers()) {
1402 if (auto parallelOp = dyn_cast<ParallelCombiningOpInterface>(user)) {
1403 storeOps.push_back(parallelOp);
1404 }
1405 }
1406 return storeOps;
1407}
1408
1409SmallVector<DeviceMappingAttrInterface> ForallOp::getDeviceMappingAttrs() {
1410 SmallVector<DeviceMappingAttrInterface> res;
1411 if (!getMapping())
1412 return res;
1413 for (auto attr : getMapping()->getValue()) {
1414 auto m = dyn_cast<DeviceMappingAttrInterface>(attr);
1415 if (m)
1416 res.push_back(m);
1417 }
1418 return res;
1419}
1420
1421FailureOr<DeviceMaskingAttrInterface> ForallOp::getDeviceMaskingAttr() {
1422 DeviceMaskingAttrInterface res;
1423 if (!getMapping())
1424 return res;
1425 for (auto attr : getMapping()->getValue()) {
1426 auto m = dyn_cast<DeviceMaskingAttrInterface>(attr);
1427 if (m && res)
1428 return failure();
1429 if (m)
1430 res = m;
1431 }
1432 return res;
1433}
1434
1435bool ForallOp::usesLinearMapping() {
1436 SmallVector<DeviceMappingAttrInterface> ifaces = getDeviceMappingAttrs();
1437 if (ifaces.empty())
1438 return false;
1439 return ifaces.front().isLinearMapping();
1440}
1441
1442std::optional<SmallVector<Value>> ForallOp::getLoopInductionVars() {
1443 return SmallVector<Value>{getBody()->getArguments().take_front(getRank())};
1444}
1445
1446// Get lower bounds as OpFoldResult.
1447std::optional<SmallVector<OpFoldResult>> ForallOp::getLoopLowerBounds() {
1448 Builder b(getOperation()->getContext());
1449 return getMixedValues(getStaticLowerBound(), getDynamicLowerBound(), b);
1450}
1451
1452// Get upper bounds as OpFoldResult.
1453std::optional<SmallVector<OpFoldResult>> ForallOp::getLoopUpperBounds() {
1454 Builder b(getOperation()->getContext());
1455 return getMixedValues(getStaticUpperBound(), getDynamicUpperBound(), b);
1456}
1457
1458// Get steps as OpFoldResult.
1459std::optional<SmallVector<OpFoldResult>> ForallOp::getLoopSteps() {
1460 Builder b(getOperation()->getContext());
1461 return getMixedValues(getStaticStep(), getDynamicStep(), b);
1462}
1463
1465 auto tidxArg = llvm::dyn_cast<BlockArgument>(val);
1466 if (!tidxArg)
1467 return ForallOp();
1468 assert(tidxArg.getOwner() && "unlinked block argument");
1469 auto *containingOp = tidxArg.getOwner()->getParentOp();
1470 return dyn_cast<ForallOp>(containingOp);
1471}
1472
1473namespace {
1474/// Fold tensor.dim(forall shared_outs(... = %t)) to tensor.dim(%t).
1475struct DimOfForallOp : public OpRewritePattern<tensor::DimOp> {
1476 using OpRewritePattern<tensor::DimOp>::OpRewritePattern;
1477
1478 LogicalResult matchAndRewrite(tensor::DimOp dimOp,
1479 PatternRewriter &rewriter) const final {
1480 auto forallOp = dimOp.getSource().getDefiningOp<ForallOp>();
1481 if (!forallOp)
1482 return failure();
1483 Value sharedOut =
1484 forallOp.getTiedOpOperand(llvm::cast<OpResult>(dimOp.getSource()))
1485 ->get();
1486 rewriter.modifyOpInPlace(
1487 dimOp, [&]() { dimOp.getSourceMutable().assign(sharedOut); });
1488 return success();
1489 }
1490};
1491
1492class ForallOpControlOperandsFolder : public OpRewritePattern<ForallOp> {
1493public:
1494 using OpRewritePattern<ForallOp>::OpRewritePattern;
1495
1496 LogicalResult matchAndRewrite(ForallOp op,
1497 PatternRewriter &rewriter) const override {
1498 SmallVector<OpFoldResult> mixedLowerBound(op.getMixedLowerBound());
1499 SmallVector<OpFoldResult> mixedUpperBound(op.getMixedUpperBound());
1500 SmallVector<OpFoldResult> mixedStep(op.getMixedStep());
1501 if (failed(foldDynamicIndexList(mixedLowerBound)) &&
1502 failed(foldDynamicIndexList(mixedUpperBound)) &&
1503 failed(foldDynamicIndexList(mixedStep)))
1504 return failure();
1505
1506 rewriter.modifyOpInPlace(op, [&]() {
1507 SmallVector<Value> dynamicLowerBound, dynamicUpperBound, dynamicStep;
1508 SmallVector<int64_t> staticLowerBound, staticUpperBound, staticStep;
1509 dispatchIndexOpFoldResults(mixedLowerBound, dynamicLowerBound,
1510 staticLowerBound);
1511 op.getDynamicLowerBoundMutable().assign(dynamicLowerBound);
1512 op.setStaticLowerBound(staticLowerBound);
1513
1514 dispatchIndexOpFoldResults(mixedUpperBound, dynamicUpperBound,
1515 staticUpperBound);
1516 op.getDynamicUpperBoundMutable().assign(dynamicUpperBound);
1517 op.setStaticUpperBound(staticUpperBound);
1518
1519 dispatchIndexOpFoldResults(mixedStep, dynamicStep, staticStep);
1520 op.getDynamicStepMutable().assign(dynamicStep);
1521 op.setStaticStep(staticStep);
1522
1523 op->setInherentAttr(
1524 rewriter.getStringAttr(ForallOp::getOperandSegmentSizeAttr()),
1525 rewriter.getDenseI32ArrayAttr(
1526 {static_cast<int32_t>(dynamicLowerBound.size()),
1527 static_cast<int32_t>(dynamicUpperBound.size()),
1528 static_cast<int32_t>(dynamicStep.size()),
1529 static_cast<int32_t>(op.getNumResults())}));
1530 });
1531 return success();
1532 }
1533};
1534
1535/// The following canonicalization pattern folds the iter arguments of
1536/// scf.forall op if :-
1537/// 1. The corresponding result has zero uses.
1538/// 2. The iter argument is NOT being modified within the loop body.
1539/// uses.
1540///
1541/// Example of first case :-
1542/// INPUT:
1543/// %res:3 = scf.forall ... shared_outs(%arg0 = %a, %arg1 = %b, %arg2 = %c)
1544/// {
1545/// ...
1546/// <SOME USE OF %arg0>
1547/// <SOME USE OF %arg1>
1548/// <SOME USE OF %arg2>
1549/// ...
1550/// scf.forall.in_parallel {
1551/// <STORE OP WITH DESTINATION %arg1>
1552/// <STORE OP WITH DESTINATION %arg0>
1553/// <STORE OP WITH DESTINATION %arg2>
1554/// }
1555/// }
1556/// return %res#1
1557///
1558/// OUTPUT:
1559/// %res:3 = scf.forall ... shared_outs(%new_arg0 = %b)
1560/// {
1561/// ...
1562/// <SOME USE OF %a>
1563/// <SOME USE OF %new_arg0>
1564/// <SOME USE OF %c>
1565/// ...
1566/// scf.forall.in_parallel {
1567/// <STORE OP WITH DESTINATION %new_arg0>
1568/// }
1569/// }
1570/// return %res
1571///
1572/// NOTE: 1. All uses of the folded shared_outs (iter argument) within the
1573/// scf.forall is replaced by their corresponding operands.
1574/// 2. Even if there are <STORE OP WITH DESTINATION *> ops within the body
1575/// of the scf.forall besides within scf.forall.in_parallel terminator,
1576/// this canonicalization remains valid. For more details, please refer
1577/// to :
1578/// https://github.com/llvm/llvm-project/pull/90189#discussion_r1589011124
1579/// 3. TODO(avarma): Generalize it for other store ops. Currently it
1580/// handles tensor.parallel_insert_slice ops only.
1581///
1582/// Example of second case :-
1583/// INPUT:
1584/// %res:2 = scf.forall ... shared_outs(%arg0 = %a, %arg1 = %b)
1585/// {
1586/// ...
1587/// <SOME USE OF %arg0>
1588/// <SOME USE OF %arg1>
1589/// ...
1590/// scf.forall.in_parallel {
1591/// <STORE OP WITH DESTINATION %arg1>
1592/// }
1593/// }
1594/// return %res#0, %res#1
1595///
1596/// OUTPUT:
1597/// %res = scf.forall ... shared_outs(%new_arg0 = %b)
1598/// {
1599/// ...
1600/// <SOME USE OF %a>
1601/// <SOME USE OF %new_arg0>
1602/// ...
1603/// scf.forall.in_parallel {
1604/// <STORE OP WITH DESTINATION %new_arg0>
1605/// }
1606/// }
1607/// return %a, %res
1608struct ForallOpIterArgsFolder : public OpRewritePattern<ForallOp> {
1609 using OpRewritePattern<ForallOp>::OpRewritePattern;
1610
1611 LogicalResult matchAndRewrite(ForallOp forallOp,
1612 PatternRewriter &rewriter) const final {
1613 // Step 1: For a given i-th result of scf.forall, check the following :-
1614 // a. If it has any use.
1615 // b. If the corresponding iter argument is being modified within
1616 // the loop, i.e. has at least one store op with the iter arg as
1617 // its destination operand. For this we use
1618 // ForallOp::getCombiningOps(iter_arg).
1619 //
1620 // Based on the check we maintain the following :-
1621 // a. op results, block arguments, outputs to delete
1622 // b. new outputs (i.e., outputs to retain)
1623 SmallVector<Value> resultsToDelete;
1624 SmallVector<Value> outsToDelete;
1625 SmallVector<BlockArgument> blockArgsToDelete;
1626 SmallVector<Value> newOuts;
1627 BitVector resultIndicesToDelete(forallOp.getNumResults(), false);
1628 BitVector blockIndicesToDelete(forallOp.getBody()->getNumArguments(),
1629 false);
1630 for (OpResult result : forallOp.getResults()) {
1631 OpOperand *opOperand = forallOp.getTiedOpOperand(result);
1632 BlockArgument blockArg = forallOp.getTiedBlockArgument(opOperand);
1633 if (result.use_empty() || forallOp.getCombiningOps(blockArg).empty()) {
1634 resultsToDelete.push_back(result);
1635 outsToDelete.push_back(opOperand->get());
1636 blockArgsToDelete.push_back(blockArg);
1637 resultIndicesToDelete[result.getResultNumber()] = true;
1638 blockIndicesToDelete[blockArg.getArgNumber()] = true;
1639 } else {
1640 newOuts.push_back(opOperand->get());
1641 }
1642 }
1643
1644 // Return early if all results of scf.forall have at least one use and being
1645 // modified within the loop.
1646 if (resultsToDelete.empty())
1647 return failure();
1648
1649 // Step 2: Erase combining ops and replace uses of deleted results and
1650 // block arguments with the corresponding outputs.
1651 for (auto blockArg : blockArgsToDelete) {
1652 SmallVector<Operation *> combiningOps =
1653 forallOp.getCombiningOps(blockArg);
1654 for (Operation *combiningOp : combiningOps)
1655 rewriter.eraseOp(combiningOp);
1656 }
1657 for (auto [blockArg, result, out] :
1658 llvm::zip_equal(blockArgsToDelete, resultsToDelete, outsToDelete)) {
1659 rewriter.replaceAllUsesWith(blockArg, out);
1660 rewriter.replaceAllUsesWith(result, out);
1661 }
1662 // TODO: There is no rewriter API for erasing block arguments.
1663 rewriter.modifyOpInPlace(forallOp, [&]() {
1664 forallOp.getBody()->eraseArguments(blockIndicesToDelete);
1665 });
1666
1667 // Step 3. Create a new scf.forall op with only the shared_outs/results
1668 // that should be retained.
1669 auto newForallOp = cast<scf::ForallOp>(
1670 rewriter.eraseOpResults(forallOp, resultIndicesToDelete));
1671 newForallOp.getOutputsMutable().assign(newOuts);
1672
1673 return success();
1674 }
1675};
1676
1677struct ForallOpSingleOrZeroIterationDimsFolder
1678 : public OpRewritePattern<ForallOp> {
1679 using OpRewritePattern<ForallOp>::OpRewritePattern;
1680
1681 LogicalResult matchAndRewrite(ForallOp op,
1682 PatternRewriter &rewriter) const override {
1683 // Do not fold dimensions if they are mapped to processing units.
1684 if (op.getMapping().has_value() && !op.getMapping()->empty())
1685 return failure();
1686 Location loc = op.getLoc();
1687
1688 // Compute new loop bounds that omit all single-iteration loop dimensions.
1689 SmallVector<OpFoldResult> newMixedLowerBounds, newMixedUpperBounds,
1690 newMixedSteps;
1691 SmallVector<std::pair<Value, OpFoldResult>> singleIterationIVs;
1692 for (auto [lb, ub, step, iv] :
1693 llvm::zip(op.getMixedLowerBound(), op.getMixedUpperBound(),
1694 op.getMixedStep(), op.getInductionVars())) {
1695 auto numIterations =
1696 constantTripCount(lb, ub, step, /*isSigned=*/true, computeUbMinusLb);
1697 if (numIterations.has_value()) {
1698 // Remove the loop if it performs zero iterations.
1699 if (*numIterations == 0) {
1700 rewriter.replaceOp(op, op.getOutputs());
1701 return success();
1702 }
1703 // Replace the loop induction variable by the lower bound if the loop
1704 // performs a single iteration. Otherwise, copy the loop bounds.
1705 if (*numIterations == 1) {
1706 singleIterationIVs.emplace_back(iv, lb);
1707 continue;
1708 }
1709 }
1710 newMixedLowerBounds.push_back(lb);
1711 newMixedUpperBounds.push_back(ub);
1712 newMixedSteps.push_back(step);
1713 }
1714
1715 // All of the loop dimensions perform a single iteration. Inline loop body.
1716 if (newMixedLowerBounds.empty())
1717 return promote(rewriter, op);
1718
1719 // Exit if none of the loop dimensions perform a single iteration.
1720 if (newMixedLowerBounds.size() == static_cast<unsigned>(op.getRank())) {
1721 return rewriter.notifyMatchFailure(
1722 op, "no dimensions have 0 or 1 iterations");
1723 }
1724
1725 // Materialize lower bounds only after all failure paths, including
1726 // promotion above, so that failed rewrites leave the IR unchanged.
1727 IRMapping mapping;
1728 for (auto [iv, lb] : singleIterationIVs)
1729 mapping.map(iv, getValueOrCreateConstantIndexOp(rewriter, loc, lb));
1730
1731 // Replace the loop by a lower-dimensional loop.
1732 ForallOp newOp;
1733 newOp = ForallOp::create(rewriter, loc, newMixedLowerBounds,
1734 newMixedUpperBounds, newMixedSteps,
1735 op.getOutputs(), std::nullopt, nullptr);
1736 newOp.getBodyRegion().getBlocks().clear();
1737 newOp.setMappingAttr(op.getMappingAttr());
1738 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1739 rewriter.cloneRegionBefore(op.getRegion(), newOp.getRegion(),
1740 newOp.getRegion().begin(), mapping);
1741 rewriter.replaceOp(op, newOp.getResults());
1742 return success();
1743 }
1744};
1745
1746/// Replace all induction vars with a single trip count with their lower bound.
1747struct ForallOpReplaceConstantInductionVar : public OpRewritePattern<ForallOp> {
1748 using OpRewritePattern<ForallOp>::OpRewritePattern;
1749
1750 LogicalResult matchAndRewrite(ForallOp op,
1751 PatternRewriter &rewriter) const override {
1752 Location loc = op.getLoc();
1753 bool changed = false;
1754 for (auto [lb, ub, step, iv] :
1755 llvm::zip(op.getMixedLowerBound(), op.getMixedUpperBound(),
1756 op.getMixedStep(), op.getInductionVars())) {
1757 if (iv.hasNUses(0))
1758 continue;
1759 auto numIterations =
1760 constantTripCount(lb, ub, step, /*isSigned=*/true, computeUbMinusLb);
1761 if (!numIterations.has_value() || numIterations.value() != 1) {
1762 continue;
1763 }
1764 rewriter.replaceAllUsesWith(
1765 iv, getValueOrCreateConstantIndexOp(rewriter, loc, lb));
1766 changed = true;
1767 }
1768 return success(changed);
1769 }
1770};
1771
1772struct FoldTensorCastOfOutputIntoForallOp
1773 : public OpRewritePattern<scf::ForallOp> {
1774 using OpRewritePattern<scf::ForallOp>::OpRewritePattern;
1775
1776 struct TypeCast {
1777 Type srcType;
1778 Type dstType;
1779 };
1780
1781 LogicalResult matchAndRewrite(scf::ForallOp forallOp,
1782 PatternRewriter &rewriter) const final {
1783 llvm::SmallMapVector<unsigned, TypeCast, 2> tensorCastProducers;
1784 llvm::SmallVector<Value> newOutputTensors = forallOp.getOutputs();
1785 for (auto en : llvm::enumerate(newOutputTensors)) {
1786 auto castOp = en.value().getDefiningOp<tensor::CastOp>();
1787 if (!castOp)
1788 continue;
1789
1790 // Only casts that that preserve static information, i.e. will make the
1791 // loop result type "more" static than before, will be folded.
1792 if (!tensor::preservesStaticInformation(castOp.getDest().getType(),
1793 castOp.getSource().getType())) {
1794 continue;
1795 }
1796
1797 tensorCastProducers[en.index()] =
1798 TypeCast{castOp.getSource().getType(), castOp.getType()};
1799 newOutputTensors[en.index()] = castOp.getSource();
1800 }
1801
1802 if (tensorCastProducers.empty())
1803 return failure();
1804
1805 // Create new loop.
1806 Location loc = forallOp.getLoc();
1807 auto newForallOp = ForallOp::create(
1808 rewriter, loc, forallOp.getMixedLowerBound(),
1809 forallOp.getMixedUpperBound(), forallOp.getMixedStep(),
1810 newOutputTensors, forallOp.getMapping(),
1811 [&](OpBuilder nestedBuilder, Location nestedLoc, ValueRange bbArgs) {
1812 auto castBlockArgs =
1813 llvm::to_vector(bbArgs.take_back(forallOp->getNumResults()));
1814 for (auto [index, cast] : tensorCastProducers) {
1815 Value &oldTypeBBArg = castBlockArgs[index];
1816 oldTypeBBArg = tensor::CastOp::create(nestedBuilder, nestedLoc,
1817 cast.dstType, oldTypeBBArg);
1818 }
1819
1820 // Move old body into new parallel loop.
1821 SmallVector<Value> ivsBlockArgs =
1822 llvm::to_vector(bbArgs.take_front(forallOp.getRank()));
1823 ivsBlockArgs.append(castBlockArgs);
1824 rewriter.mergeBlocks(forallOp.getBody(),
1825 bbArgs.front().getParentBlock(), ivsBlockArgs);
1826 });
1827
1828 // After `mergeBlocks` happened, the destinations in the terminator may be
1829 // mapped to tensor.cast values wrapping the new output bbArgs (introduced
1830 // for indices in `tensorCastProducers`). Update those destinations to
1831 // point directly to the output bbArgs, bypassing the casts.
1832 //
1833 // Note: we cannot zip yieldingOps with regionIterArgs by position because
1834 // a parallel_insert_slice inside in_parallel may write to any shared
1835 // output, not necessarily the one at the same position.
1836 llvm::SmallDenseSet<Value> newIterArgSet(
1837 newForallOp.getRegionIterArgs().begin(),
1838 newForallOp.getRegionIterArgs().end());
1839 auto terminator = newForallOp.getTerminator();
1840 for (auto &yieldingOp : terminator.getYieldingOps()) {
1841 auto parallelCombiningOp =
1842 dyn_cast<ParallelCombiningOpInterface>(&yieldingOp);
1843 if (!parallelCombiningOp)
1844 continue;
1845 for (OpOperand &dest : parallelCombiningOp.getUpdatedDestinations()) {
1846 auto castOp = dest.get().getDefiningOp<tensor::CastOp>();
1847 if (castOp && newIterArgSet.contains(castOp.getSource()))
1848 dest.set(castOp.getSource());
1849 }
1850 }
1851
1852 // Cast results back to the original types.
1853 rewriter.setInsertionPointAfter(newForallOp);
1854 SmallVector<Value> castResults = newForallOp.getResults();
1855 for (auto &item : tensorCastProducers) {
1856 Value &oldTypeResult = castResults[item.first];
1857 oldTypeResult = tensor::CastOp::create(rewriter, loc, item.second.dstType,
1858 oldTypeResult);
1859 }
1860 rewriter.replaceOp(forallOp, castResults);
1861 return success();
1862 }
1863};
1864
1865} // namespace
1866
1867void ForallOp::getCanonicalizationPatterns(RewritePatternSet &results,
1868 MLIRContext *context) {
1869 results.add<DimOfForallOp, FoldTensorCastOfOutputIntoForallOp,
1870 ForallOpControlOperandsFolder, ForallOpIterArgsFolder,
1871 ForallOpSingleOrZeroIterationDimsFolder,
1872 ForallOpReplaceConstantInductionVar>(context);
1873}
1874
1875void ForallOp::getSuccessorRegions(RegionBranchPoint point,
1876 SmallVectorImpl<RegionSuccessor> &regions) {
1877 // There are two region branch points:
1878 // 1. "parent": entering the forall op for the first time.
1879 // 2. scf.in_parallel terminator
1880 if (point.isParent()) {
1881 // When first entering the forall op, the control flow typically branches
1882 // into the forall body. (In parallel for multiple threads.)
1883 regions.push_back(RegionSuccessor(&getRegion()));
1884 // However, when there are 0 threads, the control flow may branch back to
1885 // the parent immediately.
1886 regions.push_back(RegionSuccessor(getOperation()));
1887 } else {
1888 // In accordance with the semantics of forall, its body is executed in
1889 // parallel by multiple threads. We should not expect to branch back into
1890 // the forall body after the region's execution is complete.
1891 regions.push_back(RegionSuccessor(getOperation()));
1892 }
1893}
1894
1895//===----------------------------------------------------------------------===//
1896// InParallelOp
1897//===----------------------------------------------------------------------===//
1898
1899// Build a InParallelOp with mixed static and dynamic entries.
1900void InParallelOp::build(OpBuilder &b, OperationState &result) {
1901 OpBuilder::InsertionGuard g(b);
1902 Region *bodyRegion = result.addRegion();
1903 b.createBlock(bodyRegion);
1904}
1905
1906LogicalResult InParallelOp::verify() {
1907 scf::ForallOp forallOp =
1908 dyn_cast<scf::ForallOp>(getOperation()->getParentOp());
1909 if (!forallOp)
1910 return this->emitOpError("expected forall op parent");
1911
1912 for (Operation &op : getRegion().front().getOperations()) {
1913 auto parallelCombiningOp = dyn_cast<ParallelCombiningOpInterface>(&op);
1914 if (!parallelCombiningOp) {
1915 return this->emitOpError("expected only ParallelCombiningOpInterface")
1916 << " ops";
1917 }
1918
1919 // Verify that inserts are into out block arguments.
1920 MutableOperandRange dests = parallelCombiningOp.getUpdatedDestinations();
1921 ArrayRef<BlockArgument> regionOutArgs = forallOp.getRegionOutArgs();
1922 for (OpOperand &dest : dests) {
1923 if (!llvm::is_contained(regionOutArgs, dest.get()))
1924 return op.emitOpError("may only insert into an output block argument");
1925 }
1926 }
1927
1928 return success();
1929}
1930
1931void InParallelOp::print(OpAsmPrinter &p) {
1932 p << " ";
1933 p.printRegion(getRegion(),
1934 /*printEntryBlockArgs=*/false,
1935 /*printBlockTerminators=*/false);
1937 getOperation()->getDiscardableAttrDictionary().getValue());
1938}
1939
1940ParseResult InParallelOp::parse(OpAsmParser &parser, OperationState &result) {
1941 auto &builder = parser.getBuilder();
1942
1943 SmallVector<OpAsmParser::Argument, 8> regionOperands;
1944 std::unique_ptr<Region> region = std::make_unique<Region>();
1945 if (parser.parseRegion(*region, regionOperands))
1946 return failure();
1947
1948 if (region->empty())
1949 OpBuilder(builder.getContext()).createBlock(region.get());
1950 result.addRegion(std::move(region));
1951
1952 // Parse the optional attribute list.
1953 if (parser.parseOptionalAttrDict(result.attributes))
1954 return failure();
1955 return success();
1956}
1957
1958OpResult InParallelOp::getParentResult(int64_t idx) {
1959 return getOperation()->getParentOp()->getResult(idx);
1960}
1961
1962SmallVector<BlockArgument> InParallelOp::getDests() {
1963 SmallVector<BlockArgument> updatedDests;
1964 for (Operation &yieldingOp : getYieldingOps()) {
1965 auto parallelCombiningOp =
1966 dyn_cast<ParallelCombiningOpInterface>(&yieldingOp);
1967 if (!parallelCombiningOp)
1968 continue;
1969 for (OpOperand &updatedOperand :
1970 parallelCombiningOp.getUpdatedDestinations())
1971 updatedDests.push_back(cast<BlockArgument>(updatedOperand.get()));
1972 }
1973 return updatedDests;
1974}
1975
1976llvm::iterator_range<Block::iterator> InParallelOp::getYieldingOps() {
1977 return getRegion().front().getOperations();
1978}
1979
1980//===----------------------------------------------------------------------===//
1981// IfOp
1982//===----------------------------------------------------------------------===//
1983
1985 assert(a && "expected non-empty operation");
1986 assert(b && "expected non-empty operation");
1987
1988 IfOp ifOp = a->getParentOfType<IfOp>();
1989 while (ifOp) {
1990 // Check if b is inside ifOp. (We already know that a is.)
1991 if (ifOp->isProperAncestor(b))
1992 // b is contained in ifOp. a and b are in mutually exclusive branches if
1993 // they are in different blocks of ifOp.
1994 return static_cast<bool>(ifOp.thenBlock()->findAncestorOpInBlock(*a)) !=
1995 static_cast<bool>(ifOp.thenBlock()->findAncestorOpInBlock(*b));
1996 // Check next enclosing IfOp.
1997 ifOp = ifOp->getParentOfType<IfOp>();
1998 }
1999
2000 // Could not find a common IfOp among a's and b's ancestors.
2001 return false;
2002}
2003
2004LogicalResult
2005IfOp::inferReturnTypes(MLIRContext *ctx, std::optional<Location> loc,
2006 IfOp::Adaptor adaptor,
2007 SmallVectorImpl<Type> &inferredReturnTypes) {
2008 if (adaptor.getRegions().empty())
2009 return failure();
2010 Region *r = &adaptor.getThenRegion();
2011 if (r->empty())
2012 return failure();
2013 Block &b = r->front();
2014 if (b.empty())
2015 return failure();
2016 auto yieldOp = llvm::dyn_cast<YieldOp>(b.back());
2017 if (!yieldOp)
2018 return failure();
2019 TypeRange types = yieldOp.getOperandTypes();
2020 llvm::append_range(inferredReturnTypes, types);
2021 return success();
2022}
2023
2024void IfOp::build(OpBuilder &builder, OperationState &result,
2025 TypeRange resultTypes, Value cond) {
2026 return build(builder, result, resultTypes, cond, /*addThenBlock=*/false,
2027 /*addElseBlock=*/false);
2028}
2029
2030void IfOp::build(OpBuilder &builder, OperationState &result,
2031 TypeRange resultTypes, Value cond, bool addThenBlock,
2032 bool addElseBlock) {
2033 assert((!addElseBlock || addThenBlock) &&
2034 "must not create else block w/o then block");
2035 result.addTypes(resultTypes);
2036 result.addOperands(cond);
2037
2038 // Add regions and blocks.
2039 OpBuilder::InsertionGuard guard(builder);
2040 Region *thenRegion = result.addRegion();
2041 if (addThenBlock)
2042 builder.createBlock(thenRegion);
2043 Region *elseRegion = result.addRegion();
2044 if (addElseBlock)
2045 builder.createBlock(elseRegion);
2046}
2047
2048void IfOp::build(OpBuilder &builder, OperationState &result, Value cond,
2049 bool withElseRegion) {
2050 build(builder, result, TypeRange{}, cond, withElseRegion);
2051}
2052
2053void IfOp::build(OpBuilder &builder, OperationState &result,
2054 TypeRange resultTypes, Value cond, bool withElseRegion) {
2055 result.addTypes(resultTypes);
2056 result.addOperands(cond);
2057
2058 // Build then region.
2059 OpBuilder::InsertionGuard guard(builder);
2060 Region *thenRegion = result.addRegion();
2061 builder.createBlock(thenRegion);
2062 if (resultTypes.empty())
2063 IfOp::ensureTerminator(*thenRegion, builder, result.location);
2064
2065 // Build else region.
2066 Region *elseRegion = result.addRegion();
2067 if (withElseRegion) {
2068 builder.createBlock(elseRegion);
2069 if (resultTypes.empty())
2070 IfOp::ensureTerminator(*elseRegion, builder, result.location);
2071 }
2072}
2073
2074void IfOp::build(OpBuilder &builder, OperationState &result, Value cond,
2075 function_ref<void(OpBuilder &, Location)> thenBuilder,
2076 function_ref<void(OpBuilder &, Location)> elseBuilder) {
2077 assert(thenBuilder && "the builder callback for 'then' must be present");
2078 result.addOperands(cond);
2079
2080 // Build then region.
2081 OpBuilder::InsertionGuard guard(builder);
2082 Region *thenRegion = result.addRegion();
2083 builder.createBlock(thenRegion);
2084 thenBuilder(builder, result.location);
2085
2086 // Build else region.
2087 Region *elseRegion = result.addRegion();
2088 if (elseBuilder) {
2089 builder.createBlock(elseRegion);
2090 elseBuilder(builder, result.location);
2091 }
2092
2093 // Infer result types.
2094 SmallVector<Type> inferredReturnTypes;
2095 MLIRContext *ctx = builder.getContext();
2096 auto attrDict = DictionaryAttr::get(ctx, result.attributes);
2097 if (succeeded(inferReturnTypes(ctx, std::nullopt, result.operands, attrDict,
2098 /*properties=*/PropertyRef{}, result.regions,
2099 inferredReturnTypes))) {
2100 result.addTypes(inferredReturnTypes);
2101 }
2102}
2103
2104LogicalResult IfOp::verify() {
2105 if (getNumResults() != 0 && getElseRegion().empty())
2106 return emitOpError("must have an else block if defining values");
2107 return success();
2108}
2109
2110ParseResult IfOp::parse(OpAsmParser &parser, OperationState &result) {
2111 // Create the regions for 'then'.
2112 result.regions.reserve(2);
2113 Region *thenRegion = result.addRegion();
2114 Region *elseRegion = result.addRegion();
2115
2116 auto &builder = parser.getBuilder();
2117 OpAsmParser::UnresolvedOperand cond;
2118 Type i1Type = builder.getIntegerType(1);
2119 if (parser.parseOperand(cond) ||
2120 parser.resolveOperand(cond, i1Type, result.operands))
2121 return failure();
2122 // Parse optional results type list.
2123 if (parser.parseOptionalArrowTypeList(result.types))
2124 return failure();
2125 // Parse the 'then' region.
2126 if (parser.parseRegion(*thenRegion, /*arguments=*/{}, /*argTypes=*/{}))
2127 return failure();
2128 IfOp::ensureTerminator(*thenRegion, parser.getBuilder(), result.location);
2129
2130 // If we find an 'else' keyword then parse the 'else' region.
2131 if (!parser.parseOptionalKeyword("else")) {
2132 if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{}))
2133 return failure();
2134 IfOp::ensureTerminator(*elseRegion, parser.getBuilder(), result.location);
2135 }
2136
2137 // Parse the optional attribute list.
2138 if (parser.parseOptionalAttrDict(result.attributes))
2139 return failure();
2140 return success();
2141}
2142
2143void IfOp::print(OpAsmPrinter &p) {
2144 bool printBlockTerminators = false;
2145
2146 p << " " << getCondition();
2147 if (!getResults().empty()) {
2148 p << " -> (" << getResultTypes() << ")";
2149 // Print yield explicitly if the op defines values.
2150 printBlockTerminators = true;
2151 }
2152 p << ' ';
2153 p.printRegion(getThenRegion(),
2154 /*printEntryBlockArgs=*/false,
2155 /*printBlockTerminators=*/printBlockTerminators);
2156
2157 // Print the 'else' regions if it exists and has a block.
2158 auto &elseRegion = getElseRegion();
2159 if (!elseRegion.empty()) {
2160 p << " else ";
2161 p.printRegion(elseRegion,
2162 /*printEntryBlockArgs=*/false,
2163 /*printBlockTerminators=*/printBlockTerminators);
2164 }
2165
2166 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
2167}
2168
2169void IfOp::getSuccessorRegions(RegionBranchPoint point,
2170 SmallVectorImpl<RegionSuccessor> &regions) {
2171 // The `then` and the `else` region branch back to the parent operation or one
2172 // of the recursive parent operations (early exit case).
2173 if (!point.isParent()) {
2174 regions.push_back(RegionSuccessor(getOperation()));
2175 return;
2176 }
2177
2178 regions.push_back(RegionSuccessor(&getThenRegion()));
2179
2180 // Don't consider the else region if it is empty.
2181 Region *elseRegion = &this->getElseRegion();
2182 if (elseRegion->empty())
2183 regions.push_back(RegionSuccessor(getOperation()));
2184 else
2185 regions.push_back(RegionSuccessor(elseRegion));
2186}
2187
2188ValueRange IfOp::getSuccessorInputs(RegionSuccessor successor) {
2189 return successor.isOperation() ? ValueRange(getOperation()->getResults())
2190 : ValueRange();
2191}
2192
2193void IfOp::getEntrySuccessorRegions(ArrayRef<Attribute> operands,
2194 SmallVectorImpl<RegionSuccessor> &regions) {
2195 FoldAdaptor adaptor(operands, *this);
2196 auto boolAttr = dyn_cast_or_null<BoolAttr>(adaptor.getCondition());
2197 if (!boolAttr || boolAttr.getValue())
2198 regions.emplace_back(&getThenRegion());
2199
2200 // If the else region is empty, execution continues after the parent op.
2201 if (!boolAttr || !boolAttr.getValue()) {
2202 if (!getElseRegion().empty())
2203 regions.emplace_back(&getElseRegion());
2204 else
2205 regions.emplace_back(RegionSuccessor(getOperation()));
2206 }
2207}
2208
2209LogicalResult IfOp::fold(FoldAdaptor adaptor,
2210 SmallVectorImpl<OpFoldResult> &results) {
2211 // if (!c) then A() else B() -> if c then B() else A()
2212 if (getElseRegion().empty())
2213 return failure();
2214
2215 arith::XOrIOp xorStmt = getCondition().getDefiningOp<arith::XOrIOp>();
2216 if (!xorStmt)
2217 return failure();
2218
2219 if (!matchPattern(xorStmt.getRhs(), m_One()))
2220 return failure();
2221
2222 getConditionMutable().assign(xorStmt.getLhs());
2223 Block *thenBlock = &getThenRegion().front();
2224 // It would be nicer to use iplist::swap, but that has no implemented
2225 // callbacks See: https://llvm.org/doxygen/ilist_8h_source.html#l00224
2226 getThenRegion().getBlocks().splice(getThenRegion().getBlocks().begin(),
2227 getElseRegion().getBlocks());
2228 getElseRegion().getBlocks().splice(getElseRegion().getBlocks().begin(),
2229 getThenRegion().getBlocks(), thenBlock);
2230 return success();
2231}
2232
2233void IfOp::getRegionInvocationBounds(
2234 ArrayRef<Attribute> operands,
2235 SmallVectorImpl<InvocationBounds> &invocationBounds) {
2236 if (auto cond = llvm::dyn_cast_or_null<BoolAttr>(operands[0])) {
2237 // If the condition is known, then one region is known to be executed once
2238 // and the other zero times.
2239 invocationBounds.emplace_back(0, cond.getValue() ? 1 : 0);
2240 invocationBounds.emplace_back(0, cond.getValue() ? 0 : 1);
2241 } else {
2242 // Non-constant condition. Each region may be executed 0 or 1 times.
2243 invocationBounds.assign(2, {0, 1});
2244 }
2245}
2246
2247namespace {
2248/// Hoist any yielded results whose operands are defined outside
2249/// the if, to a select instruction.
2250struct ConvertTrivialIfToSelect : public OpRewritePattern<IfOp> {
2251 using OpRewritePattern<IfOp>::OpRewritePattern;
2252
2253 LogicalResult matchAndRewrite(IfOp op,
2254 PatternRewriter &rewriter) const override {
2255 if (op->getNumResults() == 0)
2256 return failure();
2257
2258 auto cond = op.getCondition();
2259 auto thenYieldArgs = op.thenYield().getOperands();
2260 auto elseYieldArgs = op.elseYield().getOperands();
2261
2262 SmallVector<Type> nonHoistable;
2263 for (auto [trueVal, falseVal] : llvm::zip(thenYieldArgs, elseYieldArgs)) {
2264 if (&op.getThenRegion() == trueVal.getParentRegion() ||
2265 &op.getElseRegion() == falseVal.getParentRegion())
2266 nonHoistable.push_back(trueVal.getType());
2267 }
2268 // Early exit if there aren't any yielded values we can
2269 // hoist outside the if.
2270 if (nonHoistable.size() == op->getNumResults())
2271 return failure();
2272
2273 IfOp replacement = IfOp::create(rewriter, op.getLoc(), nonHoistable, cond,
2274 /*withElseRegion=*/false);
2275 if (replacement.thenBlock())
2276 rewriter.eraseBlock(replacement.thenBlock());
2277 replacement.getThenRegion().takeBody(op.getThenRegion());
2278 replacement.getElseRegion().takeBody(op.getElseRegion());
2279
2280 SmallVector<Value> results(op->getNumResults());
2281 assert(thenYieldArgs.size() == results.size());
2282 assert(elseYieldArgs.size() == results.size());
2283
2284 SmallVector<Value> trueYields;
2285 SmallVector<Value> falseYields;
2287 for (const auto &it :
2288 llvm::enumerate(llvm::zip(thenYieldArgs, elseYieldArgs))) {
2289 Value trueVal = std::get<0>(it.value());
2290 Value falseVal = std::get<1>(it.value());
2291 if (&replacement.getThenRegion() == trueVal.getParentRegion() ||
2292 &replacement.getElseRegion() == falseVal.getParentRegion()) {
2293 results[it.index()] = replacement.getResult(trueYields.size());
2294 trueYields.push_back(trueVal);
2295 falseYields.push_back(falseVal);
2296 } else if (trueVal == falseVal)
2297 results[it.index()] = trueVal;
2298 else
2299 results[it.index()] = arith::SelectOp::create(rewriter, op.getLoc(),
2300 cond, trueVal, falseVal);
2301 }
2302
2303 rewriter.setInsertionPointToEnd(replacement.thenBlock());
2304 rewriter.replaceOpWithNewOp<YieldOp>(replacement.thenYield(), trueYields);
2305
2306 rewriter.setInsertionPointToEnd(replacement.elseBlock());
2307 rewriter.replaceOpWithNewOp<YieldOp>(replacement.elseYield(), falseYields);
2308
2309 rewriter.replaceOp(op, results);
2310 return success();
2311 }
2312};
2313
2314/// Allow the true region of an if to assume the condition is true
2315/// and vice versa. For example:
2316///
2317/// scf.if %cmp {
2318/// print(%cmp)
2319/// }
2320///
2321/// becomes
2322///
2323/// scf.if %cmp {
2324/// print(true)
2325/// }
2326///
2327struct ConditionPropagation : public OpRewritePattern<IfOp> {
2328 using OpRewritePattern<IfOp>::OpRewritePattern;
2329
2330 /// Kind of parent region in the ancestor cache.
2331 enum class Parent { Then, Else, None };
2332
2333 /// Returns the kind of region ("then", "else", or "none") of the
2334 /// IfOp that the given region is transitively nested in. Updates
2335 /// the cache accordingly.
2336 static Parent getParentType(Region *toCheck, IfOp op,
2338 Region *endRegion) {
2339 SmallVector<Region *> seen;
2340 while (toCheck != endRegion) {
2341 auto found = cache.find(toCheck);
2342 if (found != cache.end())
2343 return found->second;
2344 seen.push_back(toCheck);
2345 if (&op.getThenRegion() == toCheck) {
2346 for (Region *region : seen)
2347 cache[region] = Parent::Then;
2348 return Parent::Then;
2349 }
2350 if (&op.getElseRegion() == toCheck) {
2351 for (Region *region : seen)
2352 cache[region] = Parent::Else;
2353 return Parent::Else;
2354 }
2355 toCheck = toCheck->getParentRegion();
2356 }
2357
2358 for (Region *region : seen)
2359 cache[region] = Parent::None;
2360 return Parent::None;
2361 }
2362
2363 LogicalResult matchAndRewrite(IfOp op,
2364 PatternRewriter &rewriter) const override {
2365 // Early exit if the condition is constant since replacing a constant
2366 // in the body with another constant isn't a simplification.
2367 if (matchPattern(op.getCondition(), m_Constant()))
2368 return failure();
2369
2370 bool changed = false;
2371 mlir::Type i1Ty = rewriter.getI1Type();
2372
2373 // These variables serve to prevent creating duplicate constants
2374 // and hold constant true or false values.
2375 Value constantTrue = nullptr;
2376 Value constantFalse = nullptr;
2377
2379 for (OpOperand &use :
2380 llvm::make_early_inc_range(op.getCondition().getUses())) {
2381 switch (getParentType(use.getOwner()->getParentRegion(), op, cache,
2382 op.getCondition().getParentRegion())) {
2383 case Parent::Then: {
2384 changed = true;
2385
2386 if (!constantTrue)
2387 constantTrue = arith::ConstantOp::create(
2388 rewriter, op.getLoc(), i1Ty, rewriter.getIntegerAttr(i1Ty, 1));
2389
2390 rewriter.modifyOpInPlace(use.getOwner(),
2391 [&]() { use.set(constantTrue); });
2392 break;
2393 }
2394 case Parent::Else: {
2395 changed = true;
2396
2397 if (!constantFalse)
2398 constantFalse = arith::ConstantOp::create(
2399 rewriter, op.getLoc(), i1Ty, rewriter.getIntegerAttr(i1Ty, 0));
2400
2401 rewriter.modifyOpInPlace(use.getOwner(),
2402 [&]() { use.set(constantFalse); });
2403 break;
2404 }
2405 case Parent::None: {
2406 auto selectOp = dyn_cast<arith::SelectOp>(use.getOwner());
2407 if (!selectOp || use.getOperandNumber() != 0)
2408 break;
2409
2410 // Refine captured select results only at uses in the matching branch.
2411 // Uses outside the if must retain the select result.
2412 changed |= rewriter.replaceUsesWithIf(
2413 selectOp.getResult(), selectOp.getTrueValue(),
2414 [&](OpOperand &selectUse) {
2415 return getParentType(
2416 selectUse.getOwner()->getParentRegion(), op, cache,
2417 op.getCondition().getParentRegion()) == Parent::Then;
2418 });
2419 changed |= rewriter.replaceUsesWithIf(
2420 selectOp.getResult(), selectOp.getFalseValue(),
2421 [&](OpOperand &selectUse) {
2422 return getParentType(
2423 selectUse.getOwner()->getParentRegion(), op, cache,
2424 op.getCondition().getParentRegion()) == Parent::Else;
2425 });
2426 break;
2427 }
2428 }
2429 }
2430
2431 return success(changed);
2432 }
2433};
2434
2435/// Remove any statements from an if that are equivalent to the condition
2436/// or its negation. For example:
2437///
2438/// %res:2 = scf.if %cmp {
2439/// yield something(), true
2440/// } else {
2441/// yield something2(), false
2442/// }
2443/// print(%res#1)
2444///
2445/// becomes
2446/// %res = scf.if %cmp {
2447/// yield something()
2448/// } else {
2449/// yield something2()
2450/// }
2451/// print(%cmp)
2452///
2453/// Additionally if both branches yield the same value, replace all uses
2454/// of the result with the yielded value.
2455///
2456/// %res:2 = scf.if %cmp {
2457/// yield something(), %arg1
2458/// } else {
2459/// yield something2(), %arg1
2460/// }
2461/// print(%res#1)
2462///
2463/// becomes
2464/// %res = scf.if %cmp {
2465/// yield something()
2466/// } else {
2467/// yield something2()
2468/// }
2469/// print(%arg1)
2470///
2471struct ReplaceIfYieldWithConditionOrValue : public OpRewritePattern<IfOp> {
2472 using OpRewritePattern<IfOp>::OpRewritePattern;
2473
2474 LogicalResult matchAndRewrite(IfOp op,
2475 PatternRewriter &rewriter) const override {
2476 // Early exit if there are no results that could be replaced.
2477 if (op.getNumResults() == 0)
2478 return failure();
2479
2480 auto trueYield =
2481 cast<scf::YieldOp>(op.getThenRegion().back().getTerminator());
2482 auto falseYield =
2483 cast<scf::YieldOp>(op.getElseRegion().back().getTerminator());
2484
2485 rewriter.setInsertionPoint(op->getBlock(),
2486 op.getOperation()->getIterator());
2487 bool changed = false;
2488 Type i1Ty = rewriter.getI1Type();
2489 for (auto [trueResult, falseResult, opResult] :
2490 llvm::zip(trueYield.getResults(), falseYield.getResults(),
2491 op.getResults())) {
2492 if (trueResult == falseResult) {
2493 if (!opResult.use_empty()) {
2494 opResult.replaceAllUsesWith(trueResult);
2495 changed = true;
2496 }
2497 continue;
2498 }
2499
2500 BoolAttr trueYield, falseYield;
2501 if (!matchPattern(trueResult, m_Constant(&trueYield)) ||
2502 !matchPattern(falseResult, m_Constant(&falseYield)))
2503 continue;
2504
2505 bool trueVal = trueYield.getValue();
2506 bool falseVal = falseYield.getValue();
2507 if (!trueVal && falseVal) {
2508 if (!opResult.use_empty()) {
2509 Dialect *constDialect = trueResult.getDefiningOp()->getDialect();
2510 Value notCond = arith::XOrIOp::create(
2511 rewriter, op.getLoc(), op.getCondition(),
2512 constDialect
2513 ->materializeConstant(rewriter,
2514 rewriter.getIntegerAttr(i1Ty, 1), i1Ty,
2515 op.getLoc())
2516 ->getResult(0));
2517 opResult.replaceAllUsesWith(notCond);
2518 changed = true;
2519 }
2520 }
2521 if (trueVal && !falseVal) {
2522 if (!opResult.use_empty()) {
2523 opResult.replaceAllUsesWith(op.getCondition());
2524 changed = true;
2525 }
2526 }
2527 }
2528 return success(changed);
2529 }
2530};
2531
2532/// Merge any consecutive scf.if's with the same condition.
2533///
2534/// scf.if %cond {
2535/// firstCodeTrue();...
2536/// } else {
2537/// firstCodeFalse();...
2538/// }
2539/// %res = scf.if %cond {
2540/// secondCodeTrue();...
2541/// } else {
2542/// secondCodeFalse();...
2543/// }
2544///
2545/// becomes
2546/// %res = scf.if %cmp {
2547/// firstCodeTrue();...
2548/// secondCodeTrue();...
2549/// } else {
2550/// firstCodeFalse();...
2551/// secondCodeFalse();...
2552/// }
2553struct CombineIfs : public OpRewritePattern<IfOp> {
2554 using OpRewritePattern<IfOp>::OpRewritePattern;
2555
2556 LogicalResult matchAndRewrite(IfOp nextIf,
2557 PatternRewriter &rewriter) const override {
2558 Block *parent = nextIf->getBlock();
2559 if (nextIf == &parent->front())
2560 return failure();
2561
2562 auto prevIf = dyn_cast<IfOp>(nextIf->getPrevNode());
2563 if (!prevIf)
2564 return failure();
2565
2566 // Determine the logical then/else blocks when prevIf's
2567 // condition is used. Null means the block does not exist
2568 // in that case (e.g. empty else). If neither of these
2569 // are set, the two conditions cannot be compared.
2570 Block *nextThen = nullptr;
2571 Block *nextElse = nullptr;
2572 if (nextIf.getCondition() == prevIf.getCondition()) {
2573 nextThen = nextIf.thenBlock();
2574 if (!nextIf.getElseRegion().empty())
2575 nextElse = nextIf.elseBlock();
2576 }
2577 if (arith::XOrIOp notv =
2578 nextIf.getCondition().getDefiningOp<arith::XOrIOp>()) {
2579 if (notv.getLhs() == prevIf.getCondition() &&
2580 matchPattern(notv.getRhs(), m_One())) {
2581 nextElse = nextIf.thenBlock();
2582 if (!nextIf.getElseRegion().empty())
2583 nextThen = nextIf.elseBlock();
2584 }
2585 }
2586 if (arith::XOrIOp notv =
2587 prevIf.getCondition().getDefiningOp<arith::XOrIOp>()) {
2588 if (notv.getLhs() == nextIf.getCondition() &&
2589 matchPattern(notv.getRhs(), m_One())) {
2590 nextElse = nextIf.thenBlock();
2591 if (!nextIf.getElseRegion().empty())
2592 nextThen = nextIf.elseBlock();
2593 }
2594 }
2595
2596 if (!nextThen && !nextElse)
2597 return failure();
2598
2599 SmallVector<Value> prevElseYielded;
2600 if (!prevIf.getElseRegion().empty())
2601 prevElseYielded = prevIf.elseYield().getOperands();
2602 // Replace all uses of return values of op within nextIf with the
2603 // corresponding yields
2604 for (auto it : llvm::zip(prevIf.getResults(),
2605 prevIf.thenYield().getOperands(), prevElseYielded))
2606 for (OpOperand &use :
2607 llvm::make_early_inc_range(std::get<0>(it).getUses())) {
2608 if (nextThen && nextThen->getParent()->isAncestor(
2609 use.getOwner()->getParentRegion())) {
2610 rewriter.startOpModification(use.getOwner());
2611 use.set(std::get<1>(it));
2612 rewriter.finalizeOpModification(use.getOwner());
2613 } else if (nextElse && nextElse->getParent()->isAncestor(
2614 use.getOwner()->getParentRegion())) {
2615 rewriter.startOpModification(use.getOwner());
2616 use.set(std::get<2>(it));
2617 rewriter.finalizeOpModification(use.getOwner());
2618 }
2619 }
2620
2621 SmallVector<Type> mergedTypes(prevIf.getResultTypes());
2622 llvm::append_range(mergedTypes, nextIf.getResultTypes());
2623
2624 IfOp combinedIf = IfOp::create(rewriter, nextIf.getLoc(), mergedTypes,
2625 prevIf.getCondition(), /*hasElse=*/false);
2626 rewriter.eraseBlock(&combinedIf.getThenRegion().back());
2627
2628 rewriter.inlineRegionBefore(prevIf.getThenRegion(),
2629 combinedIf.getThenRegion(),
2630 combinedIf.getThenRegion().begin());
2631
2632 if (nextThen) {
2633 YieldOp thenYield = combinedIf.thenYield();
2634 YieldOp thenYield2 = cast<YieldOp>(nextThen->getTerminator());
2635 rewriter.mergeBlocks(nextThen, combinedIf.thenBlock());
2636 rewriter.setInsertionPointToEnd(combinedIf.thenBlock());
2637
2638 SmallVector<Value> mergedYields(thenYield.getOperands());
2639 llvm::append_range(mergedYields, thenYield2.getOperands());
2640 YieldOp::create(rewriter, thenYield2.getLoc(), mergedYields);
2641 rewriter.eraseOp(thenYield);
2642 rewriter.eraseOp(thenYield2);
2643 }
2644
2645 rewriter.inlineRegionBefore(prevIf.getElseRegion(),
2646 combinedIf.getElseRegion(),
2647 combinedIf.getElseRegion().begin());
2648
2649 if (nextElse) {
2650 if (combinedIf.getElseRegion().empty()) {
2651 rewriter.inlineRegionBefore(*nextElse->getParent(),
2652 combinedIf.getElseRegion(),
2653 combinedIf.getElseRegion().begin());
2654 } else {
2655 YieldOp elseYield = combinedIf.elseYield();
2656 YieldOp elseYield2 = cast<YieldOp>(nextElse->getTerminator());
2657 rewriter.mergeBlocks(nextElse, combinedIf.elseBlock());
2658
2659 rewriter.setInsertionPointToEnd(combinedIf.elseBlock());
2660
2661 SmallVector<Value> mergedElseYields(elseYield.getOperands());
2662 llvm::append_range(mergedElseYields, elseYield2.getOperands());
2663
2664 YieldOp::create(rewriter, elseYield2.getLoc(), mergedElseYields);
2665 rewriter.eraseOp(elseYield);
2666 rewriter.eraseOp(elseYield2);
2667 }
2668 }
2669
2670 SmallVector<Value> prevValues;
2671 SmallVector<Value> nextValues;
2672 for (const auto &pair : llvm::enumerate(combinedIf.getResults())) {
2673 if (pair.index() < prevIf.getNumResults())
2674 prevValues.push_back(pair.value());
2675 else
2676 nextValues.push_back(pair.value());
2677 }
2678 rewriter.replaceOp(prevIf, prevValues);
2679 rewriter.replaceOp(nextIf, nextValues);
2680 return success();
2681 }
2682};
2683
2684/// Pattern to remove an empty else branch.
2685struct RemoveEmptyElseBranch : public OpRewritePattern<IfOp> {
2686 using OpRewritePattern<IfOp>::OpRewritePattern;
2687
2688 LogicalResult matchAndRewrite(IfOp ifOp,
2689 PatternRewriter &rewriter) const override {
2690 // Cannot remove else region when there are operation results.
2691 if (ifOp.getNumResults())
2692 return failure();
2693 Block *elseBlock = ifOp.elseBlock();
2694 if (!elseBlock || !llvm::hasSingleElement(*elseBlock))
2695 return failure();
2696 auto newIfOp = rewriter.cloneWithoutRegions(ifOp);
2697 rewriter.inlineRegionBefore(ifOp.getThenRegion(), newIfOp.getThenRegion(),
2698 newIfOp.getThenRegion().begin());
2699 rewriter.eraseOp(ifOp);
2700 return success();
2701 }
2702};
2703
2704/// Convert nested `if`s into `arith.andi` + single `if`.
2705///
2706/// scf.if %arg0 {
2707/// scf.if %arg1 {
2708/// ...
2709/// scf.yield
2710/// }
2711/// scf.yield
2712/// }
2713/// becomes
2714///
2715/// %0 = arith.andi %arg0, %arg1
2716/// scf.if %0 {
2717/// ...
2718/// scf.yield
2719/// }
2720struct CombineNestedIfs : public OpRewritePattern<IfOp> {
2721 using OpRewritePattern<IfOp>::OpRewritePattern;
2722
2723 LogicalResult matchAndRewrite(IfOp op,
2724 PatternRewriter &rewriter) const override {
2725 auto nestedOps = op.thenBlock()->without_terminator();
2726 // Nested `if` must be the only op in block.
2727 if (!llvm::hasSingleElement(nestedOps))
2728 return failure();
2729
2730 // If there is an else block, it can only yield
2731 if (op.elseBlock() && !llvm::hasSingleElement(*op.elseBlock()))
2732 return failure();
2733
2734 auto nestedIf = dyn_cast<IfOp>(*nestedOps.begin());
2735 if (!nestedIf)
2736 return failure();
2737
2738 if (nestedIf.elseBlock() && !llvm::hasSingleElement(*nestedIf.elseBlock()))
2739 return failure();
2740
2741 SmallVector<Value> thenYield(op.thenYield().getOperands());
2742 SmallVector<Value> elseYield;
2743 if (op.elseBlock())
2744 llvm::append_range(elseYield, op.elseYield().getOperands());
2745
2746 // A list of indices for which we should upgrade the value yielded
2747 // in the else to a select.
2748 SmallVector<unsigned> elseYieldsToUpgradeToSelect;
2749
2750 // If the outer scf.if yields a value produced by the inner scf.if,
2751 // only permit combining if the value yielded when the condition
2752 // is false in the outer scf.if is the same value yielded when the
2753 // inner scf.if condition is false.
2754 // Note that the array access to elseYield will not go out of bounds
2755 // since it must have the same length as thenYield, since they both
2756 // come from the same scf.if.
2757 for (const auto &tup : llvm::enumerate(thenYield)) {
2758 if (tup.value().getDefiningOp() == nestedIf) {
2759 auto nestedIdx = llvm::cast<OpResult>(tup.value()).getResultNumber();
2760 if (nestedIf.elseYield().getOperand(nestedIdx) !=
2761 elseYield[tup.index()]) {
2762 return failure();
2763 }
2764 // If the correctness test passes, we will yield
2765 // corresponding value from the inner scf.if
2766 thenYield[tup.index()] = nestedIf.thenYield().getOperand(nestedIdx);
2767 continue;
2768 }
2769
2770 // Otherwise, we need to ensure the else block of the combined
2771 // condition still returns the same value when the outer condition is
2772 // true and the inner condition is false. This can be accomplished if
2773 // the then value is defined outside the outer scf.if and we replace the
2774 // value with a select that considers just the outer condition. Since
2775 // the else region contains just the yield, its yielded value is
2776 // defined outside the scf.if, by definition.
2777
2778 // If the then value is defined within the scf.if, bail.
2779 if (tup.value().getParentRegion() == &op.getThenRegion()) {
2780 return failure();
2781 }
2782 elseYieldsToUpgradeToSelect.push_back(tup.index());
2783 }
2784
2785 Location loc = op.getLoc();
2786 Value newCondition = arith::AndIOp::create(rewriter, loc, op.getCondition(),
2787 nestedIf.getCondition());
2788 auto newIf = IfOp::create(rewriter, loc, op.getResultTypes(), newCondition);
2789 Block *newIfBlock = rewriter.createBlock(&newIf.getThenRegion());
2790
2791 SmallVector<Value> results;
2792 llvm::append_range(results, newIf.getResults());
2793 rewriter.setInsertionPoint(newIf);
2794
2795 for (auto idx : elseYieldsToUpgradeToSelect)
2796 results[idx] =
2797 arith::SelectOp::create(rewriter, op.getLoc(), op.getCondition(),
2798 thenYield[idx], elseYield[idx]);
2799
2800 rewriter.mergeBlocks(nestedIf.thenBlock(), newIfBlock);
2801 rewriter.setInsertionPointToEnd(newIf.thenBlock());
2802 rewriter.replaceOpWithNewOp<YieldOp>(newIf.thenYield(), thenYield);
2803 if (!elseYield.empty()) {
2804 rewriter.createBlock(&newIf.getElseRegion());
2805 rewriter.setInsertionPointToEnd(newIf.elseBlock());
2806 YieldOp::create(rewriter, loc, elseYield);
2807 }
2808 rewriter.replaceOp(op, results);
2809 return success();
2810 }
2811};
2812
2813} // namespace
2814
2815void IfOp::getCanonicalizationPatterns(RewritePatternSet &results,
2816 MLIRContext *context) {
2817 results.add<CombineIfs, CombineNestedIfs, ConditionPropagation,
2818 ConvertTrivialIfToSelect, RemoveEmptyElseBranch,
2819 ReplaceIfYieldWithConditionOrValue>(context);
2821 results, IfOp::getOperationName());
2823 IfOp::getOperationName());
2824}
2825
2826Block *IfOp::thenBlock() { return &getThenRegion().back(); }
2827YieldOp IfOp::thenYield() { return cast<YieldOp>(&thenBlock()->back()); }
2828Block *IfOp::elseBlock() {
2829 Region &r = getElseRegion();
2830 if (r.empty())
2831 return nullptr;
2832 return &r.back();
2833}
2834YieldOp IfOp::elseYield() { return cast<YieldOp>(&elseBlock()->back()); }
2835
2836//===----------------------------------------------------------------------===//
2837// ParallelOp
2838//===----------------------------------------------------------------------===//
2839
2840void ParallelOp::build(
2841 OpBuilder &builder, OperationState &result, ValueRange lowerBounds,
2842 ValueRange upperBounds, ValueRange steps, ValueRange initVals,
2843 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
2844 bodyBuilderFn,
2845 bool unsignedCmp) {
2846 result.addOperands(lowerBounds);
2847 result.addOperands(upperBounds);
2848 result.addOperands(steps);
2849 result.addOperands(initVals);
2850 if (unsignedCmp)
2851 result.addAttribute(getUnsignedCmpAttrName(result.name),
2852 builder.getUnitAttr());
2853 result.addAttribute(
2854 ParallelOp::getOperandSegmentSizeAttr(),
2855 builder.getDenseI32ArrayAttr({static_cast<int32_t>(lowerBounds.size()),
2856 static_cast<int32_t>(upperBounds.size()),
2857 static_cast<int32_t>(steps.size()),
2858 static_cast<int32_t>(initVals.size())}));
2859 result.addTypes(initVals.getTypes());
2860
2861 OpBuilder::InsertionGuard guard(builder);
2862 unsigned numIVs = steps.size();
2863 SmallVector<Type, 8> argTypes(numIVs, builder.getIndexType());
2864 SmallVector<Location, 8> argLocs(numIVs, result.location);
2865 Region *bodyRegion = result.addRegion();
2866 Block *bodyBlock = builder.createBlock(bodyRegion, {}, argTypes, argLocs);
2867
2868 if (bodyBuilderFn) {
2869 builder.setInsertionPointToStart(bodyBlock);
2870 bodyBuilderFn(builder, result.location,
2871 bodyBlock->getArguments().take_front(numIVs),
2872 bodyBlock->getArguments().drop_front(numIVs));
2873 }
2874 // Add terminator only if there are no reductions.
2875 if (initVals.empty())
2876 ParallelOp::ensureTerminator(*bodyRegion, builder, result.location);
2877}
2878
2879void ParallelOp::build(
2880 OpBuilder &builder, OperationState &result, ValueRange lowerBounds,
2881 ValueRange upperBounds, ValueRange steps,
2882 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn,
2883 bool unsignedCmp) {
2884 // Only pass a non-null wrapper if bodyBuilderFn is non-null itself. Make sure
2885 // we don't capture a reference to a temporary by constructing the lambda at
2886 // function level.
2887 auto wrappedBuilderFn = [&bodyBuilderFn](OpBuilder &nestedBuilder,
2888 Location nestedLoc, ValueRange ivs,
2889 ValueRange) {
2890 bodyBuilderFn(nestedBuilder, nestedLoc, ivs);
2891 };
2892 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> wrapper;
2893 if (bodyBuilderFn)
2894 wrapper = wrappedBuilderFn;
2895
2896 build(builder, result, lowerBounds, upperBounds, steps, ValueRange(), wrapper,
2897 unsignedCmp);
2898}
2899
2900LogicalResult ParallelOp::verify() {
2901 // Check that there is at least one value in lowerBound, upperBound and step.
2902 // It is sufficient to test only step, because it is ensured already that the
2903 // number of elements in lowerBound, upperBound and step are the same.
2904 Operation::operand_range stepValues = getStep();
2905 if (stepValues.empty())
2906 return emitOpError(
2907 "needs at least one tuple element for lowerBound, upperBound and step");
2908
2909 // Check whether all constant step values are positive.
2910 for (Value stepValue : stepValues)
2911 if (auto cst = getConstantIntValue(stepValue))
2912 if (*cst <= 0)
2913 return emitOpError("constant step operand must be positive");
2914
2915 // Check that the body defines the same number of block arguments as the
2916 // number of tuple elements in step.
2917 Block *body = getBody();
2918 if (body->getNumArguments() != stepValues.size())
2919 return emitOpError() << "expects the same number of induction variables: "
2920 << body->getNumArguments()
2921 << " as bound and step values: " << stepValues.size();
2922 for (auto arg : body->getArguments())
2923 if (!arg.getType().isIndex())
2924 return emitOpError(
2925 "expects arguments for the induction variable to be of index type");
2926
2927 // Check that the terminator is an scf.reduce op.
2929 *this, getRegion(), "expects body to terminate with 'scf.reduce'");
2930 if (!reduceOp)
2931 return failure();
2932
2933 // Check that the number of results is the same as the number of reductions.
2934 auto resultsSize = getResults().size();
2935 auto reductionsSize = reduceOp.getReductions().size();
2936 auto initValsSize = getInitVals().size();
2937 if (resultsSize != reductionsSize)
2938 return emitOpError() << "expects number of results: " << resultsSize
2939 << " to be the same as number of reductions: "
2940 << reductionsSize;
2941 if (resultsSize != initValsSize)
2942 return emitOpError() << "expects number of results: " << resultsSize
2943 << " to be the same as number of initial values: "
2944 << initValsSize;
2945 if (reduceOp.getNumOperands() != initValsSize)
2946 // Delegate error reporting to ReduceOp
2947 return success();
2948
2949 // Check that the types of the results and reductions are the same.
2950 for (int64_t i = 0; i < static_cast<int64_t>(reductionsSize); ++i) {
2951 auto resultType = getOperation()->getResult(i).getType();
2952 auto reductionOperandType = reduceOp.getOperands()[i].getType();
2953 if (resultType != reductionOperandType)
2954 return reduceOp.emitOpError()
2955 << "expects type of " << i
2956 << "-th reduction operand: " << reductionOperandType
2957 << " to be the same as the " << i
2958 << "-th result type: " << resultType;
2959 }
2960 return success();
2961}
2962
2963ParseResult ParallelOp::parse(OpAsmParser &parser, OperationState &result) {
2964 auto &builder = parser.getBuilder();
2965 if (succeeded(parser.parseOptionalKeyword("unsigned")))
2966 result.addAttribute(getUnsignedCmpAttrName(result.name),
2967 builder.getUnitAttr());
2968
2969 // Parse an opening `(` followed by induction variables followed by `)`
2970 SmallVector<OpAsmParser::Argument, 4> ivs;
2972 return failure();
2973
2974 // Parse loop bounds.
2975 SmallVector<OpAsmParser::UnresolvedOperand, 4> lower;
2976 if (parser.parseEqual() ||
2977 parser.parseOperandList(lower, ivs.size(),
2979 parser.resolveOperands(lower, builder.getIndexType(), result.operands))
2980 return failure();
2981
2982 SmallVector<OpAsmParser::UnresolvedOperand, 4> upper;
2983 if (parser.parseKeyword("to") ||
2984 parser.parseOperandList(upper, ivs.size(),
2986 parser.resolveOperands(upper, builder.getIndexType(), result.operands))
2987 return failure();
2988
2989 // Parse step values.
2990 SmallVector<OpAsmParser::UnresolvedOperand, 4> steps;
2991 if (parser.parseKeyword("step") ||
2992 parser.parseOperandList(steps, ivs.size(),
2994 parser.resolveOperands(steps, builder.getIndexType(), result.operands))
2995 return failure();
2996
2997 // Parse init values.
2998 SmallVector<OpAsmParser::UnresolvedOperand, 4> initVals;
2999 if (succeeded(parser.parseOptionalKeyword("init"))) {
3000 if (parser.parseOperandList(initVals, OpAsmParser::Delimiter::Paren))
3001 return failure();
3002 }
3003
3004 // Parse optional results in case there is a reduce.
3005 if (parser.parseOptionalArrowTypeList(result.types))
3006 return failure();
3007
3008 // Now parse the body.
3009 Region *body = result.addRegion();
3010 for (auto &iv : ivs)
3011 iv.type = builder.getIndexType();
3012 if (parser.parseRegion(*body, ivs))
3013 return failure();
3014
3015 // Set `operandSegmentSizes` attribute.
3016 result.addAttribute(
3017 ParallelOp::getOperandSegmentSizeAttr(),
3018 builder.getDenseI32ArrayAttr({static_cast<int32_t>(lower.size()),
3019 static_cast<int32_t>(upper.size()),
3020 static_cast<int32_t>(steps.size()),
3021 static_cast<int32_t>(initVals.size())}));
3022
3023 // Parse attributes.
3024 if (parser.parseOptionalAttrDict(result.attributes) ||
3025 parser.resolveOperands(initVals, result.types, parser.getNameLoc(),
3026 result.operands))
3027 return failure();
3028
3029 // Add a terminator if none was parsed.
3030 ParallelOp::ensureTerminator(*body, builder, result.location);
3031 return success();
3032}
3033
3034void ParallelOp::print(OpAsmPrinter &p) {
3035 if (getUnsignedCmp())
3036 p << " unsigned";
3037 p << " (" << getBody()->getArguments() << ") = (" << getLowerBound()
3038 << ") to (" << getUpperBound() << ") step (" << getStep() << ")";
3039 if (!getInitVals().empty())
3040 p << " init (" << getInitVals() << ")";
3041 p.printOptionalArrowTypeList(getResultTypes());
3042 p << ' ';
3043 p.printRegion(getRegion(), /*printEntryBlockArgs=*/false);
3044 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
3045}
3046
3047SmallVector<Region *> ParallelOp::getLoopRegions() { return {&getRegion()}; }
3048
3049std::optional<SmallVector<Value>> ParallelOp::getLoopInductionVars() {
3050 return SmallVector<Value>{getBody()->getArguments()};
3051}
3052
3053std::optional<SmallVector<OpFoldResult>> ParallelOp::getLoopLowerBounds() {
3054 return getLowerBound();
3055}
3056
3057std::optional<SmallVector<OpFoldResult>> ParallelOp::getLoopUpperBounds() {
3058 return getUpperBound();
3059}
3060
3061std::optional<SmallVector<OpFoldResult>> ParallelOp::getLoopSteps() {
3062 return getStep();
3063}
3064
3066 auto ivArg = llvm::dyn_cast<BlockArgument>(val);
3067 if (!ivArg)
3068 return ParallelOp();
3069 assert(ivArg.getOwner() && "unlinked block argument");
3070 auto *containingOp = ivArg.getOwner()->getParentOp();
3071 return dyn_cast<ParallelOp>(containingOp);
3072}
3073
3074namespace {
3075// Collapse loop dimensions that perform a single iteration.
3076struct ParallelOpSingleOrZeroIterationDimsFolder
3077 : public OpRewritePattern<ParallelOp> {
3078 using OpRewritePattern<ParallelOp>::OpRewritePattern;
3079
3080 LogicalResult matchAndRewrite(ParallelOp op,
3081 PatternRewriter &rewriter) const override {
3082 Location loc = op.getLoc();
3083
3084 // Compute new loop bounds that omit all single-iteration loop dimensions.
3085 SmallVector<Value> newLowerBounds, newUpperBounds, newSteps;
3086 IRMapping mapping;
3087 for (auto [lb, ub, step, iv] :
3088 llvm::zip(op.getLowerBound(), op.getUpperBound(), op.getStep(),
3089 op.getInductionVars())) {
3090 auto numIterations = constantTripCount(lb, ub, step, !op.getUnsignedCmp(),
3092 if (numIterations.has_value()) {
3093 // Remove the loop if it performs zero iterations.
3094 if (*numIterations == 0) {
3095 rewriter.replaceOp(op, op.getInitVals());
3096 return success();
3097 }
3098 // Replace the loop induction variable by the lower bound if the loop
3099 // performs a single iteration. Otherwise, copy the loop bounds.
3100 if (*numIterations == 1) {
3101 mapping.map(iv, getValueOrCreateConstantIndexOp(rewriter, loc, lb));
3102 continue;
3103 }
3104 }
3105 newLowerBounds.push_back(lb);
3106 newUpperBounds.push_back(ub);
3107 newSteps.push_back(step);
3108 }
3109 // Exit if none of the loop dimensions perform a single iteration.
3110 if (newLowerBounds.size() == op.getLowerBound().size())
3111 return failure();
3112
3113 if (newLowerBounds.empty()) {
3114 // All of the loop dimensions perform a single iteration. Inline
3115 // loop body and nested ReduceOp's
3116 SmallVector<Value> results;
3117 results.reserve(op.getInitVals().size());
3118 for (auto &bodyOp : op.getBody()->without_terminator())
3119 rewriter.clone(bodyOp, mapping);
3120 auto reduceOp = cast<ReduceOp>(op.getBody()->getTerminator());
3121 for (int64_t i = 0, e = reduceOp.getReductions().size(); i < e; ++i) {
3122 Block &reduceBlock = reduceOp.getReductions()[i].front();
3123 auto initValIndex = results.size();
3124 mapping.map(reduceBlock.getArgument(0), op.getInitVals()[initValIndex]);
3125 mapping.map(reduceBlock.getArgument(1),
3126 mapping.lookupOrDefault(reduceOp.getOperands()[i]));
3127 for (auto &reduceBodyOp : reduceBlock.without_terminator())
3128 rewriter.clone(reduceBodyOp, mapping);
3129
3130 auto result = mapping.lookupOrDefault(
3131 cast<ReduceReturnOp>(reduceBlock.getTerminator()).getResult());
3132 results.push_back(result);
3133 }
3134
3135 rewriter.replaceOp(op, results);
3136 return success();
3137 }
3138 // Replace the parallel loop by lower-dimensional parallel loop.
3139 auto newOp = ParallelOp::create(rewriter, op.getLoc(), newLowerBounds,
3140 newUpperBounds, newSteps, op.getInitVals(),
3141 nullptr, op.getUnsignedCmp());
3142 // Erase the empty block that was inserted by the builder.
3143 rewriter.eraseBlock(newOp.getBody());
3144 // Clone the loop body and remap the block arguments of the collapsed loops
3145 // (inlining does not support a cancellable block argument mapping).
3146 rewriter.cloneRegionBefore(op.getRegion(), newOp.getRegion(),
3147 newOp.getRegion().begin(), mapping);
3148 rewriter.replaceOp(op, newOp.getResults());
3149 return success();
3150 }
3151};
3152
3153struct MergeNestedParallelLoops : public OpRewritePattern<ParallelOp> {
3154 using OpRewritePattern<ParallelOp>::OpRewritePattern;
3155
3156 LogicalResult matchAndRewrite(ParallelOp op,
3157 PatternRewriter &rewriter) const override {
3158 Block &outerBody = *op.getBody();
3159 if (!llvm::hasSingleElement(outerBody.without_terminator()))
3160 return failure();
3161
3162 auto innerOp = dyn_cast<ParallelOp>(outerBody.front());
3163 if (!innerOp)
3164 return failure();
3165
3166 if (op.getUnsignedCmp() != innerOp.getUnsignedCmp())
3167 return failure();
3168
3169 for (auto val : outerBody.getArguments())
3170 if (llvm::is_contained(innerOp.getLowerBound(), val) ||
3171 llvm::is_contained(innerOp.getUpperBound(), val) ||
3172 llvm::is_contained(innerOp.getStep(), val))
3173 return failure();
3174
3175 // Reductions are not supported yet.
3176 if (!op.getInitVals().empty() || !innerOp.getInitVals().empty())
3177 return failure();
3178
3179 auto bodyBuilder = [&](OpBuilder &builder, Location /*loc*/,
3180 ValueRange iterVals, ValueRange) {
3181 Block &innerBody = *innerOp.getBody();
3182 assert(iterVals.size() ==
3183 (outerBody.getNumArguments() + innerBody.getNumArguments()));
3184 IRMapping mapping;
3185 mapping.map(outerBody.getArguments(),
3186 iterVals.take_front(outerBody.getNumArguments()));
3187 mapping.map(innerBody.getArguments(),
3188 iterVals.take_back(innerBody.getNumArguments()));
3189 for (Operation &op : innerBody.without_terminator())
3190 builder.clone(op, mapping);
3191 };
3192
3193 auto concatValues = [](const auto &first, const auto &second) {
3194 SmallVector<Value> ret;
3195 ret.reserve(first.size() + second.size());
3196 ret.assign(first.begin(), first.end());
3197 ret.append(second.begin(), second.end());
3198 return ret;
3199 };
3200
3201 auto newLowerBounds =
3202 concatValues(op.getLowerBound(), innerOp.getLowerBound());
3203 auto newUpperBounds =
3204 concatValues(op.getUpperBound(), innerOp.getUpperBound());
3205 auto newSteps = concatValues(op.getStep(), innerOp.getStep());
3206
3207 rewriter.replaceOpWithNewOp<ParallelOp>(op, newLowerBounds, newUpperBounds,
3208 newSteps, ValueRange(),
3209 bodyBuilder);
3210 return success();
3211 }
3212};
3213
3214} // namespace
3215
3216void ParallelOp::getCanonicalizationPatterns(RewritePatternSet &results,
3217 MLIRContext *context) {
3218 results
3219 .add<ParallelOpSingleOrZeroIterationDimsFolder, MergeNestedParallelLoops>(
3220 context);
3221}
3222
3223/// Given the region at `index`, or the parent operation if `index` is None,
3224/// return the successor regions. These are the regions that may be selected
3225/// during the flow of control. `operands` is a set of optional attributes that
3226/// correspond to a constant value for each operand, or null if that operand is
3227/// not a constant.
3228void ParallelOp::getSuccessorRegions(
3229 RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
3230 // Both the operation itself and the region may be branching into the body or
3231 // back into the operation itself. It is possible for loop not to enter the
3232 // body.
3233 regions.push_back(RegionSuccessor(&getRegion()));
3234 regions.push_back(RegionSuccessor(getOperation()));
3235}
3236
3237//===----------------------------------------------------------------------===//
3238// ReduceOp
3239//===----------------------------------------------------------------------===//
3240
3241void ReduceOp::build(OpBuilder &builder, OperationState &result) {}
3242
3243void ReduceOp::build(OpBuilder &builder, OperationState &result,
3244 ValueRange operands) {
3245 result.addOperands(operands);
3246 for (Value v : operands) {
3247 OpBuilder::InsertionGuard guard(builder);
3248 Region *bodyRegion = result.addRegion();
3249 builder.createBlock(bodyRegion, {},
3250 ArrayRef<Type>{v.getType(), v.getType()},
3251 {result.location, result.location});
3252 }
3253}
3254
3255LogicalResult ReduceOp::verifyRegions() {
3256 if (getReductions().size() != getOperands().size())
3257 return emitOpError() << "expects number of reduction regions: "
3258 << getReductions().size()
3259 << " to be the same as number of reduction operands: "
3260 << getOperands().size();
3261 // The region of a ReduceOp has two arguments of the same type as its
3262 // corresponding operand.
3263 for (int64_t i = 0, e = getReductions().size(); i < e; ++i) {
3264 auto type = getOperands()[i].getType();
3265 Block &block = getReductions()[i].front();
3266 if (block.empty())
3267 return emitOpError() << i << "-th reduction has an empty body";
3268 if (block.getNumArguments() != 2 ||
3269 llvm::any_of(block.getArguments(), [&](const BlockArgument &arg) {
3270 return arg.getType() != type;
3271 }))
3272 return emitOpError() << "expected two block arguments with type " << type
3273 << " in the " << i << "-th reduction region";
3274
3275 // Check that the block is terminated by a ReduceReturnOp.
3276 if (!isa<ReduceReturnOp>(block.getTerminator()))
3277 return emitOpError("reduction bodies must be terminated with an "
3278 "'scf.reduce.return' op");
3279 }
3280
3281 return success();
3282}
3283
3284MutableOperandRange
3285ReduceOp::getMutableSuccessorOperands(RegionSuccessor point) {
3286 // No operands are forwarded to the next iteration.
3287 return MutableOperandRange(getOperation(), /*start=*/0, /*length=*/0);
3288}
3289
3290//===----------------------------------------------------------------------===//
3291// ReduceReturnOp
3292//===----------------------------------------------------------------------===//
3293
3294LogicalResult ReduceReturnOp::verify() {
3295 // The type of the return value should be the same type as the types of the
3296 // block arguments of the reduction body.
3297 Block *reductionBody = getOperation()->getBlock();
3298 // Should already be verified by an op trait.
3299 assert(isa<ReduceOp>(reductionBody->getParentOp()) && "expected scf.reduce");
3300 Type expectedResultType = reductionBody->getArgument(0).getType();
3301 if (expectedResultType != getResult().getType())
3302 return emitOpError() << "must have type " << expectedResultType
3303 << " (the type of the reduction inputs)";
3304 return success();
3305}
3306
3307//===----------------------------------------------------------------------===//
3308// WhileOp
3309//===----------------------------------------------------------------------===//
3310
3311void WhileOp::build(::mlir::OpBuilder &odsBuilder,
3312 ::mlir::OperationState &odsState, TypeRange resultTypes,
3313 ValueRange inits, BodyBuilderFn beforeBuilder,
3314 BodyBuilderFn afterBuilder) {
3315 odsState.addOperands(inits);
3316 odsState.addTypes(resultTypes);
3317
3318 OpBuilder::InsertionGuard guard(odsBuilder);
3319
3320 // Build before region.
3321 SmallVector<Location, 4> beforeArgLocs;
3322 beforeArgLocs.reserve(inits.size());
3323 for (Value operand : inits) {
3324 beforeArgLocs.push_back(operand.getLoc());
3325 }
3326
3327 Region *beforeRegion = odsState.addRegion();
3328 Block *beforeBlock = odsBuilder.createBlock(beforeRegion, /*insertPt=*/{},
3329 inits.getTypes(), beforeArgLocs);
3330 if (beforeBuilder)
3331 beforeBuilder(odsBuilder, odsState.location, beforeBlock->getArguments());
3332
3333 // Build after region.
3334 SmallVector<Location, 4> afterArgLocs(resultTypes.size(), odsState.location);
3335
3336 Region *afterRegion = odsState.addRegion();
3337 Block *afterBlock = odsBuilder.createBlock(afterRegion, /*insertPt=*/{},
3338 resultTypes, afterArgLocs);
3339
3340 if (afterBuilder)
3341 afterBuilder(odsBuilder, odsState.location, afterBlock->getArguments());
3342}
3343
3344ConditionOp WhileOp::getConditionOp() {
3345 return cast<ConditionOp>(getBeforeBody()->getTerminator());
3346}
3347
3348YieldOp WhileOp::getYieldOp() {
3349 return cast<YieldOp>(getAfterBody()->getTerminator());
3350}
3351
3352std::optional<MutableArrayRef<OpOperand>> WhileOp::getYieldedValuesMutable() {
3353 return getYieldOp().getResultsMutable();
3354}
3355
3356Block::BlockArgListType WhileOp::getBeforeArguments() {
3357 return getBeforeBody()->getArguments();
3358}
3359
3360Block::BlockArgListType WhileOp::getAfterArguments() {
3361 return getAfterBody()->getArguments();
3362}
3363
3364Block::BlockArgListType WhileOp::getRegionIterArgs() {
3365 return getBeforeArguments();
3366}
3367
3368OperandRange WhileOp::getEntrySuccessorOperands(RegionSuccessor successor) {
3369 assert(successor.getSuccessor() == &getBefore() &&
3370 "WhileOp is expected to branch only to the first region");
3371 return getInits();
3372}
3373
3374void WhileOp::getSuccessorRegions(RegionBranchPoint point,
3375 SmallVectorImpl<RegionSuccessor> &regions) {
3376 // The parent op always branches to the condition region.
3377 if (point.isParent()) {
3378 regions.emplace_back(&getBefore());
3379 return;
3380 }
3381
3382 assert(llvm::is_contained(
3383 {&getAfter(), &getBefore()},
3384 point.getTerminatorPredecessorOrNull()->getParentRegion()) &&
3385 "there are only two regions in a WhileOp");
3386 // The body region always branches back to the condition region.
3387 if (point.getTerminatorPredecessorOrNull()->getParentRegion() ==
3388 &getAfter()) {
3389 regions.emplace_back(&getBefore());
3390 return;
3391 }
3392
3393 regions.push_back(RegionSuccessor(getOperation()));
3394 regions.emplace_back(&getAfter());
3395}
3396
3397ValueRange WhileOp::getSuccessorInputs(RegionSuccessor successor) {
3398 if (successor.isOperation())
3399 return getOperation()->getResults();
3400 if (successor == &getBefore())
3401 return getBefore().getArguments();
3402 if (successor == &getAfter())
3403 return getAfter().getArguments();
3404 llvm_unreachable("invalid region successor");
3405}
3406
3407SmallVector<Region *> WhileOp::getLoopRegions() {
3408 return {&getBefore(), &getAfter()};
3409}
3410
3411/// Parses a `while` op.
3412///
3413/// op ::= `scf.while` assignments `:` function-type region `do` region
3414/// `attributes` attribute-dict
3415/// initializer ::= /* empty */ | `(` assignment-list `)`
3416/// assignment-list ::= assignment | assignment `,` assignment-list
3417/// assignment ::= ssa-value `=` ssa-value
3418ParseResult scf::WhileOp::parse(OpAsmParser &parser, OperationState &result) {
3419 SmallVector<OpAsmParser::Argument, 4> regionArgs;
3420 SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;
3421 Region *before = result.addRegion();
3422 Region *after = result.addRegion();
3423
3424 OptionalParseResult listResult =
3425 parser.parseOptionalAssignmentList(regionArgs, operands);
3426 if (listResult.has_value() && failed(listResult.value()))
3427 return failure();
3428
3429 FunctionType functionType;
3430 SMLoc typeLoc = parser.getCurrentLocation();
3431 if (failed(parser.parseColonType(functionType)))
3432 return failure();
3433
3434 result.addTypes(functionType.getResults());
3435
3436 if (functionType.getNumInputs() != operands.size()) {
3437 return parser.emitError(typeLoc)
3438 << "expected as many input types as operands " << "(expected "
3439 << operands.size() << " got " << functionType.getNumInputs() << ")";
3440 }
3441
3442 // Resolve input operands.
3443 if (failed(parser.resolveOperands(operands, functionType.getInputs(),
3444 parser.getCurrentLocation(),
3445 result.operands)))
3446 return failure();
3447
3448 // Propagate the types into the region arguments.
3449 for (size_t i = 0, e = regionArgs.size(); i != e; ++i)
3450 regionArgs[i].type = functionType.getInput(i);
3451
3452 return failure(parser.parseRegion(*before, regionArgs) ||
3453 parser.parseKeyword("do") || parser.parseRegion(*after) ||
3454 parser.parseOptionalAttrDictWithKeyword(result.attributes));
3455}
3456
3457/// Prints a `while` op.
3458void scf::WhileOp::print(OpAsmPrinter &p) {
3459 printInitializationList(p, getBeforeArguments(), getInits(), " ");
3460 p << " : ";
3461 p.printFunctionalType(getInits().getTypes(), getResults().getTypes());
3462 p << ' ';
3463 p.printRegion(getBefore(), /*printEntryBlockArgs=*/false);
3464 p << " do ";
3465 p.printRegion(getAfter());
3467 (*this)->getDiscardableAttrDictionary().getValue());
3468}
3469
3470LogicalResult scf::WhileOp::verify() {
3471 auto beforeTerminator = verifyAndGetTerminator<scf::ConditionOp>(
3472 *this, getBefore(),
3473 "expects the 'before' region to terminate with 'scf.condition'");
3474 if (!beforeTerminator)
3475 return failure();
3476
3477 auto afterTerminator = verifyAndGetTerminator<scf::YieldOp>(
3478 *this, getAfter(),
3479 "expects the 'after' region to terminate with 'scf.yield'");
3480 return success(afterTerminator != nullptr);
3481}
3482
3483namespace {
3484/// Move a scf.if op that is directly before the scf.condition op in the while
3485/// before region, and whose condition matches the condition of the
3486/// scf.condition op, down into the while after region.
3487///
3488/// scf.while (..) : (...) -> ... {
3489/// %additional_used_values = ...
3490/// %cond = ...
3491/// ...
3492/// %res = scf.if %cond -> (...) {
3493/// use(%additional_used_values)
3494/// ... // then block
3495/// scf.yield %then_value
3496/// } else {
3497/// scf.yield %else_value
3498/// }
3499/// scf.condition(%cond) %res, ...
3500/// } do {
3501/// ^bb0(%res_arg, ...):
3502/// use(%res_arg)
3503/// ...
3504///
3505/// becomes
3506/// scf.while (..) : (...) -> ... {
3507/// %additional_used_values = ...
3508/// %cond = ...
3509/// ...
3510/// scf.condition(%cond) %else_value, ..., %additional_used_values
3511/// } do {
3512/// ^bb0(%res_arg ..., %additional_args): :
3513/// use(%additional_args)
3514/// ... // if then block
3515/// use(%then_value)
3516/// ...
3517struct WhileMoveIfDown : public OpRewritePattern<scf::WhileOp> {
3518 using OpRewritePattern<scf::WhileOp>::OpRewritePattern;
3519
3520 LogicalResult matchAndRewrite(scf::WhileOp op,
3521 PatternRewriter &rewriter) const override {
3522 auto conditionOp = op.getConditionOp();
3523
3524 // Only support ifOp right before the condition at the moment. Relaxing this
3525 // would require to:
3526 // - check that the body does not have side-effects conflicting with
3527 // operations between the if and the condition.
3528 // - check that results of the if operation are only used as arguments to
3529 // the condition.
3530 auto ifOp = dyn_cast_or_null<scf::IfOp>(conditionOp->getPrevNode());
3531
3532 // Check that the ifOp is directly before the conditionOp and that it
3533 // matches the condition of the conditionOp. Also ensure that the ifOp has
3534 // no else block with content, as that would complicate the transformation.
3535 // TODO: support else blocks with content.
3536 if (!ifOp || ifOp.getCondition() != conditionOp.getCondition() ||
3537 (ifOp.elseBlock() && !ifOp.elseBlock()->without_terminator().empty()))
3538 return failure();
3539
3540 assert((ifOp->use_empty() || (llvm::all_equal(ifOp->getUsers()) &&
3541 *ifOp->user_begin() == conditionOp)) &&
3542 "ifOp has unexpected uses");
3543
3544 Location loc = op.getLoc();
3545
3546 // Replace uses of ifOp results in the conditionOp with the yielded values
3547 // from the ifOp branches: the after-region argument takes the `then` value,
3548 // while the condition operand -- which becomes a while result once the
3549 // condition is false -- takes the `else` value.
3550 //
3551 // The same ifOp result may be forwarded to several condition operands, so
3552 // assign into the specific operand instead of replacing all uses of the
3553 // ifOp result, which would also rewrite the operands not yet visited.
3554 for (auto [idx, arg] : llvm::enumerate(conditionOp.getArgs())) {
3555 auto it = llvm::find(ifOp->getResults(), arg);
3556 if (it == ifOp->getResults().end())
3557 continue;
3558 size_t ifOpIdx = it.getIndex();
3559 rewriter.replaceAllUsesWith(op.getAfterArguments()[idx],
3560 ifOp.thenYield()->getOperand(ifOpIdx));
3561 unsigned argIdx = idx;
3562 Value elseValue = ifOp.elseYield()->getOperand(ifOpIdx);
3563 rewriter.modifyOpInPlace(conditionOp, [&] {
3564 conditionOp.getArgsMutable()[argIdx].assign(elseValue);
3565 });
3566 }
3567
3568 // Collect additional used values from before region.
3569 SetVector<Value> additionalUsedValuesSet;
3570 visitUsedValuesDefinedAbove(ifOp.getThenRegion(), [&](OpOperand *operand) {
3571 if (&op.getBefore() == operand->get().getParentRegion())
3572 additionalUsedValuesSet.insert(operand->get());
3573 });
3574
3575 // Create new whileOp with additional used values as results.
3576 auto additionalUsedValues = additionalUsedValuesSet.getArrayRef();
3577 auto additionalValueTypes = llvm::map_to_vector(
3578 additionalUsedValues, [](Value val) { return val.getType(); });
3579 size_t additionalValueSize = additionalUsedValues.size();
3580 SmallVector<Type> newResultTypes(op.getResultTypes());
3581 newResultTypes.append(additionalValueTypes);
3582
3583 auto newWhileOp =
3584 scf::WhileOp::create(rewriter, loc, newResultTypes, op.getInits());
3585
3586 rewriter.modifyOpInPlace(newWhileOp, [&] {
3587 newWhileOp.getBefore().takeBody(op.getBefore());
3588 newWhileOp.getAfter().takeBody(op.getAfter());
3589 newWhileOp.getAfter().addArguments(
3590 additionalValueTypes,
3591 SmallVector<Location>(additionalValueSize, loc));
3592 });
3593
3594 rewriter.modifyOpInPlace(conditionOp, [&] {
3595 conditionOp.getArgsMutable().append(additionalUsedValues);
3596 });
3597
3598 // Replace uses of additional used values inside the ifOp then region with
3599 // the whileOp after region arguments.
3600 rewriter.replaceUsesWithIf(
3601 additionalUsedValues,
3602 newWhileOp.getAfterArguments().take_back(additionalValueSize),
3603 [&](OpOperand &use) {
3604 return ifOp.getThenRegion().isAncestor(
3605 use.getOwner()->getParentRegion());
3606 });
3607
3608 // Inline ifOp then region into new whileOp after region.
3609 rewriter.eraseOp(ifOp.thenYield());
3610 rewriter.inlineBlockBefore(ifOp.thenBlock(), newWhileOp.getAfterBody(),
3611 newWhileOp.getAfterBody()->begin());
3612 rewriter.eraseOp(ifOp);
3613 rewriter.replaceOp(op,
3614 newWhileOp->getResults().drop_back(additionalValueSize));
3615 return success();
3616 }
3617};
3618
3619/// Replace uses of the condition within the do block with true, since otherwise
3620/// the block would not be evaluated.
3621///
3622/// scf.while (..) : (i1, ...) -> ... {
3623/// %condition = call @evaluate_condition() : () -> i1
3624/// scf.condition(%condition) %condition : i1, ...
3625/// } do {
3626/// ^bb0(%arg0: i1, ...):
3627/// use(%arg0)
3628/// ...
3629///
3630/// becomes
3631/// scf.while (..) : (i1, ...) -> ... {
3632/// %condition = call @evaluate_condition() : () -> i1
3633/// scf.condition(%condition) %condition : i1, ...
3634/// } do {
3635/// ^bb0(%arg0: i1, ...):
3636/// use(%true)
3637/// ...
3638struct WhileConditionTruth : public OpRewritePattern<WhileOp> {
3639 using OpRewritePattern<WhileOp>::OpRewritePattern;
3640
3641 LogicalResult matchAndRewrite(WhileOp op,
3642 PatternRewriter &rewriter) const override {
3643 auto term = op.getConditionOp();
3644
3645 // These variables serve to prevent creating duplicate constants
3646 // and hold constant true or false values.
3647 Value constantTrue = nullptr;
3648
3649 bool replaced = false;
3650 for (auto yieldedAndBlockArgs :
3651 llvm::zip(term.getArgs(), op.getAfterArguments())) {
3652 if (std::get<0>(yieldedAndBlockArgs) == term.getCondition()) {
3653 if (!std::get<1>(yieldedAndBlockArgs).use_empty()) {
3654 if (!constantTrue)
3655 constantTrue = arith::ConstantOp::create(
3656 rewriter, op.getLoc(), term.getCondition().getType(),
3657 rewriter.getBoolAttr(true));
3658
3659 rewriter.replaceAllUsesWith(std::get<1>(yieldedAndBlockArgs),
3660 constantTrue);
3661 replaced = true;
3662 }
3663 }
3664 }
3665 return success(replaced);
3666 }
3667};
3668
3669/// Replace operations equivalent to the condition in the do block with true,
3670/// since otherwise the block would not be evaluated.
3671///
3672/// scf.while (..) : (i32, ...) -> ... {
3673/// %z = ... : i32
3674/// %condition = cmpi pred %z, %a
3675/// scf.condition(%condition) %z : i32, ...
3676/// } do {
3677/// ^bb0(%arg0: i32, ...):
3678/// %condition2 = cmpi pred %arg0, %a
3679/// use(%condition2)
3680/// ...
3681///
3682/// becomes
3683/// scf.while (..) : (i32, ...) -> ... {
3684/// %z = ... : i32
3685/// %condition = cmpi pred %z, %a
3686/// scf.condition(%condition) %z : i32, ...
3687/// } do {
3688/// ^bb0(%arg0: i32, ...):
3689/// use(%true)
3690/// ...
3691struct WhileCmpCond : public OpRewritePattern<scf::WhileOp> {
3692 using OpRewritePattern<scf::WhileOp>::OpRewritePattern;
3693
3694 LogicalResult matchAndRewrite(scf::WhileOp op,
3695 PatternRewriter &rewriter) const override {
3696 using namespace scf;
3697 auto cond = op.getConditionOp();
3698 auto cmp = cond.getCondition().getDefiningOp<arith::CmpIOp>();
3699 if (!cmp)
3700 return failure();
3701 bool changed = false;
3702 for (auto tup : llvm::zip(cond.getArgs(), op.getAfterArguments())) {
3703 for (size_t opIdx = 0; opIdx < 2; opIdx++) {
3704 if (std::get<0>(tup) != cmp.getOperand(opIdx))
3705 continue;
3706 for (OpOperand &u :
3707 llvm::make_early_inc_range(std::get<1>(tup).getUses())) {
3708 auto cmp2 = dyn_cast<arith::CmpIOp>(u.getOwner());
3709 if (!cmp2)
3710 continue;
3711 // For a binary operator 1-opIdx gets the other side.
3712 if (cmp2.getOperand(1 - opIdx) != cmp.getOperand(1 - opIdx))
3713 continue;
3714 bool samePredicate;
3715 if (cmp2.getPredicate() == cmp.getPredicate())
3716 samePredicate = true;
3717 else if (cmp2.getPredicate() ==
3718 arith::invertPredicate(cmp.getPredicate()))
3719 samePredicate = false;
3720 else
3721 continue;
3722
3723 rewriter.replaceOpWithNewOp<arith::ConstantIntOp>(cmp2, samePredicate,
3724 1);
3725 changed = true;
3726 }
3727 }
3728 }
3729 return success(changed);
3730 }
3731};
3732
3733/// If both ranges contain same values return mappping indices from args2 to
3734/// args1. Otherwise return std::nullopt.
3735static std::optional<SmallVector<unsigned>> getArgsMapping(ValueRange args1,
3736 ValueRange args2) {
3737 if (args1.size() != args2.size())
3738 return std::nullopt;
3739
3740 SmallVector<unsigned> ret(args1.size());
3741 for (auto &&[i, arg1] : llvm::enumerate(args1)) {
3742 auto it = llvm::find(args2, arg1);
3743 if (it == args2.end())
3744 return std::nullopt;
3745
3746 ret[std::distance(args2.begin(), it)] = static_cast<unsigned>(i);
3747 }
3748
3749 return ret;
3750}
3751
3752static bool hasDuplicates(ValueRange args) {
3753 llvm::SmallDenseSet<Value> set;
3754 for (Value arg : args) {
3755 if (!set.insert(arg).second)
3756 return true;
3757 }
3758 return false;
3759}
3760
3761/// If `before` block args are directly forwarded to `scf.condition`, rearrange
3762/// `scf.condition` args into same order as block args. Update `after` block
3763/// args and op result values accordingly.
3764/// Needed to simplify `scf.while` -> `scf.for` uplifting.
3765struct WhileOpAlignBeforeArgs : public OpRewritePattern<WhileOp> {
3766 using Base::Base;
3767
3768 LogicalResult matchAndRewrite(WhileOp loop,
3769 PatternRewriter &rewriter) const override {
3770 auto *oldBefore = loop.getBeforeBody();
3771 ConditionOp oldTerm = loop.getConditionOp();
3772 ValueRange beforeArgs = oldBefore->getArguments();
3773 ValueRange termArgs = oldTerm.getArgs();
3774 if (beforeArgs == termArgs)
3775 return failure();
3776
3777 if (hasDuplicates(termArgs))
3778 return failure();
3779
3780 auto mapping = getArgsMapping(beforeArgs, termArgs);
3781 if (!mapping)
3782 return failure();
3783
3784 {
3785 OpBuilder::InsertionGuard g(rewriter);
3786 rewriter.setInsertionPoint(oldTerm);
3787 rewriter.replaceOpWithNewOp<ConditionOp>(oldTerm, oldTerm.getCondition(),
3788 beforeArgs);
3789 }
3790
3791 auto *oldAfter = loop.getAfterBody();
3792
3793 SmallVector<Type> newResultTypes(beforeArgs.size());
3794 for (auto &&[i, j] : llvm::enumerate(*mapping))
3795 newResultTypes[j] = loop.getResult(i).getType();
3796
3797 auto newLoop = WhileOp::create(
3798 rewriter, loop.getLoc(), newResultTypes, loop.getInits(),
3799 /*beforeBuilder=*/nullptr, /*afterBuilder=*/nullptr);
3800 auto *newBefore = newLoop.getBeforeBody();
3801 auto *newAfter = newLoop.getAfterBody();
3802
3803 SmallVector<Value> newResults(beforeArgs.size());
3804 SmallVector<Value> newAfterArgs(beforeArgs.size());
3805 for (auto &&[i, j] : llvm::enumerate(*mapping)) {
3806 newResults[i] = newLoop.getResult(j);
3807 newAfterArgs[i] = newAfter->getArgument(j);
3808 }
3809
3810 rewriter.inlineBlockBefore(oldBefore, newBefore, newBefore->begin(),
3811 newBefore->getArguments());
3812 rewriter.inlineBlockBefore(oldAfter, newAfter, newAfter->begin(),
3813 newAfterArgs);
3814
3815 rewriter.replaceOp(loop, newResults);
3816 return success();
3817 }
3818};
3819} // namespace
3820
3821void WhileOp::getCanonicalizationPatterns(RewritePatternSet &results,
3822 MLIRContext *context) {
3823 results.add<WhileConditionTruth, WhileCmpCond, WhileOpAlignBeforeArgs,
3824 WhileMoveIfDown>(context);
3826 results, WhileOp::getOperationName());
3828 WhileOp::getOperationName());
3829}
3830
3831//===----------------------------------------------------------------------===//
3832// IndexSwitchOp
3833//===----------------------------------------------------------------------===//
3834
3835/// Parse the case regions and values.
3836static ParseResult
3838 SmallVectorImpl<std::unique_ptr<Region>> &caseRegions) {
3839 SmallVector<int64_t> caseValues;
3840 while (succeeded(p.parseOptionalKeyword("case"))) {
3841 int64_t value;
3842 Region &region = *caseRegions.emplace_back(std::make_unique<Region>());
3843 if (p.parseInteger(value) || p.parseRegion(region, /*arguments=*/{}))
3844 return failure();
3845 caseValues.push_back(value);
3846 }
3847 cases = p.getBuilder().getDenseI64ArrayAttr(caseValues);
3848 return success();
3849}
3850
3851/// Print the case regions and values.
3853 DenseI64ArrayAttr cases, RegionRange caseRegions) {
3854 for (auto [value, region] : llvm::zip(cases.asArrayRef(), caseRegions)) {
3855 p.printNewline();
3856 p << "case " << value << ' ';
3857 p.printRegion(*region, /*printEntryBlockArgs=*/false);
3858 }
3859}
3860
3861LogicalResult scf::IndexSwitchOp::verify() {
3862 if (getCases().size() != getCaseRegions().size()) {
3863 return emitOpError("has ")
3864 << getCaseRegions().size() << " case regions but "
3865 << getCases().size() << " case values";
3866 }
3867
3868 DenseSet<int64_t> valueSet;
3869 for (int64_t value : getCases())
3870 if (!valueSet.insert(value).second)
3871 return emitOpError("has duplicate case value: ") << value;
3872 auto verifyRegion = [&](Region &region, const Twine &name) -> LogicalResult {
3873 auto yield = dyn_cast<YieldOp>(region.front().back());
3874 if (!yield)
3875 return emitOpError("expected region to end with scf.yield, but got ")
3876 << region.front().back().getName();
3877
3878 if (yield.getNumOperands() != getNumResults()) {
3879 return (emitOpError("expected each region to return ")
3880 << getNumResults() << " values, but " << name << " returns "
3881 << yield.getNumOperands())
3882 .attachNote(yield.getLoc())
3883 << "see yield operation here";
3884 }
3885 for (auto [idx, result, operand] :
3886 llvm::enumerate(getResultTypes(), yield.getOperands())) {
3887 if (!operand)
3888 return yield.emitOpError() << "operand " << idx << " is null\n";
3889 if (result == operand.getType())
3890 continue;
3891 return (emitOpError("expected result #")
3892 << idx << " of each region to be " << result)
3893 .attachNote(yield.getLoc())
3894 << name << " returns " << operand.getType() << " here";
3895 }
3896 return success();
3897 };
3898
3899 if (failed(verifyRegion(getDefaultRegion(), "default region")))
3900 return failure();
3901 for (auto [idx, caseRegion] : llvm::enumerate(getCaseRegions()))
3902 if (failed(verifyRegion(caseRegion, "case region #" + Twine(idx))))
3903 return failure();
3904
3905 return success();
3906}
3907
3908unsigned scf::IndexSwitchOp::getNumCases() { return getCases().size(); }
3909
3910Block &scf::IndexSwitchOp::getDefaultBlock() {
3911 return getDefaultRegion().front();
3912}
3913
3914Block &scf::IndexSwitchOp::getCaseBlock(unsigned idx) {
3915 assert(idx < getNumCases() && "case index out-of-bounds");
3916 return getCaseRegions()[idx].front();
3917}
3918
3919void IndexSwitchOp::getSuccessorRegions(
3920 RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &successors) {
3921 // All regions branch back to the parent op.
3922 if (!point.isParent()) {
3923 successors.push_back(RegionSuccessor(getOperation()));
3924 return;
3925 }
3926
3927 llvm::append_range(successors, getRegions());
3928}
3929
3930ValueRange IndexSwitchOp::getSuccessorInputs(RegionSuccessor successor) {
3931 return successor.isOperation() ? ValueRange(getOperation()->getResults())
3932 : ValueRange();
3933}
3934
3935void IndexSwitchOp::getEntrySuccessorRegions(
3936 ArrayRef<Attribute> operands,
3937 SmallVectorImpl<RegionSuccessor> &successors) {
3938 FoldAdaptor adaptor(operands, *this);
3939
3940 // If a constant was not provided, all regions are possible successors.
3941 auto arg = dyn_cast_or_null<IntegerAttr>(adaptor.getArg());
3942 if (!arg) {
3943 llvm::append_range(successors, getRegions());
3944 return;
3945 }
3946
3947 // Otherwise, try to find a case with a matching value. If not, the
3948 // default region is the only successor.
3949 for (auto [caseValue, caseRegion] : llvm::zip(getCases(), getCaseRegions())) {
3950 if (caseValue == arg.getInt()) {
3951 successors.emplace_back(&caseRegion);
3952 return;
3953 }
3954 }
3955 successors.emplace_back(&getDefaultRegion());
3956}
3957
3958void IndexSwitchOp::getRegionInvocationBounds(
3959 ArrayRef<Attribute> operands, SmallVectorImpl<InvocationBounds> &bounds) {
3960 auto operandValue = llvm::dyn_cast_or_null<IntegerAttr>(operands.front());
3961 if (!operandValue) {
3962 // All regions are invoked at most once.
3963 bounds.append(getNumRegions(), InvocationBounds(/*lb=*/0, /*ub=*/1));
3964 return;
3965 }
3966
3967 unsigned liveIndex = getNumRegions() - 1;
3968 const auto *it = llvm::find(getCases(), operandValue.getInt());
3969 if (it != getCases().end())
3970 liveIndex = std::distance(getCases().begin(), it);
3971 for (unsigned i = 0, e = getNumRegions(); i < e; ++i)
3972 bounds.emplace_back(/*lb=*/0, /*ub=*/i == liveIndex);
3973}
3974
3975void IndexSwitchOp::getCanonicalizationPatterns(RewritePatternSet &results,
3976 MLIRContext *context) {
3978 results, IndexSwitchOp::getOperationName());
3980 results, IndexSwitchOp::getOperationName());
3981}
3982
3983//===----------------------------------------------------------------------===//
3984// TableGen'd op method definitions
3985//===----------------------------------------------------------------------===//
3986
3987#define GET_OP_CLASSES
3988#include "mlir/Dialect/SCF/IR/SCFOps.cpp.inc"
return success()
static std::optional< int64_t > getUpperBound(Value iv)
Gets the constant upper bound on an affine.for iv.
static std::optional< int64_t > getLowerBound(Value iv)
Gets the constant lower bound on an iv.
static LogicalResult verifyRegion(emitc::SwitchOp op, Region &region, const Twine &name)
Definition EmitC.cpp:1521
static ParseResult parseSwitchCases(OpAsmParser &parser, DenseI64ArrayAttr &cases, SmallVectorImpl< std::unique_ptr< Region > > &caseRegions)
Parse the case regions and values.
Definition EmitC.cpp:1496
static void printSwitchCases(OpAsmPrinter &p, Operation *op, DenseI64ArrayAttr cases, RegionRange caseRegions)
Print the case regions and values.
Definition EmitC.cpp:1512
static void printInitializationList(OpAsmPrinter &p, Block::BlockArgListType blocksArgs, ValueRange initializers, StringRef prefix="")
Prints the initialization list in the form of <prefix>(inner = outer, inner2 = outer2,...
Definition SCF.cpp:503
static TerminatorTy verifyAndGetTerminator(Operation *op, Region &region, StringRef errorMessage)
Verifies that the first block of the given region is terminated by a TerminatorTy.
Definition SCF.cpp:103
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.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
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`
@ None
@ 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 parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
MLIRContext * getContext() const
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
virtual ParseResult parseOptionalColon()=0
Parse a : token if present.
ParseResult parseInteger(IntT &result)
Parse an integer value from the stream.
virtual ParseResult parseEqual()=0
Parse a = token.
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 getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual SMLoc getNameLoc() const =0
Return the location of the original name token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
virtual ParseResult parseArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an arrow followed by a type list.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
void printOptionalArrowTypeList(TypeRange &&types)
Print an optional arrow followed by a type list.
virtual void printNewline()
Print a newline and indent the printer to the start of the current operation/attribute/type.
This class represents an argument of a Block.
Definition Value.h:308
unsigned getArgNumber() const
Returns the number of this argument.
Definition Value.h:320
Block represents an ordered list of Operations.
Definition Block.h:34
MutableArrayRef< BlockArgument > BlockArgListType
Definition Block.h:110
bool empty()
Definition Block.h:173
BlockArgument getArgument(unsigned i)
Definition Block.h:154
unsigned getNumArguments()
Definition Block.h:153
iterator_range< args_iterator > addArguments(TypeRange types, ArrayRef< Location > locs)
Add one argument to the argument list for each type specified in the list.
Definition Block.cpp:165
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
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
BlockArgListType getArguments()
Definition Block.h:112
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:222
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
bool getValue() const
Return the boolean value of this attribute.
UnitAttr getUnitAttr()
Definition Builders.cpp:106
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:171
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:175
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
BoolAttr getBoolAttr(bool value)
Definition Builders.cpp:108
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
IntegerType getI1Type()
Definition Builders.cpp:61
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
virtual Operation * materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc)
Registered hook to materialize a single constant operation from a given attribute value with the desi...
Definition Dialect.h:83
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
IRValueT get() const
Return the current value being used by this operand.
void set(IRValueT newValue)
Set the current value being used by this operand.
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
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:119
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual OptionalParseResult parseOptionalAssignmentList(SmallVectorImpl< Argument > &lhs, SmallVectorImpl< UnresolvedOperand > &rhs)=0
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
virtual ParseResult parseArgumentList(SmallVectorImpl< Argument > &result, Delimiter delimiter=Delimiter::None, bool allowType=false, bool allowAttrs=false)=0
Parse zero or more arguments with a specified surrounding delimiter.
ParseResult parseAssignmentList(SmallVectorImpl< Argument > &lhs, SmallVectorImpl< UnresolvedOperand > &rhs)
Parse a list of assignments of the form (x1 = y1, x2 = y2, ...)
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.
void printFunctionalType(Operation *op)
Print the complete type of an operation in functional form.
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 * 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
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
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
void cloneRegionBefore(Region &region, Region &parent, Region::iterator before, IRMapping &mapping)
Clone the blocks that belong to "region" before the given position in another region "parent".
Definition Builders.cpp:608
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
Operation * cloneWithoutRegions(Operation &op, IRMapping &mapper)
Creates a deep copy of this operation but keep the operation regions empty.
Definition Builders.h:596
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:256
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:231
Set of flags used to control the behavior of the various IR print methods (e.g.
This is a value defined by a result of an operation.
Definition Value.h:456
Operation * getOwner() const
Returns the operation that owns this result.
Definition Value.h:465
unsigned getResultNumber() const
Returns the number of this result.
Definition Value.h:468
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1171
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:44
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:434
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
OperandRange operand_range
Definition Operation.h:398
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
auto getDiscardableAttrs()
Return a range of all of discardable attributes on this operation.
Definition Operation.h:540
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:247
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
ParseResult value() const
Access the internal ParseResult value.
bool has_value() const
Returns true if we contain a valid ParseResult value.
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.
RegionBranchTerminatorOpInterface getTerminatorPredecessorOrNull() const
Returns the terminator if branching from a region.
This class provides an abstraction over the different types of ranges over Regions.
Definition Region.h:363
This class represents a successor of a region.
Region * getSuccessor() const
Return the given region successor.
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
Block & front()
Definition Region.h:65
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:234
Block & back()
Definition Region.h:64
bool empty()
Definition Region.h:60
unsigned getNumArguments()
Definition Region.h:136
iterator begin()
Definition Region.h:55
BlockArgument getArgument(unsigned i)
Definition Region.h:137
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void eraseBlock(Block *block)
This method erases all operations in a block.
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 finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
Operation * eraseOpResults(Operation *op, const BitVector &eraseIndices)
Erase the specified results of the given operation.
virtual void inlineBlockBefore(Block *source, Block *dest, Block::iterator before, ValueRange argValues={})
Inline the operations of block 'source' into block 'dest' before the given position.
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 modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
void inlineRegionBefore(Region &region, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
virtual bool replaceUsesWithIf(Value from, Value to, function_ref< bool(OpOperand &)> functor, bool *allUsesReplaced=nullptr)
Find uses of from and replace them with to if the functor returns true.
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
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isIndex() const
Definition Types.cpp:56
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
type_range getTypes() const
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
user_range getUsers() const
Definition Value.h:218
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
constexpr auto RecursivelySpeculatable
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto NotSpeculatable
static Value defaultReplBuilderFn(OpBuilder &builder, Location loc, Value value)
Default implementation of the non-successor-input replacement builder function.
static LogicalResult defaultMatcherFn(Operation *op)
Default implementation of the pattern matcher function.
StringRef getMappingAttrName()
Name of the mapping attribute produced by loop mappers.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
ParallelOp getParallelForInductionVarOwner(Value val)
Returns the parallel loop parent of an induction variable.
Definition SCF.cpp:3065
void buildTerminatedBody(OpBuilder &builder, Location loc)
Default callback for IfOp builders. Inserts a yield without arguments.
Definition SCF.cpp:96
LoopNest buildLoopNest(OpBuilder &builder, Location loc, ValueRange lbs, ValueRange ubs, ValueRange steps, ValueRange iterArgs, function_ref< ValueVector(OpBuilder &, Location, ValueRange, ValueRange)> bodyBuilder=nullptr)
Creates a perfect nest of "for" loops, i.e.
Definition SCF.cpp:824
bool insideMutuallyExclusiveBranches(Operation *a, Operation *b)
Return true if ops a and b (or their ancestors) are in mutually exclusive regions/blocks of an IfOp.
Definition SCF.cpp:1984
std::optional< llvm::APSInt > computeUbMinusLb(Value lb, Value ub, bool isSigned)
Helper function to compute the difference between two values.
Definition SCF.cpp:117
ForOp getForInductionVarOwner(Value val)
Returns the loop parent of an induction variable.
Definition SCF.cpp:681
SmallVector< Value > ValueVector
An owning vector of values, handy to return from functions.
Definition SCF.h:59
bool isShapePreserving(ForOp forOp, int64_t arg)
A simple, conservative analysis to determine if the loop is shape preserving.
Definition SCF.cpp:978
llvm::function_ref< Value(OpBuilder &, Location loc, Type, Value)> ValueTypeCastFnTy
Perform a replacement of one iter OpOperand of an scf.for to the replacement value with a different t...
Definition SCF.h:102
ForallOp getForallOpThreadIndexOwner(Value val)
Returns the ForallOp parent of an thread index variable.
Definition SCF.cpp:1464
SmallVector< Value > replaceAndCastForOpIterArg(RewriterBase &rewriter, scf::ForOp forOp, OpOperand &operand, Value replacement, const ValueTypeCastFnTy &castFn)
Definition SCF.cpp:913
LogicalResult promote(RewriterBase &rewriter, scf::ForallOp forallOp)
Promotes the loop body of a scf::ForallOp to its containing block.
Definition SCF.cpp:753
bool preservesStaticInformation(Type source, Type target)
Returns true if target is a ranked tensor type that preserves static information available in the sou...
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
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
std::function< SmallVector< Value >( OpBuilder &b, Location loc, ArrayRef< BlockArgument > newBbArgs)> NewYieldValuesFn
A function that returns the additional yielded values during replaceWithAdditionalYields.
ParseResult parseDynamicIndexList(OpAsmParser &parser, SmallVectorImpl< OpAsmParser::UnresolvedOperand > &values, DenseI64ArrayAttr &integers, DenseBoolArrayAttr &scalableFlags, SmallVectorImpl< Type > *valueTypes=nullptr, AsmParser::Delimiter delimiter=AsmParser::Delimiter::Square)
Parser hooks for custom directive in assemblyFormat.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:125
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
detail::constant_int_predicate_matcher m_One()
Matches a constant scalar / vector splat / tensor splat integer one.
Definition Matchers.h:478
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:123
void populateRegionBranchOpInterfaceInliningPattern(RewritePatternSet &patterns, StringRef opName, NonSuccessorInputReplacementBuilderFn replBuilderFn=detail::defaultReplBuilderFn, PatternMatcherFn matcherFn=detail::defaultMatcherFn, PatternBenefit benefit=1)
Populate a pattern that inlines the body of region branch ops when there is a single acyclic path thr...
LogicalResult verifyListOfOperandsOrIntegers(Operation *op, StringRef name, unsigned expectedNumElements, ArrayRef< int64_t > attr, ValueRange values)
Verify that a the values has as many elements as the number of entries in attr for which isDynamic ev...
void populateRegionBranchOpInterfaceCanonicalizationPatterns(RewritePatternSet &patterns, StringRef opName, PatternBenefit benefit=1)
Populate canonicalization patterns that simplify successor operands/inputs of region branch operation...
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
void visitUsedValuesDefinedAbove(Region &region, Region &limit, function_ref< void(OpOperand *)> callback)
Calls callback for each use of a value within region or its descendants that was defined at the ances...
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
void printDynamicIndexList(OpAsmPrinter &printer, Operation *op, OperandRange values, ArrayRef< int64_t > integers, ArrayRef< bool > scalableFlags, TypeRange valueTypes=TypeRange(), AsmParser::Delimiter delimiter=AsmParser::Delimiter::Square)
Printer hooks for custom directive in assemblyFormat.
std::optional< APInt > constantTripCount(OpFoldResult lb, OpFoldResult ub, OpFoldResult step, bool isSigned, llvm::function_ref< std::optional< llvm::APSInt >(Value, Value, bool)> computeUbMinusLb)
Return the number of iterations for a loop with a lower bound lb, upper bound ub and step step,...
LogicalResult foldDynamicIndexList(SmallVectorImpl< OpFoldResult > &ofrs, bool onlyNonNegative=false, bool onlyNonZero=false)
Returns "success" when any of the elements in ofrs is a constant value.
LogicalResult matchAndRewrite(ExecuteRegionOp op, PatternRewriter &rewriter) const override
Definition SCF.cpp:224
This is the representation of an operand reference.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
This represents an operation in an abstracted form, suitable for use with the builder APIs.
void addOperands(ValueRange newOperands)
void addTypes(ArrayRef< Type > newTypes)
Region * addRegion()
Create a region that should be attached to the operation.