|
| 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