Author: Andy Kaylor
Date: 2026-05-07T13:01:53-07:00
New Revision: 36cfc5929b61ceefa264eeb6283759bf1c192d29

URL: 
https://github.com/llvm/llvm-project/commit/36cfc5929b61ceefa264eeb6283759bf1c192d29
DIFF: 
https://github.com/llvm/llvm-project/commit/36cfc5929b61ceefa264eeb6283759bf1c192d29.diff

LOG: [CIR] Lower cir.construct_catch_param on Itanium (#195904)

Implement Itanium-ABI lowering of the `cir.construct_catch_param`
operation. This operation encapsulates the target-specific work that
must happen before `__cxa_begin_catch` to bind an in-flight exception
object to a non-trivially-copyable catch parameter

In order to allow the full copy-constructor call generation handling,
including call site attribute generation, to be reused during codegen,
we will be generating a thunk function to perform the copy construction
when it is needed. This function gets inlined during EHABI lowering.
This allows us to generate a target-independent representation during
the initial CIR code generation without having to duplicate the copy
construction logic in the EHABI lowering pass.

The actual generation of the thunk function and the
construct_catch_param operation will be added in a follow-up change.

Assisted-by: Cursor / claude-opus-4.7-thinking-xhigh

Added: 
    clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
    clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.h
    clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
    clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIRDialect.td
    clang/lib/CIR/Dialect/IR/CIRDialect.cpp
    clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
    clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
    clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
    clang/test/CIR/IR/construct-catch-param.cir
    clang/test/CIR/IR/invalid-construct-catch-param.cir

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 5b808ea92f470..aaa7b48262c80 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -78,6 +78,7 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getRecordLayoutsAttrName() { return 
"cir.record_layouts"; }
     static llvm::StringRef getCUDABinaryHandleAttrName() { return 
"cir.cu.binary_handle"; }
     static llvm::StringRef getMustTailAttrName() { return "musttail"; }
+    static llvm::StringRef getCatchCopyThunkAttrName() { return 
"cir.eh.catch_copy_thunk"; }
 
     static llvm::StringRef getAMDGPUCodeObjectVersionAttrName() { return 
"cir.amdhsa_code_object_version"; }
     static llvm::StringRef getAMDGPUPrintfKindAttrName() { return 
"cir.amdgpu_printf_kind"; }

diff  --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 22aaa8602450c..9162cecaa1ed5 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -4316,6 +4316,10 @@ LogicalResult 
cir::ConstructCatchParamOp::verifySymbolUses(
     return emitOpError("'")
            << getCopyFn() << "' does not reference a valid cir.func";
 
+  if (!fn->hasAttr(cir::CIRDialect::getCatchCopyThunkAttrName()))
+    return emitOpError("catch-init copy_fn must be tagged with the ")
+           << cir::CIRDialect::getCatchCopyThunkAttrName() << " attribute";
+
   cir::FuncType fnType = fn.getFunctionType();
   if (fnType.getNumInputs() != 2 || !fnType.hasVoidReturn())
     return emitOpError("catch-init copy_fn must take two pointer arguments and 
"

diff  --git a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp 
b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
new file mode 100644
index 0000000000000..f7e7bb71f9cce
--- /dev/null
+++ b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
@@ -0,0 +1,72 @@
+//===- CIRTransformUtils.cpp - Shared helpers for CIR transforms 
----------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRTransformUtils.h"
+
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+
+mlir::Block *cir::replaceCallWithTryCall(cir::CallOp callOp,
+                                         mlir::Block *unwindDest,
+                                         mlir::Location loc,
+                                         mlir::RewriterBase &rewriter) {
+  mlir::Block *callBlock = callOp->getBlock();
+
+  assert(!callOp.getNothrow() && "call is not expected to throw");
+
+  // Split the block after the call - remaining ops become the normal
+  // destination.
+  mlir::Block *normalDest =
+      rewriter.splitBlock(callBlock, std::next(callOp->getIterator()));
+
+  // Build the try_call to replace the original call.
+  rewriter.setInsertionPoint(callOp);
+  cir::TryCallOp tryCallOp;
+  if (callOp.isIndirect()) {
+    mlir::Value indTarget = callOp.getIndirectCall();
+    auto ptrTy = mlir::cast<cir::PointerType>(indTarget.getType());
+    auto resTy = mlir::cast<cir::FuncType>(ptrTy.getPointee());
+    tryCallOp =
+        cir::TryCallOp::create(rewriter, loc, indTarget, resTy, normalDest,
+                               unwindDest, callOp.getArgOperands());
+  } else {
+    mlir::Type resType = callOp->getNumResults() > 0
+                             ? callOp->getResult(0).getType()
+                             : mlir::Type();
+    tryCallOp =
+        cir::TryCallOp::create(rewriter, loc, callOp.getCalleeAttr(), resType,
+                               normalDest, unwindDest, 
callOp.getArgOperands());
+  }
+
+  // Copy all attributes from the original call except those already set by
+  // TryCallOp::create or that are operation-specific and should not be copied.
+  llvm::StringRef excludedAttrs[] = {
+      cir::CIRDialect::getCalleeAttrName(), // Set by create()
+      cir::CIRDialect::getOperandSegmentSizesAttrName(),
+  };
+  for (mlir::NamedAttribute attr : callOp->getAttrs()) {
+    if (llvm::is_contained(excludedAttrs, attr.getName()))
+      continue;
+    assert(!llvm::is_contained(
+               {
+                   cir::CIRDialect::getNoThrowAttrName(),
+                   cir::CIRDialect::getNoUnwindAttrName(),
+               },
+               attr.getName()) &&
+           "unexpected attribute on converted call");
+    tryCallOp->setAttr(attr.getName(), attr.getValue());
+  }
+
+  // Replace uses of the call result with the try_call result. Use the
+  // rewriter API so any listener (e.g. the pattern rewriter in
+  // FlattenCFG) is notified of the in-place modifications to each user.
+  if (callOp->getNumResults() > 0)
+    rewriter.replaceAllUsesWith(callOp->getResult(0), tryCallOp.getResult());
+
+  rewriter.eraseOp(callOp);
+  return normalDest;
+}

diff  --git a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.h 
b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.h
new file mode 100644
index 0000000000000..b8c20a1fcebfe
--- /dev/null
+++ b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.h
@@ -0,0 +1,35 @@
+//===- CIRTransformUtils.h - Shared helpers for CIR transforms -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CIR_DIALECT_TRANSFORMS_CIRTRANSFORMUTILS_H
+#define CIR_DIALECT_TRANSFORMS_CIRTRANSFORMUTILS_H
+
+#include "mlir/IR/Location.h"
+#include "mlir/IR/PatternMatch.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+
+namespace cir {
+
+/// Replace a `cir::CallOp` with a `cir::TryCallOp` whose unwind destination
+/// is \p unwindDest. The call's parent block is split immediately after the
+/// call; the resulting suffix block becomes the try_call's normal
+/// destination and is returned to the caller.
+///
+/// All attributes of the original call other than the callee and operand
+/// segment sizes (which `TryCallOp::create` sets itself) are copied onto
+/// the new try_call. Uses of the original call's result, if any, are
+/// redirected to the try_call's result, and the original call is erased.
+///
+/// The call must not already be marked nothrow.
+mlir::Block *replaceCallWithTryCall(cir::CallOp callOp, mlir::Block 
*unwindDest,
+                                    mlir::Location loc,
+                                    mlir::RewriterBase &rewriter);
+
+} // namespace cir
+
+#endif // CIR_DIALECT_TRANSFORMS_CIRTRANSFORMUTILS_H

diff  --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt 
b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
index 092ccfac7ddb7..2557e14c25505 100644
--- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(TargetLowering)
 add_clang_library(MLIRCIRTransforms
   CIRCanonicalize.cpp
   CIRSimplify.cpp
+  CIRTransformUtils.cpp
   CXXABILowering.cpp
   EHABILowering.cpp
   TargetLowering.cpp

diff  --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index baec91de9f89c..1d918dc648ac3 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -11,21 +11,26 @@
 // C++ ABI is supported.
 //
 // The Itanium ABI lowering performs these transformations:
-//   - cir.eh.initiate      → cir.eh.inflight_exception (landing pad)
-//   - cir.eh.dispatch      → cir.eh.typeid + cir.cmp + cir.brcond chains
-//   - cir.begin_cleanup    → (removed)
-//   - cir.end_cleanup      → (removed)
-//   - cir.begin_catch      → call to __cxa_begin_catch
-//   - cir.end_catch        → call to __cxa_end_catch
-//   - cir.eh.terminate     → call to __clang_call_terminate + unreachable
-//   - cir.resume           → cir.resume.flat
-//   - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
+//   - cir.eh.initiate            → cir.eh.inflight_exception (landing pad)
+//   - cir.eh.dispatch            → cir.eh.typeid + cir.cmp + cir.brcond chains
+//   - cir.begin_cleanup          → (removed)
+//   - cir.end_cleanup            → (removed)
+//   - cir.begin_catch            → call to __cxa_begin_catch
+//   - cir.end_catch              → call to __cxa_end_catch
+//   - cir.eh.terminate           → call to __clang_call_terminate + 
unreachable
+//   - cir.resume                 → cir.resume.flat
+//   - !cir.eh_token values       → (!cir.ptr<!void>, !u32i) value pairs
+//   - cir.construct_catch_param  → __cxa_get_exception_ptr + inlined
+//                                  catch-copy thunk body
 //   - personality function set on functions requiring EH
 //
 
//===----------------------------------------------------------------------===//
 
+#include "CIRTransformUtils.h"
 #include "PassDetail.h"
 #include "mlir/IR/Builders.h"
+#include "mlir/IR/IRMapping.h"
+#include "mlir/IR/PatternMatch.h"
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/IR/CIROpsEnums.h"
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
@@ -117,19 +122,28 @@ class ItaniumEHLowering : public EHABILowering {
   cir::FuncOp personalityFunc;
   cir::FuncOp beginCatchFunc;
   cir::FuncOp endCatchFunc;
+  cir::FuncOp getExceptionPtrFunc;
   cir::FuncOp clangCallTerminateFunc;
 
+  DenseMap<mlir::StringAttr, cir::FuncOp> catchCopyThunks;
+
   constexpr const static ::llvm::StringLiteral kGxxPersonality =
       "__gxx_personality_v0";
 
   void ensureRuntimeDecls(mlir::Location loc);
   void ensureClangCallTerminate(mlir::Location loc);
+  mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
+  mlir::FailureOr<cir::FuncOp>
+  resolveCatchCopyThunk(cir::ConstructCatchParamOp op);
   mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
-  void lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
-                       SmallVectorImpl<mlir::Operation *> &deadOps);
+  mlir::LogicalResult
+  lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
+                  SmallVectorImpl<mlir::Operation *> &deadOps);
   void lowerDispatch(cir::EhDispatchOp dispatch, mlir::Value exnPtr,
                      mlir::Value typeId,
                      SmallVectorImpl<mlir::Operation *> &deadOps);
+  mlir::LogicalResult lowerConstructCatchParam(cir::ConstructCatchParamOp op,
+                                               mlir::Value exnPtr);
   void lowerInitCatchParam(cir::InitCatchParamOp op);
 };
 
@@ -174,6 +188,13 @@ void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location 
loc) {
     endCatchFunc =
         getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", 
endCatchFuncTy);
   }
+
+  if (!getExceptionPtrFunc) {
+    auto getExceptionPtrFuncTy =
+        cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
+    getExceptionPtrFunc = getOrCreateRuntimeFuncDecl(
+        mod, loc, "__cxa_get_exception_ptr", getExceptionPtrFuncTy);
+  }
 }
 
 /// Ensure the __clang_call_terminate function exists in the module. This
@@ -231,6 +252,26 @@ void 
ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
   clangCallTerminateFunc = funcOp;
 }
 
+/// Create a terminate landing pad block at the end of the specified function.
+mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
+                                                    mlir::Location loc) {
+  assert(clangCallTerminateFunc &&
+         "ensureClangCallTerminate must run before buildTerminateBlock");
+  mlir::Region &body = funcOp.getRegion();
+  mlir::Block *terminateBlock = builder.createBlock(&body, body.end());
+  auto inflight = cir::EhInflightOp::create(
+      builder, loc, /*cleanup=*/false, /*catch_all=*/true,
+      /*catch_type_list=*/mlir::ArrayAttr{});
+  auto terminateCall = cir::CallOp::create(
+      builder, loc, mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc),
+      voidType, mlir::ValueRange{inflight.getExceptionPtr()});
+  terminateCall.setNothrowAttr(builder.getUnitAttr());
+  terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
+                         builder.getUnitAttr());
+  cir::UnreachableOp::create(builder, loc);
+  return terminateBlock;
+}
+
 /// Lower all EH operations in a single function.
 mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
   if (funcOp.isDeclaration())
@@ -263,7 +304,8 @@ mlir::LogicalResult 
ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
   EhTokenMap ehTokenMap;
   SmallVector<mlir::Operation *> deadOps;
   for (cir::EhInitiateOp initiateOp : initiateOps)
-    lowerEhInitiate(initiateOp, ehTokenMap, deadOps);
+    if (mlir::failed(lowerEhInitiate(initiateOp, ehTokenMap, deadOps)))
+      return mlir::failure();
 
   // Erase operations that were deferred during per-initiate processing
   // (dispatch ops whose catch types were read by multiple initiates).
@@ -316,7 +358,7 @@ mlir::LogicalResult 
ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
 ///
 /// \p ehTokenMap is shared across all initiates in the function so that block
 /// arguments reachable from multiple sibling initiates are registered once.
-void ItaniumEHLowering::lowerEhInitiate(
+mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
     cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
     SmallVectorImpl<mlir::Operation *> &deadOps) {
   mlir::Value rootToken = initiateOp.getEhToken();
@@ -411,6 +453,10 @@ void ItaniumEHLowering::lowerEhInitiate(
                                   cir::CastKind::bitcast, callOp.getResult());
         op.getExnPtr().replaceAllUsesWith(castResult);
         op.erase();
+      } else if (auto op = mlir::dyn_cast<cir::ConstructCatchParamOp>(user)) {
+        auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
+        if (mlir::failed(lowerConstructCatchParam(op, exnPtr)))
+          return mlir::failure();
       } else if (auto op = mlir::dyn_cast<cir::EhDispatchOp>(user)) {
         // Read catch types from the dispatch and set them on the inflight op.
         mlir::ArrayAttr catchTypes = op.getCatchTypesAttr();
@@ -472,6 +518,7 @@ void ItaniumEHLowering::lowerEhInitiate(
   }
 
   initiateOp.erase();
+  return mlir::success();
 }
 
 /// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
@@ -538,6 +585,105 @@ void ItaniumEHLowering::lowerDispatch(
   deadOps.push_back(dispatch);
 }
 
+mlir::FailureOr<cir::FuncOp>
+ItaniumEHLowering::resolveCatchCopyThunk(cir::ConstructCatchParamOp op) {
+  mlir::FlatSymbolRefAttr thunkRef = op.getCopyFnAttr();
+  mlir::StringAttr thunkName = thunkRef.getAttr();
+  auto cached = catchCopyThunks.find(thunkName);
+  if (cached != catchCopyThunks.end())
+    return cached->second;
+
+  cir::FuncOp thunk = mod.lookupSymbol<cir::FuncOp>(thunkRef);
+  if (!thunk)
+    return op.emitError("could not resolve catch-copy thunk symbol");
+  assert(thunk->hasAttr(cir::CIRDialect::getCatchCopyThunkAttrName()) &&
+         "verifier should have rejected non-thunk catch-copy reference");
+  if (thunk.isDeclaration())
+    return op.emitError("catch-copy thunk has no body to inline");
+
+  mlir::Region &thunkRegion = thunk.getRegion();
+  if (!llvm::hasSingleElement(thunkRegion))
+    return op.emitError("multi-block catch-copy thunks are NYI");
+
+  mlir::Block &thunkEntry = thunkRegion.front();
+  assert(thunkEntry.getNumArguments() == 2 &&
+         "catch-copy thunk must have exactly two parameters");
+  if (!mlir::isa<cir::ReturnOp>(thunkEntry.getTerminator()))
+    return op.emitError("catch-copy thunk must end in cir.return");
+
+  catchCopyThunks[thunkName] = thunk;
+  return thunk;
+}
+
+/// Lower a cir.construct_catch_param into the Itanium-specific sequence
+/// that runs before `__cxa_begin_catch` to bind the catch parameter to the
+/// in-flight exception.
+mlir::LogicalResult
+ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
+                                            mlir::Value exnPtr) {
+  if (op.getKind() != cir::InitCatchKind::NonTrivialCopy)
+    return op.emitError(
+        "ConstructCatchParam: only non_trivial_copy is supported");
+
+  mlir::Location loc = op.getLoc();
+  ensureRuntimeDecls(loc);
+  ensureClangCallTerminate(loc);
+
+  mlir::Value paramAddr = op.getParamAddr();
+  cir::PointerType paramAddrType =
+      mlir::cast<cir::PointerType>(paramAddr.getType());
+
+  // Call __cxa_get_exception_ptr to get the in-flight exception.
+  builder.setInsertionPoint(op);
+  cir::CallOp getExnCall = cir::CallOp::create(
+      builder, loc, mlir::FlatSymbolRefAttr::get(getExceptionPtrFunc),
+      u8PtrType, mlir::ValueRange{exnPtr});
+  getExnCall.setNothrowAttr(builder.getUnitAttr());
+  mlir::Value adjusted =
+      cir::CastOp::create(builder, loc, paramAddrType, cir::CastKind::bitcast,
+                          getExnCall.getResult());
+
+  // Get the thunk function definition.
+  mlir::FailureOr<cir::FuncOp> thunkOr = resolveCatchCopyThunk(op);
+  if (mlir::failed(thunkOr))
+    return mlir::failure();
+  cir::FuncOp thunk = *thunkOr;
+
+  // This is also verified by resolveCatchCopyThunk, but the loop below is
+  // where the constraint is required so let's assert it again here.
+  assert(llvm::hasSingleElement(thunk.getRegion()) &&
+         "multi-block catch-copy thunks are NYI");
+
+  // Clone the thunk function to perform the copy.
+  mlir::Block &thunkEntry = thunk.getRegion().front();
+  mlir::IRMapping mapping;
+  mapping.map(thunkEntry.getArgument(0), paramAddr);
+  mapping.map(thunkEntry.getArgument(1), adjusted);
+  llvm::SmallVector<cir::CallOp> throwingCalls;
+  for (mlir::Operation &thunkOp : thunkEntry.without_terminator()) {
+    mlir::Operation *cloned = builder.clone(thunkOp, mapping);
+    if (cir::CallOp callOp = mlir::dyn_cast<cir::CallOp>(cloned))
+      if (!callOp.getNothrow())
+        throwingCalls.push_back(callOp);
+  }
+  op.erase();
+
+  if (throwingCalls.empty())
+    return mlir::success();
+
+  // All calls in the copy (which is usually just a single call) need to
+  // unwind to a terminate block if it throws an exception.
+  mlir::IRRewriter rewriter(builder);
+  mlir::Block *terminateBlock = nullptr;
+  for (cir::CallOp call : throwingCalls) {
+    if (!terminateBlock)
+      terminateBlock = 
buildTerminateBlock(call->getParentOfType<cir::FuncOp>(),
+                                           call.getLoc());
+    cir::replaceCallWithTryCall(call, terminateBlock, call.getLoc(), rewriter);
+  }
+  return mlir::success();
+}
+
 /// Lower a cir.init_catch_param into the Itanium-specific sequence that
 /// materializes the catch parameter's local variable from the exception
 /// pointer returned by __cxa_begin_catch. The shape of the lowering
@@ -554,8 +700,9 @@ void ItaniumEHLowering::lowerDispatch(
 ///   - Objc: Handle pointer representation with ObjCLifetime.
 ///   - TrivialCopy: copy the exception
 ///     object's bytes into the alloca via cir.copy.
-///   - NonTrivialCopy: copy the exception
-///     object's bytes into the alloca via copy constructor.
+///   - NonTrivialCopy: the construction was already performed by the
+///     companion `cir.construct_catch_param` before `cir.begin_catch`, so
+///     this lowering is a no-op.
 ///
 void ItaniumEHLowering::lowerInitCatchParam(cir::InitCatchParamOp op) {
   builder.setInsertionPoint(op);
@@ -591,10 +738,10 @@ void 
ItaniumEHLowering::lowerInitCatchParam(cir::InitCatchParamOp op) {
     cir::CopyOp::create(builder, loc, paramAddr, srcPtr, {}, {});
     break;
   }
-  case InitCatchKind::NonTrivialCopy: {
-    llvm_unreachable("InitCatchParam: non-trivial-copy is NYI");
+  case InitCatchKind::NonTrivialCopy:
+    // The non-trivial copy was performed by the matching
+    // cir.construct_catch_param before cir.begin_catch.
     break;
-  }
   case InitCatchKind::Scalar: {
     // Scalar by-value catch (integer, float, complex, etc.). The begin_catch
     // result points into the exception object; load the value through a
@@ -630,6 +777,23 @@ struct CIREHABILoweringPass
   void runOnOperation() override;
 };
 
+/// Erase all catch-init thunks after the EHABI lowering. CIRGen emits a thunk
+/// for every `cir.construct_catch_param` op, but those uses should all have
+/// been replaced during the lowering.
+static void eraseCatchCopyThunks(mlir::ModuleOp mod) {
+  llvm::StringRef catchHelperAttr =
+      cir::CIRDialect::getCatchCopyThunkAttrName();
+  for (cir::FuncOp f : llvm::make_early_inc_range(mod.getOps<cir::FuncOp>())) {
+    if (!f->hasAttr(catchHelperAttr))
+      continue;
+    // This is an expensive check, so we need to rely on the implementation
+    // to have done the right thing.
+    assert(mlir::SymbolTable::symbolKnownUseEmpty(f, mod) &&
+           "catch-init helper has remaining users");
+    f.erase();
+  }
+}
+
 void CIREHABILoweringPass::runOnOperation() {
   auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
 
@@ -658,6 +822,9 @@ void CIREHABILoweringPass::runOnOperation() {
 
   if (mlir::failed(lowering->run()))
     return signalPassFailure();
+
+  // Sweep away any the thunk functions. They've been inlined to all users now.
+  eraseCatchCopyThunks(mod);
 }
 
 } // namespace

diff  --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp 
b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 62be199c87d47..a0e0d07d1d4e0 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -11,6 +11,7 @@
 //
 
//===----------------------------------------------------------------------===//
 
+#include "CIRTransformUtils.h"
 #include "PassDetail.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/IR/Block.h"
@@ -682,70 +683,6 @@ static void collectResumeOps(mlir::Region &region,
   region.walk([&](cir::ResumeOp resumeOp) { resumeOps.push_back(resumeOp); });
 }
 
-// Replace a cir.call with a cir.try_call that unwinds to the `unwindDest`
-// block if an exception is thrown.
-static void replaceCallWithTryCall(cir::CallOp callOp, mlir::Block *unwindDest,
-                                   mlir::Location loc,
-                                   mlir::PatternRewriter &rewriter) {
-  mlir::Block *callBlock = callOp->getBlock();
-
-  assert(!callOp.getNothrow() && "call is not expected to throw");
-
-  // Split the block after the call - remaining ops become the normal
-  // destination.
-  mlir::Block *normalDest =
-      rewriter.splitBlock(callBlock, std::next(callOp->getIterator()));
-
-  // Build the try_call to replace the original call.
-  rewriter.setInsertionPoint(callOp);
-  cir::TryCallOp tryCallOp;
-  if (callOp.isIndirect()) {
-    mlir::Value indTarget = callOp.getIndirectCall();
-    auto ptrTy = mlir::cast<cir::PointerType>(indTarget.getType());
-    auto resTy = mlir::cast<cir::FuncType>(ptrTy.getPointee());
-    tryCallOp =
-        cir::TryCallOp::create(rewriter, loc, indTarget, resTy, normalDest,
-                               unwindDest, callOp.getArgOperands());
-  } else {
-    mlir::Type resType = callOp->getNumResults() > 0
-                             ? callOp->getResult(0).getType()
-                             : mlir::Type();
-    tryCallOp =
-        cir::TryCallOp::create(rewriter, loc, callOp.getCalleeAttr(), resType,
-                               normalDest, unwindDest, 
callOp.getArgOperands());
-  }
-
-  // Copy all attributes from the original call except those already set by
-  // TryCallOp::create or that are operation-specific and should not be copied.
-  llvm::StringRef excludedAttrs[] = {
-      CIRDialect::getCalleeAttrName(), // Set by create()
-      CIRDialect::getOperandSegmentSizesAttrName(),
-  };
-#ifndef NDEBUG
-  // We don't expect to ever see any of these attributes on a call that we
-  // converted to a try_call.
-  llvm::StringRef unexpectedAttrs[] = {
-      CIRDialect::getNoThrowAttrName(),
-      CIRDialect::getNoUnwindAttrName(),
-  };
-#endif
-  for (mlir::NamedAttribute attr : callOp->getAttrs()) {
-    if (llvm::is_contained(excludedAttrs, attr.getName()))
-      continue;
-    assert(!llvm::is_contained(unexpectedAttrs, attr.getName()) &&
-           "unexpected attribute on converted call");
-    tryCallOp->setAttr(attr.getName(), attr.getValue());
-  }
-
-  // Replace uses of the call result with the try_call result.
-  // Use the rewriter API so that the pattern rewriter is notified of the
-  // in-place modifications to each user operation.
-  if (callOp->getNumResults() > 0)
-    rewriter.replaceAllUsesWith(callOp->getResult(0), tryCallOp.getResult());
-
-  rewriter.eraseOp(callOp);
-}
-
 // Create a shared unwind destination block. The block contains a
 // cir.eh.initiate operation (optionally with the cleanup attribute) and a
 // branch to the given destination block, passing the eh_token.

diff  --git a/clang/test/CIR/IR/construct-catch-param.cir 
b/clang/test/CIR/IR/construct-catch-param.cir
index 805d917aef30b..500033a6a6996 100644
--- a/clang/test/CIR/IR/construct-catch-param.cir
+++ b/clang/test/CIR/IR/construct-catch-param.cir
@@ -14,11 +14,13 @@ cir.global "private" constant external @_ZTI1E : 
!cir.ptr<!u8i>
 // attribute; the verifier checks the symbol resolves and matches the
 // (T*, T*) -> void signature.
 cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E(
-    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>) {
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
   cir.return
 }
 
 // CHECK: cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E(
+// CHECK-SAME: attributes {cir.eh.catch_copy_thunk}
 // CHECK:   cir.return
 // CHECK: }
 

diff  --git a/clang/test/CIR/IR/invalid-construct-catch-param.cir 
b/clang/test/CIR/IR/invalid-construct-catch-param.cir
index 6704b549e1ecd..ab1f8715b0c18 100644
--- a/clang/test/CIR/IR/invalid-construct-catch-param.cir
+++ b/clang/test/CIR/IR/invalid-construct-catch-param.cir
@@ -40,8 +40,43 @@ module {
 
 cir.global "private" constant external @_ZTI1E : !cir.ptr<!u8i>
 
+// copy_fn has the right signature but is not tagged as a catch-copy thunk.
+cir.func private @plain_copy_fn(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
+
+cir.func @copy_fn_missing_thunk_attr() {
+  cir.scope {
+    %p = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+    cir.try {
+      cir.yield
+    } catch [type #cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>]
+        (%eh_token: !cir.eh_token) {
+      // expected-error @below {{catch-init copy_fn must be tagged with the 
cir.eh.catch_copy_thunk attribute}}
+      cir.construct_catch_param non_trivial_copy %eh_token to %p using 
@plain_copy_fn
+        : !cir.ptr<!rec_E>
+      %catch_token, %exn_ptr = cir.begin_catch %eh_token
+        : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+      cir.end_catch %catch_token : !cir.catch_token
+      cir.yield
+    }
+  }
+  cir.return
+}
+
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_E = !cir.record<struct "E" padded {!u8i}>
+
+module {
+
+cir.global "private" constant external @_ZTI1E : !cir.ptr<!u8i>
+
 // copy_fn takes only one argument (should be two).
 cir.func private @bad_copy_fn_arity(!cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk}
 
 cir.func @copy_fn_wrong_arity() {
   cir.scope {
@@ -78,6 +113,7 @@ cir.global "private" constant external @_ZTI1E : 
!cir.ptr<!u8i>
 // copy_fn returns a non-void type.
 cir.func private @bad_copy_fn_ret(
     !cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> !s32i
+    attributes {cir.eh.catch_copy_thunk}
 
 cir.func @copy_fn_non_void_return() {
   cir.scope {
@@ -115,6 +151,7 @@ cir.global "private" constant external @_ZTI1E : 
!cir.ptr<!u8i>
 // type (!cir.ptr<!s32i> vs !cir.ptr<!rec_E>).
 cir.func private @bad_copy_fn_first_arg(
     !cir.ptr<!s32i>, !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk}
 
 cir.func @copy_fn_first_arg_mismatch() {
   cir.scope {
@@ -152,6 +189,7 @@ cir.global "private" constant external @_ZTI1E : 
!cir.ptr<!u8i>
 // type (the source argument must also be !cir.ptr<!rec_E>).
 cir.func private @bad_copy_fn_second_arg(
     !cir.ptr<!rec_E>, !cir.ptr<!s32i>)
+    attributes {cir.eh.catch_copy_thunk}
 
 cir.func @copy_fn_second_arg_mismatch() {
   cir.scope {
@@ -185,7 +223,8 @@ module {
 cir.global "private" constant external @_ZTI1E : !cir.ptr<!u8i>
 
 cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E(
-    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>) {
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
   cir.return
 }
 

diff  --git 
a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir 
b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
new file mode 100644
index 0000000000000..e400353f36802
--- /dev/null
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch-invalid.cir
@@ -0,0 +1,175 @@
+// RUN: cir-opt %s -cir-eh-abi-lowering -split-input-file -verify-diagnostics
+
+// Diagnostics emitted by the Itanium EHABI lowering for unsupported or
+// malformed cir.construct_catch_param ops. Each case must terminate with a
+// clean pass failure (not a crash).
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_E = !cir.record<struct "E" padded {!u8i}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.global "private" external @_ZTVN10__cxxabiv117__class_type_infoE
+    : !cir.ptr<!cir.ptr<!u8i>>
+cir.global constant linkonce_odr @_ZTI1E
+    = #cir.typeinfo<{
+        #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
+            : !cir.ptr<!u8i>}>
+    : !cir.record<struct  {!cir.ptr<!u8i>}>
+
+cir.func private @_Z8mayThrowv()
+cir.func private @_ZN1EC1ERKS_(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
+
+cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E_ok(
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
+  cir.call @_ZN1EC1ERKS_(%arg0, %arg1)
+      : (!cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> ()
+  cir.return
+}
+
+// Only non_trivial_copy is currently supported on Itanium.
+cir.func @bad_kind() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb6
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb5
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  // expected-error @below {{ConstructCatchParam: only non_trivial_copy is 
supported}}
+  cir.construct_catch_param trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E_ok : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb6
+^bb5(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb6:
+  cir.return
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_E = !cir.record<struct "E" padded {!u8i}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.global "private" external @_ZTVN10__cxxabiv117__class_type_infoE
+    : !cir.ptr<!cir.ptr<!u8i>>
+cir.global constant linkonce_odr @_ZTI1E
+    = #cir.typeinfo<{
+        #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
+            : !cir.ptr<!u8i>}>
+    : !cir.record<struct  {!cir.ptr<!u8i>}>
+
+cir.func private @_Z8mayThrowv()
+
+// Thunk is properly tagged but is just a declaration (no body to inline).
+cir.func private @__clang_cir_catch_copy__ZTS1E_decl(
+    !cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk}
+
+cir.func @copy_fn_no_body() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb6
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb5
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  // expected-error @below {{catch-copy thunk has no body to inline}}
+  cir.construct_catch_param non_trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E_decl : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb6
+^bb5(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb6:
+  cir.return
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_E = !cir.record<struct "E" padded {!u8i}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.global "private" external @_ZTVN10__cxxabiv117__class_type_infoE
+    : !cir.ptr<!cir.ptr<!u8i>>
+cir.global constant linkonce_odr @_ZTI1E
+    = #cir.typeinfo<{
+        #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
+            : !cir.ptr<!u8i>}>
+    : !cir.record<struct  {!cir.ptr<!u8i>}>
+
+cir.func private @_Z8mayThrowv()
+cir.func private @_ZN1EC1ERKS_(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
+
+// Multi-block thunks (e.g. those produced for ExprWithCleanups initializers)
+// are not yet supported by the Itanium lowering and must be diagnosed.
+cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E_multi(
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
+  cir.br ^bb1
+^bb1:
+  cir.call @_ZN1EC1ERKS_(%arg0, %arg1)
+      : (!cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> ()
+  cir.return
+}
+
+cir.func @multi_block_thunk() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb6
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb5
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  // expected-error @below {{multi-block catch-copy thunks are NYI}}
+  cir.construct_catch_param non_trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E_multi : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb6
+^bb5(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb6:
+  cir.return
+}
+
+}

diff  --git a/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir 
b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
new file mode 100644
index 0000000000000..692a4a3ca4583
--- /dev/null
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-construct-catch.cir
@@ -0,0 +1,218 @@
+// RUN: cir-opt %s -cir-eh-abi-lowering -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+
+// Test the Itanium ABI lowering of cir.construct_catch_param.
+//
+// The input is the form produced by cir-flatten-cfg: catch-copy thunks are
+// already plain `cir.func`s carrying the `cir.eh.catch_copy_thunk`
+// attribute, and each `cir.try` catch handler that needs a non-trivial copy
+// begins with `cir.construct_catch_param` referring to its thunk.
+//
+// The lowering must:
+//   - At each use, insert `__cxa_get_exception_ptr` + bitcast and clone the
+//     thunk's body in place; any potentially-throwing `cir.call` becomes a
+//     `cir.try_call` whose unwind path runs `__clang_call_terminate`
+//     (Itanium's terminate scope around the catch-parameter copy). The
+//     terminate block is appended at the end of the function as a cold
+//     landing pad.
+//   - Drop any catch-copy thunks that end up uncalled after lowering.
+
+!s8i = !cir.int<s, 8>
+!s32i = !cir.int<s, 32>
+!u32i = !cir.int<u, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_E = !cir.record<struct "E" padded {!u8i}>
+!rec_anon_struct = !cir.record<struct  {!cir.ptr<!u8i>, !cir.ptr<!u8i>}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+cir.global "private" external @_ZTVN10__cxxabiv117__class_type_infoE
+    : !cir.ptr<!cir.ptr<!u8i>>
+cir.global linkonce_odr @_ZTS1E
+    = #cir.const_array<"1E" : !cir.array<!s8i x 2>, trailing_zeros>
+    : !cir.array<!s8i x 3>
+cir.global constant linkonce_odr @_ZTI1E
+    = #cir.typeinfo<{
+        #cir.global_view<@_ZTVN10__cxxabiv117__class_type_infoE, [2 : i32]>
+            : !cir.ptr<!u8i>,
+        #cir.global_view<@_ZTS1E> : !cir.ptr<!u8i>}>
+    : !rec_anon_struct
+
+cir.func private @_Z8mayThrowv()
+cir.func private @_ZN1EC1ERKS_(!cir.ptr<!rec_E>, !cir.ptr<!rec_E>)
+cir.func private @_ZN1ED1Ev(!cir.ptr<!rec_E>) attributes {nothrow}
+
+//===----------------------------------------------------------------------===//
+// Case 1: basic non_trivial_copy with a single-block catch-copy thunk.
+//===----------------------------------------------------------------------===//
+
+cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E(
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
+  cir.call @_ZN1EC1ERKS_(%arg0, %arg1)
+      : (!cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> ()
+  cir.return
+}
+
+cir.func @test_basic_construct_catch() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb7
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb6
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  cir.construct_catch_param non_trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.call @_ZN1ED1Ev(%0) nothrow : (!cir.ptr<!rec_E>) -> ()
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb7
+^bb6(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb7:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_basic_construct_catch()
+// CHECK-SAME:    personality(@__gxx_personality_v0)
+//
+// Normal flow: the original try_call to mayThrow with its landing pad.
+// CHECK:         cir.try_call @_Z8mayThrowv() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Landing pad: typed catch list materialized from the dispatch.
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         cir.eh.inflight_exception [@_ZTI1E]
+//
+// Catch-handler entry: __cxa_get_exception_ptr + bitcast, then the inlined
+// thunk body. The plain (potentially-throwing) ctor call must become a
+// cir.try_call whose unwind destination runs __clang_call_terminate.
+// CHECK:         %[[ADJUSTED_RAW:.*]] = cir.call 
@__cxa_get_exception_ptr(%{{.*}}) nothrow
+// CHECK:         %[[ADJUSTED:.*]] = cir.cast bitcast %[[ADJUSTED_RAW]] : 
!cir.ptr<!u8i> -> !cir.ptr<!rec_E>
+// CHECK:         cir.try_call @_ZN1EC1ERKS_(%{{.*}}, %[[ADJUSTED]]) 
^{{bb[0-9]+}}, ^[[TERMINATE:bb[0-9]+]]
+//
+// Normal continuation of the catch handler runs begin_catch, the user's
+// destructor call, and end_catch.
+// CHECK:         cir.call @__cxa_begin_catch
+// CHECK:         cir.call @_ZN1ED1Ev
+// CHECK:         cir.call @__cxa_end_catch()
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+//
+// Terminate scope appended at the end of the function: control reaches it
+// only if the catch-parameter copy throws.
+// CHECK:       ^[[TERMINATE]]:
+// CHECK:         %[[T_EXN:.*]], %{{.*}} = cir.eh.inflight_exception catch_all
+// CHECK:         cir.call @__clang_call_terminate(%[[T_EXN]]){{.*}}noreturn
+// CHECK:         cir.unreachable
+
+//===----------------------------------------------------------------------===//
+// Case 2: two callers share a single thunk.
+//===----------------------------------------------------------------------===//
+
+cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E_shared(
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
+  cir.call @_ZN1EC1ERKS_(%arg0, %arg1)
+      : (!cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> ()
+  cir.return
+}
+
+cir.func @test_shared_thunk_caller_a() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb7
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb6
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  cir.construct_catch_param non_trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E_shared : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.call @_ZN1ED1Ev(%0) nothrow : (!cir.ptr<!rec_E>) -> ()
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb7
+^bb6(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb7:
+  cir.return
+}
+
+cir.func @test_shared_thunk_caller_b() {
+  %0 = cir.alloca !rec_E, !cir.ptr<!rec_E>, ["e"] {alignment = 1 : i64}
+  cir.try_call @_Z8mayThrowv() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb7
+^bb2:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token: !cir.eh_token):
+  cir.eh.dispatch %eh_token : !cir.eh_token [
+    catch(#cir.global_view<@_ZTI1E> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb6
+  ]
+^bb4(%eh_token.1: !cir.eh_token):
+  cir.construct_catch_param non_trivial_copy %eh_token.1 to %0
+      using @__clang_cir_catch_copy__ZTS1E_shared : !cir.ptr<!rec_E>
+  %ct, %exn = cir.begin_catch %eh_token.1
+      : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+  cir.call @_ZN1ED1Ev(%0) nothrow : (!cir.ptr<!rec_E>) -> ()
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb7
+^bb6(%eh_token.2: !cir.eh_token):
+  cir.resume %eh_token.2 : !cir.eh_token
+^bb7:
+  cir.return
+}
+
+// Both callers should have the body inlined: each gets its own
+// __cxa_get_exception_ptr / try_call to the actual ctor.
+// CHECK-LABEL: cir.func @test_shared_thunk_caller_a()
+// CHECK:         cir.call @__cxa_get_exception_ptr
+// CHECK:         cir.try_call @_ZN1EC1ERKS_
+
+// CHECK-LABEL: cir.func @test_shared_thunk_caller_b()
+// CHECK:         cir.call @__cxa_get_exception_ptr
+// CHECK:         cir.try_call @_ZN1EC1ERKS_
+
+//===----------------------------------------------------------------------===//
+// Case 3: a thunk-tagged function with no caller is erased outright.
+//===----------------------------------------------------------------------===//
+
+cir.func linkonce_odr hidden @__clang_cir_catch_copy__ZTS1E_unused(
+    %arg0: !cir.ptr<!rec_E>, %arg1: !cir.ptr<!rec_E>)
+    attributes {cir.eh.catch_copy_thunk} {
+  cir.call @_ZN1EC1ERKS_(%arg0, %arg1)
+      : (!cir.ptr<!rec_E>, !cir.ptr<!rec_E>) -> ()
+  cir.return
+}
+
+// All three catch-copy thunks above (basic, shared, unused) are unreferenced
+// after inlining and must be erased by the lowering pass.
+// CHECK-NOT: cir.eh.catch_copy_thunk
+// CHECK-NOT: @__clang_cir_catch_copy__ZTS1E
+
+// Module-level runtime helper declarations introduced by the lowering.
+// CHECK: cir.func private @__cxa_get_exception_ptr(!cir.ptr<!void>) -> 
!cir.ptr<!u8i>
+// CHECK: cir.func {{.*}} @__clang_call_terminate(%{{.*}}: !cir.ptr<!void>) 
attributes {noreturn}
+
+}


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to