[clang] [CIR] Upstream Exception CXXTryStmt (PR #162528)

Amr Hesham via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 8 14:28:40 PDT 2025


https://github.com/AmrDeveloper updated https://github.com/llvm/llvm-project/pull/162528

>From 22d4680619e8b41b2985090d78c3ddcd687a2eef Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Tue, 7 Oct 2025 20:26:05 +0200
Subject: [PATCH 1/3] [CIR] Upstream Exception CXXTryStmt

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  64 +++++++-
 clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp        |   4 +
 clang/lib/CIR/CodeGen/CIRGenCXXABI.h          |   3 +
 clang/lib/CIR/CodeGen/CIRGenCleanup.cpp       |   7 +
 clang/lib/CIR/CodeGen/CIRGenCleanup.h         |  94 ++++++++++-
 clang/lib/CIR/CodeGen/CIRGenException.cpp     | 155 ++++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  21 +++
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          |   3 +-
 clang/lib/CIR/CodeGen/EHScopeStack.h          |   8 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 133 +++++++++++++++
 .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp |  32 +++-
 clang/test/CIR/CodeGen/try-catch.cpp          |  29 ++++
 12 files changed, 546 insertions(+), 7 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/try-catch.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 66c4c04f23108..d472445362e6f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -537,7 +537,7 @@ def CIR_StoreOp : CIR_Op<"store", [
 
 defvar CIR_ReturnableScopes = [
   "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp",
-  "DoWhileOp", "WhileOp", "ForOp"
+  "DoWhileOp", "WhileOp", "ForOp", "TryOp"
 ];
 
 def CIR_ReturnOp : CIR_Op<"return", [
@@ -684,7 +684,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 
 defvar CIR_YieldableScopes = [
   "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp",
-  "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp"
+  "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp"
 ];
 
 def CIR_YieldOp : CIR_Op<"yield", [
@@ -4202,6 +4202,66 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// TryOp
+//===----------------------------------------------------------------------===//
+
+// Represents the unwind region where unwind continues or
+// the program std::terminate's.
+def CIR_CatchUnwind : CIR_UnitAttr<"CatchUnwind", "unwind"> {
+  let storageType = [{ CatchUnwind }];
+}
+
+// Represents the catch_all region.
+def CIR_CatchAll : CIR_UnitAttr<"CatchAll", "all"> {
+  let storageType = [{ CatchAllAttr }];
+}
+
+def CIR_TryOp : CIR_Op<"try",[
+  DeclareOpInterfaceMethods<RegionBranchOpInterface>,
+  RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments
+]> {
+  let summary = "C++ try block";
+  let description = [{
+    ```mlir
+
+    Holds the lexical scope of `try {}`. Note that resources used on catch
+    clauses are usually allocated in the same parent as `cir.try`.
+
+    `synthetic`: use `cir.try` to represent try/catches not originally
+    present in the source code (e.g. `g = new Class` under `-fexceptions`).
+
+    `cleanup`: signal to targets (LLVM for now) that this try/catch, needs
+    to specially tag their landing pads as needing "cleanup".
+
+    Example: TBD
+    ```
+  }];
+
+  let arguments = (ins UnitAttr:$synthetic, UnitAttr:$cleanup,
+                       OptionalAttr<ArrayAttr>:$catch_types);
+  let regions = (region AnyRegion:$try_region,
+                        VariadicRegion<AnyRegion>:$catch_regions);
+
+  let assemblyFormat = [{
+    (`synthetic` $synthetic^)?
+    (`cleanup` $cleanup^)?
+    $try_region
+    custom<CatchRegions>($catch_regions, $catch_types)
+    attr-dict
+  }];
+
+  // Everything already covered elsewhere.
+  let builders = [
+    OpBuilder<(ins
+              "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$tryBuilder,
+              "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>"
+              :$catchBuilder)>,
+  ];
+
+  let hasLLVMLowering = false;
+}
+
 //===----------------------------------------------------------------------===//
 // Atomic operations
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
index 5f1faabde22a5..9c5ea54c3adb6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
@@ -36,6 +36,10 @@ CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs(
                                 addedArgs.suffix.size());
 }
 
+CatchTypeInfo CIRGenCXXABI::getCatchAllTypeInfo() {
+  return CatchTypeInfo{nullptr, 0};
+}
+
 void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
                                   FunctionArgList &params) {
   const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index be66240c280ec..fc16e15fd6950 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -15,6 +15,7 @@
 #define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H
 
 #include "CIRGenCall.h"
+#include "CIRGenCleanup.h"
 #include "CIRGenFunction.h"
 #include "CIRGenModule.h"
 
@@ -147,6 +148,8 @@ class CIRGenCXXABI {
   /// Loads the incoming C++ this pointer as it was passed by the caller.
   mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
 
+  virtual CatchTypeInfo getCatchAllTypeInfo();
+
   /// Emit constructor variants required by this ABI.
   virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0;
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 870069715df22..aabe4bbdf18c8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -108,6 +108,13 @@ void EHScopeStack::popCleanup() {
   assert(!cir::MissingFeatures::ehCleanupBranchFixups());
 }
 
+EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) {
+  char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers));
+  assert(!cir::MissingFeatures::innermostEHScope());
+  EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers);
+  return scope;
+}
+
 static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) {
   // Ask the cleanup to emit itself.
   assert(cgf.haveInsertPoint() && "expected insertion point");
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
index 30f5607d655da..5cce1cad57f44 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
@@ -14,14 +14,22 @@
 #ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
 #define CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
 
-#include "Address.h"
 #include "EHScopeStack.h"
-#include "mlir/IR/Value.h"
+#include "mlir/IR/BuiltinAttributeInterfaces.h"
+#include "clang/CIR/MissingFeatures.h"
 
 namespace clang::CIRGen {
 
+/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the
+/// type of a catch handler, so we use this wrapper.
+struct CatchTypeInfo {
+  mlir::TypedAttr rtti;
+  unsigned flags;
+};
+
 /// A protected scope for zero-cost EH handling.
 class EHScope {
+
   class CommonBitFields {
     friend class EHScope;
     unsigned kind : 3;
@@ -29,6 +37,13 @@ class EHScope {
   enum { NumCommonBits = 3 };
 
 protected:
+  class CatchBitFields {
+    friend class EHCatchScope;
+    unsigned : NumCommonBits;
+
+    unsigned numHandlers : 32 - NumCommonBits;
+  };
+
   class CleanupBitFields {
     friend class EHCleanupScope;
     unsigned : NumCommonBits;
@@ -58,6 +73,7 @@ class EHScope {
 
   union {
     CommonBitFields commonBits;
+    CatchBitFields catchBits;
     CleanupBitFields cleanupBits;
   };
 
@@ -67,6 +83,72 @@ class EHScope {
   EHScope(Kind kind) { commonBits.kind = kind; }
 
   Kind getKind() const { return static_cast<Kind>(commonBits.kind); }
+
+  bool hasEHBranches() const {
+    // Traditional LLVM codegen also checks for `!block->use_empty()`, but
+    // in CIRGen the block content is not important, just used as a way to
+    // signal `hasEHBranches`.
+    assert(!cir::MissingFeatures::ehstackBranches());
+    return false;
+  }
+};
+
+/// A scope which attempts to handle some, possibly all, types of
+/// exceptions.
+///
+/// Objective C \@finally blocks are represented using a cleanup scope
+/// after the catch scope.
+
+class EHCatchScope : public EHScope {
+  // In effect, we have a flexible array member
+  //   Handler Handlers[0];
+  // But that's only standard in C99, not C++, so we have to do
+  // annoying pointer arithmetic instead.
+
+public:
+  struct Handler {
+    /// A type info value, or null (C++ null, not an LLVM null pointer)
+    /// for a catch-all.
+    CatchTypeInfo type;
+
+    /// The catch handler for this type.
+    mlir::Block *block;
+  };
+
+private:
+  friend class EHScopeStack;
+
+  Handler *getHandlers() { return reinterpret_cast<Handler *>(this + 1); }
+
+public:
+  static size_t getSizeForNumHandlers(unsigned n) {
+    return sizeof(EHCatchScope) + n * sizeof(Handler);
+  }
+
+  EHCatchScope(unsigned numHandlers) : EHScope(Catch) {
+    catchBits.numHandlers = numHandlers;
+    assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?");
+  }
+
+  unsigned getNumHandlers() const { return catchBits.numHandlers; }
+
+  void setHandler(unsigned i, CatchTypeInfo type, mlir::Block *block) {
+    assert(i < getNumHandlers());
+    getHandlers()[i].type = type;
+    getHandlers()[i].block = block;
+  }
+
+  // Clear all handler blocks.
+  // FIXME: it's better to always call clearHandlerBlocks in DTOR and have a
+  // 'takeHandler' or some such function which removes ownership from the
+  // EHCatchScope object if the handlers should live longer than EHCatchScope.
+  void clearHandlerBlocks() {
+    // The blocks are owned by TryOp, nothing to delete.
+  }
+
+  static bool classof(const EHScope *scope) {
+    return scope->getKind() == Catch;
+  }
 };
 
 /// A cleanup scope which generates the cleanup blocks lazily.
@@ -147,5 +229,13 @@ EHScopeStack::find(stable_iterator savePoint) const {
   return iterator(endOfBuffer - savePoint.size);
 }
 
+inline void EHScopeStack::popCatch() {
+  assert(!empty() && "popping exception stack when not empty");
+
+  EHCatchScope &scope = llvm::cast<EHCatchScope>(*begin());
+  assert(!cir::MissingFeatures::innermostEHScope());
+  deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers()));
+}
+
 } // namespace clang::CIRGen
 #endif // CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 645384383711b..975e031ed5cd2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CIRGenCXXABI.h"
+#include "CIRGenCleanup.h"
 #include "CIRGenFunction.h"
 
 #include "clang/AST/StmtVisitor.h"
@@ -64,3 +65,157 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) {
   // Deactivate the cleanup block.
   assert(!cir::MissingFeatures::ehCleanupScope());
 }
+
+mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
+  auto loc = getLoc(s.getSourceRange());
+
+  // Create a scope to hold try local storage for catch params.
+  mlir::OpBuilder::InsertPoint scopeIP;
+  cir::ScopeOp::create(builder, loc, /*scopeBuilder=*/
+                       [&](mlir::OpBuilder &b, mlir::Location loc) {
+                         scopeIP = builder.saveInsertionPoint();
+                       });
+
+  mlir::LogicalResult result = mlir::success();
+  {
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.restoreInsertionPoint(scopeIP);
+    result = emitCXXTryStmtUnderScope(s);
+    cir::YieldOp::create(builder, loc);
+  }
+
+  return result;
+}
+
+mlir::LogicalResult
+CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
+  const llvm::Triple &t = getTarget().getTriple();
+  // If we encounter a try statement on in an OpenMP target region offloaded to
+  // a GPU, we treat it as a basic block.
+  const bool isTargetDevice =
+      (cgm.getLangOpts().OpenMPIsTargetDevice && (t.isNVPTX() || t.isAMDGCN()));
+  if (isTargetDevice) {
+    cgm.errorNYI(
+        "emitCXXTryStmtUnderScope: OpenMP target region offloaded to GPU");
+    return mlir::success();
+  }
+
+  auto hasCatchAll = [&]() {
+    if (!s.getNumHandlers())
+      return false;
+    unsigned lastHandler = s.getNumHandlers() - 1;
+    return s.getHandler(lastHandler)->getExceptionDecl() == nullptr;
+  };
+
+  unsigned numHandlers = s.getNumHandlers();
+  mlir::Location tryLoc = getLoc(s.getBeginLoc());
+
+  mlir::OpBuilder::InsertPoint beginInsertTryBody;
+
+  // Create the scope to represent only the C/C++ `try {}` part. However,
+  // don't populate right away. Reserve some space to store the exception
+  // info but don't emit the bulk right away, for now only make sure the
+  // scope returns the exception information.
+  auto tryOp = cir::TryOp::create(
+      builder, tryLoc,
+      /*tryBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location loc) {
+        beginInsertTryBody = builder.saveInsertionPoint();
+      },
+      /*catchBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location loc,
+          mlir::OperationState &result) {
+        mlir::OpBuilder::InsertionGuard guard(b);
+        unsigned numRegionsToCreate =
+            hasCatchAll() ? numHandlers : numHandlers + 1;
+        for (unsigned i = 0; i != numRegionsToCreate; ++i) {
+          builder.createBlock(result.addRegion());
+        }
+      });
+
+  // Finally emit the body for try/catch.
+  auto emitTryCatchBody = [&]() -> mlir::LogicalResult {
+    mlir::Location loc = tryOp.getLoc();
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.restoreInsertionPoint(beginInsertTryBody);
+    CIRGenFunction::LexicalScope tryScope{*this, loc,
+                                          builder.getInsertionBlock()};
+
+    tryScope.setAsTry(tryOp);
+
+    // Attach the basic blocks for the catch regions.
+    enterCXXTryStmt(s, tryOp);
+
+    // Emit the body for the `try {}` part.
+    {
+      mlir::OpBuilder::InsertionGuard guard(builder);
+      CIRGenFunction::LexicalScope tryBodyScope{*this, loc,
+                                                builder.getInsertionBlock()};
+      if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed())
+        return mlir::failure();
+    }
+
+    // Emit catch clauses.
+    exitCXXTryStmt(s);
+    return mlir::success();
+  };
+
+  return emitTryCatchBody();
+}
+
+void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
+                                     bool isFnTryBlock) {
+
+  unsigned numHandlers = s.getNumHandlers();
+  EHCatchScope *catchScope = ehStack.pushCatch(numHandlers);
+  for (unsigned i = 0; i != numHandlers; ++i) {
+    const CXXCatchStmt *catchStmt = s.getHandler(i);
+    if (catchStmt->getExceptionDecl()) {
+      cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl");
+      return;
+    }
+
+    mlir::Block *handler = &tryOp.getCatchRegions()[i].getBlocks().front();
+
+    // No exception decl indicates '...', a catch-all.
+    catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler);
+
+    // Under async exceptions, catch(...) need to catch HW exception too
+    // Mark scope with SehTryBegin as a SEH __try scope
+    if (getLangOpts().EHAsynch) {
+      cgm.errorNYI("enterCXXTryStmt: EHAsynch");
+      return;
+    }
+  }
+}
+
+void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
+  unsigned numHandlers = s.getNumHandlers();
+  EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
+  assert(catchScope.getNumHandlers() == numHandlers);
+  cir::TryOp tryOp = curLexScope->getTry();
+
+  // If the catch was not required, bail out now.
+  if (!catchScope.hasEHBranches()) {
+    catchScope.clearHandlerBlocks();
+    ehStack.popCatch();
+
+    // Drop all basic block from all catch regions.
+    SmallVector<mlir::Block *> eraseBlocks;
+    for (mlir::Region &region : tryOp.getCatchRegions()) {
+      if (region.empty())
+        continue;
+
+      for (mlir::Block &b : region.getBlocks())
+        eraseBlocks.push_back(&b);
+    }
+
+    for (mlir::Block *b : eraseBlocks)
+      b->erase();
+
+    tryOp.setCatchTypesAttr({});
+    return;
+  }
+
+  cgm.errorNYI("exitCXXTryStmt: Required catch");
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index d10d058ef289a..c0adab3abb4fd 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -883,6 +883,9 @@ class CIRGenFunction : public CIRGenTypeCache {
 
     LexicalScope *parentScope = nullptr;
 
+    // Holds actual value for ScopeKind::Try
+    cir::TryOp tryOp = nullptr;
+
     // Only Regular is used at the moment. Support for other kinds will be
     // added as the relevant statements/expressions are upstreamed.
     enum Kind {
@@ -942,6 +945,10 @@ class CIRGenFunction : public CIRGenTypeCache {
     void setAsGlobalInit() { scopeKind = Kind::GlobalInit; }
     void setAsSwitch() { scopeKind = Kind::Switch; }
     void setAsTernary() { scopeKind = Kind::Ternary; }
+    void setAsTry(cir::TryOp op) {
+      scopeKind = Kind::Try;
+      tryOp = op;
+    }
 
     // Lazy create cleanup block or return what's available.
     mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) {
@@ -964,6 +971,11 @@ class CIRGenFunction : public CIRGenTypeCache {
       return cleanupBlock;
     }
 
+    cir::TryOp getTry() {
+      assert(isTry());
+      return tryOp;
+    }
+
     // ---
     // Return handling.
     // ---
@@ -1265,6 +1277,15 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   void emitCXXThrowExpr(const CXXThrowExpr *e);
 
+  mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s);
+
+  mlir::LogicalResult emitCXXTryStmtUnderScope(const clang::CXXTryStmt &s);
+
+  void enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
+                       bool isFnTryBlock = false);
+
+  void exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock = false);
+
   void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
                         clang::CXXCtorType ctorType, FunctionArgList &args);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 0b8f8bfdfb046..cfd48a227ed20 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -154,6 +154,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
     return emitWhileStmt(cast<WhileStmt>(*s));
   case Stmt::DoStmtClass:
     return emitDoStmt(cast<DoStmt>(*s));
+  case Stmt::CXXTryStmtClass:
+    return emitCXXTryStmt(cast<CXXTryStmt>(*s));
   case Stmt::CXXForRangeStmtClass:
     return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*s), attr);
   case Stmt::OpenACCComputeConstructClass:
@@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
   case Stmt::CoroutineBodyStmtClass:
     return emitCoroutineBody(cast<CoroutineBodyStmt>(*s));
   case Stmt::CoreturnStmtClass:
-  case Stmt::CXXTryStmtClass:
   case Stmt::IndirectGotoStmtClass:
   case Stmt::OMPParallelDirectiveClass:
   case Stmt::OMPTaskwaitDirectiveClass:
diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h
index 66c1f76094c58..1dce5ac50b09d 100644
--- a/clang/lib/CIR/CodeGen/EHScopeStack.h
+++ b/clang/lib/CIR/CodeGen/EHScopeStack.h
@@ -158,6 +158,14 @@ class EHScopeStack {
   /// Pops a cleanup scope off the stack.  This is private to CIRGenCleanup.cpp.
   void popCleanup();
 
+  /// Push a set of catch handlers on the stack.  The catch is
+  /// uninitialized and will need to have the given number of handlers
+  /// set on it.
+  class EHCatchScope *pushCatch(unsigned numHandlers);
+
+  /// Pops a catch scope off the stack. This is private to CIRGenException.cpp.
+  void popCatch();
+
   /// Determines whether the exception-scopes stack is empty.
   bool empty() const { return startOfData == endOfBuffer; }
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index cdd4e3c96ca98..c6c142255e16b 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1083,6 +1083,139 @@ LogicalResult cir::ScopeOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// TryOp
+//===----------------------------------------------------------------------===//
+
+void cir::TryOp::build(
+    OpBuilder &builder, OperationState &result,
+    function_ref<void(OpBuilder &, Location)> tryBodyBuilder,
+    function_ref<void(OpBuilder &, Location, OperationState &)> catchBuilder) {
+  assert(tryBodyBuilder && "expected builder callback for 'cir.try' body");
+
+  OpBuilder::InsertionGuard guard(builder);
+
+  // Try body region
+  Region *tryBodyRegion = result.addRegion();
+
+  // Create try body region and set insertion point
+  builder.createBlock(tryBodyRegion);
+  tryBodyBuilder(builder, result.location);
+  catchBuilder(builder, result.location, result);
+}
+
+void cir::TryOp::getSuccessorRegions(
+    mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
+  // If any index all the underlying regions branch back to the parent
+  // operation.
+  if (!point.isParent()) {
+    regions.push_back(RegionSuccessor());
+    return;
+  }
+
+  // If the condition isn't constant, both regions may be executed.
+  regions.push_back(RegionSuccessor(&getTryRegion()));
+
+  // FIXME: optimize, ideas include:
+  // - If we know a target function never throws a specific type, we can
+  //   remove the catch handler.
+  for (auto &r : this->getCatchRegions())
+    regions.push_back(RegionSuccessor(&r));
+}
+
+void printCatchRegions(OpAsmPrinter &p, cir::TryOp op,
+                       mlir::MutableArrayRef<::mlir::Region> regions,
+                       mlir::ArrayAttr catchList) {
+
+  int currCatchIdx = 0;
+  if (!catchList)
+    return;
+  p << "catch [";
+  llvm::interleaveComma(catchList, p, [&](const Attribute &a) {
+    auto exRtti = a;
+
+    if (mlir::isa<cir::CatchUnwindAttr>(a)) {
+      p.printAttribute(a);
+      p << " ";
+    } else if (!exRtti) {
+      p << "all";
+    } else {
+      p << "type ";
+      p.printAttribute(exRtti);
+      p << " ";
+    }
+    p.printRegion(regions[currCatchIdx], /*printEntryBLockArgs=*/false,
+                  /*printBlockTerminators=*/true);
+    currCatchIdx++;
+  });
+  p << "]";
+}
+
+ParseResult parseCatchRegions(
+    OpAsmParser &parser,
+    llvm::SmallVectorImpl<std::unique_ptr<::mlir::Region>> &regions,
+    ::mlir::ArrayAttr &catchersAttr) {
+  llvm::SmallVector<mlir::Attribute, 4> catchList;
+
+  auto parseAndCheckRegion = [&]() -> ParseResult {
+    // Parse region attached to catch
+    regions.emplace_back(new Region);
+    Region &currRegion = *regions.back();
+    SMLoc parserLoc = parser.getCurrentLocation();
+    if (parser.parseRegion(currRegion)) {
+      regions.clear();
+      return failure();
+    }
+
+    if (currRegion.empty()) {
+      return parser.emitError(parser.getCurrentLocation(),
+                              "catch region shall not be empty");
+    }
+
+    if (!(currRegion.back().mightHaveTerminator() &&
+          currRegion.back().getTerminator()))
+      return parser.emitError(
+          parserLoc, "blocks are expected to be explicitly terminated");
+
+    return success();
+  };
+
+  auto parseCatchEntry = [&]() -> ParseResult {
+    mlir::Attribute exceptionTypeInfo;
+
+    if (parser.parseOptionalAttribute(exceptionTypeInfo).has_value()) {
+      catchList.push_back(exceptionTypeInfo);
+    } else {
+      ::llvm::StringRef attrStr;
+      if (parser.parseOptionalKeyword(&attrStr, {"all"}).succeeded()) {
+        // "all" keyword found, exceptionTypeInfo remains null
+      } else if (parser.parseOptionalKeyword("type").succeeded()) {
+        if (parser.parseAttribute(exceptionTypeInfo).failed())
+          return parser.emitError(parser.getCurrentLocation(),
+                                  "expected valid RTTI info attribute");
+      } else {
+        return parser.emitError(parser.getCurrentLocation(),
+                                "expected attribute, 'all', or 'type' keyword");
+      }
+      catchList.push_back(exceptionTypeInfo);
+    }
+    return parseAndCheckRegion();
+  };
+
+  if (parser.parseKeyword("catch").failed())
+    return parser.emitError(parser.getCurrentLocation(),
+                            "expected 'catch' keyword here");
+
+  if (parser
+          .parseCommaSeparatedList(OpAsmParser::Delimiter::Square,
+                                   parseCatchEntry, " in catch list")
+          .failed())
+    return failure();
+
+  catchersAttr = parser.getBuilder().getArrayAttr(catchList);
+  return ::mlir::success();
+}
+
 //===----------------------------------------------------------------------===//
 // BrOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 26e5c0572f12e..6b7f92299ffe7 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -532,10 +532,38 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern<cir::TernaryOp> {
   }
 };
 
+class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> {
+public:
+  using OpRewritePattern<cir::TryOp>::OpRewritePattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::TryOp tryOp,
+                  mlir::PatternRewriter &rewriter) const override {
+    mlir::Region *tryRegion = &tryOp.getTryRegion();
+
+    // Empty scope can be removed.
+    if (tryRegion->empty()) {
+      rewriter.eraseOp(tryOp);
+      return mlir::success();
+    }
+
+    // Scope with only yield can be removed.
+    mlir::Block *tryRegionFirstBlock = &tryRegion->front();
+    if (tryRegionFirstBlock->getOperations().size() == 1 &&
+        mlir::isa<YieldOp>(tryRegionFirstBlock->front())) {
+      rewriter.eraseOp(tryOp);
+      return mlir::success();
+    }
+
+    llvm_unreachable("TryOp: non-empty try block is NYI");
+    return mlir::success();
+  }
+};
+
 void populateFlattenCFGPatterns(RewritePatternSet &patterns) {
   patterns
       .add<CIRIfFlattening, CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening,
-           CIRSwitchOpFlattening, CIRTernaryOpFlattening>(
+           CIRSwitchOpFlattening, CIRTernaryOpFlattening, CIRTryOpFlattening>(
           patterns.getContext());
 }
 
@@ -549,7 +577,7 @@ void CIRFlattenCFGPass::runOnOperation() {
     assert(!cir::MissingFeatures::ifOp());
     assert(!cir::MissingFeatures::switchOp());
     assert(!cir::MissingFeatures::tryOp());
-    if (isa<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp>(op))
+    if (isa<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp, TryOp>(op))
       ops.push_back(op);
   });
 
diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp
new file mode 100644
index 0000000000000..3dea2ac67093f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -0,0 +1,29 @@
+// 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
+
+void empty_try_block_with_catch_all() {
+  try {} catch (...) {}
+}
+
+// CIR: cir.func{{.*}} @_Z30empty_try_block_with_catch_allv()
+// CIR:   cir.scope {
+// CIR:     cir.try {
+// CIR:       cir.yield
+// CIR:     }
+// CIR:   }
+
+// TODO(CIR): Those blocks will be removed once RemoveEmptyScope RewritePattern is upstreamed
+
+// LLVM: define{{.*}} void @_Z30empty_try_block_with_catch_allv()
+// LLVM:  br label %1
+// LLVM: 1:
+// LLVM:  br label %2
+// LLVM: 2:
+// LLVM:  ret void
+
+// OGCG: define{{.*}} void @_Z30empty_try_block_with_catch_allv()
+// OGCG:   ret void

>From 7504b031d50ffde4c66e65d11cd224a5fdc1dfa1 Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Wed, 8 Oct 2025 22:00:37 +0200
Subject: [PATCH 2/3] Remove invalid TODO

---
 clang/test/CIR/CodeGen/try-catch.cpp | 2 --
 1 file changed, 2 deletions(-)

diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp
index 3dea2ac67093f..76749c752e7d4 100644
--- a/clang/test/CIR/CodeGen/try-catch.cpp
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -16,8 +16,6 @@ void empty_try_block_with_catch_all() {
 // CIR:     }
 // CIR:   }
 
-// TODO(CIR): Those blocks will be removed once RemoveEmptyScope RewritePattern is upstreamed
-
 // LLVM: define{{.*}} void @_Z30empty_try_block_with_catch_allv()
 // LLVM:  br label %1
 // LLVM: 1:

>From 28e6cc84c071d128cb812b86d3363ce829b1e097 Mon Sep 17 00:00:00 2001
From: Amr Hesham <amr96 at programmer.net>
Date: Wed, 8 Oct 2025 23:03:52 +0200
Subject: [PATCH 3/3] Address code review comments

---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 15 ++++++
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 53 +++++++++----------
 clang/lib/CIR/CodeGen/CIRGenException.cpp     | 45 ++++++++--------
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  9 ++--
 clang/test/CIR/IR/try-catch.cir               | 27 ++++++++++
 5 files changed, 93 insertions(+), 56 deletions(-)
 create mode 100644 clang/test/CIR/IR/try-catch.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 7714750a53d44..573e1707b8255 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -858,4 +858,19 @@ def CIR_TypeInfoAttr : CIR_Attr<"TypeInfo", "typeinfo", [TypedAttrInterface]> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// CatAllAttr & CatchUnwindAttr
+//===----------------------------------------------------------------------===//
+
+// Represents the unwind region where unwind continues or
+// the program std::terminate's.
+def CIR_CatchUnwindAttr : CIR_UnitAttr<"CatchUnwind", "unwind"> {
+  let storageType = [{ CatchUnwind }];
+}
+
+// Represents the catch_all region.
+def CIR_CatchAllAttr : CIR_UnitAttr<"CatchAll", "all"> {
+  let storageType = [{ CatchAllAttr }];
+}
+
 #endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index d472445362e6f..af22d72bc5c51 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -535,10 +535,9 @@ def CIR_StoreOp : CIR_Op<"store", [
 // ReturnOp
 //===----------------------------------------------------------------------===//
 
-defvar CIR_ReturnableScopes = [
-  "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp",
-  "DoWhileOp", "WhileOp", "ForOp", "TryOp"
-];
+defvar CIR_ReturnableScopes = ["FuncOp", "ScopeOp", "IfOp", "SwitchOp",
+                               "CaseOp", "DoWhileOp", "WhileOp", "ForOp",
+                               "TryOp"];
 
 def CIR_ReturnOp : CIR_Op<"return", [
   ParentOneOf<CIR_ReturnableScopes>, Terminator
@@ -682,10 +681,9 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 // YieldOp
 //===----------------------------------------------------------------------===//
 
-defvar CIR_YieldableScopes = [
-  "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp",
-  "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp"
-];
+defvar CIR_YieldableScopes = ["ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp",
+                              "ForOp", "GlobalOp", "IfOp", "ScopeOp",
+                              "SwitchOp", "TernaryOp", "WhileOp", "TryOp"];
 
 def CIR_YieldOp : CIR_Op<"yield", [
   ReturnLike, Terminator, ParentOneOf<CIR_YieldableScopes>
@@ -4206,25 +4204,12 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> {
 // TryOp
 //===----------------------------------------------------------------------===//
 
-// Represents the unwind region where unwind continues or
-// the program std::terminate's.
-def CIR_CatchUnwind : CIR_UnitAttr<"CatchUnwind", "unwind"> {
-  let storageType = [{ CatchUnwind }];
-}
-
-// Represents the catch_all region.
-def CIR_CatchAll : CIR_UnitAttr<"CatchAll", "all"> {
-  let storageType = [{ CatchAllAttr }];
-}
-
 def CIR_TryOp : CIR_Op<"try",[
   DeclareOpInterfaceMethods<RegionBranchOpInterface>,
   RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments
 ]> {
   let summary = "C++ try block";
   let description = [{
-    ```mlir
-
     Holds the lexical scope of `try {}`. Note that resources used on catch
     clauses are usually allocated in the same parent as `cir.try`.
 
@@ -4234,7 +4219,20 @@ def CIR_TryOp : CIR_Op<"try",[
     `cleanup`: signal to targets (LLVM for now) that this try/catch, needs
     to specially tag their landing pads as needing "cleanup".
 
-    Example: TBD
+    Example:
+
+    ```mlir
+    %0 = cir.alloc.exception 16 -> !cir.ptr<!some_record>
+    %1 = cir.get_global @d2 : !cir.ptr<!some_record>
+    cir.try synthetic cleanup {
+      cir.call exception @_ZN7test2_DC1ERKS_(%0, %1)
+            : (!cir.ptr<!some_record>, !cir.ptr<!some_record>) -> () cleanup {
+        %2 = cir.cast bitcast %0 : !cir.ptr<!some_record> -> !cir.ptr<!void>
+        cir.free.exception %2
+        cir.yield
+      }
+      ...
+    }
     ```
   }];
 
@@ -4252,12 +4250,11 @@ def CIR_TryOp : CIR_Op<"try",[
   }];
 
   // Everything already covered elsewhere.
-  let builders = [
-    OpBuilder<(ins
-              "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$tryBuilder,
-              "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>"
-              :$catchBuilder)>,
-  ];
+  let builders = [OpBuilder<(ins 
+      "llvm::function_ref<void(mlir::OpBuilder &, " 
+        "mlir::Location)>":$tryBuilder,
+      "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, "
+        "mlir::OperationState &)>":$catchBuilder)>];
 
   let hasLLVMLowering = false;
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 975e031ed5cd2..19ecd1b635d5d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -67,7 +67,7 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) {
 }
 
 mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
-  auto loc = getLoc(s.getSourceRange());
+  mlir::Location loc = getLoc(s.getSourceRange());
 
   // Create a scope to hold try local storage for catch params.
   mlir::OpBuilder::InsertPoint scopeIP;
@@ -76,14 +76,10 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
                          scopeIP = builder.saveInsertionPoint();
                        });
 
-  mlir::LogicalResult result = mlir::success();
-  {
-    mlir::OpBuilder::InsertionGuard guard(builder);
-    builder.restoreInsertionPoint(scopeIP);
-    result = emitCXXTryStmtUnderScope(s);
-    cir::YieldOp::create(builder, loc);
-  }
-
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.restoreInsertionPoint(scopeIP);
+  mlir::LogicalResult result = emitCXXTryStmtUnderScope(s);
+  cir::YieldOp::create(builder, loc);
   return result;
 }
 
@@ -100,16 +96,8 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
     return mlir::success();
   }
 
-  auto hasCatchAll = [&]() {
-    if (!s.getNumHandlers())
-      return false;
-    unsigned lastHandler = s.getNumHandlers() - 1;
-    return s.getHandler(lastHandler)->getExceptionDecl() == nullptr;
-  };
-
   unsigned numHandlers = s.getNumHandlers();
   mlir::Location tryLoc = getLoc(s.getBeginLoc());
-
   mlir::OpBuilder::InsertPoint beginInsertTryBody;
 
   // Create the scope to represent only the C/C++ `try {}` part. However,
@@ -126,15 +114,25 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
       [&](mlir::OpBuilder &b, mlir::Location loc,
           mlir::OperationState &result) {
         mlir::OpBuilder::InsertionGuard guard(b);
+
+        bool hasCatchAll = false;
+        for (unsigned i = 0; i != numHandlers; ++i) {
+          hasCatchAll |= s.getHandler(i)->getExceptionDecl() == nullptr;
+          if (hasCatchAll)
+            break;
+        }
+
+        // We create an extra region for an unwind catch handler in case the
+        // catch-all handler doesn't exists
         unsigned numRegionsToCreate =
-            hasCatchAll() ? numHandlers : numHandlers + 1;
-        for (unsigned i = 0; i != numRegionsToCreate; ++i) {
+            hasCatchAll ? numHandlers : numHandlers + 1;
+
+        for (unsigned i = 0; i != numRegionsToCreate; ++i)
           builder.createBlock(result.addRegion());
-        }
       });
 
   // Finally emit the body for try/catch.
-  auto emitTryCatchBody = [&]() -> mlir::LogicalResult {
+  {
     mlir::Location loc = tryOp.getLoc();
     mlir::OpBuilder::InsertionGuard guard(builder);
     builder.restoreInsertionPoint(beginInsertTryBody);
@@ -157,10 +155,9 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
 
     // Emit catch clauses.
     exitCXXTryStmt(s);
-    return mlir::success();
-  };
+  }
 
-  return emitTryCatchBody();
+  return mlir::success();
 }
 
 void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index c6c142255e16b..3865ca8924728 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1089,9 +1089,10 @@ LogicalResult cir::ScopeOp::verify() {
 
 void cir::TryOp::build(
     OpBuilder &builder, OperationState &result,
-    function_ref<void(OpBuilder &, Location)> tryBodyBuilder,
+    function_ref<void(OpBuilder &, Location)> tryBuilder,
     function_ref<void(OpBuilder &, Location, OperationState &)> catchBuilder) {
-  assert(tryBodyBuilder && "expected builder callback for 'cir.try' body");
+  assert(tryBuilder && "expected builder callback for 'cir.try' body");
+  assert(catchBuilder && "expected builder callback for 'catch' body");
 
   OpBuilder::InsertionGuard guard(builder);
 
@@ -1100,7 +1101,7 @@ void cir::TryOp::build(
 
   // Create try body region and set insertion point
   builder.createBlock(tryBodyRegion);
-  tryBodyBuilder(builder, result.location);
+  tryBuilder(builder, result.location);
   catchBuilder(builder, result.location, result);
 }
 
@@ -1119,7 +1120,7 @@ void cir::TryOp::getSuccessorRegions(
   // FIXME: optimize, ideas include:
   // - If we know a target function never throws a specific type, we can
   //   remove the catch handler.
-  for (auto &r : this->getCatchRegions())
+  for (mlir::Region &r : this->getCatchRegions())
     regions.push_back(RegionSuccessor(&r));
 }
 
diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir
new file mode 100644
index 0000000000000..66a2ddcc4b861
--- /dev/null
+++ b/clang/test/CIR/IR/try-catch.cir
@@ -0,0 +1,27 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+module {
+  
+cir.func dso_local @empty_try_block_with_catch_all() {
+  cir.scope {
+    cir.try {
+      cir.yield
+    } catch [type #cir.all {
+      cir.yield
+    }]
+  }
+  cir.return
+}
+
+// CHECK:  cir.func dso_local @empty_try_block_with_catch_all() {
+// CHECK:    cir.scope {
+// CHECK:      cir.try {
+// CHECK:        cir.yield
+// CHECK:      } catch [type #cir.all {
+// CHECK:        cir.yield
+// CHECK:      }]
+// CHECK:    }
+// CHECK:    cir.return
+// CHECK:  }
+
+}
\ No newline at end of file



More information about the cfe-commits mailing list