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