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