Skip to content

[CIR] Add support for array constructors #149142

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return getConstant(loc, cir::IntAttr::get(ty, value));
}

mlir::Value getUnsignedInt(mlir::Location loc, uint64_t val,
unsigned numBits) {
auto type = cir::IntType::get(getContext(), numBits, /*isSigned=*/false);
return getConstAPInt(loc, type, llvm::APInt(numBits, val));
}

// Creates constant null value for integral type ty.
cir::ConstantOp getNullValue(mlir::Type ty, mlir::Location loc) {
return getConstant(loc, getZeroInitAttr(ty));
Expand Down
46 changes: 45 additions & 1 deletion clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [
//===----------------------------------------------------------------------===//

defvar CIR_YieldableScopes = [
"CaseOp", "DoWhileOp", "ForOp", "IfOp", "ScopeOp", "SwitchOp",
"ArrayCtor", "CaseOp", "DoWhileOp", "ForOp", "IfOp", "ScopeOp", "SwitchOp",
"TernaryOp", "WhileOp"
];

Expand Down Expand Up @@ -2228,6 +2228,50 @@ def CIR_TrapOp : CIR_Op<"trap", [Terminator]> {
let assemblyFormat = "attr-dict";
}

//===----------------------------------------------------------------------===//
// ArrayCtor
//===----------------------------------------------------------------------===//

class CIR_ArrayInitDestroy<string mnemonic> : CIR_Op<mnemonic> {
let arguments = (ins
Arg<CIR_PtrToArray, "array address", [MemWrite, MemRead]>:$addr
);

let regions = (region SizedRegion<1>:$body);
let assemblyFormat = [{
$addr `:` qualified(type($addr)) $body attr-dict
}];

let builders = [
OpBuilder<(ins "mlir::Value":$addr,
"llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$regionBuilder), [{
assert(regionBuilder && "builder callback expected");
mlir::OpBuilder::InsertionGuard guard($_builder);
mlir::Region *r = $_state.addRegion();
$_state.addOperands(ValueRange{addr});
$_builder.createBlock(r);
regionBuilder($_builder, $_state.location);
}]>
];
}

def CIR_ArrayCtor : CIR_ArrayInitDestroy<"array.ctor"> {
let summary = "Initialize array elements with C++ constructors";
let description = [{
Initialize each array element using the same C++ constructor. This
operation has one region, with one single block. The block has an
incoming argument for the current array index to initialize.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add example here please.


```mlir
cir.array.ctor(%0 : !cir.ptr<!cir.array<!rec_S x 42>>) {
^bb0(%arg0: !cir.ptr<!rec_S>):
cir.call @some_ctor(%arg0) : (!cir.ptr<!rec_S>) -> ()
cir.yield
}
```
}];
}

//===----------------------------------------------------------------------===//
// VecCreate
//===----------------------------------------------------------------------===//
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ def CIR_AnyIntOrFloatType : AnyTypeOf<[CIR_AnyFloatType, CIR_AnyIntType],

def CIR_AnyComplexType : CIR_TypeBase<"::cir::ComplexType", "complex type">;

//===----------------------------------------------------------------------===//
// Array Type predicates
//===----------------------------------------------------------------------===//

def CIR_AnyArrayType : CIR_TypeBase<"::cir::ArrayType", "array type">;

//===----------------------------------------------------------------------===//
// Pointer Type predicates
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -216,6 +222,8 @@ def CIR_PtrToIntOrFloatType : CIR_PtrToType<CIR_AnyIntOrFloatType>;

def CIR_PtrToComplexType : CIR_PtrToType<CIR_AnyComplexType>;

def CIR_PtrToArray : CIR_PtrToType<CIR_AnyArrayType>;

//===----------------------------------------------------------------------===//
// Vector Type predicates
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Dialect/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ std::unique_ptr<Pass> createCIRFlattenCFGPass();
std::unique_ptr<Pass> createCIRSimplifyPass();
std::unique_ptr<Pass> createHoistAllocasPass();
std::unique_ptr<Pass> createLoweringPreparePass();
std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx);

void populateCIRPreLoweringPasses(mlir::OpPassManager &pm);

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ struct MissingFeatures {
static bool dtorCleanups() { return false; }
static bool vtableInitialization() { return false; }
static bool msvcBuiltins() { return false; }
static bool vlas() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
111 changes: 111 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenClass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "CIRGenCXXABI.h"
#include "CIRGenFunction.h"
#include "CIRGenValue.h"

#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecordLayout.h"
Expand Down Expand Up @@ -311,6 +312,116 @@ void CIRGenFunction::emitInitializerForField(FieldDecl *field, LValue lhs,
assert(!cir::MissingFeatures::requiresCleanups());
}

/// Emit a loop to call a particular constructor for each of several members
/// of an array.
///
/// \param ctor the constructor to call for each element
/// \param arrayType the type of the array to initialize
/// \param arrayBegin an arrayType*
/// \param zeroInitialize true if each element should be
/// zero-initialized before it is constructed
void CIRGenFunction::emitCXXAggrConstructorCall(
const CXXConstructorDecl *ctor, const clang::ArrayType *arrayType,
Address arrayBegin, const CXXConstructExpr *e, bool newPointerIsChecked,
bool zeroInitialize) {
QualType elementType;
mlir::Value numElements = emitArrayLength(arrayType, elementType, arrayBegin);
emitCXXAggrConstructorCall(ctor, numElements, arrayBegin, e,
newPointerIsChecked, zeroInitialize);
}

/// Emit a loop to call a particular constructor for each of several members
/// of an array.
///
/// \param ctor the constructor to call for each element
/// \param numElements the number of elements in the array;
/// may be zero
/// \param arrayBase a T*, where T is the type constructed by ctor
/// \param zeroInitialize true if each element should be
/// zero-initialized before it is constructed
void CIRGenFunction::emitCXXAggrConstructorCall(
const CXXConstructorDecl *ctor, mlir::Value numElements, Address arrayBase,
const CXXConstructExpr *e, bool newPointerIsChecked, bool zeroInitialize) {
// It's legal for numElements to be zero. This can happen both
// dynamically, because x can be zero in 'new A[x]', and statically,
// because of GCC extensions that permit zero-length arrays. There
// are probably legitimate places where we could assume that this
// doesn't happen, but it's not clear that it's worth it.

// Optimize for a constant count.
auto constantCount = dyn_cast<cir::ConstantOp>(numElements.getDefiningOp());
if (constantCount) {
auto constIntAttr = mlir::dyn_cast<cir::IntAttr>(constantCount.getValue());
// Just skip out if the constant count is zero.
if (constIntAttr && constIntAttr.getUInt() == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to add a test for the zero case?

return;
} else {
// Otherwise, emit the check.
cgm.errorNYI(e->getSourceRange(), "dynamic-length array expression");
}

auto arrayTy = mlir::cast<cir::ArrayType>(arrayBase.getElementType());
mlir::Type elementType = arrayTy.getElementType();
cir::PointerType ptrToElmType = builder.getPointerTo(elementType);

// Tradional LLVM codegen emits a loop here. CIR lowers to a loop as part of
// LoweringPrepare.

// The alignment of the base, adjusted by the size of a single element,
// provides a conservative estimate of the alignment of every element.
// (This assumes we never start tracking offsetted alignments.)
//
// Note that these are complete objects and so we don't need to
// use the non-virtual size or alignment.
QualType type = getContext().getTypeDeclType(ctor->getParent());
CharUnits eltAlignment = arrayBase.getAlignment().alignmentOfArrayElement(
getContext().getTypeSizeInChars(type));

// Zero initialize the storage, if requested.
if (zeroInitialize)
emitNullInitialization(*currSrcLoc, arrayBase, type);

// C++ [class.temporary]p4:
// There are two contexts in which temporaries are destroyed at a different
// point than the end of the full-expression. The first context is when a
// default constructor is called to initialize an element of an array.
// If the constructor has one or more default arguments, the destruction of
// every temporary created in a default argument expression is sequenced
// before the construction of the next array element, if any.
{
assert(!cir::MissingFeatures::runCleanupsScope());

// Evaluate the constructor and its arguments in a regular
// partial-destroy cleanup.
if (getLangOpts().Exceptions &&
!ctor->getParent()->hasTrivialDestructor()) {
cgm.errorNYI(e->getSourceRange(), "partial array cleanups");
}

// Emit the constructor call that will execute for every array element.
mlir::Value arrayOp =
builder.createPtrBitcast(arrayBase.getPointer(), arrayTy);
builder.create<cir::ArrayCtor>(
*currSrcLoc, arrayOp, [&](mlir::OpBuilder &b, mlir::Location loc) {
mlir::BlockArgument arg =
b.getInsertionBlock()->addArgument(ptrToElmType, loc);
Address curAddr = Address(arg, elementType, eltAlignment);
assert(!cir::MissingFeatures::sanitizers());
auto currAVS = AggValueSlot::forAddr(
curAddr, type.getQualifiers(), AggValueSlot::IsDestructed,
AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap,
AggValueSlot::IsNotZeroed);
emitCXXConstructorCall(ctor, Ctor_Complete,
/*ForVirtualBase=*/false,
/*Delegating=*/false, currAVS, e);
builder.create<cir::YieldOp>(loc);
});
}

if (constantCount.use_empty())
constantCount.erase();
}

void CIRGenFunction::emitDelegateCXXConstructorCall(
const CXXConstructorDecl *ctor, CXXCtorType ctorType,
const FunctionArgList &args, SourceLocation loc) {
Expand Down
59 changes: 30 additions & 29 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1657,37 +1657,38 @@ void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
return;
}

if (getContext().getAsArrayType(e->getType())) {
cgm.errorNYI(e->getSourceRange(), "emitCXXConstructExpr: array type");
return;
}
if (const ArrayType *arrayType = getContext().getAsArrayType(e->getType())) {
assert(!cir::MissingFeatures::sanitizers());
emitCXXAggrConstructorCall(cd, arrayType, dest.getAddress(), e, false);
} else {

clang::CXXCtorType type = Ctor_Complete;
bool forVirtualBase = false;
bool delegating = false;

switch (e->getConstructionKind()) {
case CXXConstructionKind::Complete:
type = Ctor_Complete;
break;
case CXXConstructionKind::Delegating:
// We should be emitting a constructor; GlobalDecl will assert this
type = curGD.getCtorType();
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
// This should just set 'forVirtualBase' to true and fall through, but
// virtual base class support is otherwise missing, so this needs to wait
// until it can be tested.
cgm.errorNYI(e->getSourceRange(),
"emitCXXConstructExpr: virtual base constructor");
return;
case CXXConstructionKind::NonVirtualBase:
type = Ctor_Base;
break;
}
clang::CXXCtorType type = Ctor_Complete;
bool forVirtualBase = false;
bool delegating = false;

emitCXXConstructorCall(cd, type, forVirtualBase, delegating, dest, e);
switch (e->getConstructionKind()) {
case CXXConstructionKind::Complete:
type = Ctor_Complete;
break;
case CXXConstructionKind::Delegating:
// We should be emitting a constructor; GlobalDecl will assert this
type = curGD.getCtorType();
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
// This should just set 'forVirtualBase' to true and fall through, but
// virtual base class support is otherwise missing, so this needs to wait
// until it can be tested.
cgm.errorNYI(e->getSourceRange(),
"emitCXXConstructExpr: virtual base constructor");
return;
case CXXConstructionKind::NonVirtualBase:
type = Ctor_Base;
break;
}

emitCXXConstructorCall(cd, type, forVirtualBase, delegating, dest, e);
}
}

RValue CIRGenFunction::emitReferenceBindingToExpr(const Expr *e) {
Expand Down
44 changes: 44 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -808,4 +808,48 @@ bool CIRGenFunction::shouldNullCheckClassCastValue(const CastExpr *ce) {
return true;
}

/// Computes the length of an array in elements, as well as the base
/// element type and a properly-typed first element pointer.
mlir::Value
CIRGenFunction::emitArrayLength(const clang::ArrayType *origArrayType,
QualType &baseType, Address &addr) {
const clang::ArrayType *arrayType = origArrayType;

// If it's a VLA, we have to load the stored size. Note that
// this is the size of the VLA in bytes, not its size in elements.
if (isa<VariableArrayType>(arrayType)) {
assert(cir::MissingFeatures::vlas());
cgm.errorNYI(*currSrcLoc, "VLAs");
return builder.getConstInt(*currSrcLoc, SizeTy, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this change, but we seem to have a coding standard inconsistency with SizeTy.

}

uint64_t countFromCLAs = 1;
QualType eltType;

auto cirArrayType = mlir::dyn_cast<cir::ArrayType>(addr.getElementType());

while (cirArrayType) {
assert(isa<ConstantArrayType>(arrayType));
countFromCLAs *= cirArrayType.getSize();
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this is handling a multi-dimensional array. Can you add a test for that?

eltType = arrayType->getElementType();

cirArrayType =
mlir::dyn_cast<cir::ArrayType>(cirArrayType.getElementType());

arrayType = getContext().getAsArrayType(arrayType->getElementType());
assert((!cirArrayType || arrayType) &&
"CIR and Clang types are out-of-sync");
}

if (arrayType) {
// From this point onwards, the Clang array type has been emitted
// as some other type (probably a packed struct). Compute the array
// size, and just emit the 'begin' expression as a bitcast.
cgm.errorNYI(*currSrcLoc, "length for non-array underlying types");
}

baseType = eltType;
return builder.getConstInt(*currSrcLoc, SizeTy, countFromCLAs);
}

} // namespace clang::CIRGen
12 changes: 12 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,8 @@ class CIRGenFunction : public CIRGenTypeCache {
/// even if no aggregate location is provided.
RValue emitAnyExprToTemp(const clang::Expr *e);

mlir::Value emitArrayLength(const clang::ArrayType *arrayType,
QualType &baseType, Address &addr);
LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e);

Address emitArrayToPointerDecay(const Expr *array);
Expand Down Expand Up @@ -843,6 +845,16 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitCXXConstructExpr(const clang::CXXConstructExpr *e,
AggValueSlot dest);

void emitCXXAggrConstructorCall(const CXXConstructorDecl *ctor,
const clang::ArrayType *arrayType,
Address arrayBegin, const CXXConstructExpr *e,
bool newPointerIsChecked,
bool zeroInitialize = false);
void emitCXXAggrConstructorCall(const CXXConstructorDecl *ctor,
mlir::Value numElements, Address arrayBase,
const CXXConstructExpr *e,
bool newPointerIsChecked,
bool zeroInitialize);
void emitCXXConstructorCall(const clang::CXXConstructorDecl *d,
clang::CXXCtorType type, bool forVirtualBase,
bool delegating, AggValueSlot thisAVS,
Expand Down
Loading
Loading