-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[cxx-interop] Avoid copies when accessing pointee #82480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4097,7 +4097,8 @@ namespace { | |
func->setSelfIndex(selfIdx.value()); | ||
if (Impl.SwiftContext.LangOpts.hasFeature( | ||
Feature::AddressableParameters)) | ||
func->getImplicitSelfDecl()->setAddressable(); | ||
func->getAttrs().add(new (Impl.SwiftContext) | ||
AddressableSelfAttr(true)); | ||
} else { | ||
func->setStatic(); | ||
func->setImportAsStaticMember(); | ||
|
@@ -4145,6 +4146,34 @@ namespace { | |
return false; | ||
} | ||
|
||
// Inject lifetime annotations selectively for some STL types so we can use | ||
// unsafeAddress to avoid copies. | ||
bool inferSelfDependence(const clang::FunctionDecl *decl, | ||
AbstractFunctionDecl *result, size_t returnIdx) { | ||
const auto *method = dyn_cast<clang::CXXMethodDecl>(decl); | ||
if (!method) | ||
return false; | ||
const auto *enclosing = method->getParent(); | ||
if (enclosing->isInStdNamespace() && | ||
(enclosing->getName() == "unique_ptr" || | ||
enclosing->getName() == "shared_ptr") && | ||
method->isOverloadedOperator() && | ||
method->getOverloadedOperator() == clang::OO_Star) { | ||
Comment on lines
+4157
to
+4161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we would use API Notes to inject this, but that isn't possible right now since we don't support annotating C++ operators in API Notes, so this looks good. |
||
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies; | ||
SmallBitVector dependenciesOfRet(returnIdx); | ||
dependenciesOfRet[result->getSelfIndex()] = true; | ||
lifetimeDependencies.push_back(LifetimeDependenceInfo( | ||
nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet), | ||
returnIdx, | ||
/*isImmortal*/ false)); | ||
Impl.SwiftContext.evaluator.cacheOutput( | ||
LifetimeDependenceInfoRequest{result}, | ||
Impl.SwiftContext.AllocateCopy(lifetimeDependencies)); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
void addLifetimeDependencies(const clang::FunctionDecl *decl, | ||
AbstractFunctionDecl *result) { | ||
if (decl->getTemplatedKind() == clang::FunctionDecl::TK_FunctionTemplate) | ||
|
@@ -4163,10 +4192,19 @@ namespace { | |
CxxEscapability::Unknown) != CxxEscapability::NonEscapable; | ||
}; | ||
|
||
auto swiftParams = result->getParameters(); | ||
bool hasSelf = | ||
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result); | ||
auto returnIdx = swiftParams->size() + hasSelf; | ||
|
||
if (inferSelfDependence(decl, result, returnIdx)) | ||
return; | ||
|
||
// FIXME: this uses '0' as the result index. That only works for | ||
// standalone functions with no parameters. | ||
// See markReturnsUnsafeNonescapable() for a general approach. | ||
auto &ASTContext = result->getASTContext(); | ||
|
||
SmallVector<LifetimeDependenceInfo, 1> lifetimeDependencies; | ||
LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0, | ||
/*isImmortal*/ true); | ||
|
@@ -4189,10 +4227,7 @@ namespace { | |
} | ||
}; | ||
|
||
auto swiftParams = result->getParameters(); | ||
bool hasSelf = | ||
result->hasImplicitSelfDecl() && !isa<ConstructorDecl>(result); | ||
const auto dependencyVecSize = swiftParams->size() + hasSelf; | ||
const auto dependencyVecSize = returnIdx; | ||
SmallBitVector inheritLifetimeParamIndicesForReturn(dependencyVecSize); | ||
SmallBitVector scopedLifetimeParamIndicesForReturn(dependencyVecSize); | ||
SmallBitVector paramHasAnnotation(dependencyVecSize); | ||
|
@@ -4271,7 +4306,7 @@ namespace { | |
? IndexSubset::get(Impl.SwiftContext, | ||
scopedLifetimeParamIndicesForReturn) | ||
: nullptr, | ||
swiftParams->size() + hasSelf, | ||
returnIdx, | ||
/*isImmortal*/ false)); | ||
else if (auto *ctordecl = dyn_cast<clang::CXXConstructorDecl>(decl)) { | ||
// Assume default constructed view types have no dependencies. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
#pragma once | ||
|
||
static int copies2 = 0; | ||
|
||
struct CountCopies2 { | ||
CountCopies2() = default; | ||
CountCopies2(const CountCopies2& other) { ++copies2; } | ||
~CountCopies2() {} | ||
|
||
int getCopies() const { return copies2; } | ||
void method() {} | ||
void constMethod() const {} | ||
int field = 42; | ||
}; | ||
|
||
struct MySmartPtr { | ||
CountCopies2& operator*() const [[clang::lifetimebound]] { return *ptr; } | ||
|
||
CountCopies2* ptr; | ||
}; | ||
|
||
inline MySmartPtr getPtr() { return MySmartPtr{new CountCopies2()}; } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// RUN: %target-run-simple-swift(-I %S/Inputs -cxx-interoperability-mode=upcoming-swift -enable-experimental-feature AddressableParameters) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a strong preference here, but I'm curious to hear your thoughts around pros and cons of a run time test vs checking SIL or IR output There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think runtime checks are less fragile. SIL or the LLVM IR can break easily when some passes or the IR changes. Runtime tests might be a bit less performant but I think in this case they are more directly test what we want here because we can make copies observable and directly observe it. It is not always possible to observe the property we want to test at runtime, SIL or IR tests are great for that. Also, I think here we do not care about the shape of the IRs, just the fact that there are no copies. I guess one caveat is, the runtime test does not tell us why are there no copies? Is it because there were no copies to begin with or is it because some optimisations got rid of them? Despite this caveat, I prefer a runtime test in this case as I find it easier to read and write and less fragile in this case. |
||
|
||
// REQUIRES: executable_test | ||
// REQUIRES: swift_feature_AddressableParameters | ||
|
||
// https://github.com/apple/swift/issues/70226 | ||
// UNSUPPORTED: OS=windows-msvc | ||
|
||
import StdlibUnittest | ||
import StdUniquePtr | ||
import CustomSmartPtr | ||
import CxxStdlib | ||
|
||
var AvoidCopiesSuite = TestSuite("AvoidRedundantCopies") | ||
|
||
AvoidCopiesSuite.test("The pointee does not copy when passed as self") { | ||
let up = getNonCopyableUniquePtr() | ||
expectEqual(up.pointee.method(1), 42) | ||
expectEqual(up.pointee.method(1), 42) | ||
let cup = getCopyCountedUniquePtr(); | ||
expectEqual(cup.pointee.getCopies(), 0) | ||
cup.pointee.method() | ||
cup.pointee.constMethod() | ||
let _ = cup.pointee.field | ||
expectEqual(cup.pointee.getCopies(), 0) | ||
let copy = cup.pointee | ||
expectEqual(copy.getCopies(), 1) | ||
} | ||
|
||
AvoidCopiesSuite.test("The custom smart pointer pointee does not copy when passed as self") { | ||
let myptr = getPtr(); | ||
expectEqual(myptr.pointee.getCopies(), 0) | ||
myptr.pointee.method() | ||
myptr.pointee.constMethod() | ||
let _ = myptr.pointee.field | ||
expectEqual(myptr.pointee.getCopies(), 0) | ||
let copy = myptr.pointee | ||
expectEqual(copy.getCopies(), 1) | ||
} | ||
|
||
runAllTests() |
Uh oh!
There was an error while loading. Please reload this page.