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.eh.terminate → call to __clang_call_terminate + unreachable
21// - cir.resume → cir.resume.flat
22// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
23// - cir.construct_catch_param → __cxa_get_exception_ptr + inlined
24// catch-copy thunk body
25// - personality function set on functions requiring EH
26//
27//===----------------------------------------------------------------------===//
28
29#include "PassDetail.h"
30#include "mlir/IR/Builders.h"
31#include "mlir/IR/IRMapping.h"
32#include "mlir/IR/PatternMatch.h"
39#include "llvm/ADT/DenseMap.h"
40#include "llvm/ADT/SmallVector.h"
41#include "llvm/TargetParser/Triple.h"
42
43using namespace mlir;
44using namespace cir;
45
46namespace mlir {
47#define GEN_PASS_DEF_CIREHABILOWERING
48#include "clang/CIR/Dialect/Passes.h.inc"
49} // namespace mlir
50
51namespace {
52
53//===----------------------------------------------------------------------===//
54// Shared utilities
55//===----------------------------------------------------------------------===//
56
57/// Ensure a function with the given name and type exists in the module. If it
58/// does not exist, create a private external declaration.
59static cir::FuncOp getOrCreateRuntimeFuncDecl(mlir::ModuleOp mod,
60 mlir::Location loc,
61 StringRef name,
62 cir::FuncType funcTy) {
63 if (auto existing = mod.lookupSymbol<cir::FuncOp>(name))
64 return existing;
65
66 mlir::OpBuilder builder(mod.getContext());
67 builder.setInsertionPointToEnd(mod.getBody());
68 auto funcOp = cir::FuncOp::create(builder, loc, name, funcTy);
69 funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
70 funcOp.setPrivate();
71 return funcOp;
72}
73
74//===----------------------------------------------------------------------===//
75// EH ABI Lowering Base Class
76//===----------------------------------------------------------------------===//
77
78/// Abstract base class for exception-handling ABI lowering.
79/// Each supported ABI (Itanium, Microsoft, etc.) provides a concrete subclass.
80class EHABILowering {
81public:
82 explicit EHABILowering(mlir::ModuleOp mod)
83 : mod(mod), ctx(mod.getContext()), builder(ctx) {}
84 virtual ~EHABILowering() = default;
85
86 /// Lower all EH operations in the module to an ABI-specific form.
87 virtual mlir::LogicalResult run() = 0;
88
89protected:
90 mlir::ModuleOp mod;
91 mlir::MLIRContext *ctx;
92 mlir::OpBuilder builder;
93};
94
95//===----------------------------------------------------------------------===//
96// Itanium EH ABI Lowering
97//===----------------------------------------------------------------------===//
98
99/// Lowers flattened CIR EH operations to the Itanium C++ ABI form.
100///
101/// The entry point is run(), which iterates over all functions and
102/// calls lowerFunc() for each. lowerFunc() drives all lowering from
103/// cir.eh.initiate operations: every other EH op (begin/end_cleanup,
104/// eh.dispatch, begin/end_catch, resume) is reachable by tracing the
105/// eh_token produced by the initiate through its users.
106class ItaniumEHLowering : public EHABILowering {
107public:
108 using EHABILowering::EHABILowering;
109 mlir::LogicalResult run() override;
110
111private:
112 /// Maps a !cir.eh_token value to its Itanium ABI replacement pair:
113 /// an exception pointer (!cir.ptr<!void>) and a type id (!u32i).
114 using EhTokenMap = DenseMap<mlir::Value, std::pair<mlir::Value, mlir::Value>>;
115
116 cir::VoidType voidType;
117 cir::PointerType voidPtrType;
118 cir::PointerType u8PtrType;
119 cir::IntType u32Type;
120
121 // Cached runtime function declarations, initialized when needed by
122 // ensureRuntimeDecls().
123 cir::FuncOp personalityFunc;
124 cir::FuncOp beginCatchFunc;
125 cir::FuncOp endCatchFunc;
126 cir::FuncOp getExceptionPtrFunc;
127 cir::FuncOp clangCallTerminateFunc;
128 cir::FuncOp cxaThrowFunc;
129 cir::FuncOp cxaRethrowFunc;
130
131 DenseMap<mlir::StringAttr, cir::FuncOp> catchCopyThunks;
132
133 constexpr const static ::llvm::StringLiteral kGxxPersonality =
134 "__gxx_personality_v0";
135
136 void ensureRuntimeDecls(mlir::Location loc);
137 void ensureClangCallTerminate(mlir::Location loc);
138 void ensureCxaThrowDecl(mlir::Location loc);
139 void ensureCxaRethrowDecl(mlir::Location loc);
140 mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
141 mlir::FailureOr<cir::FuncOp>
142 resolveCatchCopyThunk(cir::ConstructCatchParamOp op);
143 mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
144 mlir::LogicalResult
145 lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
146 SmallVectorImpl<mlir::Operation *> &deadOps);
147 void lowerDispatch(cir::EhDispatchOp dispatch, mlir::Value exnPtr,
148 mlir::Value typeId,
149 SmallVectorImpl<mlir::Operation *> &deadOps);
150 mlir::LogicalResult lowerConstructCatchParam(cir::ConstructCatchParamOp op,
151 mlir::Value exnPtr);
152 void lowerInitCatchParam(cir::InitCatchParamOp op);
153 mlir::LogicalResult lowerTryThrow(cir::TryThrowOp op);
154};
155
156/// Lower all EH operations in the module to the Itanium-specific form.
157mlir::LogicalResult ItaniumEHLowering::run() {
158 // Pre-compute the common types used throughout all function lowerings.
159 // TODO(cir): Move these to the base class if they are also needed for MSVC.
160 voidType = cir::VoidType::get(ctx);
161 voidPtrType = cir::PointerType::get(voidType);
162 auto u8Type = cir::IntType::get(ctx, 8, /*isSigned=*/false);
163 u8PtrType = cir::PointerType::get(u8Type);
164 u32Type = cir::IntType::get(ctx, 32, /*isSigned=*/false);
165
166 for (cir::FuncOp funcOp : mod.getOps<cir::FuncOp>()) {
167 if (mlir::failed(lowerFunc(funcOp)))
168 return mlir::failure();
169 }
170 return mlir::success();
171}
172
173/// Ensure the necessary Itanium runtime function declarations exist in the
174/// module.
175void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
176 // TODO(cir): Handle other personality functions. This probably isn't needed
177 // here if we fix codegen to always set the personality function.
178 if (!personalityFunc) {
179 auto s32Type = cir::IntType::get(ctx, 32, /*isSigned=*/true);
180 auto personalityFuncTy = cir::FuncType::get({}, s32Type, /*isVarArg=*/true);
181 personalityFunc = getOrCreateRuntimeFuncDecl(mod, loc, kGxxPersonality,
182 personalityFuncTy);
183 }
184
185 if (!beginCatchFunc) {
186 auto beginCatchFuncTy =
187 cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
188 beginCatchFunc = getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_begin_catch",
189 beginCatchFuncTy);
190 }
191
192 if (!endCatchFunc) {
193 auto endCatchFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
194 endCatchFunc =
195 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", endCatchFuncTy);
196 }
197
198 if (!getExceptionPtrFunc) {
199 auto getExceptionPtrFuncTy =
200 cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
201 getExceptionPtrFunc = getOrCreateRuntimeFuncDecl(
202 mod, loc, "__cxa_get_exception_ptr", getExceptionPtrFuncTy);
203 }
204}
205
206/// Ensure the __clang_call_terminate function exists in the module. This
207/// function is defined with a body that calls __cxa_begin_catch followed by
208/// std::terminate, matching the behavior of Clang's LLVM IR codegen.
209///
210/// void __clang_call_terminate(void *exn) nounwind noreturn {
211/// __cxa_begin_catch(exn);
212/// std::terminate();
213/// unreachable;
214/// }
215void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
216 if (clangCallTerminateFunc)
217 return;
218
219 ensureRuntimeDecls(loc);
220
221 if (auto existing = mod.lookupSymbol<cir::FuncOp>("__clang_call_terminate")) {
222 clangCallTerminateFunc = existing;
223 return;
224 }
225
226 auto funcTy = cir::FuncType::get({voidPtrType}, voidType, /*isVarArg=*/false);
227 builder.setInsertionPointToEnd(mod.getBody());
228 auto funcOp =
229 cir::FuncOp::create(builder, loc, "__clang_call_terminate", funcTy);
230 funcOp.setLinkage(cir::GlobalLinkageKind::LinkOnceODRLinkage);
231 funcOp.setGlobalVisibility(cir::VisibilityKind::Hidden);
232
233 mlir::Block *entryBlock = funcOp.addEntryBlock();
234 builder.setInsertionPointToStart(entryBlock);
235 mlir::Value exnArg = entryBlock->getArgument(0);
236
237 auto catchCall = cir::CallOp::create(
238 builder, loc, mlir::FlatSymbolRefAttr::get(beginCatchFunc), u8PtrType,
239 mlir::ValueRange{exnArg});
240 catchCall.setNothrowAttr(builder.getUnitAttr());
241
242 auto terminateFuncDecl = getOrCreateRuntimeFuncDecl(
243 mod, loc, "_ZSt9terminatev",
244 cir::FuncType::get({}, voidType, /*isVarArg=*/false));
245 terminateFuncDecl->setAttr(cir::CIRDialect::getNoReturnAttrName(),
246 builder.getUnitAttr());
247 auto terminateCall = cir::CallOp::create(
248 builder, loc, mlir::FlatSymbolRefAttr::get(terminateFuncDecl), voidType,
249 mlir::ValueRange{});
250 terminateCall.setNothrowAttr(builder.getUnitAttr());
251 terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
252 builder.getUnitAttr());
253
254 cir::UnreachableOp::create(builder, loc);
255
256 funcOp->setAttr(cir::CIRDialect::getNoReturnAttrName(),
257 builder.getUnitAttr());
258 clangCallTerminateFunc = funcOp;
259}
260
261/// Ensure the __cxa_throw runtime function is declared in the module.
262///
263/// void __cxa_throw(void *exception, void *type_info, void *dtor);
264void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc) {
265 if (cxaThrowFunc)
266 return;
267 auto throwFuncTy = cir::FuncType::get({voidPtrType, voidPtrType, voidPtrType},
268 voidType, /*isVarArg=*/false);
269 cxaThrowFunc =
270 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_throw", throwFuncTy);
271}
272
273/// Ensure the __cxa_rethrow runtime function is declared in the module.
274///
275/// void __cxa_rethrow();
276void ItaniumEHLowering::ensureCxaRethrowDecl(mlir::Location loc) {
277 if (cxaRethrowFunc)
278 return;
279 auto rethrowFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
280 cxaRethrowFunc =
281 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_rethrow", rethrowFuncTy);
282}
283
284/// Create a terminate landing pad block at the end of the specified function.
285mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
286 mlir::Location loc) {
287 assert(clangCallTerminateFunc &&
288 "ensureClangCallTerminate must run before buildTerminateBlock");
289 mlir::Region &body = funcOp.getRegion();
290 mlir::Block *terminateBlock = builder.createBlock(&body, body.end());
291 auto inflight = cir::EhInflightOp::create(
292 builder, loc, /*cleanup=*/false, /*catch_all=*/true,
293 /*catch_type_list=*/mlir::ArrayAttr{});
294 auto terminateCall = cir::CallOp::create(
295 builder, loc, mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc),
296 voidType, mlir::ValueRange{inflight.getExceptionPtr()});
297 terminateCall.setNothrowAttr(builder.getUnitAttr());
298 terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
299 builder.getUnitAttr());
300 cir::UnreachableOp::create(builder, loc);
301 return terminateBlock;
302}
303
304/// Lower all EH operations in a single function.
305mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
306 if (funcOp.isDeclaration())
307 return mlir::success();
308
309 // All EH lowering follows from cir.eh.initiate operations. The token each
310 // initiate produces connects it to every other EH op in the function
311 // (begin/end_cleanup, eh.dispatch, begin/end_catch, resume) through the
312 // token graph. A single walk to collect initiates is therefore sufficient.
313 SmallVector<cir::EhInitiateOp> initiateOps;
314 funcOp.walk([&](cir::EhInitiateOp op) { initiateOps.push_back(op); });
315 if (initiateOps.empty())
316 return mlir::success();
317
318 ensureRuntimeDecls(funcOp.getLoc());
319
320 // Set the personality function if it is not already set.
321 // TODO(cir): The personality function should already have been set by this
322 // point. If we've seen a try operation, it will have been set by
323 // emitCXXTryStmt. If we only have cleanups, it may not have been set. We
324 // need to fix that in CodeGen. This is a placeholder until that is done.
325 if (!funcOp.getPersonality())
326 funcOp.setPersonality(kGxxPersonality);
327
328 // Lower each initiate and all EH ops connected to it. The token map is
329 // shared across all initiate operations. Multiple initiates may flow into the
330 // same dispatch block, and the map ensures the arguments are registered
331 // only once. Dispatch ops are scheduled for deferred removal so that sibling
332 // initiates can still read catch types from a shared dispatch.
333 EhTokenMap ehTokenMap;
334 SmallVector<mlir::Operation *> deadOps;
335 for (cir::EhInitiateOp initiateOp : initiateOps)
336 if (mlir::failed(lowerEhInitiate(initiateOp, ehTokenMap, deadOps)))
337 return mlir::failure();
338
339 // Erase operations that were deferred during per-initiate processing
340 // (dispatch ops whose catch types were read by multiple initiates).
341 for (mlir::Operation *op : deadOps)
342 op->erase();
343
344 // Remove the !cir.eh_token block arguments that were replaced by (ptr, u32)
345 // pairs. Iterate in reverse to preserve argument indices during removal.
346 for (mlir::Block &block : funcOp.getBody()) {
347 for (int i = block.getNumArguments() - 1; i >= 0; --i) {
348 if (mlir::isa<cir::EhTokenType>(block.getArgument(i).getType()))
349 block.eraseArgument(i);
350 }
351 }
352
353 // Lower any cir.init_catch_param ops in this function. These materialize
354 // the catch parameter local from the (already lowered) begin_catch result,
355 // and are independent of the eh_token graph traversal above.
356 SmallVector<cir::InitCatchParamOp> initCatchOps;
357 funcOp.walk([&](cir::InitCatchParamOp op) { initCatchOps.push_back(op); });
358 for (cir::InitCatchParamOp op : initCatchOps)
359 lowerInitCatchParam(op);
360
361 // Lower any cir.try_throw ops in this function to cir.try_call of
362 // __cxa_throw / __cxa_rethrow. These are produced by FlattenCFG when a
363 // cir.throw appears inside a cleanup scope or try region.
364 SmallVector<cir::TryThrowOp> tryThrowOps;
365 funcOp.walk([&](cir::TryThrowOp op) { tryThrowOps.push_back(op); });
366 for (cir::TryThrowOp op : tryThrowOps)
367 if (mlir::failed(lowerTryThrow(op)))
368 return mlir::failure();
369
370 return mlir::success();
371}
372
373/// Lower all EH operations connected to a single cir.eh.initiate.
374///
375/// The cir.eh.initiate is the root of a token graph. The token it produces
376/// flows through branch edges to consuming operations:
377///
378/// cir.eh.initiate → (via cir.br) → cir.begin_cleanup
379/// → cir.end_cleanup (via cleanup_token)
380/// → (via cir.br) → cir.eh.dispatch
381/// → (successors) →
382/// cir.begin_catch
383/// → cir.end_catch
384/// (via catch_token)
385/// → cir.resume
386///
387/// A single traversal of the token graph discovers and processes every
388/// connected op inline. The inflight_exception is created up-front without
389/// a catch_type_list; when the dispatch is encountered during traversal,
390/// the catch types are read and set on the inflight op.
391///
392/// Dispatch ops are not erased during per-initiate processing because they may
393/// be used by other initiate ops that haven't yet been lowered. Instead they
394/// are added to \p deadOps and erased by the caller after all initiates have
395/// been lowered.
396///
397/// \p ehTokenMap is shared across all initiates in the function so that block
398/// arguments reachable from multiple sibling initiates are registered once.
399mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
400 cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
401 SmallVectorImpl<mlir::Operation *> &deadOps) {
402 mlir::Value rootToken = initiateOp.getEhToken();
403
404 // Create the inflight_exception without a catch_type_list. The catch types
405 // will be set once we encounter the dispatch during the traversal below.
406 builder.setInsertionPoint(initiateOp);
407 auto inflightOp = cir::EhInflightOp::create(
408 builder, initiateOp.getLoc(), /*cleanup=*/initiateOp.getCleanup(),
409 /*catch_all=*/false,
410 /*catch_type_list=*/mlir::ArrayAttr{});
411
412 ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
413 inflightOp.getTypeId()};
414
415 // Single traversal of the token graph. For each token value (the root token
416 // or a block argument that carries it), we snapshot its users, register
417 // (ptr, u32) replacement arguments on successor blocks, then process every
418 // user inline. This avoids collecting ops into separate vectors.
419 SmallVector<mlir::Value> worklist;
420 SmallPtrSet<mlir::Value, 8> visited;
421 worklist.push_back(rootToken);
422
423 while (!worklist.empty()) {
424 mlir::Value current = worklist.pop_back_val();
425 if (!visited.insert(current).second)
426 continue;
427
428 // Snapshot users before modifying any of them (erasing ops during
429 // iteration would invalidate the use-list iterator).
430 SmallVector<mlir::Operation *> users;
431 for (mlir::OpOperand &use : current.getUses())
432 users.push_back(use.getOwner());
433
434 // Register replacement block arguments on successor blocks (extending the
435 // worklist), then lower the op itself.
436 for (mlir::Operation *user : users) {
437 // Trace into successor blocks to register (ptr, u32) replacement
438 // arguments for any !cir.eh_token block arguments found there. Even
439 // if a block arg was already registered by a sibling initiate, it is
440 // still added to the worklist so that the traversal can reach the
441 // shared dispatch to read catch types.
442 for (unsigned s = 0; s < user->getNumSuccessors(); ++s) {
443 mlir::Block *succ = user->getSuccessor(s);
444 for (mlir::BlockArgument arg : succ->getArguments()) {
445 if (!mlir::isa<cir::EhTokenType>(arg.getType()))
446 continue;
447 if (!ehTokenMap.count(arg)) {
448 mlir::Value ptrArg = succ->addArgument(voidPtrType, arg.getLoc());
449 mlir::Value u32Arg = succ->addArgument(u32Type, arg.getLoc());
450 ehTokenMap[arg] = {ptrArg, u32Arg};
451 }
452 worklist.push_back(arg);
453 }
454 }
455
456 if (auto op = mlir::dyn_cast<cir::BeginCleanupOp>(user)) {
457 // begin_cleanup / end_cleanup are no-ops for Itanium. Erase the
458 // end_cleanup first (drops the cleanup_token use) then the begin.
459 for (auto &tokenUsers :
460 llvm::make_early_inc_range(op.getCleanupToken().getUses())) {
461 if (auto endOp =
462 mlir::dyn_cast<cir::EndCleanupOp>(tokenUsers.getOwner()))
463 endOp.erase();
464 }
465 op.erase();
466 } else if (auto op = mlir::dyn_cast<cir::BeginCatchOp>(user)) {
467 // Replace end_catch → __cxa_end_catch (drops the catch_token use),
468 // then replace begin_catch → __cxa_begin_catch.
469 for (auto &tokenUsers :
470 llvm::make_early_inc_range(op.getCatchToken().getUses())) {
471 if (auto endOp =
472 mlir::dyn_cast<cir::EndCatchOp>(tokenUsers.getOwner())) {
473 builder.setInsertionPoint(endOp);
474 cir::CallOp::create(builder, endOp.getLoc(),
475 mlir::FlatSymbolRefAttr::get(endCatchFunc),
476 voidType, mlir::ValueRange{});
477 endOp.erase();
478 }
479 }
480
481 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
482 builder.setInsertionPoint(op);
483 auto callOp = cir::CallOp::create(
484 builder, op.getLoc(), mlir::FlatSymbolRefAttr::get(beginCatchFunc),
485 u8PtrType, mlir::ValueRange{exnPtr});
486 mlir::Value castResult = callOp.getResult();
487 mlir::Type expectedPtrType = op.getExnPtr().getType();
488 if (castResult.getType() != expectedPtrType)
489 castResult =
490 cir::CastOp::create(builder, op.getLoc(), expectedPtrType,
491 cir::CastKind::bitcast, callOp.getResult());
492 op.getExnPtr().replaceAllUsesWith(castResult);
493 op.erase();
494 } else if (auto op = mlir::dyn_cast<cir::ConstructCatchParamOp>(user)) {
495 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
496 if (mlir::failed(lowerConstructCatchParam(op, exnPtr)))
497 return mlir::failure();
498 } else if (auto op = mlir::dyn_cast<cir::EhDispatchOp>(user)) {
499 // Read catch types from the dispatch and set them on the inflight op.
500 mlir::ArrayAttr catchTypes = op.getCatchTypesAttr();
501 if (catchTypes && catchTypes.size() > 0) {
502 SmallVector<mlir::Attribute> typeSymbols;
503 for (mlir::Attribute attr : catchTypes)
504 typeSymbols.push_back(
505 mlir::cast<cir::GlobalViewAttr>(attr).getSymbol());
506 inflightOp.setCatchTypeListAttr(builder.getArrayAttr(typeSymbols));
507 }
508 if (op.getDefaultIsCatchAll())
509 inflightOp.setCatchAllAttr(builder.getUnitAttr());
510 // Only lower the dispatch once. A sibling initiate sharing the same
511 // dispatch will still read its catch types (above), but the comparison
512 // chain and branch replacement are only created the first time.
513 if (!llvm::is_contained(deadOps, op.getOperation())) {
514 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
515 lowerDispatch(op, exnPtr, typeId, deadOps);
516 }
517 } else if (auto op = mlir::dyn_cast<cir::EhTerminateOp>(user)) {
518 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
519 ensureClangCallTerminate(op.getLoc());
520 builder.setInsertionPoint(op);
521 auto call = cir::CallOp::create(
522 builder, op.getLoc(),
523 mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc), voidType,
524 mlir::ValueRange{exnPtr});
525 call.setNothrowAttr(builder.getUnitAttr());
526 call->setAttr(cir::CIRDialect::getNoReturnAttrName(),
527 builder.getUnitAttr());
528 cir::UnreachableOp::create(builder, op.getLoc());
529 op.erase();
530 } else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
531 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
532 builder.setInsertionPoint(op);
533 cir::ResumeFlatOp::create(builder, op.getLoc(), exnPtr, typeId);
534 op.erase();
535 } else if (auto op = mlir::dyn_cast<cir::BrOp>(user)) {
536 // Replace eh_token operands with the (ptr, u32) pair.
537 SmallVector<mlir::Value> newOperands;
538 bool changed = false;
539 for (mlir::Value operand : op.getDestOperands()) {
540 auto it = ehTokenMap.find(operand);
541 if (it != ehTokenMap.end()) {
542 newOperands.push_back(it->second.first);
543 newOperands.push_back(it->second.second);
544 changed = true;
545 } else {
546 newOperands.push_back(operand);
547 }
548 }
549 if (changed) {
550 builder.setInsertionPoint(op);
551 cir::BrOp::create(builder, op.getLoc(), op.getDest(), newOperands);
552 op.erase();
553 }
554 }
555 }
556 }
557
558 initiateOp.erase();
559 return mlir::success();
560}
561
562/// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
563/// The dispatch itself is replaced with a branch to the first comparison
564/// block and added to deadOps for deferred removal.
565void ItaniumEHLowering::lowerDispatch(
566 cir::EhDispatchOp dispatch, mlir::Value exnPtr, mlir::Value typeId,
567 SmallVectorImpl<mlir::Operation *> &deadOps) {
568 mlir::Location dispLoc = dispatch.getLoc();
569 mlir::Block *defaultDest = dispatch.getDefaultDestination();
570 mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr();
571 mlir::SuccessorRange catchDests = dispatch.getCatchDestinations();
572 mlir::Block *dispatchBlock = dispatch->getBlock();
573
574 // Build the comparison chain in new blocks inserted after the dispatch's
575 // block. The dispatch itself is replaced with a branch to the first
576 // comparison block and scheduled for deferred removal.
577 if (!catchTypes || catchTypes.empty()) {
578 // No typed catches: replace dispatch with a direct branch.
579 builder.setInsertionPoint(dispatch);
580 cir::BrOp::create(builder, dispLoc, defaultDest,
581 mlir::ValueRange{exnPtr, typeId});
582 } else {
583 unsigned numCatches = catchTypes.size();
584
585 // Create and populate comparison blocks in reverse order so that each
586 // block's false destination (the next comparison block, or defaultDest
587 // for the last one) is already available. Each createBlock inserts
588 // before the previous one, so the blocks end up in forward order.
589 mlir::Block *insertBefore = dispatchBlock->getNextNode();
590 mlir::Block *falseDest = defaultDest;
591 mlir::Block *firstCmpBlock = nullptr;
592 for (int i = numCatches - 1; i >= 0; --i) {
593 auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, u32Type},
594 {dispLoc, dispLoc});
595
596 mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
597 mlir::Value cmpTypeId = cmpBlock->getArgument(1);
598
599 auto globalView = mlir::cast<cir::GlobalViewAttr>(catchTypes[i]);
600 auto ehTypeIdOp =
601 cir::EhTypeIdOp::create(builder, dispLoc, globalView.getSymbol());
602 auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::eq,
603 cmpTypeId, ehTypeIdOp.getTypeId());
604
605 cir::BrCondOp::create(builder, dispLoc, cmpOp, catchDests[i], falseDest,
606 mlir::ValueRange{cmpExnPtr, cmpTypeId},
607 mlir::ValueRange{cmpExnPtr, cmpTypeId});
608
609 insertBefore = cmpBlock;
610 falseDest = cmpBlock;
611 firstCmpBlock = cmpBlock;
612 }
613
614 // Replace the dispatch with a branch to the first comparison block.
615 builder.setInsertionPoint(dispatch);
616 cir::BrOp::create(builder, dispLoc, firstCmpBlock,
617 mlir::ValueRange{exnPtr, typeId});
618 }
619
620 // Schedule the dispatch for deferred removal. We cannot erase it now because
621 // a sibling initiate that shares this dispatch may still need to read its
622 // catch types.
623 deadOps.push_back(dispatch);
624}
625
626mlir::FailureOr<cir::FuncOp>
627ItaniumEHLowering::resolveCatchCopyThunk(cir::ConstructCatchParamOp op) {
628 mlir::FlatSymbolRefAttr thunkRef = op.getCopyFnAttr();
629 mlir::StringAttr thunkName = thunkRef.getAttr();
630 auto cached = catchCopyThunks.find(thunkName);
631 if (cached != catchCopyThunks.end())
632 return cached->second;
633
634 cir::FuncOp thunk = mod.lookupSymbol<cir::FuncOp>(thunkRef);
635 if (!thunk)
636 return op.emitError("could not resolve catch-copy thunk symbol");
637 assert(thunk->hasAttr(cir::CIRDialect::getCatchCopyThunkAttrName()) &&
638 "verifier should have rejected non-thunk catch-copy reference");
639 if (thunk.isDeclaration())
640 return op.emitError("catch-copy thunk has no body to inline");
641
642 mlir::Region &thunkRegion = thunk.getRegion();
643 if (!llvm::hasSingleElement(thunkRegion))
644 return op.emitError("multi-block catch-copy thunks are NYI");
645
646 mlir::Block &thunkEntry = thunkRegion.front();
647 assert(thunkEntry.getNumArguments() == 2 &&
648 "catch-copy thunk must have exactly two parameters");
649 if (!mlir::isa<cir::ReturnOp>(thunkEntry.getTerminator()))
650 return op.emitError("catch-copy thunk must end in cir.return");
651
652 catchCopyThunks[thunkName] = thunk;
653 return thunk;
654}
655
656/// Lower a cir.construct_catch_param into the Itanium-specific sequence
657/// that runs before `__cxa_begin_catch` to bind the catch parameter to the
658/// in-flight exception.
659mlir::LogicalResult
660ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
661 mlir::Value exnPtr) {
662 mlir::Location loc = op.getLoc();
663 mlir::Value paramAddr = op.getParamAddr();
664 cir::PointerType paramAddrType =
665 mlir::cast<cir::PointerType>(paramAddr.getType());
666
667 if (op.getKind() == cir::InitCatchKind::Reference) {
669 constexpr unsigned headerSize = 32;
670
671 builder.setInsertionPoint(op);
672 auto index = cir::ConstantOp::create(
673 builder, loc, cir::IntAttr::get(u32Type, headerSize));
674 assert((exnPtr.getType() == voidPtrType || exnPtr.getType() == u8PtrType) &&
675 "lowerConstructCatchParam exn ptr not void* or i8*");
676 auto exnObj =
677 cir::PtrStrideOp::create(builder, loc, exnPtr.getType(), exnPtr, index);
678 mlir::Value casted =
679 cir::CastOp::create(builder, loc, paramAddrType.getPointee(),
680 cir::CastKind::bitcast, exnObj);
681 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
682 op.erase();
683 return success();
684 }
685
686 if (op.getKind() != cir::InitCatchKind::NonTrivialCopy)
687 return op.emitError(
688 "ConstructCatchParam: only non_trivial_copy is supported");
689
690 ensureRuntimeDecls(loc);
691 ensureClangCallTerminate(loc);
692
693 // Call __cxa_get_exception_ptr to get the in-flight exception.
694 builder.setInsertionPoint(op);
695 cir::CallOp getExnCall = cir::CallOp::create(
696 builder, loc, mlir::FlatSymbolRefAttr::get(getExceptionPtrFunc),
697 u8PtrType, mlir::ValueRange{exnPtr});
698 getExnCall.setNothrowAttr(builder.getUnitAttr());
699 mlir::Value adjusted =
700 cir::CastOp::create(builder, loc, paramAddrType, cir::CastKind::bitcast,
701 getExnCall.getResult());
702
703 // Get the thunk function definition.
704 mlir::FailureOr<cir::FuncOp> thunkOr = resolveCatchCopyThunk(op);
705 if (mlir::failed(thunkOr))
706 return mlir::failure();
707 cir::FuncOp thunk = *thunkOr;
708
709 // This is also verified by resolveCatchCopyThunk, but the loop below is
710 // where the constraint is required so let's assert it again here.
711 assert(llvm::hasSingleElement(thunk.getRegion()) &&
712 "multi-block catch-copy thunks are NYI");
713
714 // Clone the thunk function to perform the copy.
715 mlir::Block &thunkEntry = thunk.getRegion().front();
716 mlir::IRMapping mapping;
717 mapping.map(thunkEntry.getArgument(0), paramAddr);
718 mapping.map(thunkEntry.getArgument(1), adjusted);
719 llvm::SmallVector<cir::CallOp> throwingCalls;
720 for (mlir::Operation &thunkOp : thunkEntry.without_terminator()) {
721 mlir::Operation *cloned = builder.clone(thunkOp, mapping);
722 if (cir::CallOp callOp = mlir::dyn_cast<cir::CallOp>(cloned))
723 if (!callOp.getNothrow())
724 throwingCalls.push_back(callOp);
725 }
726 op.erase();
727
728 if (throwingCalls.empty())
729 return mlir::success();
730
731 // All calls in the copy (which is usually just a single call) need to
732 // unwind to a terminate block if it throws an exception.
733 mlir::IRRewriter rewriter(builder);
734 mlir::Block *terminateBlock = nullptr;
735 for (cir::CallOp call : throwingCalls) {
736 if (!terminateBlock)
737 terminateBlock = buildTerminateBlock(call->getParentOfType<cir::FuncOp>(),
738 call.getLoc());
739 cir::replaceCallWithTryCall(call, terminateBlock, call.getLoc(), rewriter);
740 }
741 return mlir::success();
742}
743
744/// Lower a cir.try_throw to a cir.try_call of __cxa_throw (or
745/// __cxa_rethrow for the no-operand rethrow form). Materializes the
746/// type_info and dtor pointers from their symbol attributes, bitcasting
747/// each to !cir.ptr<!void> as required by the runtime function signature.
748mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
749 mlir::Location loc = op.getLoc();
750 mlir::Block *normalDest = op.getNormalDest();
751 mlir::Block *unwindDest = op.getUnwindDest();
752 builder.setInsertionPoint(op);
753
754 if (op.rethrows()) {
755 ensureCxaRethrowDecl(loc);
756 cir::TryCallOp::create(
757 builder, loc, mlir::FlatSymbolRefAttr::get(cxaRethrowFunc), voidType,
758 normalDest, unwindDest, mlir::ValueRange{});
759 op.erase();
760 return mlir::success();
761 }
762
763 ensureCxaThrowDecl(loc);
764
765 // Bitcast the exception pointer to void* if necessary.
766 mlir::Value exnPtr = op.getExceptionPtr();
767 if (exnPtr.getType() != voidPtrType)
768 exnPtr = cir::CastOp::create(builder, loc, voidPtrType,
769 cir::CastKind::bitcast, exnPtr);
770
771 // Materialize the type_info pointer, looking up the typed symbol in the
772 // module so we get the correct pointer type for cir.get_global, then
773 // bitcasting to void* to match the runtime signature.
774 mlir::FlatSymbolRefAttr typeInfoAttr = op.getTypeInfoAttr();
775 auto typeInfoGlobal = mod.lookupSymbol<cir::GlobalOp>(typeInfoAttr);
776 if (!typeInfoGlobal)
777 return op.emitError("type_info symbol not found in module");
778 auto typeInfoPtrTy = cir::PointerType::get(typeInfoGlobal.getSymType());
779 mlir::Value typeInfo = cir::GetGlobalOp::create(builder, loc, typeInfoPtrTy,
780 typeInfoAttr.getValue());
781 if (typeInfo.getType() != voidPtrType)
782 typeInfo = cir::CastOp::create(builder, loc, voidPtrType,
783 cir::CastKind::bitcast, typeInfo);
784
785 // Materialize the dtor pointer (or null if no dtor).
786 mlir::Value dtor;
787 if (mlir::FlatSymbolRefAttr dtorAttr = op.getDtorAttr()) {
788 auto dtorFunc = mod.lookupSymbol<cir::FuncOp>(dtorAttr);
789 if (!dtorFunc)
790 return op.emitError("dtor symbol not found in module");
791 auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
792 dtor =
793 cir::GetGlobalOp::create(builder, loc, dtorPtrTy, dtorAttr.getValue());
794 if (dtor.getType() != voidPtrType)
795 dtor = cir::CastOp::create(builder, loc, voidPtrType,
796 cir::CastKind::bitcast, dtor);
797 } else {
798 dtor = cir::ConstantOp::create(
799 builder, loc,
800 cir::ConstPtrAttr::get(voidPtrType, builder.getI64IntegerAttr(0)));
801 }
802
803 cir::TryCallOp::create(
804 builder, loc, mlir::FlatSymbolRefAttr::get(cxaThrowFunc), voidType,
805 normalDest, unwindDest, mlir::ValueRange{exnPtr, typeInfo, dtor});
806 op.erase();
807 return mlir::success();
808}
809
810/// Lower a cir.init_catch_param into the Itanium-specific sequence that
811/// materializes the catch parameter's local variable from the exception
812/// pointer returned by __cxa_begin_catch. The shape of the lowering
813/// depends on the init catch kind:
814///
815/// - Reference: the begin_catch result is
816/// the pointer value itself, so just bitcast and store it into the alloca
817/// except if it reference of pointer of record.
818/// - Pointer: the begin_catch result is
819/// the pointer value itself, so just bitcast and store it into the
820/// alloca.
821/// - Scalar (any other by-value catch): treat the begin_catch result as a
822/// pointer to the value, load it, and store it into the alloca.
823/// - Objc: Handle pointer representation with ObjCLifetime.
824/// - TrivialCopy: copy the exception
825/// object's bytes into the alloca via cir.copy.
826/// - NonTrivialCopy: the construction was already performed by the
827/// companion `cir.construct_catch_param` before `cir.begin_catch`, so
828/// this lowering is a no-op.
829///
830void ItaniumEHLowering::lowerInitCatchParam(cir::InitCatchParamOp op) {
831 builder.setInsertionPoint(op);
832 mlir::Location loc = op.getLoc();
833 mlir::Value exnPtr = op.getExnPtr();
834 mlir::Value paramAddr = op.getParamAddr();
835 auto paramAddrType = mlir::cast<cir::PointerType>(paramAddr.getType());
836 mlir::Type elementType = paramAddrType.getPointee();
837 cir::InitCatchKind kind = op.getKind();
838
839 switch (kind) {
840 case InitCatchKind::Reference: {
841 // We have no way to tell the personality function that we're
842 // catching by reference, so if we're catching a pointer,
843 // __cxa_begin_catch will actually return that pointer by value.
844 if (const auto ref = mlir::dyn_cast<cir::PointerType>(elementType)) {
845 // When catching by reference, generally we should just ignore
846 // this by-value pointer and use the exception object instead.
847 if (auto ptr = mlir::dyn_cast<cir::PointerType>(ref.getPointee()))
848 if (!mlir::isa<cir::RecordType>(ptr.getPointee()))
849 // Extracting and storing the actual exception object was performed by
850 // cir.construct_catch_param before cir.begin_catch.
851 break;
852 }
853
854 mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
855 cir::CastKind::bitcast, exnPtr);
856 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
857 break;
858 }
859 case InitCatchKind::TrivialCopy: {
860 mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
861 cir::CastKind::bitcast, exnPtr);
862 cir::CopyOp::create(builder, loc, paramAddr, srcPtr, {}, {});
863 break;
864 }
865 case InitCatchKind::NonTrivialCopy:
866 // The non-trivial copy was performed by the matching
867 // cir.construct_catch_param before cir.begin_catch.
868 break;
869 case InitCatchKind::Scalar: {
870 // Scalar by-value catch (integer, float, complex, etc.). The begin_catch
871 // result points into the exception object; load the value through a
872 // typed pointer and store it into the alloca.
873 mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
874 cir::CastKind::bitcast, exnPtr);
875 auto loadOp = cir::LoadOp::create(builder, loc, elementType, srcPtr);
876 cir::StoreOp::create(builder, loc, loadOp.getResult(), paramAddr, {}, {},
877 {}, {});
878 break;
879 }
880 case InitCatchKind::Pointer: {
881 mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
882 cir::CastKind::bitcast, exnPtr);
883 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
884 break;
885 }
886 case InitCatchKind::Objc:
887 llvm_unreachable("InitCatchParam: ObjCLifetime is NYI");
888 break;
889 }
890
891 op.erase();
892}
893
894//===----------------------------------------------------------------------===//
895// The Pass
896//===----------------------------------------------------------------------===//
897
898struct CIREHABILoweringPass
899 : public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
900 CIREHABILoweringPass() = default;
901 void runOnOperation() override;
902};
903
904/// Erase all catch-init thunks after the EHABI lowering. CIRGen emits a thunk
905/// for every `cir.construct_catch_param` op, but those uses should all have
906/// been replaced during the lowering.
907static void eraseCatchCopyThunks(mlir::ModuleOp mod) {
908 llvm::StringRef catchHelperAttr =
909 cir::CIRDialect::getCatchCopyThunkAttrName();
910 for (cir::FuncOp f : llvm::make_early_inc_range(mod.getOps<cir::FuncOp>())) {
911 if (!f->hasAttr(catchHelperAttr))
912 continue;
913 // This is an expensive check, so we need to rely on the implementation
914 // to have done the right thing.
915 assert(mlir::SymbolTable::symbolKnownUseEmpty(f, mod) &&
916 "catch-init helper has remaining users");
917 f.erase();
918 }
919}
920
921void CIREHABILoweringPass::runOnOperation() {
922 auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
923
924 // The target triple is attached to the module as the "cir.triple"
925 // attribute. If it is absent (e.g. a CIR module parsed from text without a
926 // triple) we cannot determine the ABI and must skip the pass.
927 auto tripleAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(
928 mod->getAttr(cir::CIRDialect::getTripleAttrName()));
929 if (!tripleAttr) {
930 mod.emitError("Module has no target triple");
931 return;
932 }
933
934 // Select the ABI-specific lowering handler from the triple. The Microsoft
935 // C++ ABI targets a Windows MSVC environment; everything else uses Itanium.
936 // Extend this when Microsoft ABI lowering is added.
937 llvm::Triple triple(tripleAttr.getValue());
938 std::unique_ptr<EHABILowering> lowering;
939 if (triple.isWindowsMSVCEnvironment()) {
940 mod.emitError(
941 "EH ABI lowering is not yet implemented for the Microsoft ABI");
942 return signalPassFailure();
943 } else {
944 lowering = std::make_unique<ItaniumEHLowering>(mod);
945 }
946
947 if (mlir::failed(lowering->run()))
948 return signalPassFailure();
949
950 // Sweep away any the thunk functions. They've been inlined to all users now.
951 eraseCatchCopyThunks(mod);
952}
953
954} // namespace
955
956std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
957 return std::make_unique<CIREHABILoweringPass>();
958}
*collection of selector each with an associated kind and an ordered *collection of selectors A selector has a kind
__device__ __2f16 float __ockl_bool s
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.
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)
static bool sizeOfUnwindException()