https://github.com/erichkeane created 
https://github.com/llvm/llvm-project/pull/190613

C++ supports the ability to have the 'body' of a constructor be a 'try' block. 
The effect of this is that base construction and initializers are also within 
the same try/catch chain. This patch implements this lowering.

Classic codegen does this by just emitting the 'begin' and 'end' try as their 
own functions, then can emit the body separately between calls. However, CIR 
doesn't really afford us that ability, so this patch extracts a function that 
takes a callback object that is used to emit the function body.  This allows us 
to do a simple 'emitStmt' in the normal cases, plus a `emitCtorPrologue` 
followed by `emitStmt` in the try-body case.

>From d0750b0e4c5863ccc001e100340845599972d567 Mon Sep 17 00:00:00 2001
From: erichkeane <[email protected]>
Date: Fri, 3 Apr 2026 13:46:05 -0700
Subject: [PATCH] [CIR] Implement constructor 'try-body' lowering

C++ supports the ability to have the 'body' of a constructor be a 'try'
block. The effect of this is that base construction and initializers are
also within the same try/catch chain. This patch implements this
lowering.

Classic codegen does this by just emitting the 'begin' and 'end' try as
their own functions, then can emit the body separately between calls.
However, CIR doesn't really afford us that ability, so this patch
extracts a function that takes a callback object that is used to emit
the function body.  This allows us to do a simple 'emitStmt' in the
normal cases, plus a `emitCtorPrologue` followed by `emitStmt` in the
try-body case.
---
 clang/lib/CIR/CodeGen/CIRGenException.cpp | 30 +++++++--
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp  | 51 ++++++++++-----
 clang/lib/CIR/CodeGen/CIRGenFunction.h    |  7 +++
 clang/test/CIR/CodeGen/ctor-try-body.cpp  | 77 +++++++++++++++++++++++
 4 files changed, 142 insertions(+), 23 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/ctor-try-body.cpp

diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp 
b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index d5e176e18a11e..c80f292553a1c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -273,13 +273,12 @@ void CIRGenFunction::addCatchHandlerAttr(
   }
 }
 
-mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
-  if (s.getTryBlock()->body_empty())
-    return mlir::LogicalResult::success();
-
+mlir::LogicalResult
+CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s,
+                               cxxTryBodyEmitter &bodyCallback) {
   mlir::Location loc = getLoc(s.getSourceRange());
-  // Create a scope to hold try local storage for catch params.
 
+  // Create a scope to hold try local storage for catch params.
   mlir::OpBuilder::InsertPoint scopeIP;
   cir::ScopeOp::create(
       builder, loc,
@@ -326,7 +325,7 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const 
CXXTryStmt &s) {
         // are created for statements within the try body before exiting the
         // try body.
         RunCleanupsScope tryBodyCleanups(*this);
-        if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed())
+        if (bodyCallback(*this).failed())
           tryRes = mlir::failure();
         tryBodyCleanups.forceCleanup();
         cir::YieldOp::create(builder, loc);
@@ -417,6 +416,25 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const 
CXXTryStmt &s) {
   return mlir::success();
 }
 
+mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
+  if (s.getTryBlock()->body_empty())
+    return mlir::LogicalResult::success();
+
+  struct simpleTryBodyEmitter final : cxxTryBodyEmitter {
+    const clang::CXXTryStmt &s;
+    simpleTryBodyEmitter(const clang::CXXTryStmt &s) : s(s) {}
+
+    mlir::LogicalResult operator()(CIRGenFunction &cgf) override {
+      return cgf.emitStmt(s.getTryBlock(), /*useCurrentScope=*/true);
+    }
+    ~simpleTryBodyEmitter() override = default;
+  };
+
+  simpleTryBodyEmitter emitter{s};
+
+  return emitCXXTryStmt(s, emitter);
+}
+
 // in classic codegen this function is mapping to `isInvokeDest` previously and
 // currently it's mapping to the conditions that performs early returns in
 // `getInvokeDestImpl`, in CIR we need the condition to know if the EH scope 
may
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 58fe699eeff1f..78044cdb97c5d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -849,27 +849,44 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList 
&args) {
   Stmt *body = ctor->getBody(definition);
   assert(definition == ctor && "emitting wrong constructor body");
 
-  if (isa_and_nonnull<CXXTryStmt>(body)) {
-    cgm.errorNYI(ctor->getSourceRange(), "emitConstructorBody: try body");
-    return;
-  }
-
-  assert(!cir::MissingFeatures::incrementProfileCounter());
-  assert(!cir::MissingFeatures::runCleanupsScope());
-
-  // TODO: in restricted cases, we can emit the vbase initializers of a
-  // complete ctor and then delegate to the base ctor.
+  bool isTryBody = isa_and_nonnull<CXXTryStmt>(body);
+
+  // A type that handles the emission of the constructor body, that can be
+  // called directly for cases where we don't have a try-body, or passed to
+  // emitCXXTryStmt.
+  struct ctorTryBodyEmitter final : cxxTryBodyEmitter {
+    const CXXConstructorDecl *ctor = nullptr;
+    CXXCtorType ctorType;
+    FunctionArgList &args;
+    Stmt *emitterBody = nullptr;
+    ctorTryBodyEmitter(const CXXConstructorDecl *ctor, CXXCtorType ctorType,
+                       FunctionArgList &args, bool isTryBody, Stmt *b)
+        : ctor(ctor), ctorType(ctorType), args(args),
+          emitterBody(isTryBody ? cast<CXXTryStmt>(b)->getTryBlock() : b) {}
+    ~ctorTryBodyEmitter() override = default;
+
+    mlir::LogicalResult operator()(CIRGenFunction &cgf) override {
+      assert(!cir::MissingFeatures::incrementProfileCounter());
+      assert(!cir::MissingFeatures::runCleanupsScope());
+
+      //// TODO: in restricted cases, we can emit the vbase initializers of a
+      //// complete ctor and then delegate to the base ctor.
+
+      cgf.emitCtorPrologue(ctor, ctorType, args);
+      return cgf.emitStmt(emitterBody, /*useCurrentScope=*/true);
+    }
+  };
 
-  // Emit the constructor prologue, i.e. the base and member initializers.
-  emitCtorPrologue(ctor, ctorType, args);
+  ctorTryBodyEmitter emitter{ctor, ctorType, args, isTryBody, body};
+  mlir::LogicalResult bodyRes =
+      isTryBody ? emitCXXTryStmt(*cast<CXXTryStmt>(body), emitter)
+                : emitter(*this);
 
-  // TODO(cir): propagate this result via mlir::logical result. Just 
unreachable
-  // now just to have it handled.
-  if (mlir::failed(emitStmt(body, true))) {
+  // TODO(cir): propagate this result via mlir::logical result. Just
+  // unreachable now just to have it handled.
+  if (bodyRes.failed())
     cgm.errorNYI(ctor->getSourceRange(),
                  "emitConstructorBody: emit body statement failed.");
-    return;
-  }
 }
 
 /// Emits the body of the current destructor.
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 7914a03a7f1a8..fd7964ec8e9a1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1655,6 +1655,13 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   void emitCXXThrowExpr(const CXXThrowExpr *e);
 
+  struct cxxTryBodyEmitter {
+    virtual mlir::LogicalResult operator()(CIRGenFunction &cgf) = 0;
+    virtual ~cxxTryBodyEmitter() = default;
+  };
+
+  mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s,
+                                     cxxTryBodyEmitter &bodyCallback);
   mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s);
 
   void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
diff --git a/clang/test/CIR/CodeGen/ctor-try-body.cpp 
b/clang/test/CIR/CodeGen/ctor-try-body.cpp
new file mode 100644
index 0000000000000..14049e33054a4
--- /dev/null
+++ b/clang/test/CIR/CodeGen/ctor-try-body.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions 
-fcxx-exceptions -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions 
-fcxx-exceptions -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM,LLVMCIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions 
-fcxx-exceptions -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM,OGCG
+
+struct Ctor {
+  Ctor();
+};
+
+struct FromCtor {
+  FromCtor(const Ctor&);
+};
+
+void side_effect();
+void side_effect2();
+
+struct Base {
+  Base();
+};
+
+struct HasThings : Base {
+  FromCtor ct;
+
+  HasThings(const Ctor &c) try : ct(c) { side_effect(); } catch (...) {
+    side_effect2();
+  }
+
+// CIR: cir.func {{.*}}@_ZN9HasThingsC2ERK4Ctor(%[[THIS_ARG:.*]]: 
!cir.ptr<!rec_HasThings> {{.*}}, %[[C_ARG:.*]]: !cir.ptr<!rec_Ctor> {{.*}}) 
{{.*}}special_member<#cir.cxx_ctor<!rec_HasThings, custom>> {
+// CIR-NEXT:  %[[THIS_ALLOC:.*]] = cir.alloca !cir.ptr<!rec_HasThings>, 
!cir.ptr<!cir.ptr<!rec_HasThings>>, ["this", init]
+// CIR-NEXT:  %[[C_ALLOC:.*]] = cir.alloca !cir.ptr<!rec_Ctor>, 
!cir.ptr<!cir.ptr<!rec_Ctor>>, ["c", init, const]
+// CIR-NEXT:  cir.store %[[THIS_ARG]], %[[THIS_ALLOC]] : 
!cir.ptr<!rec_HasThings>, !cir.ptr<!cir.ptr<!rec_HasThings>>
+// CIR-NEXT:  cir.store %[[C_ARG]], %[[C_ALLOC]] : !cir.ptr<!rec_Ctor>, 
!cir.ptr<!cir.ptr<!rec_Ctor>>
+// CIR-NEXT:  %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOC]] : 
!cir.ptr<!cir.ptr<!rec_HasThings>>, !cir.ptr<!rec_HasThings>
+// CIR-NEXT:  cir.scope {
+// CIR-NEXT:    cir.try {
+// CIR-NEXT:      %[[BASE_ADDR:.*]] = cir.base_class_addr %[[THIS_LOAD]] : 
!cir.ptr<!rec_HasThings> nonnull [0] -> !cir.ptr<!rec_Base>
+// CIR-NEXT:      cir.call @_ZN4BaseC2Ev(%[[BASE_ADDR]]) : 
(!cir.ptr<!rec_Base>{{.*}}) -> ()
+// CIR-NEXT:      %[[FROMCTOR_ADDR:.*]] = cir.cast bitcast %[[THIS_LOAD]] : 
!cir.ptr<!rec_HasThings> -> !cir.ptr<!rec_FromCtor>
+// CIR-NEXT:      %[[C_LOAD:.*]] = cir.load %[[C_ALLOC]] : 
!cir.ptr<!cir.ptr<!rec_Ctor>>, !cir.ptr<!rec_Ctor>
+// CIR-NEXT:      cir.call @_ZN8FromCtorC1ERK4Ctor(%[[FROMCTOR_ADDR]], 
%[[C_LOAD]]) : (!cir.ptr<!rec_FromCtor> {{.*}}, !cir.ptr<!rec_Ctor> {{.*}}) -> 
()
+// CIR-NEXT:      cir.call @_Z11side_effectv() : () -> ()
+// CIR-NEXT:      cir.yield
+// CIR-NEXT:    } catch all (%[[CATCH_ARG:.*]]: !cir.eh_token {{.*}}) {
+// CIR-NEXT:      %[[CATCH_TOK:.*]], %[[EX_PTR:.*]] = cir.begin_catch 
%[[CATCH_ARG]] : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
+// CIR-NEXT:      cir.cleanup.scope {
+// CIR-NEXT:        cir.call @_Z12side_effect2v() : () -> ()
+// CIR-NEXT:        cir.yield
+// CIR-NEXT:      } cleanup all {
+// CIR-NEXT:        cir.end_catch %[[CATCH_TOK]] : !cir.catch_token
+// CIR-NEXT:        cir.yield
+// CIR-NEXT:      }
+// CIR-NEXT:      cir.yield
+// CIR-NEXT:    }
+// CIR-NEXT:  }
+// CIR-NEXT:  cir.return
+// CIR-NEXT:}
+
+// Note: This skips a LOT of lines, but otherwise dives into an absolutely
+// 'normal' try/catch/etc block, which both differs between LLVM and OGCG, but
+// isn't particularly relevant to the fact that we generate the base,
+// initializers, and body all in a try block.
+// LLVM: define linkonce_odr void @_ZN9HasThingsC2ERK4Ctor(ptr {{.*}} 
%[[THIS_ARG:.*]], ptr {{.*}} %[[C_ARG:.*]])
+// LLVM:   invoke void @_ZN4BaseC2Ev(ptr {{.*}}%{{.*}})
+// LLVM:   invoke void @_ZN8FromCtorC1ERK4Ctor(ptr {{.*}}%{{.*}}, ptr 
{{.*}}%{{.*}})
+// LLVM:   invoke void @_Z11side_effectv()
+// LLVM:   call ptr @__cxa_begin_catch(ptr %{{.*}})
+// LLVM:   invoke void @_Z12side_effect2v()
+// LLVMCIR:   call void @__cxa_end_catch()
+// OGCG:   invoke void @__cxa_end_catch()
+};
+
+void foo() {
+  Ctor ct;
+  HasThings ht(ct);
+}

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

Reply via email to