clang 24.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.eh.unexpected → call to __cxa_call_unexpected + unreachable
22// - cir.resume → cir.resume.flat
23// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
24// - cir.construct_catch_param → __cxa_get_exception_ptr + inlined
25// catch-copy thunk body, or a store of the
26// exception object address for
27// reference_to_pointer
28// - personality function set on functions requiring EH and removed from
29// functions without landing pads
30//
31//===----------------------------------------------------------------------===//
32
33#include "PassDetail.h"
34#include "mlir/IR/Builders.h"
35#include "mlir/IR/IRMapping.h"
36#include "mlir/IR/PatternMatch.h"
44#include "llvm/ADT/DenseMap.h"
45#include "llvm/ADT/SetVector.h"
46#include "llvm/ADT/SmallVector.h"
47#include "llvm/TargetParser/Triple.h"
48
49using namespace mlir;
50using namespace cir;
51
52namespace mlir {
53#define GEN_PASS_DEF_CIREHABILOWERING
54#include "clang/CIR/Dialect/Passes.h.inc"
55} // namespace mlir
56
57namespace {
58
59//===----------------------------------------------------------------------===//
60// Shared utilities
61//===----------------------------------------------------------------------===//
62
63/// Ensure a function with the given name and type exists in the module. If it
64/// does not exist, create a private external declaration.
65static cir::FuncOp getOrCreateRuntimeFuncDecl(mlir::ModuleOp mod,
66 mlir::Location loc,
67 StringRef name,
68 cir::FuncType funcTy) {
69 if (auto existing = mod.lookupSymbol<cir::FuncOp>(name))
70 return existing;
71
72 mlir::OpBuilder builder(mod.getContext());
73 builder.setInsertionPointToEnd(mod.getBody());
74 auto funcOp = cir::FuncOp::create(builder, loc, name, funcTy);
75 funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
76 funcOp.setPrivate();
77 // This pass runs after CallConvLowering and builds the declaration in its
78 // target ABI form already.
79 funcOp.setRequiresCallConvLowering(false);
80 return funcOp;
81}
82
83//===----------------------------------------------------------------------===//
84// EH ABI Lowering Base Class
85//===----------------------------------------------------------------------===//
86
87/// Abstract base class for exception-handling ABI lowering.
88/// Each supported ABI (Itanium, Microsoft, etc.) provides a concrete subclass.
89class EHABILowering {
90public:
91 explicit EHABILowering(mlir::ModuleOp mod)
92 : mod(mod), ctx(mod.getContext()), builder(ctx) {}
93 virtual ~EHABILowering() = default;
94
95 /// Lower all EH operations in the module to an ABI-specific form.
96 virtual mlir::LogicalResult run() = 0;
97
98protected:
99 mlir::ModuleOp mod;
100 mlir::MLIRContext *ctx;
101 mlir::OpBuilder builder;
102};
103
104//===----------------------------------------------------------------------===//
105// Itanium EH ABI Lowering
106//===----------------------------------------------------------------------===//
107
108/// Lowers flattened CIR EH operations to the Itanium C++ ABI form.
109///
110/// The entry point is run(), which iterates over all functions and
111/// calls lowerFunc() for each. lowerFunc() drives all lowering from
112/// cir.eh.initiate operations: every other EH op (begin/end_cleanup,
113/// eh.dispatch, begin/end_catch, resume) is reachable by tracing the
114/// eh_token produced by the initiate through its users.
115class ItaniumEHLowering : public EHABILowering {
116public:
117 using EHABILowering::EHABILowering;
118 mlir::LogicalResult run() override;
119
120private:
121 /// Maps a !cir.eh_token value to its Itanium ABI replacement pair:
122 /// an exception pointer (!cir.ptr<!void>) and a type id (!u32i).
123 using EhTokenMap = DenseMap<mlir::Value, std::pair<mlir::Value, mlir::Value>>;
124
125 cir::VoidType voidType;
126 cir::PointerType voidPtrType;
127 cir::PointerType u8PtrType;
128 cir::IntType u32Type;
129 cir::IntType s32Type;
130
131 // Cached runtime function declarations, initialized when needed by
132 // ensureRuntimeDecls().
133 cir::FuncOp personalityFunc;
134 cir::FuncOp beginCatchFunc;
135 cir::FuncOp endCatchFunc;
136 cir::FuncOp getExceptionPtrFunc;
137 cir::FuncOp clangCallTerminateFunc;
138 cir::FuncOp cxaThrowFunc;
139 cir::FuncOp cxaRethrowFunc;
140 cir::FuncOp cxaCallUnexpectedFunc;
141
142 DenseMap<mlir::StringAttr, cir::FuncOp> catchCopyThunks;
143
144 llvm::SmallSetVector<mlir::StringAttr, 1> droppedPersonalities;
145
146 constexpr const static ::llvm::StringLiteral kGxxPersonality =
147 "__gxx_personality_v0";
148
149 void ensureRuntimeDecls(mlir::Location loc);
150 void ensureClangCallTerminate(mlir::Location loc);
151 void ensureCxaThrowDecl(mlir::Location loc);
152 void ensureCxaRethrowDecl(mlir::Location loc);
153 void ensureCxaCallUnexpectedDecl(mlir::Location loc);
154 mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
155 mlir::FailureOr<cir::FuncOp>
156 resolveCatchCopyThunk(cir::ConstructCatchParamOp op);
157 mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
158 mlir::LogicalResult
159 lowerEhInitiate(cir::EhInitiateOp initiateOp,
160 llvm::ArrayRef<cir::EhDispatchOp> reachedDispatches,
161 bool reachesCleanup, EhTokenMap &ehTokenMap);
162 void lowerDispatch(cir::EhDispatchOp dispatch, mlir::Value exnPtr,
163 mlir::Value typeId);
164 mlir::LogicalResult lowerConstructCatchParam(cir::ConstructCatchParamOp op,
165 mlir::Value exnPtr);
166 void lowerInitCatchParam(cir::InitCatchParamOp op);
167 mlir::LogicalResult lowerTryThrow(cir::TryThrowOp op);
168};
169
170/// Lower all EH operations in the module to the Itanium-specific form.
171mlir::LogicalResult ItaniumEHLowering::run() {
172 // Pre-compute the common types used throughout all function lowerings.
173 // TODO(cir): Move these to the base class if they are also needed for MSVC.
174 voidType = cir::VoidType::get(ctx);
175 voidPtrType = cir::PointerType::get(voidType);
176 auto u8Type = cir::IntType::get(ctx, 8, /*isSigned=*/false);
177 u8PtrType = cir::PointerType::get(u8Type);
178 u32Type = cir::IntType::get(ctx, 32, /*isSigned=*/false);
179 s32Type = cir::IntType::get(ctx, 32, /*isSigned=*/true);
180
181 for (cir::FuncOp funcOp : mod.getOps<cir::FuncOp>()) {
182 if (mlir::failed(lowerFunc(funcOp)))
183 return mlir::failure();
184 }
185
186 for (mlir::StringAttr name : droppedPersonalities) {
187 auto personalityDecl = mod.lookupSymbol<cir::FuncOp>(name);
188 if (personalityDecl && personalityDecl.isDeclaration() &&
189 mlir::SymbolTable::symbolKnownUseEmpty(personalityDecl, mod))
190 personalityDecl.erase();
191 }
192 return mlir::success();
193}
194
195/// Ensure the necessary Itanium runtime function declarations exist in the
196/// module.
197void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
198 // TODO(cir): Handle other personality functions. This probably isn't needed
199 // here if we fix codegen to always set the personality function.
200 if (!personalityFunc) {
201 auto personalityFuncTy = cir::FuncType::get({}, s32Type, /*isVarArg=*/true);
202 personalityFunc = getOrCreateRuntimeFuncDecl(mod, loc, kGxxPersonality,
203 personalityFuncTy);
204 }
205
206 if (!beginCatchFunc) {
207 auto beginCatchFuncTy =
208 cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
209 beginCatchFunc = getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_begin_catch",
210 beginCatchFuncTy);
211 }
212
213 if (!endCatchFunc) {
214 auto endCatchFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
215 endCatchFunc =
216 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", endCatchFuncTy);
217 }
218
219 if (!getExceptionPtrFunc) {
220 auto getExceptionPtrFuncTy =
221 cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
222 getExceptionPtrFunc = getOrCreateRuntimeFuncDecl(
223 mod, loc, "__cxa_get_exception_ptr", getExceptionPtrFuncTy);
224 }
225}
226
227/// Ensure the __clang_call_terminate function exists in the module. This
228/// function is defined with a body that calls __cxa_begin_catch followed by
229/// std::terminate, matching the behavior of Clang's LLVM IR codegen.
230///
231/// void __clang_call_terminate(void *exn) nounwind noreturn {
232/// __cxa_begin_catch(exn);
233/// std::terminate();
234/// unreachable;
235/// }
236void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
237 // TODO(cir): set the runtime calling convention on the runtime calls below.
239 if (clangCallTerminateFunc)
240 return;
241
242 ensureRuntimeDecls(loc);
243
244 if (auto existing = mod.lookupSymbol<cir::FuncOp>("__clang_call_terminate")) {
245 clangCallTerminateFunc = existing;
246 return;
247 }
248
249 auto funcTy = cir::FuncType::get({voidPtrType}, voidType, /*isVarArg=*/false);
250 builder.setInsertionPointToEnd(mod.getBody());
251 auto funcOp =
252 cir::FuncOp::create(builder, loc, "__clang_call_terminate", funcTy);
253 funcOp.setLinkage(cir::GlobalLinkageKind::LinkOnceODRLinkage);
254 funcOp.setGlobalVisibility(cir::VisibilityKind::Hidden);
255 // This pass runs after CallConvLowering and builds the function in its
256 // target ABI form already.
257 funcOp.setRequiresCallConvLowering(false);
258
259 mlir::Block *entryBlock = funcOp.addEntryBlock();
260 builder.setInsertionPointToStart(entryBlock);
261 mlir::Value exnArg = entryBlock->getArgument(0);
262
263 auto catchCall = cir::CallOp::create(
264 builder, loc, mlir::FlatSymbolRefAttr::get(beginCatchFunc), u8PtrType,
265 mlir::ValueRange{exnArg});
266 catchCall.setNothrowAttr(builder.getUnitAttr());
267
268 auto terminateFuncDecl = getOrCreateRuntimeFuncDecl(
269 mod, loc, "_ZSt9terminatev",
270 cir::FuncType::get({}, voidType, /*isVarArg=*/false));
271 terminateFuncDecl->setAttr(cir::CIRDialect::getNoReturnAttrName(),
272 builder.getUnitAttr());
273 auto terminateCall = cir::CallOp::create(
274 builder, loc, mlir::FlatSymbolRefAttr::get(terminateFuncDecl), voidType,
275 mlir::ValueRange{});
276 terminateCall.setNothrowAttr(builder.getUnitAttr());
277 terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
278 builder.getUnitAttr());
279
280 cir::UnreachableOp::create(builder, loc);
281
282 funcOp->setAttr(cir::CIRDialect::getNoReturnAttrName(),
283 builder.getUnitAttr());
284 clangCallTerminateFunc = funcOp;
285}
286
287/// Ensure the __cxa_throw runtime function is declared in the module.
288///
289/// void __cxa_throw(void *exception, void *type_info, void *dtor);
290void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc) {
291 if (cxaThrowFunc)
292 return;
293 auto throwFuncTy = cir::FuncType::get({voidPtrType, voidPtrType, voidPtrType},
294 voidType, /*isVarArg=*/false);
295 cxaThrowFunc =
296 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_throw", throwFuncTy);
297}
298
299/// Ensure the __cxa_rethrow runtime function is declared in the module.
300///
301/// void __cxa_rethrow();
302void ItaniumEHLowering::ensureCxaRethrowDecl(mlir::Location loc) {
303 if (cxaRethrowFunc)
304 return;
305 auto rethrowFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
306 cxaRethrowFunc =
307 getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_rethrow", rethrowFuncTy);
308}
309
310/// void __cxa_call_unexpected(void *exn);
311void ItaniumEHLowering::ensureCxaCallUnexpectedDecl(mlir::Location loc) {
312 if (cxaCallUnexpectedFunc)
313 return;
314 auto unexpectedFuncTy =
315 cir::FuncType::get({voidPtrType}, voidType, /*isVarArg=*/false);
316 cxaCallUnexpectedFunc = getOrCreateRuntimeFuncDecl(
317 mod, loc, "__cxa_call_unexpected", unexpectedFuncTy);
318}
319
320/// Create a terminate landing pad block at the end of the specified function.
321mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
322 mlir::Location loc) {
323 // TODO(cir): set the runtime calling convention on the runtime calls below.
325 assert(clangCallTerminateFunc &&
326 "ensureClangCallTerminate must run before buildTerminateBlock");
327 mlir::Region &body = funcOp.getRegion();
328 mlir::Block *terminateBlock = builder.createBlock(&body, body.end());
329 auto inflight = cir::EhInflightOp::create(
330 builder, loc, /*cleanup=*/false, /*catch_all=*/true,
331 /*catch_type_list=*/mlir::ArrayAttr{},
332 /*filter_type_list=*/mlir::ArrayAttr{});
333 auto terminateCall = cir::CallOp::create(
334 builder, loc, mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc),
335 voidType, mlir::ValueRange{inflight.getExceptionPtr()});
336 terminateCall.setNothrowAttr(builder.getUnitAttr());
337 terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
338 builder.getUnitAttr());
339 cir::UnreachableOp::create(builder, loc);
340 return terminateBlock;
341}
342
343/// Read-only walk of the eh_token graph from an initiate's root token,
344/// collecting every cir.eh.dispatch its exception can reach, innermost first.
345/// The token flows through cleanups to the innermost dispatch and then, via
346/// that dispatch's continue-unwind edge, on to each enclosing dispatch (nested
347/// try/catch). The walk follows the token into catch-handler blocks too, but
348/// it dead-ends there because cir.begin_catch consumes the eh_token (producing
349/// a catch_token), so only the unwind chain yields further dispatches.
350///
351/// This is computed before any destructive lowering so a landing pad's catch
352/// types -- which are a property of the EH graph -- do not depend on the order
353/// in which the destructive per-initiate traversal tears down shared
354/// token-graph edges. \p dispatches is left empty for a cleanup-only initiate
355/// that reaches no dispatch (e.g. a path that only resumes).
356static void collectReachableDispatches(
357 mlir::Value rootToken,
358 llvm::SmallSetVector<cir::EhDispatchOp, 4> &dispatches,
359 bool &reachesCleanup) {
360 llvm::SmallVector<mlir::Value> worklist;
361 llvm::SmallPtrSet<mlir::Value, 8> visited;
362 worklist.push_back(rootToken);
363 // Breadth-first (process in insertion order) so dispatches are discovered
364 // innermost first, matching the order catch clauses must appear in the
365 // landing pad.
366 for (unsigned i = 0; i < worklist.size(); ++i) {
367 mlir::Value current = worklist[i];
368 if (!visited.insert(current).second)
369 continue;
370 for (mlir::OpOperand &use : current.getUses()) {
371 mlir::Operation *user = use.getOwner();
372 // A cleanup anywhere on the unwind path (this initiate's own cleanup or
373 // an enclosing scope's) means the landing pad must carry the cleanup
374 // clause so destructors still run when a foreign exception unwinds
375 // through this frame.
376 if (mlir::isa<cir::BeginCleanupOp>(user))
377 reachesCleanup = true;
378 if (auto dispatch = mlir::dyn_cast<cir::EhDispatchOp>(user))
379 dispatches.insert(dispatch);
380 // Follow the token into eh_token block arguments of successor blocks.
381 for (unsigned s = 0, e = user->getNumSuccessors(); s < e; ++s)
382 for (mlir::BlockArgument arg : user->getSuccessor(s)->getArguments())
383 if (mlir::isa<cir::EhTokenType>(arg.getType()))
384 worklist.push_back(arg);
385 }
386 }
387}
388
389/// Lower all EH operations in a single function.
390mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
391 if (funcOp.isDeclaration())
392 return mlir::success();
393
394 // All EH lowering follows from cir.eh.initiate operations. The token each
395 // initiate produces connects it to every other EH op in the function
396 // (begin/end_cleanup, eh.dispatch, begin/end_catch, resume) through the
397 // token graph. A single walk to collect initiates is therefore sufficient.
398 SmallVector<cir::EhInitiateOp> initiateOps;
399 bool hasLandingPad = false;
400 funcOp.walk([&](mlir::Operation *op) {
401 if (auto initiateOp = mlir::dyn_cast<cir::EhInitiateOp>(op))
402 initiateOps.push_back(initiateOp);
403 else if (mlir::isa<cir::EhInflightOp>(op))
404 hasLandingPad = true;
405 });
406 if (initiateOps.empty()) {
407 // Without a landing pad the personality is dead, as in classic codegen.
408 mlir::FlatSymbolRefAttr personality = funcOp.getPersonalityAttr();
409 if (personality && !hasLandingPad) {
410 droppedPersonalities.insert(personality.getAttr());
411 funcOp.removePersonalityAttr();
412 }
413 return mlir::success();
414 }
415
416 ensureRuntimeDecls(funcOp.getLoc());
417
418 // Set the personality function if it is not already set.
419 // TODO(cir): The personality function should already have been set by this
420 // point. If we've seen a try operation, it will have been set by
421 // emitCXXTryStmt. If we only have cleanups, it may not have been set. We
422 // need to fix that in CodeGen. This is a placeholder until that is done.
423 if (!funcOp.getPersonality())
424 funcOp.setPersonality(kGxxPersonality);
425
426 // Compute, read-only and before any destructive lowering, the dispatches each
427 // initiate's exception can reach (innermost first; more than one for nested
428 // try/catch). A landing pad's catch types are a property of the EH graph, so
429 // deriving them here keeps them independent of the order in which the
430 // destructive per-initiate traversal in lowerEhInitiate tears down shared
431 // token-graph edges. Otherwise a sibling or outer dispatch could be missed,
432 // leaving a landing pad without its catch clause (it would resume past the
433 // handler to std::terminate) or an un-lowered leftover dispatch.
434 // Per initiate: the dispatches its exception can reach (innermost first) and
435 // whether a cleanup lies on its unwind path. Both are properties of the EH
436 // graph and are always consumed together for the same initiate, so they live
437 // in one map keyed by the initiate op.
438 struct InitiateEHInfo {
439 llvm::SmallSetVector<cir::EhDispatchOp, 4> reachedDispatches;
440 bool reachesCleanup = false;
441 };
442 llvm::DenseMap<mlir::Operation *, InitiateEHInfo> initiateInfo;
443 llvm::SmallSetVector<cir::EhDispatchOp, 4> dispatchesToLower;
444 for (cir::EhInitiateOp initiateOp : initiateOps) {
445 InitiateEHInfo &info = initiateInfo[initiateOp.getOperation()];
446 collectReachableDispatches(initiateOp.getEhToken(), info.reachedDispatches,
447 info.reachesCleanup);
448 dispatchesToLower.insert(info.reachedDispatches.begin(),
449 info.reachedDispatches.end());
450 }
451
452 EhTokenMap ehTokenMap;
453 for (cir::EhInitiateOp initiateOp : initiateOps) {
454 const InitiateEHInfo &info = initiateInfo[initiateOp.getOperation()];
455 if (mlir::failed(lowerEhInitiate(initiateOp,
456 info.reachedDispatches.getArrayRef(),
457 info.reachesCleanup, ehTokenMap)))
458 return mlir::failure();
459 }
460
461 // Lower each dispatch exactly once. Every initiate's token block-argument
462 // (ptr, u32) replacements are registered in ehTokenMap by now, so each
463 // dispatch's own (exnPtr, typeId) pair is available. lowerDispatch erases
464 // the dispatch after building its comparison chain.
465 for (cir::EhDispatchOp dispatch : dispatchesToLower) {
466 auto [exnPtr, typeId] = ehTokenMap.lookup(dispatch.getEhToken());
467 assert(exnPtr && typeId &&
468 "dispatch eh_token must be registered in ehTokenMap");
469 lowerDispatch(dispatch, exnPtr, typeId);
470 }
471
472 // Remove the !cir.eh_token block arguments that were replaced by (ptr, u32)
473 // pairs. Iterate in reverse to preserve argument indices during removal.
474 for (mlir::Block &block : funcOp.getBody()) {
475 for (int i = block.getNumArguments() - 1; i >= 0; --i) {
476 if (mlir::isa<cir::EhTokenType>(block.getArgument(i).getType()))
477 block.eraseArgument(i);
478 }
479 }
480
481 // Lower any cir.init_catch_param ops in this function. These materialize
482 // the catch parameter local from the (already lowered) begin_catch result,
483 // and are independent of the eh_token graph traversal above.
484 SmallVector<cir::InitCatchParamOp> initCatchOps;
485 funcOp.walk([&](cir::InitCatchParamOp op) { initCatchOps.push_back(op); });
486 for (cir::InitCatchParamOp op : initCatchOps)
487 lowerInitCatchParam(op);
488
489 // Lower any cir.try_throw ops in this function to cir.try_call of
490 // __cxa_throw / __cxa_rethrow. These are produced by FlattenCFG when a
491 // cir.throw appears inside a cleanup scope or try region.
492 SmallVector<cir::TryThrowOp> tryThrowOps;
493 funcOp.walk([&](cir::TryThrowOp op) { tryThrowOps.push_back(op); });
494 for (cir::TryThrowOp op : tryThrowOps)
495 if (mlir::failed(lowerTryThrow(op)))
496 return mlir::failure();
497
498 return mlir::success();
499}
500
501/// Lower all EH operations connected to a single cir.eh.initiate.
502///
503/// The cir.eh.initiate is the root of a token graph. The token it produces
504/// flows through branch edges to consuming operations:
505///
506/// cir.eh.initiate → (via cir.br) → cir.begin_cleanup
507/// → cir.end_cleanup (via cleanup_token)
508/// → (via cir.br) → cir.eh.dispatch
509/// → (successors) →
510/// cir.begin_catch
511/// → cir.end_catch
512/// (via catch_token)
513/// → cir.resume
514///
515/// A single traversal of the token graph discovers and processes every
516/// connected op inline. The inflight_exception is created up-front without
517/// a catch_type_list; when the dispatch is encountered during traversal,
518/// the catch types are read and set on the inflight op.
519///
520/// Dispatch ops are not lowered here; they are lowered once by the caller after
521/// every initiate has been processed (a dispatch can be shared by sibling
522/// initiates), so this traversal only registers the catch-handler block
523/// arguments reachable through them.
524///
525/// \p ehTokenMap is shared across all initiates in the function so that block
526/// arguments reachable from multiple sibling initiates are registered once.
527mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
528 cir::EhInitiateOp initiateOp,
529 llvm::ArrayRef<cir::EhDispatchOp> reachedDispatches, bool reachesCleanup,
530 EhTokenMap &ehTokenMap) {
531 // TODO(cir): set the runtime calling convention on the runtime calls below.
533 mlir::Value rootToken = initiateOp.getEhToken();
534
535 // The catch clauses for this landing pad come from the dispatches its
536 // exception reaches (computed read-only by the caller before any destructive
537 // lowering). For nested try/catch the exception can reach several dispatches
538 // innermost first, so the landing pad lists their catch types in that order
539 // (matching classic CodeGen), stopping at a catch-all since nothing escapes
540 // it. Deriving this from the original EH graph -- rather than during the
541 // destructive token-graph traversal below -- keeps it correct regardless of
542 // the order in which sibling/nested initiates are lowered.
543 mlir::ArrayAttr catchTypeList;
544 mlir::ArrayAttr filterTypeList;
545 bool catchAll = false;
546 SmallVector<mlir::Attribute> typeSymbols;
547 for (cir::EhDispatchOp dispatch : reachedDispatches) {
548 if (mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr()) {
549 for (mlir::Attribute attr : catchTypes) {
550 if (auto filter = mlir::dyn_cast<cir::EhFilterAttr>(attr)) {
551 SmallVector<mlir::Attribute> filterSymbols;
552 // A filter terminates the landing-pad clause list the same way a
553 // catch-all does. Nothing outside the specification is reachable.
554 for (mlir::Attribute typeAttr : filter.getPermittedTypes()) {
555 auto globalView = mlir::cast<cir::GlobalViewAttr>(typeAttr);
556 filterSymbols.push_back(globalView.getSymbol());
557 }
558 filterTypeList = builder.getArrayAttr(filterSymbols);
559 continue;
560 }
561 typeSymbols.push_back(
562 mlir::cast<cir::GlobalViewAttr>(attr).getSymbol());
563 }
564 }
565 if (dispatch.getDefaultIsCatchAll() || filterTypeList) {
566 catchAll = dispatch.getDefaultIsCatchAll();
567 // A catch-all or filter handles the remaining exceptions, so it
568 // stops the unwind. No enclosing dispatch is reachable past it.
569 assert(dispatch == reachedDispatches.back() &&
570 "catch-all or filter must be the last reachable dispatch");
571 break;
572 }
573 }
574 if (!typeSymbols.empty())
575 catchTypeList = builder.getArrayAttr(typeSymbols);
576
577 builder.setInsertionPoint(initiateOp);
578 auto inflightOp = cir::EhInflightOp::create(
579 builder, initiateOp.getLoc(), initiateOp.getCleanup() || reachesCleanup,
580 catchAll, catchTypeList, filterTypeList);
581
582 ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
583 inflightOp.getTypeId()};
584
585 // Single traversal of the token graph. For each token value (the root token
586 // or a block argument that carries it), we snapshot its users, register
587 // (ptr, u32) replacement arguments on successor blocks, then process every
588 // user inline. This avoids collecting ops into separate vectors.
589 SmallVector<mlir::Value> worklist;
590 SmallPtrSet<mlir::Value, 8> visited;
591 worklist.push_back(rootToken);
592
593 while (!worklist.empty()) {
594 mlir::Value current = worklist.pop_back_val();
595 if (!visited.insert(current).second)
596 continue;
597
598 // Snapshot users before modifying any of them (erasing ops during
599 // iteration would invalidate the use-list iterator).
600 SmallVector<mlir::Operation *> users;
601 for (mlir::OpOperand &use : current.getUses())
602 users.push_back(use.getOwner());
603
604 // Register replacement block arguments on successor blocks (extending the
605 // worklist), then lower the op itself.
606 for (mlir::Operation *user : users) {
607 // Trace into successor blocks to register (ptr, u32) replacement
608 // arguments for any !cir.eh_token block arguments found there. Even
609 // if a block arg was already registered by a sibling initiate, it is
610 // still added to the worklist so that the traversal can reach the
611 // shared dispatch to read catch types.
612 for (unsigned s = 0; s < user->getNumSuccessors(); ++s) {
613 mlir::Block *succ = user->getSuccessor(s);
614 for (mlir::BlockArgument arg : succ->getArguments()) {
615 if (!mlir::isa<cir::EhTokenType>(arg.getType()))
616 continue;
617 if (!ehTokenMap.count(arg)) {
618 mlir::Value ptrArg = succ->addArgument(voidPtrType, arg.getLoc());
619 mlir::Value u32Arg = succ->addArgument(u32Type, arg.getLoc());
620 ehTokenMap[arg] = {ptrArg, u32Arg};
621 }
622 worklist.push_back(arg);
623 }
624 }
625
626 if (auto op = mlir::dyn_cast<cir::BeginCleanupOp>(user)) {
627 // begin_cleanup / end_cleanup are no-ops for Itanium. Erase the
628 // end_cleanup first (drops the cleanup_token use) then the begin.
629 for (auto &tokenUsers :
630 llvm::make_early_inc_range(op.getCleanupToken().getUses())) {
631 if (auto endOp =
632 mlir::dyn_cast<cir::EndCleanupOp>(tokenUsers.getOwner()))
633 endOp.erase();
634 }
635 op.erase();
636 } else if (auto op = mlir::dyn_cast<cir::BeginCatchOp>(user)) {
637 // Replace end_catch → __cxa_end_catch (drops the catch_token use),
638 // then replace begin_catch → __cxa_begin_catch.
639 for (auto &tokenUsers :
640 llvm::make_early_inc_range(op.getCatchToken().getUses())) {
641 if (auto endOp =
642 mlir::dyn_cast<cir::EndCatchOp>(tokenUsers.getOwner())) {
643 builder.setInsertionPoint(endOp);
644 cir::CallOp::create(builder, endOp.getLoc(),
645 mlir::FlatSymbolRefAttr::get(endCatchFunc),
646 voidType, mlir::ValueRange{});
647 endOp.erase();
648 }
649 }
650
651 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
652 builder.setInsertionPoint(op);
653 auto callOp = cir::CallOp::create(
654 builder, op.getLoc(), mlir::FlatSymbolRefAttr::get(beginCatchFunc),
655 u8PtrType, mlir::ValueRange{exnPtr});
656 mlir::Value castResult = callOp.getResult();
657 mlir::Type expectedPtrType = op.getExnPtr().getType();
658 if (castResult.getType() != expectedPtrType)
659 castResult =
660 cir::CastOp::create(builder, op.getLoc(), expectedPtrType,
661 cir::CastKind::bitcast, callOp.getResult());
662 op.getExnPtr().replaceAllUsesWith(castResult);
663 op.erase();
664 } else if (auto op = mlir::dyn_cast<cir::ConstructCatchParamOp>(user)) {
665 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
666 if (mlir::failed(lowerConstructCatchParam(op, exnPtr)))
667 return mlir::failure();
668 } else if (mlir::isa<cir::EhDispatchOp>(user)) {
669 // The dispatch's catch types were already read into every reaching
670 // landing pad (read-only, before this traversal). The dispatch op
671 // itself is lowered once by the caller after all initiates are
672 // processed, so nothing is done here; the successor catch-handler
673 // blocks are still reached via the block-argument registration above.
674 } else if (auto op = mlir::dyn_cast<cir::EhTerminateOp>(user)) {
675 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
676 ensureClangCallTerminate(op.getLoc());
677 builder.setInsertionPoint(op);
678 auto call = cir::CallOp::create(
679 builder, op.getLoc(),
680 mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc), voidType,
681 mlir::ValueRange{exnPtr});
682 call.setNothrowAttr(builder.getUnitAttr());
683 call->setAttr(cir::CIRDialect::getNoReturnAttrName(),
684 builder.getUnitAttr());
685 cir::UnreachableOp::create(builder, op.getLoc());
686 op.erase();
687 } else if (auto op = mlir::dyn_cast<cir::EhUnexpectedOp>(user)) {
688 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
689 ensureCxaCallUnexpectedDecl(op.getLoc());
690 builder.setInsertionPoint(op);
691 auto call = cir::CallOp::create(
692 builder, op.getLoc(),
693 mlir::FlatSymbolRefAttr::get(cxaCallUnexpectedFunc), voidType,
694 mlir::ValueRange{exnPtr});
695 call->setAttr(cir::CIRDialect::getNoReturnAttrName(),
696 builder.getUnitAttr());
697 cir::UnreachableOp::create(builder, op.getLoc());
698 op.erase();
699 } else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
700 auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
701 builder.setInsertionPoint(op);
702 cir::ResumeFlatOp::create(builder, op.getLoc(), exnPtr, typeId);
703 op.erase();
704 } else if (auto op = mlir::dyn_cast<cir::BrOp>(user)) {
705 // Replace eh_token operands with the (ptr, u32) pair.
706 SmallVector<mlir::Value> newOperands;
707 bool changed = false;
708 for (mlir::Value operand : op.getDestOperands()) {
709 auto it = ehTokenMap.find(operand);
710 if (it != ehTokenMap.end()) {
711 newOperands.push_back(it->second.first);
712 newOperands.push_back(it->second.second);
713 changed = true;
714 } else {
715 newOperands.push_back(operand);
716 }
717 }
718 if (changed) {
719 builder.setInsertionPoint(op);
720 // We shouldn't get any loop information here.
721 assert(!op.getLoopIdAttr() && !op.getLoopHintsAttr());
722 cir::BrOp::create(builder, op.getLoc(), newOperands,
723 /*loop_id=*/nullptr, /*loop_hints=*/nullptr,
724 op.getDest());
725 op.erase();
726 }
727 }
728 }
729 }
730
731 initiateOp.erase();
732 return mlir::success();
733}
734
735/// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
736/// The dispatch itself is replaced with a branch to the first comparison
737/// block and then erased.
738void ItaniumEHLowering::lowerDispatch(cir::EhDispatchOp dispatch,
739 mlir::Value exnPtr, mlir::Value typeId) {
740 mlir::Location dispLoc = dispatch.getLoc();
741 mlir::Block *defaultDest = dispatch.getDefaultDestination();
742 mlir::Block *dispatchBlock = dispatch->getBlock();
743
744 llvm::SmallVector<mlir::Attribute> catchAttrs;
745 llvm::SmallVector<mlir::Block *> catchDests;
746 cir::EhFilterAttr filterAttr;
747 mlir::Block *filterDest = nullptr;
748 if (mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr()) {
749 for (auto [attr, dest] :
750 llvm::zip(catchTypes, dispatch.getCatchDestinations())) {
751 if (auto filter = mlir::dyn_cast<cir::EhFilterAttr>(attr)) {
752 assert(!filterAttr && "at most one filter handler");
753 filterAttr = filter;
754 filterDest = dest;
755 continue;
756 }
757 catchAttrs.push_back(attr);
758 catchDests.push_back(dest);
759 }
760 }
761
762 // Build the comparison chain in new blocks inserted after the dispatch's
763 // block. The dispatch itself is replaced with a branch to the first
764 // comparison block and erased below.
765 mlir::Block *insertBefore = dispatchBlock->getNextNode();
766 mlir::Block *falseDest = defaultDest;
767 if (filterAttr) {
768 assert(filterDest && "filter handler requires a destination");
769 // An empty permitted-type list means that every exception violates the
770 // specification, so the filter destination is taken unconditionally.
771 if (filterAttr.getPermittedTypes().empty()) {
772 falseDest = filterDest;
773 } else {
774 // The personality reports a filter failure with a negative selector.
775 // Type ids are still !u32i on the eh_token replacement pair, so recast
776 // to !s32i for the signed comparison. TODO: produce !s32i throughout.
777 auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, u32Type},
778 {dispLoc, dispLoc});
779 mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
780 mlir::Value cmpTypeId = cmpBlock->getArgument(1);
781 mlir::Value signedTypeId = cir::CastOp::create(
782 builder, dispLoc, s32Type, cir::CastKind::integral, cmpTypeId);
783 mlir::Value zero = cir::ConstantOp::create(builder, dispLoc,
784 cir::IntAttr::get(s32Type, 0));
785 auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::lt,
786 signedTypeId, zero);
787 cir::BrCondOp::create(builder, dispLoc, cmpOp, filterDest, defaultDest,
788 mlir::ValueRange{cmpExnPtr, cmpTypeId},
789 mlir::ValueRange{cmpExnPtr, cmpTypeId});
790 insertBefore = cmpBlock;
791 falseDest = cmpBlock;
792 }
793 }
794
795 mlir::Block *firstCmpBlock = nullptr;
796 if (!catchAttrs.empty()) {
797 unsigned numCatches = catchAttrs.size();
798 for (int i = numCatches - 1; i >= 0; --i) {
799 auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, u32Type},
800 {dispLoc, dispLoc});
801
802 mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
803 mlir::Value cmpTypeId = cmpBlock->getArgument(1);
804
805 auto globalView = mlir::cast<cir::GlobalViewAttr>(catchAttrs[i]);
806 auto ehTypeIdOp =
807 cir::EhTypeIdOp::create(builder, dispLoc, globalView.getSymbol());
808 auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::eq,
809 cmpTypeId, ehTypeIdOp.getTypeId());
810
811 cir::BrCondOp::create(builder, dispLoc, cmpOp, catchDests[i], falseDest,
812 mlir::ValueRange{cmpExnPtr, cmpTypeId},
813 mlir::ValueRange{cmpExnPtr, cmpTypeId});
814
815 insertBefore = cmpBlock;
816 falseDest = cmpBlock;
817 firstCmpBlock = cmpBlock;
818 }
819 } else {
820 firstCmpBlock = falseDest;
821 }
822
823 builder.setInsertionPoint(dispatch);
824 cir::BrOp::create(builder, dispLoc, firstCmpBlock,
825 mlir::ValueRange{exnPtr, typeId});
826
827 // The caller lowers each dispatch exactly once after every initiate has been
828 // processed, so no sibling still needs it; erase it now.
829 dispatch.erase();
830}
831
832mlir::FailureOr<cir::FuncOp>
833ItaniumEHLowering::resolveCatchCopyThunk(cir::ConstructCatchParamOp op) {
834 mlir::FlatSymbolRefAttr thunkRef = op.getCopyFnAttr();
835 mlir::StringAttr thunkName = thunkRef.getAttr();
836 auto cached = catchCopyThunks.find(thunkName);
837 if (cached != catchCopyThunks.end())
838 return cached->second;
839
840 cir::FuncOp thunk = mod.lookupSymbol<cir::FuncOp>(thunkRef);
841 if (!thunk)
842 return op.emitError("could not resolve catch-copy thunk symbol");
843 assert(thunk->hasAttr(cir::CIRDialect::getCatchCopyThunkAttrName()) &&
844 "verifier should have rejected non-thunk catch-copy reference");
845 if (thunk.isDeclaration())
846 return op.emitError("catch-copy thunk has no body to inline");
847
848 mlir::Region &thunkRegion = thunk.getRegion();
849 if (!llvm::hasSingleElement(thunkRegion))
850 return op.emitError("multi-block catch-copy thunks are NYI");
851
852 mlir::Block &thunkEntry = thunkRegion.front();
853 assert(thunkEntry.getNumArguments() == 2 &&
854 "catch-copy thunk must have exactly two parameters");
855 if (!mlir::isa<cir::ReturnOp>(thunkEntry.getTerminator()))
856 return op.emitError("catch-copy thunk must end in cir.return");
857
858 catchCopyThunks[thunkName] = thunk;
859 return thunk;
860}
861
862/// Lower a cir.construct_catch_param into the Itanium-specific sequence
863/// that runs before `__cxa_begin_catch` to bind the catch parameter to the
864/// in-flight exception.
865mlir::LogicalResult
866ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
867 mlir::Value exnPtr) {
868 // TODO(cir): set the runtime calling convention on the runtime calls below.
870 mlir::Location loc = op.getLoc();
871 mlir::Value paramAddr = op.getParamAddr();
872 cir::PointerType paramAddrType =
873 mlir::cast<cir::PointerType>(paramAddr.getType());
874
875 if (op.getKind() == cir::InitCatchKind::ReferenceToPointer) {
877 constexpr unsigned headerSize = 32;
878
879 builder.setInsertionPoint(op);
880 auto index = cir::ConstantOp::create(
881 builder, loc, cir::IntAttr::get(u32Type, headerSize));
882 assert((exnPtr.getType() == voidPtrType || exnPtr.getType() == u8PtrType) &&
883 "lowerConstructCatchParam exn ptr not void* or i8*");
884 auto exnObj =
885 cir::PtrStrideOp::create(builder, loc, exnPtr.getType(), exnPtr, index);
886 mlir::Value casted =
887 cir::CastOp::create(builder, loc, paramAddrType.getPointee(),
888 cir::CastKind::bitcast, exnObj);
889 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {}, {});
890 op.erase();
891 return success();
892 }
893
894 if (op.getKind() != cir::InitCatchKind::NonTrivialCopy)
895 return op.emitOpError("only reference_to_pointer and non_trivial_copy "
896 "kinds are supported");
897
898 ensureRuntimeDecls(loc);
899 ensureClangCallTerminate(loc);
900
901 // Call __cxa_get_exception_ptr to get the in-flight exception.
902 builder.setInsertionPoint(op);
903 cir::CallOp getExnCall = cir::CallOp::create(
904 builder, loc, mlir::FlatSymbolRefAttr::get(getExceptionPtrFunc),
905 u8PtrType, mlir::ValueRange{exnPtr});
906 getExnCall.setNothrowAttr(builder.getUnitAttr());
907 mlir::Value adjusted =
908 cir::CastOp::create(builder, loc, paramAddrType, cir::CastKind::bitcast,
909 getExnCall.getResult());
910
911 // Get the thunk function definition.
912 mlir::FailureOr<cir::FuncOp> thunkOr = resolveCatchCopyThunk(op);
913 if (mlir::failed(thunkOr))
914 return mlir::failure();
915 cir::FuncOp thunk = *thunkOr;
916
917 // This is also verified by resolveCatchCopyThunk, but the loop below is
918 // where the constraint is required so let's assert it again here.
919 assert(llvm::hasSingleElement(thunk.getRegion()) &&
920 "multi-block catch-copy thunks are NYI");
921
922 // Clone the thunk function to perform the copy.
923 mlir::Block &thunkEntry = thunk.getRegion().front();
924 mlir::IRMapping mapping;
925 mapping.map(thunkEntry.getArgument(0), paramAddr);
926 mapping.map(thunkEntry.getArgument(1), adjusted);
927 llvm::SmallVector<cir::CallOp> throwingCalls;
928 for (mlir::Operation &thunkOp : thunkEntry.without_terminator()) {
929 mlir::Operation *cloned = builder.clone(thunkOp, mapping);
930 if (cir::CallOp callOp = mlir::dyn_cast<cir::CallOp>(cloned))
931 if (!callOp.getNothrow())
932 throwingCalls.push_back(callOp);
933 }
934 op.erase();
935
936 if (throwingCalls.empty())
937 return mlir::success();
938
939 // All calls in the copy (which is usually just a single call) need to
940 // unwind to a terminate block if it throws an exception.
941 mlir::IRRewriter rewriter(builder);
942 mlir::Block *terminateBlock = nullptr;
943 for (cir::CallOp call : throwingCalls) {
944 if (!terminateBlock)
945 terminateBlock = buildTerminateBlock(call->getParentOfType<cir::FuncOp>(),
946 call.getLoc());
947 cir::replaceCallWithTryCall(call, terminateBlock, call.getLoc(), rewriter);
948 }
949 return mlir::success();
950}
951
952/// Lower a cir.try_throw to a cir.try_call of __cxa_throw (or
953/// __cxa_rethrow for the no-operand rethrow form). Materializes the
954/// type_info and dtor pointers from their symbol attributes, bitcasting
955/// each to !cir.ptr<!void> as required by the runtime function signature.
956mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
957 // TODO(cir): set the runtime calling convention on the runtime calls below.
959 mlir::Location loc = op.getLoc();
960 mlir::Block *normalDest = op.getNormalDest();
961 mlir::Block *unwindDest = op.getUnwindDest();
962 builder.setInsertionPoint(op);
963
964 if (op.rethrows()) {
965 ensureCxaRethrowDecl(loc);
966 cir::TryCallOp::create(
967 builder, loc, mlir::FlatSymbolRefAttr::get(cxaRethrowFunc), voidType,
968 normalDest, unwindDest, mlir::ValueRange{});
969 op.erase();
970 return mlir::success();
971 }
972
973 ensureCxaThrowDecl(loc);
974
975 // Bitcast the exception pointer to void* if necessary.
976 mlir::Value exnPtr = op.getExceptionPtr();
977 if (exnPtr.getType() != voidPtrType)
978 exnPtr = cir::CastOp::create(builder, loc, voidPtrType,
979 cir::CastKind::bitcast, exnPtr);
980
981 // Materialize the type_info pointer, looking up the typed symbol in the
982 // module so we get the correct pointer type for cir.get_global, then
983 // bitcasting to void* to match the runtime signature.
984 mlir::FlatSymbolRefAttr typeInfoAttr = op.getTypeInfoAttr();
985 auto typeInfoGlobal = mod.lookupSymbol<cir::GlobalOp>(typeInfoAttr);
986 if (!typeInfoGlobal)
987 return op.emitError("type_info symbol not found in module");
988 auto typeInfoPtrTy = cir::PointerType::get(typeInfoGlobal.getSymType());
989 mlir::Value typeInfo = cir::GetGlobalOp::create(builder, loc, typeInfoPtrTy,
990 typeInfoAttr.getValue());
991 if (typeInfo.getType() != voidPtrType)
992 typeInfo = cir::CastOp::create(builder, loc, voidPtrType,
993 cir::CastKind::bitcast, typeInfo);
994
995 // Materialize the dtor pointer (or null if no dtor).
996 mlir::Value dtor;
997 if (mlir::FlatSymbolRefAttr dtorAttr = op.getDtorAttr()) {
998 auto dtorFunc = mod.lookupSymbol<cir::FuncOp>(dtorAttr);
999 if (!dtorFunc)
1000 return op.emitError("dtor symbol not found in module");
1001 auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
1002 dtor =
1003 cir::GetGlobalOp::create(builder, loc, dtorPtrTy, dtorAttr.getValue());
1004 if (dtor.getType() != voidPtrType)
1005 dtor = cir::CastOp::create(builder, loc, voidPtrType,
1006 cir::CastKind::bitcast, dtor);
1007 } else {
1008 dtor = cir::ConstantOp::create(
1009 builder, loc,
1010 cir::ConstPtrAttr::get(voidPtrType, builder.getI64IntegerAttr(0)));
1011 }
1012
1013 cir::TryCallOp::create(
1014 builder, loc, mlir::FlatSymbolRefAttr::get(cxaThrowFunc), voidType,
1015 normalDest, unwindDest, mlir::ValueRange{exnPtr, typeInfo, dtor});
1016 op.erase();
1017 return mlir::success();
1018}
1019
1020/// Lower a cir.init_catch_param into the Itanium-specific sequence that
1021/// materializes the catch parameter's local variable from the exception
1022/// pointer returned by __cxa_begin_catch. The shape of the lowering
1023/// depends on the init catch kind:
1024///
1025/// - Reference: the begin_catch result is the adjusted exception object
1026/// pointer, so just bitcast and store it into the alloca.
1027/// - ReferenceToPointer: the companion `cir.construct_catch_param`
1028/// already bound the reference to the exception object, so this lowering
1029/// is a no-op.
1030/// - ReferenceToRecordPointer: the begin_catch result is the adjusted
1031/// pointer value itself, so store it into a temporary and bind the
1032/// reference to the temporary.
1033/// - Pointer: the begin_catch result is
1034/// the pointer value itself, so just bitcast and store it into the
1035/// alloca.
1036/// - Scalar (any other by-value catch): treat the begin_catch result as a
1037/// pointer to the value, load it, and store it into the alloca.
1038/// - Objc: Handle pointer representation with ObjCLifetime.
1039/// - TrivialCopy: copy the exception
1040/// object's bytes into the alloca via cir.copy.
1041/// - NonTrivialCopy: the construction was already performed by the
1042/// companion `cir.construct_catch_param` before `cir.begin_catch`, so
1043/// this lowering is a no-op.
1044///
1045void ItaniumEHLowering::lowerInitCatchParam(cir::InitCatchParamOp op) {
1046 builder.setInsertionPoint(op);
1047 mlir::Location loc = op.getLoc();
1048 mlir::Value exnPtr = op.getExnPtr();
1049 mlir::Value paramAddr = op.getParamAddr();
1050 auto paramAddrType = mlir::cast<cir::PointerType>(paramAddr.getType());
1051 mlir::Type elementType = paramAddrType.getPointee();
1052 cir::InitCatchKind kind = op.getKind();
1053
1054 switch (kind) {
1055 case InitCatchKind::Reference: {
1056 mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
1057 cir::CastKind::bitcast, exnPtr);
1058 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {}, {});
1059 break;
1060 }
1061 case InitCatchKind::ReferenceToPointer:
1062 // The reference was bound to the exception object by the matching
1063 // cir.construct_catch_param before cir.begin_catch.
1064 break;
1065 case InitCatchKind::ReferenceToRecordPointer: {
1066 // __cxa_begin_catch returns the caught pointer by value, and the
1067 // personality function may have adjusted it. Store the adjusted pointer
1068 // into a temporary and bind the reference to it. Assigning to the catch
1069 // parameter then changes the temporary, not the exception object, which
1070 // departs from the language semantics.
1071 auto caughtType = mlir::cast<cir::PointerType>(
1072 mlir::cast<cir::PointerType>(elementType).getPointee());
1073 cir::AllocaOp byRefTmp;
1074 {
1075 mlir::OpBuilder::InsertionGuard guard(builder);
1076 builder.setInsertionPointToStart(
1077 &op->getParentOfType<cir::FuncOp>().getBody().front());
1078 cir::CIRDataLayout dataLayout(mod);
1079 uint64_t alignment =
1080 dataLayout.getAlignment(caughtType, /*useABIAlign=*/true).value();
1081 byRefTmp =
1082 cir::AllocaOp::create(builder, loc, elementType, "exn.byref.tmp",
1083 builder.getI64IntegerAttr(alignment));
1084 }
1085 mlir::Value casted = cir::CastOp::create(builder, loc, caughtType,
1086 cir::CastKind::bitcast, exnPtr);
1087 cir::StoreOp::create(builder, loc, casted, byRefTmp, {}, {}, {}, {}, {});
1088 cir::StoreOp::create(builder, loc, byRefTmp, paramAddr, {}, {}, {}, {}, {});
1089 break;
1090 }
1091 case InitCatchKind::TrivialCopy: {
1092 mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
1093 cir::CastKind::bitcast, exnPtr);
1094 cir::CopyOp::create(builder, loc, paramAddr, srcPtr, {}, {});
1095 break;
1096 }
1097 case InitCatchKind::NonTrivialCopy:
1098 // The non-trivial copy was performed by the matching
1099 // cir.construct_catch_param before cir.begin_catch.
1100 break;
1101 case InitCatchKind::Scalar: {
1102 // Scalar by-value catch (integer, float, complex, etc.). The begin_catch
1103 // result points into the exception object; load the value through a
1104 // typed pointer and store it into the alloca.
1105 mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
1106 cir::CastKind::bitcast, exnPtr);
1107 auto loadOp = cir::LoadOp::create(builder, loc, elementType, srcPtr);
1108 cir::StoreOp::create(builder, loc, loadOp.getResult(), paramAddr, {}, {},
1109 {}, {}, {});
1110 break;
1111 }
1112 case InitCatchKind::Pointer: {
1113 mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
1114 cir::CastKind::bitcast, exnPtr);
1115 cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {}, {});
1116 break;
1117 }
1118 case InitCatchKind::Objc:
1119 llvm_unreachable("InitCatchParam: ObjCLifetime is NYI");
1120 break;
1121 }
1122
1123 op.erase();
1124}
1125
1126//===----------------------------------------------------------------------===//
1127// The Pass
1128//===----------------------------------------------------------------------===//
1129
1130struct CIREHABILoweringPass
1131 : public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
1132 CIREHABILoweringPass() = default;
1133 void runOnOperation() override;
1134};
1135
1136/// Erase all catch-init thunks after the EHABI lowering. CIRGen emits a thunk
1137/// for every `cir.construct_catch_param` op, but those uses should all have
1138/// been replaced during the lowering.
1139static void eraseCatchCopyThunks(mlir::ModuleOp mod) {
1140 llvm::StringRef catchHelperAttr =
1141 cir::CIRDialect::getCatchCopyThunkAttrName();
1142 for (cir::FuncOp f : llvm::make_early_inc_range(mod.getOps<cir::FuncOp>())) {
1143 if (!f->hasAttr(catchHelperAttr))
1144 continue;
1145 // This is an expensive check, so we need to rely on the implementation
1146 // to have done the right thing.
1147 assert(mlir::SymbolTable::symbolKnownUseEmpty(f, mod) &&
1148 "catch-init helper has remaining users");
1149 f.erase();
1150 }
1151}
1152
1153void CIREHABILoweringPass::runOnOperation() {
1154 auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
1155
1156 // The target triple is attached to the module as the "cir.triple"
1157 // attribute. If it is absent (e.g. a CIR module parsed from text without a
1158 // triple) we cannot determine the ABI and must skip the pass.
1159 auto tripleAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(
1160 mod->getAttr(cir::CIRDialect::getTripleAttrName()));
1161 if (!tripleAttr) {
1162 mod.emitError("Module has no target triple");
1163 return;
1164 }
1165
1166 // Select the ABI-specific lowering handler from the triple. The Microsoft
1167 // C++ ABI targets a Windows MSVC environment; everything else uses Itanium.
1168 // Extend this when Microsoft ABI lowering is added.
1169 llvm::Triple triple(tripleAttr.getValue());
1170 std::unique_ptr<EHABILowering> lowering;
1171 if (triple.isWindowsMSVCEnvironment()) {
1172 mod.emitError(
1173 "EH ABI lowering is not yet implemented for the Microsoft ABI");
1174 return signalPassFailure();
1175 } else {
1176 lowering = std::make_unique<ItaniumEHLowering>(mod);
1177 }
1178
1179 if (mlir::failed(lowering->run()))
1180 return signalPassFailure();
1181
1182 // Sweep away any the thunk functions. They've been inlined to all users now.
1183 eraseCatchCopyThunks(mod);
1184}
1185
1186} // namespace
1187
1188std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
1189 return std::make_unique<CIREHABILoweringPass>();
1190}
*collection of selector each with an associated kind and an ordered *collection of selectors A selector has a kind
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.
void info(bool Verbose, unsigned Level, const char *Fmt, Ts &&...Args)
Prints an indented note to stderr when Verbose is set.
Definition Utils.h:57
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...
unsigned long uint64_t
std::unique_ptr< Pass > createCIREHABILoweringPass()
__DEVICE__ _Tp arg(const std::complex< _Tp > &__c)
static bool opFuncCallingConv()
static bool sizeOfUnwindException()