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