-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Metadata and runtime support for suppressible protocol requirements #72470
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
Metadata and runtime support for suppressible protocol requirements #72470
Conversation
We need to track suppressible protocols as part of metadata in the ABI.
6337048
to
1b5be2a
Compare
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.
Looks good!
I'm sure you've thought about this so this is just me being paranoid and double-checking: we're pretty sure we'll never need more than 16 of these? I can't think of anything close to 14 more that we'd ever want to add, but, paranoid.
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
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 bump this to 2024.
// | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
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.
Likewise 2024.
constexpr bool contains##Name() const { \ | ||
return contains(SuppressibleProtocolKind::Name); \ | ||
} | ||
#include "swift/ABI/SuppressibleProtocols.def" |
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.
A static assert that the value for Bit
fits into StorageType
might be nice. Unlikely we'd ever hit it, but would save some pain in the off chance we did.
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.
Sure. I'll bury it in a C++ file somewhere
1b5be2a
to
579a965
Compare
@swift-ci please test |
579a965
to
1d0848d
Compare
Introduce metadata and runtime support for describing conformances to "suppressible" protocols such as `Copyable`. The metadata changes occur in several different places: * Context descriptors gain a flag bit to indicate when the type itself has suppressed one or more suppressible protocols (e.g., it is `~Copyable`). When the bit is set, the context will have a trailing `SuppressibleProtocolSet`, a 16-bit bitfield that records one bit for each suppressed protocol. Types with no suppressed conformances will leave the bit unset (so the metadata is unchanged), and older runtimes don't look at the bit, so they will ignore the extra data. * Generic context descriptors gain a flag bit to indicate when the type has conditional conformances to suppressible protocols. When set, there will be trailing metadata containing another `SuppressibleProtocolSet` (a subset of the one in the main context descriptor) indicating which suppressible protocols have conditional conformances, followed by the actual lists of generic requirements for each of the conditional conformances. Again, if there are no conditional conformances to suppressible protocols, the bit won't be set. Old runtimes ignore the bit and any trailing metadata. * Generic requirements get a new "kind", which provides an ignored protocol set (another `SuppressibleProtocolSet`) stating which suppressible protocols should *not* be checked for the subject type of the generic requirement. For example, this encodes a requirement like `T: ~Copyable`. These generic requirements can occur anywhere that there is a generic requirement list, e.g., conditional conformances and extended existentials. Older runtimes handle unknown generic requirement kinds by stating that the requirement isn't satisfied. Extend the runtime to perform checking of the suppressible conformances on generic arguments as part of checking generic requirements. This checking follows the defaults of the language, which is that every generic argument must conform to each of the suppressible protocols unless there is an explicit generic requirement that states which suppressible protocols to ignore. Thus, a generic parameter list `<T, Y where T: ~Escapable>` will check that `T` is `Copyable` but not that it is `Escapable`, and check that `U` is both `Copyable` and `Escapable`. To implement this, we collect the ignored protocol sets from these suppressed requirements while processing the generic requirements, then check all of the generic arguments against any conformances not suppressed. Answering the actual question "does `X` conform to `Copyable`?" (for any suppressible protocol) looks at the context descriptor metadata to answer the question, e.g., 1. If there is no "suppressed protocol set", then the type conforms. This covers types that haven't suppressed any conformances, including all types that predate noncopyable generics. 2. If the suppressed protocol set doesn't contain `Copyable`, then the type conforms. 3. If the type is generic and has a conditional conformance to `Copyable`, evaluate the generic requirements for that conditional conformance to answer whether it conforms. The procedure above handles the bits of a `SuppressibleProtocolSet` opaquely, with no mapping down to specific protocols. Therefore, the same implementation will work even with future suppressible protocols, including back deployment. The end result of this is that we can dynamically evaluate conditional conformances to protocols that depend on conformances to suppressible protocols. Implements rdar://123466649.
1d0848d
to
b167eec
Compare
@swift-ci please test |
I still have more to do, but this is passing all tests locally (finally). |
Everyone seems to think we'll be okay with 16. If not, we'll have a non-back-deployable issue with the 17th. |
Add more runtime support for checking suppressible protocol requirements: * Parameter packs now check all of the arguments appropriately * Most structural types now implement checking (these are hard to test).
Added support for variadic generics and structural types, including "noescape" function types (based on the existing bits). |
a5d742f
to
ef7d9df
Compare
@swift-ci please test |
Form a set of suppressed protocols for a function type based on the extended flags (where future compilers can start recording suppressible protocols) and the existing "noescape" bit. Compare that against the "ignored" suppressible protocol requirements, as we do for other types. This involves a behavior change if any client has managed to evade the static checking for noescape function types, but it's unlikely that existing code has done so (and it was unsafe anyway).
ef7d9df
to
11774e5
Compare
@swift-ci please smoke test |
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.
Spotted a few typos. Otherwise looks good.
auto suppressedBits = conditionallySuppressed.rawBits(); | ||
unsigned priorBits = 0; | ||
for (unsigned i = 0; i != targetBit; ++i) { | ||
if (suppressedBits & 0x01) | ||
++priorBits; | ||
suppressedBits = suppressedBits >> 1; | ||
} |
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 can't remember which C++ revision we require, but C++20 has std::popcount()
for this. Alternatively LLVM has llvm::popcount()
(but we'd probably need to add the necessary header to our copy in the stdlib
directory?) and Clang has __builtin_popcount()
.
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.
We'd probably need to pull in the header. I'd like to handle this as a follow-up, because dragging another LLVM header into the runtime is the kind of thing that will find a way to blow up on me somewhere.
Help, I've been replaced by a snake who puts too many "s"'s in everything |
@swift-ci please smoke test |
@swift-ci please smoke test Windows |
@swift-ci please test |
@swift-ci please smoke test |
@swift-ci please smoke test |
Introduce metadata and runtime support for describing conformances to
"suppressible" protocols such as
Copyable
. The metadata changes occurin several different places:
suppressed one or more suppressible protocols (e.g., it is
~Copyable
).When the bit is set, the context will have a trailing
SuppressibleProtocolSet
, a 16-bit bitfield that records one bit foreach suppressed protocol. Types with no suppressed conformances will
leave the bit unset (so the metadata is unchanged), and older runtimes
don't look at the bit, so they will ignore the extra data.
has conditional conformances to suppressible protocols. When set,
there will be trailing metadata containing another
SuppressibleProtocolSet
(a subset of the one in the main contextdescriptor) indicating which suppressible protocols have conditional
conformances, followed by the actual lists of generic requirements
for each of the conditional conformances. Again, if there are no
conditional conformances to suppressible protocols, the bit won't be
set. Old runtimes ignore the bit and any trailing metadata.
protocol set (another
SuppressibleProtocolSet
) stating whichsuppressible protocols should not be checked for the subject type
of the generic requirement. For example, this encodes a requirement
like
T: ~Copyable
. These generic requirements can occur anywherethat there is a generic requirement list, e.g., conditional
conformances and extended existentials. Older runtimes handle unknown
generic requirement kinds by stating that the requirement isn't
satisfied.
Extend the runtime to perform checking of the suppressible
conformances on generic arguments as part of checking generic
requirements. This checking follows the defaults of the language, which
is that every generic argument must conform to each of the suppressible
protocols unless there is an explicit generic requirement that states
which suppressible protocols to ignore. Thus, a generic parameter list
<T, Y where T: ~Escapable>
will check thatT
isCopyable
butnot that it is
Escapable
, and check thatU
is bothCopyable
andEscapable
. To implement this, we collect the ignored protocol setsfrom these suppressed requirements while processing the generic
requirements, then check all of the generic arguments against any
conformances not suppressed.
Answering the actual question "does
X
conform toCopyable
?" (forany suppressible protocol) looks at the context descriptor metadata to
answer the question, e.g.,
This covers types that haven't suppressed any conformances, including
all types that predate noncopyable generics.
Copyable
, then thetype conforms.
Copyable
, evaluate the generic requirements for that conditionalconformance to answer whether it conforms.
The procedure above handles the bits of a
SuppressibleProtocolSet
opaquely, with no mapping down to specific protocols. Therefore, the
same implementation will work even with future suppressible protocols,
including back deployment.
The end result of this is that we can dynamically evaluate conditional
conformances to protocols that depend on conformances to suppressible
protocols.
Implements rdar://123466649.