Skip to content

Commit b00ff45

Browse files
authored
Merge pull request #82480 from swiftlang/gaborh/addressable_params_copy
[cxx-interop] Avoid copies when accessing pointee
2 parents 396379e + 7474a51 commit b00ff45

File tree

7 files changed

+147
-9
lines changed

7 files changed

+147
-9
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,

test/Interop/Cxx/class/method/methods-addressable-silgen.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public func addressableTest(x: borrowing @_addressable NonTrivialInWrapper, y: i
1717
// CHECK: %{{[0-9]+}} = apply %{{[0-9]+}}([[UNWRAPPED]], %{{[0-9]+}}) : $@convention(cxx_method) (@in_guaranteed NonTrivialInWrapper, @in_guaranteed HasMethods) -> ()
1818
var m2 = HasMethods()
1919
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [modify] [unknown] [[INPUT2]]
20-
// CHECK: %{{[0-9]+}} = apply %32([[ACCESS]], %{{[0-9]+}}) : $@convention(cxx_method) (@inout NonTrivialInWrapper, @in_guaranteed HasMethods) -> ()
20+
// CHECK: %{{[0-9]+}} = apply %{{[0-9]+}}([[ACCESS]], %{{[0-9]+}}) : $@convention(cxx_method) (@inout NonTrivialInWrapper, @in_guaranteed HasMethods) -> ()
2121
// CHECK-NEXT: end_access [[ACCESS]]
2222
m2.nonTrivialTakesRef(&y)
2323
}
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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
// https://github.com/apple/swift/issues/70226
7+
// UNSUPPORTED: OS=windows-msvc
8+
9+
import StdlibUnittest
10+
import StdUniquePtr
11+
import CustomSmartPtr
12+
import CxxStdlib
13+
14+
var AvoidCopiesSuite = TestSuite("AvoidRedundantCopies")
15+
16+
AvoidCopiesSuite.test("The pointee does not copy when passed as self") {
17+
let up = getNonCopyableUniquePtr()
18+
expectEqual(up.pointee.method(1), 42)
19+
expectEqual(up.pointee.method(1), 42)
20+
let cup = getCopyCountedUniquePtr();
21+
expectEqual(cup.pointee.getCopies(), 0)
22+
cup.pointee.method()
23+
cup.pointee.constMethod()
24+
let _ = cup.pointee.field
25+
expectEqual(cup.pointee.getCopies(), 0)
26+
let copy = cup.pointee
27+
expectEqual(copy.getCopies(), 1)
28+
}
29+
30+
AvoidCopiesSuite.test("The custom smart pointer pointee does not copy when passed as self") {
31+
let myptr = getPtr();
32+
expectEqual(myptr.pointee.getCopies(), 0)
33+
myptr.pointee.method()
34+
myptr.pointee.constMethod()
35+
let _ = myptr.pointee.field
36+
expectEqual(myptr.pointee.getCopies(), 0)
37+
let copy = myptr.pointee
38+
expectEqual(copy.getCopies(), 1)
39+
}
40+
41+
runAllTests()

0 commit comments

Comments
 (0)