Skip to content

Commit ab1c929

Browse files
author
Gabor Horvath
committed
Reapply [cxx-interop] Avoid copies when accessing pointee
After #83289 and #82879 landed we should no longer get deserialization failures and this feature is no longer behind a flag. This patch also changes how we query if a function's return value depends on self. Previously, we queried the lifetime dependencies from the Swift declaration. Unfortunately, this is problematic as we might have not finished fully importing the types in the function signature just yet and the compiler might end up populating the conformance tables prematurely. To work this around, I store functions with self-dependent return values where lifetimes are computed in the importer for later use. The PR also adds a test to make sure the addressable dependency feature will not result in deserialization errors. rdar://155319311&154213694&112690482&128293252
1 parent aa16ff5 commit ab1c929

File tree

8 files changed

+182
-7
lines changed

8 files changed

+182
-7
lines changed

lib/ClangImporter/ImportDecl.cpp

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4090,6 +4090,50 @@ namespace {
40904090
return false;
40914091
}
40924092

4093+
// Inject lifetime annotations selectively for some STL types so we can use
4094+
// unsafeAddress to avoid copies.
4095+
bool inferSelfDependence(const clang::FunctionDecl *decl,
4096+
AbstractFunctionDecl *result, size_t returnIdx) {
4097+
const auto *method = dyn_cast<clang::CXXMethodDecl>(decl);
4098+
if (!method)
4099+
return false;
4100+
const auto *enclosing = method->getParent();
4101+
if (enclosing->isInStdNamespace() &&
4102+
(enclosing->getName() == "unique_ptr" ||
4103+
enclosing->getName() == "shared_ptr") &&
4104+
method->isOverloadedOperator() &&
4105+
method->getOverloadedOperator() == clang::OO_Star) {
4106+
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
4107+
SmallBitVector dependenciesOfRet(returnIdx);
4108+
dependenciesOfRet[result->getSelfIndex()] = true;
4109+
lifetimeDependencies.push_back(LifetimeDependenceInfo(
4110+
nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet),
4111+
returnIdx,
4112+
/*isImmortal*/ false));
4113+
Impl.SwiftContext.evaluator.cacheOutput(
4114+
LifetimeDependenceInfoRequest{result},
4115+
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
4116+
Impl.returnsSelfDependentValue.insert(result);
4117+
return true;
4118+
}
4119+
return false;
4120+
}
4121+
4122+
static bool isReturnDependsOnSelf(
4123+
AbstractFunctionDecl *f,
4124+
const ArrayRef<LifetimeDependenceInfo> &lifetimeDeps) {
4125+
if (isa<ConstructorDecl>(f) || !f->getImportAsMemberStatus().isInstance())
4126+
return false;
4127+
for (auto dependence : lifetimeDeps) {
4128+
auto returnIdx = f->getParameters()->size() + !isa<ConstructorDecl>(f);
4129+
if (!dependence.hasInheritLifetimeParamIndices() &&
4130+
dependence.hasScopeLifetimeParamIndices() &&
4131+
dependence.getTargetIndex() == returnIdx)
4132+
return dependence.getScopeIndices()->contains(f->getSelfIndex());
4133+
}
4134+
return false;
4135+
}
4136+
40934137
void addLifetimeDependencies(const clang::FunctionDecl *decl,
40944138
AbstractFunctionDecl *result) {
40954139
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate)
@@ -4108,10 +4152,19 @@ namespace {
41084152
CxxEscapability::Unknown) != CxxEscapability::NonEscapable;
41094153
};
41104154

4155+
auto swiftParams = result->getParameters();
4156+
bool hasSelf =
4157+
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4158+
auto returnIdx = swiftParams->size() + hasSelf;
4159+
4160+
if (inferSelfDependence(decl, result, returnIdx))
4161+
return;
4162+
41114163
// FIXME: this uses '0' as the result index. That only works for
41124164
// standalone functions with no parameters.
41134165
// See markReturnsUnsafeNonescapable() for a general approach.
41144166
auto &ASTContext = result->getASTContext();
4167+
41154168
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
41164169
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0,
41174170
/*isImmortal*/ true);
@@ -4134,10 +4187,7 @@ namespace {
41344187
}
41354188
};
41364189

4137-
auto swiftParams = result->getParameters();
4138-
bool hasSelf =
4139-
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4140-
const auto dependencyVecSize = swiftParams->size() + hasSelf;
4190+
const auto dependencyVecSize = returnIdx;
41414191
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize);
41424192
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
41434193
SmallBitVector paramHasAnnotation(dependencyVecSize);
@@ -4216,7 +4266,7 @@ namespace {
42164266
? IndexSubset::get(Impl.SwiftContext,
42174267
scopedLifetimeParamIndicesForReturn)
42184268
: nullptr,
4219-
swiftParams->size() + hasSelf,
4269+
returnIdx,
42204270
/*isImmortal*/ false));
42214271
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
42224272
// Assume default constructed view types have no dependencies.
@@ -4255,6 +4305,10 @@ namespace {
42554305
}
42564306

42574307
Impl.diagnoseTargetDirectly(decl);
4308+
4309+
if (isReturnDependsOnSelf(result, lifetimeDependencies)) {
4310+
Impl.returnsSelfDependentValue.insert(result);
4311+
}
42584312
}
42594313

42604314
void finishFuncDecl(const clang::FunctionDecl *decl,

lib/ClangImporter/ImporterImpl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,12 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
728728
/// properties.
729729
llvm::DenseMap<const clang::FunctionDecl *, VarDecl *> FunctionsAsProperties;
730730

731+
/// Calling AbstractFunctionDecl::getLifetimeDependencies before we added
732+
/// the conformances we want to all the imported types is problematic because
733+
/// it will populate the conformance too early. To avoid the need for that we
734+
/// store functions with interesting lifetimes.
735+
llvm::DenseSet<const AbstractFunctionDecl *> returnsSelfDependentValue;
736+
731737
importer::EnumInfo getEnumInfo(const clang::EnumDecl *decl) {
732738
return getNameImporter().getEnumInfo(decl);
733739
}

lib/ClangImporter/SwiftDeclSynthesizer.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,7 @@ SubscriptDecl *SwiftDeclSynthesizer::makeSubscript(FuncDecl *getter,
16911691
FuncDecl *getterImpl = getter ? getter : setter;
16921692
FuncDecl *setterImpl = setter;
16931693

1694+
// FIXME: support unsafeAddress accessors.
16941695
// Get the return type wrapped in `Unsafe(Mutable)Pointer<T>`.
16951696
const auto rawElementTy = getterImpl->getResultInterfaceType();
16961697
// Unwrap `T`. Use rawElementTy for return by value.
@@ -1784,6 +1785,8 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17841785
FuncDecl *getterImpl = getter ? getter : setter;
17851786
FuncDecl *setterImpl = setter;
17861787
auto dc = getterImpl->getDeclContext();
1788+
bool resultDependsOnSelf =
1789+
ImporterImpl.returnsSelfDependentValue.contains(getterImpl);
17871790

17881791
// Get the return type wrapped in `Unsafe(Mutable)Pointer<T>`.
17891792
const auto rawElementTy = getterImpl->getResultInterfaceType();
@@ -1794,9 +1797,9 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17941797
// Use 'address' or 'mutableAddress' accessors for non-copyable
17951798
// types that are returned indirectly.
17961799
bool isNoncopyable = dc->mapTypeIntoContext(elementTy)->isNoncopyable();
1797-
bool isImplicit = !isNoncopyable;
1800+
bool isImplicit = !(isNoncopyable || resultDependsOnSelf);
17981801
bool useAddress =
1799-
rawElementTy->getAnyPointerElementType() && isNoncopyable;
1802+
rawElementTy->getAnyPointerElementType() && (isNoncopyable || resultDependsOnSelf);
18001803

18011804
auto result = new (ctx)
18021805
VarDecl(/*isStatic*/ false, VarDecl::Introducer::Var,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// This ensures that a Swift module built without AddressableParameters can be
2+
// consumed by a dependency Swift module that enables AddressableParameters.
3+
4+
// RUN: rm -rf %t
5+
// RUN: split-file %s %t
6+
7+
// RUN: %target-build-swift %t/library.swift -emit-module -emit-library -enable-library-evolution -cxx-interoperability-mode=upcoming-swift -module-name MyLibrary -emit-module-path %t/artifacts/MyLibrary.swiftmodule -emit-module-interface-path %t/artifacts/MyLibrary.swiftinterface -I %S/Inputs -swift-version 6 -enable-experimental-feature AssumeResilientCxxTypes
8+
// RUN: rm %t/artifacts/MyLibrary.swiftmodule
9+
// RUN: %target-build-swift %t/executable.swift -emit-irgen -cxx-interoperability-mode=default -module-name ImportsMyLibrary -I %t/artifacts -I %S/Inputs -swift-version 6 -enable-experimental-feature AssumeResilientCxxTypes -enable-experimental-feature AddressableParameters
10+
11+
// REQUIRES: swift_feature_AddressableParameters
12+
// REQUIRES: swift_feature_AssumeResilientCxxTypes
13+
14+
//--- library.swift
15+
import Methods
16+
17+
@inlinable // emit the body of the function into the textual interface
18+
public func addressableTest(_ x: borrowing NonTrivialInWrapper) {
19+
let m = HasMethods()
20+
m.nonTrivialTakesConstRef(x)
21+
}
22+
23+
//--- executable.swift
24+
import MyLibrary
25+
import Methods
26+
27+
let x = NonTrivialInWrapper(123)
28+
let m = HasMethods()
29+
m.nonTrivialTakesConstRef(x)
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: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift)
2+
3+
// REQUIRES: executable_test
4+
5+
// https://github.com/apple/swift/issues/70226
6+
// UNSUPPORTED: OS=windows-msvc
7+
8+
import StdlibUnittest
9+
import StdUniquePtr
10+
import CustomSmartPtr
11+
import CxxStdlib
12+
13+
var AvoidCopiesSuite = TestSuite("AvoidRedundantCopies")
14+
15+
AvoidCopiesSuite.test("The pointee does not copy when passed as self") {
16+
let up = getNonCopyableUniquePtr()
17+
expectEqual(up.pointee.method(1), 42)
18+
expectEqual(up.pointee.method(1), 42)
19+
let cup = getCopyCountedUniquePtr();
20+
expectEqual(cup.pointee.getCopies(), 0)
21+
cup.pointee.method()
22+
cup.pointee.constMethod()
23+
let _ = cup.pointee.field
24+
expectEqual(cup.pointee.getCopies(), 0)
25+
let copy = cup.pointee
26+
expectEqual(copy.getCopies(), 1)
27+
}
28+
29+
AvoidCopiesSuite.test("The custom smart pointer pointee does not copy when passed as self") {
30+
let myptr = getPtr();
31+
expectEqual(myptr.pointee.getCopies(), 0)
32+
myptr.pointee.method()
33+
myptr.pointee.constMethod()
34+
let _ = myptr.pointee.field
35+
expectEqual(myptr.pointee.getCopies(), 0)
36+
let copy = myptr.pointee
37+
expectEqual(copy.getCopies(), 1)
38+
}
39+
40+
runAllTests()

0 commit comments

Comments
 (0)