Skip to content

[CIR] Generalize cxx alloc new size handling#187790

Merged
andykaylor merged 3 commits intollvm:mainfrom
andykaylor:cir-new-alloc-size
Mar 23, 2026
Merged

[CIR] Generalize cxx alloc new size handling#187790
andykaylor merged 3 commits intollvm:mainfrom
andykaylor:cir-new-alloc-size

Conversation

@andykaylor
Copy link
Copy Markdown
Contributor

The non-constant size handling in emitCXXNewAllocSize was making the incorrect assumption that the default behavior of the size value being explicitly cast to size_t would be the only behavior we'd see. This is actually only true with C++14 and later. To properly handle earlier standards, we need the more robust checking that classic codegen does in the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high

The non-constant size handling in `emitCXXNewAllocSize` was making the
incorrect assumption that the default behavior of the size value being
explicitly cast to size_t would be the only behavior we'd see. This is
actually only true with C++14 and later. To properly handle earlier
standards, we need the more robust checking that classic codegen does
in the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Mar 20, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 20, 2026

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

The non-constant size handling in emitCXXNewAllocSize was making the incorrect assumption that the default behavior of the size value being explicitly cast to size_t would be the only behavior we'd see. This is actually only true with C++14 and later. To properly handle earlier standards, we need the more robust checking that classic codegen does in the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high


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

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+78-20)
  • (added) clang/test/CIR/CodeGen/new-array-size-conv.cpp (+86)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 03298faf79ce3..30a833564ec2f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -534,39 +534,97 @@ static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
     // Create a value for the variable number of elements
     numElements = cgf.emitScalarExpr(*e->getArraySize());
     auto numElementsType = mlir::cast<cir::IntType>(numElements.getType());
-    [[maybe_unused]] unsigned numElementsWidth = numElementsType.getWidth();
-
-    // We might need check for overflow.
-
-    mlir::Value hasOverflow;
-    // Classic codegen checks for the size variable being signed, having a
-    // smaller width than size_t, and having a larger width than size_t.
-    // However, the AST implicitly casts the size variable to size_t so none of
-    // these conditions will ever be met.
-    assert(
-        !(*e->getArraySize())->getType()->isSignedIntegerOrEnumerationType() &&
-        (numElementsWidth == sizeWidth) &&
-        (numElements.getType() == cgf.sizeTy) &&
-        "Expected array size to be implicitly cast to size_t!");
-
-    // There are up to three conditions we need to test for:
-    // 1) if minElements > 0, we need to check whether numElements is smaller
+    unsigned numElementsWidth = numElementsType.getWidth();
+
+    // The number of elements can have an arbitrary integer type;
+    // essentially, we need to multiply it by a constant factor, add a
+    // cookie size, and verify that the result is representable as a
+    // size_t.  That's just a gloss, though, and it's wrong in one
+    // important way: if the count is negative, it's an error even if
+    // the cookie size would bring the total size >= 0.
+    bool isSigned =
+        (*e->getArraySize())->getType()->isSignedIntegerOrEnumerationType();
+
+    // There are up to five conditions we need to test for:
+    // 1) if isSigned, we need to check whether numElements is negative;
+    // 2) if numElementsWidth > sizeWidth, we need to check whether
+    //    numElements is larger than something representable in size_t;
+    // 3) if minElements > 0, we need to check whether numElements is smaller
     //    than that.
-    // 2) we need to compute
+    // 4) we need to compute
     //      sizeWithoutCookie := numElements * typeSizeMultiplier
     //    and check whether it overflows; and
-    // 3) if we need a cookie, we need to compute
+    // 5) if we need a cookie, we need to compute
     //      size := sizeWithoutCookie + cookieSize
     //    and check whether it overflows.
 
+    mlir::Value hasOverflow;
+
+    // If numElementsWidth > sizeWidth, then one way or another, we're
+    // going to have to do a comparison for (2), and this happens to
+    // take care of (1), too.
+    if (numElementsWidth > sizeWidth) {
+      llvm::APInt threshold =
+          llvm::APInt::getOneBitSet(numElementsWidth, sizeWidth);
+
+      // Use an unsigned comparison regardless of the sign of numElements.
+      mlir::Value unsignedNumElements = numElements;
+      if (isSigned)
+        unsignedNumElements = cgf.getBuilder().createIntCast(
+            numElements, cgf.getBuilder().getUIntNTy(numElementsWidth));
+
+      mlir::Value thresholdV =
+          cgf.getBuilder().getConstInt(loc, threshold, /*isUnsigned=*/true);
+      hasOverflow = cgf.getBuilder().createCompare(
+          loc, cir::CmpOpKind::ge, unsignedNumElements, thresholdV);
+      numElements = cgf.getBuilder().createIntCast(
+          unsignedNumElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+
+      // Otherwise, if we're signed, we want to sext up to size_t.
+    } else if (isSigned) {
+      if (numElementsWidth < sizeWidth)
+        numElements = cgf.getBuilder().createIntCast(
+            numElements, cgf.getBuilder().getSIntNTy(sizeWidth));
+
+      // If there's a non-1 type size multiplier, then we can do the
+      // signedness check at the same time as we do the multiply
+      // because a negative number times anything will cause an
+      // unsigned overflow.  Otherwise, we have to do it here. But at
+      // least in this case, we can subsume the >= minElements check.
+      if (typeSizeMultiplier == 1)
+        hasOverflow = cgf.getBuilder().createCompare(
+            loc, cir::CmpOpKind::lt, numElements,
+            cgf.getBuilder().getConstInt(loc, numElements.getType(),
+                                         minElements));
+
+      numElements = cgf.getBuilder().createIntCast(
+          numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+
+      // Otherwise, zext up to size_t if necessary.
+    } else if (numElementsWidth < sizeWidth) {
+      numElements = cgf.getBuilder().createIntCast(
+          numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+    }
+
+    assert(numElements.getType() == cgf.sizeTy);
+
     if (minElements) {
       // Don't allow allocation of fewer elements than we have initializers.
       if (!hasOverflow) {
-        // FIXME: Avoid creating this twice. It may happen above.
         mlir::Value minElementsV = cgf.getBuilder().getConstInt(
             loc, llvm::APInt(sizeWidth, minElements));
         hasOverflow = cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt,
                                                      numElements, minElementsV);
+      } else if (numElementsWidth > sizeWidth) {
+        // The other existing overflow subsumes this check.
+        // We do an unsigned comparison, since any signed value < -1 is
+        // taken care of either above or below.
+        mlir::Value minElementsV = cgf.getBuilder().getConstInt(
+            loc, llvm::APInt(sizeWidth, minElements));
+        hasOverflow = cgf.getBuilder().createOr(
+            loc, hasOverflow,
+            cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt, numElements,
+                                           minElementsV));
       }
     }
 
diff --git a/clang/test/CIR/CodeGen/new-array-size-conv.cpp b/clang/test/CIR/CodeGen/new-array-size-conv.cpp
new file mode 100644
index 0000000000000..d983d75c1893f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/new-array-size-conv.cpp
@@ -0,0 +1,86 @@
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++11 -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++11 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+// Tests for array new expressions where the array size is not implicitly
+// converted to size_t by Sema (pre-C++14 behavior). The CIR codegen must
+// handle the signed/width conversion itself.
+
+typedef __typeof__(sizeof(int)) size_t;
+
+// Sized non-allocating (placement) new.
+void *operator new[](size_t, void *p) noexcept { return p; }
+
+struct S {
+  int x;
+  S();
+};
+
+// Signed int array size with multi-byte element (typeSizeMultiplier != 1).
+// The sign extension is done and the multiply overflow catches negative values.
+void t_new_signed_size(int n) {
+  auto p = new double[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z17t_new_signed_sizei
+// CIR:    %[[N:.*]] = cir.load{{.*}} %[[ARG_ALLOCA:.*]]
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+// CIR:    %[[ELEMENT_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:    %[[RESULT:.*]], %[[OVERFLOW:.*]] = cir.mul.overflow %[[N_SIZE_T]], %[[ELEMENT_SIZE]] : !u64i -> !u64i
+// CIR:    %[[ALL_ONES:.*]] = cir.const #cir.int<18446744073709551615> : !u64i
+// CIR:    %[[ALLOC_SIZE:.*]] = cir.select if %[[OVERFLOW]] then %[[ALL_ONES]] else %[[RESULT]]
+
+// LLVM-LABEL: define{{.*}} void @_Z17t_new_signed_sizei
+// LLVM:    %[[N:.*]] = load i32, ptr %{{.+}}
+// LLVM:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// LLVM:    %[[MUL_OVERFLOW:.*]] = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %[[N_SIZE_T]], i64 8)
+
+// OGCG-LABEL: define{{.*}} void @_Z17t_new_signed_sizei
+// OGCG:    %[[N:.*]] = load i32, ptr %{{.+}}
+// OGCG:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// OGCG:    %[[MUL_OVERFLOW:.*]] = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %[[N_SIZE_T]], i64 8)
+
+// Signed int array size with single-byte element (typeSizeMultiplier == 1).
+// A signed comparison catches negative values directly.
+void t_new_signed_size_char(int n) {
+  auto p = new char[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22t_new_signed_size_chari
+// CIR:    %[[N:.*]] = cir.load{{.*}} %[[ARG_ALLOCA:.*]]
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[ZERO:.*]] = cir.const #cir.int<0> : !s64i
+// CIR:    %[[IS_NEG:.*]] = cir.cmp lt %[[N_SEXT]], %[[ZERO]] : !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+// CIR:    %[[ALL_ONES:.*]] = cir.const #cir.int<18446744073709551615> : !u64i
+// CIR:    %[[ALLOC_SIZE:.*]] = cir.select if %[[IS_NEG]] then %[[ALL_ONES]] else %[[N_SIZE_T]]
+
+// LLVM-LABEL: define{{.*}} void @_Z22t_new_signed_size_chari
+// LLVM:    %[[N:.*]] = load i32, ptr %{{.+}}
+// LLVM:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// LLVM:    %[[IS_NEG:.*]] = icmp slt i64 %[[N_SIZE_T]], 0
+
+// OGCG-LABEL: define{{.*}} void @_Z22t_new_signed_size_chari
+// OGCG:    %[[N:.*]] = load i32, ptr %{{.+}}
+// OGCG:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// OGCG:    %[[IS_NEG:.*]] = icmp slt i64 %[[N_SIZE_T]], 0
+
+// Placement new with signed int array size.
+void t_placement_new_signed(void *ptr, int n) {
+  S *result = new(ptr) S[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22t_placement_new_signedPvi
+// CIR:    %[[N:.*]] = cir.load{{.*}}
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+
+// LLVM-LABEL: define{{.*}} void @_Z22t_placement_new_signedPvi
+// LLVM:    sext i32 %{{.*}} to i64
+
+// OGCG-LABEL: define{{.*}} void @_Z22t_placement_new_signedPvi
+// OGCG:    sext i32 %{{.*}} to i64

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 20, 2026

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

The non-constant size handling in emitCXXNewAllocSize was making the incorrect assumption that the default behavior of the size value being explicitly cast to size_t would be the only behavior we'd see. This is actually only true with C++14 and later. To properly handle earlier standards, we need the more robust checking that classic codegen does in the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high


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

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp (+78-20)
  • (added) clang/test/CIR/CodeGen/new-array-size-conv.cpp (+86)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 03298faf79ce3..30a833564ec2f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -534,39 +534,97 @@ static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
     // Create a value for the variable number of elements
     numElements = cgf.emitScalarExpr(*e->getArraySize());
     auto numElementsType = mlir::cast<cir::IntType>(numElements.getType());
-    [[maybe_unused]] unsigned numElementsWidth = numElementsType.getWidth();
-
-    // We might need check for overflow.
-
-    mlir::Value hasOverflow;
-    // Classic codegen checks for the size variable being signed, having a
-    // smaller width than size_t, and having a larger width than size_t.
-    // However, the AST implicitly casts the size variable to size_t so none of
-    // these conditions will ever be met.
-    assert(
-        !(*e->getArraySize())->getType()->isSignedIntegerOrEnumerationType() &&
-        (numElementsWidth == sizeWidth) &&
-        (numElements.getType() == cgf.sizeTy) &&
-        "Expected array size to be implicitly cast to size_t!");
-
-    // There are up to three conditions we need to test for:
-    // 1) if minElements > 0, we need to check whether numElements is smaller
+    unsigned numElementsWidth = numElementsType.getWidth();
+
+    // The number of elements can have an arbitrary integer type;
+    // essentially, we need to multiply it by a constant factor, add a
+    // cookie size, and verify that the result is representable as a
+    // size_t.  That's just a gloss, though, and it's wrong in one
+    // important way: if the count is negative, it's an error even if
+    // the cookie size would bring the total size >= 0.
+    bool isSigned =
+        (*e->getArraySize())->getType()->isSignedIntegerOrEnumerationType();
+
+    // There are up to five conditions we need to test for:
+    // 1) if isSigned, we need to check whether numElements is negative;
+    // 2) if numElementsWidth > sizeWidth, we need to check whether
+    //    numElements is larger than something representable in size_t;
+    // 3) if minElements > 0, we need to check whether numElements is smaller
     //    than that.
-    // 2) we need to compute
+    // 4) we need to compute
     //      sizeWithoutCookie := numElements * typeSizeMultiplier
     //    and check whether it overflows; and
-    // 3) if we need a cookie, we need to compute
+    // 5) if we need a cookie, we need to compute
     //      size := sizeWithoutCookie + cookieSize
     //    and check whether it overflows.
 
+    mlir::Value hasOverflow;
+
+    // If numElementsWidth > sizeWidth, then one way or another, we're
+    // going to have to do a comparison for (2), and this happens to
+    // take care of (1), too.
+    if (numElementsWidth > sizeWidth) {
+      llvm::APInt threshold =
+          llvm::APInt::getOneBitSet(numElementsWidth, sizeWidth);
+
+      // Use an unsigned comparison regardless of the sign of numElements.
+      mlir::Value unsignedNumElements = numElements;
+      if (isSigned)
+        unsignedNumElements = cgf.getBuilder().createIntCast(
+            numElements, cgf.getBuilder().getUIntNTy(numElementsWidth));
+
+      mlir::Value thresholdV =
+          cgf.getBuilder().getConstInt(loc, threshold, /*isUnsigned=*/true);
+      hasOverflow = cgf.getBuilder().createCompare(
+          loc, cir::CmpOpKind::ge, unsignedNumElements, thresholdV);
+      numElements = cgf.getBuilder().createIntCast(
+          unsignedNumElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+
+      // Otherwise, if we're signed, we want to sext up to size_t.
+    } else if (isSigned) {
+      if (numElementsWidth < sizeWidth)
+        numElements = cgf.getBuilder().createIntCast(
+            numElements, cgf.getBuilder().getSIntNTy(sizeWidth));
+
+      // If there's a non-1 type size multiplier, then we can do the
+      // signedness check at the same time as we do the multiply
+      // because a negative number times anything will cause an
+      // unsigned overflow.  Otherwise, we have to do it here. But at
+      // least in this case, we can subsume the >= minElements check.
+      if (typeSizeMultiplier == 1)
+        hasOverflow = cgf.getBuilder().createCompare(
+            loc, cir::CmpOpKind::lt, numElements,
+            cgf.getBuilder().getConstInt(loc, numElements.getType(),
+                                         minElements));
+
+      numElements = cgf.getBuilder().createIntCast(
+          numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+
+      // Otherwise, zext up to size_t if necessary.
+    } else if (numElementsWidth < sizeWidth) {
+      numElements = cgf.getBuilder().createIntCast(
+          numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
+    }
+
+    assert(numElements.getType() == cgf.sizeTy);
+
     if (minElements) {
       // Don't allow allocation of fewer elements than we have initializers.
       if (!hasOverflow) {
-        // FIXME: Avoid creating this twice. It may happen above.
         mlir::Value minElementsV = cgf.getBuilder().getConstInt(
             loc, llvm::APInt(sizeWidth, minElements));
         hasOverflow = cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt,
                                                      numElements, minElementsV);
+      } else if (numElementsWidth > sizeWidth) {
+        // The other existing overflow subsumes this check.
+        // We do an unsigned comparison, since any signed value < -1 is
+        // taken care of either above or below.
+        mlir::Value minElementsV = cgf.getBuilder().getConstInt(
+            loc, llvm::APInt(sizeWidth, minElements));
+        hasOverflow = cgf.getBuilder().createOr(
+            loc, hasOverflow,
+            cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt, numElements,
+                                           minElementsV));
       }
     }
 
diff --git a/clang/test/CIR/CodeGen/new-array-size-conv.cpp b/clang/test/CIR/CodeGen/new-array-size-conv.cpp
new file mode 100644
index 0000000000000..d983d75c1893f
--- /dev/null
+++ b/clang/test/CIR/CodeGen/new-array-size-conv.cpp
@@ -0,0 +1,86 @@
+// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -std=c++11 -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++11 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
+
+// Tests for array new expressions where the array size is not implicitly
+// converted to size_t by Sema (pre-C++14 behavior). The CIR codegen must
+// handle the signed/width conversion itself.
+
+typedef __typeof__(sizeof(int)) size_t;
+
+// Sized non-allocating (placement) new.
+void *operator new[](size_t, void *p) noexcept { return p; }
+
+struct S {
+  int x;
+  S();
+};
+
+// Signed int array size with multi-byte element (typeSizeMultiplier != 1).
+// The sign extension is done and the multiply overflow catches negative values.
+void t_new_signed_size(int n) {
+  auto p = new double[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z17t_new_signed_sizei
+// CIR:    %[[N:.*]] = cir.load{{.*}} %[[ARG_ALLOCA:.*]]
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+// CIR:    %[[ELEMENT_SIZE:.*]] = cir.const #cir.int<8> : !u64i
+// CIR:    %[[RESULT:.*]], %[[OVERFLOW:.*]] = cir.mul.overflow %[[N_SIZE_T]], %[[ELEMENT_SIZE]] : !u64i -> !u64i
+// CIR:    %[[ALL_ONES:.*]] = cir.const #cir.int<18446744073709551615> : !u64i
+// CIR:    %[[ALLOC_SIZE:.*]] = cir.select if %[[OVERFLOW]] then %[[ALL_ONES]] else %[[RESULT]]
+
+// LLVM-LABEL: define{{.*}} void @_Z17t_new_signed_sizei
+// LLVM:    %[[N:.*]] = load i32, ptr %{{.+}}
+// LLVM:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// LLVM:    %[[MUL_OVERFLOW:.*]] = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %[[N_SIZE_T]], i64 8)
+
+// OGCG-LABEL: define{{.*}} void @_Z17t_new_signed_sizei
+// OGCG:    %[[N:.*]] = load i32, ptr %{{.+}}
+// OGCG:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// OGCG:    %[[MUL_OVERFLOW:.*]] = call { i64, i1 } @llvm.umul.with.overflow.i64(i64 %[[N_SIZE_T]], i64 8)
+
+// Signed int array size with single-byte element (typeSizeMultiplier == 1).
+// A signed comparison catches negative values directly.
+void t_new_signed_size_char(int n) {
+  auto p = new char[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22t_new_signed_size_chari
+// CIR:    %[[N:.*]] = cir.load{{.*}} %[[ARG_ALLOCA:.*]]
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[ZERO:.*]] = cir.const #cir.int<0> : !s64i
+// CIR:    %[[IS_NEG:.*]] = cir.cmp lt %[[N_SEXT]], %[[ZERO]] : !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+// CIR:    %[[ALL_ONES:.*]] = cir.const #cir.int<18446744073709551615> : !u64i
+// CIR:    %[[ALLOC_SIZE:.*]] = cir.select if %[[IS_NEG]] then %[[ALL_ONES]] else %[[N_SIZE_T]]
+
+// LLVM-LABEL: define{{.*}} void @_Z22t_new_signed_size_chari
+// LLVM:    %[[N:.*]] = load i32, ptr %{{.+}}
+// LLVM:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// LLVM:    %[[IS_NEG:.*]] = icmp slt i64 %[[N_SIZE_T]], 0
+
+// OGCG-LABEL: define{{.*}} void @_Z22t_new_signed_size_chari
+// OGCG:    %[[N:.*]] = load i32, ptr %{{.+}}
+// OGCG:    %[[N_SIZE_T:.*]] = sext i32 %[[N]] to i64
+// OGCG:    %[[IS_NEG:.*]] = icmp slt i64 %[[N_SIZE_T]], 0
+
+// Placement new with signed int array size.
+void t_placement_new_signed(void *ptr, int n) {
+  S *result = new(ptr) S[n];
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22t_placement_new_signedPvi
+// CIR:    %[[N:.*]] = cir.load{{.*}}
+// CIR:    %[[N_SEXT:.*]] = cir.cast integral %[[N]] : !s32i -> !s64i
+// CIR:    %[[N_SIZE_T:.*]] = cir.cast integral %[[N_SEXT]] : !s64i -> !u64i
+
+// LLVM-LABEL: define{{.*}} void @_Z22t_placement_new_signedPvi
+// LLVM:    sext i32 %{{.*}} to i64
+
+// OGCG-LABEL: define{{.*}} void @_Z22t_placement_new_signedPvi
+// OGCG:    sext i32 %{{.*}} to i64

Copy link
Copy Markdown
Member

@AmrDeveloper AmrDeveloper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 20, 2026

🐧 Linux x64 Test Results

  • 116265 tests passed
  • 3937 tests skipped

✅ The build succeeded and all tests passed.

This change fixes another problem with unterminated scopes. If we reach the
LexicalScope::cleanup function with an empty block at the end, we were
erasing the empty block without checking to see if the scope was properly
terminated. This change adds a terminator.

The LexicalScope::cleanup function still has a lot of residual handling
from the old exception handling and cleanup design, but that can be
dealt with in a future change.
One of the new test cases was failing when the change is applied to top of
trunk. I developed the patch in a branch which was apparently masking this
failure. I'm removing the unsupported test case for now.
@andykaylor andykaylor merged commit 729480c into llvm:main Mar 23, 2026
10 checks passed
@andykaylor andykaylor deleted the cir-new-alloc-size branch March 24, 2026 18:34
ambergorzynski pushed a commit to ambergorzynski/llvm-project that referenced this pull request Mar 27, 2026
The non-constant size handling in `emitCXXNewAllocSize` was making the
incorrect assumption that the default behavior of the size value being
explicitly cast to size_t would be the only behavior we'd see. This is
actually only true with C++14 and later. To properly handle earlier
standards, we need the more robust checking that classic codegen does in
the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high
Aadarsh-Keshri pushed a commit to Aadarsh-Keshri/llvm-project that referenced this pull request Mar 28, 2026
The non-constant size handling in `emitCXXNewAllocSize` was making the
incorrect assumption that the default behavior of the size value being
explicitly cast to size_t would be the only behavior we'd see. This is
actually only true with C++14 and later. To properly handle earlier
standards, we need the more robust checking that classic codegen does in
the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high
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.

3 participants