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