diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 5c04d59475b6a..b6dd4eecaef67 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -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); diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 32bb9009aeec9..34b455f14e845 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -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", [ @@ -2229,7 +2229,7 @@ def CIR_TrapOp : CIR_Op<"trap", [Terminator]> { } //===----------------------------------------------------------------------===// -// ArrayCtor +// ArrayCtor & ArrayDtor //===----------------------------------------------------------------------===// class CIR_ArrayInitDestroy : CIR_Op { @@ -2260,7 +2260,9 @@ def CIR_ArrayCtor : CIR_ArrayInitDestroy<"array.ctor"> { 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. + incoming argument for the current array element to initialize. + + Example: ```mlir cir.array.ctor(%0 : !cir.ptr>) { @@ -2272,6 +2274,25 @@ 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 element to destruct. + + Example: + + ```mlir + cir.array.dtor(%0 : !cir.ptr>) { + ^bb0(%arg0: !cir.ptr): + cir.call @some_dtor(%arg0) : (!cir.ptr) -> () + cir.yield + } + ``` + }]; +} + //===----------------------------------------------------------------------===// // VecCreate //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index a28ac3c16ce59..6527fb5697f7c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -649,6 +649,38 @@ 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) { + assert(!elementType->isArrayType()); + + // 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. + cir::ArrayDtor::create( + builder, *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); + + cir::YieldOp::create(builder, loc); + }); +} + /// Immediately perform the destruction of the given object. /// /// \param addr - the address of the object; a type* @@ -658,10 +690,34 @@ 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)); + + auto constantCount = length.getDefiningOp(); + if (!constantCount) { + assert(!cir::MissingFeatures::vlas()); + cgm.errorNYI("emitDestroy: variable length array"); + return; + } + + auto constIntAttr = mlir::dyn_cast(constantCount.getValue()); + // If it's constant zero, we can just skip the entire thing. + if (constIntAttr && constIntAttr.getUInt() == 0) + return; + + mlir::Value begin = addr.getPointer(); + mlir::Value end; // This will be used for future non-constant counts. + emitArrayDestroy(begin, end, type, elementAlign, destroyer); - return destroyer(*this, addr, type); + // If the array destroy didn't use the length op, we can erase it. + if (constantCount.use_empty()) + constantCount.erase(); } CIRGenFunction::Destroyer * diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 4891c7496588f..e51b1aaecd799 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -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); + mlir::Value emitArrayLength(const clang::ArrayType *arrayType, QualType &baseType, Address &addr); LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e); diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index cef83eae2ef50..ce3b30d7464aa 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -29,7 +29,8 @@ struct LoweringPreparePass : public LoweringPrepareBase { void runOnOp(mlir::Operation *op); void lowerCastOp(cir::CastOp op); void lowerUnaryOp(cir::UnaryOp op); - void lowerArrayCtor(ArrayCtor op); + void lowerArrayDtor(cir::ArrayDtor op); + void lowerArrayCtor(cir::ArrayCtor op); /// /// AST related @@ -172,28 +173,30 @@ 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); + uint64_t endOffset = isCtor ? arrayLen : arrayLen - 1; + mlir::Value endOffsetVal = + builder.getUnsignedInt(loc, endOffset, sizeTypeSize); - auto begin = builder.create( - loc, eltTy, cir::CastKind::array_to_ptrdecay, arrayAddr); - mlir::Value end = builder.create(loc, eltTy, begin, - numArrayElementsConst); + auto begin = cir::CastOp::create(builder, loc, eltTy, + cir::CastKind::array_to_ptrdecay, arrayAddr); + mlir::Value end = + cir::PtrStrideOp::create(builder, loc, eltTy, begin, endOffsetVal); + 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, @@ -202,7 +205,7 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, auto currentElement = b.create(loc, eltTy, tmpAddr); mlir::Type boolTy = cir::BoolType::get(b.getContext()); auto cmp = builder.create(loc, boolTy, cir::CmpOpKind::ne, - currentElement, end); + currentElement, stop); builder.createCondition(cmp); }, /*bodyBuilder=*/ @@ -213,15 +216,19 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, op->walk([&](cir::CallOp c) { ctorCall = c; }); assert(ctorCall && "expected ctor call"); - auto one = builder.create( - loc, ptrDiffTy, cir::IntAttr::get(ptrDiffTy, 1)); + // Array elements get constructed in order but destructed in reverse. + mlir::Value stride; + if (isCtor) + stride = builder.getUnsignedInt(loc, 1, sizeTypeSize); + else + stride = builder.getSignedInt(loc, -1, sizeTypeSize); - ctorCall->moveAfter(one); + ctorCall->moveBefore(stride.getDefiningOp()); ctorCall->setOperand(0, currentElement); + auto nextElement = cir::PtrStrideOp::create(builder, loc, eltTy, + currentElement, stride); - // Advance pointer and store them to temporary variable - auto nextElement = - builder.create(loc, eltTy, currentElement, one); + // Store the element pointer to the temporary variable builder.createStore(loc, nextElement, tmpAddr); builder.createYield(loc); }); @@ -230,6 +237,18 @@ static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder, op->erase(); } +void LoweringPreparePass::lowerArrayDtor(cir::ArrayDtor op) { + CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointAfter(op.getOperation()); + + mlir::Type eltTy = op->getRegion(0).getArgument(0).getType(); + assert(!cir::MissingFeatures::vlas()); + auto arrayLen = + mlir::cast(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()); @@ -238,13 +257,15 @@ void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) { assert(!cir::MissingFeatures::vlas()); auto arrayLen = mlir::cast(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) { if (auto arrayCtor = dyn_cast(op)) lowerArrayCtor(arrayCtor); + else if (auto arrayDtor = dyn_cast(op)) + lowerArrayDtor(arrayDtor); else if (auto cast = mlir::dyn_cast(op)) lowerCastOp(cast); else if (auto unary = mlir::dyn_cast(op)) @@ -257,7 +278,8 @@ void LoweringPreparePass::runOnOperation() { llvm::SmallVector opsToTransform; op->walk([&](mlir::Operation *op) { - if (mlir::isa(op)) + if (mlir::isa( + op)) opsToTransform.push_back(op); }); diff --git a/clang/test/CIR/CodeGen/array-ctor.cpp b/clang/test/CIR/CodeGen/array-ctor.cpp index b3d81a8ab82a4..c373acf0bff8c 100644 --- a/clang/test/CIR/CodeGen/array-ctor.cpp +++ b/clang/test/CIR/CodeGen/array-ctor.cpp @@ -33,8 +33,8 @@ void foo() { // CIR: cir.store %[[DECAY]], %[[ITER]] : !cir.ptr, !cir.ptr> // CIR: cir.do { // CIR: %[[CURRENT:.*]] = cir.load %[[ITER]] : !cir.ptr>, !cir.ptr -// CIR: %[[CONST1:.*]] = cir.const #cir.int<1> : !u64i // CIR: cir.call @_ZN1SC1Ev(%[[CURRENT]]) : (!cir.ptr) -> () +// CIR: %[[CONST1:.*]] = cir.const #cir.int<1> : !u64i // CIR: %[[NEXT:.*]] = cir.ptr_stride(%[[CURRENT]] : !cir.ptr, %[[CONST1]] : !u64i), !cir.ptr // CIR: cir.store %[[NEXT]], %[[ITER]] : !cir.ptr, !cir.ptr> // CIR: cir.yield diff --git a/clang/test/CIR/CodeGen/array-dtor.cpp b/clang/test/CIR/CodeGen/array-dtor.cpp new file mode 100644 index 0000000000000..3edc6f1a6538d --- /dev/null +++ b/clang/test/CIR/CodeGen/array-dtor.cpp @@ -0,0 +1,104 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before-lp.cir +// RUN: FileCheck --input-file=%t-before-lp.cir %s -check-prefix=CIR-BEFORE-LPP +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG + +struct S { + ~S(); +}; + +void test_cleanup_array() { + S s[42]; +} + +// CIR-BEFORE-LPP: cir.func{{.*}} @_Z18test_cleanup_arrayv() +// CIR-BEFORE-LPP: %[[S:.*]] = cir.alloca !cir.array, !cir.ptr>, ["s"] +// CIR-BEFORE-LPP: cir.array.dtor %[[S]] : !cir.ptr> { +// CIR-BEFORE-LPP: ^bb0(%arg0: !cir.ptr +// CIR-BEFORE-LPP: cir.call @_ZN1SD1Ev(%arg0) nothrow : (!cir.ptr) -> () +// CIR-BEFORE-LPP: cir.yield +// CIR-BEFORE-LPP: } +// CIR-BEFORE-LPP: cir.return + +// CIR: cir.func{{.*}} @_Z18test_cleanup_arrayv() +// CIR: %[[S:.*]] = cir.alloca !cir.array, !cir.ptr>, ["s"] +// CIR: %[[CONST41:.*]] = cir.const #cir.int<41> : !u64i +// CIR: %[[DECAY:.*]] = cir.cast(array_to_ptrdecay, %[[S]] : !cir.ptr>), !cir.ptr +// CIR: %[[END_PTR:.*]] = cir.ptr_stride(%[[DECAY]] : !cir.ptr, %[[CONST41]] : !u64i), !cir.ptr +// CIR: %[[ITER:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["__array_idx"] +// CIR: cir.store %[[END_PTR]], %[[ITER]] : !cir.ptr, !cir.ptr> +// CIR: cir.do { +// CIR: %[[CURRENT:.*]] = cir.load %[[ITER]] : !cir.ptr>, !cir.ptr +// CIR: cir.call @_ZN1SD1Ev(%[[CURRENT]]) nothrow : (!cir.ptr) -> () +// CIR: %[[CONST_MINUS1:.*]] = cir.const #cir.int<-1> : !s64i +// CIR: %[[NEXT:.*]] = cir.ptr_stride(%[[CURRENT]] : !cir.ptr, %[[CONST_MINUS1]] : !s64i), !cir.ptr +// CIR: cir.store %[[NEXT]], %[[ITER]] : !cir.ptr, !cir.ptr> +// CIR: cir.yield +// CIR: } while { +// CIR: %[[CURRENT2:.*]] = cir.load %[[ITER]] : !cir.ptr>, !cir.ptr +// CIR: %[[CMP:.*]] = cir.cmp(ne, %[[CURRENT2]], %[[DECAY]]) +// CIR: cir.condition(%[[CMP]]) +// CIR: } +// CIR: cir.return + +// LLVM: define{{.*}} void @_Z18test_cleanup_arrayv() +// LLVM: %[[ARRAY:.*]] = alloca [42 x %struct.S] +// LLVM: %[[START:.*]] = getelementptr %struct.S, ptr %[[ARRAY]], i32 0 +// LLVM: %[[END:.*]] = getelementptr %struct.S, ptr %[[START]], i64 41 +// LLVM: %[[ITER:.*]] = alloca ptr +// LLVM: store ptr %[[END]], ptr %[[ITER]] +// LLVM: br label %[[LOOP:.*]] +// LLVM: [[COND:.*]]: +// LLVM: %[[CURRENT_CHECK:.*]] = load ptr, ptr %[[ITER]] +// LLVM: %[[DONE:.*]] = icmp ne ptr %[[CURRENT_CHECK]], %[[START]] +// LLVM: br i1 %[[DONE]], label %[[LOOP]], label %[[EXIT:.*]] +// LLVM: [[LOOP]]: +// LLVM: %[[CURRENT:.*]] = load ptr, ptr %[[ITER]] +// LLVM: call void @_ZN1SD1Ev(ptr %[[CURRENT]]) +// LLVM: %[[NEXT:.*]] = getelementptr %struct.S, ptr %[[CURRENT]], i64 -1 +// LLVM: store ptr %[[NEXT]], ptr %[[ITER]] +// LLVM: br label %[[COND]] +// LLVM: [[EXIT]]: +// LLVM: ret void + +// OGCG: define{{.*}} void @_Z18test_cleanup_arrayv() +// OGCG: %[[ARRAY:.*]] = alloca [42 x %struct.S] +// OGCG: %[[START:.*]] = getelementptr{{.*}} %struct.S{{.*}} +// OGCG: %[[END:.*]] = getelementptr{{.*}} %struct.S{{.*}} i64 42 +// OGCG: br label %[[LOOP:.*]] +// OGCG: [[LOOP]]: +// OGCG: %[[NEXT:.*]] = phi ptr [ %[[END]], %{{.*}} ], [ %[[LAST:.*]], %[[LOOP]] ] +// OGCG: %[[LAST]] = getelementptr{{.*}} %struct.S{{.*}}, ptr %[[NEXT]], i64 -1 +// OGCG: call void @_ZN1SD1Ev(ptr{{.*}} %[[LAST]]) +// OGCG: %[[DONE:.*]] = icmp eq ptr %[[LAST]], %[[START]] +// OGCG: br i1 %[[DONE]], label %[[EXIT:.*]], label %[[LOOP]] +// OGCG: [[EXIT]]: +// OGCG: ret void + +void test_cleanup_zero_length_array() { + S s[0]; +} + +// CIR-BEFORE-LPP: cir.func{{.*}} @_Z30test_cleanup_zero_length_arrayv() +// CIR-BEFORE-LPP: %[[S:.*]] = cir.alloca !cir.array, !cir.ptr>, ["s"] +// CIR-BEFORE-LPP-NOT: cir.array.dtor +// CIR-BEFORE-LPP: cir.return + +// CIR: cir.func{{.*}} @_Z30test_cleanup_zero_length_arrayv() +// CIR: %[[S:.*]] = cir.alloca !cir.array, !cir.ptr>, ["s"] +// CIR-NOT: cir.do +// CIR-NOT: cir.call @_ZN1SD1Ev +// CIR: cir.return + +// LLVM: define{{.*}} void @_Z30test_cleanup_zero_length_arrayv() +// LLVM: alloca [0 x %struct.S] +// LLVM-NOT: call void @_ZN1SD1Ev +// LLVM: ret void + +// OGCG: define{{.*}} void @_Z30test_cleanup_zero_length_arrayv() +// OGCG: alloca [0 x %struct.S] +// OGCG-NOT: call void @_ZN1SD1Ev +// OGCG: ret void diff --git a/clang/test/CIR/IR/array-dtor.cir b/clang/test/CIR/IR/array-dtor.cir new file mode 100644 index 0000000000000..6d08d1639f0db --- /dev/null +++ b/clang/test/CIR/IR/array-dtor.cir @@ -0,0 +1,28 @@ +// RUN: cir-opt %s | FileCheck %s + +!u8i = !cir.int +!rec_S = !cir.record + +module { + cir.func private @_ZN1SD1Ev(!cir.ptr) + cir.func dso_local @_Z3foov() { + %0 = cir.alloca !cir.array, !cir.ptr>, ["s", init] {alignment = 16 : i64} + cir.array.dtor %0 : !cir.ptr> { + ^bb0(%arg0: !cir.ptr): + cir.call @_ZN1SD1Ev(%arg0) : (!cir.ptr) -> () + cir.yield + } + cir.return + } + + // CHECK: cir.func private @_ZN1SD1Ev(!cir.ptr) + // CHECK: cir.func dso_local @_Z3foov() { + // CHECK: %0 = cir.alloca !cir.array, !cir.ptr>, ["s", init] {alignment = 16 : i64} + // CHECK: cir.array.dtor %0 : !cir.ptr> { + // CHECK: ^bb0(%arg0: !cir.ptr): + // CHECK: cir.call @_ZN1SD1Ev(%arg0) : (!cir.ptr) -> () + // CHECK: cir.yield + // CHECK: } + // CHECK: cir.return + // CHECK: } +}