Skip to content

[Clang importer] Allow noncopyable C structs to define "destroy" operation #83364

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7887,8 +7887,9 @@ getRefParentDecls(const clang::RecordDecl *decl, ASTContext &ctx,
return matchingDecls;
}

static llvm::SmallVector<ValueDecl *, 1>
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name) {
llvm::SmallVector<ValueDecl *, 1>
importer::getValueDeclsForName(
const clang::Decl *decl, ASTContext &ctx, StringRef name) {
llvm::SmallVector<ValueDecl *, 1> results;
auto *clangMod = decl->getOwningModule();
if (clangMod && clangMod->isSubModule())
Expand Down Expand Up @@ -8232,6 +8233,9 @@ CxxRecordSemantics::evaluate(Evaluator &evaluator,

auto cxxDecl = dyn_cast<clang::CXXRecordDecl>(decl);
if (!cxxDecl) {
if (hasNonCopyableAttr(decl))
return CxxRecordSemanticsKind::MoveOnly;

return CxxRecordSemanticsKind::Trivial;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2687,6 +2687,9 @@ namespace {
}
}

// If we need it, add an explicit "deinit" to this type.
synthesizer.addExplicitDeinitIfRequired(result, decl);

result->setMemberLoader(&Impl, 0);
return result;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +2179,14 @@ inline clang::QualType desugarIfBoundsAttributed(clang::QualType type) {
/// If \a type is an elaborated type, it should be desugared first.
ImportedType findOptionSetEnum(clang::QualType type,
ClangImporter::Implementation &Impl);

/// Find value declarations in the same module as the given Clang declaration
/// and with the given name.
///
/// The name we're looking for is the Swift name.
llvm::SmallVector<ValueDecl *, 1>
getValueDeclsForName(const clang::Decl *decl, ASTContext &ctx, StringRef name);

} // end namespace importer
} // end namespace swift

Expand Down
65 changes: 45 additions & 20 deletions lib/ClangImporter/SwiftBridging/swift/bridging
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// -*- C++ -*-
//===------------------ bridging - C++ and Swift Interop --------*- C++ -*-===//
// -*- C -*-
//===------------------ bridging - C and Swift Interop ----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
Expand Down Expand Up @@ -32,7 +32,7 @@
/// that return a `class` or `struct` type that is annotated with this macro.
#define SWIFT_SELF_CONTAINED __attribute__((swift_attr("import_owned")))

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

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

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

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

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

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

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

/// Specifies that a specific c++ type such class or struct should be imported
/// as a non-escapable Swift value type when the non-escapable language feature
/// is enabled.
/// Specifies that a `class` or `struct` should be imported as a non-copyable
/// Swift value type that calls the given `_destroy` function when a value is no
/// longer used.
///
/// This example shows how to use this macro to let Swift know that
/// a given C struct should have its members freed when it goes out of scope:
/// ```c
/// typedef struct SWIFT_NONCOPYABLE_WITH_DESTROY(mytypeFreeMembers) MyType {
/// void *storage
/// } MyType;
///
/// void mytypeFreeMembers(MyType toBeDestroyed);
/// MyType mytypeCreate(void);
/// ```
///
/// Usage in Swift:
/// ```swift
/// let mt = mytypeCreate()
/// let mt2 = mt // consumes mt
/// // once mt2 is unused, Swift will call mytypeFreeMembers(mt2)
/// ```
#define SWIFT_NONCOPYABLE_WITH_DESTROY(_destroy) \
__attribute__((swift_attr("~Copyable"))) \
__attribute__((swift_attr(_CXX_INTEROP_STRINGIFY(destroy:_destroy))))

/// Specifies that a specific class or struct should be imported
/// as a non-escapable Swift value type.
#define SWIFT_NONESCAPABLE \
__attribute__((swift_attr("~Escapable")))

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

/// Specifies that the return value is passed as owned for C++ functions and
/// Specifies that the return value is passed as owned for functions and
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
#define SWIFT_RETURNS_RETAINED __attribute__((swift_attr("returns_retained")))
/// Specifies that the return value is passed as unowned for C++ functions and
/// Specifies that the return value is passed as unowned for functions and
/// methods returning types annotated as `SWIFT_SHARED_REFERENCE`
#define SWIFT_RETURNS_UNRETAINED \
__attribute__((swift_attr("returns_unretained")))

/// Applied to a C++ foreign reference type annotated with
/// SWIFT_SHARED_REFERENCE. Indicates that C++ APIs returning this type are
/// Applied to a foreign reference type annotated with
/// SWIFT_SHARED_REFERENCE. Indicates that APIs returning this type are
/// assumed to return an unowned (+0) value by default, unless explicitly annotated
/// with SWIFT_RETURNS_RETAINED.
///
Expand All @@ -203,7 +227,7 @@
/// Bar { ... };
/// ```
///
/// In Swift, C++ APIs returning `Bar*` will be assumed to return an unowned
/// In Swift, APIs returning `Bar*` will be assumed to return an unowned
/// value.
#define SWIFT_RETURNED_AS_UNRETAINED_BY_DEFAULT \
__attribute__((swift_attr("returned_as_unretained_by_default")))
Expand Down Expand Up @@ -258,6 +282,7 @@
#define SWIFT_MUTATING
#define SWIFT_UNCHECKED_SENDABLE
#define SWIFT_NONCOPYABLE
#define SWIDT_NONCOPYABLE_WITH_DESTROY(_destroy)
#define SWIFT_NONESCAPABLE
#define SWIFT_ESCAPABLE
#define SWIFT_ESCAPABLE_IF(...)
Expand Down
174 changes: 174 additions & 0 deletions lib/ClangImporter/SwiftDeclSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "swift/AST/Stmt.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/Basic/Assertions.h"
#include "swift/ClangImporter/ClangImporterRequests.h"
#include "clang/AST/Mangle.h"
#include "clang/Sema/DelayedDiagnostic.h"

Expand Down Expand Up @@ -2809,6 +2810,179 @@ synthesizeAvailabilityDomainPredicateBody(AbstractFunctionDecl *afd,
return {body, /*isTypeChecked=*/false};
}

/// Mark the given declaration as always deprecated for the given reason.
static void markDeprecated(Decl *decl, llvm::Twine message) {
ASTContext &ctx = decl->getASTContext();
decl->getAttrs().add(
AvailableAttr::createUniversallyDeprecated(
ctx, ctx.AllocateCopy(message.str())));
}

/// Find an explicitly-provided "destroy" operation specified for the
/// given Clang type and return it.
FuncDecl *SwiftDeclSynthesizer::findExplicitDestroy(
NominalTypeDecl *nominal, const clang::RecordDecl *clangType) {
if (!clangType->hasAttrs())
return nullptr;

llvm::SmallPtrSet<FuncDecl *, 2> matchingDestroyFuncs;
llvm::TinyPtrVector<FuncDecl *> nonMatchingDestroyFuncs;
for (auto attr : clangType->getAttrs()) {
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
if (!swiftAttr)
continue;

auto destroyFuncName = swiftAttr->getAttribute();
if (!destroyFuncName.consume_front("destroy:"))
continue;

auto decls = getValueDeclsForName(
clangType, nominal->getASTContext(), destroyFuncName);
for (auto decl : decls) {
auto func = dyn_cast<FuncDecl>(decl);
if (!func)
continue;

auto params = func->getParameters();
if (params->size() != 1) {
nonMatchingDestroyFuncs.push_back(func);
continue;
}

if (!params->get(0)->getInterfaceType()->isEqual(
nominal->getDeclaredInterfaceType())) {
nonMatchingDestroyFuncs.push_back(func);
continue;
}

matchingDestroyFuncs.insert(func);
}
}

switch (matchingDestroyFuncs.size()) {
case 0:
if (!nonMatchingDestroyFuncs.empty()) {
markDeprecated(
nominal,
"destroy function '" +
nonMatchingDestroyFuncs.front()->getName().getBaseName()
.userFacingName() +
"' must have a single parameter with type '" +
nominal->getDeclaredInterfaceType().getString() + "'");
}

return nullptr;

case 1:
// Handled below.
break;

default: {
auto iter = matchingDestroyFuncs.begin();
auto first = *iter++;
auto second = *iter;
markDeprecated(
nominal,
"multiple destroy operations ('" +
first->getName().getBaseName().userFacingName() +
"' and '" +
second->getName().getBaseName().userFacingName() +
"') provided for type");
return nullptr;
}
}

auto destroyFunc = *matchingDestroyFuncs.begin();

// If this type isn't imported as noncopyable, we can't respect the request
// for a destroy operation.
ASTContext &ctx = ImporterImpl.SwiftContext;
auto semanticsKind = evaluateOrDefault(
ctx.evaluator,
CxxRecordSemantics({clangType, ctx, &ImporterImpl}), {});
switch (semanticsKind) {
case CxxRecordSemanticsKind::Owned:
case CxxRecordSemanticsKind::Reference:
if (auto cxxRecord = dyn_cast<clang::CXXRecordDecl>(clangType)) {
if (!cxxRecord->hasTrivialDestructor()) {
markDeprecated(
nominal,
"destroy operation '" +
destroyFunc->getName().getBaseName().userFacingName() +
"' is not allowed on types with a non-trivial destructor");
return nullptr;
}
}

LLVM_FALLTHROUGH;

case CxxRecordSemanticsKind::Trivial:
markDeprecated(
nominal,
"destroy operation '" +
destroyFunc->getName().getBaseName().userFacingName() +
"' is only allowed on non-copyable types; "
"did you mean to use SWIFT_NONCOPYABLE?");
return nullptr;

case CxxRecordSemanticsKind::Iterator:
case CxxRecordSemanticsKind::MissingLifetimeOperation:
case CxxRecordSemanticsKind::SwiftClassType:
return nullptr;

case CxxRecordSemanticsKind::MoveOnly:
case CxxRecordSemanticsKind::UnavailableConstructors:
// Handled below.
break;
}
if (semanticsKind != CxxRecordSemanticsKind::MoveOnly) {
return nullptr;
}

return destroyFunc;
}

/// Function body synthesizer for a deinit of a noncopyable type, which
/// passes "self" to the given "destroy" function.
static std::pair<BraceStmt *, bool>
synthesizeDeinitBodyForCustomDestroy(
AbstractFunctionDecl *deinitFunc, void *opaqueDestroyFunc) {
auto deinit = cast<DestructorDecl>(deinitFunc);
auto destroyFunc = static_cast<FuncDecl *>(opaqueDestroyFunc);

ASTContext &ctx = deinit->getASTContext();
auto funcRef = new (ctx) DeclRefExpr(
destroyFunc, DeclNameLoc(), /*Implicit=*/true);
auto selfRef = new (ctx) DeclRefExpr(
deinit->getImplicitSelfDecl(), DeclNameLoc(), /*Implicit=*/true);
auto callExpr = CallExpr::createImplicit(
ctx, funcRef,
ArgumentList::createImplicit(
ctx,
{ Argument(SourceLoc(), Identifier(), selfRef)}
)
);

auto braceStmt = BraceStmt::createImplicit(ctx, { ASTNode(callExpr) });
return std::make_pair(braceStmt, /*typechecked=*/false);
}

void SwiftDeclSynthesizer::addExplicitDeinitIfRequired(
NominalTypeDecl *nominal, const clang::RecordDecl *clangType) {
auto destroyFunc = findExplicitDestroy(nominal, clangType);
if (!destroyFunc)
return;

ASTContext &ctx = nominal->getASTContext();
auto destructor = new (ctx) DestructorDecl(SourceLoc(), nominal);
destructor->setSynthesized(true);
destructor->copyFormalAccessFrom(nominal, /*sourceIsParentContext*/true);
destructor->setBodySynthesizer(
synthesizeDeinitBodyForCustomDestroy, destroyFunc);

nominal->addMember(destructor);
}

FuncDecl *SwiftDeclSynthesizer::makeAvailabilityDomainPredicate(
const clang::VarDecl *var) {
ASTContext &ctx = ImporterImpl.SwiftContext;
Expand Down
Loading