-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[CIR] Add initial support for operator delete #160574
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
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -210,6 +210,60 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall( | |
| return emitCall(fnInfo, callee, returnValue, args, nullptr, loc); | ||
| } | ||
|
|
||
| namespace { | ||
| /// The parameters to pass to a usual operator delete. | ||
| struct UsualDeleteParams { | ||
| TypeAwareAllocationMode typeAwareDelete = TypeAwareAllocationMode::No; | ||
| bool destroyingDelete = false; | ||
| bool size = false; | ||
| AlignedAllocationMode alignment = AlignedAllocationMode::No; | ||
| }; | ||
| } // namespace | ||
|
|
||
| // FIXME(cir): this should be shared with LLVM codegen | ||
| static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *fd) { | ||
| UsualDeleteParams params; | ||
|
|
||
| const FunctionProtoType *fpt = fd->getType()->castAs<FunctionProtoType>(); | ||
| auto ai = fpt->param_type_begin(), ae = fpt->param_type_end(); | ||
|
|
||
| if (fd->isTypeAwareOperatorNewOrDelete()) { | ||
| params.typeAwareDelete = TypeAwareAllocationMode::Yes; | ||
| assert(ai != ae); | ||
| ++ai; | ||
| } | ||
|
|
||
| // The first argument after the type-identity parameter (if any) is | ||
| // always a void* (or C* for a destroying operator delete for class | ||
| // type C). | ||
| ++ai; | ||
|
|
||
| // The next parameter may be a std::destroying_delete_t. | ||
| if (fd->isDestroyingOperatorDelete()) { | ||
| params.destroyingDelete = true; | ||
| assert(ai != ae); | ||
| ++ai; | ||
| } | ||
|
|
||
| // Figure out what other parameters we should be implicitly passing. | ||
| if (ai != ae && (*ai)->isIntegerType()) { | ||
| params.size = true; | ||
| ++ai; | ||
| } else { | ||
| assert(!isTypeAwareAllocation(params.typeAwareDelete)); | ||
| } | ||
|
|
||
| if (ai != ae && (*ai)->isAlignValT()) { | ||
| params.alignment = AlignedAllocationMode::Yes; | ||
| ++ai; | ||
| } else { | ||
| assert(!isTypeAwareAllocation(params.typeAwareDelete)); | ||
| } | ||
|
|
||
| assert(ai == ae && "unexpected usual deallocation function parameter"); | ||
| return params; | ||
| } | ||
|
|
||
| static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e, | ||
| unsigned minElements, | ||
| mlir::Value &numElements, | ||
|
|
@@ -332,6 +386,117 @@ static RValue emitNewDeleteCall(CIRGenFunction &cgf, | |
| return rv; | ||
| } | ||
|
|
||
| namespace { | ||
| /// Calls the given 'operator delete' on a single object. | ||
| struct CallObjectDelete final : EHScopeStack::Cleanup { | ||
| mlir::Value ptr; | ||
| const FunctionDecl *operatorDelete; | ||
| QualType elementType; | ||
|
|
||
| CallObjectDelete(mlir::Value ptr, const FunctionDecl *operatorDelete, | ||
| QualType elementType) | ||
| : ptr(ptr), operatorDelete(operatorDelete), elementType(elementType) {} | ||
|
|
||
| void emit(CIRGenFunction &cgf) override { | ||
| cgf.emitDeleteCall(operatorDelete, ptr, elementType); | ||
| } | ||
|
|
||
| // This is a placeholder until EHCleanupScope is implemented. | ||
| size_t getSize() const override { | ||
| assert(!cir::MissingFeatures::ehCleanupScope()); | ||
| return sizeof(CallObjectDelete); | ||
| } | ||
| }; | ||
| } // namespace | ||
|
|
||
| /// Emit the code for deleting a single object. | ||
| static void emitObjectDelete(CIRGenFunction &cgf, const CXXDeleteExpr *de, | ||
| Address ptr, QualType elementType) { | ||
| // C++11 [expr.delete]p3: | ||
| // If the static type of the object to be deleted is different from its | ||
| // dynamic type, the static type shall be a base class of the dynamic type | ||
| // of the object to be deleted and the static type shall have a virtual | ||
| // destructor or the behavior is undefined. | ||
| assert(!cir::MissingFeatures::emitTypeCheck()); | ||
|
|
||
| const FunctionDecl *operatorDelete = de->getOperatorDelete(); | ||
| assert(!operatorDelete->isDestroyingOperatorDelete()); | ||
|
|
||
| // Find the destructor for the type, if applicable. If the | ||
| // destructor is virtual, we'll just emit the vcall and return. | ||
| const CXXDestructorDecl *dtor = nullptr; | ||
| if (const auto *rd = elementType->getAsCXXRecordDecl()) { | ||
| if (rd->hasDefinition() && !rd->hasTrivialDestructor()) { | ||
| dtor = rd->getDestructor(); | ||
|
|
||
| if (dtor->isVirtual()) { | ||
| cgf.cgm.errorNYI(de->getSourceRange(), | ||
| "emitObjectDelete: virtual destructor"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Make sure that we call delete even if the dtor throws. | ||
| // This doesn't have to a conditional cleanup because we're going | ||
| // to pop it off in a second. | ||
| cgf.ehStack.pushCleanup<CallObjectDelete>( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will eventually ensure that the delete call happens even if an exception occurs, but even without exceptions, this is where the delete call is emitted. |
||
| NormalAndEHCleanup, ptr.getPointer(), operatorDelete, elementType); | ||
|
|
||
| if (dtor) { | ||
| cgf.emitCXXDestructorCall(dtor, Dtor_Complete, | ||
| /*ForVirtualBase=*/false, | ||
| /*Delegating=*/false, ptr, elementType); | ||
| } else if (elementType.getObjCLifetime()) { | ||
| assert(!cir::MissingFeatures::objCLifetime()); | ||
| cgf.cgm.errorNYI(de->getSourceRange(), "emitObjectDelete: ObjCLifetime"); | ||
| } | ||
|
|
||
| // In traditional LLVM codegen null checks are emitted to save a delete call. | ||
| // In CIR we optimize for size by default, the null check should be added into | ||
| // this function callers. | ||
| assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls()); | ||
|
|
||
| cgf.popCleanupBlock(); | ||
| } | ||
|
|
||
| void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) { | ||
| const Expr *arg = e->getArgument(); | ||
| Address ptr = emitPointerWithAlignment(arg); | ||
|
|
||
| // Null check the pointer. | ||
| // | ||
| // We could avoid this null check if we can determine that the object | ||
| // destruction is trivial and doesn't require an array cookie; we can | ||
| // unconditionally perform the operator delete call in that case. For now, we | ||
| // assume that deleted pointers are null rarely enough that it's better to | ||
| // keep the branch. This might be worth revisiting for a -O0 code size win. | ||
| // | ||
| // CIR note: emit the code size friendly by default for now, such as mentioned | ||
| // in `emitObjectDelete`. | ||
| assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls()); | ||
| QualType deleteTy = e->getDestroyedType(); | ||
|
|
||
| // A destroying operator delete overrides the entire operation of the | ||
| // delete expression. | ||
| if (e->getOperatorDelete()->isDestroyingOperatorDelete()) { | ||
| cgm.errorNYI(e->getSourceRange(), | ||
| "emitCXXDeleteExpr: destroying operator delete"); | ||
| return; | ||
| } | ||
|
|
||
| // We might be deleting a pointer to array. | ||
| deleteTy = getContext().getBaseElementType(deleteTy); | ||
| ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy)); | ||
|
|
||
| if (e->isArrayForm()) { | ||
| assert(!cir::MissingFeatures::deleteArray()); | ||
| cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete"); | ||
| return; | ||
| } else { | ||
| emitObjectDelete(*this, e, ptr, deleteTy); | ||
| } | ||
| } | ||
|
|
||
| mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { | ||
| // The element type being allocated. | ||
| QualType allocType = getContext().getBaseElementType(e->getAllocatedType()); | ||
|
|
@@ -443,3 +608,53 @@ mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) { | |
| allocSizeWithoutCookie); | ||
| return result.getPointer(); | ||
| } | ||
|
|
||
| void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD, | ||
| mlir::Value ptr, QualType deleteTy) { | ||
| assert(!cir::MissingFeatures::deleteArray()); | ||
|
|
||
| const auto *deleteFTy = deleteFD->getType()->castAs<FunctionProtoType>(); | ||
| CallArgList deleteArgs; | ||
|
|
||
| UsualDeleteParams params = getUsualDeleteParams(deleteFD); | ||
| auto paramTypeIt = deleteFTy->param_type_begin(); | ||
|
|
||
| // Pass std::type_identity tag if present | ||
| if (isTypeAwareAllocation(params.typeAwareDelete)) | ||
| cgm.errorNYI(deleteFD->getSourceRange(), | ||
| "emitDeleteCall: type aware delete"); | ||
|
|
||
| // Pass the pointer itself. | ||
| QualType argTy = *paramTypeIt++; | ||
| mlir::Value deletePtr = | ||
| builder.createBitcast(ptr.getLoc(), ptr, convertType(argTy)); | ||
| deleteArgs.add(RValue::get(deletePtr), argTy); | ||
|
|
||
| // Pass the std::destroying_delete tag if present. | ||
| if (params.destroyingDelete) | ||
| cgm.errorNYI(deleteFD->getSourceRange(), | ||
| "emitDeleteCall: destroying delete"); | ||
|
|
||
| // Pass the size if the delete function has a size_t parameter. | ||
| if (params.size) { | ||
| QualType sizeType = *paramTypeIt++; | ||
| CharUnits deleteTypeSize = getContext().getTypeSizeInChars(deleteTy); | ||
| assert(mlir::isa<cir::IntType>(convertType(sizeType)) && | ||
| "expected cir::IntType"); | ||
| cir::ConstantOp size = builder.getConstInt( | ||
| *currSrcLoc, convertType(sizeType), deleteTypeSize.getQuantity()); | ||
|
|
||
| deleteArgs.add(RValue::get(size), sizeType); | ||
| } | ||
|
|
||
| // Pass the alignment if the delete function has an align_val_t parameter. | ||
| if (isAlignedAllocation(params.alignment)) | ||
| cgm.errorNYI(deleteFD->getSourceRange(), | ||
| "emitDeleteCall: aligned allocation"); | ||
|
|
||
| assert(paramTypeIt == deleteFTy->param_type_end() && | ||
| "unknown parameter to usual delete function"); | ||
|
|
||
| // Emit the call to delete. | ||
| emitNewDeleteCall(*this, deleteFD, deleteFTy, deleteArgs); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-cir %s -o %t.cir | ||
| // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s | ||
| // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -mconstructor-aliases -emit-llvm %s -o %t-cir.ll | ||
| // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s | ||
| // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -mconstructor-aliases -emit-llvm %s -o %t.ll | ||
| // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s | ||
|
|
||
| typedef __typeof(sizeof(int)) size_t; | ||
|
|
||
| struct SizedDelete { | ||
| void operator delete(void*, size_t); | ||
| int member; | ||
| }; | ||
| void test_sized_delete(SizedDelete *x) { | ||
| delete x; | ||
| } | ||
|
|
||
| // SizedDelete::operator delete(void*, unsigned long) | ||
| // CIR: cir.func private @_ZN11SizedDeletedlEPvm(!cir.ptr<!void>, !u64i) | ||
| // LLVM: declare void @_ZN11SizedDeletedlEPvm(ptr, i64) | ||
|
|
||
| // CIR: cir.func dso_local @_Z17test_sized_deleteP11SizedDelete | ||
| // CIR: %[[X:.*]] = cir.load{{.*}} %{{.*}} | ||
| // CIR: %[[X_CAST:.*]] = cir.cast(bitcast, %[[X]] : !cir.ptr<!rec_SizedDelete>), !cir.ptr<!void> | ||
| // CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<4> : !u64i | ||
| // CIR: cir.call @_ZN11SizedDeletedlEPvm(%[[X_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> () | ||
|
|
||
| // LLVM: define dso_local void @_Z17test_sized_deleteP11SizedDelete | ||
| // LLVM: %[[X:.*]] = load ptr, ptr %{{.*}} | ||
| // LLVM: call void @_ZN11SizedDeletedlEPvm(ptr %[[X]], i64 4) | ||
|
|
||
| // OGCG: define dso_local void @_Z17test_sized_deleteP11SizedDelete | ||
| // OGCG: %[[X:.*]] = load ptr, ptr %{{.*}} | ||
| // OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[X]], null | ||
| // OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]] | ||
| // OGCG: [[DELETE_NOTNULL]]: | ||
| // OGCG: call void @_ZN11SizedDeletedlEPvm(ptr noundef %[[X]], i64 noundef 4) | ||
|
|
||
| // This function is declared below the call in OGCG. | ||
| // OGCG: declare void @_ZN11SizedDeletedlEPvm(ptr noundef, i64 noundef) | ||
|
|
||
| struct Contents { | ||
| ~Contents() {} | ||
| }; | ||
| struct Container { | ||
| Contents *contents; | ||
| ~Container(); | ||
| }; | ||
| Container::~Container() { delete contents; } | ||
|
|
||
| // Contents::~Contents() | ||
| // CIR: cir.func comdat linkonce_odr @_ZN8ContentsD2Ev | ||
| // LLVM: define linkonce_odr void @_ZN8ContentsD2Ev | ||
|
|
||
| // operator delete(void*, unsigned long) | ||
| // CIR: cir.func private @_ZdlPvm(!cir.ptr<!void>, !u64i) | ||
| // LLVM: declare void @_ZdlPvm(ptr, i64) | ||
|
|
||
| // Container::~Container() | ||
| // CIR: cir.func dso_local @_ZN9ContainerD2Ev | ||
| // CIR: %[[THIS:.*]] = cir.load %{{.*}} | ||
| // CIR: %[[CONTENTS_PTR_ADDR:.*]] = cir.get_member %[[THIS]][0] {name = "contents"} : !cir.ptr<!rec_Container> -> !cir.ptr<!cir.ptr<!rec_Contents>> | ||
| // CIR: %[[CONTENTS_PTR:.*]] = cir.load{{.*}} %[[CONTENTS_PTR_ADDR]] | ||
| // CIR: cir.call @_ZN8ContentsD2Ev(%[[CONTENTS_PTR]]) nothrow : (!cir.ptr<!rec_Contents>) -> () | ||
| // CIR: %[[CONTENTS_CAST:.*]] = cir.cast(bitcast, %[[CONTENTS_PTR]] : !cir.ptr<!rec_Contents>), !cir.ptr<!void> | ||
| // CIR: %[[OBJ_SIZE:.*]] = cir.const #cir.int<1> : !u64i | ||
| // CIR: cir.call @_ZdlPvm(%[[CONTENTS_CAST]], %[[OBJ_SIZE]]) nothrow : (!cir.ptr<!void>, !u64i) -> () | ||
|
|
||
| // LLVM: define dso_local void @_ZN9ContainerD2Ev | ||
| // LLVM: %[[THIS:.*]] = load ptr, ptr %{{.*}} | ||
| // LLVM: %[[CONTENTS_PTR_ADDR:.*]] = getelementptr %struct.Container, ptr %[[THIS]], i32 0, i32 0 | ||
| // LLVM: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS_PTR_ADDR]] | ||
| // LLVM: call void @_ZN8ContentsD2Ev(ptr %[[CONTENTS_PTR]]) | ||
| // LLVM: call void @_ZdlPvm(ptr %[[CONTENTS_PTR]], i64 1) | ||
|
|
||
| // OGCG: define dso_local void @_ZN9ContainerD2Ev | ||
| // OGCG: %[[THIS:.*]] = load ptr, ptr %{{.*}} | ||
| // OGCG: %[[CONTENTS:.*]] = getelementptr inbounds nuw %struct.Container, ptr %[[THIS]], i32 0, i32 0 | ||
| // OGCG: %[[CONTENTS_PTR:.*]] = load ptr, ptr %[[CONTENTS]] | ||
| // OGCG: %[[ISNULL:.*]] = icmp eq ptr %[[CONTENTS_PTR]], null | ||
| // OGCG: br i1 %[[ISNULL]], label %{{.*}}, label %[[DELETE_NOTNULL:.*]] | ||
| // OGCG: [[DELETE_NOTNULL]]: | ||
| // OGCG: call void @_ZN8ContentsD2Ev(ptr noundef nonnull align 1 dereferenceable(1) %[[CONTENTS_PTR]]) | ||
| // OGCG: call void @_ZdlPvm(ptr noundef %[[CONTENTS_PTR]], i64 noundef 1) | ||
|
|
||
| // These functions are declared/defined below the calls in OGCG. | ||
| // OGCG: define linkonce_odr void @_ZN8ContentsD2Ev | ||
| // OGCG: declare void @_ZdlPvm(ptr noundef, i64 noundef) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created #160554 to refactor this for sharing. If that's accepted before this PR lands, I'll update this here. Otherwise, I'll do that as a follow-up.