Skip to content

Commit 8b10bdc

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. (cherry picked from commit 6ba560f)
1 parent f10c42f commit 8b10bdc

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
@@ -7867,8 +7867,9 @@ getRefParentDecls(const clang::RecordDecl *decl, ASTContext &ctx,
78677867
return matchingDecls;
78687868
}
78697869

7870-
static llvm::SmallVector<ValueDecl *, 1>
7871-
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name) {
7870+
llvm::SmallVector<ValueDecl *, 1>
7871+
importer::getValueDeclsForName(
7872+
const clang::Decl *decl, ASTContext &ctx, StringRef name) {
78727873
llvm::SmallVector<ValueDecl *, 1> results;
78737874
auto *clangMod = decl->getOwningModule();
78747875
if (clangMod && clangMod->isSubModule())

lib/ClangImporter/ImportDecl.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2666,10 +2666,103 @@ namespace {
26662666
}
26672667
}
26682668

2669+
// A type imported as noncopyable, check whether an explicit deinit
2670+
// should be introduced to call a user-defined "destroy" function.
2671+
if (recordHasMoveOnlySemantics(decl)) {
2672+
addExplicitDeinitIfRequired(result, decl);
2673+
}
2674+
26692675
result->setMemberLoader(&Impl, 0);
26702676
return result;
26712677
}
26722678

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

lib/ClangImporter/ImporterImpl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,14 @@ inline clang::QualType desugarIfBoundsAttributed(clang::QualType type) {
21452145
/// If \a type is an elaborated type, it should be desugared first.
21462146
ImportedType findOptionSetEnum(clang::QualType type,
21472147
ClangImporter::Implementation &Impl);
2148+
2149+
/// Find value declarations in the same module as the given Clang declaration
2150+
/// and with the given name.
2151+
///
2152+
/// The name we're looking for is the Swift name.
2153+
llvm::SmallVector<ValueDecl *, 1>
2154+
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name);
2155+
21482156
} // end namespace importer
21492157
} // end namespace swift
21502158

lib/IRGen/GenStruct.cpp

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

@@ -470,6 +472,7 @@ namespace {
470472
AddressOnlyPointerAuthRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
471473
llvm::Type *storageType, Size size,
472474
Alignment align,
475+
IsCopyable_t isCopyable,
473476
const clang::RecordDecl *clangDecl)
474477
: StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo,
475478
fields, FieldsAreABIAccessible, storageType, size,
@@ -478,7 +481,7 @@ namespace {
478481
SpareBitVector(std::optional<APInt>{
479482
llvm::APInt(size.getValueInBits(), 0)}),
480483
align, IsNotTriviallyDestroyable,
481-
IsNotBitwiseTakable, IsCopyable, IsFixedSize,
484+
IsNotBitwiseTakable, isCopyable, IsFixedSize,
482485
IsABIAccessible),
483486
clangDecl(clangDecl) {
484487
(void)clangDecl;
@@ -646,6 +649,7 @@ namespace {
646649
AddressOnlyCXXClangRecordTypeInfo(ArrayRef<ClangFieldInfo> fields,
647650
llvm::Type *storageType, Size size,
648651
Alignment align,
652+
IsCopyable_t isCopyable,
649653
const clang::RecordDecl *clangDecl)
650654
: StructTypeInfoBase(StructTypeInfoKind::AddressOnlyClangRecordTypeInfo,
651655
fields, FieldsAreABIAccessible, storageType, size,
@@ -655,9 +659,7 @@ namespace {
655659
llvm::APInt(size.getValueInBits(), 0)}),
656660
align, IsNotTriviallyDestroyable,
657661
IsNotBitwiseTakable,
658-
// TODO: Set this appropriately for the type's
659-
// C++ import behavior.
660-
IsCopyable, IsFixedSize, IsABIAccessible),
662+
isCopyable, IsFixedSize, IsABIAccessible),
661663
ClangDecl(clangDecl) {
662664
(void)ClangDecl;
663665
}
@@ -1343,15 +1345,26 @@ class ClangRecordLowering {
13431345
llvmType->setBody(LLVMFields, /*packed*/ true);
13441346
if (SwiftType.getStructOrBoundGenericStruct()->isCxxNonTrivial()) {
13451347
return AddressOnlyCXXClangRecordTypeInfo::create(
1346-
FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl);
1348+
FieldInfos, llvmType, TotalStride, TotalAlignment,
1349+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1350+
? IsNotCopyable : IsCopyable,
1351+
ClangDecl);
13471352
}
13481353
if (SwiftType.getStructOrBoundGenericStruct()->isNonTrivialPtrAuth()) {
13491354
return AddressOnlyPointerAuthRecordTypeInfo::create(
1350-
FieldInfos, llvmType, TotalStride, TotalAlignment, ClangDecl);
1355+
FieldInfos, llvmType, TotalStride, TotalAlignment,
1356+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1357+
? IsNotCopyable : IsCopyable,
1358+
ClangDecl);
13511359
}
13521360
return LoadableClangRecordTypeInfo::create(
13531361
FieldInfos, NextExplosionIndex, llvmType, TotalStride,
1354-
std::move(SpareBits), TotalAlignment, ClangDecl);
1362+
std::move(SpareBits), TotalAlignment,
1363+
(SwiftDecl && SwiftDecl->getValueTypeDestructor())
1364+
? IsNotTriviallyDestroyable : IsTriviallyDestroyable,
1365+
(SwiftDecl && !SwiftDecl->canBeCopyable())
1366+
? IsNotCopyable : IsCopyable,
1367+
ClangDecl);
13551368
}
13561369

13571370
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)