Joe_Groff
(Joe Groff)
September 3, 2024, 3:35pm
122
jrose:
Seconding this, itās been talked through before both here and in Rust (which is currently C-like, but has considered what it would take to go Swift-like with separate size and stride). As far as I know, thereās actually nothing that requires the size of [T * N]
to be MemoryLayout<T>.stride * N
, rather than MemoryLayout<T>.stride * (N-1) + MemoryLayout<T>.size
(yes, I know thatās wrong for 0, donāt worry about it). Anything that reads or writes the entire value in a typed way will use the correct size, whatever we decide that is; anything that only goes element-by-element will never read any of the tail padding at all. The only problem comes if someone reads or writes the āfullā N * stride when only the unpadded length was allocated. And that can certainly happen! But it implies someone is manually calculating sizes that they hopefully donāt need to anymore. (Unfortunately, if theyāre doing it at all, itās already in unsafe-pointer land, so thereās not an obvious way for the compiler to catch it.)
There is a performance cost, just as there is with any size vs stride difference: care must be taken to preserve the elements in the tail padding, which may mean more instructions to avoid touching extra memory that doesnāt belong to you. But that isnāt really new in Swift, though it should probably go into a performance guide for fixed-sized arrays.
In Swift's case, there's already a decent amount of code out there using UnsafeBufferPointer
for bulk data operations, and the underlying array value witness operations in the runtime that they use assume that an "array" is stride-padded. We want new contiguous collection types to be compatible with existing practice, and Span
to be a mostly drop-in replacement for unsafe types, so IMO we should stride-pad any type that's used for array storage.
2 Likes