Skip to content

Commit 16f625f

Browse files
author
Gabor Horvath
committed
[cxx-interop] Avoid copies when accessing pointee
Previously, we would get two copies, one accessing the pointee and one when we pass the pointee as a method as the implicit self argument. These copies are unsafe as they might introduce slicing. When addressable paramaters features are enabled, we no longer make these copies for the standard STL types. Custom smart pointers can replicate this by making the lifetime dependency between the implicit object parameter and the returned reference of operator* explicit via a lifetime annotation. rdar://154213694&128293252&112690482
1 parent a28515e commit 16f625f

File tree

6 files changed

+143
-8
lines changed

6 files changed

+143
-8
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4097,7 +4097,8 @@ namespace {
40974097
func->setSelfIndex(selfIdx.value());
40984098
if (Impl.SwiftContext.LangOpts.hasFeature(
40994099
Feature::AddressableParameters))
4100-
func->getImplicitSelfDecl()->setAddressable();
4100+
func->getAttrs().add(new (Impl.SwiftContext)
4101+
AddressableSelfAttr(true));
41014102
} else {
41024103
func->setStatic();
41034104
func->setImportAsStaticMember();
@@ -4145,6 +4146,34 @@ namespace {
41454146
return false;
41464147
}
41474148

4149+
// Inject lifetime annotations selectively for some STL types so we can use
4150+
// unsafeAddress to avoid copies.
4151+
bool inferSelfDependence(const clang::FunctionDecl *decl,
4152+
AbstractFunctionDecl *result, size_t returnIdx) {
4153+
const auto *method = dyn_cast<clang::CXXMethodDecl>(decl);
4154+
if (!method)
4155+
return false;
4156+
const auto *enclosing = method->getParent();
4157+
if (enclosing->isInStdNamespace() &&
4158+
(enclosing->getName() == "unique_ptr" ||
4159+
enclosing->getName() == "shared_ptr") &&
4160+
method->isOverloadedOperator() &&
4161+
method->getOverloadedOperator() == clang::OO_Star) {
4162+
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
4163+
SmallBitVector dependenciesOfRet(returnIdx);
4164+
dependenciesOfRet[result->getSelfIndex()] = true;
4165+
lifetimeDependencies.push_back(LifetimeDependenceInfo(
4166+
nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet),
4167+
returnIdx,
4168+
/*isImmortal*/ false));
4169+
Impl.SwiftContext.evaluator.cacheOutput(
4170+
LifetimeDependenceInfoRequest{result},
4171+
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
4172+
return true;
4173+
}
4174+
return false;
4175+
}
4176+
41484177
void addLifetimeDependencies(const clang::FunctionDecl *decl,
41494178
AbstractFunctionDecl *result) {
41504179
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate)
@@ -4163,10 +4192,19 @@ namespace {
41634192
CxxEscapability::Unknown) != CxxEscapability::NonEscapable;
41644193
};
41654194

4195+
auto swiftParams = result->getParameters();
4196+
bool hasSelf =
4197+
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4198+
auto returnIdx = swiftParams->size() + hasSelf;
4199+
4200+
if (inferSelfDependence(decl, result, returnIdx))
4201+
return;
4202+
41664203
// FIXME: this uses '0' as the result index. That only works for
41674204
// standalone functions with no parameters.
41684205
// See markReturnsUnsafeNonescapable() for a general approach.
41694206
auto &ASTContext = result->getASTContext();
4207+
41704208
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
41714209
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0,
41724210
/*isImmortal*/ true);
@@ -4189,10 +4227,7 @@ namespace {
41894227
}
41904228
};
41914229

4192-
auto swiftParams = result->getParameters();
4193-
bool hasSelf =
4194-
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4195-
const auto dependencyVecSize = swiftParams->size() + hasSelf;
4230+
const auto dependencyVecSize = returnIdx;
41964231
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize);
41974232
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
41984233
SmallBitVector paramHasAnnotation(dependencyVecSize);
@@ -4271,7 +4306,7 @@ namespace {
42714306
? IndexSubset::get(Impl.SwiftContext,
42724307
scopedLifetimeParamIndicesForReturn)
42734308
: nullptr,
4274-
swiftParams->size() + hasSelf,
4309+
returnIdx,
42754310
/*isImmortal*/ false));
42764311
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
42774312
// Assume default constructed view types have no dependencies.

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,7 @@ SubscriptDecl *SwiftDeclSynthesizer::makeSubscript(FuncDecl *getter,
16791679
FuncDecl *getterImpl = getter ? getter : setter;
16801680
FuncDecl *setterImpl = setter;
16811681

1682+
// FIXME: support unsafeAddress accessors.
16821683
// Get the return type wrapped in `Unsafe(Mutable)Pointer<T>`.
16831684
const auto rawElementTy = getterImpl->getResultInterfaceType();
16841685
// Unwrap `T`. Use rawElementTy for return by value.
@@ -1761,6 +1762,23 @@ SubscriptDecl *SwiftDeclSynthesizer::makeSubscript(FuncDecl *getter,
17611762
return subscript;
17621763
}
17631764

1765+
static bool doesReturnDependsOnSelf(FuncDecl *f) {
1766+
if (!f->getASTContext().LangOpts.hasFeature(Feature::AddressableParameters))
1767+
return false;
1768+
if (!f->hasImplicitSelfDecl())
1769+
return false;
1770+
if (auto deps = f->getLifetimeDependencies()) {
1771+
for (auto dependence : *deps) {
1772+
auto returnIdx = f->getParameters()->size() + !isa<ConstructorDecl>(f);
1773+
if (!dependence.hasInheritLifetimeParamIndices() &&
1774+
dependence.hasScopeLifetimeParamIndices() &&
1775+
dependence.getTargetIndex() == returnIdx)
1776+
return dependence.getScopeIndices()->contains(f->getSelfIndex());
1777+
}
1778+
}
1779+
return false;
1780+
}
1781+
17641782
// MARK: C++ dereference operator
17651783

17661784
VarDecl *
@@ -1772,6 +1790,7 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17721790
FuncDecl *getterImpl = getter ? getter : setter;
17731791
FuncDecl *setterImpl = setter;
17741792
auto dc = getterImpl->getDeclContext();
1793+
bool resultDependsOnSelf = doesReturnDependsOnSelf(getterImpl);
17751794

17761795
// Get the return type wrapped in `Unsafe(Mutable)Pointer<T>`.
17771796
const auto rawElementTy = getterImpl->getResultInterfaceType();
@@ -1782,9 +1801,9 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17821801
// Use 'address' or 'mutableAddress' accessors for non-copyable
17831802
// types that are returned indirectly.
17841803
bool isNoncopyable = dc->mapTypeIntoContext(elementTy)->isNoncopyable();
1785-
bool isImplicit = !isNoncopyable;
1804+
bool isImplicit = !(isNoncopyable || resultDependsOnSelf);
17861805
bool useAddress =
1787-
rawElementTy->getAnyPointerElementType() && isNoncopyable;
1806+
rawElementTy->getAnyPointerElementType() && (isNoncopyable || resultDependsOnSelf);
17881807

17891808
auto result = new (ctx)
17901809
VarDecl(/*isStatic*/ false, VarDecl::Introducer::Var,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
static int copies2 = 0;
4+
5+
struct CountCopies2 {
6+
CountCopies2() = default;
7+
CountCopies2(const CountCopies2& other) { ++copies2; }
8+
~CountCopies2() {}
9+
10+
int getCopies() const { return copies2; }
11+
void method() {}
12+
void constMethod() const {}
13+
int field = 42;
14+
};
15+
16+
struct MySmartPtr {
17+
CountCopies2& operator*() const [[clang::lifetimebound]] { return *ptr; }
18+
19+
CountCopies2* ptr;
20+
};
21+
22+
inline MySmartPtr getPtr() { return MySmartPtr{new CountCopies2()}; }

test/Interop/Cxx/stdlib/Inputs/module.modulemap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,9 @@ module NoCXXStdlib {
8181
requires cplusplus
8282
export *
8383
}
84+
85+
module CustomSmartPtr {
86+
header "custom-smart-ptr.h"
87+
requires cplusplus
88+
export *
89+
}

test/Interop/Cxx/stdlib/Inputs/std-unique-ptr.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,19 @@ std::shared_ptr<std::string> makeStringShared() {
6666
return std::make_unique<std::string>("Shared string");
6767
}
6868

69+
static int copies = 0;
70+
71+
struct CountCopies {
72+
CountCopies() = default;
73+
CountCopies(const CountCopies& other) { ++copies; }
74+
~CountCopies() {}
75+
76+
int getCopies() const { return copies; }
77+
void method() {}
78+
void constMethod() const {}
79+
int field = 42;
80+
};
81+
82+
inline std::unique_ptr<CountCopies> getCopyCountedUniquePtr() { return std::make_unique<CountCopies>(); }
83+
6984
#endif // TEST_INTEROP_CXX_STDLIB_INPUTS_STD_UNIQUE_PTR_H
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift -enable-experimental-feature AddressableParameters)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: swift_feature_AddressableParameters
5+
6+
import StdlibUnittest
7+
import StdUniquePtr
8+
import CustomSmartPtr
9+
import CxxStdlib
10+
11+
var AvoidCopiesSuite = TestSuite("AvoidRedundantCopies")
12+
13+
AvoidCopiesSuite.test("The pointee does not copy when passed as self") {
14+
let up = getNonCopyableUniquePtr()
15+
expectEqual(up.pointee.method(1), 42)
16+
expectEqual(up.pointee.method(1), 42)
17+
let cup = getCopyCountedUniquePtr();
18+
expectEqual(cup.pointee.getCopies(), 0)
19+
cup.pointee.method()
20+
cup.pointee.constMethod()
21+
let _ = cup.pointee.field
22+
expectEqual(cup.pointee.getCopies(), 0)
23+
let copy = cup.pointee
24+
expectEqual(copy.getCopies(), 1)
25+
}
26+
27+
AvoidCopiesSuite.test("The custom smart pointer pointee does not copy when passed as self") {
28+
let myptr = getPtr();
29+
expectEqual(myptr.pointee.getCopies(), 0)
30+
myptr.pointee.method()
31+
myptr.pointee.constMethod()
32+
let _ = myptr.pointee.field
33+
expectEqual(myptr.pointee.getCopies(), 0)
34+
let copy = myptr.pointee
35+
expectEqual(copy.getCopies(), 1)
36+
}
37+
38+
runAllTests()

0 commit comments

Comments
 (0)