Skip to content

Commit d44ebbf

Browse files
authored
Merge pull request #2314 from nate-chandler/bitwise-copyable
[BitwiseCopyable] Pitch.
2 parents 420a166 + 00d8a08 commit d44ebbf

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

proposals/0426-bitwise-copyable.md

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
# BitwiseCopyable
2+
3+
* Proposal: [SE-0426](0426-bitwise-copyable.md)
4+
* Authors: [Kavon Farvardin](https://github.com/kavon), [Guillaume Lessard](https://github.com/glessard), [Nate Chandler](https://github.com/nate-chandler), [Tim Kientzle](https://github.com/tbkka)
5+
* Review Manager: [Tony Allevato](https://github.com/allevato)
6+
* Implementation: On `main` gated behind `-enable-experimental-feature BitwiseCopyable`
7+
8+
<!-- *During the review process, add the following fields as needed:*
9+
10+
* Implementation: [apple/swift#NNNNN](https://github.com/apple/swift/pull/NNNNN) or [apple/swift-evolution-staging#NNNNN](https://github.com/apple/swift-evolution-staging/pull/NNNNN)
11+
* Decision Notes: [Rationale](https://forums.swift.org/), [Additional Commentary](https://forums.swift.org/)
12+
* Bugs: [SR-NNNN](https://bugs.swift.org/browse/SR-NNNN), [SR-MMMM](https://bugs.swift.org/browse/SR-MMMM)
13+
* Previous Revision: [1](https://github.com/apple/swift-evolution/blob/...commit-ID.../proposals/NNNN-filename.md)
14+
* Previous Proposal: [SE-XXXX](XXXX-filename.md) -->
15+
16+
## Introduction
17+
18+
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].
19+
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.
20+
Alternatively, developers can use this constraint to selectively provide high-performance variations of specific operations, such as bulk copying of a container.
21+
22+
[^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`.
23+
24+
## Motivation
25+
26+
Swift can compile generic code into an unspecialized form in which the compiled function receives a value and type information about that value.
27+
Basic operations are implemented by the compiler as calls to a table of "value witness functions."
28+
29+
This approach is flexible, but can represent significant overhead.
30+
For example, using this approach to copy a buffer with a large number of `Int` values requires a function call for each value.
31+
32+
Constraining the types in generic functions to `BitwiseCopyable` allows the compiler (and in some cases, the developer) to instead use highly efficient direct memory operations in such cases.
33+
34+
The standard library already contains many examples of functions that could benefit from such a concept, and more are being proposed:
35+
36+
The `UnsafeMutablePointer.initialize(to:count:)` function introduced in [SE-0370](0370-pointer-family-initialization-improvements.md) could use a bulk memory copy whenever it statically knew that its argument was `BitwiseCopyable`.
37+
38+
The proposal for [`StorageView`](nnnn-safe-shared-contiguous-storage.md) includes the ability to copy items to or from potentially-unaligned storage, which requires that it be safe to use bulk memory operations:
39+
```swift
40+
public func loadUnaligned<T: BitwiseCopyable>(
41+
fromByteOffset: Int = 0, as: T.Type
42+
) -> T
43+
44+
public func loadUnaligned<T: BitwiseCopyable>(
45+
from index: Index, as: T.Type
46+
) -> T
47+
```
48+
49+
And this proposal includes the addition of three overloads of existing standard library functions.
50+
51+
## Proposed solution
52+
53+
We add a new protocol `BitwiseCopyable` to the standard library:
54+
```swift
55+
@_marker public protocol BitwiseCopyable {}
56+
```
57+
58+
Many basic types in the standard library will conformed to this protocol.
59+
60+
Developer's own types may be conformed to the protocol, as well.
61+
The compiler will check any such conformance and emit a diagnostic if the type contains elements that are not `BitwiseCopyable`.
62+
63+
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`.
64+
65+
Developers cannot conform types defined in other modules to the protocol.
66+
67+
## Detailed design
68+
69+
Our design first conforms a number of core types to `BitwiseCopyable`, and then extends that to aggregate types.
70+
71+
### Standard library changes
72+
73+
Many types and a few key protocols are constrained to `BitwiseCopyable`.
74+
A few highlights:
75+
76+
* Integer types
77+
* Floating point types
78+
* SIMD types
79+
* Pointer types
80+
* `Unmanaged`
81+
* `Optional`
82+
83+
For an exhaustive list, see the [appendix](#all-stdlib-conformers).
84+
85+
### Additional BitwiseCopyable types
86+
87+
In addition to the standard library types marked above, the compiler will recognize several other types as `BitwiseCopyable`:
88+
89+
* Tuples of `BitwiseCopyable` elements.
90+
91+
* `unowned(unsafe)` references.
92+
Such references can be copied without reference counting operations.
93+
94+
* `@convention(c)` and `@convention(thin)` function types do not carry a reference-counted capture context, unlike other Swift function types, and are therefore `BitwiseCopyable`.
95+
96+
### Explicit conformance to `BitwiseCopyable`
97+
98+
Enum and struct types can be explicitly declared to conform to `BitwiseCopyable`.
99+
When a type is declared to conform, the compiler will check that its elements are all `BitwiseCopyable` and emit an error otherwise.
100+
101+
For example, the following struct can conform to `BitwiseCopayble`
102+
```swift
103+
public struct Coordinate : BitwiseCopyable {
104+
var x: Int
105+
var y: Int
106+
}
107+
```
108+
because `Int` is `BitwiseCopyable`.
109+
110+
Similarly, the following enum can conform to `BitwiseCopyable`
111+
```swift
112+
public enum PositionUpdate : BitwiseCopyable {
113+
case begin(Coordinate)
114+
case move(x_change: Int, y_change: Int)
115+
case end
116+
}
117+
```
118+
because both `Coordinate` and `(x_change: Int, y_change: Int)` are `BitwiseCopyable`.
119+
120+
The same applies to generic types. For example, the following struct can conform to `BitwiseCopyable`
121+
```swift
122+
struct BittyBox<Value : BitwiseCopyable> : BitwiseCopyable {
123+
var first: Value
124+
}
125+
```
126+
because its field `first` is a of type `Value` which is `BitwiseCopyable`.
127+
128+
Generic types may be `BitwiseCopyable` only some of the time.
129+
For example,
130+
```swift
131+
struct RegularBox<Value> {
132+
var first: Value
133+
}
134+
```
135+
cannot conform unconditionally because `Value` needn't conform to `BitwiseCopyable`.
136+
In this case, a conditional conformance may be written:
137+
138+
```swift
139+
extension Box : BitwiseCopyable where Value : BitwiseCopyable {}
140+
```
141+
142+
### Automatic inference for aggregates
143+
144+
As a convenience, unconditional conformances will be inferred for structs and enums[^2] much of the time.
145+
When the module containing the type is built, if all of the type's fields are `BitwiseCopyable`, the compiler will generate a conformance for it to `BitwiseCopyable`.
146+
147+
For generic types, a conformance will only be inferred if its fields unconditionally conform to `BitwiseCopyable`.
148+
In the `RegularBox` example above, a conditional conformance will not be inferred.
149+
If this is desired, the developer can explicitly write the conditional conformance.
150+
151+
[^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`.
152+
153+
### Inference for imported types
154+
155+
The same inference will be done on imported C and C++ types.
156+
157+
For an imported C or C++ enum, the compiler will always generate a conformance to to `BitwiseCopyable`.
158+
159+
For an imported C struct, if all its fields are `BitwiseCopyable`, the compiler will generate a conformance to `BitwiseCopyable`.
160+
The same is true for an imported C++ struct or class, unless the type is non-trivial[^3].
161+
162+
For an imported C or C++ struct, if any of its fields cannot be represented in Swift, the compiler will not generate a conformance.
163+
This can be overridden, however, by annotating the type `__attribute__((__swift_attr__("_BitwiseCopyable")))`.
164+
165+
[^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.
166+
167+
### Inference for exported types
168+
169+
This does not apply to exported (`public`, `package`, or `@usableFromInline`) types.
170+
In the case of a library built with library evolution, while all the type's fields may be `BitwiseCopyable` at the moment, the compiler can't predict that they will always be.
171+
If this is the developer's intent, they can explicitly conform the type.
172+
To avoid having semantics that vary based on library evolution, the same applies to all exported (`public`, `package`, or `@usableFromInline`) types.
173+
174+
For `@frozen` types, however, `BitwiseCopyable` conformance will be inferred.
175+
That's allowed, even in the case of a library built with library evolution, because the compiler can see that the type's fields are all `BitwiseCopyable` and knows that they will remain that way.
176+
177+
For example, the compiler will infer a conformance of the following struct
178+
```swift
179+
@frozen
180+
public struct Coordinate3 {
181+
var x: Int
182+
var y: Int
183+
}
184+
```
185+
to `BitwiseCopyable`.
186+
187+
### Suppressing inferred conformance
188+
189+
To suppress the inference of `BitwiseCopyable`, a conformance can explicitly be made unavailable:
190+
191+
```
192+
@available(*, unavailable)
193+
extension Coordinate4 : BitwiseCopyable {}
194+
```
195+
196+
### Standard library API improvements
197+
198+
The standard library includes a load method on both `UnsafeRawPointer` and `UnsafeMutableRawPointer`
199+
200+
```
201+
@inlinable
202+
@_alwaysEmitIntoClient
203+
public func loadUnaligned<T>(
204+
fromByteOffset offset: Int = 0,
205+
as type: T.Type
206+
) -> T
207+
```
208+
209+
and a corresponding write method on `UnsafeMutableRawPointer`
210+
211+
```
212+
@inlinable
213+
@_alwaysEmitIntoClient
214+
public func storeBytes<T>(
215+
of value: T, toByteOffset offset: Int = 0, as type: T.Type
216+
)
217+
```
218+
219+
that must be called with a trivial `T`.
220+
221+
We propose adding overloads of these methods to constrain the value to `BitwiseCopyable`:
222+
223+
```
224+
// on both UnsafeRawPointer and UnsafeMutableRawPointer
225+
@inlinable
226+
@_alwaysEmitIntoClient
227+
public func loadUnaligned<T : BitwiseCopyable>(
228+
fromByteOffset offset: Int = 0,
229+
as type: T.Type
230+
) -> T
231+
232+
// on UnsafeMutableRawPointer
233+
@inlinable
234+
@_alwaysEmitIntoClient
235+
public func storeBytes<T : BitwiseCopyable>(
236+
of value: T, toByteOffset offset: Int = 0, as type: T.Type
237+
)
238+
```
239+
240+
This allows for optimal code generation because `memcpy` instead of value witnesses can be used.
241+
242+
## Effect on ABI stability
243+
244+
The addition of the `BitwiseCopyable` constraint to either a type or a protocol in a library will not cause an ABI break for users.
245+
246+
## Source compatibility
247+
248+
This addition of a new protocol will not impact existing source code that does not use it.
249+
250+
Removing the `BitwiseCopyable` marker from a type is source-breaking.
251+
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`.
252+
253+
## Effect on API resilience
254+
255+
Adding a `BitwiseCopyable` constraint on a generic type will not cause an ABI break.
256+
As with any protocol, the additional constraint can cause a source break for users.
257+
258+
## Future Directions
259+
260+
### Automatic derivation of conditional conformances
261+
262+
The wrapper type mentioned above
263+
```swift
264+
struct RegularBox<Value> {
265+
var first: Value
266+
}
267+
```
268+
cannot conform to `BitwiseCopyable` unconditionally.
269+
It can, however, so long as `Value` is `BitwiseCopyable`.
270+
271+
With this proposal, such a conditional conformance can be added manually:
272+
273+
```swift
274+
extension Box : BitwiseCopyable where Value : BitwiseCopyable {}
275+
```
276+
277+
In the future we may in some cases be able to derive it automatically.
278+
279+
### MemoryLayout<T>.isBitwiseCopyable
280+
281+
In certain circumstances, it would be useful to be able to dynamically determine whether a type conforms to `BitwiseCopyable`.
282+
In order to allow that, a new field could be added to `MemoryLayout`.
283+
284+
### BitwiseMovable
285+
286+
Most Swift types have the property that their representation can be relocated in memory with direct memory operations.
287+
This could be represented with a `BitwiseMovable` protocol that would be handled similarly to `BitwiseCopyable`.
288+
289+
### BitwiseCopyable as a composition
290+
291+
Some discussion in the pitch thread discussed how `BitwiseCopyable` could be defined as the composition of several protocols.
292+
For example,
293+
```swift
294+
typealias BitwiseCopyable = Bitwise & Copyable & DefaultDeinit
295+
```
296+
Such a definition remains possible after this proposal.
297+
298+
Because `BitwiseCopyable` is a marker protocol, its ABI is rather limited.
299+
Specifically, it only affects name mangling.
300+
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.
301+
302+
## Alternatives considered
303+
304+
### Alternate Spellings
305+
306+
**Trivial** is widely used within the compiler and Swift evolution discussions to refer to the property of bitwise copyability. `BitwiseCopyable`, on the other hand, is more self-documenting.
307+
308+
## Acknowledgments
309+
310+
This proposal has benefitted from discussions with John McCall, Joe Groff, Andrew Trick, Michael Gottesman, and Arnold Schwaigofer.
311+
312+
## Appendix: Standard library conformers<a name="all-stdlib-conformers"/>
313+
314+
The following protocols in the standard library will gain the `BitwiseCopyable` constraint:
315+
316+
- `_Pointer`
317+
- `SIMDStorage`, `SIMDScalar`, `SIMD`
318+
319+
320+
The following types in the standard library will gain the `BitwiseCopyable` constraint:
321+
322+
- `Optional<T>` when `T` is `BitwiseCopyable`
323+
- The fixed-precision integer types:
324+
- `Bool`
325+
- `Int8`, `Int16`, `Int32`, `Int64`, `Int`
326+
- `UInt8`, `UInt16`, `UInt32`, `UInt64`, `UInt`
327+
- `StaticBigInt`
328+
- `UInt8.Words`, `UInt16.Words`, `UInt32.Words`, `UInt64.Words`, `UInt.Words`
329+
- `Int8.Words`, `Int16.Words`, `Int32.Words`, `Int64.Words`, `Int.Words`
330+
- The fixed-precision floating-point types:
331+
- `Float`, `Double`, `Float16`, `Float80`
332+
- `FloatingPointSign`, `FloatingPointClassification`
333+
- The family of `SIMDx<Scalar>` types
334+
- The family of unmanaged pointer types:
335+
- `OpaquePointer`
336+
- `UnsafeRawPointer`, `UnsafeMutableRawPointer`
337+
- `UnsafePointer`, `UnsafeMutablePointer`, `AutoreleasingUnsafeMutablePointer`
338+
- `UnsafeBufferPointer`, `UnsafeMutableBufferPointer`
339+
- `UnsafeRawBufferPointer`, `UnsafeMutableRawBufferPointer`
340+
- `Unmanaged`
341+
- `CVaListPointer`
342+
- Some types related to collections
343+
- `EmptyCollection`
344+
- `UnsafeBufferPointer.Iterator`, `UnsafeRawBufferPointer.Iterator`, `EmptyCollection.Iterator`
345+
- `String.Index`, `CollectionDifference.Index`
346+
- Some types related to unicode
347+
- `Unicode.ASCII`, `Unicode.UTF8`, `Unicode.UTF16`, `Unicode.UTF32`, `Unicode.Scalar`
348+
- `Unicode.ASCII.Parser`, `Unicode.UTF8.ForwardParser`, `Unicode.UTF8.ReverseParser`, `Unicode.UTF16.ForwardParser`, `Unicode.UTF16.ReverseParser`, `Unicode.UTF32.Parser`
349+
- `Unicode.Scalar.UTF8View`, `Unicode.Scalar.UTF16View`
350+
- `UnicodeDecodingResult`
351+
- Some fieldless types
352+
- `Never`, `SystemRandomNumberGenerator`
353+
- `StaticString`
354+
- `Hasher`
355+
- `ObjectIdentifier`
356+
- `Duration`

0 commit comments

Comments
 (0)