Author: Andy Kaylor
Date: 2026-03-03T20:09:49Z
New Revision: 375d65ee8de7dd65e19faab567db2799a52dfe52

URL: 
https://github.com/llvm/llvm-project/commit/375d65ee8de7dd65e19faab567db2799a52dfe52
DIFF: 
https://github.com/llvm/llvm-project/commit/375d65ee8de7dd65e19faab567db2799a52dfe52.diff

LOG: [CIR] Implement EH lowering to Itanium form and LLVM IR (#184386)

This introduces a new pass to lower from a flattened, target-independent
form of CIR to a form that uses Itanium-specific representation for
exception handling. It also includes a small amount of code needed to
lower the Itanium form to LLVM IR.

Substantial amounts of this PR were created using agentic AI tools, but
I have carefully reviewed the code, comments, and tests and made changes
as needed.

Added: 
    clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
    clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir

Modified: 
    clang/include/clang/CIR/Dialect/IR/CIROps.td
    clang/include/clang/CIR/Dialect/Passes.h
    clang/include/clang/CIR/Dialect/Passes.td
    clang/include/clang/CIR/MissingFeatures.h
    clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
    clang/lib/CIR/Lowering/CIRPasses.cpp
    clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
    clang/test/CIR/CodeGen/try-catch-tmp.cpp
    clang/test/CIR/CodeGen/try-catch.cpp
    clang/tools/cir-opt/cir-opt.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a5a5197cd3ea6..064be6f54def2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3486,7 +3486,6 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
   );
 
   let skipDefaultBuilders = 1;
-  let hasLLVMLowering = false;
 
   let builders = [
     OpBuilder<(ins "mlir::SymbolRefAttr":$callee,

diff  --git a/clang/include/clang/CIR/Dialect/Passes.h 
b/clang/include/clang/CIR/Dialect/Passes.h
index 7b57b00f7da8c..d441dfcbc6c14 100644
--- a/clang/include/clang/CIR/Dialect/Passes.h
+++ b/clang/include/clang/CIR/Dialect/Passes.h
@@ -24,6 +24,7 @@ namespace mlir {
 std::unique_ptr<Pass> createCIRCanonicalizePass();
 std::unique_ptr<Pass> createCIRFlattenCFGPass();
 std::unique_ptr<Pass> createCIRSimplifyPass();
+std::unique_ptr<Pass> createCIREHABILoweringPass();
 std::unique_ptr<Pass> createCXXABILoweringPass();
 std::unique_ptr<Pass> createTargetLoweringPass();
 std::unique_ptr<Pass> createHoistAllocasPass();

diff  --git a/clang/include/clang/CIR/Dialect/Passes.td 
b/clang/include/clang/CIR/Dialect/Passes.td
index 0d60d5e33dd4b..32cd182aacec7 100644
--- a/clang/include/clang/CIR/Dialect/Passes.td
+++ b/clang/include/clang/CIR/Dialect/Passes.td
@@ -150,6 +150,29 @@ def TargetLowering : Pass<"cir-target-lowering", 
"mlir::ModuleOp"> {
   let dependentDialects = ["cir::CIRDialect"];
 }
 
+def CIREHABILowering : Pass<"cir-eh-abi-lowering", "mlir::ModuleOp"> {
+  let summary = "Lower flattened CIR EH operations to target-specific ABI 
form";
+  let description = [{
+    This pass lowers the ABI-agnostic exception handling operations produced by
+    CFG flattening into an ABI-specific form. Currently only the Itanium C++
+    ABI is implemented.
+
+    For the Itanium ABI, the pass performs the following transformations:
+      - Replaces `cir.eh.initiate` with `cir.eh.inflight_exception`
+      - Replaces `cir.eh.dispatch` with `cir.eh.typeid` + comparison chains
+      - Removes `cir.begin_cleanup` and `cir.end_cleanup` operations
+      - Replaces `cir.begin_catch` with a call to `__cxa_begin_catch`
+      - Replaces `cir.end_catch` with a call to `__cxa_end_catch`
+      - Replaces `cir.resume` with `cir.resume.flat`
+      - Sets the personality function attribute on functions that require EH
+
+    If a non-Itanium ABI is specified, the pass emits a diagnostic indicating
+    that the target is not yet implemented.
+  }];
+  let constructor = "mlir::createCIREHABILoweringPass()";
+  let dependentDialects = ["cir::CIRDialect"];
+}
+
 def LoweringPrepare : Pass<"cir-lowering-prepare"> {
   let summary = "Lower to more fine-grained CIR operations before lowering to "
     "other dialects";

diff  --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 02d22bb628900..ab05d2191d9b0 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -114,8 +114,6 @@ struct MissingFeatures {
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
   static bool opCallCIRGenFuncInfoExtParamInfo() { return false; }
-  static bool opCallLandingPad() { return false; }
-  static bool opCallContinueBlock() { return false; }
   static bool opCallChain() { return false; }
   static bool opCallExceptionAttr() { return false; }
 

diff  --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt 
b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
index 3fe6c5f9ecb55..411bbc386b9d1 100644
--- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
+++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt
@@ -4,6 +4,7 @@ add_clang_library(MLIRCIRTransforms
   CIRCanonicalize.cpp
   CIRSimplify.cpp
   CXXABILowering.cpp
+  EHABILowering.cpp
   TargetLowering.cpp
   FlattenCFG.cpp
   HoistAllocas.cpp

diff  --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
new file mode 100644
index 0000000000000..d5d10b1b251be
--- /dev/null
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -0,0 +1,502 @@
+//===- EHABILowering.cpp - Lower flattened CIR EH ops to ABI-specific form 
===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements a pass that lowers ABI-agnostic flattened CIR exception
+// handling operations into an ABI-specific form. Currently only the Itanium
+// 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.resume           → cir.resume.flat
+//   - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
+//   - personality function set on functions requiring EH
+//
+//===----------------------------------------------------------------------===//
+
+#include "PassDetail.h"
+#include "mlir/IR/Builders.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "clang/CIR/Dialect/Passes.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/TargetParser/Triple.h"
+
+using namespace mlir;
+using namespace cir;
+
+namespace mlir {
+#define GEN_PASS_DEF_CIREHABILOWERING
+#include "clang/CIR/Dialect/Passes.h.inc"
+} // namespace mlir
+
+namespace {
+
+//===----------------------------------------------------------------------===//
+// Shared utilities
+//===----------------------------------------------------------------------===//
+
+/// Ensure a function with the given name and type exists in the module. If it
+/// does not exist, create a private external declaration.
+static cir::FuncOp getOrCreateRuntimeFuncDecl(mlir::ModuleOp mod,
+                                              mlir::Location loc,
+                                              StringRef name,
+                                              cir::FuncType funcTy) {
+  if (auto existing = mod.lookupSymbol<cir::FuncOp>(name))
+    return existing;
+
+  mlir::OpBuilder builder(mod.getContext());
+  builder.setInsertionPointToEnd(mod.getBody());
+  auto funcOp = cir::FuncOp::create(builder, loc, name, funcTy);
+  funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
+  funcOp.setPrivate();
+  return funcOp;
+}
+
+//===----------------------------------------------------------------------===//
+// EH ABI Lowering Base Class
+//===----------------------------------------------------------------------===//
+
+/// Abstract base class for exception-handling ABI lowering.
+/// Each supported ABI (Itanium, Microsoft, etc.) provides a concrete subclass.
+class EHABILowering {
+public:
+  explicit EHABILowering(mlir::ModuleOp mod)
+      : mod(mod), ctx(mod.getContext()), builder(ctx) {}
+  virtual ~EHABILowering() = default;
+
+  /// Lower all EH operations in the module to an ABI-specific form.
+  virtual mlir::LogicalResult run() = 0;
+
+protected:
+  mlir::ModuleOp mod;
+  mlir::MLIRContext *ctx;
+  mlir::OpBuilder builder;
+};
+
+//===----------------------------------------------------------------------===//
+// Itanium EH ABI Lowering
+//===----------------------------------------------------------------------===//
+
+/// Lowers flattened CIR EH operations to the Itanium C++ ABI form.
+///
+/// The entry point is run(), which iterates over all functions and
+/// calls lowerFunc() for each. lowerFunc() drives all lowering from
+/// cir.eh.initiate operations: every other EH op (begin/end_cleanup,
+/// eh.dispatch, begin/end_catch, resume) is reachable by tracing the
+/// eh_token produced by the initiate through its users.
+class ItaniumEHLowering : public EHABILowering {
+public:
+  using EHABILowering::EHABILowering;
+  mlir::LogicalResult run() override;
+
+private:
+  /// Maps a !cir.eh_token value to its Itanium ABI replacement pair:
+  /// an exception pointer (!cir.ptr<!void>) and a type id (!u32i).
+  using EhTokenMap = DenseMap<mlir::Value, std::pair<mlir::Value, 
mlir::Value>>;
+
+  cir::VoidType voidType;
+  cir::PointerType voidPtrType;
+  cir::PointerType u8PtrType;
+  cir::IntType u32Type;
+
+  // Cached runtime function declarations, initialized when needed by
+  // ensureRuntimeDecls().
+  cir::FuncOp personalityFunc;
+  cir::FuncOp beginCatchFunc;
+  cir::FuncOp endCatchFunc;
+
+  constexpr const static ::llvm::StringLiteral kGxxPersonality =
+      "__gxx_personality_v0";
+
+  void ensureRuntimeDecls(mlir::Location loc);
+  mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
+  void 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);
+};
+
+/// Lower all EH operations in the module to the Itanium-specific form.
+mlir::LogicalResult ItaniumEHLowering::run() {
+  // Pre-compute the common types used throughout all function lowerings.
+  // TODO(cir): Move these to the base class if they are also needed for MSVC.
+  voidType = cir::VoidType::get(ctx);
+  voidPtrType = cir::PointerType::get(voidType);
+  auto u8Type = cir::IntType::get(ctx, 8, /*isSigned=*/false);
+  u8PtrType = cir::PointerType::get(u8Type);
+  u32Type = cir::IntType::get(ctx, 32, /*isSigned=*/false);
+
+  for (cir::FuncOp funcOp : mod.getOps<cir::FuncOp>()) {
+    if (mlir::failed(lowerFunc(funcOp)))
+      return mlir::failure();
+  }
+  return mlir::success();
+}
+
+/// Ensure the necessary Itanium runtime function declarations exist in the
+/// module.
+void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
+  // TODO(cir): Handle other personality functions. This probably isn't needed
+  // here if we fix codegen to always set the personality function.
+  if (!personalityFunc) {
+    auto s32Type = cir::IntType::get(ctx, 32, /*isSigned=*/true);
+    auto personalityFuncTy = cir::FuncType::get({}, s32Type, 
/*isVarArg=*/true);
+    personalityFunc = getOrCreateRuntimeFuncDecl(mod, loc, kGxxPersonality,
+                                                 personalityFuncTy);
+  }
+
+  if (!beginCatchFunc) {
+    auto beginCatchFuncTy =
+        cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
+    beginCatchFunc = getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_begin_catch",
+                                                beginCatchFuncTy);
+  }
+
+  if (!endCatchFunc) {
+    auto endCatchFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
+    endCatchFunc =
+        getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", 
endCatchFuncTy);
+  }
+}
+
+/// Lower all EH operations in a single function.
+mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
+  if (funcOp.isDeclaration())
+    return mlir::success();
+
+  // All EH lowering follows from cir.eh.initiate operations. The token each
+  // initiate produces connects it to every other EH op in the function
+  // (begin/end_cleanup, eh.dispatch, begin/end_catch, resume) through the
+  // token graph. A single walk to collect initiates is therefore sufficient.
+  SmallVector<cir::EhInitiateOp> initiateOps;
+  funcOp.walk([&](cir::EhInitiateOp op) { initiateOps.push_back(op); });
+  if (initiateOps.empty())
+    return mlir::success();
+
+  ensureRuntimeDecls(funcOp.getLoc());
+
+  // Set the personality function if it is not already set.
+  // TODO(cir): The personality function should already have been set by this
+  // point. If we've seen a try operation, it will have been set by
+  // emitCXXTryStmt. If we only have cleanups, it may not have been set. We
+  // need to fix that in CodeGen. This is a placeholder until that is done.
+  if (!funcOp.getPersonality())
+    funcOp.setPersonality(kGxxPersonality);
+
+  // Lower each initiate and all EH ops connected to it. The token map is
+  // shared across all initiate operations. Multiple initiates may flow into 
the
+  // same dispatch block, and the map ensures the arguments are registered
+  // only once. Dispatch ops are scheduled for deferred removal so that sibling
+  // initiates can still read catch types from a shared dispatch.
+  EhTokenMap ehTokenMap;
+  SmallVector<mlir::Operation *> deadOps;
+  for (cir::EhInitiateOp initiateOp : initiateOps)
+    lowerEhInitiate(initiateOp, ehTokenMap, deadOps);
+
+  // Erase operations that were deferred during per-initiate processing
+  // (dispatch ops whose catch types were read by multiple initiates).
+  for (mlir::Operation *op : deadOps)
+    op->erase();
+
+  // Remove the !cir.eh_token block arguments that were replaced by (ptr, u32)
+  // pairs. Iterate in reverse to preserve argument indices during removal.
+  for (mlir::Block &block : funcOp.getBody()) {
+    for (int i = block.getNumArguments() - 1; i >= 0; --i) {
+      if (mlir::isa<cir::EhTokenType>(block.getArgument(i).getType()))
+        block.eraseArgument(i);
+    }
+  }
+
+  return mlir::success();
+}
+
+/// Lower all EH operations connected to a single cir.eh.initiate.
+///
+/// The cir.eh.initiate is the root of a token graph. The token it produces
+/// flows through branch edges to consuming operations:
+///
+///   cir.eh.initiate → (via cir.br) → cir.begin_cleanup
+///                                     → cir.end_cleanup (via cleanup_token)
+///                                   → (via cir.br) → cir.eh.dispatch
+///                                                     → (successors) →
+///                                                       cir.begin_catch
+///                                                       → cir.end_catch
+///                                                         (via catch_token)
+///                                   → cir.resume
+///
+/// A single traversal of the token graph discovers and processes every
+/// connected op inline. The inflight_exception is created up-front without
+/// a catch_type_list; when the dispatch is encountered during traversal,
+/// the catch types are read and set on the inflight op.
+///
+/// Dispatch ops are not erased during per-initiate processing because they may
+/// be used by other initiate ops that haven't yet been lowered. Instead they
+/// are added to \p deadOps and erased by the caller after all initiates have
+/// been lowered.
+///
+/// \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(
+    cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
+    SmallVectorImpl<mlir::Operation *> &deadOps) {
+  mlir::Value rootToken = initiateOp.getEhToken();
+
+  // Create the inflight_exception without a catch_type_list. The catch types
+  // will be set once we encounter the dispatch during the traversal below.
+  builder.setInsertionPoint(initiateOp);
+  auto inflightOp = cir::EhInflightOp::create(
+      builder, initiateOp.getLoc(), /*cleanup=*/initiateOp.getCleanup(),
+      /*catch_type_list=*/mlir::ArrayAttr{});
+
+  ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
+                           inflightOp.getTypeId()};
+
+  // Single traversal of the token graph. For each token value (the root token
+  // or a block argument that carries it), we snapshot its users, register
+  // (ptr, u32) replacement arguments on successor blocks, then process every
+  // user inline. This avoids collecting ops into separate vectors.
+  SmallVector<mlir::Value> worklist;
+  SmallPtrSet<mlir::Value, 8> visited;
+  worklist.push_back(rootToken);
+
+  while (!worklist.empty()) {
+    mlir::Value current = worklist.pop_back_val();
+    if (!visited.insert(current).second)
+      continue;
+
+    // Snapshot users before modifying any of them (erasing ops during
+    // iteration would invalidate the use-list iterator).
+    SmallVector<mlir::Operation *> users;
+    for (mlir::OpOperand &use : current.getUses())
+      users.push_back(use.getOwner());
+
+    // Register replacement block arguments on successor blocks (extending the
+    // worklist), then lower the op itself.
+    for (mlir::Operation *user : users) {
+      // Trace into successor blocks to register (ptr, u32) replacement
+      // arguments for any !cir.eh_token block arguments found there.  Even
+      // if a block arg was already registered by a sibling initiate, it is
+      // still added to the worklist so that the traversal can reach the
+      // shared dispatch to read catch types.
+      for (unsigned s = 0; s < user->getNumSuccessors(); ++s) {
+        mlir::Block *succ = user->getSuccessor(s);
+        for (mlir::BlockArgument arg : succ->getArguments()) {
+          if (!mlir::isa<cir::EhTokenType>(arg.getType()))
+            continue;
+          if (!ehTokenMap.count(arg)) {
+            mlir::Value ptrArg = succ->addArgument(voidPtrType, arg.getLoc());
+            mlir::Value u32Arg = succ->addArgument(u32Type, arg.getLoc());
+            ehTokenMap[arg] = {ptrArg, u32Arg};
+          }
+          worklist.push_back(arg);
+        }
+      }
+
+      if (auto op = mlir::dyn_cast<cir::BeginCleanupOp>(user)) {
+        // begin_cleanup / end_cleanup are no-ops for Itanium.  Erase the
+        // end_cleanup first (drops the cleanup_token use) then the begin.
+        for (auto &tokenUsers :
+             llvm::make_early_inc_range(op.getCleanupToken().getUses())) {
+          if (auto endOp =
+                  mlir::dyn_cast<cir::EndCleanupOp>(tokenUsers.getOwner()))
+            endOp.erase();
+        }
+        op.erase();
+      } else if (auto op = mlir::dyn_cast<cir::BeginCatchOp>(user)) {
+        // Replace end_catch → __cxa_end_catch (drops the catch_token use),
+        // then replace begin_catch → __cxa_begin_catch.
+        for (auto &tokenUsers :
+             llvm::make_early_inc_range(op.getCatchToken().getUses())) {
+          if (auto endOp =
+                  mlir::dyn_cast<cir::EndCatchOp>(tokenUsers.getOwner())) {
+            builder.setInsertionPoint(endOp);
+            cir::CallOp::create(builder, endOp.getLoc(),
+                                mlir::FlatSymbolRefAttr::get(endCatchFunc),
+                                voidType, mlir::ValueRange{});
+            endOp.erase();
+          }
+        }
+
+        auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
+        builder.setInsertionPoint(op);
+        auto callOp = cir::CallOp::create(
+            builder, op.getLoc(), mlir::FlatSymbolRefAttr::get(beginCatchFunc),
+            u8PtrType, mlir::ValueRange{exnPtr});
+        mlir::Value castResult = callOp.getResult();
+        mlir::Type expectedPtrType = op.getExnPtr().getType();
+        if (castResult.getType() != expectedPtrType)
+          castResult =
+              cir::CastOp::create(builder, op.getLoc(), expectedPtrType,
+                                  cir::CastKind::bitcast, callOp.getResult());
+        op.getExnPtr().replaceAllUsesWith(castResult);
+        op.erase();
+      } 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();
+        if (catchTypes && catchTypes.size() > 0) {
+          SmallVector<mlir::Attribute> typeSymbols;
+          for (mlir::Attribute attr : catchTypes)
+            typeSymbols.push_back(
+                mlir::cast<cir::GlobalViewAttr>(attr).getSymbol());
+          inflightOp.setCatchTypeListAttr(builder.getArrayAttr(typeSymbols));
+        }
+        // Only lower the dispatch once. A sibling initiate sharing the same
+        // dispatch will still read its catch types (above), but the comparison
+        // chain and branch replacement are only created the first time.
+        if (!llvm::is_contained(deadOps, op.getOperation())) {
+          auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
+          lowerDispatch(op, exnPtr, typeId, deadOps);
+        }
+      } else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
+        auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
+        builder.setInsertionPoint(op);
+        cir::ResumeFlatOp::create(builder, op.getLoc(), exnPtr, typeId);
+        op.erase();
+      } else if (auto op = mlir::dyn_cast<cir::BrOp>(user)) {
+        // Replace eh_token operands with the (ptr, u32) pair.
+        SmallVector<mlir::Value> newOperands;
+        bool changed = false;
+        for (mlir::Value operand : op.getDestOperands()) {
+          auto it = ehTokenMap.find(operand);
+          if (it != ehTokenMap.end()) {
+            newOperands.push_back(it->second.first);
+            newOperands.push_back(it->second.second);
+            changed = true;
+          } else {
+            newOperands.push_back(operand);
+          }
+        }
+        if (changed) {
+          builder.setInsertionPoint(op);
+          cir::BrOp::create(builder, op.getLoc(), op.getDest(), newOperands);
+          op.erase();
+        }
+      }
+    }
+  }
+
+  initiateOp.erase();
+}
+
+/// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
+/// The dispatch itself is replaced with a branch to the first comparison
+/// block and added to deadOps for deferred removal.
+void ItaniumEHLowering::lowerDispatch(
+    cir::EhDispatchOp dispatch, mlir::Value exnPtr, mlir::Value typeId,
+    SmallVectorImpl<mlir::Operation *> &deadOps) {
+  mlir::Location dispLoc = dispatch.getLoc();
+  mlir::Block *defaultDest = dispatch.getDefaultDestination();
+  mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr();
+  mlir::SuccessorRange catchDests = dispatch.getCatchDestinations();
+  mlir::Block *dispatchBlock = dispatch->getBlock();
+
+  // Build the comparison chain in new blocks inserted after the dispatch's
+  // block. The dispatch itself is replaced with a branch to the first
+  // comparison block and scheduled for deferred removal.
+  if (!catchTypes || catchTypes.empty()) {
+    // No typed catches: replace dispatch with a direct branch.
+    builder.setInsertionPoint(dispatch);
+    cir::BrOp::create(builder, dispLoc, defaultDest,
+                      mlir::ValueRange{exnPtr, typeId});
+  } else {
+    unsigned numCatches = catchTypes.size();
+
+    // Create and populate comparison blocks in reverse order so that each
+    // block's false destination (the next comparison block, or defaultDest
+    // for the last one) is already available. Each createBlock inserts
+    // before the previous one, so the blocks end up in forward order.
+    mlir::Block *insertBefore = dispatchBlock->getNextNode();
+    mlir::Block *falseDest = defaultDest;
+    mlir::Block *firstCmpBlock = nullptr;
+    for (int i = numCatches - 1; i >= 0; --i) {
+      auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, 
u32Type},
+                                           {dispLoc, dispLoc});
+
+      mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
+      mlir::Value cmpTypeId = cmpBlock->getArgument(1);
+
+      auto globalView = mlir::cast<cir::GlobalViewAttr>(catchTypes[i]);
+      auto ehTypeIdOp =
+          cir::EhTypeIdOp::create(builder, dispLoc, globalView.getSymbol());
+      auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::eq,
+                                      cmpTypeId, ehTypeIdOp.getTypeId());
+
+      cir::BrCondOp::create(builder, dispLoc, cmpOp, catchDests[i], falseDest,
+                            mlir::ValueRange{cmpExnPtr, cmpTypeId},
+                            mlir::ValueRange{cmpExnPtr, cmpTypeId});
+
+      insertBefore = cmpBlock;
+      falseDest = cmpBlock;
+      firstCmpBlock = cmpBlock;
+    }
+
+    // Replace the dispatch with a branch to the first comparison block.
+    builder.setInsertionPoint(dispatch);
+    cir::BrOp::create(builder, dispLoc, firstCmpBlock,
+                      mlir::ValueRange{exnPtr, typeId});
+  }
+
+  // Schedule the dispatch for deferred removal. We cannot erase it now because
+  // a sibling initiate that shares this dispatch may still need to read its
+  // catch types.
+  deadOps.push_back(dispatch);
+}
+
+//===----------------------------------------------------------------------===//
+// The Pass
+//===----------------------------------------------------------------------===//
+
+struct CIREHABILoweringPass
+    : public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
+  CIREHABILoweringPass() = default;
+  void runOnOperation() override;
+};
+
+void CIREHABILoweringPass::runOnOperation() {
+  auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
+
+  // The target triple is attached to the module as the "cir.triple" attribute.
+  // If it is absent (e.g. a CIR module parsed from text without a triple) we
+  // cannot determine the ABI and must skip the pass.
+  auto tripleAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(
+      mod->getAttr(cir::CIRDialect::getTripleAttrName()));
+  if (!tripleAttr) {
+    mod.emitError("Module has no target triple");
+    return;
+  }
+
+  // Select the ABI-specific lowering handler from the triple. The Microsoft
+  // C++ ABI targets a Windows MSVC environment; everything else uses Itanium.
+  // Extend this when Microsoft ABI lowering is added.
+  llvm::Triple triple(tripleAttr.getValue());
+  std::unique_ptr<EHABILowering> lowering;
+  if (triple.isWindowsMSVCEnvironment()) {
+    mod.emitError(
+        "EH ABI lowering is not yet implemented for the Microsoft ABI");
+    return signalPassFailure();
+  } else {
+    lowering = std::make_unique<ItaniumEHLowering>(mod);
+  }
+
+  if (mlir::failed(lowering->run()))
+    return signalPassFailure();
+}
+
+} // namespace
+
+std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
+  return std::make_unique<CIREHABILoweringPass>();
+}

diff  --git a/clang/lib/CIR/Lowering/CIRPasses.cpp 
b/clang/lib/CIR/Lowering/CIRPasses.cpp
index c0dcc4ba5e90a..7b93356a34c38 100644
--- a/clang/lib/CIR/Lowering/CIRPasses.cpp
+++ b/clang/lib/CIR/Lowering/CIRPasses.cpp
@@ -49,6 +49,7 @@ namespace mlir {
 void populateCIRPreLoweringPasses(OpPassManager &pm) {
   pm.addPass(createHoistAllocasPass());
   pm.addPass(createCIRFlattenCFGPass());
+  pm.addPass(createCIREHABILoweringPass());
   pm.addPass(createGotoSolverPass());
 }
 

diff  --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 03085ad29ab78..4f71c588b5018 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1837,7 +1837,9 @@ static mlir::LogicalResult
 rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
                     mlir::ConversionPatternRewriter &rewriter,
                     const mlir::TypeConverter *converter,
-                    mlir::FlatSymbolRefAttr calleeAttr) {
+                    mlir::FlatSymbolRefAttr calleeAttr,
+                    mlir::Block *continueBlock = nullptr,
+                    mlir::Block *landingPadBlock = nullptr) {
   llvm::SmallVector<mlir::Type, 8> llvmResults;
   mlir::ValueTypeRange<mlir::ResultRange> cirResults = op->getResultTypes();
   auto call = cast<cir::CIRCallOpInterface>(op);
@@ -1908,18 +1910,23 @@ rewriteCallOrInvoke(mlir::Operation *op, 
mlir::ValueRange callOperands,
         converter->convertType(calleeFuncTy));
   }
 
-  assert(!cir::MissingFeatures::opCallLandingPad());
-  assert(!cir::MissingFeatures::opCallContinueBlock());
   assert(!cir::MissingFeatures::opCallCallConv());
 
-  auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
-      op, llvmFnTy, calleeAttr, callOperands);
-  newOp->setAttrs(attributes);
-  if (memoryEffects)
-    newOp.setMemoryEffectsAttr(memoryEffects);
-  newOp.setNoUnwind(noUnwind);
-  newOp.setWillReturn(willReturn);
-  newOp.setNoreturn(noReturn);
+  if (landingPadBlock) {
+    auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::InvokeOp>(
+        op, llvmFnTy, calleeAttr, callOperands, continueBlock,
+        mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{});
+    newOp->setAttrs(attributes);
+  } else {
+    auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
+        op, llvmFnTy, calleeAttr, callOperands);
+    newOp->setAttrs(attributes);
+    if (memoryEffects)
+      newOp.setMemoryEffectsAttr(memoryEffects);
+    newOp.setNoUnwind(noUnwind);
+    newOp.setWillReturn(willReturn);
+    newOp.setNoreturn(noReturn);
+  }
 
   return mlir::success();
 }
@@ -1931,6 +1938,15 @@ mlir::LogicalResult 
CIRToLLVMCallOpLowering::matchAndRewrite(
                              getTypeConverter(), op.getCalleeAttr());
 }
 
+mlir::LogicalResult CIRToLLVMTryCallOpLowering::matchAndRewrite(
+    cir::TryCallOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  assert(!cir::MissingFeatures::opCallCallConv());
+  return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), 
rewriter,
+                             getTypeConverter(), op.getCalleeAttr(),
+                             op.getNormalDest(), op.getUnwindDest());
+}
+
 mlir::LogicalResult CIRToLLVMReturnAddrOpLowering::matchAndRewrite(
     cir::ReturnAddrOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {

diff  --git a/clang/test/CIR/CodeGen/try-catch-tmp.cpp 
b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
index 5cb60aaf6b2cc..f7951e229c06f 100644
--- a/clang/test/CIR/CodeGen/try-catch-tmp.cpp
+++ b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
@@ -1,8 +1,12 @@
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
 // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
 
+// TODO(cir): Merge this with try-catch.cpp.
+
 int division();
 
 void call_function_inside_try_catch_all() {
@@ -29,6 +33,43 @@ void call_function_inside_try_catch_all() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void @_Z34call_function_inside_try_catch_allv() {{.*}} 
personality ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           catch ptr null
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   br label %[[BEGIN_CATCH:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI1]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
 
 // OGCG: define {{.*}} void @_Z34call_function_inside_try_catch_allv() #0 
personality ptr @__gxx_personality_v0
 // OGCG:   %[[EXN_OBJ_ADDR:.*]] = alloca ptr, align 8
@@ -82,6 +123,58 @@ void call_function_inside_try_catch_with_exception_type() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void 
@_Z50call_function_inside_try_catch_with_exception_typev() {{.*}} personality 
ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           catch ptr @_ZTIi
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   br label %[[DISPATCH:.*]]
+// LLVM: [[DISPATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] 
]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// LLVM:   %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], 
%[[EH_TYPE_ID]]
+// LLVM:   br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label 
%[[RESUME:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   %[[LOAD:.*]] = load i32, ptr %[[TOKEN]], align 4
+// LLVM:   store i32 %[[LOAD]], ptr {{.*}}, align 4
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[RESUME]]:
+// LLVM:   %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr 
%[[EXN_OBJ_PHI3]], 0
+// LLVM:   %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } 
%[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
+// LLVM:   resume { ptr, i32 } %[[EXCEPTION_INFO]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
 // OGCG: define {{.*}} void 
@_Z50call_function_inside_try_catch_with_exception_typev() #0 personality ptr 
@__gxx_personality_v0
 // OGCG:   %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
 // OGCG:   %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@@ -148,6 +241,58 @@ void 
call_function_inside_try_catch_with_complex_exception_type() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void 
@_Z58call_function_inside_try_catch_with_complex_exception_typev() {{.*}} 
personality ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           catch ptr @_ZTICi
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   br label %[[DISPATCH:.*]]
+// LLVM: [[DISPATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] 
]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTICi)
+// LLVM:   %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], 
%[[EH_TYPE_ID]]
+// LLVM:   br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label 
%[[RESUME:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   %[[LOAD:.*]] = load { i32, i32 }, ptr %[[TOKEN]], align 4
+// LLVM:   store { i32, i32 } %[[LOAD]], ptr {{.*}}, align 4
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[RESUME]]:
+// LLVM:   %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr 
%[[EXN_OBJ_PHI3]], 0
+// LLVM:   %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } 
%[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
+// LLVM:   resume { ptr, i32 } %[[EXCEPTION_INFO]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
 // OGCG: define {{.*}} void 
@_Z58call_function_inside_try_catch_with_complex_exception_typev() #0 
personality ptr @__gxx_personality_v0
 // OGCG:   %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
 // OGCG:   %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@@ -219,6 +364,57 @@ void 
call_function_inside_try_catch_with_array_exception_type() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void 
@_Z56call_function_inside_try_catch_with_array_exception_typev() {{.*}} 
personality ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           catch ptr @_ZTIPi
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   br label %[[DISPATCH:.*]]
+// LLVM: [[DISPATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] 
]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIPi)
+// LLVM:   %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], 
%[[EH_TYPE_ID]]
+// LLVM:   br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label 
%[[RESUME:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   store ptr %[[TOKEN]], ptr {{.*}}, align 8
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[RESUME]]:
+// LLVM:   %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr 
%[[EXN_OBJ_PHI3]], 0
+// LLVM:   %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } 
%[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
+// LLVM:   resume { ptr, i32 } %[[EXCEPTION_INFO]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
 // OGCG: define {{.*}} void 
@_Z56call_function_inside_try_catch_with_array_exception_typev() #0 personality 
ptr @__gxx_personality_v0
 // OGCG:   %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
 // OGCG:   %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@@ -292,6 +488,66 @@ void 
call_function_inside_try_catch_with_exception_type_and_catch_all() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void 
@_Z64call_function_inside_try_catch_with_exception_type_and_catch_allv() {{.*}} 
personality ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           catch ptr @_ZTIi
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   br label %[[DISPATCH:.*]]
+// LLVM: [[DISPATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] 
]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// LLVM:   %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], 
%[[EH_TYPE_ID]]
+// LLVM:   br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label 
%[[CATCH_ALL:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   %[[LOAD:.*]] = load i32, ptr %[[TOKEN]], align 4
+// LLVM:   store i32 %[[LOAD]], ptr {{.*}}, align 4
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[CATCH_ALL]]:
+// LLVM:   %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[DISPATCH:.*]] ]
+// LLVM:   %[[TOKEN2:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI3]])
+// LLVM:   br label %[[CATCH_ALL_BODY:.*]]
+// LLVM: [[CATCH_ALL_BODY]]:
+// LLVM:   br label %[[END_CATCH2:.*]]
+// LLVM: [[END_CATCH2]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH2:.*]]
+// LLVM: [[END_DISPATCH2]]:
+// LLVM:   br label %[[END_TRY2:.*]]
+// LLVM: [[END_TRY2]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
 // OGCG: define {{.*}} void 
@_Z64call_function_inside_try_catch_with_exception_type_and_catch_allv() #0 
personality ptr @__gxx_personality_v0
 // OGCG:   %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
 // OGCG:   %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@@ -364,6 +620,56 @@ void cleanup_inside_try_body() {
 // CIR:   }
 // CIR: }
 
+// LLVM: define {{.*}} void @_Z23cleanup_inside_try_bodyv() {{.*}} personality 
ptr @__gxx_personality_v0
+// LLVM:   br label %[[TRY_SCOPE:.*]]
+// LLVM: [[TRY_SCOPE]]:
+// LLVM:   br label %[[TRY_BEGIN:.*]]
+// LLVM: [[TRY_BEGIN]]:
+// LLVM:   br label %[[CLEANUP_SCOPE:.*]]
+// LLVM: [[CLEANUP_SCOPE]]:
+// LLVM:   %[[CALL:.*]] = invoke i32 @_Z8divisionv()
+// LLVM:           to label %[[INVOKE_CONT:.*]] unwind label 
%[[LANDING_PAD:.*]]
+// LLVM: [[INVOKE_CONT]]:
+// LLVM:   br label %[[CLEANUP:.*]]
+// LLVM: [[CLEANUP]]:
+// LLVM:   call void @_ZN1SD1Ev(ptr {{.*}})
+// LLVM:   br label %[[END_CLEANUP:.*]]
+// LLVM: [[END_CLEANUP]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[LANDING_PAD]]:
+// LLVM:   %[[LP:.*]] = landingpad { ptr, i32 }
+// LLVM:           cleanup
+// LLVM:   %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
+// LLVM:   %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
+// LLVM:   br label %[[CLEANUP_LANDING:.*]]
+// LLVM: [[CLEANUP_LANDING]]:
+// LLVM:   %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], 
%[[LANDING_PAD:.*]] ]
+// LLVM:   call void @_ZN1SD1Ev(ptr {{.*}})
+// LLVM:   br label %[[CATCH:.*]]
+// LLVM: [[CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], 
%[[CLEANUP_LANDING:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], 
%[[CLEANUP_LANDING:.*]] ]
+// LLVM:   br label %[[BEGIN_CATCH:.*]]
+// LLVM: [[BEGIN_CATCH]]:
+// LLVM:   %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], 
%[[CATCH:.*]] ]
+// LLVM:   %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
+// LLVM:   br label %[[CATCH_BODY:.*]]
+// LLVM: [[CATCH_BODY]]:
+// LLVM:   br label %[[END_CATCH:.*]]
+// LLVM: [[END_CATCH]]:
+// LLVM:   call void @__cxa_end_catch()
+// LLVM:   br label %[[END_DISPATCH:.*]]
+// LLVM: [[END_DISPATCH]]:
+// LLVM:   br label %[[END_TRY:.*]]
+// LLVM: [[END_TRY]]:
+// LLVM:   br label %[[TRY_CONT:.*]]
+// LLVM: [[TRY_CONT]]:
+// LLVM:   br label %[[DONE:.*]]
+// LLVM: [[DONE]]:
+// LLVM:   ret void
+
 // OGCG: define {{.*}} void @_Z23cleanup_inside_try_bodyv() {{.*}} personality 
ptr @__gxx_personality_v0 {
 // OGCG:   %[[S:.*]] = alloca %struct.S
 // OGCG:   %[[EXN_SLOT:.*]] = alloca ptr

diff  --git a/clang/test/CIR/CodeGen/try-catch.cpp 
b/clang/test/CIR/CodeGen/try-catch.cpp
index 81ac15eeb7016..27e3d8ef41115 100644
--- a/clang/test/CIR/CodeGen/try-catch.cpp
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -1,10 +1,10 @@
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu 
-fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
 // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
 
-// TODO(cir): Reenable lowering to LLVM after new try-catch lowering is 
implemented.
-
 void empty_try_block_with_catch_all() {
   try {} catch (...) {}
 }

diff  --git a/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir 
b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir
new file mode 100644
index 0000000000000..42e2722a43455
--- /dev/null
+++ b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir
@@ -0,0 +1,514 @@
+// RUN: cir-opt %s -cir-eh-abi-lowering -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+
+// Test Itanium ABI lowering of flattened CIR exception handling operations.
+// The input is the form produced by cir-flatten-cfg, and the output should
+// have all EH-specific tokens replaced with concrete values and ABI calls.
+
+!s32i = !cir.int<s, 32>
+!u32i = !cir.int<u, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_SomeClass = !cir.record<struct "SomeClass" {!s32i}>
+!rec_exception = !cir.record<struct "std::exception" {!s32i}>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+
+// Test: EH cleanup only (no catch handlers).
+// cir.eh.initiate cleanup → cir.eh.inflight_exception cleanup
+// cir.begin_cleanup / cir.end_cleanup → removed
+// cir.resume → cir.resume.flat
+cir.func @test_cleanup_only() {
+  %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] 
{alignment = 4 : i64}
+  cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.try_call @doSomething(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_SomeClass>) -> ()
+^bb1:
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.br ^bb5
+^bb2:
+  %1 = cir.eh.initiate cleanup : !cir.eh_token
+  cir.br ^bb3(%1 : !cir.eh_token)
+^bb3(%eh_token : !cir.eh_token):
+  %2 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.end_cleanup %2 : !cir.cleanup_token
+  cir.resume %eh_token : !cir.eh_token
+^bb5:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_cleanup_only()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+// CHECK:         cir.try_call @doSomething(%{{.*}}) ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// Normal path unchanged.
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.call @dtor
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Unwind: eh.initiate replaced with eh.inflight_exception.
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception cleanup
+// CHECK:         cir.br ^[[CLEANUP:bb[0-9]+]](%[[EXN]], %[[TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Cleanup: begin_cleanup/end_cleanup removed, resume becomes resume.flat.
+// CHECK:       ^[[CLEANUP]](%[[C_EXN:.*]]: !cir.ptr<!void>, %[[C_TID:.*]]: 
!u32i):
+// CHECK-NOT:     cir.begin_cleanup
+// CHECK:         cir.call @dtor
+// CHECK-NOT:     cir.end_cleanup
+// CHECK:         cir.resume.flat %[[C_EXN]], %[[C_TID]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: Try-catch with catch-all handler.
+// cir.eh.dispatch [catch_all] → cir.br to catch-all block
+// cir.begin_catch → __cxa_begin_catch + bitcast
+// cir.end_catch → __cxa_end_catch
+cir.func @test_catch_all() {
+  cir.try_call @mayThrow() ^bb1, ^bb2 : () -> ()
+^bb1:
+  cir.br ^bb5
+^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_all : ^bb4
+  ]
+^bb4(%eh_token.1 : !cir.eh_token):
+  %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 ^bb5
+^bb5:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_catch_all()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+// CHECK:         cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Landing pad with catch_all (no cleanup, no typed catches).
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[EXN]], %[[TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch replaced with direct branch to catch-all handler.
+// CHECK:       ^[[DISPATCH]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: 
!u32i):
+// CHECK:         cir.br ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Catch-all: begin_catch → __cxa_begin_catch, end_catch → __cxa_end_catch.
+// CHECK:       ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         %{{.*}} = cir.call @__cxa_begin_catch(%[[CA_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: Try-catch with typed handler and unwind.
+// cir.eh.dispatch with typed catch → typeid + cmp + brcond chain
+// unwind handler → cir.resume.flat
+cir.func @test_typed_catch_with_unwind() {
+  cir.try_call @mayThrow() ^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<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb4,
+    unwind : ^bb5
+  ]
+^bb4(%eh_token.1 : !cir.eh_token):
+  %ct, %exn = cir.begin_catch %eh_token.1 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
+  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
+}
+
+// CHECK-LABEL: cir.func @test_typed_catch_with_unwind()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+// CHECK:         cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Landing pad with typed catch list.
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception 
[@_ZTISt9exception]
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[EXN]], %[[TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch: branch to comparison chain.
+// CHECK:       ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.br ^[[CMP_BLOCK:bb[0-9]+]]
+//
+// Comparison: typeid comparison + brcond.
+// CHECK:       ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: 
!u32i):
+// CHECK:         %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
+// CHECK:         %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
+// CHECK:         cir.brcond %[[CMP]] ^[[CATCH:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[UNWIND_HANDLER:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i)
+//
+// Typed catch handler.
+// CHECK:       ^[[CATCH]](%[[C_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         %{{.*}} = cir.call @__cxa_begin_catch(%[[C_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// Unwind handler: resume.flat.
+// CHECK:       ^[[UNWIND_HANDLER]](%[[UW_EXN:.*]]: !cir.ptr<!void>, 
%[[UW_TID:.*]]: !u32i):
+// CHECK:         cir.resume.flat %[[UW_EXN]], %[[UW_TID]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: Try-catch with multiple typed handlers and catch-all.
+// Dispatch → chain of typeid comparisons, catch-all as final default.
+cir.func @test_multiple_typed_and_catch_all() {
+  cir.try_call @mayThrow() ^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<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb4,
+    catch(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb5,
+    catch_all : ^bb6
+  ]
+^bb4(%eh_token.1 : !cir.eh_token):
+  %ct1, %exn1 = cir.begin_catch %eh_token.1 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
+  cir.end_catch %ct1 : !cir.catch_token
+  cir.br ^bb7
+^bb5(%eh_token.2 : !cir.eh_token):
+  %ct2, %exn2 = cir.begin_catch %eh_token.2 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!s32i>)
+  cir.end_catch %ct2 : !cir.catch_token
+  cir.br ^bb7
+^bb6(%eh_token.3 : !cir.eh_token):
+  %ct3, %exn3 = cir.begin_catch %eh_token.3 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %ct3 : !cir.catch_token
+  cir.br ^bb7
+^bb7:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_multiple_typed_and_catch_all()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+// CHECK:         cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Landing pad with typed catches AND catch-all.
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception 
[@_ZTISt9exception, @_ZTIi]
+// CHECK:         cir.br ^[[CMP1_BLOCK:bb[0-9]+]](%[[EXN]], %[[TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch: branch to comparison chain.
+// CHECK:       ^[[CMP1_BLOCK]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.br ^[[CMP1:bb[0-9]+]]
+//
+// First type comparison (exception type).
+// CHECK:       ^[[CMP1]](%[[C1_EXN:.*]]: !cir.ptr<!void>, %[[C1_TID:.*]]: 
!u32i):
+// CHECK:         %[[TID1:.*]] = cir.eh.typeid @_ZTISt9exception
+// CHECK:         %[[CMP1V:.*]] = cir.cmp(eq, %[[C1_TID]], %[[TID1]])
+// CHECK:         cir.brcond %[[CMP1V]] ^[[CATCH_EXN:bb[0-9]+]](%[[C1_EXN]], 
%[[C1_TID]] : !cir.ptr<!void>, !u32i), ^[[CMP2_BLOCK:bb[0-9]+]](%[[C1_EXN]], 
%[[C1_TID]] : !cir.ptr<!void>, !u32i)
+//
+// Second type comparison (int type).
+// CHECK:       ^[[CMP2_BLOCK]](%[[C2_EXN:.*]]: !cir.ptr<!void>, 
%[[C2_TID:.*]]: !u32i):
+// CHECK:         %[[TID2:.*]] = cir.eh.typeid @_ZTIi
+// CHECK:         %[[CMP2:.*]] = cir.cmp(eq, %[[C2_TID]], %[[TID2]])
+// CHECK:         cir.brcond %[[CMP2]] ^[[CATCH_INT:bb[0-9]+]](%[[C2_EXN]], 
%[[C2_TID]] : !cir.ptr<!void>, !u32i), ^[[CATCH_ALL:bb[0-9]+]](%[[C2_EXN]], 
%[[C2_TID]] : !cir.ptr<!void>, !u32i)
+//
+// Exception catch handler.
+// CHECK:       ^[[CATCH_EXN]](%[[CE_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[CE_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// Int catch handler.
+// CHECK:       ^[[CATCH_INT]](%[[CI_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[CI_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// Catch-all handler.
+// CHECK:       ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[CA_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: Try-catch with cleanup + catch handler.
+// Two landing pads: one without cleanup (ctor), one with cleanup 
(doSomething).
+// cleanup ops are removed, cleanup code stays.
+cir.func @test_catch_with_cleanup() {
+  %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] 
{alignment = 4 : i64}
+  cir.try_call @ctor(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> ()
+^bb1:
+  cir.try_call @doSomething(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> ()
+^bb2:
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.br ^bb8
+^bb3:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb6(%1 : !cir.eh_token)
+^bb4:
+  %2 = cir.eh.initiate cleanup : !cir.eh_token
+  cir.br ^bb5(%2 : !cir.eh_token)
+^bb5(%eh_token : !cir.eh_token):
+  %3 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.end_cleanup %3 : !cir.cleanup_token
+  cir.br ^bb6(%eh_token : !cir.eh_token)
+^bb6(%eh_token.1 : !cir.eh_token):
+  cir.eh.dispatch %eh_token.1 : !cir.eh_token [
+    catch_all : ^bb7
+  ]
+^bb7(%eh_token.2 : !cir.eh_token):
+  %ct, %exn = cir.begin_catch %eh_token.2 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb8
+^bb8:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_catch_with_cleanup()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+//
+// Entry: ctor call.
+// CHECK:         cir.try_call @ctor(%{{.*}}) ^[[AFTER_CTOR:bb[0-9]+]], 
^[[CTOR_UNWIND:bb[0-9]+]]
+//
+// After ctor: doSomething call.
+// CHECK:       ^[[AFTER_CTOR]]:
+// CHECK:         cir.try_call @doSomething(%{{.*}}) 
^[[NORMAL_CLEANUP:bb[0-9]+]], ^[[DO_UNWIND:bb[0-9]+]]
+//
+// Normal cleanup.
+// CHECK:       ^[[NORMAL_CLEANUP]]:
+// CHECK:         cir.call @dtor
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Ctor unwind: landing pad without cleanup.
+// CHECK:       ^[[CTOR_UNWIND]]:
+// CHECK:         %[[E1:.*]], %[[T1:.*]] = cir.eh.inflight_exception
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[E1]], %[[T1]] : 
!cir.ptr<!void>, !u32i)
+//
+// doSomething unwind: landing pad with cleanup.
+// CHECK:       ^[[DO_UNWIND]]:
+// CHECK:         %[[E2:.*]], %[[T2:.*]] = cir.eh.inflight_exception cleanup
+// CHECK:         cir.br ^[[CLEANUP:bb[0-9]+]](%[[E2]], %[[T2]] : 
!cir.ptr<!void>, !u32i)
+//
+// EH cleanup: begin_cleanup and end_cleanup are REMOVED, dtor call stays.
+// CHECK:       ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: 
!u32i):
+// CHECK-NOT:     cir.begin_cleanup
+// CHECK:         cir.call @dtor
+// CHECK-NOT:     cir.end_cleanup
+// CHECK:         cir.br ^[[DISPATCH]](%[[CL_EXN]], %[[CL_TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch: catch-all handler (direct branch).
+// CHECK:       ^[[DISPATCH]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: 
!u32i):
+// CHECK:         cir.br ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Catch-all handler.
+// CHECK:       ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[CA_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: Try-catch with cleanup + typed catch handler.
+// Like test_catch_with_cleanup but with a typed catch instead of catch-all.
+// Having a typed catch in the dispatch causes both eh.initiate sites to
+// include @_ZTISt9exception in their catch_type_list.
+cir.func @test_typed_catch_with_cleanup() {
+  %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] 
{alignment = 4 : i64}
+  cir.try_call @ctor(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> ()
+^bb1:
+  cir.try_call @doSomething(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> ()
+^bb2:
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.br ^bb9
+^bb3:
+  %1 = cir.eh.initiate : !cir.eh_token
+  cir.br ^bb6(%1 : !cir.eh_token)
+^bb4:
+  %2 = cir.eh.initiate cleanup : !cir.eh_token
+  cir.br ^bb5(%2 : !cir.eh_token)
+^bb5(%eh_token : !cir.eh_token):
+  %3 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.end_cleanup %3 : !cir.cleanup_token
+  cir.br ^bb6(%eh_token : !cir.eh_token)
+^bb6(%eh_token.1 : !cir.eh_token):
+  cir.eh.dispatch %eh_token.1 : !cir.eh_token [
+    catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb7,
+    unwind : ^bb8
+  ]
+^bb7(%eh_token.2 : !cir.eh_token):
+  %ct, %exn = cir.begin_catch %eh_token.2 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
+  cir.end_catch %ct : !cir.catch_token
+  cir.br ^bb9
+^bb8(%eh_token.3 : !cir.eh_token):
+  cir.resume %eh_token.3 : !cir.eh_token
+^bb9:
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_typed_catch_with_cleanup()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+//
+// Entry: ctor call.
+// CHECK:         cir.try_call @ctor(%{{.*}}) ^[[AFTER_CTOR:bb[0-9]+]], 
^[[CTOR_UNWIND:bb[0-9]+]]
+//
+// After ctor: doSomething call.
+// CHECK:       ^[[AFTER_CTOR]]:
+// CHECK:         cir.try_call @doSomething(%{{.*}}) 
^[[NORMAL_CLEANUP:bb[0-9]+]], ^[[DO_UNWIND:bb[0-9]+]]
+//
+// Normal cleanup.
+// CHECK:       ^[[NORMAL_CLEANUP]]:
+// CHECK:         cir.call @dtor
+// CHECK:         cir.br ^[[RETURN:bb[0-9]+]]
+//
+// Ctor unwind: landing pad without cleanup; typed catch list from shared 
dispatch.
+// CHECK:       ^[[CTOR_UNWIND]]:
+// CHECK:         %[[E1:.*]], %[[T1:.*]] = cir.eh.inflight_exception 
[@_ZTISt9exception]
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[E1]], %[[T1]] : 
!cir.ptr<!void>, !u32i)
+//
+// doSomething unwind: landing pad with cleanup and same typed catch list.
+// CHECK:       ^[[DO_UNWIND]]:
+// CHECK:         %[[E2:.*]], %[[T2:.*]] = cir.eh.inflight_exception cleanup 
[@_ZTISt9exception]
+// CHECK:         cir.br ^[[CLEANUP:bb[0-9]+]](%[[E2]], %[[T2]] : 
!cir.ptr<!void>, !u32i)
+//
+// EH cleanup: begin_cleanup and end_cleanup are REMOVED, dtor call stays.
+// CHECK:       ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: 
!u32i):
+// CHECK-NOT:     cir.begin_cleanup
+// CHECK:         cir.call @dtor
+// CHECK-NOT:     cir.end_cleanup
+// CHECK:         cir.br ^[[DISPATCH]](%[[CL_EXN]], %[[CL_TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch: branch to comparison chain.
+// CHECK:       ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.br ^[[CMP_BLOCK:bb[0-9]+]]
+//
+// Comparison: typeid comparison for std::exception, with unwind fallthrough.
+// CHECK:       ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: 
!u32i):
+// CHECK:         %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
+// CHECK:         %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
+// CHECK:         cir.brcond %[[CMP]] ^[[CATCH:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[UNWIND_HANDLER:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i)
+//
+// Typed catch handler.
+// CHECK:       ^[[CATCH]](%[[C_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[C_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+// CHECK:         cir.br ^[[RETURN]]
+//
+// Unwind handler: resume.flat.
+// CHECK:       ^[[UNWIND_HANDLER]](%[[UW_EXN:.*]]: !cir.ptr<!void>, 
%[[UW_TID:.*]]: !u32i):
+// CHECK:         cir.resume.flat %[[UW_EXN]], %[[UW_TID]]
+//
+// CHECK:       ^[[RETURN]]:
+// CHECK:         cir.return
+
+// Test: try-catch with a local variable that has a non-trivial destructor.
+// The cleanup path calls ~S() then falls through to the catch-all dispatch.
+// begin_cleanup/end_cleanup are removed; typed catch + catch-all dispatch is 
lowered.
+cir.func no_inline dso_local @test_catch_with_cleanup_no_ctor() 
personality(@__gxx_personality_v0) {
+  cir.br ^bb1
+^bb1:  // pred: ^bb0
+  %0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["s"] {alignment = 
1 : i64}
+  cir.br ^bb2
+^bb2:  // pred: ^bb1
+  cir.try_call @mayThrow() ^bb3, ^bb4 : () -> ()
+^bb3:  // pred: ^bb2
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.br ^bb9
+^bb4:  // pred: ^bb2
+  %1 = cir.eh.initiate cleanup : !cir.eh_token
+  cir.br ^bb5(%1 : !cir.eh_token)
+^bb5(%2: !cir.eh_token):  // pred: ^bb4
+  %3 = cir.begin_cleanup %2 : !cir.eh_token -> !cir.cleanup_token
+  cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
+  cir.end_cleanup %3 : !cir.cleanup_token
+  cir.br ^bb6(%2 : !cir.eh_token)
+^bb6(%4: !cir.eh_token):  // pred: ^bb5
+  cir.eh.dispatch %4 : !cir.eh_token [
+    catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb7,
+    catch_all : ^bb8
+  ]
+^bb7(%6: !cir.eh_token):  // pred: ^bb6
+  %catch_token2, %exn_ptr2 = cir.begin_catch %6 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
+  cir.end_catch %catch_token2 : !cir.catch_token
+  cir.br ^bb9
+^bb8(%5: !cir.eh_token):  // pred: ^bb6
+  %catch_token, %exn_ptr = cir.begin_catch %5 : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!void>)
+  cir.end_catch %catch_token : !cir.catch_token
+  cir.br ^bb9
+^bb9:  // preds: ^bb3, ^bb7, ^bb8
+  cir.return
+}
+
+// CHECK-LABEL: cir.func {{.*}} @test_catch_with_cleanup_no_ctor()
+// CHECK-SAME: personality(@__gxx_personality_v0)
+// CHECK:         cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], 
^[[UNWIND:bb[0-9]+]]
+//
+// Normal path: destructor called on success.
+// CHECK:       ^[[NORMAL]]:
+// CHECK:         cir.call @dtor
+//
+// Unwind: initiate cleanup → inflight_exception cleanup with std::exception 
type.
+// CHECK:       ^[[UNWIND]]:
+// CHECK:         %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception cleanup 
[@_ZTISt9exception]
+// CHECK:         cir.br ^[[CLEANUP:bb[0-9]+]](%[[EXN]], %[[TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Cleanup: begin_cleanup/end_cleanup removed, destructor call stays.
+// CHECK:       ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: 
!u32i):
+// CHECK-NOT:     cir.begin_cleanup
+// CHECK:         cir.call @dtor
+// CHECK-NOT:     cir.end_cleanup
+// CHECK:         cir.br ^[[DISPATCH:bb[0-9]+]](%[[CL_EXN]], %[[CL_TID]] : 
!cir.ptr<!void>, !u32i)
+//
+// Dispatch: branch to comparison chain.
+// CHECK:       ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.br ^[[CMP_BLOCK:bb[0-9]+]]
+//
+// Comparison: typeid comparison for std::exception, with catch-all 
fallthrough.
+// CHECK:       ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: 
!u32i):
+// CHECK:         %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
+// CHECK:         %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
+// CHECK:         cir.brcond %[[CMP]] ^[[TYPED:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], 
%[[D_TID]] : !cir.ptr<!void>, !u32i)
+//
+// Typed catch for std::exception (^bb7 in source).
+// CHECK:       ^[[TYPED]](%[[T_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[T_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+//
+// Catch-all handler (^bb8 in source).
+// CHECK:       ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: 
!u32i):
+// CHECK:         cir.call @__cxa_begin_catch(%[[CA_EXN]])
+// CHECK:         cir.call @__cxa_end_catch()
+
+// Verify that runtime function declarations are added.
+// CHECK: cir.func private @__gxx_personality_v0(...)
+// CHECK: cir.func private @__cxa_begin_catch(!cir.ptr<!void>)
+// CHECK: cir.func private @__cxa_end_catch()
+
+cir.func private @mayThrow()
+cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
+cir.func private @dtor(!cir.ptr<!rec_SomeClass>) attributes {nothrow}
+cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
+cir.global "private" constant external @_ZTISt9exception : !cir.ptr<!u8i>
+cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
+
+} // end module

diff  --git a/clang/tools/cir-opt/cir-opt.cpp b/clang/tools/cir-opt/cir-opt.cpp
index edadfeec09a2a..83832a100debb 100644
--- a/clang/tools/cir-opt/cir-opt.cpp
+++ b/clang/tools/cir-opt/cir-opt.cpp
@@ -55,6 +55,10 @@ int main(int argc, char **argv) {
     return mlir::createCIRFlattenCFGPass();
   });
 
+  ::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
+    return mlir::createCIREHABILoweringPass();
+  });
+
   ::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
     return mlir::createHoistAllocasPass();
   });


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

Reply via email to