Right, while this proposal won't deliver a complete formal spec, we can confidently expect any future spec to cover some obvious cases where the implemenation already assumes layout compatibility and would have no reason to change.
Two types are mutually layout-compatible if their in-memory representation has the same size and alignment.
For the purpose of this API, it may be more clear to talk about "layout equivalence" rather than "[mutual] layout compatibility". When we rebind memory, we require one type's stride to be a whole multiple of the other. We can't rebind part of a value. So we can simply state that the contiguous sequence of the smaller type must be layout equivalent with the members of the larger type.
All "mutually layout-compatible" rules here could just be called "layout equivalent".
Some examples of mutually layout-compatible types follow:
- identical types
- a typealias with the type for which it is an alias
- floating point types of the same size
- class types and
AnyObject existentials
- pointer types, such as
UnsafePointer and OpaquePointer
- frozen structs with a single stored property with their stored property type
- frozen enums with a single case with the type of the associated value of their single case
Aggregate types (tuples, array storage, and structs) are mutually layout-compatible if they have the same number of mutually layout-compatible elements. This means that contiguous array storage and homogeneous tuples are mutually layout-compatible as long as they have the same number of elements (and their elements are themselves mutually layout-compatible.)
Some types can be layout compatible but not mutually:
- aggregates are layout compatible with larger aggregates of the same kind when their common elements are mutually layout compatible.
I don't think this rule is useful for rebinding memory in its current form. We would need more precise description of structure layout to make it useful. I'm not comfortable making those kinds of guarantees, especially for nonfrozen structs, without more review.
- an enum associated values's type is layout compatible its enum type if the enum has only one case with associated value (and zero or more no-payload cases).
Similarly, this rule is not useful for rebinding non-frozen enums, because the enum may be larger than its payload.
For non-homogeneous tuples and structs whose stored properties are of multiple types, layout compatibility requires the constitutive elements to be mutually layout compatible, and to be ordered in memory with the same stride and alignment.
This wording needs work. It's trying to say that 'S3' and 'S2_1' below are layout equivalent:
struct S1 {
var i: Int32
}
struct S2 {
var i: Int32
var j: Int32
}
struct S3 {
var i: Int32
var j: Int32
var k: Int32
}
struct S2_1 {
var s2: S2
var i: Int32
}
I personally think that's a sensible rule, but we should avoid avoid documenting it until it's been reviewed.
Triviality is critical. As Guillaume explains below, the language implementation needs to know how to copy or deinitialize the in-memory value. For nontrivial values, object references need to be in the same positions, and should at least have a superclass/subclass relationship. So, rebinding memory is not meant to be a mechanism to extract the bitpattern of a reference or vice-versa.
For trivial types, the programmer simply needs to guarantee a valid bit pattern for the new type when those bits are evaluated. The language implementation doesn't care.
In the general case, the runtime performs housekeeping tasks when initializing or deinitializing a value,
as well as when updating a value. When a value is updated, reference counts may change, leading to possible deinitialization elsewhere. Initialization and deinitialization mean that type-specific code is executed, and therefore memory layout is not enough for compatibility in the general case. In other words, in the most general case, type identity would still be required.
Types variously referred to as "POD" (plain old data) or "trivial" in the documentation do not trigger actions by the runtime after an initialization, a deinitialization or an update. For such types, layout compatibility is sufficient for correct temporary rebinding.
It looks like this only made it to the doc-comments, but:
/// If `T` and `Pointee` have different alignments, this pointer
/// must be aligned with the larger of the two alignments.
(each version has an appropriate variation on that theme.)
That should be included in the "proposed solution" section. Thanks.
[/quote]
Yep. Typed pointers need to be naturally aligned when they are accessed, period. When you rebind memory, it's up to you to ensure the resulting pointer alignment.
Of course, you can always rebind to a type with lower alignment without worry. Rebinding to a higher alignment requires an alignment check.