Skip to content

Commit 6ba560f

Browse files
committed
[Clang importer] Allow noncopyable C structs to define "destroy" operation
A C struct can be imported as noncopyable, but C doesn't have destructors, so there is no way to provide user-defined logic to perform the destruction. Introduce a new swift_attr that applies to imported noncopyable types and which provides such a "destroy" operation. It can be used like this: typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:wgpuAdapterInfoFreeMembers"))) WGPUAdapterInfo { /*...*/ } WGPUAdapterInfo; void wgpuAdapterInfoFreeMembers(WGPUAdapterInfo adapterInfo); This will bring the WGPUAdapterInfo struct in as a noncopyable type that will be cleaned up by calling wgpuAdapterInfoFreeMembers once it is no longer in use. Implements rdar://156889370.
1 parent 21b9b9f commit 6ba560f

File tree

9 files changed

+198
-18
lines changed

9 files changed

+198
-18
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7887,8 +7887,9 @@ getRefParentDecls(const clang::RecordDecl *decl, ASTContext &ctx,
78877887
return matchingDecls;
78887888
}
78897889

7890-
static llvm::SmallVector<ValueDecl *, 1>
7891-
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name) {
7890+
llvm::SmallVector<ValueDecl *, 1>
7891+
importer::getValueDeclsForName(
7892+
const clang::Decl *decl, ASTContext &ctx, StringRef name) {
78927893
llvm::SmallVector<ValueDecl *, 1> results;
78937894
auto *clangMod = decl->getOwningModule();
78947895
if (clangMod && clangMod->isSubModule())

lib/ClangImporter/ImportDecl.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,10 +2687,103 @@ namespace {
26872687
}
26882688
}
26892689

2690+
// A type imported as noncopyable, check whether an explicit deinit
2691+
// should be introduced to call a user-defined "destroy" function.
2692+
if (recordHasMoveOnlySemantics(decl)) {
2693+
addExplicitDeinitIfRequired(result, decl);
2694+
}
2695+
26902696
result->setMemberLoader(&Impl, 0);
26912697
return result;
26922698
}
26932699

2700+
/// Find an explicitly-provided "destroy" operation specified for the
2701+
/// given Clang type and return it.
2702+
static FuncDecl *findExplicitDestroy(
2703+
NominalTypeDecl *nominal, const clang::TypeDecl *clangType) {
2704+
if (!clangType->hasAttrs())
2705+
return nullptr;
2706+
2707+
llvm::TinyPtrVector<FuncDecl *> matchingDestroyFuncs;
2708+
for (auto attr : clangType->getAttrs()) {
2709+
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
2710+
if (!swiftAttr)
2711+
continue;
2712+
2713+
auto attributeName = swiftAttr->getAttribute();
2714+
if (!attributeName.starts_with("destroy:"))
2715+
continue;
2716+
2717+
auto destroyFuncName = attributeName.drop_front(strlen("destroy:"));
2718+
auto decls = getValueDeclsForName(
2719+
clangType, nominal->getASTContext(), destroyFuncName);
2720+
for (auto decl : decls) {
2721+
auto func = dyn_cast<FuncDecl>(decl);
2722+
if (!func)
2723+
continue;
2724+
2725+
auto params = func->getParameters();
2726+
if (params->size() != 1)
2727+
continue;
2728+
2729+
if (!params->get(0)->getInterfaceType()->isEqual(
2730+
nominal->getDeclaredInterfaceType()))
2731+
continue;
2732+
2733+
matchingDestroyFuncs.push_back(func);
2734+
}
2735+
}
2736+
2737+
if (matchingDestroyFuncs.size() == 1)
2738+
return matchingDestroyFuncs[0];
2739+
2740+
return nullptr;
2741+
}
2742+
2743+
/// Function body synthesizer for a deinit of a noncopyable type, which
2744+
/// passes "self" to the given "destroy" function.
2745+
static std::pair<BraceStmt *, bool>
2746+
synthesizeDeinitBodyForCustomDestroy(
2747+
AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) {
2748+
auto deinit = cast<DestructorDecl>(deinitFunc);
2749+
auto destroyFunc = static_cast<FuncDecl *>(opaqueDestroyFunc);
2750+
2751+
ASTContext &ctx = deinit->getASTContext();
2752+
auto funcRef = new (ctx) DeclRefExpr(
2753+
destroyFunc, DeclNameLoc(), /*Implicit=*/true);
2754+
auto selfRef = new (ctx) DeclRefExpr(
2755+
deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true);
2756+
auto callExpr = CallExpr::createImplicit(
2757+
ctx, funcRef,
2758+
ArgumentList::createImplicit(
2759+
ctx,
2760+
{ Argument(SourceLoc(), Identifier(), selfRef)}
2761+
)
2762+
);
2763+
2764+
auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) });
2765+
return std::make_pair(braceStmt, /*typechecked=*/false);
2766+
}
2767+
2768+
/// For a type that is imported as noncopyable, look for an
2769+
/// explicitly-provided "destroy" operation. If present, introduce a deinit
2770+
/// that calls it.
2771+
void addExplicitDeinitIfRequired(
2772+
NominalTypeDecl *nominal, const clang::TypeDecl *clangType) {
2773+
auto destroyFunc = findExplicitDestroy(nominal, clangType);
2774+
if (!destroyFunc)
2775+
return;
2776+
2777+
ASTContext &ctx = Impl.SwiftContext;
2778+
auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal);
2779+
destructor->setSynthesized(true);
2780+
destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true);
2781+
destructor->setBodySynthesizer(
2782+
synthesizeDeinitBodyForCustomDestroy, destroyFunc);
2783+
2784+
nominal->addMember(destructor);
2785+
}
2786+
26942787
void validatePrivateFileIDAttributes(const clang::CXXRecordDecl *decl) {
26952788
auto anns = importer::getPrivateFileIDAttrs(decl);
26962789

lib/ClangImporter/ImporterImpl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2179,6 +2179,14 @@ inline clang::QualType desugarIfBoundsAttributed(clang::QualType type) {
21792179
/// If \a type is an elaborated type, it should be desugared first.
21802180
ImportedType findOptionSetEnum(clang::QualType type,
21812181
ClangImporter::Implementation &Impl);
2182+
2183+
/// Find value declarations in the same module as the given Clang declaration
2184+
/// and with the given name.
2185+
///
2186+
/// The name we're looking for is the Swift name.
2187+
llvm::SmallVector<ValueDecl *, 1>
2188+
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name);
2189+
21822190
} // end namespace importer
21832191
} // end namespace swift
21842192

lib/IRGen/GenStruct.cpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,14 @@ namespace {
374374
unsigned explosionSize, llvm::Type *storageType,
375375
Size size, SpareBitVector &&spareBits,
376376
Alignment align,
377+
IsTriviallyDestroyable_t isTriviallyDestroyable,
378+
IsCopyable_t isCopyable,
377379
const clang::RecordDecl *clangDecl)
378380
: StructTypeInfoBase(StructTypeInfoKind::LoadableClangRecordTypeInfo,
379381
fields, explosionSize, FieldsAreABIAccessible,
380382
storageType, size, std::move(spareBits), align,
381-
IsTriviallyDestroyable,
382-
IsCopyable,
383+
isTriviallyDestroyable,
384+
isCopyable,
383385
IsFixedSize, IsABIAccessible),
384386
ClangDecl(clangDecl) {}
385387

@@ -469,6 +471,7 @@ namespace {
469471
AddressOnlyPointerAuthRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
470472
llvm::Type *storageType, Size size,
471473
Alignment align,
474+
IsCopyable_t isCopyable,
472475
const clang::RecordDecl *clangDecl)
473476
: StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo,
474477
fields, FieldsAreABIAccessible, storageType, size,
@@ -477,7 +480,7 @@ namespace {
477480
SpareBitVector(std::optional<APInt>{
478481
llvm::APInt(size.getValueInBits(), 0)}),
479482
align, IsNotTriviallyDestroyable,
480-
IsNotBitwiseTakable, IsCopyable, IsFixedSize,
483+
IsNotBitwiseTakable, isCopyable, IsFixedSize,
481484
IsABIAccessible),
482485
clangDecl(clangDecl) {
483486
(void)clangDecl;
@@ -645,6 +648,7 @@ namespace {
645648
AddressOnlyCXXClangRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
646649
llvm::Type *storageType, Size size,
647650
Alignment align,
651+
IsCopyable_t isCopyable,
648652
const clang::RecordDecl *clangDecl)
649653
: StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo,
650654
fields, FieldsAreABIAccessible, storageType, size,
@@ -654,9 +658,7 @@ namespace {
654658
llvm::APInt(size.getValueInBits(), 0)}),
655659
align, IsNotTriviallyDestroyable,
656660
IsNotBitwiseTakable,
657-
// TODO: Set this appropriately for the type's
658-
// C++ import behavior.
659-
IsCopyable, IsFixedSize, IsABIAccessible),
661+
isCopyable, IsFixedSize, IsABIAccessible),
660662
ClangDecl(clangDecl) {
661663
(void)ClangDecl;
662664
}
@@ -1342,15 +1344,26 @@ class ClangRecordLowering {
13421344
llvmType->setBody(LLVMFields, /*packed*/ true);
13431345
if (SwiftType.getStructOrBoundGenericStruct()->isCxxNonTrivial()) {
13441346
return AddressOnlyCXXClangRecordTypeInfo::create(
1345-
FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl);
1347+
FieldInfos, llvmType, TotalStride, TotalAlignment,
1348+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1349+
? IsNotCopyable : IsCopyable,
1350+
ClangDecl);
13461351
}
13471352
if (SwiftType.getStructOrBoundGenericStruct()->isNonTrivialPtrAuth()) {
13481353
return AddressOnlyPointerAuthRecordTypeInfo::create(
1349-
FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl);
1354+
FieldInfos, llvmType, TotalStride, TotalAlignment,
1355+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1356+
? IsNotCopyable : IsCopyable,
1357+
ClangDecl);
13501358
}
13511359
return LoadableClangRecordTypeInfo::create(
13521360
FieldInfos, NextExplosionIndex, llvmType, TotalStride,
1353-
std::move(SpareBits), TotalAlignment, ClangDecl);
1361+
std::move(SpareBits), TotalAlignment,
1362+
(SwiftDecl && SwiftDecl->getValueTypeDestructor())
1363+
? IsNotTriviallyDestroyable : IsTriviallyDestroyable,
1364+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1365+
? IsNotCopyable : IsCopyable,
1366+
ClangDecl);
13541367
}
13551368

13561369
private:

lib/SILGen/SILGen.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
8484
/// Set of delayed conformances that have already been forced.
8585
llvm::DenseSet<NormalProtocolConformance *> forcedConformances;
8686

87+
/// Imported noncopyable types that we have seen.
88+
llvm::DenseSet<NominalTypeDecl *> importedNontrivialNoncopyableTypes;
89+
8790
size_t anonymousSymbolCounter = 0;
8891

8992
std::optional<SILDeclRef> StringToNSStringFn;
@@ -282,6 +285,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
282285
void visitMacroDecl(MacroDecl *d);
283286
void visitMacroExpansionDecl(MacroExpansionDecl *d);
284287

288+
void visitImportedNontrivialNoncopyableType(NominalTypeDecl *nominal);
289+
285290
// Same as AbstractStorageDecl::visitEmittedAccessors, but skips over skipped
286291
// (unavailable) decls.
287292
void visitEmittedAccessors(AbstractStorageDecl *D,

lib/SILGen/SILGenLazyConformance.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ void SILGenModule::useConformancesFromType(CanType type) {
110110
if (isa<ProtocolDecl>(decl))
111111
return;
112112

113+
// If this is an imported noncopyable type with a deinitializer, record it.
114+
if (decl->hasClangNode() && !decl->canBeCopyable() &&
115+
decl->getValueTypeDestructor() &&
116+
importedNontrivialNoncopyableTypes.insert(decl).second) {
117+
visitImportedNontrivialNoncopyableType(decl);
118+
}
119+
113120
auto genericSig = decl->getGenericSignature();
114121
if (!genericSig)
115122
return;

lib/SILGen/SILGenType.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,6 +1577,13 @@ void SILGenModule::visitNominalTypeDecl(NominalTypeDecl *ntd) {
15771577
SILGenType(*this, ntd).emitType();
15781578
}
15791579

1580+
void SILGenModule::visitImportedNontrivialNoncopyableType(
1581+
NominalTypeDecl *nominal) {
1582+
emitNonCopyableTypeDeinitTable(nominal);
1583+
SILGenType(*this, nominal)
1584+
.visitDestructorDecl(nominal->getValueTypeDestructor());
1585+
}
1586+
15801587
/// SILGenExtension - an ASTVisitor for generating SIL from method declarations
15811588
/// and protocol conformances inside type extensions.
15821589
class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {

test/Interop/C/struct/Inputs/noncopyable-struct.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,17 @@
33
typedef struct __attribute__((swift_attr("~Copyable"))) NonCopyable {
44
float x, y;
55
} NonCopyable;
6+
7+
typedef struct __attribute__((swift_attr("~Copyable"))) __attribute__((swift_attr("destroy:freeNonCopyableWithDeinit"))) NonCopyableWithDeinit {
8+
void *storage;
9+
} NonCopyableWithDeinit;
10+
11+
#ifdef __cplusplus
12+
extern "C" {
13+
#endif
14+
15+
void freeNonCopyableWithDeinit(NonCopyableWithDeinit ncd);
16+
17+
#ifdef __cplusplus
18+
}
19+
#endif

test/Interop/C/struct/noncopyable_structs.swift

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@
55
// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS
66
// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -verify -DERRORS -cxx-interoperability-mode=default
77

8+
// Check that we get the expected SIL
9+
// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-SIL %s
10+
// RUN: %target-swift-frontend -emit-sil -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck -check-prefix CHECK-SIL %s
11+
812
// Check that we get the expected IR
913

10-
// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck %s
11-
// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default| %FileCheck %s
14+
// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - | %FileCheck -check-prefix CHECK-IR %s
15+
// RUN: %target-swift-frontend -emit-ir -I %S/Inputs/ %s -o - -cxx-interoperability-mode=default | %FileCheck -check-prefix CHECK-IR %s
1216

1317
import NoncopyableStructs
1418

15-
// CHECK-LABEL: define hidden swiftcc void @"$s19noncopyable_structs9consumeNCyySo11NonCopyableVnF"(float %0, float %1) #0 {
16-
// CHECK: call ptr @"$sSo11NonCopyableVWOh"
17-
// CHECK: define linkonce_odr hidden ptr @"$sSo11NonCopyableVWOh"(ptr %0)
18-
// CHECK-NEXT: entry:
19-
// CHECK-NEXT: ret ptr
19+
// CHECK-IR-LABEL: define hidden swiftcc void @"$s19noncopyable_structs9consumeNCyySo11NonCopyableVnF"(float %0, float %1) #0 {
20+
// CHECK-IR: call ptr @"$sSo11NonCopyableVWOh"
21+
// CHECK-IR: define linkonce_odr hidden ptr @"$sSo11NonCopyableVWOh"(ptr %0)
22+
// CHECK-IR-NEXT: entry:
23+
// CHECK-IR-NEXT: ret ptr
2024
func consumeNC(_ nc: consuming NonCopyable) { }
2125

2226
func testNC() {
@@ -27,3 +31,31 @@ func testNC() {
2731
consumeNC(nc) // expected-note{{consumed again here}}
2832
#endif
2933
}
34+
35+
func consumeNCWithDeinit(_ nc: consuming NonCopyableWithDeinit) { }
36+
37+
func testNCWithDeinit() {
38+
let nc = NonCopyableWithDeinit() // expected-error{{'nc' consumed more than once}}
39+
consumeNCWithDeinit(nc) // expected-note{{consumed here}}
40+
41+
#if ERRORS
42+
consumeNCWithDeinit(nc) // expected-note{{consumed again here}}
43+
#endif
44+
}
45+
46+
// CHECK-SIL: sil shared @$sSo21NonCopyableWithDeinitVfD : $@convention(method) (@owned NonCopyableWithDeinit) -> () {
47+
// CHECK-SIL: bb0([[SELF:%[0-9]+]] : $NonCopyableWithDeinit):
48+
// CHECK-SIL: [[SELF_ALLOC:%[0-9]+]] = alloc_stack $NonCopyableWithDeinit
49+
// CHECK-SIL: store [[SELF]] to [[SELF_ALLOC]]
50+
// CHECK-SIL: [[SELF_RELOAD:%[0-9]+]] = load [[SELF_ALLOC]]
51+
// CHECK-SIL: [[FN:%[0-9]+]] = function_ref @{{.*}}freeNonCopyableWithDeinit : $@convention(c) (NonCopyableWithDeinit) -> ()
52+
// CHECK-SIL: apply [[FN]]([[SELF_RELOAD]]) : $@convention(c) (NonCopyableWithDeinit) -> ()
53+
54+
// CHECK-IR-LABEL: define hidden swiftcc void @"$s19noncopyable_structs19consumeNCWithDeinityySo015NonCopyableWithE0VnF"
55+
// CHECK-IR: call swiftcc void @"$sSo21NonCopyableWithDeinitVfD"
56+
57+
// CHECK-IR-LABEL: define {{.*}} swiftcc void @"$sSo21NonCopyableWithDeinitVfD"
58+
// CHECK-IR: {{(call|invoke)}} void @{{.*}}freeNonCopyableWithDeinit
59+
60+
// CHECK-SIL-LABEL: sil_moveonlydeinit NonCopyableWithDeinit {
61+
// CHECK-SIL: @$sSo21NonCopyableWithDeinitVfD

0 commit comments

Comments
 (0)