clang 23.0.0git
EHABILowering.cpp
Go to the documentation of this file.
1//===- EHABILowering.cpp - Lower flattened CIR EH ops to ABI-specific form ===//
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 a pass that lowers ABI-agnostic flattened CIR exception
10// handling operations into an ABI-specific form. Currently only the Itanium
11// C++ ABI is supported.
12//
13// The Itanium ABI lowering performs these transformations:
14// - cir.eh.initiate → cir.eh.inflight_exception (landing pad)
15// - cir.eh.dispatch → cir.eh.typeid + cir.cmp + cir.brcond chains
16// - cir.begin_cleanup → (removed)
17// - cir.end_cleanup → (removed)
18// - cir.begin_catch → call to __cxa_begin_catch
19// - cir.end_catch → call to __cxa_end_catch
20// - cir.resume → cir.resume.flat
21// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
22// - personality function set on functions requiring EH
23//
24//===----------------------------------------------------------------------===//
25
26#include "PassDetail.h"
27#include "mlir/IR/Builders.h"
32#include "llvm/ADT/DenseMap.h"
33#include "llvm/ADT/SmallVector.h"
34#include "llvm/TargetParser/Triple.h"
35
36using namespace mlir;
37using namespace cir;
38
39namespace mlir {
40#define GEN_PASS_DEF_CIREHABILOWERING
41#include "clang/CIR/Dialect/Passes.h.inc"
42} // namespace mlir
43
44namespace {
45
46//===----------------------------------------------------------------------===//
47// Shared utilities
48//===----------------------------------------------------------------------===//
49
50/// Ensure a function with the given name and type exists in the module. If it
51/// does not exist, create a private external declaration.
52static cir::FuncOp getOrCreateRuntimeFuncDecl(mlir::ModuleOp mod,
53 mlir::Location loc,
54 StringRef name,
55 cir::FuncType funcTy) {
56 if (auto existing = mod.lookupSymbol<cir::FuncOp>(name))
57 return existing;
58
59 mlir::OpBuilder builder(mod.getContext());
60 builder.setInsertionPointToEnd(mod.getBody());
61 auto funcOp = cir::FuncOp::create(builder, loc, name, funcTy);
62 funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
63 funcOp.setPrivate();
64 return funcOp;
65}
66
67//===----------------------------------------------------------------------===//
68// EH ABI Lowering Base Class
69//===----------------------------------------------------------------------===//
70
71/// Abstract base class for exception-handling ABI lowering.
72/// Each supported ABI (Itanium, Microsoft, etc.) provides a concrete subclass.
73class EHABILowering {
74public:
75 explicit EHABILowering(mlir::ModuleOp mod)
76 : mod(mod), ctx(mod.getContext()), builder(ctx) {}
77 virtual ~EHABILowering() = default;
78
79 /// Lower all EH operations in the module to an ABI-specific form.
80 virtual mlir::LogicalResult run() = 0;
81
82protected:
83 mlir::ModuleOp mod;
84 mlir::MLIRContext *ctx;
85 mlir::OpBuilder builder;
86};
87
88//===----------------------------------------------------------------------===//
89// Itanium EH ABI Lowering
90//===----------------------------------------------------------------------===//
91
92/// Lowers flattened CIR EH operations to the Itanium C++ ABI form.
93///
94/// The entry point is run(), which iterates over all functions and
95/// calls lowerFunc() for each. lowerFunc() drives all lowering from
96/// cir.eh.initiate operations: every other EH op (begin/end_cleanup,
97/// eh.dispatch, begin/end_catch, resume) is reachable by tracing the
98/// eh_token produced by the initiate through its users.
99class ItaniumEHLowering : public EHABILowering {
100public:
101 using EHABILowering::EHABILowering;
102 mlir::LogicalResult run() override;
103
104private:
105 /// Maps a !cir.eh_token value to its Itanium ABI replacement pair:
106 /// an exception pointer (!cir.ptr<!void>) and a type id (!u32i).
107 using EhTokenMap = DenseMap<mlir::Value, std::pair<mlir::Value, mlir::Value>>;
108
109 cir::VoidType voidType;
110 cir::PointerType voidPtrType;
111 cir::PointerType u8PtrType;
112 cir::IntType u32Type;
113
114 // Cached runtime function declarations, initialized when needed by
115 // ensureRuntimeDecls().
116 cir::FuncOp personalityFunc;
117 cir::FuncOp beginCatchFunc;
118 cir::FuncOp endCatchFunc;
119
120 constexpr const static ::llvm::StringLiteral kGxxPersonality =
121 "__gxx_personality_v0";
122
123 void ensureRuntimeDecls(mlir::Location loc);
124 mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
125 void lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
126 SmallVectorImpl<mlir::Operation *> &deadOps);
127 void lowerDispatch(cir::EhDispatchOp dispatch, mlir::Value exnPtr,
128 mlir::Value typeId,
129 SmallVectorImpl<mlir::Operation *> &deadOps);
130};
131
132/// Lower all EH operations in the module to the Itanium-specific form.
133mlir::LogicalResult ItaniumEHLowering::run() {
134 // Pre-compute the common types used throughout all function lowerings.
135 // TODO(cir): Move these to the base class if they are also needed for MSVC.
136 voidType = cir::VoidType::get(ctx);
137 voidPtrType = cir::PointerType::get(voidType);
138 auto u8Type = cir::IntType::get(ctx, 8, /*isSigned=*/false);
139 u8PtrType = cir::PointerType::get(u8Type);
140 u32Type = cir::IntType::get(ctx, 32, /*isSigned=*/false);
141
142 for (cir::FuncOp funcOp : mod.getOps<cir::FuncOp>()) {
143 if (mlir::failed(lowerFunc(funcOp)))
144 return mlir::failure();
145 }
146 return mlir::success();
147}
148
149/// Ensure the necessary Itanium runtime function declarations exist in the
150/// module.
151void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
152 // TODO(cir): Handle other personality functions. This probably isn't needed
153 // here if we fix codegen to always set the personality function.
154 if (!personalityFunc) {
155 auto s32Type = cir::IntType::get(ctx, 32, /*isSigned=*/true);
156 auto personalityFuncTy = cir::FuncType::get({}, s32Type, /*isVarArg=*/true);
157 personalityFunc = getOrCreateRuntimeFuncDecl(mod, loc, kGxxPersonality,
158 personalityFuncTy);
159 }
160
161 if (!beginCatchFunc) {
162 auto beginCatchFuncTy =
163 cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
164 beginCatchFunc = getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_begin_catch",
165 beginCatchFuncTy);
166 }
167
168 if (!endCatchFunc) {
169 auto endCatchFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
170 endCatchFunc =
171 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", endCatchFuncTy);
172 }
173}
174
175/// Lower all EH operations in a single function.
176mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
177 if (funcOp.isDeclaration())
178 return mlir::success();
179
180 // All EH lowering follows from cir.eh.initiate operations. The token each
181 // initiate produces connects it to every other EH op in the function
182 // (begin/end_cleanup, eh.dispatch, begin/end_catch, resume) through the
183 // token graph. A single walk to collect initiates is therefore sufficient.
184 SmallVector<cir::EhInitiateOp> initiateOps;
185 funcOp.walk([&](cir::EhInitiateOp op) { initiateOps.push_back(op); });
186 if (initiateOps.empty())
187 return mlir::success();
188
189 ensureRuntimeDecls(funcOp.getLoc());
190
191 // Set the personality function if it is not already set.
192 // TODO(cir): The personality function should already have been set by this
193 // point. If we've seen a try operation, it will have been set by
194 // emitCXXTryStmt. If we only have cleanups, it may not have been set. We
195 // need to fix that in CodeGen. This is a placeholder until that is done.
196 if (!funcOp.getPersonality())
197 funcOp.setPersonality(kGxxPersonality);
198
199 // Lower each initiate and all EH ops connected to it. The token map is
200 // shared across all initiate operations. Multiple initiates may flow into the
201 // same dispatch block, and the map ensures the arguments are registered
202 // only once. Dispatch ops are scheduled for deferred removal so that sibling
203 // initiates can still read catch types from a shared dispatch.
204 EhTokenMap ehTokenMap;
205 SmallVector<mlir::Operation *> deadOps;
206 for (cir::EhInitiateOp initiateOp : initiateOps)
207 lowerEhInitiate(initiateOp, ehTokenMap, deadOps);
208
209 // Erase operations that were deferred during per-initiate processing
210 // (dispatch ops whose catch types were read by multiple initiates).
211 for (mlir::Operation *op : deadOps)
212 op->erase();
213
214 // Remove the !cir.eh_token block arguments that were replaced by (ptr, u32)
215 // pairs. Iterate in reverse to preserve argument indices during removal.
216 for (mlir::Block &block : funcOp.getBody()) {
217 for (int i = block.getNumArguments() - 1; i >= 0; --i) {
218 if (mlir::isa<cir::EhTokenType>(block.getArgument(i).getType()))
219 block.eraseArgument(i);
220 }
221 }
222
223 return mlir::success();
224}
225
226/// Lower all EH operations connected to a single cir.eh.initiate.
227///
228/// The cir.eh.initiate is the root of a token graph. The token it produces
229/// flows through branch edges to consuming operations:
230///
231/// cir.eh.initiate → (via cir.br) → cir.begin_cleanup
232/// → cir.end_cleanup (via cleanup_token)
233/// → (via cir.br) → cir.eh.dispatch
234/// → (successors) →
235/// cir.begin_catch
236/// → cir.end_catch
237/// (via catch_token)
238/// → cir.resume
239///
240/// A single traversal of the token graph discovers and processes every
241/// connected op inline. The inflight_exception is created up-front without
242/// a catch_type_list; when the dispatch is encountered during traversal,
243/// the catch types are read and set on the inflight op.
244///
245/// Dispatch ops are not erased during per-initiate processing because they may
246/// be used by other initiate ops that haven't yet been lowered. Instead they
247/// are added to \p deadOps and erased by the caller after all initiates have
248/// been lowered.
249///
250/// \p ehTokenMap is shared across all initiates in the function so that block
251/// arguments reachable from multiple sibling initiates are registered once.
252void ItaniumEHLowering::lowerEhInitiate(
253 cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
254 SmallVectorImpl<mlir::Operation *> &deadOps) {
255 mlir::Value rootToken = initiateOp.getEhToken();
256
257 // Create the inflight_exception without a catch_type_list. The catch types
258 // will be set once we encounter the dispatch during the traversal below.
259 builder.setInsertionPoint(initiateOp);
260 auto inflightOp = cir::EhInflightOp::create(
261 builder, initiateOp.getLoc(), /*cleanup=*/initiateOp.getCleanup(),
262 /*catch_type_list=*/mlir::ArrayAttr{});
263
264 ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
265 inflightOp.getTypeId()};
266
267 // Single traversal of the token graph. For each token value (the root token
268 // or a block argument that carries it), we snapshot its users, register
269 // (ptr, u32) replacement arguments on successor blocks, then process every
270 // user inline. This avoids collecting ops into separate vectors.
271 SmallVector<mlir::Value> worklist;
272 SmallPtrSet<mlir::Value, 8> visited;
273 worklist.push_back(rootToken);
274
275 while (!worklist.empty()) {
276 mlir::Value current = worklist.pop_back_val();
277 if (!visited.insert(current).second)
278 continue;
279
280 // Snapshot users before modifying any of them (erasing ops during
281 // iteration would invalidate the use-list iterator).
282 SmallVector<mlir::Operation *> users;
283 for (mlir::OpOperand &use : current.getUses())
284 users.push_back(use.getOwner());
285
286 // Register replacement block arguments on successor blocks (extending the
287 // worklist), then lower the op itself.
288 for (mlir::Operation *user : users) {
289 // Trace into successor blocks to register (ptr, u32) replacement
290 // arguments for any !cir.eh_token block arguments found there. Even
291 // if a block arg was already registered by a sibling initiate, it is
292 // still added to the worklist so that the traversal can reach the
293 // shared dispatch to read catch types.
294 for (unsigned s = 0; s < user->getNumSuccessors(); ++s) {
295 mlir::Block *succ = user->getSuccessor(s);
296 for (mlir::BlockArgument arg : succ->getArguments()) {
297 if (!mlir::isa<cir::EhTokenType>(arg.getType()))
298 continue;
299 if (!ehTokenMap.count(arg)) {
300 mlir::Value ptrArg = succ->addArgument(voidPtrType, arg.getLoc());
301 mlir::Value u32Arg = succ->addArgument(u32Type, arg.getLoc());
302 ehTokenMap[arg] = {ptrArg, u32Arg};
303 }
304 worklist.push_back(arg);
305 }
306 }
307
308 if (auto op = mlir::dyn_cast<cir::BeginCleanupOp>(user)) {
309 // begin_cleanup / end_cleanup are no-ops for Itanium. Erase the
310 // end_cleanup first (drops the cleanup_token use) then the begin.
311 for (auto &tokenUsers :
312 llvm::make_early_inc_range(op.getCleanupToken().getUses())) {
313 if (auto endOp =
314 mlir::dyn_cast<cir::EndCleanupOp>(tokenUsers.getOwner()))
315 endOp.erase();
316 }
317 op.erase();
318 } else if (auto op = mlir::dyn_cast<cir::BeginCatchOp>(user)) {
319 // Replace end_catch → __cxa_end_catch (drops the catch_token use),
320 // then replace begin_catch → __cxa_begin_catch.
321 for (auto &tokenUsers :
322 llvm::make_early_inc_range(op.getCatchToken().getUses())) {
323 if (auto endOp =
324 mlir::dyn_cast<cir::EndCatchOp>(tokenUsers.getOwner())) {
325 builder.setInsertionPoint(endOp);
326 cir::CallOp::create(builder, endOp.getLoc(),
327 mlir::FlatSymbolRefAttr::get(endCatchFunc),
328 voidType, mlir::ValueRange{});
329 endOp.erase();
330 }
331 }
332
333 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
334 builder.setInsertionPoint(op);
335 auto callOp = cir::CallOp::create(
336 builder, op.getLoc(), mlir::FlatSymbolRefAttr::get(beginCatchFunc),
337 u8PtrType, mlir::ValueRange{exnPtr});
338 mlir::Value castResult = callOp.getResult();
339 mlir::Type expectedPtrType = op.getExnPtr().getType();
340 if (castResult.getType() != expectedPtrType)
341 castResult =
342 cir::CastOp::create(builder, op.getLoc(), expectedPtrType,
343 cir::CastKind::bitcast, callOp.getResult());
344 op.getExnPtr().replaceAllUsesWith(castResult);
345 op.erase();
346 } else if (auto op = mlir::dyn_cast<cir::EhDispatchOp>(user)) {
347 // Read catch types from the dispatch and set them on the inflight op.
348 mlir::ArrayAttr catchTypes = op.getCatchTypesAttr();
349 if (catchTypes && catchTypes.size() > 0) {
350 SmallVector<mlir::Attribute> typeSymbols;
351 for (mlir::Attribute attr : catchTypes)
352 typeSymbols.push_back(
353 mlir::cast<cir::GlobalViewAttr>(attr).getSymbol());
354 inflightOp.setCatchTypeListAttr(builder.getArrayAttr(typeSymbols));
355 }
356 // Only lower the dispatch once. A sibling initiate sharing the same
357 // dispatch will still read its catch types (above), but the comparison
358 // chain and branch replacement are only created the first time.
359 if (!llvm::is_contained(deadOps, op.getOperation())) {
360 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
361 lowerDispatch(op, exnPtr, typeId, deadOps);
362 }
363 } else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
364 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
365 builder.setInsertionPoint(op);
366 cir::ResumeFlatOp::create(builder, op.getLoc(), exnPtr, typeId);
367 op.erase();
368 } else if (auto op = mlir::dyn_cast<cir::BrOp>(user)) {
369 // Replace eh_token operands with the (ptr, u32) pair.
370 SmallVector<mlir::Value> newOperands;
371 bool changed = false;
372 for (mlir::Value operand : op.getDestOperands()) {
373 auto it = ehTokenMap.find(operand);
374 if (it != ehTokenMap.end()) {
375 newOperands.push_back(it->second.first);
376 newOperands.push_back(it->second.second);
377 changed = true;
378 } else {
379 newOperands.push_back(operand);
380 }
381 }
382 if (changed) {
383 builder.setInsertionPoint(op);
384 cir::BrOp::create(builder, op.getLoc(), op.getDest(), newOperands);
385 op.erase();
386 }
387 }
388 }
389 }
390
391 initiateOp.erase();
392}
393
394/// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
395/// The dispatch itself is replaced with a branch to the first comparison
396/// block and added to deadOps for deferred removal.
397void ItaniumEHLowering::lowerDispatch(
398 cir::EhDispatchOp dispatch, mlir::Value exnPtr, mlir::Value typeId,
399 SmallVectorImpl<mlir::Operation *> &deadOps) {
400 mlir::Location dispLoc = dispatch.getLoc();
401 mlir::Block *defaultDest = dispatch.getDefaultDestination();
402 mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr();
403 mlir::SuccessorRange catchDests = dispatch.getCatchDestinations();
404 mlir::Block *dispatchBlock = dispatch->getBlock();
405
406 // Build the comparison chain in new blocks inserted after the dispatch's
407 // block. The dispatch itself is replaced with a branch to the first
408 // comparison block and scheduled for deferred removal.
409 if (!catchTypes || catchTypes.empty()) {
410 // No typed catches: replace dispatch with a direct branch.
411 builder.setInsertionPoint(dispatch);
412 cir::BrOp::create(builder, dispLoc, defaultDest,
413 mlir::ValueRange{exnPtr, typeId});
414 } else {
415 unsigned numCatches = catchTypes.size();
416
417 // Create and populate comparison blocks in reverse order so that each
418 // block's false destination (the next comparison block, or defaultDest
419 // for the last one) is already available. Each createBlock inserts
420 // before the previous one, so the blocks end up in forward order.
421 mlir::Block *insertBefore = dispatchBlock->getNextNode();
422 mlir::Block *falseDest = defaultDest;
423 mlir::Block *firstCmpBlock = nullptr;
424 for (int i = numCatches - 1; i >= 0; --i) {
425 auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, u32Type},
426 {dispLoc, dispLoc});
427
428 mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
429 mlir::Value cmpTypeId = cmpBlock->getArgument(1);
430
431 auto globalView = mlir::cast<cir::GlobalViewAttr>(catchTypes[i]);
432 auto ehTypeIdOp =
433 cir::EhTypeIdOp::create(builder, dispLoc, globalView.getSymbol());
434 auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::eq,
435 cmpTypeId, ehTypeIdOp.getTypeId());
436
437 cir::BrCondOp::create(builder, dispLoc, cmpOp, catchDests[i], falseDest,
438 mlir::ValueRange{cmpExnPtr, cmpTypeId},
439 mlir::ValueRange{cmpExnPtr, cmpTypeId});
440
441 insertBefore = cmpBlock;
442 falseDest = cmpBlock;
443 firstCmpBlock = cmpBlock;
444 }
445
446 // Replace the dispatch with a branch to the first comparison block.
447 builder.setInsertionPoint(dispatch);
448 cir::BrOp::create(builder, dispLoc, firstCmpBlock,
449 mlir::ValueRange{exnPtr, typeId});
450 }
451
452 // Schedule the dispatch for deferred removal. We cannot erase it now because
453 // a sibling initiate that shares this dispatch may still need to read its
454 // catch types.
455 deadOps.push_back(dispatch);
456}
457
458//===----------------------------------------------------------------------===//
459// The Pass
460//===----------------------------------------------------------------------===//
461
462struct CIREHABILoweringPass
463 : public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
464 CIREHABILoweringPass() = default;
465 void runOnOperation() override;
466};
467
468void CIREHABILoweringPass::runOnOperation() {
469 auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
470
471 // The target triple is attached to the module as the "cir.triple" attribute.
472 // If it is absent (e.g. a CIR module parsed from text without a triple) we
473 // cannot determine the ABI and must skip the pass.
474 auto tripleAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(
475 mod->getAttr(cir::CIRDialect::getTripleAttrName()));
476 if (!tripleAttr) {
477 mod.emitError("Module has no target triple");
478 return;
479 }
480
481 // Select the ABI-specific lowering handler from the triple. The Microsoft
482 // C++ ABI targets a Windows MSVC environment; everything else uses Itanium.
483 // Extend this when Microsoft ABI lowering is added.
484 llvm::Triple triple(tripleAttr.getValue());
485 std::unique_ptr<EHABILowering> lowering;
486 if (triple.isWindowsMSVCEnvironment()) {
487 mod.emitError(
488 "EH ABI lowering is not yet implemented for the Microsoft ABI");
489 return signalPassFailure();
490 } else {
491 lowering = std::make_unique<ItaniumEHLowering>(mod);
492 }
493
494 if (mlir::failed(lowering->run()))
495 return signalPassFailure();
496}
497
498} // namespace
499
500std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
501 return std::make_unique<CIREHABILoweringPass>();
502}
__device__ __2f16 float __ockl_bool s
ASTEdit insertBefore(RangeSelector S, TextGenerator Replacement)
Inserts Replacement before S, leaving the source selected by \S unchanged.
Stencil run(MatchConsumer< std::string > C)
Wraps a MatchConsumer in a Stencil, so that it can be used in a Stencil.
Definition Stencil.cpp:489
RangeSelector name(std::string ID)
Given a node with a "name", (like NamedDecl, DeclRefExpr, CxxCtorInitializer, and TypeLoc) selects th...
std::unique_ptr< Pass > createCIREHABILoweringPass()
__DEVICE__ _Tp arg(const std::complex< _Tp > &__c)