-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix EscapeAnalysis::mayReleaseContent #36046
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
Conversation
The previous implementation made extremely subtle and specific assumptions about how the API is used which doesn't apply everywhere. It was trying very hard to avoid regressing performance relative to an even older implementation that didn't even try to consider deinitializer side effects. The aggressive logic was based on the idea that a release must have a corresponding retain somewhere in the same function and we don't care if the last release happens early if there are no more aliasing uses. All the unit tests I wrote previously were based on release hoisting, which happens to work given the way the API is used. But this logic is incorrect for retain sinking. In that case sinking past an "unrelated" release could cause the object to be freed early. See test/SILOptimizer/arc_crash.swift. With SemanticARC and other SIL improvements being made, I'm afraid bugs like this will begin to surface. To fix it, just remove the subtle logic to leave behind a simple and sound EscapeAnalysis API. To do better, we will need to rewrite the AliasAnalysis logic for release side effects, which is currently a tangled web. In the meantime, SemanticARC can handle many cases without EscapeAnalysis. Fixes rdar://74469299 (ARC miscompile: EscapeAnalysis::mayReleaseContent; potential use-after-free) While fixing this, add support for address-type queries too: Fixes rdar://74360041 (Assertion failed: (!releasedReference->getType().isAddress() && "an address is never a reference"), function mayReleaseContent
@slavapestov In this commit: For |
@swift-ci test |
@swift-ci test source compatibility |
Build failed |
@swift-ci test |
Build failed |
Build failed |
@atrick Is it worth running the benchmark suite too? |
@swift-ci benchmark |
@swift-ci test |
@swift-ci test source compatibility |
Build failed before running benchmark. |
Build failed |
Build failed |
@@ -744,7 +754,7 @@ bool AliasAnalysis::canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr) { | |||
// to be an owned reference and disallows addresses. Conservatively handle | |||
// address type arguments as and conservatively treat all other values | |||
// potential owned references. | |||
if (Arg->getType().isAddress() || EA->mayReleaseContent(Arg, Ptr)) | |||
if (Arg->getType().isAddress() || EA->mayReleaseReferenceContent(Arg, Ptr)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure we don't have any builtins with indirect arguments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to enable this now for builtins taking addresses
// to point to any other objects. | ||
EscapeAnalysis::PointerKind | ||
EscapeAnalysis::findObjectKind(SILType Ty, const SILFunction &F) const { | ||
if (Ty.isAddress()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this ever be an address? Operands to release instructions are not addresses and types of stored properties are not addresses either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ambivalent about whether this should be an assert. It's ok right now to call it on any type and it gives you a conservative answer for non-class types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fine with me. Maybe you can mention in a comment that it's (currently) never called for an address type.
return AnyPointer; | ||
|
||
auto *classDecl = Ty.getClassOrBoundGenericClass(); | ||
if (!classDecl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not handle structs and tuples as well? That's a pretty common case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Structs and tuples are handles by getPointerKind. References are always pointers in that case. This specifically looks at object properties.
for (VarDecl *property : classDecl->getStoredProperties()) { | ||
SILType fieldTy = | ||
objTy.getFieldType(property, M, expansion).getObjectType(); | ||
meetAggregateKind(findCachedPointerKind(fieldTy, F)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That logic looks wrong to me. In case a stored property is a reference, shouldn't it return ReferenceOnly? Instead it recurses into the referenced class type and will e.g. return NoPointer if the referenced class has only trivial properties.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed the new helper to findClassPropertiesPointerKind
with this comment
// For each field in the class, get the pointer kind for that field. For
// reference-type properties, this will be ReferenceOnly. For aggregates, it
// will be the meet over all aggregate fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I misread: it's calling findCachedPointerKind
and not findCachedClassPropertiesKind
@@ -113,6 +156,17 @@ EscapeAnalysis::findCachedPointerKind(SILType Ty, const SILFunction &F) const { | |||
return pointerKind; | |||
} | |||
|
|||
EscapeAnalysis::PointerKind | |||
EscapeAnalysis::findCachedObjectKind(SILType Ty, const SILFunction &F) const { | |||
auto iter = objectKindCache.find(Ty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caching the pointer kind sounds dangerous. The pointer kind is computed per function and can be different per function (because the resilience expansion can be different). But it's cached globally for all functions.
Maybe always use ResilienceExpansion::Minimal?
BTW, it's the same situation with the already existing pointerKindCache
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I changed that to Maximal after the last feedback without even thinking.
85e206f
to
5aa415e
Compare
@swift-ci test |
@swift-ci benchmark |
@swift-ci test source compatibility |
Deinitializer side effects severely handicap the connection-graph based EscapeAnalysis. Because it's not flow-sensitive, this approach falls apart whenever an object is released in the current function, which makes it seem to have escaped everywhere (it generally doesn't matter if it doesn't escape until the release point, but the analysis can't discern that). This can be slightly mitigated by realizing that releasing an object can only cause things it points to to escape if the object itself has references or pointers. Fix: Don't create a escaping content node when releasing an object that can't contain any references or pointers. The previous commit, "Fix EscapeAnalysis::mayReleaseContent" would defeat release-hoisting in important cases without doing anything else. Adding this extra precision to the connection graph avoids some regressions.
@swift-ci test |
@swift-ci test source compatibility |
Build failed |
@swift-ci benchmark |
Build failed |
Performance: -O
Code size: -OPerformance: -Osize
Code size: -OsizePerformance: -Onone
Code size: -swiftlibsHow to read the dataThe tables contain differences in performance which are larger than 8% and differences in code size which are larger than 1%.If you see any unexpected regressions, you should consider fixing the Noise: Sometimes the performance results (not code size!) contain false Hardware Overview
|
@swift-ci test |
Build failed |
@swift-ci smoke test linux |
Build failed |
The only SCK failure is SwiftDate, where I see lots of
|
@swift-ci test |
Build failed |
Build failed |
@swift-ci test |
The previous implementation made extremely subtle and specific
assumptions about how the API is used which doesn't apply
everywhere. It was trying very hard to avoid regressing performance
relative to an even older implementation that didn't even try to consider deinitializer side effects.
The aggressive logic was based on the idea that a release must have a
corresponding retain somewhere in the same function and we don't care
if the last release happens early if there are no more aliasing
uses. All the unit tests I wrote previously were based on release
hoisting, which happens to work given the way the API is used.
But this logic is incorrect for retain sinking. In that case sinking
past an "unrelated" release could cause the object to be freed
early. See test/SILOptimizer/arc_crash.swift.
With SemanticARC and other SIL improvements being made, I'm afraid
bugs like this will begin to surface.
To fix it, just remove the subtle logic to leave behind a simple and
sound EscapeAnalysis API. To do better, we will need to rewrite the
AliasAnalysis logic for release side effects, which is currently
a tangled web. In the meantime, SemanticARC can handle many cases without EscapeAnalysis.
Fixes rdar://74469299 (ARC miscompile:
EscapeAnalysis::mayReleaseContent; potential use-after-free)
While fixing this, add support for address-type queries too:
Fixes rdar://74360041 (Assertion failed:
(!releasedReference->getType().isAddress() && "an address is never a
reference"), function mayReleaseContent
Deinitializer side effects severely handicap the connection-graph
based EscapeAnalysis. Because it's not flow-sensitive, this approach
falls apart whenever an object is released in the current function,
which makes it seem to have escaped everywhere (it generally doesn't
matter if it doesn't escape until the release point, but the analysis
can't discern that).
This can be slightly mitigated by realizing that releasing an object
can only cause things it points to to escape if the object itself has
references or pointers.
Fix: Don't create a escaping content node when releasing an object
that can't contain any references or pointers. The previous commit,
"Fix EscapeAnalysis::mayReleaseContent" would defeat release-hoisting
in important cases without doing anything else. Adding this extra
precision to the connection graph avoids some regressions.