Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 getSignedInt(mlir::Location loc, int64_t val, unsigned numBits) {
auto type = cir::IntType::get(getContext(), numBits, /*isSigned=*/true);
return getConstAPInt(loc, type,
llvm::APInt(numBits, val, /*isSigned=*/true));
}

mlir::Value getUnsignedInt(mlir::Location loc, uint64_t val,
unsigned numBits) {
auto type = cir::IntType::get(getContext(), numBits, /*isSigned=*/false);
Expand Down
23 changes: 20 additions & 3 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -607,8 +607,8 @@ def CIR_ConditionOp : CIR_Op<"condition", [
//===----------------------------------------------------------------------===//

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

def CIR_YieldOp : CIR_Op<"yield", [
Expand Down Expand Up @@ -2229,7 +2229,7 @@ def CIR_TrapOp : CIR_Op<"trap", [Terminator]> {
}

//===----------------------------------------------------------------------===//
// ArrayCtor
// ArrayCtor & ArrayDtor
//===----------------------------------------------------------------------===//

class CIR_ArrayInitDestroy<string mnemonic> : CIR_Op<mnemonic> {
Expand Down Expand Up @@ -2272,6 +2272,23 @@ def CIR_ArrayCtor : CIR_ArrayInitDestroy<"array.ctor"> {
}];
}

def CIR_ArrayDtor : CIR_ArrayInitDestroy<"array.dtor"> {
let summary = "Destroy array elements with C++ dtors";
let description = [{
Destroy each array element using the same C++ destructor. This
operation has one region, with one single block. The block has an
incoming argument for the current array index to initialize.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
incoming argument for the current array index to initialize.
incoming argument for the current array element to destruct.


```mlir
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
incoming argument for the current array index to initialize.
```mlir
incoming argument for the current array index to initialize.
Example:
```mlir

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

//===----------------------------------------------------------------------===//
// VecCreate
//===----------------------------------------------------------------------===//
Expand Down
69 changes: 66 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,41 @@ void CIRGenFunction::emitNullabilityCheck(LValue lhs, mlir::Value rhs,
assert(!cir::MissingFeatures::sanitizers());
}

/// Destroys all the elements of the given array, beginning from last to first.
/// The array cannot be zero-length.
///
/// \param begin - a type* denoting the first element of the array
/// \param end - a type* denoting one past the end of the array
/// \param elementType - the element type of the array
/// \param destroyer - the function to call to destroy elements
void CIRGenFunction::emitArrayDestroy(mlir::Value begin, mlir::Value end,
QualType elementType,
CharUnits elementAlign,
Destroyer *destroyer,
bool checkZeroLength) {
assert(!elementType->isArrayType());
if (checkZeroLength)
cgm.errorNYI("emitArrayDestroy: check for zero length");
Copy link
Contributor

Choose a reason for hiding this comment

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

We turn ArrayDtor into a loop in LoweringPrepare. I don't think we ever need to insert a branch here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, though that means we will need to check for that condition in LowerPrepare when we implement support for VLAs.


// Differently from LLVM traditional codegen, use a higher level
// representation instead of lowering directly to a loop.
mlir::Type cirElementType = convertTypeForMem(elementType);
cir::PointerType ptrToElmType = builder.getPointerTo(cirElementType);

// Emit the dtor call that will execute for every array element.
builder.create<cir::ArrayDtor>(
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably align with mlir work of transforming from builder.create to Op::create
At some point I will give passthrough what is upstreamed and fix it, but new stuff can be fixed on the fly directly. So this should be cir::ArrayDtor::create(builder, ...

See more info here #147168

*currSrcLoc, begin, [&](mlir::OpBuilder &b, mlir::Location loc) {
auto arg = b.getInsertionBlock()->addArgument(ptrToElmType, loc);
Address curAddr = Address(arg, cirElementType, elementAlign);
assert(!cir::MissingFeatures::dtorCleanups());

// Perform the actual destruction there.
destroyer(*this, curAddr, elementType);

builder.create<cir::YieldOp>(loc);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
builder.create<cir::YieldOp>(loc);
cir::YieldOp::create(builder, loc);

});
}

/// Immediately perform the destruction of the given object.
///
/// \param addr - the address of the object; a type*
Expand All @@ -658,10 +693,38 @@ void CIRGenFunction::emitNullabilityCheck(LValue lhs, mlir::Value rhs,
/// elements
void CIRGenFunction::emitDestroy(Address addr, QualType type,
Destroyer *destroyer) {
if (getContext().getAsArrayType(type))
cgm.errorNYI("emitDestroy: array type");
const ArrayType *arrayType = getContext().getAsArrayType(type);
if (!arrayType)
return destroyer(*this, addr, type);

mlir::Value length = emitArrayLength(arrayType, type, addr);

CharUnits elementAlign = addr.getAlignment().alignmentOfArrayElement(
getContext().getTypeSizeInChars(type));

// Normally we have to check whether the array is zero-length.
bool checkZeroLength = true;

// But if the array length is constant, we can suppress that.
auto constantCount = dyn_cast<cir::ConstantOp>(length.getDefiningOp());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
auto constantCount = dyn_cast<cir::ConstantOp>(length.getDefiningOp());
auto constantCount = length.getDefiningOp<cir::ConstantOp>();

if (constantCount) {
auto constIntAttr = mlir::dyn_cast<cir::IntAttr>(constantCount.getValue());
// ...and if it's constant zero, we can just skip the entire thing.
if (constIntAttr && constIntAttr.getUInt() == 0)
return;
checkZeroLength = false;
} else {
cgm.errorNYI("emitDestroy: variable length array");
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 a cir::MissingFeature::vla() here?

return;
}

mlir::Value begin = addr.getPointer();
mlir::Value end; // This will be used for future non-constant counts.
emitArrayDestroy(begin, end, type, elementAlign, destroyer, checkZeroLength);

return destroyer(*this, addr, type);
// If the array destroy didn't use the length op, we can erase it.
if (constantCount.use_empty())
Copy link
Contributor

@mmha mmha Jul 25, 2025

Choose a reason for hiding this comment

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

I think we can simplify the control flow a little if you invert the condition up there. Something like:

if(!constantCount) {
  assert(!cir::MissingFeature::vlas());
  cgm.errorNYI("emitDestroy: variable length array");
  return;
}

auto constIntAttr = mlir::dyn_cast<cir::IntAttr>(constantCount.getValue());
// ...and if it's constant zero, we can just skip the entire thing.
if (constIntAttr && constIntAttr.getUInt() == 0)
  return;
checkZeroLength = false;

mlir::Value begin = addr.getPointer();
mlir::Value end; // This will be used for future non-constant counts.
emitArrayDestroy(begin, end, type, elementAlign, destroyer, checkZeroLength);

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We will probably need to switch back to the if-else structure when we support VLAs, but for now your suggestion looks better. I'll make the change.

constantCount.erase();
}

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

void emitArrayDestroy(mlir::Value begin, mlir::Value end,
QualType elementType, CharUnits elementAlign,
Destroyer *destroyer, bool checkZeroLength);

mlir::Value emitArrayLength(const clang::ArrayType *arrayType,
QualType &baseType, Address &addr);
LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e);
Expand Down
66 changes: 45 additions & 21 deletions clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
void runOnOp(mlir::Operation *op);
void lowerCastOp(cir::CastOp op);
void lowerUnaryOp(cir::UnaryOp op);
void lowerArrayDtor(ArrayDtor op);
void lowerArrayCtor(ArrayCtor op);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
void lowerArrayDtor(ArrayDtor op);
void lowerArrayCtor(ArrayCtor op);
void lowerArrayDtor(cir::ArrayDtor op);
void lowerArrayCtor(cir::ArrayCtor op);


///
Expand Down Expand Up @@ -172,28 +173,29 @@ void LoweringPreparePass::lowerUnaryOp(cir::UnaryOp op) {
static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
clang::ASTContext *astCtx,
mlir::Operation *op, mlir::Type eltTy,
mlir::Value arrayAddr,
uint64_t arrayLen) {
mlir::Value arrayAddr, uint64_t arrayLen,
bool isCtor) {
// Generate loop to call into ctor/dtor for every element.
mlir::Location loc = op->getLoc();

// TODO: instead of fixed integer size, create alias for PtrDiffTy and unify
// with CIRGen stuff.
// TODO: instead of getting the size from the AST context, create alias for
// PtrDiffTy and unify with CIRGen stuff.
const unsigned sizeTypeSize =
astCtx->getTypeSize(astCtx->getSignedSizeType());
auto ptrDiffTy =
cir::IntType::get(builder.getContext(), sizeTypeSize, /*isSigned=*/false);
mlir::Value numArrayElementsConst = builder.getUnsignedInt(loc, arrayLen, 64);
mlir::Value numArrayElementsConst =
builder.getUnsignedInt(loc, arrayLen, sizeTypeSize);

auto begin = builder.create<cir::CastOp>(
loc, eltTy, cir::CastKind::array_to_ptrdecay, arrayAddr);
mlir::Value end = builder.create<cir::PtrStrideOp>(loc, eltTy, begin,
numArrayElementsConst);
mlir::Value start = isCtor ? begin : end;
mlir::Value stop = isCtor ? end : begin;

mlir::Value tmpAddr = builder.createAlloca(
loc, /*addr type*/ builder.getPointerTo(eltTy),
/*var type*/ eltTy, "__array_idx", builder.getAlignmentAttr(1));
builder.createStore(loc, begin, tmpAddr);
builder.createStore(loc, start, tmpAddr);

cir::DoWhileOp loop = builder.createDoWhile(
loc,
Expand All @@ -202,7 +204,7 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
auto currentElement = b.create<cir::LoadOp>(loc, eltTy, tmpAddr);
mlir::Type boolTy = cir::BoolType::get(b.getContext());
auto cmp = builder.create<cir::CmpOp>(loc, boolTy, cir::CmpOpKind::ne,
currentElement, end);
currentElement, stop);
builder.createCondition(cmp);
},
/*bodyBuilder=*/
Expand All @@ -213,15 +215,23 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
op->walk([&](cir::CallOp c) { ctorCall = c; });
assert(ctorCall && "expected ctor call");

auto one = builder.create<cir::ConstantOp>(
loc, ptrDiffTy, cir::IntAttr::get(ptrDiffTy, 1));

ctorCall->moveAfter(one);
ctorCall->setOperand(0, currentElement);

// Advance pointer and store them to temporary variable
auto nextElement =
builder.create<cir::PtrStrideOp>(loc, eltTy, currentElement, one);
// Array elements get constructed in order but destructed in reverse.
cir::PtrStrideOp nextElement;
if (isCtor) {
mlir::Value stride = builder.getUnsignedInt(loc, 1, sizeTypeSize);
ctorCall->moveBefore(stride.getDefiningOp());
ctorCall->setOperand(0, currentElement);
nextElement = builder.create<cir::PtrStrideOp>(
loc, eltTy, currentElement, stride);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
nextElement = builder.create<cir::PtrStrideOp>(
loc, eltTy, currentElement, stride);
nextElement = cir::PtrStrideOp::create(
builder, loc, eltTy, currentElement, stride);

} else {
mlir::Value stride = builder.getSignedInt(loc, -1, sizeTypeSize);
nextElement = builder.create<cir::PtrStrideOp>(
loc, eltTy, currentElement, stride);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
nextElement = builder.create<cir::PtrStrideOp>(
loc, eltTy, currentElement, stride);
nextElement = cir::PtrStrideOp::create(
builder, loc, eltTy, currentElement, stride);

ctorCall->moveAfter(nextElement);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think your loop is correct. But I also think if you initialize stop with end - 1 you don't have to use moveAfter here and can share more code between the ctor and dtor case (the zero case is handled elsewhere so that would be safe to do). But that's more of a comment and less of a request to change your patch. I don't care either way :)

ctorCall->setOperand(0, nextElement);
}

// Store the element pointer to the temporary variable
builder.createStore(loc, nextElement, tmpAddr);
builder.createYield(loc);
});
Expand All @@ -230,6 +240,17 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
op->erase();
}

void LoweringPreparePass::lowerArrayDtor(ArrayDtor op) {
CIRBaseBuilderTy builder(getContext());
builder.setInsertionPointAfter(op.getOperation());

mlir::Type eltTy = op->getRegion(0).getArgument(0).getType();
Copy link
Contributor

Choose a reason for hiding this comment

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

ArrayCtor asserts here !cir::MissingFeatures::vlas(), shouldn't it be asserted also here?

auto arrayLen =
mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(), arrayLen,
false);
}

void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
cir::CIRBaseBuilderTy builder(getContext());
builder.setInsertionPointAfter(op.getOperation());
Expand All @@ -238,8 +259,8 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
assert(!cir::MissingFeatures::vlas());
auto arrayLen =
mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
arrayLen);
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(), arrayLen,
true);
}

void LoweringPreparePass::runOnOp(mlir::Operation *op) {
Expand All @@ -249,6 +270,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
lowerCastOp(cast);
else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op))
lowerUnaryOp(unary);
else if (auto arrayDtor = dyn_cast<ArrayDtor>(op))
Copy link
Contributor

Choose a reason for hiding this comment

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

We should try to keep this if/else cascade in lexicographical order.

lowerArrayDtor(arrayDtor);
}

void LoweringPreparePass::runOnOperation() {
Expand All @@ -257,7 +280,8 @@ void LoweringPreparePass::runOnOperation() {
llvm::SmallVector<mlir::Operation *> opsToTransform;

op->walk([&](mlir::Operation *op) {
if (mlir::isa<cir::ArrayCtor, cir::CastOp, cir::UnaryOp>(op))
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp, cir::UnaryOp>(
op))
opsToTransform.push_back(op);
});

Expand Down
2 changes: 1 addition & 1 deletion clang/test/CIR/CodeGen/array-ctor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ void foo() {
// CIR: cir.store %[[DECAY]], %[[ITER]] : !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>
// CIR: cir.do {
// CIR: %[[CURRENT:.*]] = cir.load %[[ITER]] : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
// CIR: %[[CONST1:.*]] = cir.const #cir.int<1> : !u64i
// CIR: cir.call @_ZN1SC1Ev(%[[CURRENT]]) : (!cir.ptr<!rec_S>) -> ()
// CIR: %[[CONST1:.*]] = cir.const #cir.int<1> : !u64i
// CIR: %[[NEXT:.*]] = cir.ptr_stride(%[[CURRENT]] : !cir.ptr<!rec_S>, %[[CONST1]] : !u64i), !cir.ptr<!rec_S>
// CIR: cir.store %[[NEXT]], %[[ITER]] : !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>
// CIR: cir.yield
Expand Down
Loading