Skip to content

[6.0] Metadata and runtime support for invertible protocol requirements #72709

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

Merged

Conversation

DougGregor
Copy link
Member

  • Explanation: Implement metadata and runtime support for invertible protocol requirements (which covers noncopyable generics), so that we can evaluate constraints such as T: Copyable at runtime, e.g., for conditional conformances and existentials.
  • Issue: rdar://123466649
  • Original PR: Metadata and runtime support for suppressible protocol requirements #72470
  • Reviewer: @mikeash , @al45tair
  • Risk: Moderate. This is a significant change to the runtime checking of generic constraints that affects all programs. Specifically, every time we check generic constraints (dynamic casting to existential types, conditional conformance checking, demangling a name to type metadat, etc.), every generic argument argument will be checked against the constraint T: Copyable unless it was specifically disabled (e.g., via a T: ~Copyable constraint). Moreover, this extends the metadata for nominal types and generic requirements in a manner that is backward compatible except for generic types that suppress conformances. We can't think of any way in which this would cause problems, but it's a risk.
  • Testing: New tests, source compatibility suite.

@DougGregor DougGregor requested a review from a team as a code owner March 29, 2024 20:10
@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor
Copy link
Member Author

@swift-ci please benchmark

@DougGregor
Copy link
Member Author

@swift-ci please test

We need to track suppressible protocols as part of metadata in the ABI.

(cherry picked from commit 32a5b4f)
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.

(cherry picked from commit b167eec)
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).

(cherry picked from commit 5b02006)
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).

(cherry picked from commit 11774e5)
(cherry picked from commit 5746c11)
Invertible protocols are currently always mangled with `Ri`, followed by
a single letter for each invertible protocol (e.g., `c` and `e` for
`Copyable` and `Escapable`, respectively), followed by the generic
parameter index. However, this requires that we extend the mangling
for any future invertible protocols, which mean they won't be
backward compatible.

Replace this mangling with one that mangles the bit # for the
invertible protocol, e.g., `Ri_` (followed by the generic parameter
index) is bit 0, which is `Copyable`. `Ri0_` (then generic parameter
index) is bit 1, which is `Escapable`. This allows us to round-trip
through mangled names for any invertible protocol, without any
knowledge of what the invertible protocol is, providing forward
compatibility. The same forward compatibility is present in all
metadata and the runtime, allowing us to add more invertible
protocols in the future without updating any of them, and also
allowing backward compatibility.

Only the demangling to human-readable strings maps the bit numbers
back to their names, and there's a fallback printing with just the bit
number when appropriate.

Also generalize the mangling a bit to allow for mangling of invertible
requirements on associated types, e.g., `S.Sequence: ~Copyable`. This
is currently unsupported by the compiler or runtime, but that may
change, and it was easy enough to finish off the mangling work for it.

(cherry picked from commit 757ebe2)
Collapse the representations of "suppressible" and "invertible"
protocol sets. Only minor adjustments were required.
We've decided to use the "invertible protocols" terminology throughout
the runtime and compiler, so move over to that terminology
consistently.
@DougGregor DougGregor force-pushed the dynamic-invertible-protocols-6.0 branch from 6481e69 to 5e3604b Compare March 29, 2024 23:52
@DougGregor
Copy link
Member Author

@swift-ci please test

@DougGregor DougGregor enabled auto-merge March 29, 2024 23:53
@DougGregor DougGregor merged commit 1e5659e into swiftlang:release/6.0 Apr 2, 2024
@DougGregor DougGregor deleted the dynamic-invertible-protocols-6.0 branch April 2, 2024 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants