-
Notifications
You must be signed in to change notification settings - Fork 10.5k
RCIdentity: fix another case of not-RC-identity-preserving casts. #34383
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
@swift-ci test |
@swift-ci benchmark |
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
|
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.
This refactoring looks great, but I'm deeply confused about the logic behind which kinds of casts are allowed vs. disallowed, and how that applies to Archetypes.
lib/SIL/Utils/DynamicCasts.cpp
Outdated
CanType target = getTargetFormalType(); | ||
|
||
// Be conservative with generic parameters. | ||
if (source->is<ArchetypeType>() || target->is<ArchetypeType>()) |
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.
The ArchetypeType
check is confusing to me, and "be conservative" doesn't help. In this context, based on all the comments so far, the check implies that an archetype can be substituted with a Metatype. I don't think that true, is it? If so, what would that look like? Instead, the comment should indicate specifically what kind of cast we need to protect against here.
Or is it simply that one of the Archetypes may be a bridged type and one side is non-bridged? In that case, why don't we have the same problem with casting concrete types? (can a concrete bridgeable struct be cast to a bridged class?)
Why don't we treat Archetypes similar to existentials and allow the RC-preserving cast as long as requiresClass()
is true for both sides?
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 wonder if we could maybe have a different instruction or something like that. The problem here is as I understand it around bridging. Rather than using normal casts for bridging, lets use special casts. Then we have a nice split.
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 whether a cast is bridging depends entirely on the types and may depend on substituted types. I mainly want to know the rules for which type classifications may result in bridging (metatype to ObjC class, struct to ObjC object, ...), whether it's legal to substitute those types with archetypes. Maybe @slavapestov, @tbkka, or @Catfish-Man can help classify these dynamic casts.
For example, in a struct-to-ObjC-object cast, can we assume that the struct is always a trivial type? If so, we just need to comment the 'isTrivial' check to make it clear that it covers bridged structs too, not just metatypes. If not, it seems that we're still missing a case here.
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.
Remind me what Swift considers trivial? Is String trivial? I’d assume not...
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.
@Catfish-Man "trivial" basically means: no ref-counting involved, i.e. values can be copied with a simple memcpy. So yes, String is not trivial.
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.
@atrick I pushed a new version, which should make things clearer about archetypes. The idea is that an archetype can be a class or existential - we don't know.
This check is there to be absolutely on the safe side. Currently we even cannot hit this case, because the only cast instruction which can have an archetype is checked_cast_addr_br - which has the MayRelease attribute. And for RCidentity we don't deal with addr-casts.
I added the check to be future prove, once we switch to opaque values.
For the remaining logic (existential <-> class casts) I consulted with @tbkka (who rewrote big parts of the runtime cast machinery). This should catch all the cases where we can end up with not having RC identity.
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.
This change came out of some tests I'm developing to stress the cast machinery. My tests uncovered problems with casts where one side is AnyObject
and the other is a pointer type (metatype, class type, existential, etc). These cases were failing because the code was implicitly assuming that such casts simply copied a pointer, but my tests demonstrate cases where the AnyObject
references a container structure that in turn holds the pointer in question. In these cases, retain/release of the AnyObject
is not equivalent to retain/release on the contained pointer.
In particular, the previous logic could do the wrong thing in cases like the following:
let source : AnyObject = ....
// retain source for later use
let result = source as? ClassType
// Re-use source here
If source
above is just a pointer to a class instance, then it is valid to sink the retain past the cast:
let source : AnyObject = ClassType(). // <<== Simple case
let result = source as? ClassType
// retain source (which is the same pointer as result). <== THIS IS OK
// Re-use source
But this transformation is not valid if source
holds a pointer to a runtime container structure that in turn holds the class reference. In that case, the transformation above releases the container prematurely:
let source : AnyObject = ... something gotten from somewhere ...
// MUST retain `source` BEFORE the cast, since source may not be a simple class reference
let result = source as? ClassType
// Re-use source
This is primarily a concern when there are complex interactions with Obj-C code. The bridging support wraps Swift types into a variety of heap-allocated containers. The compiler cannot productively reason about these. They are being exchanged with Obj-C code that is not visible to the compiler, so their structure can really only be understood by dynamic inspection in the runtime casting code.
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.
@eeckstein @tbkka The code is more clear now. Thanks for your patience. It's always helpful for me to see more explanation like this in comments.
Just one question: we seem to be assuming that AnyObject -> Metatype casts will never happen because we don't check for trivial targets. If so, can we add that assertion at least?
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.
Oh? AnyObject -> Metatype casts can definitely happen. (Briefly: anything can be stored in an AnyObject
via casting and can later be pulled back out with another cast.)
7acd48b
to
0fb1b81
Compare
@swift-ci smoke test |
0fb1b81
to
368257c
Compare
@atrick yes, it makes sense to also check the target to be trivial. I pushed a new version where I check if the source and target mismatch in "triviality". |
@swift-ci smoke test |
I tried testing this PR combined with my PR #33761 on Ubuntu 16.04 and it looks good so far. (There was something strange with the merge, though, so it's possible I wasn't exactly testing what I hoped.) |
When casting from existentials to class - and vice versa - it can happen that a cast is not RC identity preserving (because of potential bridging). This also affects mayRelease() of such cast instructions. For details see the comments in SILDynamicCastInst::isRCIdentityPreserving(). This change also includes some refactoring: I centralized the logic in SILDynamicCastInst::isRCIdentityPreserving(). rdar://problem/70454804
368257c
to
e8e613b
Compare
@swift-ci smoke test and merge |
When casting from existentials to class - and vice versa - it can happen that a cast is not RC identity preserving (because of potential bridging).
This also affects mayRelease() of such cast instructions.
For details see the comments in SILDynamicCastInst::isRCIdentityPreserving().
This change also includes some refactoring: I centralized the logic in SILDynamicCastInst::isRCIdentityPreserving().
rdar://problem/70454804