Skip to content

Commit 3c0f177

Browse files
authored
Merge pull request #2395 from nate-chandler/bitwise-copyable-updates
[BitwiseCopyable] Post-review changes.
2 parents dcb34c4 + 5a0b342 commit 3c0f177

File tree

1 file changed

+146
-20
lines changed

1 file changed

+146
-20
lines changed

proposals/0426-bitwise-copyable.md

Lines changed: 146 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717

1818
## Introduction
1919

20-
We propose a new marker protocol `BitwiseCopyable` that can be conformed to by types that can be moved or copied with direct calls to `memcpy` and which require no special destroy operation[^1].
20+
We propose a new, [limited](#limitations) protocol `BitwiseCopyable` that _can_ be conformed to by types that are "bitwise-copyable"[^1]--that is, that can be moved or copied with direct calls to `memcpy` and which require no special destroy operation.
2121
When compiling generic code with such constraints, the compiler can emit these efficient operations directly, only requiring minimal overhead to look up the size of the value at runtime.
2222
Alternatively, developers can use this constraint to selectively provide high-performance variations of specific operations, such as bulk copying of a container.
2323

24-
[^1]: The term "trivial" is used in [SE-138](0138-unsaferawbufferpointer.md) and [SE-0370](0370-pointer-family-initialization-improvements.md) to refer to types with the property above. The discussion below will explain why certain generic or exported types that are trivial will not in fact be `BitwiseCopyable`.
24+
[^1]: The term "trivial" is used in [SE-138](0138-unsaferawbufferpointer.md) and [SE-0370](0370-pointer-family-initialization-improvements.md) to refer to types with this property. The discussion below will explain why certain generic or exported types that are bitwise-copyable will not in fact be `BitwiseCopyable`.
2525

2626
## Motivation
2727

@@ -57,12 +57,15 @@ We add a new protocol `BitwiseCopyable` to the standard library:
5757
@_marker public protocol BitwiseCopyable {}
5858
```
5959

60+
That a type conforms to the protocol [implies](#transient-and-permanent) that the type is bitwise-copyable; the reverse is _not_ true.
61+
6062
Many basic types in the standard library will conformed to this protocol.
6163

6264
Developer's own types may be conformed to the protocol, as well.
6365
The compiler will check any such conformance and emit a diagnostic if the type contains elements that are not `BitwiseCopyable`.
6466

65-
Furthermore, when building a module, the compiler will infer conformance to `BitwiseCopyable` for any non-exported struct or enum defined within the module whose stored members are all `BitwiseCopyable`.
67+
Furthermore, when building a module, the compiler will infer conformance to `BitwiseCopyable` for any non-exported struct or enum defined within the module whose stored members are all `BitwiseCopyable`,
68+
except those for which conformance is explicitly [suppressed](#suppression).
6669

6770
Developers cannot conform types defined in other modules to the protocol.
6871

@@ -148,7 +151,7 @@ When the module containing the type is built, if all of the type's fields are `B
148151

149152
For generic types, a conformance will only be inferred if its fields unconditionally conform to `BitwiseCopyable`.
150153
In the `RegularBox` example above, a conditional conformance will not be inferred.
151-
If this is desired, the developer can explicitly write the conditional conformance.
154+
If such a conformance is desired, the developer must explicitly write the conditional conformance.
152155

153156
[^2]: This includes raw-value enums. While such enums do include a conformance to `RawRepresentable` where `RawValue` could be a non-conforming type (`String`), the instances of the enums themselves are `BitwiseCopyable`.
154157

@@ -162,7 +165,7 @@ For an imported C struct, if all its fields are `BitwiseCopyable`, the compiler
162165
The same is true for an imported C++ struct or class, unless the type is non-trivial[^3].
163166

164167
For an imported C or C++ struct, if any of its fields cannot be represented in Swift, the compiler will not generate a conformance.
165-
This can be overridden, however, by annotating the type `__attribute__((__swift_attr__("_BitwiseCopyable")))`.
168+
This can be overridden, however, by annotating the type `__attribute__((__swift_attr__("BitwiseCopyable")))`.
166169

167170
[^3]: A C++ type is considered non-trivial (for the purpose of calls, as defined by the Itanium ABI) if any of the following is non-default: its constructor; its copy-constructor; its destructor.
168171

@@ -180,26 +183,65 @@ For example, the compiler will infer a conformance of the following struct
180183
```swift
181184
@frozen
182185
public struct Coordinate3 {
183-
var x: Int
184-
var y: Int
186+
public var x: Int
187+
public var y: Int
185188
}
186189
```
187190
to `BitwiseCopyable`.
188191

189-
### Suppressing inferred conformance
192+
### Suppressing inferred conformance<a name="suppression"/>
190193

191-
To suppress the inference of `BitwiseCopyable`, a conformance can explicitly be made unavailable:
194+
To suppress the inference of `BitwiseCopyable`, `~BitwiseCopyable` can be added to the type's inheritance list.
192195

196+
```swift
197+
struct Coordinate4 : ~BitwiseCopyable {...}
193198
```
194-
@available(*, unavailable)
195-
extension Coordinate4 : BitwiseCopyable {}
196-
```
199+
200+
Suppression must be declared on the type declaration itself, not on an extension.
201+
202+
### Transient and permanent notions<a name="transient-and-permanent"/>
203+
204+
The Swift runtime already describes[^4] whether a type is bitwise-copyable.
205+
It is surfaced, among other places, in the standard library function `_isPOD`[^5].
206+
207+
[^4]: The `IsNonPOD` value witness flag is set for every type that is _not_ bitwise-copyable.
208+
209+
[^5]: "POD" here is an acronym for "plain old data" which is yet another name for the notion of bitwise-copyable or trivial.
210+
211+
If a type conforms to `BitwiseCopyable`, then `_isPOD` must be true for the type.
212+
The converse is not true, however.
213+
214+
As a type evolves, it may [both gain _and_ lose bitwise-copyability](#fluctuating-bitwise-copyability).
215+
A type may only _gain_ a conformance to `BitwiseCopyable`, however;
216+
it cannot _lose_ its conformance without breaking source and ABI.
217+
218+
The two notions are related, but distinct:
219+
That a type `_isPOD` is a statement that the type is currently bitwise-copyable.
220+
That a type conforms to `BitwiseCopyable` is a promise that the type is now and will remain bitwise-copyable as the library evolves.
221+
In other words returning true from `_isPOD` is a transient property, and conformance to `BitwiseCopyable` is a permanent one.
222+
223+
For this reason, conformance to `BitwiseCopyable` is not inherent.
224+
Its declaration on a public type provides a guarantee that the compiler cannot infer.
225+
226+
### Limitations of BitwiseCopyable<a name="limitations"/>
227+
228+
Being declared with `@_marker`, `BitwiseCopyable` is a limited protocol.
229+
Its limited nature allows the protocol's runtime behavior to be defined later, as needed.
230+
231+
1. `BitwiseCopyable` cannot be extended.
232+
This limitation is similar to that on `Sendable` and `Any`:
233+
it prevents polluting the namespace of conforming types, especially types whose conformance is inferred.
234+
235+
2. Because conformance to `BitwiseCopyable` is distinct from being bitwise-copyable,
236+
the runtime cannot use the `IsNonPOD` bit as a proxy for conformance (although actual [conformance could be ignored](#casting-by-duck-typing)).
237+
A separate mechanism would be necessary.
238+
Until such a mechanism is added, `is`, `as?` and usage as a generic constraint to enable conditional conformance to another protocol is not possible.
197239

198240
### Standard library API improvements
199241

200242
The standard library includes a load method on both `UnsafeRawPointer` and `UnsafeMutableRawPointer`
201243

202-
```
244+
```swift
203245
@inlinable
204246
@_alwaysEmitIntoClient
205247
public func loadUnaligned<T>(
@@ -210,7 +252,7 @@ public func loadUnaligned<T>(
210252

211253
and a corresponding write method on `UnsafeMutableRawPointer`
212254

213-
```
255+
```swift
214256
@inlinable
215257
@_alwaysEmitIntoClient
216258
public func storeBytes<T>(
@@ -222,7 +264,7 @@ that must be called with a trivial `T`.
222264

223265
We propose adding overloads of these methods to constrain the value to `BitwiseCopyable`:
224266

225-
```
267+
```swift
226268
// on both UnsafeRawPointer and UnsafeMutableRawPointer
227269
@inlinable
228270
@_alwaysEmitIntoClient
@@ -241,6 +283,8 @@ public func storeBytes<T : BitwiseCopyable>(
241283

242284
This allows for optimal code generation because `memcpy` instead of value witnesses can be used.
243285

286+
Additionally, we propose deprecating the original, unconstrained overloads of `loadUnaligned` and `storeBytes`.
287+
244288
## Effect on ABI stability
245289

246290
The addition of the `BitwiseCopyable` constraint to either a type or a protocol in a library will not cause an ABI break for users.
@@ -249,7 +293,7 @@ The addition of the `BitwiseCopyable` constraint to either a type or a protocol
249293

250294
This addition of a new protocol will not impact existing source code that does not use it.
251295

252-
Removing the `BitwiseCopyable` marker from a type is source-breaking.
296+
Removing the `BitwiseCopyable` conformance from a type is source-breaking.
253297
As a result, future versions of Swift may conform additional existing types to `BitwiseCopyable`, but will not remove it from any type already conforming to `BitwiseCopyable`.
254298

255299
## Effect on API resilience
@@ -278,10 +322,42 @@ extension Box : BitwiseCopyable where Value : BitwiseCopyable {}
278322

279323
In the future we may in some cases be able to derive it automatically.
280324

281-
### MemoryLayout<T>.isBitwiseCopyable
325+
### Dynamic casting
326+
327+
Being a [limited](#limitations) protocol, `BitwiseCopyable` does not currently have any runtime representation.
328+
While a type's [transient](#transient-and-permanent) bitwise-copyability has a preexisting runtime representation, that is different from the type conforming to `BitwiseCopyable`.
329+
330+
Being a low-level, performance-enabling feature, it is not clear that dynamic casting should be allowed at all.
331+
If it were to be allowed at some point, a few different approaches can already be foreseen:
332+
333+
#### Explicitly record a type's conformance
334+
335+
The standard way to support dynamic casting would be to represent a type's conformance to the protocol and query the type at runtime.
336+
337+
This approach has the virtue that dynamic casting behaves as usual.
338+
A type could only be cast to `BitwiseCopyable` if it actually conformed to the protocol.
339+
For example, casting a type which suppressed a conformance to `BitwiseCopyable` would fail.
340+
341+
If this approach were taken, such casting could be back-deployed as far as the oldest OS in which this runtime representation was added.
342+
Further back deployment would be possible by adding conformance records to back deployed binaries.
282343

283-
In certain circumstances, it would be useful to be able to dynamically determine whether a type conforms to `BitwiseCopyable`.
284-
In order to allow that, a new field could be added to `MemoryLayout`.
344+
#### Duck typing for BitwiseCopyable<a name="casting-by-duck-typing"/>
345+
346+
An alternative would be to dynamically treat any type that's bitwise-copyable as if it conformed to `BitwiseCopyable`.
347+
348+
This is quite different from typical Swift casting behavior.
349+
Rather than relying on a permanent characteristic of the type, it would rely on a [transient](#transient-and-permanent) one.
350+
This would be visible to the programmer in several ways:
351+
- different overloads would be selected for a value of concrete type from those selected for a value dynamically cast to `BitwiseCopyable`
352+
- dynamic casts to `BitwiseCopyable` could fail, then succeed, then fail again in successive OS versions
353+
354+
On the other hand, these behavioral differences may be desireable.
355+
356+
Considering that this approach would just ignore the existence of conformances to `BitwiseCopyable`,
357+
it would be reasonable to ignore the existence of a suppressed conformance as well.
358+
359+
This approach also has the virtue of being completely back-deployable[^6].
360+
[^6]: All runtimes have had the `IsNonPOD` bit.
285361

286362
### BitwiseMovable
287363

@@ -297,7 +373,7 @@ typealias BitwiseCopyable = Bitwise & Copyable & DefaultDeinit
297373
```
298374
Such a definition remains possible after this proposal.
299375

300-
Because `BitwiseCopyable` is a marker protocol, its ABI is rather limited.
376+
Because `BitwiseCopyable` is annotated `@_marker`, its ABI is rather limited.
301377
Specifically, it only affects name mangling.
302378
If, in a subsequent proposal, the protocol were redefined as a composition, symbols into which `BitwiseCopyable` was mangled could still be mangled in the same way, ensuring ABI compatibility.
303379

@@ -356,3 +432,53 @@ The following types in the standard library will gain the `BitwiseCopyable` cons
356432
- `Hasher`
357433
- `ObjectIdentifier`
358434
- `Duration`
435+
436+
## Appendix: Fluctuating bitwise-copyability<a name="fluctuating-bitwise-copyability"/>
437+
438+
Let's say the following type is defined in a framework built with library evolution.
439+
440+
```swift
441+
public struct Dish {...}
442+
```
443+
444+
In the first version of the framework, the type only contains bitwise-copyable fields:
445+
446+
```swift
447+
/// NoodleKit v1.0
448+
449+
public struct Dish {
450+
public let substrate: Noodle
451+
public let isTopped: Bool
452+
}
453+
```
454+
455+
So in version `1.0`, the type is bitwise-copyable.
456+
457+
In the next version of the framework, to expose more information to its clients, the stored `Bool` is replaced with a stored `Array`:
458+
459+
```swift
460+
/// NoodleKit v1.1
461+
462+
public struct Dish {
463+
public let substrate: Noodle
464+
public let toppings: [Topping]
465+
public let isTopped: Bool { toppings.count > 0 }
466+
}
467+
```
468+
469+
As a result, in version `1.1`, the type is _not_ bitwise-copyable.
470+
471+
In a subsequent version, as an optimization, the stored `Array` is replaced with an `OptionSet`
472+
473+
```swift
474+
/// NoodleKit v2.0
475+
476+
public struct Dish {
477+
public let substrate: Noodle
478+
private let toppingOptions: Topping
479+
public let toppings: [Topping] { ... }
480+
public let isTopped: Bool { toppings.count > 0 }
481+
}
482+
```
483+
484+
In release `2.0` the type is once again bitwise-copyable.

0 commit comments

Comments
 (0)