Skip to content

[cxx-interop] Teach importer to interpret lifetimebound annotations #76311

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 1 commit into from
Sep 18, 2024
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
5 changes: 0 additions & 5 deletions include/swift/AST/LifetimeDependence.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,6 @@ class LifetimeDependenceInfo {
static std::optional<ArrayRef<LifetimeDependenceInfo>>
get(AbstractFunctionDecl *decl);

/// Builds LifetimeDependenceInfo from the bitvectors passes as parameters.
static LifetimeDependenceInfo
get(ASTContext &ctx, const SmallBitVector &inheritLifetimeIndices,
const SmallBitVector &scopeLifetimeIndices);

/// Builds LifetimeDependenceInfo from SIL
static std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
get(FunctionTypeRepr *funcRepr, ArrayRef<SILParameterInfo> params,
Expand Down
83 changes: 80 additions & 3 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
//===----------------------------------------------------------------------===//

#include "CFTypeInfo.h"
#include "ImporterImpl.h"
#include "ClangDerivedConformances.h"
#include "ImporterImpl.h"
#include "SwiftDeclSynthesizer.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
Expand Down Expand Up @@ -53,6 +53,7 @@
#include "swift/Strings.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclObjCCommon.h"
#include "clang/Basic/Specifiers.h"
Expand Down Expand Up @@ -3474,6 +3475,21 @@ namespace {
return true;
}

static bool
implicitObjectParamIsLifetimeBound(const clang::FunctionDecl *FD) {
const clang::TypeSourceInfo *TSI = FD->getTypeSourceInfo();
if (!TSI)
return false;
clang::AttributedTypeLoc ATL;
for (clang::TypeLoc TL = TSI->getTypeLoc();
(ATL = TL.getAsAdjusted<clang::AttributedTypeLoc>());
TL = ATL.getModifiedLoc()) {
if (ATL.getAttrAs<clang::LifetimeBoundAttr>())
return true;
}
return false;
}

Decl *importFunctionDecl(
const clang::FunctionDecl *decl, ImportedName importedName,
std::optional<ImportedName> correctSwiftName,
Expand Down Expand Up @@ -3780,8 +3796,12 @@ namespace {
if (!dc->isModuleScopeContext()) {
if (selfIsInOut)
func->setSelfAccessKind(SelfAccessKind::Mutating);
else
func->setSelfAccessKind(SelfAccessKind::NonMutating);
else {
if (implicitObjectParamIsLifetimeBound(decl))
func->setSelfAccessKind(SelfAccessKind::Borrowing);
else
func->setSelfAccessKind(SelfAccessKind::NonMutating);
}
if (selfIdx) {
func->setSelfIndex(selfIdx.value());
} else {
Expand Down Expand Up @@ -3820,13 +3840,70 @@ namespace {
return result;
}

void addLifetimeDependencies(const clang::FunctionDecl *decl,
AbstractFunctionDecl *result) {
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate)
return;

auto swiftParams = result->getParameters();
bool hasSelf = result->hasImplicitSelfDecl();
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
SmallBitVector inheritLifetimeParamIndicesForReturn(swiftParams->size() +
hasSelf);
SmallBitVector scopedLifetimeParamIndicesForReturn(swiftParams->size() +
hasSelf);
for (auto [idx, param] : llvm::enumerate(decl->parameters())) {
if (param->hasAttr<clang::LifetimeBoundAttr>()) {
if (swiftParams->get(idx)->getInterfaceType()->isEscapable())
scopedLifetimeParamIndicesForReturn[idx] = true;
else
inheritLifetimeParamIndicesForReturn[idx] = true;
}
}
if (implicitObjectParamIsLifetimeBound(decl)) {
auto idx = result->getSelfIndex();
if (result->getImplicitSelfDecl()->getInterfaceType()->isEscapable())
scopedLifetimeParamIndicesForReturn[idx] = true;
else
inheritLifetimeParamIndicesForReturn[idx] = true;
}

if (inheritLifetimeParamIndicesForReturn.any() ||
scopedLifetimeParamIndicesForReturn.any())
lifetimeDependencies.push_back(LifetimeDependenceInfo(
inheritLifetimeParamIndicesForReturn.any()
? IndexSubset::get(Impl.SwiftContext,
inheritLifetimeParamIndicesForReturn)
: nullptr,
scopedLifetimeParamIndicesForReturn.any()
? IndexSubset::get(Impl.SwiftContext,
scopedLifetimeParamIndicesForReturn)
: nullptr,
swiftParams->size(),
/*isImmortal*/ false));
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
// Assume default constructed view types have no dependencies.
if (ctordecl->isDefaultConstructor() &&
importer::hasNonEscapableAttr(ctordecl->getParent()))
lifetimeDependencies.push_back(
LifetimeDependenceInfo(nullptr, nullptr, 0, /*isImmortal*/ true));
}
if (!lifetimeDependencies.empty()) {
Impl.SwiftContext.evaluator.cacheOutput(
LifetimeDependenceInfoRequest{result},
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
}
}

void finishFuncDecl(const clang::FunctionDecl *decl,
AbstractFunctionDecl *result) {
// Set availability.
if (decl->isVariadic()) {
Impl.markUnavailable(result, "Variadic function is unavailable");
}

addLifetimeDependencies(decl, result);

if (decl->hasAttr<clang::ReturnsTwiceAttr>()) {
// The Clang 'returns_twice' attribute is used for functions like
// 'vfork' or 'setjmp'. Because these functions may return control flow
Expand Down
6 changes: 3 additions & 3 deletions lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
//===----------------------------------------------------------------------===//

#include "CFTypeInfo.h"
#include "ClangDiagnosticConsumer.h"
#include "ImporterImpl.h"
#include "SwiftDeclSynthesizer.h"
#include "swift/ABI/MetadataValues.h"
Expand Down Expand Up @@ -51,7 +50,6 @@
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Compiler.h"

using namespace swift;
Expand Down Expand Up @@ -2649,7 +2647,9 @@ static ParamDecl *getParameterInfo(ClangImporter::Implementation *impl,
// Foreign references are already references so they don't need to be passed
// as inout.
paramInfo->setSpecifier(isInOut ? ParamSpecifier::InOut
: ParamSpecifier::Default);
: (param->getAttr<clang::LifetimeBoundAttr>()
? ParamSpecifier::Borrowing
: ParamSpecifier::Default));
paramInfo->setInterfaceType(swiftParamTy);
impl->recordImplicitUnwrapForDecl(paramInfo, isParamTypeImplicitlyUnwrapped);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ module Test {
//--- Inputs/nonescapable.h
#include "swift/bridging"

struct SWIFT_NONESCAPABLE A {
int a;
struct SWIFT_NONESCAPABLE View {
View() : member(nullptr) {}
View(const int *p [[clang::lifetimebound]]) : member(p) {}
View(const View&) = default;
private:
const int *member;
};

//--- test.swift

import Test

// CHECK: error: cannot infer lifetime dependence, no parameters found that are either ~Escapable or Escapable with a borrowing ownership
public func test() -> A {
A()
public func noAnnotations() -> View {
View()
}
88 changes: 88 additions & 0 deletions test/Interop/Cxx/class/nonescapable-lifetimebound.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: %target-swift-frontend -typecheck -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t/Inputs %t/test.swift -enable-experimental-feature NonescapableTypes -cxx-interoperability-mode=default -diagnostic-style llvm 2>&1
// RUN: %target-swift-frontend -I %swift_src_root/lib/ClangImporter/SwiftBridging -I %t/Inputs -emit-sil %t/test.swift -enable-experimental-feature NonescapableTypes -cxx-interoperability-mode=default -diagnostic-style llvm 2>&1 | %FileCheck %s

//--- Inputs/module.modulemap
module Test {
header "nonescapable.h"
requires cplusplus
}

//--- Inputs/nonescapable.h
#include "swift/bridging"

struct SWIFT_NONESCAPABLE View {
View() : member(nullptr) {}
View(const int *p [[clang::lifetimebound]]) : member(p) {}
View(const View&) = default;
private:
const int *member;
};

struct Owner {
int data;

View handOutView() const [[clang::lifetimebound]] {
return View(&data);
}
};

Owner makeOwner() {
return Owner{42};
}

View getView(const Owner& owner [[clang::lifetimebound]]) {
return View(&owner.data);
}

View getViewFromFirst(const Owner& owner [[clang::lifetimebound]], const Owner& owner2) {
return View(&owner.data);
}

bool coinFlip;

View getViewFromEither(const Owner& owner [[clang::lifetimebound]], const Owner& owner2 [[clang::lifetimebound]]) {
if (coinFlip)
return View(&owner.data);
else
return View(&owner2.data);
}

View getViewFromEither(View view1 [[clang::lifetimebound]], View view2 [[clang::lifetimebound]]) {
if (coinFlip)
return view1;
else
return view2;
}

struct SWIFT_NONESCAPABLE TestAnnotationTranslation {
TestAnnotationTranslation() : member(nullptr) {}
TestAnnotationTranslation(const int *p [[clang::lifetimebound]]) : member(p) {}
TestAnnotationTranslation(const TestAnnotationTranslation& [[clang::lifetimebound]]) = default;
private:
const int *member;
};

// CHECK: sil [clang makeOwner] {{.*}}: $@convention(c) () -> Owner
// CHECK: sil [clang getView] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> _scope(0) @autoreleased View
// CHECK: sil [clang getViewFromFirst] {{.*}} : $@convention(c) (@in_guaranteed Owner, @in_guaranteed Owner) -> _scope(0) @autoreleased View
// CHECK: sil [clang getViewFromEither] {{.*}} : $@convention(c) (@in_guaranteed Owner, @in_guaranteed Owner) -> _scope(0, 1) @autoreleased View
// CHECK: sil [clang Owner.handOutView] {{.*}} : $@convention(cxx_method) (@in_guaranteed Owner) -> _scope(0) @autoreleased View
// CHECK: sil [clang getViewFromEither] {{.*}} : $@convention(c) (@guaranteed View, @guaranteed View) -> _inherit(0, 1) @autoreleased View
// CHECK: sil [clang View.init] {{.*}} : $@convention(c) () -> @out View

//--- test.swift

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a module interface test that would check that the Swift lifetime attributes are printed in the generated interface for a C++ module?

See test/Interop/Cxx/class/access-specifiers-module-interface.swift for example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the Swift lifetimes are not supported by the module printer yet as there is no official/final syntax for it. That being said, I added checks like that because it is still useful to see what parameters are imported as borrowing. Let me know if that looks good.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meg-gupta is planning to add @lifetime() annotations to the module printer.
rdar://130342167 ([nonescapable] add .swiftinterface support for lifetime dependence)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write an -emit-sil test like test/SIL/lifetime_dependence_buffer_view_test.swift to ensure that the SIL representation has _scope/ _inherit in the interim.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly unrelated comment: I might be missing something but lifetime_dependence_buffer_view_test.swift does not seem to invoke FileCheck? Is that intentional, or a bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the test to check the SIL.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'll fix lifetime_dependence_buffer_view_test.swift to include FileCheck!

import Test

public func test() {
let o = makeOwner()
let o2 = makeOwner()
let v1 = getView(o)
let v2 = getViewFromFirst(o, o2)
let _ = getViewFromEither(o, o2)
let _ = o.handOutView()
let _ = getViewFromEither(v1, v2)
let defaultView = View()
}