Skip to content

Commit

Permalink
[CIR] Add limited support for array new (#1286)
Browse files Browse the repository at this point in the history
This change adds initial support for array new expressions where the
array size is constant and the element does not require a cookie.
  • Loading branch information
andykaylor authored and lanza committed Jan 27, 2025
1 parent 6ea1d14 commit 3c3b096
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 19 deletions.
15 changes: 15 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,18 @@ bool CIRGenCXXABI::isZeroInitializable(const MemberPointerType *MPT) {
// Fake answer.
return true;
}

CharUnits CIRGenCXXABI::getArrayCookieSize(const CXXNewExpr *E) {
if (!requiresArrayCookie(E))
return CharUnits::Zero();
llvm_unreachable("NYI");
}

bool CIRGenCXXABI::requiresArrayCookie(const CXXNewExpr *E) {
// If the class's usual deallocation function takes two arguments,
// it needs a cookie.
if (E->doesUsualArrayDeleteWantSize())
return true;

return E->getAllocatedType().isDestructedType();
}
15 changes: 15 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class CIRGenCXXABI {

clang::ASTContext &getContext() const { return CGM.getASTContext(); }

virtual bool requiresArrayCookie(const CXXNewExpr *E);

public:
/// Similar to AddedStructorArgs, but only notes the number of additional
/// arguments.
Expand Down Expand Up @@ -347,6 +349,19 @@ class CIRGenCXXABI {

virtual cir::MethodAttr buildVirtualMethodAttr(cir::MethodType MethodTy,
const CXXMethodDecl *MD) = 0;

/**************************** Array cookies ******************************/

/// Returns the extra size required in order to store the array
/// cookie for the given new-expression. May return 0 to indicate that no
/// array cookie is required.
///
/// Several cases are filtered out before this method is called:
/// - non-array allocations never need a cookie
/// - calls to \::operator new(size_t, void*) never need a cookie
///
/// \param E - the new-expression being allocated.
virtual CharUnits getArrayCookieSize(const CXXNewExpr *E);
};

/// Creates and Itanium-family ABI
Expand Down
138 changes: 119 additions & 19 deletions clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/MissingFeatures.h"
#include <CIRGenCXXABI.h>
#include <CIRGenCstEmitter.h>
#include <CIRGenFunction.h>
#include <CIRGenModule.h>
#include <CIRGenValue.h>
Expand Down Expand Up @@ -549,11 +550,25 @@ static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *FD) {
return Params;
}

static CharUnits CalculateCookiePadding(CIRGenFunction &CGF,
const CXXNewExpr *E) {
if (!E->isArray())
return CharUnits::Zero();

// No cookie is required if the operator new[] being used is the
// reserved placement operator new[].
if (E->getOperatorNew()->isReservedGlobalPlacementOperator())
return CharUnits::Zero();

return CGF.CGM.getCXXABI().getArrayCookieSize(E);
}

static mlir::Value emitCXXNewAllocSize(CIRGenFunction &CGF, const CXXNewExpr *e,
unsigned minElements,
mlir::Value &numElements,
mlir::Value &sizeWithoutCookie) {
QualType type = e->getAllocatedType();
mlir::Location Loc = CGF.getLoc(e->getSourceRange());

if (!e->isArray()) {
CharUnits typeSize = CGF.getContext().getTypeSizeInChars(type);
Expand All @@ -563,7 +578,96 @@ static mlir::Value emitCXXNewAllocSize(CIRGenFunction &CGF, const CXXNewExpr *e,
return sizeWithoutCookie;
}

llvm_unreachable("NYI");
// The width of size_t.
unsigned sizeWidth = CGF.CGM.getDataLayout().getTypeSizeInBits(CGF.SizeTy);

// The number of elements can be 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.
//
// If the array size is constant, Sema will have prevented negative
// values and size overflow.

// Compute the constant factor.
llvm::APInt arraySizeMultiplier(sizeWidth, 1);
while (const ConstantArrayType *CAT =
CGF.getContext().getAsConstantArrayType(type)) {
type = CAT->getElementType();
arraySizeMultiplier *= CAT->getSize();
}

CharUnits typeSize = CGF.getContext().getTypeSizeInChars(type);
llvm::APInt typeSizeMultiplier(sizeWidth, typeSize.getQuantity());
typeSizeMultiplier *= arraySizeMultiplier;

// Figure out the cookie size.
llvm::APInt cookieSize(sizeWidth,
CalculateCookiePadding(CGF, e).getQuantity());

// This will be a size_t.
mlir::Value size;

// Emit the array size expression.
// We multiply the size of all dimensions for NumElements.
// e.g for 'int[2][3]', ElemType is 'int' and NumElements is 6.
const Expr *arraySize = *e->getArraySize();
mlir::Attribute constNumElements =
ConstantEmitter(CGF.CGM, &CGF)
.tryEmitAbstract(arraySize, arraySize->getType());
if (constNumElements) {
// Get an APInt from the constant
const llvm::APInt &count =
mlir::cast<cir::IntAttr>(constNumElements).getValue();

unsigned numElementsWidth = count.getBitWidth();

// The equivalent code in CodeGen/CGExprCXX.cpp handles these cases as
// overflow, but they should never happen. The size argument is implicitly
// cast to a size_t, so it can never be negative and numElementsWidth will
// always equal sizeWidth.
assert(!count.isNegative() && "Expected non-negative array size");
assert(numElementsWidth == sizeWidth &&
"Expected a size_t array size constant");

// Okay, compute a count at the right width.
llvm::APInt adjustedCount = count.zextOrTrunc(sizeWidth);

// Scale numElements by that. This might overflow, but we don't
// care because it only overflows if allocationSize does, too, and
// if that overflows then we shouldn't use this.
// This emits a constant that may not be used, but we can't tell here
// whether it will be needed or not.
numElements =
CGF.getBuilder().getConstInt(Loc, adjustedCount * arraySizeMultiplier);

// Compute the size before cookie, and track whether it overflowed.
bool overflow;
llvm::APInt allocationSize =
adjustedCount.umul_ov(typeSizeMultiplier, overflow);

// Sema prevents us from hitting this case
assert(!overflow && "Overflow in array allocation size");

// Add in the cookie, and check whether it's overflowed.
if (cookieSize != 0) {
llvm_unreachable("NYI");
}

size = CGF.getBuilder().getConstInt(Loc, allocationSize);
} else {
// TODO: Handle the variable size case
llvm_unreachable("NYI");
}

if (cookieSize == 0)
sizeWithoutCookie = size;
else
assert(sizeWithoutCookie && "didn't set sizeWithoutCookie?");

return size;
}

namespace {
Expand Down Expand Up @@ -745,33 +849,32 @@ static void StoreAnyExprIntoOneUnit(CIRGenFunction &CGF, const Expr *Init,
llvm_unreachable("bad evaluation kind");
}

void CIRGenFunction::emitNewArrayInitializer(
const CXXNewExpr *E, QualType ElementType, mlir::Type ElementTy,
Address BeginPtr, mlir::Value NumElements,
mlir::Value AllocSizeWithoutCookie) {
// If we have a type with trivial initialization and no initializer,
// there's nothing to do.
if (!E->hasInitializer())
return;

llvm_unreachable("NYI");
}

static void emitNewInitializer(CIRGenFunction &CGF, const CXXNewExpr *E,
QualType ElementType, mlir::Type ElementTy,
Address NewPtr, mlir::Value NumElements,
mlir::Value AllocSizeWithoutCookie) {
assert(!cir::MissingFeatures::generateDebugInfo());
if (E->isArray()) {
llvm_unreachable("NYI");
CGF.emitNewArrayInitializer(E, ElementType, ElementTy, NewPtr, NumElements,
AllocSizeWithoutCookie);
} else if (const Expr *Init = E->getInitializer()) {
StoreAnyExprIntoOneUnit(CGF, Init, E->getAllocatedType(), NewPtr,
AggValueSlot::DoesNotOverlap);
}
}

static CharUnits CalculateCookiePadding(CIRGenFunction &CGF,
const CXXNewExpr *E) {
if (!E->isArray())
return CharUnits::Zero();

// No cookie is required if the operator new[] being used is the
// reserved placement operator new[].
if (E->getOperatorNew()->isReservedGlobalPlacementOperator())
return CharUnits::Zero();

llvm_unreachable("NYI");
// return CGF.CGM.getCXXABI().GetArrayCookieSize(E);
}

namespace {
/// Calls the given 'operator delete' on a single object.
struct CallObjectDelete final : EHScopeStack::Cleanup {
Expand Down Expand Up @@ -1129,9 +1232,6 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *E) {
emitNewInitializer(*this, E, allocType, elementTy, result, numElements,
allocSizeWithoutCookie);
auto resultPtr = result.getPointer();
if (E->isArray()) {
llvm_unreachable("NYI");
}

// Deactivate the 'operator delete' cleanup if we finished
// initialization.
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,11 @@ class CIRGenFunction : public CIRGenTypeCache {
mlir::Value emitCXXNewExpr(const CXXNewExpr *E);
void emitCXXDeleteExpr(const CXXDeleteExpr *E);

void emitNewArrayInitializer(const CXXNewExpr *E, QualType ElementType,
mlir::Type ElementTy, Address BeginPtr,
mlir::Value NumElements,
mlir::Value AllocSizeWithoutCookie);

void emitCXXAggrConstructorCall(const CXXConstructorDecl *D,
const clang::ArrayType *ArrayTy,
Address ArrayPtr, const CXXConstructExpr *E,
Expand Down
34 changes: 34 additions & 0 deletions clang/test/CIR/CodeGen/new.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,37 @@ void t() {
B b;
b.construct(&b);
}


void t_new_constant_size() {
auto p = new double[16];
}

// In this test, NUM_ELEMENTS isn't used because no cookie is needed and there
// are no constructor calls needed.

// CHECK: cir.func @_Z19t_new_constant_sizev()
// CHECK: %0 = cir.alloca !cir.ptr<!cir.double>, !cir.ptr<!cir.ptr<!cir.double>>, ["p", init] {alignment = 8 : i64}
// CHECK: %[[#NUM_ELEMENTS:]] = cir.const #cir.int<16> : !u64i
// CHECK: %[[#ALLOCATION_SIZE:]] = cir.const #cir.int<128> : !u64i
// CHECK: %3 = cir.call @_Znam(%[[#ALLOCATION_SIZE]]) : (!u64i) -> !cir.ptr<!void>
// CHECK: %4 = cir.cast(bitcast, %3 : !cir.ptr<!void>), !cir.ptr<!cir.double>
// CHECK: cir.store %4, %0 : !cir.ptr<!cir.double>, !cir.ptr<!cir.ptr<!cir.double>>
// CHECK: cir.return
// CHECK: }

void t_new_multidim_constant_size() {
auto p = new double[2][3][4];
}

// As above, NUM_ELEMENTS isn't used.

// CHECK: cir.func @_Z28t_new_multidim_constant_sizev()
// CHECK: %0 = cir.alloca !cir.ptr<!cir.array<!cir.array<!cir.double x 4> x 3>>, !cir.ptr<!cir.ptr<!cir.array<!cir.array<!cir.double x 4> x 3>>>, ["p", init] {alignment = 8 : i64}
// CHECK: %[[#NUM_ELEMENTS:]] = cir.const #cir.int<24> : !u64i
// CHECK: %[[#ALLOCATION_SIZE:]] = cir.const #cir.int<192> : !u64i
// CHECK: %3 = cir.call @_Znam(%[[#ALLOCATION_SIZE]]) : (!u64i) -> !cir.ptr<!void>
// CHECK: %4 = cir.cast(bitcast, %3 : !cir.ptr<!void>), !cir.ptr<!cir.double>
// CHECK: %5 = cir.cast(bitcast, %0 : !cir.ptr<!cir.ptr<!cir.array<!cir.array<!cir.double x 4> x 3>>>), !cir.ptr<!cir.ptr<!cir.double>>
// CHECK: cir.store %4, %5 : !cir.ptr<!cir.double>, !cir.ptr<!cir.ptr<!cir.double>>
// CHECK: }
20 changes: 20 additions & 0 deletions clang/test/CIR/Lowering/new.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM

void t_new_constant_size() {
auto p = new double[16];
}

// LLVM: @_Z19t_new_constant_sizev()
// LLVM: %[[ALLOCA:.*]] = alloca ptr, i64 1, align 8
// LLVM: %[[ADDR:.*]] = call ptr @_Znam(i64 128)
// LLVM: store ptr %[[ADDR]], ptr %[[ALLOCA]], align 8

void t_new_multidim_constant_size() {
auto p = new double[2][3][4];
}

// LLVM: @_Z28t_new_multidim_constant_sizev()
// LLVM: %[[ALLOCA:.*]] = alloca ptr, i64 1, align 8
// LLVM: %[[ADDR:.*]] = call ptr @_Znam(i64 192)
// LLVM: store ptr %[[ADDR]], ptr %[[ALLOCA]], align 8

0 comments on commit 3c3b096

Please sign in to comment.