-
Notifications
You must be signed in to change notification settings - Fork 10.6k
[cxx-interop] Add support for custom C++ destructors. #32291
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,20 +16,25 @@ | |
|
|
||
| #include "GenStruct.h" | ||
|
|
||
| #include "swift/AST/Types.h" | ||
| #include "swift/AST/ClangModuleLoader.h" | ||
| #include "swift/AST/Decl.h" | ||
| #include "swift/AST/IRGenOptions.h" | ||
| #include "swift/AST/Pattern.h" | ||
| #include "swift/AST/SubstitutionMap.h" | ||
| #include "swift/AST/Types.h" | ||
| #include "swift/IRGen/Linking.h" | ||
| #include "swift/SIL/SILModule.h" | ||
| #include "llvm/IR/DerivedTypes.h" | ||
| #include "llvm/IR/Function.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/AST/Attr.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/AST/GlobalDecl.h" | ||
| #include "clang/AST/Mangle.h" | ||
| #include "clang/AST/RecordLayout.h" | ||
| #include "clang/CodeGen/CodeGenABITypes.h" | ||
| #include "clang/CodeGen/SwiftCallingConv.h" | ||
| #include "clang/Sema/Sema.h" | ||
| #include "llvm/IR/DerivedTypes.h" | ||
| #include "llvm/IR/Function.h" | ||
|
|
||
| #include "GenMeta.h" | ||
| #include "GenRecord.h" | ||
|
|
@@ -62,6 +67,24 @@ static StructTypeInfoKind getStructTypeInfoKind(const TypeInfo &type) { | |
| return (StructTypeInfoKind) type.getSubclassKind(); | ||
| } | ||
|
|
||
| /// If this type has a CXXDestructorDecl, find it and return it. Otherwise, | ||
| /// return nullptr. | ||
| static clang::CXXDestructorDecl *getCXXDestructor(SILType type) { | ||
| auto *structDecl = type.getStructOrBoundGenericStruct(); | ||
| if (!structDecl || !structDecl->getClangDecl()) | ||
| return nullptr; | ||
| const clang::CXXRecordDecl *cxxRecordDecl = | ||
| dyn_cast<clang::CXXRecordDecl>(structDecl->getClangDecl()); | ||
| if (!cxxRecordDecl) | ||
| return nullptr; | ||
| for (auto member : cxxRecordDecl->methods()) { | ||
| if (auto dest = dyn_cast<clang::CXXDestructorDecl>(member)) { | ||
| return dest; | ||
| } | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| namespace { | ||
| class StructFieldInfo : public RecordField<StructFieldInfo> { | ||
| public: | ||
|
|
@@ -362,11 +385,60 @@ namespace { | |
| // with user-defined special member functions. | ||
| SpareBitVector(llvm::Optional<APInt>{ | ||
| llvm::APInt(size.getValueInBits(), 0)}), | ||
| align, IsPOD, IsNotBitwiseTakable, IsFixedSize), | ||
| align, IsNotPOD, IsNotBitwiseTakable, IsFixedSize), | ||
| ClangDecl(clangDecl) { | ||
| (void)ClangDecl; | ||
| } | ||
|
|
||
| void destroy(IRGenFunction &IGF, Address address, SILType T, | ||
| bool isOutlined) const override { | ||
| auto *destructor = getCXXDestructor(T); | ||
| // If the destructor is trivial, clang will assert when we call | ||
| // `emitCXXDestructorCall` so, just let Swift handle this destructor. | ||
| if (!destructor || destructor->isTrivial()) { | ||
zoecarver marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
gribozavr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // If we didn't find a destructor to call, bail out to the parent | ||
| // implementation. | ||
| StructTypeInfoBase<AddressOnlyClangRecordTypeInfo, FixedTypeInfo, | ||
| ClangFieldInfo>::destroy(IGF, address, T, | ||
| isOutlined); | ||
| return; | ||
| } | ||
|
|
||
| if (!destructor->isUserProvided() && | ||
| !destructor->doesThisDeclarationHaveABody()) { | ||
| assert(!destructor->isDeleted() && | ||
zoecarver marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "Swift cannot handle a type with no known destructor."); | ||
| // Make sure we define the destructor so we have something to call. | ||
| auto &sema = IGF.IGM.Context.getClangModuleLoader()->getClangSema(); | ||
| sema.DefineImplicitDestructor(clang::SourceLocation(), destructor); | ||
| } | ||
|
|
||
| clang::GlobalDecl destructorGlobalDecl(destructor, clang::Dtor_Complete); | ||
| auto *destructorFnAddr = | ||
| cast<llvm::Function>(IGF.IGM.getAddrOfClangGlobalDecl( | ||
| destructorGlobalDecl, NotForDefinition)); | ||
|
|
||
| SmallVector<llvm::Value *, 2> args; | ||
| auto *thisArg = IGF.coerceValue(address.getAddress(), | ||
| destructorFnAddr->getArg(0)->getType(), | ||
| IGF.IGM.DataLayout); | ||
| args.push_back(thisArg); | ||
| llvm::Value *implicitParam = | ||
| clang::CodeGen::getCXXDestructorImplicitParam( | ||
| IGF.IGM.getClangCGM(), IGF.Builder.GetInsertBlock(), | ||
| IGF.Builder.GetInsertPoint(), destructor, clang::Dtor_Complete, | ||
| false, false); | ||
| if (implicitParam) { | ||
| implicitParam = IGF.coerceValue(implicitParam, | ||
|
||
| destructorFnAddr->getArg(1)->getType(), | ||
| IGF.IGM.DataLayout); | ||
| args.push_back(implicitParam); | ||
| } | ||
|
|
||
| IGF.Builder.CreateCall(destructorFnAddr->getFunctionType(), | ||
| destructorFnAddr, args); | ||
| } | ||
|
|
||
| TypeLayoutEntry *buildTypeLayoutEntry(IRGenModule &IGM, | ||
| SILType T) const override { | ||
| return IGM.typeLayoutCache.getOrCreateScalarEntry(*this, T); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1062,10 +1062,10 @@ class IRGenModule { | |
| SILType objectType, const TypeInfo &objectTI, | ||
| const OutliningMetadataCollector &collector); | ||
|
|
||
| private: | ||
|
||
| llvm::Constant *getAddrOfClangGlobalDecl(clang::GlobalDecl global, | ||
| ForDefinition_t forDefinition); | ||
|
|
||
| private: | ||
| using CopyAddrHelperGenerator = | ||
| llvm::function_ref<void(IRGenFunction &IGF, Address dest, Address src, | ||
| SILType objectType, const TypeInfo &objectTI)>; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #ifndef TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H | ||
| #define TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H | ||
|
|
||
| struct DummyStruct {}; | ||
|
|
||
| struct HasUserProvidedDestructorAndDummy { | ||
| DummyStruct dummy; | ||
| ~HasUserProvidedDestructorAndDummy() {} | ||
| }; | ||
|
|
||
| struct HasUserProvidedDestructor { | ||
| int *value; | ||
| ~HasUserProvidedDestructor() { *value = 42; } | ||
| }; | ||
|
|
||
| struct HasNonTrivialImplicitDestructor { | ||
| HasUserProvidedDestructor member; | ||
| }; | ||
|
|
||
| #endif // TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // RUN: %swift -I %S/Inputs -enable-cxx-interop -emit-ir %s | %FileCheck %s | ||
|
|
||
| import Destructors | ||
|
|
||
| // CHECK-LABEL: define {{.*}}void @"$s4main4testyyF" | ||
| // CHECK: [[H:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV | ||
| // CHECK: [[CXX_THIS:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[H]] to %struct.HasUserProvidedDestructorAndDummy* | ||
| // CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_THIS]]) | ||
| // CHECK: ret void | ||
| public func test() { | ||
| let d = DummyStruct() | ||
| let h = HasUserProvidedDestructorAndDummy(dummy: d) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s | ||
|
|
||
| import Destructors | ||
|
|
||
| // CHECK-LABEL: define {{.*}}void @"$s4main35testHasNonTrivialImplicitDestructoryyF" | ||
| // CHECK: call {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD(1|2)Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor* | ||
| // CHECK: ret void | ||
|
|
||
| // TODO: Somehow check that _ZN31HasNonTrivialImplicitDestructorD1Ev (if present) calls _ZN25HasUserProvidedDestructorD2Ev. | ||
|
|
||
| public func testHasNonTrivialImplicitDestructor() { | ||
| _ = HasNonTrivialImplicitDestructor() | ||
| } | ||
|
|
||
| // Check that we call the base destructor. | ||
| // CHECK-LABEL: define {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD2Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor* | ||
| // CHECK: call {{.*}}@{{_ZN25HasUserProvidedDestructorD(1|2)Ev|"\?\?1HasUserProvidedDestructor@@QEAA@XZ"}} | ||
| // CHECK: ret |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| #ifndef TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H | ||
| #define TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H | ||
|
|
||
| struct HasUserProvidedDestructor { | ||
zoecarver marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| int *value; | ||
| ~HasUserProvidedDestructor() { *value = 42; } | ||
| }; | ||
|
|
||
| struct HasEmptyDestructorAndMemberWithUserDefinedConstructor { | ||
| HasUserProvidedDestructor member; | ||
| ~HasEmptyDestructorAndMemberWithUserDefinedConstructor() { /* empty */ | ||
| } | ||
| }; | ||
|
|
||
| struct HasNonTrivialImplicitDestructor { | ||
| HasUserProvidedDestructor member; | ||
| }; | ||
|
|
||
| struct HasNonTrivialDefaultedDestructor { | ||
| HasUserProvidedDestructor member; | ||
| ~HasNonTrivialDefaultedDestructor() = default; | ||
| }; | ||
|
|
||
| struct HasDefaultedDestructor { | ||
| ~HasDefaultedDestructor() = default; | ||
| }; | ||
|
|
||
| // For the following objects with virtual bases / destructors, make sure that | ||
| // any exectuable user of these objects disable rtti and exceptions. Otherwise, | ||
| // the linker will error because of undefined vtables. | ||
| // FIXME: Once we can link with libc++ we can enable RTTI. | ||
|
|
||
| struct HasVirtualBaseAndDestructor : virtual HasDefaultedDestructor { | ||
| int *value; | ||
| HasVirtualBaseAndDestructor(int *value) : value(value) {} | ||
| ~HasVirtualBaseAndDestructor() { *value = 42; } | ||
| }; | ||
|
|
||
| struct HasVirtualDestructor { | ||
| // An object with a virtual destructor requires a delete operator in case | ||
| // we try to delete the base object. Until we can link against libc++, use | ||
| // this dummy implementation. | ||
| static void operator delete(void *p) { __builtin_unreachable(); } | ||
| virtual ~HasVirtualDestructor(){}; | ||
| }; | ||
|
|
||
| struct HasVirtualDefaultedDestructor { | ||
| static void operator delete(void *p) { __builtin_unreachable(); } | ||
| virtual ~HasVirtualDefaultedDestructor() = default; | ||
| }; | ||
|
|
||
| struct HasBaseWithVirtualDestructor : HasVirtualDestructor { | ||
| int *value; | ||
| HasBaseWithVirtualDestructor(int *value) : value(value) {} | ||
| ~HasBaseWithVirtualDestructor() { *value = 42; } | ||
| }; | ||
|
|
||
| struct HasVirtualBaseWithVirtualDestructor : virtual HasVirtualDestructor { | ||
| int *value; | ||
| HasVirtualBaseWithVirtualDestructor(int *value) : value(value) {} | ||
| ~HasVirtualBaseWithVirtualDestructor() { *value = 42; } | ||
| }; | ||
|
|
||
| struct DummyStruct {}; | ||
|
|
||
| struct HasUserProvidedDestructorAndDummy { | ||
| DummyStruct dummy; | ||
| ~HasUserProvidedDestructorAndDummy() {} | ||
| }; | ||
|
|
||
| // Make sure that we don't crash on struct templates with destructors. | ||
| template <typename T> struct S { | ||
| ~S() {} | ||
| }; | ||
|
|
||
| #endif // TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| module CustomDestructor { | ||
| header "custom-destructors.h" | ||
zoecarver marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| requires cplusplus | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s | ||
|
|
||
| import CustomDestructor | ||
|
|
||
| protocol InitWithDummy { | ||
| init(dummy: DummyStruct) | ||
| } | ||
|
|
||
| extension HasUserProvidedDestructorAndDummy : InitWithDummy { } | ||
|
|
||
| // Make sure the destructor is added as a witness. | ||
| // CHECK: @"$sSo33HasUserProvidedDestructorAndDummyVWV" = linkonce_odr hidden constant %swift.vwtable | ||
| // CHECK-SAME: i8* bitcast (void (%swift.opaque*, %swift.type*)* @"$sSo33HasUserProvidedDestructorAndDummyVwxx" to i8*) | ||
|
|
||
| // CHECK-LABEL: define {{.*}}void @"$s4main37testHasUserProvidedDestructorAndDummyyyF" | ||
| // CHECK: [[OBJ:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV | ||
| // CHECK: [[CXX_OBJ:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[OBJ]] to %struct.HasUserProvidedDestructorAndDummy* | ||
| // CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_OBJ]]) | ||
| // CHECK: ret void | ||
|
|
||
| // Make sure we not only declare but define the destructor. | ||
| // CHECK-LABEL: define {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}} | ||
| // CHECK: ret | ||
| public func testHasUserProvidedDestructorAndDummy() { | ||
| _ = HasUserProvidedDestructorAndDummy(dummy: DummyStruct()) | ||
| } | ||
|
|
||
| // CHECK-LABEL: define {{.*}}void @"$s4main26testHasDefaultedDestructoryyF" | ||
| // CHECK: call {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor* | ||
| // CHECK: ret void | ||
|
|
||
| // CHECK-LABEL: define {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor* | ||
| // CHECK: ret | ||
| public func testHasDefaultedDestructor() { | ||
| _ = HasDefaultedDestructor() | ||
| } | ||
|
|
||
| // Make sure the destroy value witness calls the destructor. | ||
| // CHECK-LABEL: define {{.*}}void @"$sSo33HasUserProvidedDestructorAndDummyVwxx" | ||
| // CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* | ||
| // CHECK: ret |
Uh oh!
There was an error while loading. Please reload this page.