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