clang 24.0.0git
FlattenCFG.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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//
9// This file implements pass that inlines CIR operations regions into the parent
10// function region.
11//
12//===----------------------------------------------------------------------===//
13
14#include "PassDetail.h"
15#include "mlir/Dialect/Func/IR/FuncOps.h"
16#include "mlir/IR/Block.h"
17#include "mlir/IR/Builders.h"
18#include "mlir/IR/PatternMatch.h"
19#include "mlir/Interfaces/SideEffectInterfaces.h"
20#include "mlir/Rewrite/PatternApplicator.h"
21#include "mlir/Support/LogicalResult.h"
22#include "mlir/Transforms/DialectConversion.h"
28#include "llvm/ADT/TypeSwitch.h"
29
30using namespace mlir;
31using namespace cir;
32
33namespace mlir {
34#define GEN_PASS_DEF_CIRFLATTENCFG
35#include "clang/CIR/Dialect/Passes.h.inc"
36} // namespace mlir
37
38namespace {
39
40/// Lowers operations with the terminator trait that have a single successor.
41void lowerTerminator(mlir::Operation *op, mlir::Block *dest,
42 mlir::PatternRewriter &rewriter,
43 mlir::DistinctAttr loopId = {},
44 cir::LoopHintAttr loopHints = {}) {
45 assert(op->hasTrait<mlir::OpTrait::IsTerminator>() && "not a terminator");
46 mlir::OpBuilder::InsertionGuard guard(rewriter);
47 rewriter.setInsertionPoint(op);
48 rewriter.replaceOpWithNewOp<cir::BrOp>(op, mlir::ValueRange{}, loopId,
49 loopHints, dest);
50}
51
52/// Walks a region while skipping operations of type `Ops`. This ensures the
53/// callback is not applied to said operations and its children.
54template <typename... Ops>
55void walkRegionSkipping(
56 mlir::Region &region,
57 mlir::function_ref<mlir::WalkResult(mlir::Operation *)> callback) {
58 region.walk<mlir::WalkOrder::PreOrder>([&](mlir::Operation *op) {
59 if (isa<Ops...>(op))
60 return mlir::WalkResult::skip();
61 return callback(op);
62 });
63}
64
65/// Check whether a region contains any nested op with regions (i.e. structured
66/// CIR ops that must be flattened before their parent). The greedy pattern
67/// rewriter doesn't guarantee inside-out processing order — when a pattern
68/// fires and modifies IR, newly created ops go onto the worklist and can be
69/// visited in any order. So each flattening pattern must explicitly defer
70/// until its nested structured ops are flat.
71///
72/// CaseOps are excluded because they are structural children of SwitchOp and
73/// are handled by the SwitchOp flattening pattern.
74static bool hasNestedOpsToFlatten(mlir::Region &region) {
75 return region
76 .walk([](mlir::Operation *op) {
77 if (op->getNumRegions() > 0 && !isa<cir::CaseOp>(op))
78 return mlir::WalkResult::interrupt();
79 return mlir::WalkResult::advance();
80 })
81 .wasInterrupted();
82}
83
84/// True if `op` is a non-returning terminator — currently `cir.unreachable`
85/// or `cir.trap`. Such terminators don't fall through and don't yield a
86/// value, so when flattening a region they can be left in place rather than
87/// being replaced with a branch to the continuation block. Add new ops here
88/// (e.g. a hypothetical `cir.abort`) so every flattening pattern picks them
89/// up at once.
90static bool isNonReturningTerminator(mlir::Operation *op) {
91 return mlir::isa_and_nonnull<cir::UnreachableOp, cir::TrapOp>(op);
92}
93
94/// Rewrite the terminator of `region`'s exit block so that, after
95/// flattening, control falls through to `continueBlock`. The exit
96/// terminator is expected to be either:
97/// - `cir.yield`: replaced with `cir.br` to `continueBlock` (yielded
98/// args become the destination block's arguments).
99/// - non-returning (`cir.unreachable`, `cir.trap`): left in place — no
100/// branch is needed.
101///
102/// On success returns `success()`. If the terminator is anything else, an
103/// error is emitted and `failure()` is returned. NOTE: callers in this
104/// file have typically already mutated IR (splitBlock / createBlock) by
105/// the time this is invoked, so the MLIR pattern rewriter contract
106/// requires them to still return `success()` from the surrounding
107/// pattern; the `failure()` here just signals "stop trying to wire up
108/// this region".
109static mlir::LogicalResult
110rewriteRegionExitToContinue(mlir::PatternRewriter &rewriter,
111 mlir::Region &region, mlir::Block *continueBlock,
112 llvm::StringRef regionDescription) {
113 mlir::Operation *terminator = region.back().getTerminator();
114 rewriter.setInsertionPointToEnd(&region.back());
115 if (auto yieldOp = mlir::dyn_cast<cir::YieldOp>(terminator)) {
116 rewriter.replaceOpWithNewOp<cir::BrOp>(
117 yieldOp, yieldOp.getArgs(), /*loop_id=*/nullptr, /*loop_hints=*/nullptr,
118 continueBlock);
119 return mlir::success();
120 }
121 if (isNonReturningTerminator(terminator))
122 return mlir::success();
123 terminator->emitError("unexpected terminator in ")
124 << regionDescription
125 << " region, expected yield, unreachable, or trap, got: "
126 << terminator->getName();
127 return mlir::failure();
128}
129
130struct CIRFlattenCFGPass : public impl::CIRFlattenCFGBase<CIRFlattenCFGPass> {
131
132 CIRFlattenCFGPass() = default;
133 void runOnOperation() override;
134};
135
136struct CIRIfFlattening : public mlir::OpRewritePattern<cir::IfOp> {
137 using OpRewritePattern<IfOp>::OpRewritePattern;
138
139 mlir::LogicalResult
140 matchAndRewrite(cir::IfOp ifOp,
141 mlir::PatternRewriter &rewriter) const override {
142 mlir::OpBuilder::InsertionGuard guard(rewriter);
143 mlir::Location loc = ifOp.getLoc();
144 bool emptyElse = ifOp.getElseRegion().empty();
145 mlir::Block *currentBlock = rewriter.getInsertionBlock();
146 mlir::Block *remainingOpsBlock =
147 rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
148 mlir::Block *continueBlock;
149 if (ifOp->getResults().empty())
150 continueBlock = remainingOpsBlock;
151 else
152 llvm_unreachable("NYI");
153
154 // Inline the region
155 mlir::Block *thenBeforeBody = &ifOp.getThenRegion().front();
156 mlir::Block *thenAfterBody = &ifOp.getThenRegion().back();
157 rewriter.inlineRegionBefore(ifOp.getThenRegion(), continueBlock);
158
159 rewriter.setInsertionPointToEnd(thenAfterBody);
160 if (auto thenYieldOp =
161 dyn_cast<cir::YieldOp>(thenAfterBody->getTerminator())) {
162 rewriter.replaceOpWithNewOp<cir::BrOp>(
163 thenYieldOp, thenYieldOp.getArgs(),
164 /*loop_id=*/nullptr, /*loop_hints=*/nullptr, continueBlock);
165 }
166
167 rewriter.setInsertionPointToEnd(continueBlock);
168
169 // Has else region: inline it.
170 mlir::Block *elseBeforeBody = nullptr;
171 mlir::Block *elseAfterBody = nullptr;
172 if (!emptyElse) {
173 elseBeforeBody = &ifOp.getElseRegion().front();
174 elseAfterBody = &ifOp.getElseRegion().back();
175 rewriter.inlineRegionBefore(ifOp.getElseRegion(), continueBlock);
176 } else {
177 elseBeforeBody = elseAfterBody = continueBlock;
178 }
179
180 rewriter.setInsertionPointToEnd(currentBlock);
181 cir::BrCondOp::create(rewriter, loc, ifOp.getCondition(), thenBeforeBody,
182 elseBeforeBody);
183
184 if (!emptyElse) {
185 rewriter.setInsertionPointToEnd(elseAfterBody);
186 if (auto elseYieldOP =
187 dyn_cast<cir::YieldOp>(elseAfterBody->getTerminator())) {
188 rewriter.replaceOpWithNewOp<cir::BrOp>(
189 elseYieldOP, elseYieldOP.getArgs(), /*loop_id=*/nullptr,
190 /*loop_hints=*/nullptr, continueBlock);
191 }
192 }
193
194 rewriter.replaceOp(ifOp, continueBlock->getArguments());
195 return mlir::success();
196 }
197};
198
199class CIRScopeOpFlattening : public mlir::OpRewritePattern<cir::ScopeOp> {
200public:
201 using OpRewritePattern<cir::ScopeOp>::OpRewritePattern;
202
203 mlir::LogicalResult
204 matchAndRewrite(cir::ScopeOp scopeOp,
205 mlir::PatternRewriter &rewriter) const override {
206 mlir::OpBuilder::InsertionGuard guard(rewriter);
207 mlir::Location loc = scopeOp.getLoc();
208
209 // Empty scope: just remove it.
210 // TODO: Remove this logic once CIR uses MLIR infrastructure to remove
211 // trivially dead operations. MLIR canonicalizer is too aggressive and we
212 // need to either (a) make sure all our ops model all side-effects and/or
213 // (b) have more options in the canonicalizer in MLIR to temper
214 // aggressiveness level.
215 if (scopeOp.isEmpty()) {
216 rewriter.eraseOp(scopeOp);
217 return mlir::success();
218 }
219
220 // Split the current block before the ScopeOp to create the inlining
221 // point.
222 mlir::Block *currentBlock = rewriter.getInsertionBlock();
223 mlir::Block *continueBlock =
224 rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
225 if (scopeOp.getNumResults() > 0)
226 continueBlock->addArguments(scopeOp.getResultTypes(), loc);
227
228 // Inline body region.
229 mlir::Block *beforeBody = &scopeOp.getScopeRegion().front();
230 mlir::Block *afterBody = &scopeOp.getScopeRegion().back();
231 rewriter.inlineRegionBefore(scopeOp.getScopeRegion(), continueBlock);
232
233 // Save stack and then branch into the body of the region.
234 rewriter.setInsertionPointToEnd(currentBlock);
236 cir::BrOp::create(rewriter, loc, mlir::ValueRange(), /*loop_id=*/nullptr,
237 /*loop_hints=*/nullptr, beforeBody);
238
239 // Replace the scopeop return with a branch that jumps out of the body.
240 // Stack restore before leaving the body region.
241 rewriter.setInsertionPointToEnd(afterBody);
242 if (auto yieldOp = dyn_cast<cir::YieldOp>(afterBody->getTerminator())) {
243 rewriter.replaceOpWithNewOp<cir::BrOp>(
244 yieldOp, yieldOp.getArgs(),
245 /*loop_id=*/nullptr, /*loop_hints=*/nullptr, continueBlock);
246 }
247
248 // Replace the op with values return from the body region.
249 rewriter.replaceOp(scopeOp, continueBlock->getArguments());
250
251 return mlir::success();
252 }
253};
254
255class CIRSwitchOpFlattening : public mlir::OpRewritePattern<cir::SwitchOp> {
256public:
257 using OpRewritePattern<cir::SwitchOp>::OpRewritePattern;
258
259 inline void rewriteYieldOp(mlir::PatternRewriter &rewriter,
260 cir::YieldOp yieldOp,
261 mlir::Block *destination) const {
262 rewriter.setInsertionPoint(yieldOp);
263 rewriter.replaceOpWithNewOp<cir::BrOp>(yieldOp, yieldOp.getOperands(),
264 /*loop_id=*/nullptr,
265 /*loop_hints=*/nullptr, destination);
266 }
267
268 // Return the new defaultDestination block.
269 Block *condBrToRangeDestination(cir::SwitchOp op,
270 mlir::PatternRewriter &rewriter,
271 mlir::Block *rangeDestination,
272 mlir::Block *defaultDestination,
273 const APInt &lowerBound,
274 const APInt &upperBound) const {
275 auto condType = mlir::cast<cir::IntType>(op.getCondition().getType());
276 bool isSigned = condType.isSigned();
277 assert(
278 (isSigned ? lowerBound.sle(upperBound) : lowerBound.ule(upperBound)) &&
279 "Invalid range");
280 mlir::Block *resBlock = rewriter.createBlock(defaultDestination);
281
282 // Build the range check at the switch operand's width. The classic
283 // `sub x, lo; ule (x - lo), (hi - lo)` idiom is a cyclic-distance test that
284 // is correct for both signed and unsigned switches, so the comparison is
285 // always unsigned.
286 cir::IntType uIntType =
287 cir::IntType::get(op.getContext(), condType.getWidth(),
288 /*isSigned=*/false);
289
290 cir::ConstantOp lowerBoundValue = cir::ConstantOp::create(
291 rewriter, op.getLoc(), cir::IntAttr::get(condType, lowerBound));
292 mlir::Value diffValue = cir::SubOp::create(
293 rewriter, op.getLoc(), op.getCondition(), lowerBoundValue);
294
295 // Use an unsigned comparison to check whether the condition is in range.
296 // cir.cmp takes its signedness from the operand type, so a signed
297 // difference needs a same-width integral cast to the unsigned type (a
298 // signedness reinterpretation) to make the `le` unsigned; an unsigned
299 // difference already has the right type.
300 if (isSigned)
301 diffValue = cir::CastOp::create(rewriter, op.getLoc(), uIntType,
302 CastKind::integral, diffValue);
303
304 cir::ConstantOp rangeLength = cir::ConstantOp::create(
305 rewriter, op.getLoc(),
306 cir::IntAttr::get(uIntType, upperBound - lowerBound));
307
308 cir::CmpOp cmpResult = cir::CmpOp::create(
309 rewriter, op.getLoc(), cir::CmpOpKind::le, diffValue, rangeLength);
310 cir::BrCondOp::create(rewriter, op.getLoc(), cmpResult, rangeDestination,
311 defaultDestination);
312 return resBlock;
313 }
314
315 mlir::LogicalResult
316 matchAndRewrite(cir::SwitchOp op,
317 mlir::PatternRewriter &rewriter) const override {
318 // All nested structured CIR ops must be flattened before the switch.
319 // Break statements inside nested structured ops would create branches to
320 // blocks outside those ops' regions, which is invalid. Fail the match so
321 // the pattern rewriter will process them first.
322 for (mlir::Region &region : op->getRegions())
323 if (hasNestedOpsToFlatten(region))
324 return mlir::failure();
325
326 // Empty switch statement: just erase it.
327 if (op.getBody().hasOneBlock() &&
328 op.getBody().front().without_terminator().empty()) {
329 rewriter.eraseOp(op);
330 return mlir::success();
331 }
332
333 llvm::SmallVector<CaseOp> cases;
334 op.collectCases(cases);
335
336 // Create exit block from the next node of cir.switch op.
337 mlir::Block *exitBlock = rewriter.splitBlock(
338 rewriter.getBlock(), op->getNextNode()->getIterator());
339
340 // We lower cir.switch op in the following process:
341 // 1. Inline the region from the switch op after switch op.
342 // 2. Traverse each cir.case op:
343 // a. Record the entry block, block arguments and condition for every
344 // case. b. Inline the case region after the case op.
345 // 3. Replace the empty cir.switch.op with the new cir.switchflat op by the
346 // recorded block and conditions.
347
348 // First we have to handle the rewrite of all of the 'break' ops to make
349 // sure they now go to the right place, including the ones in the pre-case
350 // blcoks.
351 walkRegionSkipping<cir::LoopOpInterface, cir::SwitchOp>(
352 op.getBody(), [&](mlir::Operation *op) {
353 if (!isa<cir::BreakOp>(op))
354 return mlir::WalkResult::advance();
355
356 lowerTerminator(op, exitBlock, rewriter);
357 return mlir::WalkResult::skip();
358 });
359
360 // inline everything from switch body between the switch op and the exit
361 // block.
362 {
363 cir::YieldOp switchYield = nullptr;
364 // Clear switch operation.
365 for (mlir::Block &block :
366 llvm::make_early_inc_range(op.getBody().getBlocks()))
367 if (auto yieldOp = dyn_cast<cir::YieldOp>(block.getTerminator()))
368 switchYield = yieldOp;
369
370 assert(!op.getBody().empty());
371 mlir::Block *originalBlock = op->getBlock();
372 mlir::Block *swopBlock =
373 rewriter.splitBlock(originalBlock, op->getIterator());
374 rewriter.inlineRegionBefore(op.getBody(), exitBlock);
375
376 if (switchYield)
377 rewriteYieldOp(rewriter, switchYield, exitBlock);
378
379 rewriter.setInsertionPointToEnd(originalBlock);
380 cir::BrOp::create(rewriter, op.getLoc(), swopBlock);
381 }
382
383 // Allocate required data structures (disconsider default case in
384 // vectors).
385 llvm::SmallVector<mlir::APInt, 8> caseValues;
386 llvm::SmallVector<mlir::Block *, 8> caseDestinations;
387 llvm::SmallVector<mlir::ValueRange, 8> caseOperands;
388
389 llvm::SmallVector<std::pair<APInt, APInt>> rangeValues;
390 llvm::SmallVector<mlir::Block *> rangeDestinations;
391 llvm::SmallVector<mlir::ValueRange> rangeOperands;
392
393 // Initialize default case as optional.
394 mlir::Block *defaultDestination = exitBlock;
395 mlir::ValueRange defaultOperands = exitBlock->getArguments();
396
397 // Digest the case statements values and bodies.
398 for (cir::CaseOp caseOp : cases) {
399 mlir::Region &region = caseOp.getCaseRegion();
400
401 // Found default case: save destination and operands.
402 switch (caseOp.getKind()) {
403 case cir::CaseOpKind::Default:
404 defaultDestination = &region.front();
405 defaultOperands = defaultDestination->getArguments();
406 break;
407 case cir::CaseOpKind::Range:
408 assert(caseOp.getValue().size() == 2 &&
409 "Case range should have 2 case value");
410 rangeValues.push_back(
411 {cast<cir::IntAttr>(caseOp.getValue()[0]).getValue(),
412 cast<cir::IntAttr>(caseOp.getValue()[1]).getValue()});
413 rangeDestinations.push_back(&region.front());
414 rangeOperands.push_back(rangeDestinations.back()->getArguments());
415 break;
416 case cir::CaseOpKind::Anyof:
417 case cir::CaseOpKind::Equal:
418 // AnyOf cases kind can have multiple values, hence the loop below.
419 for (const mlir::Attribute &value : caseOp.getValue()) {
420 caseValues.push_back(cast<cir::IntAttr>(value).getValue());
421 caseDestinations.push_back(&region.front());
422 caseOperands.push_back(caseDestinations.back()->getArguments());
423 }
424 break;
425 }
426
427 // Track fallthrough in cases.
428 for (mlir::Block &blk : region.getBlocks()) {
429 if (blk.getNumSuccessors())
430 continue;
431
432 if (auto yieldOp = dyn_cast<cir::YieldOp>(blk.getTerminator())) {
433 mlir::Operation *nextOp = caseOp->getNextNode();
434 assert(nextOp && "caseOp is not expected to be the last op");
435 mlir::Block *oldBlock = nextOp->getBlock();
436 mlir::Block *newBlock =
437 rewriter.splitBlock(oldBlock, nextOp->getIterator());
438 rewriter.setInsertionPointToEnd(oldBlock);
439 cir::BrOp::create(rewriter, nextOp->getLoc(), mlir::ValueRange(),
440 /*loop_id=*/nullptr, /*loop_hints=*/nullptr,
441 newBlock);
442 rewriteYieldOp(rewriter, yieldOp, newBlock);
443 }
444 }
445
446 mlir::Block *oldBlock = caseOp->getBlock();
447 mlir::Block *newBlock =
448 rewriter.splitBlock(oldBlock, caseOp->getIterator());
449
450 mlir::Block &entryBlock = caseOp.getCaseRegion().front();
451 rewriter.inlineRegionBefore(caseOp.getCaseRegion(), newBlock);
452
453 // Create a branch to the entry of the inlined region.
454 rewriter.setInsertionPointToEnd(oldBlock);
455 cir::BrOp::create(rewriter, caseOp.getLoc(), &entryBlock);
456 }
457
458 // Remove all cases since we've inlined the regions.
459 for (cir::CaseOp caseOp : cases) {
460 mlir::Block *caseBlock = caseOp->getBlock();
461 // Erase the block with no predecessors here to make the generated code
462 // simpler a little bit.
463 if (caseBlock->hasNoPredecessors())
464 rewriter.eraseBlock(caseBlock);
465 else
466 rewriter.eraseOp(caseOp);
467 }
468
469 bool isSigned =
470 mlir::cast<cir::IntType>(op.getCondition().getType()).isSigned();
471 for (auto [rangeVal, operand, destination] :
472 llvm::zip(rangeValues, rangeOperands, rangeDestinations)) {
473 APInt lowerBound = rangeVal.first;
474 APInt upperBound = rangeVal.second;
475
476 // An empty range (lo > hi in the switch's signedness) is unreachable.
477 if (isSigned ? lowerBound.sgt(upperBound) : lowerBound.ugt(upperBound))
478 continue;
479
480 // If range is small, add multiple switch instruction cases.
481 // This magical number is from the original CGStmt code.
482 constexpr uint64_t kSmallRangeThreshold = 64;
483 APInt rangeSize = upperBound - lowerBound;
484 if (rangeSize.ult(kSmallRangeThreshold)) {
485 // Expand into individual cases. rangeSize < kSmallRangeThreshold, so
486 // the inclusive case count (rangeSize + 1) fits in a uint64_t. Drive
487 // termination by the count rather than by comparing caseValue to
488 // upperBound: when upperBound is the type's maximum the final
489 // caseValue++ wraps past the top, which is harmless because the
490 // wrapped value is never used.
491 APInt caseValue = lowerBound;
492 for (uint64_t n = rangeSize.getZExtValue() + 1; n != 0; --n) {
493 caseValues.push_back(caseValue++);
494 caseOperands.push_back(operand);
495 caseDestinations.push_back(destination);
496 }
497 continue;
498 }
499
500 defaultDestination =
501 condBrToRangeDestination(op, rewriter, destination,
502 defaultDestination, lowerBound, upperBound);
503 defaultOperands = operand;
504 }
505
506 // Set switch op to branch to the newly created blocks.
507 rewriter.setInsertionPoint(op);
508 rewriter.replaceOpWithNewOp<cir::SwitchFlatOp>(
509 op, op.getCondition(), defaultDestination, defaultOperands, caseValues,
510 caseDestinations, caseOperands);
511
512 return mlir::success();
513 }
514};
515
516class CIRLoopOpInterfaceFlattening
517 : public mlir::OpInterfaceRewritePattern<cir::LoopOpInterface> {
518public:
519 using mlir::OpInterfaceRewritePattern<
520 cir::LoopOpInterface>::OpInterfaceRewritePattern;
521
522 inline void lowerConditionOp(cir::ConditionOp op, mlir::Block *body,
523 mlir::Block *exit,
524 mlir::PatternRewriter &rewriter,
525 mlir::DistinctAttr loopId = {},
526 cir::LoopHintAttr loopHints = {}) const {
527 mlir::OpBuilder::InsertionGuard guard(rewriter);
528 rewriter.setInsertionPoint(op);
529 rewriter.replaceOpWithNewOp<cir::BrCondOp>(
530 op, op.getCondition(), mlir::ValueRange{}, mlir::ValueRange{}, loopId,
531 loopHints, body, exit);
532 }
533
534 // Rewrite a loop that has a per-iteration cleanup region into a loop whose
535 // condition is always 'true' and whose body is a cir.cleanup.scope enclosing
536 // the original condition, body, and step regions.
537 //
538 // The condition test is sunk to the top of the scope body (a false
539 // result becomes a cir.break out of the loop) and, for a for loop, the step
540 // is appended to the end of the scope body. The loop's cleanup region becomes
541 // the cleanup region of a cir.cleanup.scope enclosing the new body.
542 //
543 // For example, a while loop:
544 //
545 // cir.while { <cond>; cir.condition(%c) }
546 // do { <body> }
547 // cleanup all { <cleanup> }
548 //
549 // becomes:
550 //
551 // cir.while { cir.condition(%true) } do {
552 // cir.cleanup.scope {
553 // <cond>
554 // cir.brcond %c ^body, ^cond_false
555 // ^cond_false:
556 // cir.break
557 // ^body:
558 // <body>
559 // } cleanup all { <cleanup> }
560 // cir.yield
561 // }
562 //
563 // A for loop is the same, except the step region is appended to the scope
564 // body (after the body) and both the body's normal end and any continue are
565 // redirected into the step so that the step runs before the cleanup. The init
566 // section of a for loop is already outside the loop op.
567 mlir::LogicalResult
568 rewriteLoopWithCleanup(cir::LoopOpInterface op,
569 mlir::PatternRewriter &rewriter) const {
570 mlir::Location loc = op.getLoc();
571 mlir::Region *stepRegion = op.maybeGetStep();
572
573 cir::CleanupKindAttr cleanupKind = op.maybeGetCleanupKind();
574 assert(cleanupKind && "loop cleanup region without a cleanup kind");
575
576 mlir::Region &condRegion = op.getCond();
577 mlir::Region &bodyRegion = op.getBody();
578 mlir::Region &cleanupRegion = *op.maybeGetCleanup();
579
580 // The cir.condition is the terminator of the condition region's last block
581 // (there can be more than one block if a nested scope was already
582 // flattened within the condition).
583 auto conditionOp =
584 cast<cir::ConditionOp>(condRegion.back().getTerminator());
585 mlir::Value condVal = conditionOp.getCondition();
586
587 // Capture block references and exit terminators before moving blocks
588 // around. Block pointers stay valid across region inlining because blocks
589 // are reparented, not recreated.
590 mlir::Block *bodyFront = &bodyRegion.front();
591 mlir::Block *stepFront = stepRegion ? &stepRegion->front() : nullptr;
592
593 // For a for loop the body's normal end and any continue must run the step
594 // before the cleanup, so collect them to redirect into the step region. For
595 // a while loop they are genuine cleanup-scope exits and are left untouched.
596 llvm::SmallVector<cir::YieldOp> bodyYieldsToStep;
597 llvm::SmallVector<cir::ContinueOp> continuesToStep;
598 if (stepRegion) {
599 for (mlir::Block &blk : bodyRegion.getBlocks())
600 if (auto y = dyn_cast<cir::YieldOp>(blk.getTerminator()))
601 bodyYieldsToStep.push_back(y);
602 op.walkBodySkippingNestedLoops([&](mlir::Operation *o) {
603 if (auto c = dyn_cast<cir::ContinueOp>(o)) {
604 continuesToStep.push_back(c);
605 return mlir::WalkResult::skip();
606 }
607 return mlir::WalkResult::advance();
608 });
609 }
610
611 // Assemble the per-iteration blocks into the condition region, in
612 // execution order: [ cond blocks..., body blocks..., step blocks... ].
613 rewriter.inlineRegionBefore(bodyRegion, condRegion, condRegion.end());
614 if (stepRegion)
615 rewriter.inlineRegionBefore(*stepRegion, condRegion, condRegion.end());
616
617 // Replace cir.condition(%c) with a conditional branch into the body whose
618 // false edge breaks out of the loop.
619 mlir::Block *breakBlock =
620 rewriter.createBlock(&condRegion, condRegion.end());
621 rewriter.setInsertionPointToEnd(breakBlock);
622 cir::BreakOp::create(rewriter, conditionOp.getLoc());
623
624 rewriter.setInsertionPoint(conditionOp);
625 rewriter.replaceOpWithNewOp<cir::BrCondOp>(conditionOp, condVal, bodyFront,
626 breakBlock);
627
628 // For a for loop, redirect the body's normal end and any continue to the
629 // step. The step's own yield remains the normal end of the iteration.
630 for (cir::YieldOp y : bodyYieldsToStep)
631 lowerTerminator(y, stepFront, rewriter);
632 for (cir::ContinueOp c : continuesToStep)
633 lowerTerminator(c, stepFront, rewriter);
634
635 // Build the trivial loop body: a single cir.cleanup.scope followed by a
636 // yield, in a fresh block (the body region's blocks were moved out above).
637 mlir::Block *newBodyBlock = rewriter.createBlock(&bodyRegion);
638 rewriter.setInsertionPointToEnd(newBodyBlock);
639 auto emitYield = [](mlir::OpBuilder &b, mlir::Location l) {
640 cir::YieldOp::create(b, l);
641 };
642 auto scope = cir::CleanupScopeOp::create(
643 rewriter, loc, cleanupKind.getValue(), emitYield, emitYield);
644 cir::YieldOp::create(rewriter, loc);
645
646 // Move the assembled per-iteration blocks into the scope's body region and
647 // the loop's cleanup blocks into the scope's cleanup region, discarding the
648 // placeholder blocks the builder created.
649 mlir::Block *bodyPlaceholder = &scope.getBodyRegion().front();
650 rewriter.inlineRegionBefore(condRegion, bodyPlaceholder);
651 rewriter.eraseBlock(bodyPlaceholder);
652
653 mlir::Block *cleanupPlaceholder = &scope.getCleanupRegion().front();
654 rewriter.inlineRegionBefore(cleanupRegion, cleanupPlaceholder);
655 rewriter.eraseBlock(cleanupPlaceholder);
656
657 // Rebuild the loop condition region with an always-true condition.
658 mlir::Block *newCondBlock = rewriter.createBlock(&condRegion);
659 rewriter.setInsertionPointToEnd(newCondBlock);
660 mlir::Value trueVal = cir::ConstantOp::create(
661 rewriter, loc, cir::BoolAttr::get(rewriter.getContext(), true));
662 cir::ConditionOp::create(rewriter, loc, trueVal);
663
664 // For a for loop, rebuild the (now empty) step region with a trivial yield.
665 if (stepRegion) {
666 mlir::Block *newStepBlock = rewriter.createBlock(stepRegion);
667 rewriter.setInsertionPointToEnd(newStepBlock);
668 cir::YieldOp::create(rewriter, loc);
669 }
670
671 // Drop the cleanup-kind attribute now that the loop's cleanup region is
672 // empty, so the trivial loop verifies and takes the no-cleanup flattening
673 // path when the greedy driver revisits it.
674 if (auto whileOp = mlir::dyn_cast<cir::WhileOp>(op.getOperation()))
675 whileOp.removeCleanupKindAttr();
676 else if (auto forOp = mlir::dyn_cast<cir::ForOp>(op.getOperation()))
677 forOp.removeCleanupKindAttr();
678
679 return mlir::success();
680 }
681
682 mlir::LogicalResult
683 matchAndRewrite(cir::LoopOpInterface op,
684 mlir::PatternRewriter &rewriter) const final {
685 // All nested structured CIR ops must be flattened before the loop.
686 // Break/continue statements inside nested structured ops would create
687 // branches to blocks outside those ops' regions, which is invalid. Fail
688 // the match so the pattern rewriter will process them first.
689 for (mlir::Region &region : op->getRegions())
690 if (hasNestedOpsToFlatten(region))
691 return mlir::failure();
692
693 // Loops with a per-iteration cleanup region need every iteration-exit edge
694 // routed through that cleanup, including the false-condition exit and any
695 // exceptions that might be thrown from the step region. Rather than trying
696 // to figure out all of the cleanup routing here, we sink the condition into
697 // the body region, hoist the step region (if any) and create a new
698 // cir.cleanup.scope enclosing the body region. A subsequent sweep of the
699 // pass will flatten the cir.cleanup.scope and the loop reusing the normal
700 // handlers.
701 if (op.maybeGetCleanup())
702 return rewriteLoopWithCleanup(op, rewriter);
703
704 // Setup CFG blocks.
705 mlir::Block *entry = rewriter.getInsertionBlock();
706 mlir::Block *exit =
707 rewriter.splitBlock(entry, rewriter.getInsertionPoint());
708 mlir::Block *cond = &op.getCond().front();
709 mlir::Block *body = &op.getBody().front();
710 mlir::Block *step =
711 (op.maybeGetStep() ? &op.maybeGetStep()->front() : nullptr);
712
713 // Setup loop entry branch.
714 mlir::Block *firstBlock = &op.getEntry().front();
715 rewriter.setInsertionPointToEnd(entry);
716 cir::BrOp::create(rewriter, op.getLoc(), firstBlock);
717
718 // We attach the op's loop hints to the ops that break back to the
719 // 'beginning' of the operation. For a while/for it is the condition block,
720 // for a do-while it is the top of the body. All of those ops also get the
721 // loop's id, so that they can be told apart from other loops' hints.
722 cir::LoopHintAttr loopHints = op.getLoopHintsAttr();
723 mlir::DistinctAttr loopId = op.getLoopIdAttr();
724 auto idFor = [&](mlir::Block *target) {
725 return target == firstBlock ? loopId : mlir::DistinctAttr{};
726 };
727 auto hintsFor = [&](mlir::Block *target) {
728 return target == firstBlock ? loopHints : cir::LoopHintAttr{};
729 };
730
731 // Branch from condition region to body or exit. The ConditionOp may not
732 // be in the first block of the condition region if a cleanup scope was
733 // already flattened within it, introducing multiple blocks. The
734 // ConditionOp is always the terminator of the last block.
735 auto conditionOp =
736 cast<cir::ConditionOp>(op.getCond().back().getTerminator());
737 lowerConditionOp(conditionOp, body, exit, rewriter, idFor(body),
738 hintsFor(body));
739
740 // TODO(cir): Remove the walks below. It visits operations unnecessarily.
741 // However, to solve this we would likely need a custom DialectConversion
742 // driver to customize the order that operations are visited.
743
744 // Lower continue statements.
745 mlir::Block *dest = (step ? step : cond);
746 op.walkBodySkippingNestedLoops([&](mlir::Operation *op) {
747 if (!isa<cir::ContinueOp>(op))
748 return mlir::WalkResult::advance();
749
750 lowerTerminator(op, dest, rewriter, idFor(dest), hintsFor(dest));
751 return mlir::WalkResult::skip();
752 });
753
754 // Lower break statements.
755 walkRegionSkipping<cir::LoopOpInterface, cir::SwitchOp>(
756 op.getBody(), [&](mlir::Operation *op) {
757 if (!isa<cir::BreakOp>(op))
758 return mlir::WalkResult::advance();
759
760 lowerTerminator(op, exit, rewriter);
761 return mlir::WalkResult::skip();
762 });
763
764 // Lower optional body region yield.
765 for (mlir::Block &blk : op.getBody().getBlocks()) {
766 auto bodyYield = dyn_cast<cir::YieldOp>(blk.getTerminator());
767 if (bodyYield)
768 lowerTerminator(bodyYield, dest, rewriter, idFor(dest), hintsFor(dest));
769 }
770
771 // Lower mandatory step region yield. Like the condition region, the
772 // YieldOp may be in the last block rather than the first if a cleanup
773 // scope was already flattened within the step region.
774 if (step)
775 lowerTerminator(
776 cast<cir::YieldOp>(op.maybeGetStep()->back().getTerminator()), cond,
777 rewriter, idFor(cond), hintsFor(cond));
778
779 // Move region contents out of the loop op.
780 rewriter.inlineRegionBefore(op.getCond(), exit);
781 rewriter.inlineRegionBefore(op.getBody(), exit);
782 if (step)
783 rewriter.inlineRegionBefore(*op.maybeGetStep(), exit);
784
785 rewriter.eraseOp(op);
786 return mlir::success();
787 }
788};
789
790class CIRTernaryOpFlattening : public mlir::OpRewritePattern<cir::TernaryOp> {
791public:
792 using OpRewritePattern<cir::TernaryOp>::OpRewritePattern;
793
794 mlir::LogicalResult
795 matchAndRewrite(cir::TernaryOp op,
796 mlir::PatternRewriter &rewriter) const override {
797 Location loc = op->getLoc();
798 Block *condBlock = rewriter.getInsertionBlock();
799 Block::iterator opPosition = rewriter.getInsertionPoint();
800 Block *remainingOpsBlock = rewriter.splitBlock(condBlock, opPosition);
801 llvm::SmallVector<mlir::Location, 2> locs;
802 // Ternary result is optional, make sure to populate the location only
803 // when relevant.
804 if (op->getResultTypes().size())
805 locs.push_back(loc);
806 Block *continueBlock =
807 rewriter.createBlock(remainingOpsBlock, op->getResultTypes(), locs);
808 cir::BrOp::create(rewriter, loc, remainingOpsBlock);
809
810 Region &trueRegion = op.getTrueRegion();
811 Block *trueBlock = &trueRegion.front();
812 // Wire up the true region's exit (cir.yield -> br, cir.unreachable /
813 // cir.trap kept as-is). IR has already been modified by splitBlock /
814 // createBlock above, so per the MLIR pattern rewriter contract we must
815 // still return success() if the terminator turns out to be unexpected.
816 if (failed(rewriteRegionExitToContinue(rewriter, trueRegion, continueBlock,
817 "ternary true")))
818 return mlir::success();
819 rewriter.inlineRegionBefore(trueRegion, continueBlock);
820
821 Block *falseBlock = continueBlock;
822 Region &falseRegion = op.getFalseRegion();
823
824 falseBlock = &falseRegion.front();
825 if (failed(rewriteRegionExitToContinue(rewriter, falseRegion, continueBlock,
826 "ternary false")))
827 return mlir::success();
828 rewriter.inlineRegionBefore(falseRegion, continueBlock);
829
830 rewriter.setInsertionPointToEnd(condBlock);
831 cir::BrCondOp::create(rewriter, loc, op.getCond(), trueBlock, falseBlock);
832
833 rewriter.replaceOp(op, continueBlock->getArguments());
834
835 // Ok, we're done!
836 return mlir::success();
837 }
838};
839
840// Get or create the cleanup destination slot for a function. This slot is
841// shared across all cleanup scopes in the function to track which exit path
842// to take after running cleanup code when there are multiple exits.
843static cir::AllocaOp getOrCreateCleanupDestSlot(cir::FuncOp funcOp,
844 mlir::PatternRewriter &rewriter,
845 mlir::Location loc) {
846 mlir::Block &entryBlock = funcOp.getBody().front();
847
848 // Look for an existing cleanup dest slot in the entry block.
849 auto it = llvm::find_if(entryBlock, [](auto &op) {
850 return mlir::isa<AllocaOp>(&op) &&
851 mlir::cast<AllocaOp>(&op).getCleanupDestSlot();
852 });
853 if (it != entryBlock.end())
854 return mlir::cast<cir::AllocaOp>(*it);
855
856 // Create a new cleanup dest slot at the start of the entry block.
857 mlir::OpBuilder::InsertionGuard guard(rewriter);
858 rewriter.setInsertionPointToStart(&entryBlock);
859 cir::IntType s32Type =
860 cir::IntType::get(rewriter.getContext(), 32, /*isSigned=*/true);
861 cir::CIRDataLayout dataLayout(funcOp->getParentOfType<mlir::ModuleOp>());
862 cir::PointerType ptrToS32Type = cir::PointerType::get(
863 s32Type, dataLayout.getAllocaAddrSpace(rewriter.getContext()));
864 uint64_t alignment = dataLayout.getAlignment(s32Type, true).value();
865 auto allocaOp = cir::AllocaOp::create(
866 rewriter, loc, ptrToS32Type, "__cleanup_dest_slot",
867 /*alignment=*/rewriter.getI64IntegerAttr(alignment));
868 allocaOp.setCleanupDestSlot(true);
869 return allocaOp;
870}
871
872/// Shared EH flattening utilities used by both CIRCleanupScopeOpFlattening
873/// and CIRTryOpFlattening.
874
875// Lifetime markers participate in existing EH paths, but do not require an
876// unwind edge on their own. Conservatively treat other cleanup code as real
877// cleanup, including regions with control flow.
878static bool isLifetimeMarkerOnly(mlir::Region &region) {
879 return llvm::hasSingleElement(region) &&
880 llvm::any_of(
881 region.front(),
882 [](mlir::Operation &op) { return isa<cir::LifetimeEndOp>(op); }) &&
883 llvm::all_of(region.front(), [](mlir::Operation &op) {
884 return isa<cir::LifetimeEndOp, cir::YieldOp>(op);
885 });
886}
887
888// Look for an enclosing handler or non-marker EH cleanup that protects this
889// operation. Follow regions, rather than just parent ops: a try does not catch
890// exceptions from its own handlers, and a cleanup does not protect itself.
891static bool hasEnclosingEHRequirement(mlir::Operation *op) {
892 for (mlir::Region *region = op->getParentRegion(); region;
893 region = region->getParentRegion()) {
894 mlir::Operation *parent = region->getParentOp();
895 if (!parent || isa<cir::FuncOp>(parent))
896 break;
897 if (auto tryOp = dyn_cast<cir::TryOp>(parent)) {
898 auto handlers = tryOp.getHandlerTypesAttr();
899 if (region == &tryOp.getTryRegion() && handlers &&
900 llvm::any_of(handlers, [](mlir::Attribute handler) {
901 return !isa<cir::UnwindAttr>(handler);
902 }))
903 return true;
904 } else if (auto cleanupOp = dyn_cast<cir::CleanupScopeOp>(parent)) {
905 if (region == &cleanupOp.getBodyRegion() &&
906 cleanupOp.getCleanupKindAttr().isEH() &&
907 !isLifetimeMarkerOnly(cleanupOp.getCleanupRegion()))
908 return true;
909 } else if (auto loopOp = dyn_cast<cir::LoopOpInterface>(parent)) {
910 // The enclosing loop may not yet have been rewritten to a cleanup scope.
911 mlir::Region *cleanup = loopOp.maybeGetCleanup();
912 if (cleanup && region != cleanup && loopOp.maybeGetCleanupKind().isEH() &&
913 !isLifetimeMarkerOnly(*cleanup))
914 return true;
915 }
916 }
917 return false;
918}
919
920// Collect all function calls in a region that may throw exceptions and need
921// to be replaced with try_call operations. Skips calls marked nothrow.
922// Nested cleanup scopes and try ops are always flattened before their
923// enclosing parents, so there are no nested regions to skip here.
924static void
925collectThrowingCalls(mlir::Region &region,
926 llvm::SmallVectorImpl<cir::CallOp> &callsToRewrite) {
927 region.walk([&](cir::CallOp callOp) {
928 // A musttail call is never an invoke. LLVM has no musttail invoke, and
929 // the tail call replaces the frame the cleanup would have run in, so
930 // there is nothing left to unwind through.
931 if (!callOp.getNothrow() && !callOp.getMusttail())
932 callsToRewrite.push_back(callOp);
933 });
934}
935
936// Collect all cir.throw operations in a region that need to be replaced
937// with cir.try_throw operations so they can unwind through an enclosing
938// cleanup or catch handler. Nested cleanup scopes and try ops are always
939// flattened before their enclosing parents, so there are no nested
940// regions to skip here.
941static void
942collectThrows(mlir::Region &region,
943 llvm::SmallVectorImpl<cir::ThrowOp> &throwsToRewrite) {
944 region.walk(
945 [&](cir::ThrowOp throwOp) { throwsToRewrite.push_back(throwOp); });
946}
947
948// Collect all cir.resume operations in a region that come from
949// already-flattened try or cleanup scope operations. These resume ops need
950// to be chained through this scope's EH handler instead of unwinding
951// directly to the caller. Nested cleanup scopes and try ops are always
952// flattened before their enclosing parents, so there are no nested regions
953// to skip here.
954static void collectResumeOps(mlir::Region &region,
956 region.walk([&](cir::ResumeOp resumeOp) { resumeOps.push_back(resumeOp); });
957}
958
959// Create a shared unwind destination block. The block contains a
960// cir.eh.initiate operation (optionally with the cleanup attribute) and a
961// branch to the given destination block, passing the eh_token.
962static mlir::Block *buildUnwindBlock(mlir::Block *dest, bool isCleanupOnly,
963 mlir::Location loc,
964 mlir::Block *insertBefore,
965 mlir::PatternRewriter &rewriter) {
966 mlir::Block *unwindBlock = rewriter.createBlock(insertBefore);
967 rewriter.setInsertionPointToEnd(unwindBlock);
968 auto ehInitiate =
969 cir::EhInitiateOp::create(rewriter, loc, /*cleanup=*/isCleanupOnly);
970 cir::BrOp::create(rewriter, loc, mlir::ValueRange{ehInitiate.getEhToken()},
971 /*loop_id=*/nullptr, /*loop_hints=*/nullptr, dest);
972 return unwindBlock;
973}
974
975// Create a shared terminate unwind block for throwing calls in EH cleanup
976// regions. When an exception is thrown during cleanup (unwinding), the C++
977// standard requires that std::terminate() be called.
978static mlir::Block *buildTerminateUnwindBlock(mlir::Location loc,
979 mlir::Block *insertBefore,
980 mlir::PatternRewriter &rewriter) {
981 mlir::Block *terminateBlock = rewriter.createBlock(insertBefore);
982 rewriter.setInsertionPointToEnd(terminateBlock);
983 auto ehInitiate = cir::EhInitiateOp::create(rewriter, loc, /*cleanup=*/false);
984 cir::EhTerminateOp::create(rewriter, loc, ehInitiate.getEhToken());
985 return terminateBlock;
986}
987
988class CIRCleanupScopeOpFlattening
989 : public mlir::OpRewritePattern<cir::CleanupScopeOp> {
990public:
991 using OpRewritePattern<cir::CleanupScopeOp>::OpRewritePattern;
992
993 struct CleanupExit {
994 // An operation that exits the cleanup scope (yield, break, continue,
995 // return, etc.)
996 mlir::Operation *exitOp;
997
998 // A unique identifier for this exit's destination (used for switch dispatch
999 // when there are multiple exits).
1000 int destinationId;
1001
1002 CleanupExit(mlir::Operation *op, int id) : exitOp(op), destinationId(id) {}
1003 };
1004
1005 // Determine whether a goto operation transfers control to a label that
1006 // exists somewhere inside the given region (or any of its nested regions).
1007 // Label names are unique within a function, so finding a matching cir.label
1008 // inside the region implies that the goto definitely targets that label and
1009 // therefore stays within the region. If no match is found, the goto either
1010 // exits the region or its target is unknown; in either case the caller must
1011 // treat it as exiting the region.
1012 static bool gotoTargetsLabelInRegion(cir::GotoOp gotoOp,
1013 mlir::Region &region) {
1014 llvm::StringRef targetLabel = gotoOp.getLabel();
1015 return region
1016 .walk([&](cir::LabelOp labelOp) {
1017 if (labelOp.getLabel() == targetLabel)
1018 return mlir::WalkResult::interrupt();
1019 return mlir::WalkResult::advance();
1020 })
1021 .wasInterrupted();
1022 }
1023
1024 // Collect all operations that exit a cleanup scope body. Return, goto, break,
1025 // and continue can all require branches through the cleanup region. When a
1026 // loop is encountered, only return and goto are collected because break and
1027 // continue are handled by the loop and stay within the cleanup scope. When a
1028 // switch is encountered, return, goto and continue are collected because they
1029 // may all branch through the cleanup, but break is local to the switch. When
1030 // a nested cleanup scope is encountered, we recursively collect exits since
1031 // any return, goto, break, or continue from the nested cleanup will also
1032 // branch through the outer cleanup.
1033 //
1034 // A goto is only treated as an exit if its target label is not somewhere
1035 // inside the cleanup body region. Gotos whose target label is within the
1036 // cleanup body stay inside the cleanup scope and need no special handling
1037 // during flattening; they are simply inlined along with the rest of the
1038 // body region.
1039 //
1040 // A musttail return is likewise not an exit. It leaves the function
1041 // directly without running any cleanup, which is what the tail call means:
1042 // the frame those cleanups would run in no longer exists. CIRGen only emits
1043 // one when every cleanup it skips is redundant before a return.
1044 //
1045 // This function assigns unique destination IDs to each exit, which are
1046 // used when multi-exit cleanup scopes are flattened.
1047 void collectExits(mlir::Region &cleanupBodyRegion,
1048 llvm::SmallVectorImpl<CleanupExit> &exits,
1049 int &nextId) const {
1050 // Collect yield terminators from the body region. We do this separately
1051 // because yields in nested operations, including those in nested cleanup
1052 // scopes, won't branch through the outer cleanup region.
1053 for (mlir::Block &block : cleanupBodyRegion) {
1054 auto *terminator = block.getTerminator();
1055 if (isa<cir::YieldOp>(terminator))
1056 exits.emplace_back(terminator, nextId++);
1057 }
1058
1059 // Helper to decide whether an op is a goto that needs to be treated as an
1060 // exit from the cleanup scope being flattened. If op is a goto and targets
1061 // a label inside the cleanup body region, control stays within the cleanup
1062 // and we leave the goto in place.
1063 auto isGotoThatExitsCleanup = [&](mlir::Operation *op) {
1064 auto gotoOp = dyn_cast<cir::GotoOp>(op);
1065 return gotoOp && !gotoTargetsLabelInRegion(gotoOp, cleanupBodyRegion);
1066 };
1067
1068 // Helper to decide whether a return branches through the cleanup. The
1069 // return that completes a musttail call does not. LLVM requires the two
1070 // to stay adjacent, so it cannot be rewritten into a store and a branch
1071 // to the cleanup. CIRGen emits the pair adjacently in the same block, and
1072 // the predecessor is only ever a cir.call, never a cir.try_call, since a
1073 // try_call terminates its block and so cannot be followed by a return.
1074 auto isReturnThatExitsCleanup = [](mlir::Operation *op) {
1075 if (!isa<cir::ReturnOp>(op))
1076 return false;
1077 auto callOp = dyn_cast_if_present<cir::CallOp>(op->getPrevNode());
1078 return !callOp || !callOp.getMusttail();
1079 };
1080
1081 // Lambda to walk a loop and collect only returns and gotos.
1082 // Break and continue inside loops are handled by the loop itself.
1083 // Loops don't require special handling for nested switch or cleanup scopes
1084 // because break and continue never branch out of the loop.
1085 auto collectExitsInLoop = [&](mlir::Operation *loopOp) {
1086 loopOp->walk<mlir::WalkOrder::PreOrder>([&](mlir::Operation *nestedOp) {
1087 if (isa<cir::CoroCleanupOp>(nestedOp) ||
1088 isReturnThatExitsCleanup(nestedOp) ||
1089 isGotoThatExitsCleanup(nestedOp)) {
1090 exits.emplace_back(nestedOp, nextId++);
1091 }
1092 return mlir::WalkResult::advance();
1093 });
1094 };
1095
1096 // Forward declaration for mutual recursion.
1097 std::function<void(mlir::Region &, bool)> collectExitsInCleanup;
1098 std::function<void(mlir::Operation *)> collectExitsInSwitch;
1099
1100 // Lambda to collect exits from a switch. Collects return/goto/continue but
1101 // not break (handled by switch). For nested loops/cleanups, recurses.
1102 collectExitsInSwitch = [&](mlir::Operation *switchOp) {
1103 switchOp->walk<mlir::WalkOrder::PreOrder>([&](mlir::Operation *nestedOp) {
1104 if (isa<cir::CleanupScopeOp>(nestedOp)) {
1105 // Walk the nested cleanup, but ignore break statements because they
1106 // will be handled by the switch we are currently walking.
1107 collectExitsInCleanup(
1108 cast<cir::CleanupScopeOp>(nestedOp).getBodyRegion(),
1109 /*ignoreBreak=*/true);
1110 return mlir::WalkResult::skip();
1111 } else if (isa<cir::LoopOpInterface>(nestedOp)) {
1112 collectExitsInLoop(nestedOp);
1113 return mlir::WalkResult::skip();
1114 } else if (isa<cir::ContinueOp, cir::CoReturnOp, cir::CoroCleanupOp>(
1115 nestedOp) ||
1116 isReturnThatExitsCleanup(nestedOp) ||
1117 isGotoThatExitsCleanup(nestedOp)) {
1118 exits.emplace_back(nestedOp, nextId++);
1119 }
1120 return mlir::WalkResult::advance();
1121 });
1122 };
1123
1124 // Lambda to collect exits from a cleanup scope body region. This collects
1125 // break (optionally), continue, return, and goto, handling nested loops,
1126 // switches, and cleanups appropriately.
1127 collectExitsInCleanup = [&](mlir::Region &region, bool ignoreBreak) {
1128 region.walk<mlir::WalkOrder::PreOrder>([&](mlir::Operation *op) {
1129 // We need special handling for break statements because if this cleanup
1130 // scope was nested within a switch op, break will be handled by the
1131 // switch operation and therefore won't exit the cleanup scope enclosing
1132 // the switch. We're only collecting exits from the cleanup that started
1133 // this walk. Exits from nested cleanups will be handled when we flatten
1134 // the nested cleanup.
1135 if (!ignoreBreak && isa<cir::BreakOp>(op)) {
1136 exits.emplace_back(op, nextId++);
1137 } else if (isa<cir::ContinueOp, cir::CoReturnOp, cir::CoroCleanupOp>(
1138 op) ||
1139 isReturnThatExitsCleanup(op) || isGotoThatExitsCleanup(op)) {
1140 exits.emplace_back(op, nextId++);
1141 } else if (isa<cir::CleanupScopeOp>(op)) {
1142 // Recurse into nested cleanup's body region.
1143 collectExitsInCleanup(cast<cir::CleanupScopeOp>(op).getBodyRegion(),
1144 /*ignoreBreak=*/ignoreBreak);
1145 return mlir::WalkResult::skip();
1146 } else if (isa<cir::LoopOpInterface>(op)) {
1147 // This kicks off a separate walk rather than continuing to dig deeper
1148 // in the current walk because we need to handle break and continue
1149 // differently inside loops.
1150 collectExitsInLoop(op);
1151 return mlir::WalkResult::skip();
1152 } else if (isa<cir::SwitchOp>(op)) {
1153 // This kicks off a separate walk rather than continuing to dig deeper
1154 // in the current walk because we need to handle break differently
1155 // inside switches.
1156 collectExitsInSwitch(op);
1157 return mlir::WalkResult::skip();
1158 }
1159 return mlir::WalkResult::advance();
1160 });
1161 };
1162
1163 // Collect exits from the body region.
1164 collectExitsInCleanup(cleanupBodyRegion, /*ignoreBreak=*/false);
1165 }
1166
1167 // Check if an operand's defining op should be moved to the destination block.
1168 // We only sink constants and simple loads. Anything else should be saved
1169 // to a temporary alloca and reloaded at the destination block.
1170 static bool shouldSinkReturnOperand(mlir::Value operand,
1171 cir::ReturnOp returnOp) {
1172 // Block arguments can't be moved
1173 mlir::Operation *defOp = operand.getDefiningOp();
1174 if (!defOp)
1175 return false;
1176
1177 // Only move constants and loads to the dispatch block. For anything else,
1178 // we'll store to a temporary and reload in the dispatch block.
1179 if (!mlir::isa<cir::ConstantOp, cir::LoadOp>(defOp))
1180 return false;
1181
1182 // Check if the return is the only user
1183 if (!operand.hasOneUse())
1184 return false;
1185
1186 // Only move ops that are in the same block as the return.
1187 if (defOp->getBlock() != returnOp->getBlock())
1188 return false;
1189
1190 if (auto loadOp = mlir::dyn_cast<cir::LoadOp>(defOp)) {
1191 // Only attempt to move loads of allocas in the entry block.
1192 mlir::Value ptr = loadOp.getAddr();
1193 auto funcOp = returnOp->getParentOfType<cir::FuncOp>();
1194 assert(funcOp && "Return op has no function parent?");
1195 mlir::Block &funcEntryBlock = funcOp.getBody().front();
1196
1197 // Check if it's an alloca in the function entry block
1198 if (auto allocaOp =
1199 mlir::dyn_cast_if_present<cir::AllocaOp>(ptr.getDefiningOp()))
1200 return allocaOp->getBlock() == &funcEntryBlock;
1201
1202 return false;
1203 }
1204
1205 // Make sure we only fall through to here with constants.
1206 assert(mlir::isa<cir::ConstantOp>(defOp) && "Expected constant op");
1207 return true;
1208 }
1209
1210 // For returns with operands in cleanup dispatch blocks, the operands may not
1211 // dominate the dispatch block. This function handles that by either sinking
1212 // the operand's defining op to the dispatch block (for constants and simple
1213 // loads) or by storing to a temporary alloca and reloading it.
1214 void
1215 getReturnOpOperands(cir::ReturnOp returnOp, mlir::Operation *exitOp,
1216 mlir::Location loc, mlir::PatternRewriter &rewriter,
1217 llvm::SmallVectorImpl<mlir::Value> &returnValues) const {
1218 mlir::Block *destBlock = rewriter.getInsertionBlock();
1219 auto funcOp = exitOp->getParentOfType<cir::FuncOp>();
1220 assert(funcOp && "Return op has no function parent?");
1221 mlir::Block &funcEntryBlock = funcOp.getBody().front();
1222
1223 for (mlir::Value operand : returnOp.getOperands()) {
1224 if (shouldSinkReturnOperand(operand, returnOp)) {
1225 // Sink the defining op to the dispatch block.
1226 mlir::Operation *defOp = operand.getDefiningOp();
1227 rewriter.moveOpBefore(defOp, destBlock, destBlock->end());
1228 returnValues.push_back(operand);
1229 } else {
1230 // Create an alloca in the function entry block.
1231 cir::AllocaOp alloca;
1232 {
1233 mlir::OpBuilder::InsertionGuard guard(rewriter);
1234 rewriter.setInsertionPointToStart(&funcEntryBlock);
1235 cir::CIRDataLayout dataLayout(
1236 funcOp->getParentOfType<mlir::ModuleOp>());
1237 uint64_t alignment =
1238 dataLayout.getAlignment(operand.getType(), true).value();
1239 cir::PointerType ptrType = cir::PointerType::get(operand.getType());
1240 alloca =
1241 cir::AllocaOp::create(rewriter, loc, ptrType, "__ret_operand_tmp",
1242 rewriter.getI64IntegerAttr(alignment));
1243 }
1244
1245 // Store the operand value at the original return location.
1246 {
1247 mlir::OpBuilder::InsertionGuard guard(rewriter);
1248 rewriter.setInsertionPoint(exitOp);
1249 cir::StoreOp::create(rewriter, loc, operand, alloca,
1250 /*isVolatile=*/false,
1251 /*isNontemporal=*/false,
1252 /*alignment=*/mlir::IntegerAttr(),
1253 cir::SyncScopeKindAttr(), cir::MemOrderAttr());
1254 }
1255
1256 // Reload the value from the temporary alloca in the destination block.
1257 rewriter.setInsertionPointToEnd(destBlock);
1258 auto loaded =
1259 cir::LoadOp::create(rewriter, loc, alloca, /*isDeref=*/false,
1260 /*isVolatile=*/false, /*isNontemporal=*/false,
1261 /*alignment=*/mlir::IntegerAttr(),
1262 cir::SyncScopeKindAttr(), cir::MemOrderAttr(),
1263 /*invariant=*/false);
1264 returnValues.push_back(loaded);
1265 }
1266 }
1267 }
1268
1269 // Create the appropriate terminator for an exit operation in the dispatch
1270 // block. For return ops with operands, this handles the dominance issue by
1271 // either moving the operand's defining op to the dispatch block (if it's a
1272 // trivial use) or by storing to a temporary alloca and loading it.
1273 mlir::LogicalResult
1274 createExitTerminator(mlir::Operation *exitOp, mlir::Location loc,
1275 mlir::Block *continueBlock,
1276 mlir::PatternRewriter &rewriter) const {
1277 return llvm::TypeSwitch<mlir::Operation *, mlir::LogicalResult>(exitOp)
1278 .Case<cir::YieldOp>([&](auto) {
1279 // Yield becomes a branch to continue block.
1280 cir::BrOp::create(rewriter, loc, continueBlock);
1281 return mlir::success();
1282 })
1283 .Case<cir::BreakOp>([&](auto) {
1284 // Break is preserved for later lowering by enclosing switch/loop.
1285 cir::BreakOp::create(rewriter, loc);
1286 return mlir::success();
1287 })
1288 .Case<cir::ContinueOp>([&](auto) {
1289 // Continue is preserved for later lowering by enclosing loop.
1290 cir::ContinueOp::create(rewriter, loc);
1291 return mlir::success();
1292 })
1293 .Case<cir::CoReturnOp>([&](auto) {
1294 // CoReturnOp does not carry a destination operand. The continuation
1295 // block determines whether execution proceeds to another cleanup or
1296 // to the coroutine's final suspend path.
1297 cir::CoReturnOp::create(rewriter, loc);
1298 return mlir::success();
1299 })
1300 .Case<cir::CoroCleanupOp>([&](auto) {
1301 // Like cir.co_return, it keeps propagating through the enclosing
1302 // cleanup scopes until CIRCoroutineFlattening rewrites it.
1303 cir::CoroCleanupOp::create(rewriter, loc);
1304 return mlir::success();
1305 })
1306 .Case<cir::ReturnOp>([&](auto returnOp) {
1307 // Return from the cleanup exit. Note, if this is a return inside a
1308 // nested cleanup scope, the flattening of the outer scope will handle
1309 // branching through the outer cleanup.
1310 if (returnOp.hasOperand()) {
1311 llvm::SmallVector<mlir::Value, 2> returnValues;
1312 getReturnOpOperands(returnOp, exitOp, loc, rewriter, returnValues);
1313 cir::ReturnOp::create(rewriter, loc, returnValues);
1314 } else {
1315 cir::ReturnOp::create(rewriter, loc);
1316 }
1317 return mlir::success();
1318 })
1319 .Case<cir::GotoOp>([&](auto gotoOp) {
1320 // Gotos that target a label within the cleanup body region are
1321 // filtered out by collectExits and never reach this code, so any
1322 // goto that does reach here transfers control out of the cleanup
1323 // scope. The goto is just moved to the exit block.
1324 cir::GotoOp::create(rewriter, loc, gotoOp.getLabel());
1325 return mlir::success();
1326 })
1327 .Default([&](mlir::Operation *op) {
1328 cir::UnreachableOp::create(rewriter, loc);
1329 return op->emitError(
1330 "unexpected exit operation in cleanup scope body");
1331 });
1332 }
1333
1334#ifndef NDEBUG
1335 // Check that no block other than the last one in a region exits the region.
1336 static bool regionExitsOnlyFromLastBlock(mlir::Region &region) {
1337 for (mlir::Block &block : region) {
1338 if (&block == &region.back())
1339 continue;
1340 bool expectedTerminator =
1341 llvm::TypeSwitch<mlir::Operation *, bool>(block.getTerminator())
1342 // It is theoretically possible to have a cleanup block with
1343 // any of the following exits in non-final blocks, but we won't
1344 // currently generate any CIR that does that, and being able to
1345 // assume that it doesn't happen simplifies the implementation.
1346 // If we ever need to handle this case, the code will need to
1347 // be updated to handle it.
1348 .Case<cir::YieldOp, cir::ReturnOp, cir::ResumeFlatOp,
1349 cir::ContinueOp, cir::BreakOp, cir::GotoOp>(
1350 [](auto) { return false; })
1351 // We expect that call operations have not yet been rewritten
1352 // as try_call operations. A call can unwind out of the cleanup
1353 // scope, but we will be handling that during flattening. The
1354 // only case where a try_call could be present inside an
1355 // unflattened cleanup region is if the cleanup contained a
1356 // nested try-catch region, and that isn't expected as of the
1357 // time of this implementation. If it does, this could be
1358 // updated to tolerate it.
1359 .Case<cir::TryCallOp>([](auto) { return false; })
1360 // Likewise, we don't expect to find an EH dispatch operation
1361 // because we weren't expecting try-catch regions nested in the
1362 // cleanup region.
1363 .Case<cir::EhDispatchOp>([](auto) { return false; })
1364 // In theory, it would be possible to have a flattened switch
1365 // operation that does not exit the cleanup region. For now,
1366 // that's not happening.
1367 .Case<cir::SwitchFlatOp>([](auto) { return false; })
1368 // These aren't expected either, but if they occur, they don't
1369 // exit the region, so that's OK.
1370 .Case<cir::UnreachableOp, cir::TrapOp>([](auto) { return true; })
1371 // Indirect branches are not expected.
1372 .Case<cir::IndirectBrOp>([](auto) { return false; })
1373 // We do expect branches, but we don't expect them to leave
1374 // the region.
1375 .Case<cir::BrOp>([&](cir::BrOp brOp) {
1376 assert(brOp.getDest()->getParent() == &region &&
1377 "branch destination is not in the region");
1378 return true;
1379 })
1380 .Case<cir::BrCondOp>([&](cir::BrCondOp brCondOp) {
1381 assert(brCondOp.getDestTrue()->getParent() == &region &&
1382 "branch destination is not in the region");
1383 assert(brCondOp.getDestFalse()->getParent() == &region &&
1384 "branch destination is not in the region");
1385 return true;
1386 })
1387 // What else could there be?
1388 .Default([](mlir::Operation *) -> bool {
1389 llvm_unreachable("unexpected terminator in cleanup region");
1390 });
1391 if (!expectedTerminator)
1392 return false;
1393 }
1394 return true;
1395 }
1396#endif
1397
1398 // Build the EH cleanup block structure by cloning the cleanup region. The
1399 // cloned entry block gets an !cir.eh_token argument and a cir.begin_cleanup
1400 // inserted at the top. All cir.yield terminators that might exit the cleanup
1401 // region are replaced with cir.end_cleanup + cir.resume.
1402 //
1403 // For a single-block cleanup region, this produces:
1404 //
1405 // ^eh_cleanup(%eh_token : !cir.eh_token):
1406 // %ct = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
1407 // <cloned cleanup operations>
1408 // cir.end_cleanup %ct : !cir.cleanup_token
1409 // cir.resume %eh_token : !cir.eh_token
1410 //
1411 // For a multi-block cleanup region (e.g. containing a flattened cir.if),
1412 // the same wrapping is applied around the cloned block structure: the entry
1413 // block gets begin_cleanup and all exit blocks (those terminated by yield)
1414 // get end_cleanup + resume.
1415 //
1416 // If this cleanup scope is nested within a TryOp, the resume will be updated
1417 // to branch to the catch dispatch block of the enclosing try operation when
1418 // the TryOp is flattened.
1419 mlir::Block *buildEHCleanupBlocks(cir::CleanupScopeOp cleanupOp,
1420 mlir::Location loc,
1421 mlir::Block *insertBefore,
1422 mlir::PatternRewriter &rewriter) const {
1423 assert(regionExitsOnlyFromLastBlock(cleanupOp.getCleanupRegion()) &&
1424 "cleanup region has exits in non-final blocks");
1425
1426 // Track the block before the insertion point so we can find the cloned
1427 // blocks after cloning.
1428 mlir::Block *blockBeforeClone = insertBefore->getPrevNode();
1429
1430 // Clone the entire cleanup region before insertBefore.
1431 rewriter.cloneRegionBefore(cleanupOp.getCleanupRegion(), insertBefore);
1432
1433 // Find the first cloned block.
1434 mlir::Block *clonedEntry = blockBeforeClone
1435 ? blockBeforeClone->getNextNode()
1436 : &insertBefore->getParent()->front();
1437
1438 // Add the eh_token argument to the cloned entry block and insert
1439 // begin_cleanup at the top.
1440 auto ehTokenType = cir::EhTokenType::get(rewriter.getContext());
1441 mlir::Value ehToken = clonedEntry->addArgument(ehTokenType, loc);
1442
1443 rewriter.setInsertionPointToStart(clonedEntry);
1444 auto beginCleanup = cir::BeginCleanupOp::create(rewriter, loc, ehToken);
1445
1446 // Replace the yield terminator in the last cloned block with
1447 // end_cleanup + resume.
1448 mlir::Block *lastClonedBlock = insertBefore->getPrevNode();
1449 auto yieldOp =
1450 mlir::dyn_cast<cir::YieldOp>(lastClonedBlock->getTerminator());
1451 if (yieldOp) {
1452 rewriter.setInsertionPoint(yieldOp);
1453 cir::EndCleanupOp::create(rewriter, loc, beginCleanup.getCleanupToken());
1454 rewriter.replaceOpWithNewOp<cir::ResumeOp>(yieldOp, ehToken);
1455 } else {
1456 cleanupOp->emitError("Not yet implemented: cleanup region terminated "
1457 "with non-yield operation");
1458 }
1459
1460 return clonedEntry;
1461 }
1462
1463 // Flatten a cleanup scope. The body region's exits branch to the cleanup
1464 // block, and the cleanup block branches to destination blocks whose contents
1465 // depend on the type of operation that exited the body region. Yield becomes
1466 // a branch to the block after the cleanup scope, break and continue are
1467 // preserved for later lowering by enclosing switch or loop, and return
1468 // is preserved as is.
1469 //
1470 // If there are multiple exits from the cleanup body, a destination slot and
1471 // switch dispatch are used to continue to the correct destination after the
1472 // cleanup is complete. A destination slot alloca is created at the function
1473 // entry block. Each exit operation is replaced by a store of its unique ID to
1474 // the destination slot and a branch to cleanup. An operation is appended to
1475 // the to branch to a dispatch block that loads the destination slot and uses
1476 // switch.flat to branch to the correct destination.
1477 //
1478 // If the cleanup scope requires EH cleanup, any call operations in the body
1479 // that may throw are replaced with cir.try_call operations that unwind to an
1480 // EH cleanup block. The cleanup block(s) will be terminated with a cir.resume
1481 // operation. If this cleanup scope is enclosed by a try operation, the
1482 // flattening of the try operation flattening will replace the cir.resume with
1483 // a branch to a catch dispatch block. Otherwise, the cir.resume operation
1484 // remains in place and will unwind to the caller.
1485 mlir::LogicalResult
1486 flattenCleanup(cir::CleanupScopeOp cleanupOp,
1487 llvm::SmallVectorImpl<CleanupExit> &exits,
1488 llvm::SmallVectorImpl<cir::CallOp> &callsToRewrite,
1489 llvm::SmallVectorImpl<cir::ThrowOp> &throwsToRewrite,
1490 llvm::SmallVectorImpl<cir::ResumeOp> &resumeOpsToChain,
1491 mlir::PatternRewriter &rewriter) const {
1492 mlir::Location loc = cleanupOp.getLoc();
1493 cir::CleanupKindAttr cleanupKind = cleanupOp.getCleanupKindAttr();
1494 bool hasNormalCleanup = cleanupKind.isNormal();
1495 bool hasEHCleanup = cleanupKind.isEH();
1496 bool isMultiExit = exits.size() > 1;
1497
1498 // If every exit from the body is a musttail return, nothing reaches the
1499 // normal cleanup, so it is dropped rather than emitted as dead code. The
1500 // EH path is still built. The only cleanup a musttail call may skip and
1501 // still unwind through is a lifetime marker, which is pushed as an "all"
1502 // cleanup, and the body can throw before reaching the tail call.
1503 bool emitNormalCleanup = hasNormalCleanup && !exits.empty();
1504
1505 // Get references to region blocks before inlining.
1506 mlir::Block *bodyEntry = &cleanupOp.getBodyRegion().front();
1507 mlir::Block *cleanupEntry = &cleanupOp.getCleanupRegion().front();
1508 mlir::Block *cleanupExit = &cleanupOp.getCleanupRegion().back();
1509 assert(regionExitsOnlyFromLastBlock(cleanupOp.getCleanupRegion()) &&
1510 "cleanup region has exits in non-final blocks");
1511 auto cleanupYield = dyn_cast<cir::YieldOp>(cleanupExit->getTerminator());
1512 if (!cleanupYield) {
1513 return rewriter.notifyMatchFailure(cleanupOp,
1514 "Not yet implemented: cleanup region "
1515 "terminated with non-yield operation");
1516 }
1517
1518 // For multiple exits from the body region, get or create a destination slot
1519 // at function entry. The slot is shared across all cleanup scopes in the
1520 // function. This is only needed if the cleanup scope requires normal
1521 // cleanup.
1522 cir::AllocaOp destSlot;
1523 if (isMultiExit && emitNormalCleanup) {
1524 auto funcOp = cleanupOp->getParentOfType<cir::FuncOp>();
1525 if (!funcOp)
1526 return cleanupOp->emitError("cleanup scope not inside a function");
1527 destSlot = getOrCreateCleanupDestSlot(funcOp, rewriter, loc);
1528 }
1529
1530 // Split the current block to create the insertion point.
1531 mlir::Block *currentBlock = rewriter.getInsertionBlock();
1532 mlir::Block *continueBlock =
1533 rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
1534
1535 // Build EH cleanup blocks if needed. This must be done before inlining
1536 // the cleanup region since buildEHCleanupBlocks clones from it. The unwind
1537 // block is inserted before the EH cleanup entry so that the final layout
1538 // is: body -> normal cleanup -> exit -> unwind -> EH cleanup -> continue.
1539 // EH cleanup blocks are needed when there are throwing calls or throws
1540 // that need to be rewritten, or when there are resume ops from
1541 // already-flattened inner cleanup scopes that need to chain through this
1542 // cleanup's EH handler.
1543 mlir::Block *unwindBlock = nullptr;
1544 mlir::Block *ehCleanupEntry = nullptr;
1545 if (hasEHCleanup && (!callsToRewrite.empty() || !throwsToRewrite.empty() ||
1546 !resumeOpsToChain.empty())) {
1547 ehCleanupEntry =
1548 buildEHCleanupBlocks(cleanupOp, loc, continueBlock, rewriter);
1549 // The unwind block is only needed when there are throwing calls or
1550 // throws that need a shared unwind destination. Resume ops from inner
1551 // cleanups branch directly to the EH cleanup entry.
1552 if (!callsToRewrite.empty() || !throwsToRewrite.empty())
1553 unwindBlock = buildUnwindBlock(ehCleanupEntry, /*isCleanupOnly=*/true,
1554 loc, ehCleanupEntry, rewriter);
1555 }
1556
1557 // All normal flow blocks are inserted before this point — either before
1558 // the unwind block (if it exists), or before the EH cleanup entry (if EH
1559 // cleanup exists but no unwind block is needed), or before the continue
1560 // block.
1561 mlir::Block *normalInsertPt =
1562 unwindBlock ? unwindBlock
1563 : (ehCleanupEntry ? ehCleanupEntry : continueBlock);
1564
1565 // Inline the body region.
1566 rewriter.inlineRegionBefore(cleanupOp.getBodyRegion(), normalInsertPt);
1567
1568 // Inline the cleanup region for the normal cleanup path.
1569 if (emitNormalCleanup)
1570 rewriter.inlineRegionBefore(cleanupOp.getCleanupRegion(), normalInsertPt);
1571
1572 // Branch from current block to body entry.
1573 rewriter.setInsertionPointToEnd(currentBlock);
1574 cir::BrOp::create(rewriter, loc, bodyEntry);
1575
1576 // Handle normal exits.
1577 mlir::LogicalResult result = mlir::success();
1578 if (emitNormalCleanup) {
1579 // Create the exit/dispatch block (after cleanup, before continue).
1580 mlir::Block *exitBlock = rewriter.createBlock(normalInsertPt);
1581
1582 // Rewrite the cleanup region's yield to branch to exit block.
1583 rewriter.setInsertionPoint(cleanupYield);
1584 rewriter.replaceOpWithNewOp<cir::BrOp>(cleanupYield, exitBlock);
1585
1586 if (isMultiExit) {
1587 // Build the dispatch switch in the exit block.
1588 rewriter.setInsertionPointToEnd(exitBlock);
1589
1590 // Load the destination slot value.
1591 auto slotValue =
1592 cir::LoadOp::create(rewriter, loc, destSlot, /*isDeref=*/false,
1593 /*isVolatile=*/false, /*isNontemporal=*/false,
1594 /*alignment=*/mlir::IntegerAttr(),
1595 cir::SyncScopeKindAttr(), cir::MemOrderAttr(),
1596 /*invariant=*/false);
1597
1598 // Create destination blocks for each exit and collect switch case info.
1599 llvm::SmallVector<mlir::APInt, 8> caseValues;
1600 llvm::SmallVector<mlir::Block *, 8> caseDestinations;
1601 llvm::SmallVector<mlir::ValueRange, 8> caseOperands;
1602 cir::IntType s32Type =
1603 cir::IntType::get(rewriter.getContext(), 32, /*isSigned=*/true);
1604
1605 for (const CleanupExit &exit : exits) {
1606 // Create a block for this destination.
1607 mlir::Block *destBlock = rewriter.createBlock(normalInsertPt);
1608 rewriter.setInsertionPointToEnd(destBlock);
1609 result =
1610 createExitTerminator(exit.exitOp, loc, continueBlock, rewriter);
1611
1612 // Add to switch cases.
1613 caseValues.push_back(
1614 llvm::APInt(32, static_cast<uint64_t>(exit.destinationId), true));
1615 caseDestinations.push_back(destBlock);
1616 caseOperands.push_back(mlir::ValueRange());
1617
1618 // Replace the original exit op with: store dest ID, branch to
1619 // cleanup.
1620 rewriter.setInsertionPoint(exit.exitOp);
1621 auto destIdConst = cir::ConstantOp::create(
1622 rewriter, loc, cir::IntAttr::get(s32Type, exit.destinationId));
1623 cir::StoreOp::create(rewriter, loc, destIdConst, destSlot,
1624 /*isVolatile=*/false,
1625 /*isNontemporal=*/false,
1626 /*alignment=*/mlir::IntegerAttr(),
1627 cir::SyncScopeKindAttr(), cir::MemOrderAttr());
1628 rewriter.replaceOpWithNewOp<cir::BrOp>(exit.exitOp, cleanupEntry);
1629
1630 // If the exit terminator creation failed, we're going to end up with
1631 // partially flattened code, but we'll also have reported an error so
1632 // that's OK. We need to finish out this function to keep the IR in a
1633 // valid state to help diagnose the error. This is a temporary
1634 // possibility during development. It shouldn't ever happen after the
1635 // implementation is complete.
1636 if (result.failed())
1637 break;
1638 }
1639
1640 // Create the default destination (unreachable).
1641 mlir::Block *defaultBlock = rewriter.createBlock(normalInsertPt);
1642 rewriter.setInsertionPointToEnd(defaultBlock);
1643 cir::UnreachableOp::create(rewriter, loc);
1644
1645 // Build the switch.flat operation in the exit block.
1646 rewriter.setInsertionPointToEnd(exitBlock);
1647 cir::SwitchFlatOp::create(rewriter, loc, slotValue, defaultBlock,
1648 mlir::ValueRange(), caseValues,
1649 caseDestinations, caseOperands);
1650 } else {
1651 // Single exit: put the appropriate terminator directly in the exit
1652 // block.
1653 rewriter.setInsertionPointToEnd(exitBlock);
1654 mlir::Operation *exitOp = exits[0].exitOp;
1655 result = createExitTerminator(exitOp, loc, continueBlock, rewriter);
1656
1657 // Replace body exit with branch to cleanup entry.
1658 rewriter.setInsertionPoint(exitOp);
1659 rewriter.replaceOpWithNewOp<cir::BrOp>(exitOp, cleanupEntry);
1660 }
1661 } else {
1662 // No normal cleanup to run: normal exits skip the cleanup entirely.
1663 // Replace yield exits with branches to the continue block.
1664 for (CleanupExit &exit : exits) {
1665 if (isa<cir::YieldOp>(exit.exitOp)) {
1666 rewriter.setInsertionPoint(exit.exitOp);
1667 rewriter.replaceOpWithNewOp<cir::BrOp>(exit.exitOp, continueBlock);
1668 }
1669 // Non-yield exits (break, continue, return) stay as-is since no normal
1670 // cleanup is needed.
1671 }
1672 }
1673
1674 // Replace non-nothrow calls and throws with try_call/try_throw
1675 // operations. All calls and throws within this cleanup scope share the
1676 // same unwind destination.
1677 if (hasEHCleanup) {
1678 for (cir::CallOp callOp : callsToRewrite)
1679 replaceCallWithTryCall(callOp, unwindBlock, loc, rewriter);
1680 for (cir::ThrowOp throwOp : throwsToRewrite)
1681 replaceThrowWithTryThrow(throwOp, unwindBlock, loc, rewriter);
1682 }
1683
1684 // Handle throwing calls and throws in EH cleanup blocks. When an
1685 // exception is thrown during cleanup code that runs on the exception
1686 // unwind path, the C++ standard requires that std::terminate() be
1687 // called. Replace such calls and throws with try_call/try_throw
1688 // operations that unwind to a terminate block containing
1689 // cir.eh.initiate + cir.eh.terminate.
1690 if (ehCleanupEntry) {
1691 llvm::SmallVector<cir::CallOp> ehCleanupThrowingCalls;
1692 llvm::SmallVector<cir::ThrowOp> ehCleanupThrows;
1693 for (mlir::Block *block = ehCleanupEntry; block != continueBlock;
1694 block = block->getNextNode()) {
1695 block->walk([&](mlir::Operation *op) {
1696 if (auto callOp = mlir::dyn_cast<cir::CallOp>(op)) {
1697 if (!callOp.getNothrow())
1698 ehCleanupThrowingCalls.push_back(callOp);
1699 } else if (auto throwOp = mlir::dyn_cast<cir::ThrowOp>(op)) {
1700 ehCleanupThrows.push_back(throwOp);
1701 }
1702 });
1703 }
1704 if (!ehCleanupThrowingCalls.empty() || !ehCleanupThrows.empty()) {
1705 mlir::Block *terminateBlock =
1706 buildTerminateUnwindBlock(loc, continueBlock, rewriter);
1707 for (cir::CallOp callOp : ehCleanupThrowingCalls)
1708 replaceCallWithTryCall(callOp, terminateBlock, loc, rewriter);
1709 for (cir::ThrowOp throwOp : ehCleanupThrows)
1710 replaceThrowWithTryThrow(throwOp, terminateBlock, loc, rewriter);
1711 }
1712 }
1713
1714 // Chain inner EH cleanup resume ops to this cleanup's EH handler.
1715 // Each cir.resume from an already-flattened inner cleanup is replaced
1716 // with a branch to the outer EH cleanup entry, passing the eh_token
1717 // from the inner's begin_cleanup so that the same in-flight exception
1718 // flows through the outer cleanup before unwinding to the caller.
1719 if (ehCleanupEntry) {
1720 for (cir::ResumeOp resumeOp : resumeOpsToChain) {
1721 mlir::Value ehToken = resumeOp.getEhToken();
1722 rewriter.setInsertionPoint(resumeOp);
1723 rewriter.replaceOpWithNewOp<cir::BrOp>(
1724 resumeOp, mlir::ValueRange{ehToken}, /*loop_id=*/nullptr,
1725 /*loop_hints=*/nullptr, ehCleanupEntry);
1726 }
1727 }
1728
1729 // Erase the original cleanup scope op.
1730 rewriter.eraseOp(cleanupOp);
1731
1732 // Always return success because the IR has been modified (blocks split,
1733 // regions inlined, ops erased, etc.). The MLIR pattern rewriter contract
1734 // requires that if a pattern modifies IR, it must return success().
1735 return mlir::success();
1736 }
1737
1738 mlir::LogicalResult
1739 matchAndRewrite(cir::CleanupScopeOp cleanupOp,
1740 mlir::PatternRewriter &rewriter) const override {
1741 mlir::OpBuilder::InsertionGuard guard(rewriter);
1742
1743 // All nested structured CIR ops must be flattened before the cleanup scope.
1744 // Operations like loops, switches, scopes, and ifs may contain exits
1745 // (return, break, continue) that the cleanup scope will replace with
1746 // branches to the cleanup entry. If those exits are inside a structured
1747 // op's region, the branch would reference a block outside that region,
1748 // which is invalid. Fail the match so they are processed first.
1749 //
1750 // Before checking, erase any trivially dead nested cleanup scopes. These
1751 // arise from deactivated cleanups (e.g. partial-construction guards for
1752 // lambda captures). The greedy rewriter may have already DCE'd them, but
1753 // when a trivially dead nested op is erased first, the parent isn't always
1754 // re-added to the worklist, so we handle it here.
1755 llvm::SmallVector<cir::CleanupScopeOp> deadNestedOps;
1756 cleanupOp.getBodyRegion().walk([&](cir::CleanupScopeOp nested) {
1757 if (mlir::isOpTriviallyDead(nested))
1758 deadNestedOps.push_back(nested);
1759 });
1760 for (auto op : deadNestedOps)
1761 rewriter.eraseOp(op);
1762
1763 if (hasNestedOpsToFlatten(cleanupOp.getBodyRegion()))
1764 return mlir::failure();
1765
1766 bool hasEHCleanup = cleanupOp.getCleanupKindAttr().isEH();
1767
1768 // Collect all exits from the body region.
1769 llvm::SmallVector<CleanupExit> exits;
1770 int nextId = 0;
1771 collectExits(cleanupOp.getBodyRegion(), exits, nextId);
1772
1773#ifndef NDEBUG
1774 // A body with no exits at all only happens when every path out of it is a
1775 // musttail return, which leaves the function without running the cleanup.
1776 bool hasMustTailCall = false;
1777 cleanupOp.getBodyRegion().walk(
1778 [&](cir::CallOp callOp) { hasMustTailCall |= callOp.getMusttail(); });
1779 assert((!exits.empty() || hasMustTailCall) &&
1780 "cleanup scope body has no exit");
1781#endif
1782
1783 // Collect non-nothrow calls and throws that need to be converted to
1784 // try_call/try_throw. A marker-only cleanup must not introduce an unwind
1785 // edge unless an enclosing handler or real EH cleanup requires one.
1786 llvm::SmallVector<cir::CallOp> callsToRewrite;
1787 llvm::SmallVector<cir::ThrowOp> throwsToRewrite;
1788 if (hasEHCleanup && (!isLifetimeMarkerOnly(cleanupOp.getCleanupRegion()) ||
1789 hasEnclosingEHRequirement(cleanupOp))) {
1790 collectThrowingCalls(cleanupOp.getBodyRegion(), callsToRewrite);
1791 collectThrows(cleanupOp.getBodyRegion(), throwsToRewrite);
1792 }
1793
1794 // Collect resume ops from already-flattened inner cleanup scopes that
1795 // need to chain through this cleanup's EH handler, including lifetime
1796 // markers even when they did not introduce any unwind edges themselves.
1797 llvm::SmallVector<cir::ResumeOp> resumeOpsToChain;
1798 if (hasEHCleanup)
1799 collectResumeOps(cleanupOp.getBodyRegion(), resumeOpsToChain);
1800
1801 return flattenCleanup(cleanupOp, exits, callsToRewrite, throwsToRewrite,
1802 resumeOpsToChain, rewriter);
1803 }
1804};
1805
1806// Trace an !cir.eh_token value back through block arguments to find the
1807// cir.eh.initiate operation that defines it. Returns {} if the defining op
1808// cannot be found (e.g. multiple predecessors).
1809static cir::EhInitiateOp traceToEhInitiate(mlir::Value ehToken) {
1810 while (ehToken) {
1811 if (auto initiate = ehToken.getDefiningOp<cir::EhInitiateOp>())
1812 return initiate;
1813 auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(ehToken);
1814 if (!blockArg)
1815 return {};
1816 mlir::Block *pred = blockArg.getOwner()->getSinglePredecessor();
1817 if (!pred)
1818 return {};
1819 auto brOp = mlir::dyn_cast<cir::BrOp>(pred->getTerminator());
1820 if (!brOp)
1821 return {};
1822 ehToken = brOp.getDestOperands()[blockArg.getArgNumber()];
1823 }
1824 return {};
1825}
1826
1827class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
1828public:
1829 using OpRewritePattern<cir::TryOp>::OpRewritePattern;
1830
1831 // Build the catch dispatch block with a cir.eh.dispatch operation.
1832 // The dispatch block receives an !cir.eh_token argument and dispatches
1833 // to the appropriate catch handler blocks based on exception types.
1834 mlir::Block *buildCatchDispatchBlock(
1835 cir::TryOp tryOp, mlir::ArrayAttr handlerTypes,
1836 llvm::SmallVectorImpl<mlir::Block *> &catchHandlerBlocks,
1837 mlir::Location loc, mlir::Block *insertBefore,
1838 mlir::PatternRewriter &rewriter) const {
1839 mlir::Block *dispatchBlock = rewriter.createBlock(insertBefore);
1840 auto ehTokenType = cir::EhTokenType::get(rewriter.getContext());
1841 mlir::Value ehToken = dispatchBlock->addArgument(ehTokenType, loc);
1842
1843 rewriter.setInsertionPointToEnd(dispatchBlock);
1844
1845 // Build the catch types and destinations for the dispatch.
1846 llvm::SmallVector<mlir::Attribute> catchTypeAttrs;
1847 llvm::SmallVector<mlir::Block *> catchDests;
1848 mlir::Block *defaultDest = nullptr;
1849 bool defaultIsCatchAll = false;
1850
1851 // A filter try operation has exactly two handlers. The unexpected region
1852 // is the destination of the filter clause (taken when the exception is
1853 // *not* permitted). The filter region is the unwind destination (taken
1854 // when it is).
1855 cir::EhFilterAttr filterAttr;
1856 mlir::Block *filterUnwindDest = nullptr;
1857 mlir::Block *filterClauseDest = nullptr;
1858
1859 for (auto zipped : llvm::zip(handlerTypes, catchHandlerBlocks)) {
1860 mlir::Attribute typeAttr = std::get<0>(zipped);
1861 mlir::Block *handlerBlock = std::get<1>(zipped);
1862 llvm::TypeSwitch<mlir::Attribute>(typeAttr)
1863 .Case<cir::CatchAllAttr>([&](auto) {
1864 assert(!defaultDest && "multiple catch_all or unwind handlers");
1865 defaultDest = handlerBlock;
1866 defaultIsCatchAll = true;
1867 })
1868 .Case<cir::UnwindAttr>([&](auto) {
1869 assert(!defaultDest && "multiple catch_all or unwind handlers");
1870 defaultDest = handlerBlock;
1871 defaultIsCatchAll = false;
1872 })
1873 .Case<cir::EhFilterAttr>([&](cir::EhFilterAttr ehFilter) {
1874 assert(!filterAttr && "multiple filter handlers");
1875 filterAttr = ehFilter;
1876 filterUnwindDest = handlerBlock;
1877 })
1878 .Case<cir::EhUnexpectedAttr>([&](auto) {
1879 assert(!filterClauseDest && "multiple unexpected handlers");
1880 filterClauseDest = handlerBlock;
1881 })
1882 .Default([&](mlir::Attribute attr) {
1883 // Typed catch handler (GlobalViewAttr with type info).
1884 catchTypeAttrs.push_back(attr);
1885 catchDests.push_back(handlerBlock);
1886 });
1887 }
1888
1889 if (filterAttr) {
1890 assert(filterUnwindDest && filterClauseDest &&
1891 "filter handler requires an unexpected handler");
1892 assert(!defaultDest &&
1893 "filter handler cannot share a dispatch with catch_all or unwind");
1894 catchTypeAttrs.push_back(filterAttr);
1895 catchDests.push_back(filterClauseDest);
1896 defaultDest = filterUnwindDest;
1897 defaultIsCatchAll = false;
1898 }
1899
1900 assert(defaultDest && "dispatch must have a catch_all or unwind handler");
1901
1902 mlir::ArrayAttr catchTypesArrayAttr;
1903 if (!catchTypeAttrs.empty())
1904 catchTypesArrayAttr = rewriter.getArrayAttr(catchTypeAttrs);
1905
1906 cir::EhDispatchOp::create(rewriter, loc, ehToken, catchTypesArrayAttr,
1907 defaultIsCatchAll, defaultDest, catchDests);
1908
1909 return dispatchBlock;
1910 }
1911
1912 // Flatten a single catch handler region. Each handler region has an
1913 // !cir.eh_token argument and starts with cir.begin_catch, followed by
1914 // a cir.cleanup.scope containing the handler body (with cir.end_catch in
1915 // its cleanup region), and ending with cir.yield.
1916 //
1917 // After flattening, the handler region becomes a block that receives the
1918 // eh_token, calls begin_catch, runs the handler body inline, calls
1919 // end_catch, and branches to the continue block.
1920 //
1921 // The cleanup scope inside the catch handler is expected to have been
1922 // flattened before we get here, so what we see in the handler region is
1923 // already flat code with begin_catch at the top and end_catch in any place
1924 // that we would exit the catch handler. We just need to inline the region
1925 // and fix up terminators.
1926 mlir::Block *flattenCatchHandler(mlir::Region &handlerRegion,
1927 mlir::Block *continueBlock,
1928 mlir::Location loc,
1929 mlir::Block *insertBefore,
1930 mlir::PatternRewriter &rewriter) const {
1931 // The handler region entry block has the !cir.eh_token argument.
1932 mlir::Block *handlerEntry = &handlerRegion.front();
1933
1934 // Inline the handler region before insertBefore.
1935 rewriter.inlineRegionBefore(handlerRegion, insertBefore);
1936
1937 // Replace yield terminators in the handler with branches to continue.
1938 for (mlir::Block &block : llvm::make_range(handlerEntry->getIterator(),
1939 insertBefore->getIterator())) {
1940 if (auto yieldOp = dyn_cast<cir::YieldOp>(block.getTerminator())) {
1941 // Verify that end_catch is the last non-branch operation before
1942 // this yield. After cleanup scope flattening, end_catch may be
1943 // in a predecessor block rather than immediately before the yield.
1944 // Walk back through predecessors (including multi-predecessor
1945 // blocks), verifying that each intermediate block contains only a
1946 // branch terminator, until we find end_catch as the last
1947 // non-terminator in some block.
1948 // Verify that end_catch is reachable on some predecessor path
1949 // before this yield. After cleanup scope flattening, end_catch
1950 // may be separated from yield by conditional branches (e.g.,
1951 // from flattened cir.if inside the catch body).
1952 assert(([&]() {
1953 if (mlir::Operation *prev = yieldOp->getPrevNode())
1954 return isa<cir::EndCatchOp>(prev);
1955 llvm::SmallPtrSet<mlir::Block *, 8> visited;
1956 llvm::SmallVector<mlir::Block *, 4> worklist;
1957 for (mlir::Block *pred : block.getPredecessors())
1958 worklist.push_back(pred);
1959 while (!worklist.empty()) {
1960 mlir::Block *b = worklist.pop_back_val();
1961 if (!visited.insert(b).second)
1962 continue;
1963 mlir::Operation *term = b->getTerminator();
1964 if (mlir::Operation *prev = term->getPrevNode()) {
1965 if (isa<cir::EndCatchOp>(prev))
1966 return true;
1967 }
1968 for (mlir::Block *pred : b->getPredecessors())
1969 worklist.push_back(pred);
1970 }
1971 return false;
1972 }()) &&
1973 "expected end_catch reachable before yield "
1974 "in catch handler");
1975 rewriter.setInsertionPoint(yieldOp);
1976 rewriter.replaceOpWithNewOp<cir::BrOp>(yieldOp, continueBlock);
1977 }
1978 }
1979
1980 return handlerEntry;
1981 }
1982
1983 // Flatten an unwind, filter, or unexpected handler region. These regions
1984 // do not catch the exception. They resume, are unreachable, or call
1985 // cir.eh.unexpected. We inline them and leave the terminator in place. If
1986 // this try op is nested inside an EH cleanup or another try op, the
1987 // enclosing op will rewrite a resume as a branch to its cleanup or dispatch
1988 // block when it is flattened. Otherwise, a resume will unwind to the caller.
1989 mlir::Block *flattenUnwindHandler(mlir::Region &unwindRegion,
1990 mlir::Location loc,
1991 mlir::Block *insertBefore,
1992 mlir::PatternRewriter &rewriter) const {
1993 mlir::Block *unwindEntry = &unwindRegion.front();
1994 rewriter.inlineRegionBefore(unwindRegion, insertBefore);
1995 return unwindEntry;
1996 }
1997
1998 mlir::LogicalResult
1999 matchAndRewrite(cir::TryOp tryOp,
2000 mlir::PatternRewriter &rewriter) const override {
2001 // All nested structured CIR ops must be flattened before the try op.
2002 // Cleanup scopes and nested try ops need to be flat so EH cleanup is
2003 // properly handled. Other structured ops (scopes, ifs, loops, switches,
2004 // ternaries) must be flat because replaceCallWithTryCall creates try_call
2005 // ops whose unwind destination is outside the structured op's region,
2006 // which would be an invalid cross-region reference.
2007 for (mlir::Region &region : tryOp->getRegions())
2008 if (hasNestedOpsToFlatten(region))
2009 return mlir::failure();
2010
2011 mlir::OpBuilder::InsertionGuard guard(rewriter);
2012 mlir::Location loc = tryOp.getLoc();
2013
2014 mlir::ArrayAttr handlerTypes = tryOp.getHandlerTypesAttr();
2015 mlir::MutableArrayRef<mlir::Region> handlerRegions =
2016 tryOp.getHandlerRegions();
2017
2018 // Collect throwing calls and throws in the try body.
2019 llvm::SmallVector<cir::CallOp> callsToRewrite;
2020 collectThrowingCalls(tryOp.getTryRegion(), callsToRewrite);
2021 llvm::SmallVector<cir::ThrowOp> throwsToRewrite;
2022 collectThrows(tryOp.getTryRegion(), throwsToRewrite);
2023
2024 // Collect resume ops from already-flattened cleanup scopes in the try body.
2025 llvm::SmallVector<cir::ResumeOp> resumeOpsToChain;
2026 collectResumeOps(tryOp.getTryRegion(), resumeOpsToChain);
2027
2028 // Split the current block and inline the try body.
2029 mlir::Block *currentBlock = rewriter.getInsertionBlock();
2030 mlir::Block *continueBlock =
2031 rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
2032
2033 // Get references to try body blocks before inlining.
2034 mlir::Block *bodyEntry = &tryOp.getTryRegion().front();
2035 mlir::Block *bodyExit = &tryOp.getTryRegion().back();
2036
2037 // Inline the try body region before the continue block.
2038 rewriter.inlineRegionBefore(tryOp.getTryRegion(), continueBlock);
2039
2040 // Branch from the current block to the body entry.
2041 rewriter.setInsertionPointToEnd(currentBlock);
2042 cir::BrOp::create(rewriter, loc, bodyEntry);
2043
2044 // Replace the try body's yield terminator with a branch to continue.
2045 if (auto bodyYield = dyn_cast<cir::YieldOp>(bodyExit->getTerminator())) {
2046 rewriter.setInsertionPoint(bodyYield);
2047 rewriter.replaceOpWithNewOp<cir::BrOp>(bodyYield, continueBlock);
2048 }
2049
2050 // If there are no handlers, we're done.
2051 if (!handlerTypes || handlerTypes.empty()) {
2052 rewriter.eraseOp(tryOp);
2053 return mlir::success();
2054 }
2055
2056 // If there are no throwing calls, no throws, and no resume ops from
2057 // inner cleanup scopes, exceptions cannot reach the catch handlers.
2058 // Drop all uses from the (unreachable) handler regions before erasing
2059 // the try op, since handler ops may reference values that were inlined
2060 // from the try body into the parent block.
2061 if (callsToRewrite.empty() && throwsToRewrite.empty() &&
2062 resumeOpsToChain.empty()) {
2063 for (mlir::Region &handlerRegion : handlerRegions)
2064 for (mlir::Block &block : handlerRegion)
2065 block.dropAllDefinedValueUses();
2066 rewriter.eraseOp(tryOp);
2067 return mlir::success();
2068 }
2069
2070 // Build the catch handler blocks.
2071
2072 // First, flatten all handler regions and collect the entry blocks.
2073 llvm::SmallVector<mlir::Block *> catchHandlerBlocks;
2074
2075 for (const auto &[idx, typeAttr] : llvm::enumerate(handlerTypes)) {
2076 mlir::Region &handlerRegion = handlerRegions[idx];
2077
2078 if (mlir::isa<cir::UnwindAttr, cir::EhFilterAttr, cir::EhUnexpectedAttr>(
2079 typeAttr)) {
2080 mlir::Block *unwindEntry =
2081 flattenUnwindHandler(handlerRegion, loc, continueBlock, rewriter);
2082 catchHandlerBlocks.push_back(unwindEntry);
2083 } else {
2084 mlir::Block *handlerEntry = flattenCatchHandler(
2085 handlerRegion, continueBlock, loc, continueBlock, rewriter);
2086 catchHandlerBlocks.push_back(handlerEntry);
2087 }
2088 }
2089
2090 // Build the catch dispatch block.
2091 mlir::Block *dispatchBlock =
2092 buildCatchDispatchBlock(tryOp, handlerTypes, catchHandlerBlocks, loc,
2093 catchHandlerBlocks.front(), rewriter);
2094
2095 // Check whether the try has a catch-all handler. When catch-all is
2096 // present, the personality function will always stop unwinding at this
2097 // frame (because catch-all matches every exception type). The LLVM
2098 // landingpad therefore needs "catch ptr null" rather than "cleanup".
2099 // The downstream pipeline (EHABILowering + LowerToLLVM) emits
2100 // "catch ptr null" when the EhInitiateOp has neither cleanup nor typed
2101 // catch types, so we clear the cleanup flag on every EhInitiateOp that
2102 // feeds into a dispatch with a catch-all handler.
2103 bool hasCatchAll =
2104 handlerTypes && llvm::any_of(handlerTypes, [](mlir::Attribute attr) {
2105 return mlir::isa<cir::CatchAllAttr>(attr);
2106 });
2107
2108 // Build a block to be the unwind desination for throwing calls/throws
2109 // and replace the calls/throws with try_call/try_throw ops. Note that
2110 // the unwind block created here is something different than the unwind
2111 // handler that we may have created above. The unwind handler continues
2112 // unwinding after uncaught exceptions. This is the block that will
2113 // eventually become the landing pad for invoke instructions.
2114 bool isCleanupOnly = tryOp.getCleanup() && !hasCatchAll;
2115 if (!callsToRewrite.empty() || !throwsToRewrite.empty()) {
2116 // Create a shared unwind block for all throwing calls/throws.
2117 mlir::Block *unwindBlock = buildUnwindBlock(dispatchBlock, isCleanupOnly,
2118 loc, dispatchBlock, rewriter);
2119
2120 for (cir::CallOp callOp : callsToRewrite)
2121 replaceCallWithTryCall(callOp, unwindBlock, loc, rewriter);
2122 for (cir::ThrowOp throwOp : throwsToRewrite)
2123 replaceThrowWithTryThrow(throwOp, unwindBlock, loc, rewriter);
2124 }
2125
2126 // Chain resume ops from inner cleanup scopes.
2127 // Resume ops from already-flattened cleanup scopes within the try body
2128 // should branch to the catch dispatch block instead of unwinding directly.
2129 for (cir::ResumeOp resumeOp : resumeOpsToChain) {
2130 // When there is a catch-all handler, clear the cleanup flag on the
2131 // cir.eh.initiate that produced this token. With catch-all, the LLVM
2132 // landingpad needs "catch ptr null" instead of "cleanup".
2133 if (hasCatchAll) {
2134 if (auto ehInitiate = traceToEhInitiate(resumeOp.getEhToken())) {
2135 rewriter.modifyOpInPlace(ehInitiate,
2136 [&] { ehInitiate.removeCleanupAttr(); });
2137 }
2138 }
2139
2140 mlir::Value ehToken = resumeOp.getEhToken();
2141 rewriter.setInsertionPoint(resumeOp);
2142 rewriter.replaceOpWithNewOp<cir::BrOp>(
2143 resumeOp, mlir::ValueRange{ehToken}, /*loop_id=*/nullptr,
2144 /*loop_hints=*/nullptr, dispatchBlock);
2145 }
2146
2147 // Finally, erase the original try op ----
2148 rewriter.eraseOp(tryOp);
2149
2150 return mlir::success();
2151 }
2152};
2153
2154static void
2155collectCoReturnsOps(mlir::Region &region,
2157 region.walk(
2158 [&](cir::CoReturnOp coReturnOp) { coReturnOps.push_back(coReturnOp); });
2159}
2160
2161// Flatten `cir.coroutine`. This runs after every nested op (awaits, cir.scope,
2162// cleanup scopes, ...) has been flattened. The regions are inlined in order,
2163// the same layout classic codegen produces:
2164//
2165// initialSuspend ---> body ---> finalSuspend ---> destroy ---> exit
2166// | ^ ^ ^
2167// +-------------+ | |
2168// cir.co_return | |
2169// await destroy case (cir.coro.cleanup) -------------+ |
2170// await suspended case (cir.coro.exit) -------------------------+
2171//
2172// Completing the final suspend and an await's destroy case both free the frame
2173// (the destroy region, cir.coro.intrinsic.free) and then continue to the exit
2174// region. A suspended coroutine (cir.coro.exit) must keep its frame, so it
2175// branches straight to the exit region.
2176class CIRCoroutineFlattening : public mlir::OpRewritePattern<cir::CoroutineOp> {
2177 // cir.coro.exit and cir.coro.cleanup are placeholders produced by flattening
2178 // a cir.await, which can only appear in the initialSuspend, body and
2179 // finalSuspend regions.
2180 template <class PlaceholderOp>
2181 void
2182 collectAwaitPlaceholders(cir::CoroutineOp coroOp,
2183 llvm::SmallVectorImpl<PlaceholderOp> &ops) const {
2184 auto collect = [&](PlaceholderOp op) { ops.push_back(op); };
2185 coroOp.getInitialSuspend().walk(collect);
2186 coroOp.getBody().walk(collect);
2187 coroOp.getFinalSuspend().walk(collect);
2188 }
2189
2190 // Replace the cir.yield that ends `region` with a branch to `dest`.
2191 void replaceYieldWithBr(mlir::PatternRewriter &rewriter, mlir::Region &region,
2192 mlir::Block *dest) const {
2193 auto yieldOp = cast<cir::YieldOp>(region.back().getTerminator());
2194 rewriter.setInsertionPoint(yieldOp);
2195 rewriter.replaceOpWithNewOp<cir::BrOp>(yieldOp, dest, yieldOp.getArgs());
2196 }
2197
2198 // initialSuspend ends in a cir.yield once the init await is done. Replace it
2199 // with a branch to the body.
2200 void inlineInitialSuspend(mlir::PatternRewriter &rewriter,
2201 cir::CoroutineOp coroOp, mlir::Block *insertBefore,
2202 mlir::Block *beforeBody) const {
2203 mlir::Region &initialRegion = coroOp.getInitialSuspend();
2204 replaceYieldWithBr(rewriter, initialRegion, beforeBody);
2205 rewriter.inlineRegionBefore(initialRegion, insertBefore);
2206 }
2207
2208 // The body either falls off the end (cir.yield) or ends in a
2209 // cir.co_return. Both, like every other cir.co_return in the body, branch
2210 // to the final suspend.
2211 void inlineBody(mlir::PatternRewriter &rewriter, cir::CoroutineOp coroOp,
2212 mlir::Block *insertBefore, mlir::Block *beforeFinal) const {
2213 mlir::Region &bodyRegion = coroOp.getBody();
2214
2215 llvm::SmallVector<cir::CoReturnOp> coReturnsOps;
2216 collectCoReturnsOps(bodyRegion, coReturnsOps);
2217
2218 mlir::Operation *bodyTerminator = bodyRegion.back().getTerminator();
2219 if (auto bodyYield = dyn_cast<cir::YieldOp>(bodyTerminator)) {
2220 rewriter.setInsertionPoint(bodyYield);
2221 rewriter.replaceOpWithNewOp<cir::BrOp>(bodyYield, beforeFinal,
2222 mlir::ValueRange{});
2223 }
2224
2225 for (auto &coReturn : coReturnsOps) {
2226 rewriter.setInsertionPoint(coReturn);
2227 rewriter.replaceOpWithNewOp<cir::BrOp>(coReturn, beforeFinal,
2228 mlir::ValueRange{});
2229 }
2230
2231 rewriter.inlineRegionBefore(bodyRegion, insertBefore);
2232 }
2233
2234 // finalSuspend ends in a cir.yield once the final await completes. The
2235 // coroutine is done, so branch to the destroy region to free the frame.
2236 void inlineFinalSuspend(mlir::PatternRewriter &rewriter,
2237 cir::CoroutineOp coroOp, mlir::Block *insertBefore,
2238 mlir::Block *beforeDestroy) const {
2239 mlir::Region &finalRegion = coroOp.getFinalSuspend();
2240 replaceYieldWithBr(rewriter, finalRegion, beforeDestroy);
2241 rewriter.inlineRegionBefore(finalRegion, insertBefore);
2242 }
2243
2244 // The destroy region frees the frame (cir.coro.intrinsic.free) and always
2245 // continues to the exit region.
2246 void inlineDestroy(mlir::PatternRewriter &rewriter, cir::CoroutineOp coroOp,
2247 mlir::Block *insertBefore, mlir::Block *beforeExit) const {
2248 mlir::Region &destroyRegion = coroOp.getDestroy();
2249 replaceYieldWithBr(rewriter, destroyRegion, beforeExit);
2250 rewriter.inlineRegionBefore(destroyRegion, insertBefore);
2251 }
2252
2253 // The exit region ends in a cir.return, so nothing falls through it.
2254 void inlineExit(mlir::PatternRewriter &rewriter, cir::CoroutineOp coroOp,
2255 mlir::Block *insertBefore) const {
2256 rewriter.inlineRegionBefore(coroOp.getExit(), insertBefore);
2257 }
2258
2259 // A suspended coroutine keeps its frame, so each cir.coro.exit branches
2260 // straight to the exit region without running the destroy region.
2261 void lowerCoroExits(mlir::PatternRewriter &rewriter,
2262 llvm::ArrayRef<cir::CoroExitOp> coroExitsOps,
2263 mlir::Block *beforeExit) const {
2264 for (cir::CoroExitOp coroExit : coroExitsOps) {
2265 // TODO(cir): once the GRO region is supported, this may need to branch
2266 // there instead of to the exit region.
2268 rewriter.setInsertionPoint(coroExit);
2269 rewriter.replaceOpWithNewOp<cir::BrOp>(coroExit, beforeExit,
2270 mlir::ValueRange{});
2271 }
2272 }
2273
2274 // An await's destroy case: by now each cir.coro.cleanup has already gone
2275 // through the enclosing cleanup scopes (running their cleanups) and sits
2276 // directly in the coroutine. Replace it with a branch to the destroy region,
2277 // which frees the frame and then reaches the exit region.
2278 void lowerCoroCleanups(mlir::PatternRewriter &rewriter,
2279 llvm::ArrayRef<cir::CoroCleanupOp> coroCleanupOps,
2280 mlir::Block *beforeDestroy) const {
2281 for (cir::CoroCleanupOp coroCleanup : coroCleanupOps) {
2282 rewriter.setInsertionPoint(coroCleanup);
2283 rewriter.replaceOpWithNewOp<cir::BrOp>(coroCleanup, beforeDestroy,
2284 mlir::ValueRange{});
2285 }
2286 }
2287
2288public:
2289 using OpRewritePattern<cir::CoroutineOp>::OpRewritePattern;
2290
2291 mlir::LogicalResult
2292 matchAndRewrite(cir::CoroutineOp coroOp,
2293 mlir::PatternRewriter &rewriter) const override {
2294
2296 for (mlir::Region &region : coroOp->getRegions())
2297 if (hasNestedOpsToFlatten(region))
2298 return mlir::failure();
2299
2300 // Collect the await placeholders before the regions are moved. Each
2301 // cir.coro.exit becomes a branch to the exit region, and each
2302 // cir.coro.cleanup a branch to the destroy region.
2303 llvm::SmallVector<cir::CoroExitOp> coroExitsOps;
2304 collectAwaitPlaceholders(coroOp, coroExitsOps);
2305 llvm::SmallVector<cir::CoroCleanupOp> coroCleanupOps;
2306 collectAwaitPlaceholders(coroOp, coroCleanupOps);
2307
2308 // Take the entry blocks before the regions are moved.
2309 mlir::Block *beforeInitial = &coroOp.getInitialSuspend().front();
2310 mlir::Block *beforeBody = &coroOp.getBody().front();
2311 mlir::Block *beforeFinal = &coroOp.getFinalSuspend().front();
2312 mlir::Block *beforeDestroy = &coroOp.getDestroy().front();
2313 mlir::Block *beforeExit = &coroOp.getExit().front();
2314
2315 mlir::Block *coroBlock = rewriter.getInsertionBlock();
2316 // Split the current block before the CoroutineOp to create the inlining
2317 // point.
2318 mlir::Block *remainingOpsBlock =
2319 rewriter.splitBlock(coroBlock, rewriter.getInsertionPoint());
2320
2321 lowerCoroExits(rewriter, coroExitsOps, beforeExit);
2322 lowerCoroCleanups(rewriter, coroCleanupOps, beforeDestroy);
2323
2324 inlineInitialSuspend(rewriter, coroOp, remainingOpsBlock, beforeBody);
2325 inlineBody(rewriter, coroOp, remainingOpsBlock, beforeFinal);
2326 inlineFinalSuspend(rewriter, coroOp, remainingOpsBlock, beforeDestroy);
2327 inlineDestroy(rewriter, coroOp, remainingOpsBlock, beforeExit);
2328 inlineExit(rewriter, coroOp, remainingOpsBlock);
2329
2330 rewriter.setInsertionPointToEnd(coroBlock);
2331 cir::BrOp::create(rewriter, coroOp.getLoc(), beforeInitial,
2332 mlir::ValueRange{});
2333
2334 auto func = coroOp->getParentOfType<cir::FuncOp>();
2335 // The coroutine regions have been flattened, so the original coroutine
2336 // attribute no longer verifies. Preserve the information that this function
2337 // originated from a coroutine for LLVM lowering.
2338 func.setCoroutineKind(cir::CoroutineKind::FlattenedCoroutine);
2339
2340 rewriter.eraseOp(coroOp);
2341 return mlir::success();
2342 }
2343};
2344
2345class CIRAwaitOpFlattening : public mlir::OpRewritePattern<cir::AwaitOp> {
2346 // The ready region ends in a cir.condition that holds the result of
2347 // `await_ready()`. Replace it with a cir.brcond: if the awaiter is already
2348 // ready, skip the suspension and go straight to the resume region
2349 // otherwise go to the suspend region.
2350 void flattenReadyRegion(mlir::PatternRewriter &rewriter, cir::AwaitOp awaitOp,
2351 mlir::Block *resumeEntry, mlir::Block *suspendEntry,
2352 mlir::Block *remainingOpsBlock) const {
2353 mlir::Region &readyRegion = awaitOp.getReady();
2354 auto conditionOp =
2355 cast<cir::ConditionOp>(readyRegion.back().getTerminator());
2356 rewriter.setInsertionPoint(conditionOp);
2357 rewriter.replaceOpWithNewOp<cir::BrCondOp>(
2358 conditionOp, conditionOp.getCondition(), resumeEntry, suspendEntry);
2359 rewriter.inlineRegionBefore(readyRegion, remainingOpsBlock);
2360 }
2361
2362 // The suspend region runs `await_suspend()` and ends in a
2363 // cir.coro.suspend_point. Emit cir.coro.intrinsic.save and
2364 // cir.coro.intrinsic.suspend around it, and replace the suspend point with a
2365 // cir.switch.flat on the suspend result, which decides whether the coroutine
2366 // resumes, is destroyed or stays suspended.
2367 void flattenSuspendRegion(mlir::PatternRewriter &rewriter,
2368 cir::AwaitOp awaitOp, mlir::Block *resumeEntry,
2369 mlir::Block *remainingOpsBlock) const {
2370 mlir::Region &suspendRegion = awaitOp.getSuspend();
2371 auto suspendPoint =
2372 cast<cir::CoroSuspendPoint>(suspendRegion.back().getTerminator());
2373 cir::CoroSuspendOp coroSuspendIntri =
2374 emitCoroSuspend(rewriter, awaitOp, suspendPoint);
2375
2376 // Inline the suspend region before creating the destroy and suspended
2377 // blocks, so they are placed right after it, in front of the continuation.
2378 rewriter.inlineRegionBefore(suspendRegion, remainingOpsBlock);
2379
2380 mlir::Location loc = awaitOp.getLoc();
2381 mlir::Block *cleanupBlock =
2382 createDestroyBlock(rewriter, loc, remainingOpsBlock);
2383 mlir::Block *retBlock =
2384 createSuspendedBlock(rewriter, loc, remainingOpsBlock);
2385
2386 // 0 resumes, 1 destroys and the default (-1) stays suspended.
2387 llvm::SmallVector<mlir::APInt> caseValues{mlir::APInt(8, 0),
2388 mlir::APInt(8, 1)};
2389 llvm::SmallVector<mlir::Block *> caseDestinations{resumeEntry,
2390 cleanupBlock};
2391 llvm::SmallVector<mlir::ValueRange> caseOperands{caseValues.size()};
2392
2393 // Dispatch to the appropriate coroutine path based on the result of
2394 // `cir.coro.intrinsic.suspend`: resume, destroy, or suspend.
2395 rewriter.setInsertionPoint(suspendPoint);
2396 rewriter.replaceOpWithNewOp<cir::SwitchFlatOp>(
2397 suspendPoint, coroSuspendIntri.getResult(), retBlock,
2398 mlir::ValueRange{}, caseValues, caseDestinations, caseOperands);
2399 }
2400
2401 // Emit cir.coro.intrinsic.save at the start of the suspend region and
2402 // cir.coro.intrinsic.suspend right before its cir.coro.suspend_point.
2403 cir::CoroSuspendOp emitCoroSuspend(mlir::PatternRewriter &rewriter,
2404 cir::AwaitOp awaitOp,
2405 cir::CoroSuspendPoint suspendPoint) const {
2406 mlir::Location loc = awaitOp.getLoc();
2407
2408 // cir.coro.intrinsic.suspend takes the token produced by
2409 // cir.coro.intrinsic.save as its first argument, so emit the save first.
2410 rewriter.setInsertionPointToStart(&awaitOp.getSuspend().front());
2411
2412 // The save goes first in the suspend region, before await_suspend()
2413 // runs, so the coroutine is already considered suspended (and can be
2414 // resumed) from that point.
2415 auto voidPtrTy = cir::PointerType::get(cir::VoidType::get(getContext()));
2416 auto nullPtr = cir::ConstantOp::create(
2417 rewriter, loc,
2418 cir::ConstPtrAttr::get(voidPtrTy, rewriter.getI64IntegerAttr(0)));
2419 auto coroSave = cir::CoroSaveOp::create(rewriter, loc, nullPtr);
2420 rewriter.setInsertionPoint(suspendPoint);
2421 if (suspendPoint.getCond() != nullptr) {
2422 auto *oldinsertpoint = rewriter.getInsertionBlock();
2423 mlir::Block *suspendBlock = rewriter.splitBlock(
2424 rewriter.getInsertionBlock(), rewriter.getInsertionPoint());
2425 mlir::OpBuilder::InsertionGuard guard(rewriter);
2426 rewriter.setInsertionPointToEnd(oldinsertpoint);
2427 cir::BrCondOp::create(rewriter, loc, suspendPoint.getCond(), suspendBlock,
2428 &awaitOp.getResume().front(), mlir::ValueRange{},
2429 mlir::ValueRange{});
2430 }
2431 rewriter.setInsertionPoint(suspendPoint);
2432
2433 // The second argument to `cir.coro.intrinsic.suspend` indicates whether
2434 // this is the final suspend point. Coroutines suspended at a final suspend
2435 // point are considered done (`cir.coro.intrinsic.done` returns true) and
2436 // may only be destroyed; resuming them is undefined behavior.
2437 bool isFinalSuspend = awaitOp.getKind() == cir::AwaitKind::Final;
2438 auto isFinalCoroSuspend = cir::ConstantOp::create(
2439 rewriter, loc, cir::BoolAttr::get(getContext(), isFinalSuspend));
2440
2441 // cir.coro.intrinsic.suspend returns:
2442 // -1 : coroutine suspended
2443 // 0 : coroutine resumed
2444 // 1 : coroutine destroyed
2445 return cir::CoroSuspendOp::create(
2446 rewriter, loc,
2447 mlir::ValueRange{coroSave.getResult(), isFinalCoroSuspend});
2448 }
2449
2450 // Destroy case. Classic codegen uses EmitBranchThroughCleanup() so every
2451 // enclosing cleanup runs before the frame is freed. Here the destroy block
2452 // ends in a cir.coro.cleanup which, like cir.co_return, leaves every
2453 // enclosing cir.cleanup.scope (and loop) through its cleanup. Once it reaches
2454 // the coroutine, CIRCoroutineFlattening turns it into a branch to the
2455 // destroy region, which frees the frame (cir.coro.intrinsic.free) and then
2456 // reaches the exit region.
2457 mlir::Block *createDestroyBlock(mlir::PatternRewriter &rewriter,
2458 mlir::Location loc,
2459 mlir::Block *insertBefore) const {
2460 mlir::OpBuilder::InsertionGuard guard(rewriter);
2461 mlir::Block *cleanupBlock = rewriter.createBlock(insertBefore);
2462 cir::CoroCleanupOp::create(rewriter, loc);
2463 return cleanupBlock;
2464 }
2465
2466 // Suspended case: cir.coro.exit, which CIRCoroutineFlattening turns into
2467 // a branch to the exit region.
2468 mlir::Block *createSuspendedBlock(mlir::PatternRewriter &rewriter,
2469 mlir::Location loc,
2470 mlir::Block *insertBefore) const {
2471 mlir::OpBuilder::InsertionGuard guard(rewriter);
2473 mlir::Block *retBlock = rewriter.createBlock(insertBefore);
2474 cir::CoroExitOp::create(rewriter, loc);
2475 return retBlock;
2476 }
2477
2478 void flattenResumeRegion(mlir::PatternRewriter &rewriter,
2479 cir::AwaitOp awaitOp,
2480 mlir::Block *remainingOpsBlock) const {
2481 mlir::Region &resumeRegion = awaitOp.getResume();
2482 auto resumeYield = cast<cir::YieldOp>(resumeRegion.back().getTerminator());
2483 rewriter.setInsertionPoint(resumeYield);
2484 // Once the coroutine resumes, continue with the operations that
2485 // originally followed the await.
2486 rewriter.replaceOpWithNewOp<cir::BrOp>(resumeYield, remainingOpsBlock);
2487 rewriter.inlineRegionBefore(resumeRegion, remainingOpsBlock);
2488 }
2489
2490public:
2491 using OpRewritePattern<cir::AwaitOp>::OpRewritePattern;
2492
2493 // Flatten `cir.await` into the following control flow:
2494 //
2495 // awaitBlock
2496 // |
2497 // v
2498 // ready await_ready()
2499 // true / \ false
2500 // | v
2501 // | suspend await_suspend(), with
2502 // | / | \ cir.coro.intrinsic.save/suspend
2503 // | 0/ 1| \-1 (default)
2504 // v v v v
2505 // resume destroy suspended
2506 // | cir.coro cir.coro.exit
2507 // | .cleanup
2508 // v
2509 // continuation ops after the await
2510 //
2511 // `destroy` leaves through the enclosing cleanups to the coroutine's destroy
2512 // region (see createDestroyBlock), and `suspended` goes straight to its exit
2513 // region (see CIRCoroutineFlattening).
2514 mlir::LogicalResult
2515 matchAndRewrite(cir::AwaitOp awaitOp,
2516 mlir::PatternRewriter &rewriter) const override {
2517 // Nested ops (cir.if, cir.scope, cir.try, cleanup scopes, ...) must be
2518 // flattened first. Fail here so the rewriter processes them before
2519 // flattening this op.
2520 for (mlir::Region &region : awaitOp->getRegions())
2521 if (hasNestedOpsToFlatten(region))
2522 return mlir::failure();
2523
2524 mlir::Block *awaitBlock = rewriter.getInsertionBlock();
2525 // Split the current block before the AwaitOp to create the inlining
2526 // point.
2527 mlir::Block *remainingOpsBlock =
2528 rewriter.splitBlock(awaitBlock, rewriter.getInsertionPoint());
2529
2530 // Take the entry blocks before the regions are inlined.
2531 mlir::Block *readyEntry = &awaitOp.getReady().front();
2532 mlir::Block *suspendEntry = &awaitOp.getSuspend().front();
2533 mlir::Block *resumeEntry = &awaitOp.getResume().front();
2534
2535 flattenReadyRegion(rewriter, awaitOp, resumeEntry, suspendEntry,
2536 remainingOpsBlock);
2537
2538 // After inlining the ready region, branch from the original await block
2539 // to the beginning of the inlined ready region.
2540 rewriter.setInsertionPointToEnd(awaitBlock);
2541 cir::BrOp::create(rewriter, awaitOp.getLoc(), readyEntry);
2542
2543 flattenSuspendRegion(rewriter, awaitOp, resumeEntry, remainingOpsBlock);
2544 flattenResumeRegion(rewriter, awaitOp, remainingOpsBlock);
2545
2546 rewriter.eraseOp(awaitOp);
2547 return mlir::success();
2548 }
2549};
2550
2551void populateFlattenCFGPatterns(RewritePatternSet &patterns) {
2552 patterns
2553 .add<CIRIfFlattening, CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening,
2554 CIRSwitchOpFlattening, CIRTernaryOpFlattening,
2555 CIRCleanupScopeOpFlattening, CIRTryOpFlattening,
2556 CIRAwaitOpFlattening, CIRCoroutineFlattening>(patterns.getContext());
2557}
2558
2559namespace {
2560// An implementation of RewriterBase::Listener that determines whether the IR
2561// has been modified since the last time it was 'reset'. At the moment, this is
2562// the only use for something like this, but we might wish to move this
2563// somewhere if someone else needs similar functionality in the future.
2564class MLIRChangedListener final : public mlir::RewriterBase::Listener {
2565 bool hasChanged = false;
2566
2567public:
2568 void reset() { hasChanged = false; }
2569
2570 bool changed() const { return hasChanged; }
2571
2572 void notifyBlockErased(Block *) override { hasChanged = true; }
2573 void notifyOperationModified(Operation *) override { hasChanged = true; }
2574 void notifyOperationReplaced(Operation *, Operation *) override {
2575 hasChanged = true;
2576 }
2577 void notifyOperationReplaced(Operation *, ValueRange) override {
2578 hasChanged = true;
2579 }
2580 void notifyOperationErased(Operation *) override { hasChanged = true; }
2581
2582 // notifyPatternBegin, notifyPatternEnd, notifyMatchFailure all skipped, since
2583 // they don't modify.
2584 void notifyOperationInserted(Operation *,
2585 mlir::IRRewriter::InsertPoint) override {
2586 hasChanged = true;
2587 }
2588 void notifyBlockInserted(Block *, Region *, Region::iterator) override {
2589 hasChanged = true;
2590 }
2591};
2592} // namespace
2593
2594void CIRFlattenCFGPass::runOnOperation() {
2595 RewritePatternSet patternList(&getContext());
2596 populateFlattenCFGPatterns(patternList);
2597 FrozenRewritePatternSet patterns(std::move(patternList));
2598
2599 PatternApplicator applicator(patterns);
2600 // We need _A_ cost model, and everything here is the same cost-model, so this
2601 // is effectively a no-op, but necessary to use the PatternApplicator.
2602 applicator.applyDefaultCostModel();
2603
2604 mlir::PatternRewriter rewriter(&getContext());
2605 MLIRChangedListener changedListener;
2606 rewriter.setListener(&changedListener);
2607
2608 do {
2609 changedListener.reset();
2610
2611 // Collect flatten candidates post-order so an inner op is handled before
2612 // its parent; op pointers stay valid across the block splits / region
2613 // inlines the patterns perform (a pattern only erases the matched op and
2614 // its descendants, which are visited first), so the list can be iterated
2615 // directly.
2616 llvm::SmallVector<Operation *, 16> ops;
2617 getOperation()->walk<mlir::WalkOrder::PostOrder>([&](Operation *op) {
2618 if (isa<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp,
2619 CleanupScopeOp, TryOp, AwaitOp, CoroutineOp>(op))
2620 ops.push_back(op);
2621 });
2622
2623 for (mlir::Operation *op : ops) {
2624 rewriter.setInsertionPoint(op);
2625 (void)applicator.matchAndRewrite(op, rewriter);
2626 }
2627 } while (changedListener.changed());
2628}
2629
2630} // namespace
2631
2632namespace mlir {
2633
2634std::unique_ptr<Pass> createCIRFlattenCFGPass() {
2635 return std::make_unique<CIRFlattenCFGPass>();
2636}
2637
2638} // namespace mlir
mlir::Block * replaceThrowWithTryThrow(cir::ThrowOp throwOp, mlir::Block *unwindDest, mlir::Location loc, mlir::RewriterBase &rewriter)
Replace a cir::ThrowOp with a cir::TryThrowOp whose unwind destination is unwindDest.
mlir::Block * replaceCallWithTryCall(cir::CallOp callOp, mlir::Block *unwindDest, mlir::Location loc, mlir::RewriterBase &rewriter)
Replace a cir::CallOp with a cir::TryCallOp whose unwind destination is unwindDest.
tooling::Replacements cleanup(const FormatStyle &Style, StringRef Code, ArrayRef< tooling::Range > Ranges, StringRef FileName="<stdin>")
Clean up any erroneous/redundant code in the given Ranges in Code.
Definition Format.cpp:4563
llvm::APInt APInt
Definition FixedPoint.h:19
ASTEdit insertBefore(RangeSelector S, TextGenerator Replacement)
Inserts Replacement before S, leaving the source selected by \S unchanged.
@ Default
Set to the current date and time.
unsigned long uint64_t
std::unique_ptr< Pass > createCIRFlattenCFGPass()
int const char * function
Definition c++config.h:31
float __ovld __cnfn step(float, float)
Returns 0.0 if x < edge, otherwise it returns 1.0.
static bool stackSaveOp()
static bool coroGetRetObjManager()
static bool coroUnwindRegion()