[clang] [CIR] Implement null check for CXXNewExpr (PR #192848)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 23 05:48:26 PDT 2026


https://github.com/AbdallahRashed updated https://github.com/llvm/llvm-project/pull/192848

>From ea26dad59c01ba3b19e2c0a095bab349e31a0e2c Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Sun, 19 Apr 2026 15:52:35 +0200
Subject: [PATCH 1/3] [CIR] Implement null check for CXXNewExpr

---
 clang/include/clang/CIR/MissingFeatures.h |  3 -
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp   | 48 +++++++++--
 clang/test/CIR/CodeGen/new-null.cpp       | 97 +++++++++++++++++++++++
 3 files changed, 140 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/new-null.cpp

diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 92d052de4e8db..f52648b7c5476 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -114,9 +114,6 @@ struct MissingFeatures {
   static bool opCallChain() { return false; }
   static bool opCallExceptionAttr() { return false; }
 
-  // CXXNewExpr
-  static bool exprNewNullCheck() { return false; }
-
   // FnInfoOpts -- This is used to track whether calls are chain calls or
   // instance methods. Classic codegen uses chain call to track and extra free
   // register for x86 and uses instance method as a condition for a thunk
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 30a833564ec2f..bb4f5291c2145 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1472,9 +1472,22 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
   // interesting initializer will be running sanitizers on the initialization.
   bool nullCheck = e->shouldNullCheckAllocation() &&
                    (!allocType.isPODType(getContext()) || e->hasInitializer());
-  assert(!cir::MissingFeatures::exprNewNullCheck());
-  if (nullCheck)
-    cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: null check");
+
+  mlir::Location loc = getLoc(e->getSourceRange());
+  cir::IfOp nullCheckOp;
+  cir::YieldOp nullCheckYield;
+  if (nullCheck) {
+    mlir::Value isNotNull = builder.createPtrIsNotNull(allocation.getPointer());
+    nullCheckOp = cir::IfOp::create(builder, loc, isNotNull,
+                                    /*withElseRegion=*/false,
+                                    /*thenBuilder=*/
+                                    [&](mlir::OpBuilder &, mlir::Location loc) {
+                                      nullCheckYield = builder.createYield(loc);
+                                    });
+
+    // Emit initialization inside the if-op's then region.
+    builder.setInsertionPoint(nullCheckYield);
+  }
 
   // If there's an operator delete, enter a cleanup to call it if an
   // exception is thrown. If we do this, we'll be creating the result pointer
@@ -1548,9 +1561,34 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
     result = result.withPointer(loadResult.getResult());
   }
 
-  assert(!cir::MissingFeatures::exprNewNullCheck());
+  mlir::Value resultValue = result.getPointer();
+
+  if (nullCheck) {
+    // Restore insertion point to after the IfOp.
+    builder.setInsertionPointAfter(nullCheckOp);
+
+    mlir::Type resultTy = resultValue.getType();
+
+    // If we needed a NewDeleteCleanup, allocation may have been modified
+    // inside the cir.if (e.g. by cookie adjustment). Use the result stored
+    // in the alloca instead, since the alloca dominates this point.
+    mlir::Value trueVal;
+    if (useNewDeleteCleanup) {
+      trueVal = builder.createLoad(getLoc(e->getSourceRange()), resultPtr)
+                    .getResult();
+    } else {
+      trueVal = allocation.getPointer();
+    }
+    if (trueVal.getType() != resultTy)
+      trueVal = builder.createBitcast(trueVal, resultTy);
+    mlir::Value nullPtr =
+        builder.getNullPtr(resultTy, getLoc(e->getSourceRange())).getResult();
+    resultValue =
+        builder.createSelect(getLoc(e->getSourceRange()),
+                             nullCheckOp.getCondition(), trueVal, nullPtr);
+  }
 
-  return result.getPointer();
+  return resultValue;
 }
 
 void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD,
diff --git a/clang/test/CIR/CodeGen/new-null.cpp b/clang/test/CIR/CodeGen/new-null.cpp
new file mode 100644
index 0000000000000..50b69f5f9293f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/new-null.cpp
@@ -0,0 +1,97 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+typedef __typeof__(sizeof(int)) size_t;
+
+namespace std {
+  struct nothrow_t {};
+}
+std::nothrow_t nothrow;
+
+void *operator new(size_t, const std::nothrow_t &) throw();
+void operator delete(void *, const std::nothrow_t &) throw();
+
+struct S {
+  S();
+  ~S();
+  int a;
+};
+
+// nothrow new with non-POD type triggers null check
+S *test_nothrow_new() {
+  return new (nothrow) S;
+}
+
+// CHECK: cir.func {{.*}} @_Z16test_nothrow_newv()
+// CHECK:   %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow
+// CHECK:   %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
+// CHECK:   %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void>
+// CHECK:   cir.if %[[IS_NOT_NULL]] {
+// CHECK:     %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!rec_S>
+// CHECK:     cir.call @_ZN1SC1Ev(%[[CAST]])
+// CHECK:   }
+// CHECK:   %[[RESULT_CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!rec_S>
+// CHECK:   %[[NULL_S:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_S>
+// CHECK:   %[[RESULT:.*]] = cir.select if %[[IS_NOT_NULL]] then %[[RESULT_CAST]] else %[[NULL_S]]
+
+// LLVM: define {{.*}} ptr @_Z16test_nothrow_newv()
+// LLVM:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
+// LLVM:   %[[CMP:.*]] = icmp ne ptr %[[ALLOC]], null
+// LLVM:   br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]]
+// LLVM: [[NOT_NULL]]:
+// LLVM:   call void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
+// LLVM:   br label %[[CONT]]
+// LLVM: [[CONT]]:
+// LLVM:   %[[RESULT:.*]] = select i1 %[[CMP]], ptr %[[ALLOC]], ptr null
+
+// OGCG: define {{.*}} ptr @_Z16test_nothrow_newv()
+// OGCG:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
+// OGCG:   %[[IS_NULL:.*]] = icmp eq ptr %[[ALLOC]], null
+// OGCG:   br i1 %[[IS_NULL]], label %[[CONT:.*]], label %[[NOT_NULL:.*]]
+// OGCG: [[NOT_NULL]]:
+// OGCG:   call void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
+// OGCG:   br label %[[CONT]]
+// OGCG: [[CONT]]:
+// OGCG:   phi ptr
+
+// nothrow new with POD + initializer triggers null check
+int *test_nothrow_new_init() {
+  return new (nothrow) int(42);
+}
+
+// CHECK: cir.func {{.*}} @_Z21test_nothrow_new_initv()
+// CHECK:   %[[ALLOC:.*]] = cir.call @_ZnwmRKSt9nothrow_t({{.*}}) nothrow
+// CHECK:   %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
+// CHECK:   %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void>
+// CHECK:   cir.if %[[IS_NOT_NULL]] {
+// CHECK:     %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!s32i>
+// CHECK:     %[[FORTY_TWO:.*]] = cir.const #cir.int<42> : !s32i
+// CHECK:     cir.store {{.*}} %[[FORTY_TWO]], %[[CAST]]
+// CHECK:   }
+// CHECK:   %[[RESULT_CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!s32i>
+// CHECK:   %[[NULL_I:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s32i>
+// CHECK:   %[[RESULT:.*]] = cir.select if %[[IS_NOT_NULL]] then %[[RESULT_CAST]] else %[[NULL_I]]
+
+// LLVM: define {{.*}} ptr @_Z21test_nothrow_new_initv()
+// LLVM:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
+// LLVM:   %[[CMP:.*]] = icmp ne ptr %[[ALLOC]], null
+// LLVM:   br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]]
+// LLVM: [[NOT_NULL]]:
+// LLVM:   store i32 42, ptr %[[ALLOC]], align 4
+// LLVM:   br label %[[CONT]]
+// LLVM: [[CONT]]:
+// LLVM:   %[[RESULT:.*]] = select i1 %[[CMP]], ptr %[[ALLOC]], ptr null
+
+// OGCG: define {{.*}} ptr @_Z21test_nothrow_new_initv()
+// OGCG:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
+// OGCG:   %[[IS_NULL:.*]] = icmp eq ptr %[[ALLOC]], null
+// OGCG:   br i1 %[[IS_NULL]], label %[[CONT:.*]], label %[[NOT_NULL:.*]]
+// OGCG: [[NOT_NULL]]:
+// OGCG:   store i32 42, ptr %[[ALLOC]], align 4
+// OGCG:   br label %[[CONT]]
+// OGCG: [[CONT]]:
+// OGCG:   phi ptr

>From 772968a627c757e488de46ba5ff94fa077483038 Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Tue, 21 Apr 2026 23:08:42 +0200
Subject: [PATCH 2/3] [CIR][NFC] Factor new-expression init emission into
 emitNewExprInit

---
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 152 +++++++++++++-----------
 1 file changed, 81 insertions(+), 71 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index bb4f5291c2145..e22180095b734 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1366,6 +1366,84 @@ void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
   }
 }
 
+/// Emit the initialization of a new-expression result, including setting up
+/// a delete cleanup if needed, initializing array cookies, and calling the
+/// initializer. This is factored out to avoid manual insertion point
+/// manipulation at the call site.
+///
+/// \returns a tuple of (result pointer, resultPtr alloca, useNewDeleteCleanup).
+static std::tuple<Address, Address, bool>
+emitNewExprInit(CIRGenFunction &cgf, const CXXNewExpr *e, QualType allocType,
+                Address allocation, mlir::Value allocSize,
+                mlir::Value allocSizeWithoutCookie, CharUnits allocAlign,
+                const CallArgList &allocatorArgs, mlir::Value numElements,
+                const FunctionDecl *allocator) {
+  auto &builder = cgf.getBuilder();
+
+  bool useNewDeleteCleanup =
+      e->getOperatorDelete() &&
+      !e->getOperatorDelete()->isReservedGlobalPlacementOperator();
+  EHScopeStack::stable_iterator operatorDeleteCleanup;
+  mlir::Operation *cleanupDominator = nullptr;
+  if (useNewDeleteCleanup) {
+    assert(!cir::MissingFeatures::typeAwareAllocation());
+    enterNewDeleteCleanup(cgf, e, allocation, allocSize, allocAlign,
+                          allocatorArgs);
+    operatorDeleteCleanup = cgf.ehStack.stable_begin();
+    cleanupDominator =
+        cir::UnreachableOp::create(builder, cgf.getLoc(e->getSourceRange()))
+            .getOperation();
+  }
+
+  if (allocSize != allocSizeWithoutCookie) {
+    assert(e->isArray());
+    allocation = cgf.cgm.getCXXABI().initializeArrayCookie(
+        cgf, allocation, numElements, e, allocType);
+  }
+
+  mlir::Type elementTy;
+  if (e->isArray())
+    elementTy = cgf.convertTypeForMem(e->getAllocatedType());
+  else
+    elementTy = cgf.convertTypeForMem(allocType);
+
+  Address result = builder.createElementBitCast(cgf.getLoc(e->getSourceRange()),
+                                                allocation, elementTy);
+
+  Address resultPtr = Address::invalid();
+  if (useNewDeleteCleanup) {
+    resultPtr = cgf.createTempAlloca(
+        builder.getPointerTo(elementTy), result.getAlignment(),
+        cgf.getLoc(e->getSourceRange()), "__new_result");
+    builder.createStore(cgf.getLoc(e->getSourceRange()), result.getPointer(),
+                        resultPtr);
+  }
+
+  // Passing pointer through launder.invariant.group to avoid propagation of
+  // vptrs information which may be included in previous type.
+  if (cgf.cgm.getCodeGenOpts().StrictVTablePointers &&
+      allocator->isReservedGlobalPlacementOperator())
+    cgf.cgm.errorNYI(e->getSourceRange(),
+                     "emitCXXNewExpr: strict vtable pointers");
+
+  assert(!cir::MissingFeatures::sanitizers());
+
+  emitNewInitializer(cgf, e, allocType, elementTy, result, numElements,
+                     allocSizeWithoutCookie);
+
+  if (useNewDeleteCleanup) {
+    assert(operatorDeleteCleanup.isValid());
+    assert(resultPtr.isValid());
+    cgf.deactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator);
+    cleanupDominator->erase();
+    cir::LoadOp loadResult =
+        builder.createLoad(cgf.getLoc(e->getSourceRange()), resultPtr);
+    result = result.withPointer(loadResult.getResult());
+  }
+
+  return {result, resultPtr, useNewDeleteCleanup};
+}
+
 mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
   // The element type being allocated.
   QualType allocType = getContext().getBaseElementType(e->getAllocatedType());
@@ -1489,77 +1567,9 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
     builder.setInsertionPoint(nullCheckYield);
   }
 
-  // If there's an operator delete, enter a cleanup to call it if an
-  // exception is thrown. If we do this, we'll be creating the result pointer
-  // inside a cleanup scope, either with a bitcast or an offset based on the
-  // array cookie size. However, we need to return that pointer from outside
-  // the cleanup scope, so we need to store it in a temporary variable.
-  bool useNewDeleteCleanup =
-      e->getOperatorDelete() &&
-      !e->getOperatorDelete()->isReservedGlobalPlacementOperator();
-  EHScopeStack::stable_iterator operatorDeleteCleanup;
-  mlir::Operation *cleanupDominator = nullptr;
-  if (useNewDeleteCleanup) {
-    assert(!cir::MissingFeatures::typeAwareAllocation());
-    enterNewDeleteCleanup(*this, e, allocation, allocSize, allocAlign,
-                          allocatorArgs);
-    operatorDeleteCleanup = ehStack.stable_begin();
-    cleanupDominator =
-        cir::UnreachableOp::create(builder, getLoc(e->getSourceRange()))
-            .getOperation();
-  }
-
-  if (allocSize != allocSizeWithoutCookie) {
-    assert(e->isArray());
-    allocation = cgm.getCXXABI().initializeArrayCookie(
-        *this, allocation, numElements, e, allocType);
-  }
-
-  mlir::Type elementTy;
-  if (e->isArray()) {
-    // For array new, use the allocated type to handle multidimensional arrays
-    // correctly
-    elementTy = convertTypeForMem(e->getAllocatedType());
-  } else {
-    elementTy = convertTypeForMem(allocType);
-  }
-  Address result = builder.createElementBitCast(getLoc(e->getSourceRange()),
-                                                allocation, elementTy);
-
-  // If we're inside a new delete cleanup, store the result pointer.
-  Address resultPtr = Address::invalid();
-  if (useNewDeleteCleanup) {
-    resultPtr =
-        createTempAlloca(builder.getPointerTo(elementTy), result.getAlignment(),
-                         getLoc(e->getSourceRange()), "__new_result");
-    builder.createStore(getLoc(e->getSourceRange()), result.getPointer(),
-                        resultPtr);
-  }
-
-  // Passing pointer through launder.invariant.group to avoid propagation of
-  // vptrs information which may be included in previous type.
-  // To not break LTO with different optimizations levels, we do it regardless
-  // of optimization level.
-  if (cgm.getCodeGenOpts().StrictVTablePointers &&
-      allocator->isReservedGlobalPlacementOperator())
-    cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: strict vtable pointers");
-
-  assert(!cir::MissingFeatures::sanitizers());
-
-  emitNewInitializer(*this, e, allocType, elementTy, result, numElements,
-                     allocSizeWithoutCookie);
-
-  // Deactivate the 'operator delete' cleanup if we finished
-  // initialization.
-  if (useNewDeleteCleanup) {
-    assert(operatorDeleteCleanup.isValid());
-    assert(resultPtr.isValid());
-    deactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator);
-    cleanupDominator->erase();
-    cir::LoadOp loadResult =
-        builder.createLoad(getLoc(e->getSourceRange()), resultPtr);
-    result = result.withPointer(loadResult.getResult());
-  }
+  auto [result, resultPtr, useNewDeleteCleanup] = emitNewExprInit(
+      *this, e, allocType, allocation, allocSize, allocSizeWithoutCookie,
+      allocAlign, allocatorArgs, numElements, allocator);
 
   mlir::Value resultValue = result.getPointer();
 

>From db351da24238c2f39f4a6ed230f7a2e6b678a0fb Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Thu, 23 Apr 2026 14:48:07 +0200
Subject: [PATCH 3/3] [CIR] Restructure emitNewExprInit and enable exceptions
 in test

Narrow emitNewExprInit to just bitcast through emitNewInitializer.
Use a lambda and IfOp thenBuilder instead of setInsertionPoint.
Enable -fexceptions -fcxx-exceptions in new-null.cpp.
---
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 151 ++++++++++++------------
 clang/test/CIR/CodeGen/new-null.cpp     |  57 +++++----
 2 files changed, 115 insertions(+), 93 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index e22180095b734..6479845066287 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -1366,58 +1366,25 @@ void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
   }
 }
 
-/// Emit the initialization of a new-expression result, including setting up
-/// a delete cleanup if needed, initializing array cookies, and calling the
-/// initializer. This is factored out to avoid manual insertion point
-/// manipulation at the call site.
-///
-/// \returns a tuple of (result pointer, resultPtr alloca, useNewDeleteCleanup).
-static std::tuple<Address, Address, bool>
-emitNewExprInit(CIRGenFunction &cgf, const CXXNewExpr *e, QualType allocType,
-                Address allocation, mlir::Value allocSize,
-                mlir::Value allocSizeWithoutCookie, CharUnits allocAlign,
-                const CallArgList &allocatorArgs, mlir::Value numElements,
-                const FunctionDecl *allocator) {
+/// Emit the bitcast and initialization for a new-expression.
+/// This is factored out so it can be called from the IfOp's thenBuilder
+/// when a null check is needed, or directly when it is not.
+static Address emitNewExprInit(CIRGenFunction &cgf, const CXXNewExpr *e,
+                               QualType allocType, mlir::Type elementTy,
+                               Address allocation, mlir::Value numElements,
+                               mlir::Value allocSizeWithoutCookie,
+                               const FunctionDecl *allocator,
+                               Address resultPtr) {
   auto &builder = cgf.getBuilder();
 
-  bool useNewDeleteCleanup =
-      e->getOperatorDelete() &&
-      !e->getOperatorDelete()->isReservedGlobalPlacementOperator();
-  EHScopeStack::stable_iterator operatorDeleteCleanup;
-  mlir::Operation *cleanupDominator = nullptr;
-  if (useNewDeleteCleanup) {
-    assert(!cir::MissingFeatures::typeAwareAllocation());
-    enterNewDeleteCleanup(cgf, e, allocation, allocSize, allocAlign,
-                          allocatorArgs);
-    operatorDeleteCleanup = cgf.ehStack.stable_begin();
-    cleanupDominator =
-        cir::UnreachableOp::create(builder, cgf.getLoc(e->getSourceRange()))
-            .getOperation();
-  }
-
-  if (allocSize != allocSizeWithoutCookie) {
-    assert(e->isArray());
-    allocation = cgf.cgm.getCXXABI().initializeArrayCookie(
-        cgf, allocation, numElements, e, allocType);
-  }
-
-  mlir::Type elementTy;
-  if (e->isArray())
-    elementTy = cgf.convertTypeForMem(e->getAllocatedType());
-  else
-    elementTy = cgf.convertTypeForMem(allocType);
-
   Address result = builder.createElementBitCast(cgf.getLoc(e->getSourceRange()),
                                                 allocation, elementTy);
 
-  Address resultPtr = Address::invalid();
-  if (useNewDeleteCleanup) {
-    resultPtr = cgf.createTempAlloca(
-        builder.getPointerTo(elementTy), result.getAlignment(),
-        cgf.getLoc(e->getSourceRange()), "__new_result");
+  // Store the result pointer before initialization so that it is available
+  // to the cleanup if the initializer throws.
+  if (resultPtr.isValid())
     builder.createStore(cgf.getLoc(e->getSourceRange()), result.getPointer(),
                         resultPtr);
-  }
 
   // Passing pointer through launder.invariant.group to avoid propagation of
   // vptrs information which may be included in previous type.
@@ -1431,17 +1398,7 @@ emitNewExprInit(CIRGenFunction &cgf, const CXXNewExpr *e, QualType allocType,
   emitNewInitializer(cgf, e, allocType, elementTy, result, numElements,
                      allocSizeWithoutCookie);
 
-  if (useNewDeleteCleanup) {
-    assert(operatorDeleteCleanup.isValid());
-    assert(resultPtr.isValid());
-    cgf.deactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator);
-    cleanupDominator->erase();
-    cir::LoadOp loadResult =
-        builder.createLoad(cgf.getLoc(e->getSourceRange()), resultPtr);
-    result = result.withPointer(loadResult.getResult());
-  }
-
-  return {result, resultPtr, useNewDeleteCleanup};
+  return result;
 }
 
 mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
@@ -1552,31 +1509,79 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
                    (!allocType.isPODType(getContext()) || e->hasInitializer());
 
   mlir::Location loc = getLoc(e->getSourceRange());
+
+  bool useNewDeleteCleanup =
+      e->getOperatorDelete() &&
+      !e->getOperatorDelete()->isReservedGlobalPlacementOperator();
+
+  mlir::Type elementTy;
+  if (e->isArray())
+    elementTy = convertTypeForMem(e->getAllocatedType());
+  else
+    elementTy = convertTypeForMem(allocType);
+
+  // Lambda that emits the init sequence: cleanup setup, cookie init,
+  // bitcast + initializer (via the helper), and cleanup deactivation.
+  Address result = Address::invalid();
+  Address resultPtr = Address::invalid();
+  auto emitInit = [&]() {
+    EHScopeStack::stable_iterator operatorDeleteCleanup;
+    mlir::Operation *cleanupDominator = nullptr;
+    if (useNewDeleteCleanup) {
+      assert(!cir::MissingFeatures::typeAwareAllocation());
+      enterNewDeleteCleanup(*this, e, allocation, allocSize, allocAlign,
+                            allocatorArgs);
+      operatorDeleteCleanup = ehStack.stable_begin();
+      cleanupDominator =
+          cir::UnreachableOp::create(builder, getLoc(e->getSourceRange()))
+              .getOperation();
+    }
+
+    // Create __new_result alloca before emitNewExprInit so it appears
+    // before any temporaries created during initialization.
+    if (useNewDeleteCleanup) {
+      resultPtr = createTempAlloca(
+          builder.getPointerTo(elementTy), allocation.getAlignment(),
+          getLoc(e->getSourceRange()), "__new_result");
+    }
+
+    if (allocSize != allocSizeWithoutCookie) {
+      assert(e->isArray());
+      allocation = cgm.getCXXABI().initializeArrayCookie(
+          *this, allocation, numElements, e, allocType);
+    }
+
+    result = emitNewExprInit(*this, e, allocType, elementTy, allocation,
+                             numElements, allocSizeWithoutCookie, allocator,
+                             resultPtr);
+
+    if (useNewDeleteCleanup) {
+      deactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator);
+      cleanupDominator->erase();
+      cir::LoadOp loadResult =
+          builder.createLoad(getLoc(e->getSourceRange()), resultPtr);
+      result = result.withPointer(loadResult.getResult());
+    }
+  };
+
   cir::IfOp nullCheckOp;
-  cir::YieldOp nullCheckYield;
   if (nullCheck) {
     mlir::Value isNotNull = builder.createPtrIsNotNull(allocation.getPointer());
-    nullCheckOp = cir::IfOp::create(builder, loc, isNotNull,
-                                    /*withElseRegion=*/false,
-                                    /*thenBuilder=*/
-                                    [&](mlir::OpBuilder &, mlir::Location loc) {
-                                      nullCheckYield = builder.createYield(loc);
-                                    });
-
-    // Emit initialization inside the if-op's then region.
-    builder.setInsertionPoint(nullCheckYield);
+    nullCheckOp = cir::IfOp::create(
+        builder, loc, isNotNull,
+        /*withElseRegion=*/false,
+        /*thenBuilder=*/
+        [&](mlir::OpBuilder &, mlir::Location loc) {
+          emitInit();
+          builder.createYield(loc);
+        });
+  } else {
+    emitInit();
   }
 
-  auto [result, resultPtr, useNewDeleteCleanup] = emitNewExprInit(
-      *this, e, allocType, allocation, allocSize, allocSizeWithoutCookie,
-      allocAlign, allocatorArgs, numElements, allocator);
-
   mlir::Value resultValue = result.getPointer();
 
   if (nullCheck) {
-    // Restore insertion point to after the IfOp.
-    builder.setInsertionPointAfter(nullCheckOp);
-
     mlir::Type resultTy = resultValue.getType();
 
     // If we needed a NewDeleteCleanup, allocation may have been modified
diff --git a/clang/test/CIR/CodeGen/new-null.cpp b/clang/test/CIR/CodeGen/new-null.cpp
index 50b69f5f9293f..1c4760f843c46 100644
--- a/clang/test/CIR/CodeGen/new-null.cpp
+++ b/clang/test/CIR/CodeGen/new-null.cpp
@@ -1,8 +1,8 @@
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --input-file=%t.cir %s
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -fclangir -emit-llvm %s -o %t-cir.ll
 // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm %s -o %t.ll
 // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
 
 typedef __typeof__(sizeof(int)) size_t;
@@ -31,32 +31,47 @@ S *test_nothrow_new() {
 // CHECK:   %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
 // CHECK:   %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void>
 // CHECK:   cir.if %[[IS_NOT_NULL]] {
-// CHECK:     %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!rec_S>
-// CHECK:     cir.call @_ZN1SC1Ev(%[[CAST]])
+// CHECK:     cir.cleanup.scope {
+// CHECK:       %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!rec_S>
+// CHECK:       cir.call @_ZN1SC1Ev(%[[CAST]])
+// CHECK:     } cleanup eh {
+// CHECK:       cir.call @_ZdlPvRKSt9nothrow_t(%[[ALLOC]], {{.*}}) nothrow
+// CHECK:     }
 // CHECK:   }
-// CHECK:   %[[RESULT_CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!rec_S>
 // CHECK:   %[[NULL_S:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_S>
-// CHECK:   %[[RESULT:.*]] = cir.select if %[[IS_NOT_NULL]] then %[[RESULT_CAST]] else %[[NULL_S]]
+// CHECK:   cir.select if %[[IS_NOT_NULL]] then {{.*}} else %[[NULL_S]]
 
-// LLVM: define {{.*}} ptr @_Z16test_nothrow_newv()
+// LLVM: define {{.*}} ptr @_Z16test_nothrow_newv() {{.*}}personality ptr @__gxx_personality_v0
 // LLVM:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
 // LLVM:   %[[CMP:.*]] = icmp ne ptr %[[ALLOC]], null
 // LLVM:   br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]]
 // LLVM: [[NOT_NULL]]:
-// LLVM:   call void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
-// LLVM:   br label %[[CONT]]
+// LLVM:   invoke void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
+// LLVM:     to label {{.*}} unwind label %[[LPAD:.*]]
+// LLVM: [[LPAD]]:
+// LLVM:   landingpad { ptr, i32 }
+// LLVM:     cleanup
+// LLVM:   call void @_ZdlPvRKSt9nothrow_t({{.*}} %[[ALLOC]], {{.*}})
+// LLVM:   resume
 // LLVM: [[CONT]]:
-// LLVM:   %[[RESULT:.*]] = select i1 %[[CMP]], ptr %[[ALLOC]], ptr null
+// LLVM:   select i1 %[[CMP]], ptr {{.*}}, ptr null
 
-// OGCG: define {{.*}} ptr @_Z16test_nothrow_newv()
+// OGCG: define {{.*}} ptr @_Z16test_nothrow_newv() {{.*}}personality ptr @__gxx_personality_v0
 // OGCG:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
 // OGCG:   %[[IS_NULL:.*]] = icmp eq ptr %[[ALLOC]], null
 // OGCG:   br i1 %[[IS_NULL]], label %[[CONT:.*]], label %[[NOT_NULL:.*]]
 // OGCG: [[NOT_NULL]]:
-// OGCG:   call void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
+// OGCG:   invoke void @_ZN1SC1Ev({{.*}} %[[ALLOC]])
+// OGCG:     to label %[[OK:.*]] unwind label %[[LPAD:.*]]
+// OGCG: [[OK]]:
 // OGCG:   br label %[[CONT]]
 // OGCG: [[CONT]]:
 // OGCG:   phi ptr
+// OGCG: [[LPAD]]:
+// OGCG:   landingpad { ptr, i32 }
+// OGCG:     cleanup
+// OGCG:   call void @_ZdlPvRKSt9nothrow_t({{.*}} %[[ALLOC]], {{.*}})
+// OGCG:   resume
 
 // nothrow new with POD + initializer triggers null check
 int *test_nothrow_new_init() {
@@ -68,13 +83,16 @@ int *test_nothrow_new_init() {
 // CHECK:   %[[NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
 // CHECK:   %[[IS_NOT_NULL:.*]] = cir.cmp ne %[[ALLOC]], %[[NULL]] : !cir.ptr<!void>
 // CHECK:   cir.if %[[IS_NOT_NULL]] {
-// CHECK:     %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!s32i>
-// CHECK:     %[[FORTY_TWO:.*]] = cir.const #cir.int<42> : !s32i
-// CHECK:     cir.store {{.*}} %[[FORTY_TWO]], %[[CAST]]
+// CHECK:     cir.cleanup.scope {
+// CHECK:       %[[CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!s32i>
+// CHECK:       %[[FORTY_TWO:.*]] = cir.const #cir.int<42> : !s32i
+// CHECK:       cir.store {{.*}} %[[FORTY_TWO]], %[[CAST]]
+// CHECK:     } cleanup eh {
+// CHECK:       cir.call @_ZdlPvRKSt9nothrow_t(%[[ALLOC]], {{.*}}) nothrow
+// CHECK:     }
 // CHECK:   }
-// CHECK:   %[[RESULT_CAST:.*]] = cir.cast bitcast %[[ALLOC]] : !cir.ptr<!void> -> !cir.ptr<!s32i>
 // CHECK:   %[[NULL_I:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s32i>
-// CHECK:   %[[RESULT:.*]] = cir.select if %[[IS_NOT_NULL]] then %[[RESULT_CAST]] else %[[NULL_I]]
+// CHECK:   cir.select if %[[IS_NOT_NULL]] then {{.*}} else %[[NULL_I]]
 
 // LLVM: define {{.*}} ptr @_Z21test_nothrow_new_initv()
 // LLVM:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})
@@ -82,9 +100,8 @@ int *test_nothrow_new_init() {
 // LLVM:   br i1 %[[CMP]], label %[[NOT_NULL:.*]], label %[[CONT:.*]]
 // LLVM: [[NOT_NULL]]:
 // LLVM:   store i32 42, ptr %[[ALLOC]], align 4
-// LLVM:   br label %[[CONT]]
 // LLVM: [[CONT]]:
-// LLVM:   %[[RESULT:.*]] = select i1 %[[CMP]], ptr %[[ALLOC]], ptr null
+// LLVM:   select i1 %[[CMP]], ptr {{.*}}, ptr null
 
 // OGCG: define {{.*}} ptr @_Z21test_nothrow_new_initv()
 // OGCG:   %[[ALLOC:.*]] = call {{.*}} ptr @_ZnwmRKSt9nothrow_t(i64 noundef 4, {{.*}})



More information about the cfe-commits mailing list