Skip to content

Commit 66d15a1

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 299f173 commit 66d15a1

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
@@ -4107,6 +4107,50 @@ namespace {
41074107
return false;
41084108
}
41094109

4110+
// Inject lifetime annotations selectively for some STL types so we can use
4111+
// unsafeAddress to avoid copies.
4112+
bool inferSelfDependence(const clang::FunctionDecl *decl,
4113+
AbstractFunctionDecl *result, size_t returnIdx) {
4114+
const auto *method = dyn_cast<clang::CXXMethodDecl>(decl);
4115+
if (!method)
4116+
return false;
4117+
const auto *enclosing = method->getParent();
4118+
if (enclosing->isInStdNamespace() &&
4119+
(enclosing->getName() == "unique_ptr" ||
4120+
enclosing->getName() == "shared_ptr") &&
4121+
method->isOverloadedOperator() &&
4122+
method->getOverloadedOperator() == clang::OO_Star) {
4123+
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
4124+
SmallBitVector dependenciesOfRet(returnIdx);
4125+
dependenciesOfRet[result->getSelfIndex()] = true;
4126+
lifetimeDependencies.push_back(LifetimeDependenceInfo(
4127+
nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet),
4128+
returnIdx,
4129+
/*isImmortal*/ false));
4130+
Impl.SwiftContext.evaluator.cacheOutput(
4131+
LifetimeDependenceInfoRequest{result},
4132+
Impl.SwiftContext.AllocateCopy(lifetimeDependencies));
4133+
Impl.returnsSelfDependentValue.insert(result);
4134+
return true;
4135+
}
4136+
return false;
4137+
}
4138+
4139+
static bool isReturnDependsOnSelf(
4140+
AbstractFunctionDecl *f,
4141+
const ArrayRef<LifetimeDependenceInfo> &lifetimeDeps) {
4142+
if (isa<ConstructorDecl>(f) || !f->getImportAsMemberStatus().isInstance())
4143+
return false;
4144+
for (auto dependence : lifetimeDeps) {
4145+
auto returnIdx = f->getParameters()->size() + !isa<ConstructorDecl>(f);
4146+
if (!dependence.hasInheritLifetimeParamIndices() &&
4147+
dependence.hasScopeLifetimeParamIndices() &&
4148+
dependence.getTargetIndex() == returnIdx)
4149+
return dependence.getScopeIndices()->contains(f->getSelfIndex());
4150+
}
4151+
return false;
4152+
}
4153+
41104154
void addLifetimeDependencies(const clang::FunctionDecl *decl,
41114155
AbstractFunctionDecl *result) {
41124156
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate)
@@ -4125,10 +4169,19 @@ namespace {
41254169
CxxEscapability::Unknown) != CxxEscapability::NonEscapable;
41264170
};
41274171

4172+
auto swiftParams = result->getParameters();
4173+
bool hasSelf =
4174+
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4175+
auto returnIdx = swiftParams->size() + hasSelf;
4176+
4177+
if (inferSelfDependence(decl, result, returnIdx))
4178+
return;
4179+
41284180
// FIXME: this uses '0' as the result index. That only works for
41294181
// standalone functions with no parameters.
41304182
// See markReturnsUnsafeNonescapable() for a general approach.
41314183
auto &ASTContext = result->getASTContext();
4184+
41324185
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
41334186
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0,
41344187
/*isImmortal*/ true);
@@ -4151,10 +4204,7 @@ namespace {
41514204
}
41524205
};
41534206

4154-
auto swiftParams = result->getParameters();
4155-
bool hasSelf =
4156-
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4157-
const auto dependencyVecSize = swiftParams->size() + hasSelf;
4207+
const auto dependencyVecSize = returnIdx;
41584208
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize);
41594209
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
41604210
SmallBitVector paramHasAnnotation(dependencyVecSize);
@@ -4233,7 +4283,7 @@ namespace {
42334283
? IndexSubset::get(Impl.SwiftContext,
42344284
scopedLifetimeParamIndicesForReturn)
42354285
: nullptr,
4236-
swiftParams->size() + hasSelf,
4286+
returnIdx,
42374287
/*isImmortal*/ false));
42384288
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
42394289
// Assume default constructed view types have no dependencies.
@@ -4272,6 +4322,10 @@ namespace {
42724322
}
42734323

42744324
Impl.diagnoseTargetDirectly(decl);
4325+
4326+
if (isReturnDependsOnSelf(result, lifetimeDependencies)) {
4327+
Impl.returnsSelfDependentValue.insert(result);
4328+
}
42754329
}
42764330

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

lib/ClangImporter/ImporterImpl.h

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

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

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.
@@ -1788,6 +1789,8 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17881789
FuncDecl *getterImpl = getter ? getter : setter;
17891790
FuncDecl *setterImpl = setter;
17901791
auto dc = getterImpl->getDeclContext();
1792+
bool resultDependsOnSelf =
1793+
ImporterImpl.returnsSelfDependentValue.contains(getterImpl);
17911794

17921795
// Get the return type wrapped in `Unsafe(Mutable)Pointer<T>`.
17931796
const auto rawElementTy = getterImpl->getResultInterfaceType();
@@ -1798,9 +1801,9 @@ SwiftDeclSynthesizer::makeDereferencedPointeeProperty(FuncDecl *getter,
17981801
// Use 'address' or 'mutableAddress' accessors for non-copyable
17991802
// types that are returned indirectly.
18001803
bool isNoncopyable = dc->mapTypeIntoContext(elementTy)->isNoncopyable();
1801-
bool isImplicit = !isNoncopyable;
1804+
bool isImplicit = !(isNoncopyable || resultDependsOnSelf);
18021805
bool useAddress =
1803-
rawElementTy->getAnyPointerElementType() && isNoncopyable;
1806+
rawElementTy->getAnyPointerElementType() && (isNoncopyable || resultDependsOnSelf);
18041807

18051808
auto result = new (ctx)
18061809
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)