Skip to content

Conversation

@AmrDeveloper
Copy link
Member

Add support for try-catch with a handler for a specific exception type

Issue #154992

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Dec 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 7, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clangir

Author: Amr Hesham (AmrDeveloper)

Changes

Add support for try-catch with a handler for a specific exception type

Issue #154992


Full diff: https://github.com/llvm/llvm-project/pull/171042.diff

7 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+4-1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+3)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenException.cpp (+133-16)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+7)
  • (modified) clang/test/CIR/CodeGen/try-catch-tmp.cpp (+55-1)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index fcc7585cf81a5..710a91320a155 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -802,7 +802,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
 //===----------------------------------------------------------------------===//
 
 defvar CIR_YieldableScopes = [
-  "ArrayCtor", "ArrayDtor", "AwaitOp", "CaseOp", "DoWhileOp", "ForOp",
+  "ArrayCtor", "ArrayDtor", "AwaitOp", "CallOp", "CaseOp", "DoWhileOp", "ForOp",
   "GlobalOp", "IfOp", "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp"
 ];
 
@@ -2956,6 +2956,7 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
 
   let results = (outs Optional<CIR_AnyType>:$result);
   let arguments = commonArgs;
+  let regions = (region AnyRegion:$cleanup);
 
   let builders = [
     OpBuilder<(ins "mlir::SymbolRefAttr":$callee, "mlir::Type":$resType,
@@ -2965,6 +2966,8 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
         $_state.addAttribute("callee", callee);
       if (resType && !isa<VoidType>(resType))
         $_state.addTypes(resType);
+      // Create region placeholder for potential cleanups.
+      $_state.addRegion();
     }]>
   ];
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 57b1a1f20aa17..c341a6139cea5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -158,6 +158,9 @@ class CIRGenCXXABI {
   /// Loads the incoming C++ this pointer as it was passed by the caller.
   mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);
 
+  virtual CatchTypeInfo
+  getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType ty,
+                               QualType catchHandlerType) = 0;
   virtual CatchTypeInfo getCatchAllTypeInfo();
 
   /// Get the implicit (second) parameter that comes after the "this" pointer,
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 17f0c6dbab35c..3ef443962aaa7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -503,7 +503,9 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
     callOpWithExceptions =
         builder.createCallOp(callLoc, directFuncOp, cirCallArgs);
 
+    cgf.callWithExceptionCtx = callOpWithExceptions;
     cgf.populateCatchHandlersIfRequired(tryOp);
+    cgf.callWithExceptionCtx = nullptr;
     return callOpWithExceptions;
   }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 375828421eb1b..d5e10ccaef456 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -232,6 +232,30 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) {
   assert(!cir::MissingFeatures::ehCleanupScope());
 }
 
+void CIRGenFunction::populateUnwindResumeBlock(bool isCleanup,
+                                               cir::TryOp tryOp) {
+  const EHPersonality &personality = EHPersonality::get(*this);
+  // This can always be a call because we necessarily didn't find
+  // anything on the EH stack which needs our help.
+  const char *rethrowName = personality.catchallRethrowFn;
+  if (rethrowName != nullptr && !isCleanup) {
+    cgm.errorNYI("populateUnwindResumeBlock CatchallRethrowFn");
+    return;
+  }
+
+  unsigned regionsNum = tryOp->getNumRegions();
+  mlir::Region *unwindRegion = &tryOp->getRegion(regionsNum - 1);
+  mlir::Block *unwindResumeBlock = &unwindRegion->front();
+  if (!unwindResumeBlock->empty())
+    return;
+
+  // Emit cir.resume into the unwind region last block
+  cir::CIRBaseBuilderTy::InsertPoint ip = builder.saveInsertionPoint();
+  builder.setInsertionPointToStart(unwindResumeBlock);
+  cir::ResumeOp::create(builder, tryOp.getLoc());
+  builder.restoreInsertionPoint(ip);
+}
+
 mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
   if (s.getTryBlock()->body_empty())
     return mlir::LogicalResult::success();
@@ -332,21 +356,88 @@ CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
   return mlir::success();
 }
 
+/// Emit the structure of the dispatch block for the given catch scope.
+/// It is an invariant that the dispatch block already exists.
+static void emitCatchDispatchBlock(CIRGenFunction &cgf,
+                                   EHCatchScope &catchScope, cir::TryOp tryOp) {
+  if (EHPersonality::get(cgf).isWasmPersonality()) {
+    cgf.cgm.errorNYI("emitCatchDispatchBlock: WasmPersonality");
+    return;
+  }
+
+  if (EHPersonality::get(cgf).usesFuncletPads()) {
+    cgf.cgm.errorNYI("emitCatchDispatchBlock: usesFuncletPads");
+    return;
+  }
+
+  unsigned int numHandlers = catchScope.getNumHandlers();
+  if (numHandlers == 1 && catchScope.getHandler(0).isCatchAll()) {
+    return;
+  }
+
+  // In traditional LLVM codegen, the right handler is selected (with
+  // calls to eh_typeid_for) and the selector value is loaded. After that,
+  // blocks get connected for later codegen. In CIR, these are all
+  // implicit behaviors of cir.catch - not a lot of work to do.
+  //
+  // Test against each of the exception types we claim to catch.
+  for (unsigned i = 0;; ++i) {
+    assert(i < numHandlers && "ran off end of handlers!");
+    const EHCatchScope::Handler &handler = catchScope.getHandler(i);
+
+    [[maybe_unused]] mlir::TypedAttr typeValue = handler.type.rtti;
+    assert(handler.Type.Flags == 0 && "catch handler flags not supported");
+    assert(typeValue && "fell into catch-all case!");
+
+    // Check for address space mismatch
+    assert(!cir::MissingFeatures::addressSpace());
+
+    // If this is the last handler, we're at the end, and the next
+    // block is the block for the enclosing EH scope. Make sure to call
+    // populateEHCatchRegions for caching it.
+    if (i + 1 == numHandlers) {
+      cgf.populateEHCatchRegions(catchScope.getEnclosingEHScope(), tryOp);
+      return;
+    }
+
+    // If the next handler is a catch-all, we're at the end, and the
+    // next block is that handler.
+    if (catchScope.getHandler(i + 1).isCatchAll())
+      return;
+  }
+}
+
 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);
+    mlir::Region *handler = &tryOp.getHandlerRegions()[i];
     if (catchStmt->getExceptionDecl()) {
-      cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl");
-      return;
-    }
+      // FIXME: Dropping the reference type on the type into makes it
+      // impossible to correctly implement catch-by-reference
+      // semantics for pointers.  Unfortunately, this is what all
+      // existing compilers do, and it's not clear that the standard
+      // personality routine is capable of doing this right.  See C++ DR 388:
+      //   http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#388
+      Qualifiers caughtTypeQuals;
+      QualType caughtType = cgm.getASTContext().getUnqualifiedArrayType(
+          catchStmt->getCaughtType().getNonReferenceType(), caughtTypeQuals);
+      if (caughtType->isObjCObjectPointerType()) {
+        cgm.errorNYI("enterCXXTryStmt: caughtType ObjCObjectPointerType");
+        return;
+      }
 
-    // No exception decl indicates '...', a catch-all.
-    mlir::Region *handler = &tryOp.getHandlerRegions()[i];
-    catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler,
-                           s.getHandler(i));
+      CatchTypeInfo typeInfo = cgm.getCXXABI().getAddrOfCXXCatchHandlerType(
+          getLoc(catchStmt->getSourceRange()), caughtType,
+          catchStmt->getCaughtType());
+      catchScope->setHandler(i, typeInfo, handler, catchStmt);
+    } else {
+      // No exception decl indicates '...', a catch-all.
+      catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler,
+                             s.getHandler(i));
+    }
 
     // Under async exceptions, catch(...) needs to catch HW exception too
     // Mark scope with SehTryBegin as a SEH __try scope
@@ -385,6 +476,9 @@ void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
     return;
   }
 
+  // Emit the structure of the EH dispatch for this catch.
+  emitCatchDispatchBlock(*this, catchScope, tryOp);
+
   // Copy the handler blocks off before we pop the EH stack.  Emitting
   // the handlers might scribble on this memory.
   SmallVector<EHCatchScope::Handler> handlers(catchScope.begin(),
@@ -486,9 +580,11 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
   // with function local static initializers).
   mlir::ArrayAttr handlerTypesAttr = tryOp.getHandlerTypesAttr();
   if (!handlerTypesAttr || handlerTypesAttr.empty()) {
+    // Accumulate all the handlers in scope.
     // Accumulate all the handlers in scope.
     bool hasCatchAll = false;
-    llvm::SmallVector<mlir::Attribute, 4> handlerAttrs;
+    llvm::SmallPtrSet<mlir::Attribute, 4> catchTypes;
+    llvm::SmallVector<mlir::Attribute> handlerAttrs;
     for (EHScopeStack::iterator i = ehStack.begin(), e = ehStack.end(); i != e;
          ++i) {
       switch (i->getKind()) {
@@ -521,8 +617,10 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
           break;
         }
 
-        cgm.errorNYI("emitLandingPad: non catch-all");
-        return;
+        // Check whether we already have a handler for this type.
+        // If not, keep track to later add to catch op.
+        if (catchTypes.insert(handler.type.rtti).second)
+          handlerAttrs.push_back(handler.type.rtti);
       }
 
       if (hasCatchAll)
@@ -531,9 +629,12 @@ void CIRGenFunction::populateCatchHandlers(cir::TryOp tryOp) {
 
     if (hasCatchAll) {
       handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext()));
-    } else {
-      cgm.errorNYI("emitLandingPad: non catch-all");
-      return;
+    }
+
+    // If there's no catch_all, attach the unwind region. This needs to be the
+    // last region in the TryOp catch list.
+    if (!hasCatchAll) {
+      handlerAttrs.push_back(cir::UnwindAttr::get(&getMLIRContext()));
     }
 
     // Add final array of clauses into TryOp.
@@ -558,6 +659,13 @@ void CIRGenFunction::populateEHCatchRegions(EHScopeStack::stable_iterator scope,
     return;
   }
 
+  // The dispatch block for the end of the scope chain is a block that
+  // just resumes unwinding.
+  if (scope == ehStack.stable_end()) {
+    populateUnwindResumeBlock(/*isCleanup=*/true, tryOp);
+    return;
+  }
+
   // Otherwise, we should look at the actual scope.
   EHScope &ehScope = *ehStack.find(scope);
   bool mayThrow = ehScope.mayThrow();
@@ -575,16 +683,25 @@ void CIRGenFunction::populateEHCatchRegions(EHScopeStack::stable_iterator scope,
   if (!mayThrow) {
     switch (ehScope.getKind()) {
     case EHScope::Catch: {
+      mayThrow = true;
+
       // LLVM does some optimization with branches here, CIR just keep track of
       // the corresponding calls.
       EHCatchScope &catchScope = cast<EHCatchScope>(ehScope);
       if (catchScope.getNumHandlers() == 1 &&
           catchScope.getHandler(0).isCatchAll()) {
-        mayThrow = true;
         break;
       }
-      cgm.errorNYI("getEHDispatchBlock: mayThrow non-catch all");
-      return;
+
+      assert(callWithExceptionCtx && "expected call information");
+      {
+        mlir::OpBuilder::InsertionGuard guard(builder);
+        assert(callWithExceptionCtx.getCleanup().empty() &&
+               "one per call: expected empty region at this point");
+        builder.createBlock(&callWithExceptionCtx.getCleanup());
+        builder.createYield(callWithExceptionCtx.getLoc());
+      }
+      break;
     }
     case EHScope::Cleanup: {
       cgm.errorNYI("getEHDispatchBlock: mayThrow & cleanup");
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0df812bcfb94e..48bfcddc09009 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -969,6 +969,8 @@ class CIRGenFunction : public CIRGenTypeCache {
     return false;
   }
 
+  cir::CallOp callWithExceptionCtx = nullptr;
+  void populateUnwindResumeBlock(bool isCleanup, cir::TryOp tryOp);
   void populateEHCatchRegions(EHScopeStack::stable_iterator scope,
                               cir::TryOp tryOp);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 7e145f2c57ce6..2e51130e271f5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -118,6 +118,13 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
 
   mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc,
                                           QualType ty) override;
+  CatchTypeInfo
+  getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType ty,
+                               QualType catchHandlerType) override {
+    auto rtti = dyn_cast<cir::GlobalViewAttr>(getAddrOfRTTIDescriptor(loc, ty));
+    assert(rtti && "expected GlobalViewAttr");
+    return CatchTypeInfo{rtti, 0};
+  }
 
   bool doStructorsInitializeVPtrs(const CXXRecordDecl *vtableClass) override {
     return true;
diff --git a/clang/test/CIR/CodeGen/try-catch-tmp.cpp b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
index 078447f844d9a..5f57af9876615 100644
--- a/clang/test/CIR/CodeGen/try-catch-tmp.cpp
+++ b/clang/test/CIR/CodeGen/try-catch-tmp.cpp
@@ -5,7 +5,7 @@
 
 int division();
 
-void calling_division_inside_try_block() {
+void call_function_inside_try_catch_all() {
   try {
     division();
   } catch (...) {
@@ -42,3 +42,57 @@ void calling_division_inside_try_block() {
 // OGCG:   br label %[[TRY_CONT]]
 // OGCG: [[TRY_CONT]]:
 // OGCG:   ret void
+
+void call_function_inside_try_catch_with_exception_type() {
+  try {
+    division();
+  } catch (int e) {
+  }
+}
+
+// CIR: cir.scope {
+// CIR:   cir.try {
+// CIR:     %[[CALL:.*]] = cir.call @_Z8divisionv() : () -> !s32i
+// CIR:     cir.yield
+// CIR:   } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] {
+// CIR:     cir.yield
+// CIR:   } unwind {
+// CIR:     cir.resume
+// CIR:   }
+// CIR: }
+
+// OGCG:   %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
+// OGCG:   %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
+// OGCG:   %[[E_ADDR:.*]] = alloca i32, align 4
+// OGCG:   %[[CALL:.*]] = invoke noundef i32 @_Z8divisionv()
+// OGCG:           to label %[[INVOKE_NORMAL:.*]] unwind label %[[INVOKE_UNWIND:.*]]
+// OGCG: [[INVOKE_NORMAL]]:
+// OGCG:   br label %try.cont
+// OGCG: [[INVOKE_UNWIND]]:
+// OGCG:   %[[LANDING_PAD:.*]] = landingpad { ptr, i32 }
+// OGCG:           catch ptr @_ZTIi
+// OGCG:   %[[EXCEPTION:.*]] = extractvalue { ptr, i32 } %[[LANDING_PAD]], 0
+// OGCG:   store ptr %[[EXCEPTION]], ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG:   %[[EH_TYPE_ID:.*]] = extractvalue { ptr, i32 } %[[LANDING_PAD]], 1
+// OGCG:   store i32 %[[EH_TYPE_ID]], ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG:   br label %[[CATCH_DISPATCH:.*]]
+// OGCG: [[CATCH_DISPATCH]]:
+// OGCG:   %[[TMP_EH_TYPE_ID:.*]] = load i32, ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG:   %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
+// OGCG:   %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[TMP_EH_TYPE_ID]], %[[EH_TYPE_ID]]
+// OGCG:   br i1 %[[TYPE_ID_EQ]], label %[[CATCH_EXCEPTION:.*]], label %[[EH_RESUME:.*]]
+// OGCG: [[CATCH_EXCEPTION]]:
+// OGCG:   %[[TMP_EXCEPTION:.*]] = load ptr, ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG:   %[[BEGIN_CATCH:.*]] = call ptr @__cxa_begin_catch(ptr %[[TMP_EXCEPTION]])
+// OGCG:   %[[TMP_BEGIN_CATCH:.*]] = load i32, ptr %[[BEGIN_CATCH]], align 4
+// OGCG:   store i32 %[[TMP_BEGIN_CATCH]], ptr %[[E_ADDR]], align 4
+// OGCG:   call void @__cxa_end_catch()
+// OGCG:   br label %[[TRY_NORMA:.*]]
+// OGCG: [[TRY_NORMA]]:
+// OGCG:   ret void
+// OGCG: [[EH_RESUME]]:
+// OGCG:   %[[TMP_EXCEPTION:.*]] = load ptr, ptr %[[EXCEPTION_ADDR]], align 8
+// OGCG:   %[[TMP_EH_TYPE_ID:.*]] = load i32, ptr %[[EH_TYPE_ID_ADDR]], align 4
+// OGCG:   %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[TMP_EXCEPTION]], 0
+// OGCG:   %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[TMP_EH_TYPE_ID]], 1
+// OGCG:   resume { ptr, i32 } %[[EXCEPTION_INFO]]

@github-actions
Copy link

github-actions bot commented Dec 7, 2025

🐧 Linux x64 Test Results

  • 112623 tests passed
  • 4091 tests skipped

✅ The build succeeded and all tests passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants