Skip to content

Commit d2691dc

Browse files
authored
Merge pull request #83370 from Xazax-hun/addressable-param-copy-reapply
Reapply [cxx-interop] Avoid copies when accessing pointee
2 parents 3fc59c4 + 66d15a1 commit d2691dc

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
@@ -4117,6 +4117,50 @@ namespace {
41174117
return false;
41184118
}
41194119

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

4182+
auto swiftParams = result->getParameters();
4183+
bool hasSelf =
4184+
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4185+
auto returnIdx = swiftParams->size() + hasSelf;
4186+
4187+
if (inferSelfDependence(decl, result, returnIdx))
4188+
return;
4189+
41384190
// FIXME: this uses '0' as the result index. That only works for
41394191
// standalone functions with no parameters.
41404192
// See markReturnsUnsafeNonescapable() for a general approach.
41414193
auto &ASTContext = result->getASTContext();
4194+
41424195
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies;
41434196
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0,
41444197
/*isImmortal*/ true);
@@ -4161,10 +4214,7 @@ namespace {
41614214
}
41624215
};
41634216

4164-
auto swiftParams = result->getParameters();
4165-
bool hasSelf =
4166-
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result);
4167-
const auto dependencyVecSize = swiftParams->size() + hasSelf;
4217+
const auto dependencyVecSize = returnIdx;
41684218
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize);
41694219
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize);
41704220
SmallBitVector paramHasAnnotation(dependencyVecSize);
@@ -4243,7 +4293,7 @@ namespace {
42434293
? IndexSubset::get(Impl.SwiftContext,
42444294
scopedLifetimeParamIndicesForReturn)
42454295
: nullptr,
4246-
swiftParams->size() + hasSelf,
4296+
returnIdx,
42474297
/*isImmortal*/ false));
42484298
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) {
42494299
// Assume default constructed view types have no dependencies.
@@ -4282,6 +4332,10 @@ namespace {
42824332
}
42834333

42844334
Impl.diagnoseTargetDirectly(decl);
4335+
4336+
if (isReturnDependsOnSelf(result, lifetimeDependencies)) {
4337+
Impl.returnsSelfDependentValue.insert(result);
4338+
}
42854339
}
42864340

42874341
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)