Skip to content

[cxx-interop] Support lifetime_capture_by in ClangImporter #77617

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
Dec 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
94 changes: 70 additions & 24 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsClangImporter.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/Expr.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/GenericSignature.h"
#include "swift/AST/LifetimeDependence.h"
#include "swift/AST/Module.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/NameLookupRequests.h"
Expand All @@ -49,6 +49,7 @@
#include "swift/Basic/StringExtras.h"
#include "swift/Basic/Version.h"
#include "swift/ClangImporter/CXXMethodBridging.h"
#include "swift/ClangImporter/ClangImporter.h"
#include "swift/ClangImporter/ClangImporterRequests.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/Parse/Lexer.h"
Expand All @@ -65,6 +66,7 @@
#include "clang/Sema/Lookup.h"

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
Expand Down Expand Up @@ -3545,19 +3547,20 @@ namespace {
return true;
}

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

Decl *importFunctionDecl(
Expand Down Expand Up @@ -3869,7 +3872,8 @@ namespace {
if (selfIsInOut)
func->setSelfAccessKind(SelfAccessKind::Mutating);
else {
if (implicitObjectParamIsLifetimeBound(decl))
if (getImplicitObjectParamAnnotation<clang::LifetimeBoundAttr>(
decl))
func->setSelfAccessKind(SelfAccessKind::Borrowing);
else
func->setSelfAccessKind(SelfAccessKind::NonMutating);
Expand Down Expand Up @@ -3961,26 +3965,68 @@ namespace {
auto swiftParams = result->getParameters();
bool hasSelf =
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
SmallBitVector inheritLifetimeParamIndicesForReturn(swiftParams->size() +
hasSelf);
SmallBitVector scopedLifetimeParamIndicesForReturn(swiftParams->size() +
hasSelf);
for (auto [idx, param] : llvm::enumerate(decl->parameters())) {
if (param->hasAttr<clang::LifetimeBoundAttr>()) {
warnForEscapableReturnType();
if (swiftParams->get(idx)->getInterfaceType()->isEscapable())
scopedLifetimeParamIndicesForReturn[idx] = true;
else
inheritLifetimeParamIndicesForReturn[idx] = true;
}
}
if (implicitObjectParamIsLifetimeBound(decl)) {
const auto dependencyVecSize = swiftParams->size() + hasSelf;
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize);
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
std::map<unsigned, SmallBitVector> inheritedArgDependences;
auto processLifetimeBound = [&](unsigned idx, Type ty) {
warnForEscapableReturnType();
auto idx = result->getSelfIndex();
if (result->getImplicitSelfDecl()->getInterfaceType()->isEscapable())
if (ty->isEscapable())
scopedLifetimeParamIndicesForReturn[idx] = true;
else
inheritLifetimeParamIndicesForReturn[idx] = true;
};
auto processLifetimeCaptureBy =
[&](const clang::LifetimeCaptureByAttr *attr, unsigned idx, Type ty) {
// FIXME: support scoped lifetimes. This is not straightforward as
// const T& is imported as taking a value
// and we assume the address of T would not escape. An
// annotation in this case contradicts our assumptions. We
// should diagnose that, and support this for the non-const
// case.
if (ty->isEscapable())
return;
for (auto param : attr->params()) {
// FIXME: Swift assumes no escaping to globals. We should diagnose
// this.
if (param == clang::LifetimeCaptureByAttr::GLOBAL ||
param == clang::LifetimeCaptureByAttr::UNKNOWN ||
param == clang::LifetimeCaptureByAttr::INVALID)
continue;

if (isa<clang::CXXMethodDecl>(decl) &&
param == clang::LifetimeCaptureByAttr::THIS) {
auto [it, inserted] = inheritedArgDependences.try_emplace(
result->getSelfIndex(), SmallBitVector(dependencyVecSize));
it->second[idx] = true;
} else {
auto [it, inserted] = inheritedArgDependences.try_emplace(
param - isa<clang::CXXMethodDecl>(decl),
SmallBitVector(dependencyVecSize));
it->second[idx] = true;
}
}
};
for (auto [idx, param] : llvm::enumerate(decl->parameters())) {
if (param->hasAttr<clang::LifetimeBoundAttr>())
processLifetimeBound(idx, swiftParams->get(idx)->getInterfaceType());
if (const auto *attr = param->getAttr<clang::LifetimeCaptureByAttr>())
processLifetimeCaptureBy(attr, idx,
swiftParams->get(idx)->getInterfaceType());
}
if (getImplicitObjectParamAnnotation<clang::LifetimeBoundAttr>(decl))
processLifetimeBound(result->getSelfIndex(),
result->getImplicitSelfDecl()->getInterfaceType());
if (auto attr =
getImplicitObjectParamAnnotation<clang::LifetimeCaptureByAttr>(
decl))
processLifetimeCaptureBy(
attr, result->getSelfIndex(),
result->getImplicitSelfDecl()->getInterfaceType());

for (auto& [idx, inheritedDepVec]: inheritedArgDependences) {
lifetimeDependencies.push_back(LifetimeDependenceInfo(inheritedDepVec.any() ? IndexSubset::get(Impl.SwiftContext,
inheritedDepVec): nullptr, nullptr, idx, /*isImmortal=*/false));
}

if (inheritLifetimeParamIndicesForReturn.any() ||
Expand Down
34 changes: 33 additions & 1 deletion test/Interop/Cxx/class/nonescapable-lifetimebound.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,29 @@ View returnsImmortal() SWIFT_RETURNS_INDEPENDENT_VALUE {
return View();
}

void copyView(View view1 [[clang::lifetime_capture_by(view2)]], View &view2) {
view2 = view1;
}

struct SWIFT_NONESCAPABLE CaptureView {
CaptureView() : view(nullptr) {}
CaptureView(View p [[clang::lifetimebound]]) : view(p) {}

void captureView(View v [[clang::lifetime_capture_by(this)]]) {
view = v;
}

void handOut(View &v) const [[clang::lifetime_capture_by(v)]] {
v = view;
}

View view;
};

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

// CHECK: sil [clang makeOwner] {{.*}}: $@convention(c) () -> Owner
// CHECK: sil [clang getView] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow 0) @autoreleased View
// CHECK: sil [clang getViewFromFirst] {{.*}} : $@convention(c) (@in_guaranteed Owner, @in_guaranteed Owner) -> @lifetime(borrow 0) @autoreleased View
Expand All @@ -92,6 +115,10 @@ View returnsImmortal() SWIFT_RETURNS_INDEPENDENT_VALUE {
// CHECK: sil [clang View.init] {{.*}} : $@convention(c) () -> @lifetime(immortal) @out View
// CHECK: sil [clang OtherView.init] {{.*}} : $@convention(c) (@guaranteed View) -> @lifetime(copy 0) @out OtherView
// CHECK: sil [clang returnsImmortal] {{.*}} : $@convention(c) () -> @lifetime(immortal) @autoreleased View
// CHECK: sil [clang copyView] {{.*}} : $@convention(c) (View, @lifetime(copy 0) @inout View) -> ()
// CHECK: sil [clang getCaptureView] {{.*}} : $@convention(c) (@in_guaranteed Owner) -> @lifetime(borrow 0) @autoreleased CaptureView
// CHECK: sil [clang CaptureView.captureView] {{.*}} : $@convention(cxx_method) (View, @lifetime(copy 0) @inout CaptureView) -> ()
// CHECK: sil [clang CaptureView.handOut] {{.*}} : $@convention(cxx_method) (@lifetime(copy 1) @inout View, @in_guaranteed CaptureView) -> ()

// CHECK-NO-LIFETIMES: nonescapable.h:36:6: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
// CHECK-NO-LIFETIMES: nonescapable.h:40:6: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
Expand All @@ -104,6 +131,7 @@ View returnsImmortal() SWIFT_RETURNS_INDEPENDENT_VALUE {
// CHECK-NO-LIFETIMES: nonescapable.h:13:5: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
// CHECK-NO-LIFETIMES: nonescapable.h:14:5: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
// CHECK-NO-LIFETIMES: nonescapable.h:68:6: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
// CHECK-NO-LIFETIMES: nonescapable.h:91:13: error: returning ~Escapable type requires '-enable-experimental-feature LifetimeDependence'
// CHECK-NO-LIFETIMES-NOT: error

//--- test.swift
Expand All @@ -113,7 +141,7 @@ import Test
public func test() {
let o = makeOwner()
let o2 = makeOwner()
let v1 = getView(o)
var v1 = getView(o)
let v2 = getViewFromFirst(o, o2)
let _ = getViewFromEither(o, o2)
let _ = o.handOutView()
Expand All @@ -122,4 +150,8 @@ public func test() {
let defaultView = View()
let _ = OtherView(defaultView)
let _ = returnsImmortal()
copyView(v2, &v1)
var cv = getCaptureView(o)
cv.captureView(v1)
cv.handOut(&v1)
}