Skip to content

Commit ffa0d1c

Browse files
committed
[cxx-interop] Add support for custom C++ destructors.
This patch adds support for custom C++ destructors. The most notable thing here, I think, is that this is the first place a struct type has a custom destructor. I suspect with more code we will expose a few places where optimization passes need to be fixed to account for this. One of many patches to fix SR-12797.
1 parent bbe3d4f commit ffa0d1c

13 files changed

+466
-5
lines changed

lib/IRGen/GenStruct.cpp

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,25 @@
1616

1717
#include "GenStruct.h"
1818

19-
#include "swift/AST/Types.h"
19+
#include "swift/AST/ClangModuleLoader.h"
2020
#include "swift/AST/Decl.h"
2121
#include "swift/AST/IRGenOptions.h"
2222
#include "swift/AST/Pattern.h"
2323
#include "swift/AST/SubstitutionMap.h"
24+
#include "swift/AST/Types.h"
2425
#include "swift/IRGen/Linking.h"
2526
#include "swift/SIL/SILModule.h"
26-
#include "llvm/IR/DerivedTypes.h"
27-
#include "llvm/IR/Function.h"
2827
#include "clang/AST/ASTContext.h"
2928
#include "clang/AST/Attr.h"
3029
#include "clang/AST/Decl.h"
30+
#include "clang/AST/GlobalDecl.h"
31+
#include "clang/AST/Mangle.h"
3132
#include "clang/AST/RecordLayout.h"
33+
#include "clang/CodeGen/CodeGenABITypes.h"
3234
#include "clang/CodeGen/SwiftCallingConv.h"
35+
#include "clang/Sema/Sema.h"
36+
#include "llvm/IR/DerivedTypes.h"
37+
#include "llvm/IR/Function.h"
3338

3439
#include "GenMeta.h"
3540
#include "GenRecord.h"
@@ -62,6 +67,24 @@ static StructTypeInfoKind getStructTypeInfoKind(const TypeInfo &type) {
6267
return (StructTypeInfoKind) type.getSubclassKind();
6368
}
6469

70+
/// If this type has a CXXDestructorDecl, find it and return it. Otherwise,
71+
/// return nullptr.
72+
static clang::CXXDestructorDecl *getCXXDestructor(SILType type) {
73+
auto *structDecl = type.getStructOrBoundGenericStruct();
74+
if (!structDecl || !structDecl->getClangDecl())
75+
return nullptr;
76+
const clang::CXXRecordDecl *cxxRecordDecl =
77+
dyn_cast<clang::CXXRecordDecl>(structDecl->getClangDecl());
78+
if (!cxxRecordDecl)
79+
return nullptr;
80+
for (auto member : cxxRecordDecl->methods()) {
81+
if (auto dest = dyn_cast<clang::CXXDestructorDecl>(member)) {
82+
return dest;
83+
}
84+
}
85+
return nullptr;
86+
}
87+
6588
namespace {
6689
class StructFieldInfo : public RecordField<StructFieldInfo> {
6790
public:
@@ -362,11 +385,60 @@ namespace {
362385
// with user-defined special member functions.
363386
SpareBitVector(llvm::Optional<APInt>{
364387
llvm::APInt(size.getValueInBits(), 0)}),
365-
align, IsPOD, IsNotBitwiseTakable, IsFixedSize),
388+
align, IsNotPOD, IsNotBitwiseTakable, IsFixedSize),
366389
ClangDecl(clangDecl) {
367390
(void)ClangDecl;
368391
}
369392

393+
void destroy(IRGenFunction &IGF, Address address, SILType T,
394+
bool isOutlined) const override {
395+
auto *destructor = getCXXDestructor(T);
396+
// If the destructor is trivial, clang will assert when we call
397+
// `emitCXXDestructorCall` so, just let Swift handle this destructor.
398+
if (!destructor || destructor->isTrivial()) {
399+
// If we didn't find a destructor to call, bail out to the parent
400+
// implementation.
401+
StructTypeInfoBase<AddressOnlyClangRecordTypeInfo, FixedTypeInfo,
402+
ClangFieldInfo>::destroy(IGF, address, T,
403+
isOutlined);
404+
return;
405+
}
406+
407+
if (!destructor->isUserProvided() &&
408+
!destructor->doesThisDeclarationHaveABody()) {
409+
assert(!destructor->isDeleted() &&
410+
"Swift cannot handle a type with no known destructor.");
411+
// Make sure we define the destructor so we have something to call.
412+
auto &sema = IGF.IGM.Context.getClangModuleLoader()->getClangSema();
413+
sema.DefineImplicitDestructor(clang::SourceLocation(), destructor);
414+
}
415+
416+
clang::GlobalDecl destructorGlobalDecl(destructor, clang::Dtor_Complete);
417+
auto *destructorFnAddr =
418+
cast<llvm::Function>(IGF.IGM.getAddrOfClangGlobalDecl(
419+
destructorGlobalDecl, NotForDefinition));
420+
421+
SmallVector<llvm::Value *, 2> args;
422+
auto *thisArg = IGF.coerceValue(address.getAddress(),
423+
destructorFnAddr->getArg(0)->getType(),
424+
IGF.IGM.DataLayout);
425+
args.push_back(thisArg);
426+
llvm::Value *implicitParam =
427+
clang::CodeGen::getCXXDestructorImplicitParam(
428+
IGF.IGM.getClangCGM(), IGF.Builder.GetInsertBlock(),
429+
IGF.Builder.GetInsertPoint(), destructor, clang::Dtor_Complete,
430+
false, false);
431+
if (implicitParam) {
432+
implicitParam = IGF.coerceValue(implicitParam,
433+
destructorFnAddr->getArg(1)->getType(),
434+
IGF.IGM.DataLayout);
435+
args.push_back(implicitParam);
436+
}
437+
438+
IGF.Builder.CreateCall(destructorFnAddr->getFunctionType(),
439+
destructorFnAddr, args);
440+
}
441+
370442
TypeLayoutEntry *buildTypeLayoutEntry(IRGenModule &IGM,
371443
SILType T) const override {
372444
return IGM.typeLayoutCache.getOrCreateScalarEntry(*this, T);

lib/IRGen/IRGenModule.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,10 +1062,10 @@ class IRGenModule {
10621062
SILType objectType, const TypeInfo &objectTI,
10631063
const OutliningMetadataCollector &collector);
10641064

1065-
private:
10661065
llvm::Constant *getAddrOfClangGlobalDecl(clang::GlobalDecl global,
10671066
ForDefinition_t forDefinition);
10681067

1068+
private:
10691069
using CopyAddrHelperGenerator =
10701070
llvm::function_ref<void(IRGenFunction &IGF, Address dest, Address src,
10711071
SILType objectType, const TypeInfo &objectTI)>;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H
2+
#define TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H
3+
4+
struct DummyStruct {};
5+
6+
struct HasUserProvidedDestructorAndDummy {
7+
DummyStruct dummy;
8+
~HasUserProvidedDestructorAndDummy() {}
9+
};
10+
11+
struct HasUserProvidedDestructor {
12+
int *value;
13+
~HasUserProvidedDestructor() { *value = 42; }
14+
};
15+
16+
struct HasNonTrivialImplicitDestructor {
17+
HasUserProvidedDestructor member;
18+
};
19+
20+
#endif // TEST_INTEROP_CXX_CLASS_INPUTS_DESTRUCTORS_H

test/Interop/Cxx/class/Inputs/module.modulemap

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ module ConstructorsObjC {
1818
requires cplusplus
1919
}
2020

21+
module Destructors {
22+
header "destructors.h"
23+
requires cplusplus
24+
}
25+
2126
module LoadableTypes {
2227
header "loadable-types.h"
2328
requires cplusplus
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %swift -I %S/Inputs -enable-cxx-interop -emit-ir %s | %FileCheck %s
2+
3+
import Destructors
4+
5+
// CHECK-LABEL: define {{.*}}void @"$s4main4testyyF"
6+
// CHECK: [[H:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV
7+
// CHECK: [[CXX_THIS:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[H]] to %struct.HasUserProvidedDestructorAndDummy*
8+
// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_THIS]])
9+
// CHECK: ret void
10+
public func test() {
11+
let d = DummyStruct()
12+
let h = HasUserProvidedDestructorAndDummy(dummy: d)
13+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s
2+
3+
import Destructors
4+
5+
// CHECK-LABEL: define {{.*}}void @"$s4main35testHasNonTrivialImplicitDestructoryyF"
6+
// CHECK: call {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD(1|2)Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor*
7+
// CHECK: ret void
8+
9+
// TODO: Somehow check that _ZN31HasNonTrivialImplicitDestructorD1Ev (if present) calls _ZN25HasUserProvidedDestructorD2Ev.
10+
11+
public func testHasNonTrivialImplicitDestructor() {
12+
_ = HasNonTrivialImplicitDestructor()
13+
}
14+
15+
// Check that we call the base destructor.
16+
// CHECK-LABEL: define {{.*}}@{{_ZN31HasNonTrivialImplicitDestructorD2Ev|"\?\?1HasNonTrivialImplicitDestructor@@QEAA@XZ"}}(%struct.HasNonTrivialImplicitDestructor*
17+
// CHECK: call {{.*}}@{{_ZN25HasUserProvidedDestructorD(1|2)Ev|"\?\?1HasUserProvidedDestructor@@QEAA@XZ"}}
18+
// CHECK: ret
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#ifndef TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H
2+
#define TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H
3+
4+
struct HasUserProvidedDestructor {
5+
int *value;
6+
~HasUserProvidedDestructor() { *value = 42; }
7+
};
8+
9+
struct HasEmptyDestructorAndMemberWithUserDefinedConstructor {
10+
HasUserProvidedDestructor member;
11+
~HasEmptyDestructorAndMemberWithUserDefinedConstructor() { /* empty */
12+
}
13+
};
14+
15+
struct HasNonTrivialImplicitDestructor {
16+
HasUserProvidedDestructor member;
17+
};
18+
19+
struct HasNonTrivialDefaultedDestructor {
20+
HasUserProvidedDestructor member;
21+
~HasNonTrivialDefaultedDestructor() = default;
22+
};
23+
24+
struct HasDefaultedDestructor {
25+
~HasDefaultedDestructor() = default;
26+
};
27+
28+
// For the following objects with virtual bases / destructors, make sure that
29+
// any exectuable user of these objects disable rtti and exceptions. Otherwise,
30+
// the linker will error because of undefined vtables.
31+
// FIXME: Once we can link with libc++ we can enable RTTI.
32+
33+
struct HasVirtualBaseAndDestructor : virtual HasDefaultedDestructor {
34+
int *value;
35+
HasVirtualBaseAndDestructor(int *value) : value(value) {}
36+
~HasVirtualBaseAndDestructor() { *value = 42; }
37+
};
38+
39+
struct HasVirtualDestructor {
40+
// An object with a virtual destructor requires a delete operator in case
41+
// we try to delete the base object. Until we can link against libc++, use
42+
// this dummy implementation.
43+
static void operator delete(void *p) { __builtin_unreachable(); }
44+
virtual ~HasVirtualDestructor(){};
45+
};
46+
47+
struct HasVirtualDefaultedDestructor {
48+
static void operator delete(void *p) { __builtin_unreachable(); }
49+
virtual ~HasVirtualDefaultedDestructor() = default;
50+
};
51+
52+
struct HasBaseWithVirtualDestructor : HasVirtualDestructor {
53+
int *value;
54+
HasBaseWithVirtualDestructor(int *value) : value(value) {}
55+
~HasBaseWithVirtualDestructor() { *value = 42; }
56+
};
57+
58+
struct HasVirtualBaseWithVirtualDestructor : virtual HasVirtualDestructor {
59+
int *value;
60+
HasVirtualBaseWithVirtualDestructor(int *value) : value(value) {}
61+
~HasVirtualBaseWithVirtualDestructor() { *value = 42; }
62+
};
63+
64+
struct DummyStruct {};
65+
66+
struct HasUserProvidedDestructorAndDummy {
67+
DummyStruct dummy;
68+
~HasUserProvidedDestructorAndDummy() {}
69+
};
70+
71+
// Make sure that we don't crash on struct templates with destructors.
72+
template <typename T> struct S {
73+
~S() {}
74+
};
75+
76+
#endif // TEST_INTEROP_CXX_VALUE_WITNESS_TABLE_INPUTS_CUSTOM_DESTRUCTORS_H
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module CustomDestructor {
2+
header "custom-destructors.h"
3+
requires cplusplus
4+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %target-swift-frontend -enable-cxx-interop -I %S/Inputs %s -emit-ir | %FileCheck %s
2+
3+
import CustomDestructor
4+
5+
protocol InitWithDummy {
6+
init(dummy: DummyStruct)
7+
}
8+
9+
extension HasUserProvidedDestructorAndDummy : InitWithDummy { }
10+
11+
// Make sure the destructor is added as a witness.
12+
// CHECK: @"$sSo33HasUserProvidedDestructorAndDummyVWV" = linkonce_odr hidden constant %swift.vwtable
13+
// CHECK-SAME: i8* bitcast (void (%swift.opaque*, %swift.type*)* @"$sSo33HasUserProvidedDestructorAndDummyVwxx" to i8*)
14+
15+
// CHECK-LABEL: define {{.*}}void @"$s4main37testHasUserProvidedDestructorAndDummyyyF"
16+
// CHECK: [[OBJ:%.*]] = alloca %TSo33HasUserProvidedDestructorAndDummyV
17+
// CHECK: [[CXX_OBJ:%.*]] = bitcast %TSo33HasUserProvidedDestructorAndDummyV* [[OBJ]] to %struct.HasUserProvidedDestructorAndDummy*
18+
// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy* [[CXX_OBJ]])
19+
// CHECK: ret void
20+
21+
// Make sure we not only declare but define the destructor.
22+
// CHECK-LABEL: define {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}
23+
// CHECK: ret
24+
public func testHasUserProvidedDestructorAndDummy() {
25+
_ = HasUserProvidedDestructorAndDummy(dummy: DummyStruct())
26+
}
27+
28+
// CHECK-LABEL: define {{.*}}void @"$s4main26testHasDefaultedDestructoryyF"
29+
// CHECK: call {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor*
30+
// CHECK: ret void
31+
32+
// CHECK-LABEL: define {{.*}}@{{_ZN22HasDefaultedDestructorC(1|2)Ev|"\?\?0HasDefaultedDestructor@@QEAA@XZ"}}(%struct.HasDefaultedDestructor*
33+
// CHECK: ret
34+
public func testHasDefaultedDestructor() {
35+
_ = HasDefaultedDestructor()
36+
}
37+
38+
// Make sure the destroy value witness calls the destructor.
39+
// CHECK-LABEL: define {{.*}}void @"$sSo33HasUserProvidedDestructorAndDummyVwxx"
40+
// CHECK: call {{.*}}@{{_ZN33HasUserProvidedDestructorAndDummyD(1|2)Ev|"\?\?1HasUserProvidedDestructorAndDummy@@QEAA@XZ"}}(%struct.HasUserProvidedDestructorAndDummy*
41+
// CHECK: ret

0 commit comments

Comments
 (0)