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