Skip to content

Commit 9194fa0

Browse files
authored
Merge pull request #83364 from DougGregor/noncopyable-imported-c-struct-destroy
[Clang importer] Allow noncopyable C structs to define "destroy" operation
2 parents 45656ab + 2e7e884 commit 9194fa0

17 files changed

+432
-43
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,6 +2687,9 @@ namespace {
26872687
}
26882688
}
26892689

2690+
// If we need it, add an explicit "deinit" to this type.
2691+
synthesizer.addExplicitDeinitIfRequired(result, decl);
2692+
26902693
result->setMemberLoader(&Impl, 0);
26912694
return result;
26922695
}

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/ClangImporter/SwiftBridging/swift/bridging

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// -*- C++ -*-
2-
//===------------------ bridging - C++ and Swift Interop --------*- C++ -*-===//
1+
// -*- C -*-
2+
//===------------------ bridging - C and Swift Interop ----------*- C++ -*-===//
33
//
44
// This source file is part of the Swift.org open source project
55
//
@@ -32,7 +32,7 @@
3232
/// that return a `class` or `struct` type that is annotated with this macro.
3333
#define SWIFT_SELF_CONTAINED __attribute__((swift_attr("import_owned")))
3434

35-
/// Specifies that a C++ method returns a value that is presumed to contain
35+
/// Specifies that a method returns a value that is presumed to contain
3636
/// objects whose lifetime is not dependent on `this` or other parameters passed
3737
/// to the method.
3838
#define SWIFT_RETURNS_INDEPENDENT_VALUE __attribute__((swift_attr("import_unsafe")))
@@ -45,7 +45,7 @@
4545
#define _CXX_INTEROP_CONCAT(...) \
4646
_CXX_INTEROP_CONCAT_(__VA_ARGS__,,,,,,,,,,,,,,,,,)
4747

48-
/// Specifies that a C++ `class` or `struct` is reference-counted using
48+
/// Specifies that a `class` or `struct` is reference-counted using
4949
/// the given `retain` and `release` functions. This annotation lets Swift import
5050
/// such a type as reference counted type in Swift, taking advantage of Swift's
5151
/// automatic reference counting.
@@ -76,7 +76,7 @@
7676
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:_retain)))) \
7777
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:_release))))
7878

79-
/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime
79+
/// Specifies that a `class` or `struct` is a reference type whose lifetime
8080
/// is presumed to be immortal, i.e. the reference to such object is presumed to
8181
/// always be valid. This annotation lets Swift import such a type as a reference
8282
/// type in Swift.
@@ -103,7 +103,7 @@
103103
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(retain:immortal)))) \
104104
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(release:immortal))))
105105

106-
/// Specifies that a C++ `class` or `struct` is a reference type whose lifetime
106+
/// Specifies that a `class` or `struct` is a reference type whose lifetime
107107
/// is not managed automatically. The programmer must validate that any reference
108108
/// to such object is valid themselves. This annotation lets Swift import such a type as a reference type in Swift.
109109
#define SWIFT_UNSAFE_REFERENCE \
@@ -115,7 +115,7 @@
115115
/// Specifies a name that will be used in Swift for this declaration instead of its original name.
116116
#define SWIFT_NAME(_name) __attribute__((swift_name(#_name)))
117117

118-
/// Specifies that a specific C++ `class` or `struct` conforms to a
118+
/// Specifies that a specific `class` or `struct` conforms to a
119119
/// a specific Swift protocol.
120120
///
121121
/// This example shows how to use this macro to conform a class template to a Swift protocol:
@@ -147,7 +147,7 @@
147147
#define SWIFT_MUTATING \
148148
__attribute__((swift_attr("mutating")))
149149

150-
/// Specifies that a specific c++ type such class or struct should be imported as type marked
150+
/// Specifies that a specific class or struct should be imported as type marked
151151
/// as `@unchecked Sendable` type in swift. If this annotation is used, the type is therefore allowed to
152152
/// use safely across async contexts.
153153
///
@@ -160,19 +160,43 @@
160160
#define SWIFT_UNCHECKED_SENDABLE \
161161
__attribute__((swift_attr("@Sendable")))
162162

163-
/// Specifies that a specific c++ type such class or struct should be imported
164-
/// as a non-copyable Swift value type.
163+
/// Specifies that a `class` or `struct` should be imported as a non-copyable
164+
/// Swift value type.
165165
#define SWIFT_NONCOPYABLE \
166166
__attribute__((swift_attr("~Copyable")))
167167

168-
/// Specifies that a specific c++ type such class or struct should be imported
169-
/// as a non-escapable Swift value type when the non-escapable language feature
170-
/// is enabled.
168+
/// Specifies that a `class` or `struct` should be imported as a non-copyable
169+
/// Swift value type that calls the given `_destroy` function when a value is no
170+
/// longer used.
171+
///
172+
/// This example shows how to use this macro to let Swift know that
173+
/// a given C struct should have its members freed when it goes out of scope:
174+
/// ```c
175+
/// typedef struct SWIFT_NONCOPYABLE_WITH_DESTROY(mytypeFreeMembers) MyType {
176+
/// void *storage
177+
/// } MyType;
178+
///
179+
/// void mytypeFreeMembers(MyType toBeDestroyed);
180+
/// MyType mytypeCreate(void);
181+
/// ```
182+
///
183+
/// Usage in Swift:
184+
/// ```swift
185+
/// let mt = mytypeCreate()
186+
/// let mt2 = mt // consumes mt
187+
/// // once mt2 is unused, Swift will call mytypeFreeMembers(mt2)
188+
/// ```
189+
#define SWIFT_NONCOPYABLE_WITH_DESTROY(_destroy) \
190+
__attribute__((swift_attr("~Copyable"))) \
191+
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(destroy:_destroy))))
192+
193+
/// Specifies that a specific class or struct should be imported
194+
/// as a non-escapable Swift value type.
171195
#define SWIFT_NONESCAPABLE \
172196
__attribute__((swift_attr("~Escapable")))
173197

174-
/// Specifies that a specific c++ type such class or struct should be imported
175-
/// as a escapable Swift value. While this matches the default behavior,
198+
/// Specifies that a specific class or struct should be imported
199+
/// as an escapable Swift value. While this matches the default behavior,
176200
/// in safe mode interop mode it ensures that the type is not marked as
177201
/// unsafe.
178202
#define SWIFT_ESCAPABLE \
@@ -183,16 +207,16 @@
183207
#define SWIFT_ESCAPABLE_IF(...) \
184208
__attribute__((swift_attr("escapable_if:" _CXX_INTEROP_CONCAT(__VA_ARGS__))))
185209

186-
/// Specifies that the return value is passed as owned for C++ functions and
210+
/// Specifies that the return value is passed as owned for functions and
187211
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
188212
#define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained")))
189-
/// Specifies that the return value is passed as unowned for C++ functions and
213+
/// Specifies that the return value is passed as unowned for functions and
190214
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
191215
#define SWIFT_RETURNS_UNRETAINED \
192216
__attribute__((swift_attr("returns_unretained")))
193217

194-
/// Applied to a C++ foreign reference type annotated with
195-
/// SWIFT_SHARED_REFERENCE. Indicates that C++ APIs returning this type are
218+
/// Applied to a foreign reference type annotated with
219+
/// SWIFT_SHARED_REFERENCE. Indicates that APIs returning this type are
196220
/// assumed to return an unowned (+0) value by default, unless explicitly annotated
197221
/// with SWIFT_RETURNS_RETAINED.
198222
///
@@ -203,7 +227,7 @@
203227
/// Bar { ... };
204228
/// ```
205229
///
206-
/// In Swift, C++ APIs returning `Bar*` will be assumed to return an unowned
230+
/// In Swift, APIs returning `Bar*` will be assumed to return an unowned
207231
/// value.
208232
#define SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT \
209233
__attribute__((swift_attr("returned_as_unretained_by_default")))
@@ -258,6 +282,7 @@
258282
#define SWIFT_MUTATING
259283
#define SWIFT_UNCHECKED_SENDABLE
260284
#define SWIFT_NONCOPYABLE
285+
#define SWIDT_NONCOPYABLE_WITH_DESTROY(_destroy)
261286
#define SWIFT_NONESCAPABLE
262287
#define SWIFT_ESCAPABLE
263288
#define SWIFT_ESCAPABLE_IF(...)

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "swift/AST/Stmt.h"
2424
#include "swift/AST/TypeCheckRequests.h"
2525
#include "swift/Basic/Assertions.h"
26+
#include "swift/ClangImporter/ClangImporterRequests.h"
2627
#include "clang/AST/Mangle.h"
2728
#include "clang/Sema/DelayedDiagnostic.h"
2829

@@ -2809,6 +2810,179 @@ synthesizeAvailabilityDomainPredicateBody(AbstractFunctionDecl *afd,
28092810
return {body, /*isTypeChecked=*/false};
28102811
}
28112812

2813+
/// Mark the given declaration as always deprecated for the given reason.
2814+
static void markDeprecated(Decl *decl, llvm::Twine message) {
2815+
ASTContext &ctx = decl->getASTContext();
2816+
decl->getAttrs().add(
2817+
AvailableAttr::createUniversallyDeprecated(
2818+
ctx, ctx.AllocateCopy(message.str())));
2819+
}
2820+
2821+
/// Find an explicitly-provided "destroy" operation specified for the
2822+
/// given Clang type and return it.
2823+
FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
2824+
NominalTypeDecl *nominal, const clang::RecordDecl *clangType) {
2825+
if (!clangType->hasAttrs())
2826+
return nullptr;
2827+
2828+
llvm::SmallPtrSet<FuncDecl *, 2> matchingDestroyFuncs;
2829+
llvm::TinyPtrVector<FuncDecl *> nonMatchingDestroyFuncs;
2830+
for (auto attr : clangType->getAttrs()) {
2831+
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
2832+
if (!swiftAttr)
2833+
continue;
2834+
2835+
auto destroyFuncName = swiftAttr->getAttribute();
2836+
if (!destroyFuncName.consume_front("destroy:"))
2837+
continue;
2838+
2839+
auto decls = getValueDeclsForName(
2840+
clangType, nominal->getASTContext(), destroyFuncName);
2841+
for (auto decl : decls) {
2842+
auto func = dyn_cast<FuncDecl>(decl);
2843+
if (!func)
2844+
continue;
2845+
2846+
auto params = func->getParameters();
2847+
if (params->size() != 1) {
2848+
nonMatchingDestroyFuncs.push_back(func);
2849+
continue;
2850+
}
2851+
2852+
if (!params->get(0)->getInterfaceType()->isEqual(
2853+
nominal->getDeclaredInterfaceType())) {
2854+
nonMatchingDestroyFuncs.push_back(func);
2855+
continue;
2856+
}
2857+
2858+
matchingDestroyFuncs.insert(func);
2859+
}
2860+
}
2861+
2862+
switch (matchingDestroyFuncs.size()) {
2863+
case 0:
2864+
if (!nonMatchingDestroyFuncs.empty()) {
2865+
markDeprecated(
2866+
nominal,
2867+
"destroy function '" +
2868+
nonMatchingDestroyFuncs.front()->getName().getBaseName()
2869+
.userFacingName() +
2870+
"' must have a single parameter with type '" +
2871+
nominal->getDeclaredInterfaceType().getString() + "'");
2872+
}
2873+
2874+
return nullptr;
2875+
2876+
case 1:
2877+
// Handled below.
2878+
break;
2879+
2880+
default: {
2881+
auto iter = matchingDestroyFuncs.begin();
2882+
auto first = *iter++;
2883+
auto second = *iter;
2884+
markDeprecated(
2885+
nominal,
2886+
"multiple destroy operations ('" +
2887+
first->getName().getBaseName().userFacingName() +
2888+
"' and '" +
2889+
second->getName().getBaseName().userFacingName() +
2890+
"') provided for type");
2891+
return nullptr;
2892+
}
2893+
}
2894+
2895+
auto destroyFunc = *matchingDestroyFuncs.begin();
2896+
2897+
// If this type isn't imported as noncopyable, we can't respect the request
2898+
// for a destroy operation.
2899+
ASTContext &ctx = ImporterImpl.SwiftContext;
2900+
auto semanticsKind = evaluateOrDefault(
2901+
ctx.evaluator,
2902+
CxxRecordSemantics({clangType, ctx, &ImporterImpl}), {});
2903+
switch (semanticsKind) {
2904+
case CxxRecordSemanticsKind::Owned:
2905+
case CxxRecordSemanticsKind::Reference:
2906+
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(clangType)) {
2907+
if (!cxxRecord->hasTrivialDestructor()) {
2908+
markDeprecated(
2909+
nominal,
2910+
"destroy operation '" +
2911+
destroyFunc->getName().getBaseName().userFacingName() +
2912+
"' is not allowed on types with a non-trivial destructor");
2913+
return nullptr;
2914+
}
2915+
}
2916+
2917+
LLVM_FALLTHROUGH;
2918+
2919+
case CxxRecordSemanticsKind::Trivial:
2920+
markDeprecated(
2921+
nominal,
2922+
"destroy operation '" +
2923+
destroyFunc->getName().getBaseName().userFacingName() +
2924+
"' is only allowed on non-copyable types; "
2925+
"did you mean to use SWIFT_NONCOPYABLE?");
2926+
return nullptr;
2927+
2928+
case CxxRecordSemanticsKind::Iterator:
2929+
case CxxRecordSemanticsKind::MissingLifetimeOperation:
2930+
case CxxRecordSemanticsKind::SwiftClassType:
2931+
return nullptr;
2932+
2933+
case CxxRecordSemanticsKind::MoveOnly:
2934+
case CxxRecordSemanticsKind::UnavailableConstructors:
2935+
// Handled below.
2936+
break;
2937+
}
2938+
if (semanticsKind != CxxRecordSemanticsKind::MoveOnly) {
2939+
return nullptr;
2940+
}
2941+
2942+
return destroyFunc;
2943+
}
2944+
2945+
/// Function body synthesizer for a deinit of a noncopyable type, which
2946+
/// passes "self" to the given "destroy" function.
2947+
static std::pair<BraceStmt *, bool>
2948+
synthesizeDeinitBodyForCustomDestroy(
2949+
AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) {
2950+
auto deinit = cast<DestructorDecl>(deinitFunc);
2951+
auto destroyFunc = static_cast<FuncDecl *>(opaqueDestroyFunc);
2952+
2953+
ASTContext &ctx = deinit->getASTContext();
2954+
auto funcRef = new (ctx) DeclRefExpr(
2955+
destroyFunc, DeclNameLoc(), /*Implicit=*/true);
2956+
auto selfRef = new (ctx) DeclRefExpr(
2957+
deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true);
2958+
auto callExpr = CallExpr::createImplicit(
2959+
ctx, funcRef,
2960+
ArgumentList::createImplicit(
2961+
ctx,
2962+
{ Argument(SourceLoc(), Identifier(), selfRef)}
2963+
)
2964+
);
2965+
2966+
auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) });
2967+
return std::make_pair(braceStmt, /*typechecked=*/false);
2968+
}
2969+
2970+
void SwiftDeclSynthesizer::addExplicitDeinitIfRequired(
2971+
NominalTypeDecl *nominal, const clang::RecordDecl *clangType) {
2972+
auto destroyFunc = findExplicitDestroy(nominal, clangType);
2973+
if (!destroyFunc)
2974+
return;
2975+
2976+
ASTContext &ctx = nominal->getASTContext();
2977+
auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal);
2978+
destructor->setSynthesized(true);
2979+
destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true);
2980+
destructor->setBodySynthesizer(
2981+
synthesizeDeinitBodyForCustomDestroy, destroyFunc);
2982+
2983+
nominal->addMember(destructor);
2984+
}
2985+
28122986
FuncDecl *SwiftDeclSynthesizer::makeAvailabilityDomainPredicate(
28132987
const clang::VarDecl *var) {
28142988
ASTContext &ctx = ImporterImpl.SwiftContext;

0 commit comments

Comments
 (0)