Skip to content

Commit a6a792c

Browse files
committed
Add a MemoryLayout<T>.offset(of:) method for getting the offset of inline storage.
If a key path refers to inline storage of the root type, this produces the offset in bytes between a pointer to the root and a pointer to the projected storage. Otherwise, returns nil.
1 parent 08c7391 commit a6a792c

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

stdlib/public/core/KeyPath.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,27 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
159159
let base = UnsafeRawPointer(Builtin.projectTailElems(self, Int32.self))
160160
return try f(KeyPathBuffer(base: base))
161161
}
162+
163+
@_inlineable // FIXME(sil-serialize-all)
164+
@_versioned // FIXME(sil-serialize-all)
165+
internal var _storedInlineOffset: Int? {
166+
return withBuffer {
167+
var buffer = $0
168+
var offset = 0
169+
while true {
170+
let (rawComponent, optNextType) = buffer.next()
171+
switch rawComponent.header.kind {
172+
case .struct:
173+
offset += rawComponent._structOrClassOffset
174+
175+
case .class, .computed, .optionalChain, .optionalForce, .optionalWrap:
176+
return .none
177+
}
178+
179+
if optNextType == nil { return .some(offset) }
180+
}
181+
}
182+
}
162183
}
163184

164185
/// A partially type-erased key path, from a concrete root type to any

stdlib/public/core/MemoryLayout.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,41 @@ extension MemoryLayout {
162162
public static func alignment(ofValue value: T) -> Int {
163163
return MemoryLayout.alignment
164164
}
165+
166+
/// Returns the offset of an inline stored property of `T` within the
167+
/// in-memory representation of `T`.
168+
///
169+
/// If the given `key` refers to inline storage within the
170+
/// in-memory representation of `T`, and the storage is directly
171+
/// addressable (meaning that accessing it does not need to trigger any
172+
/// `didSet` or `willSet` accessors, perform any representation changes
173+
/// such as bridging or closure reabstraction, or mask the value out of
174+
/// overlapping storage as for packed bitfields), then the return value
175+
/// is a distance in bytes that can be added to a pointer of type `T` to
176+
/// get a pointer to the storage accessed by `key`. If the return value is
177+
/// non-nil, then these formulations are equivalent:
178+
///
179+
/// var root: T, value: U
180+
/// var key: WritableKeyPath<T, U>
181+
/// // Mutation through the key path...
182+
/// root[keyPath: \.key] = value
183+
/// // ...is exactly equivalent to mutation through the offset pointer...
184+
/// withUnsafePointer(to: &root) {
185+
/// (UnsafeMutableRawPointer($0) + MemoryLayout<T>.offset(of: \.key))
186+
/// // ...which can be assumed to be bound to the target type
187+
/// .assumingMemoryBound(to: U.self).pointee = value
188+
/// }
189+
///
190+
/// - Parameter key: A key path referring to storage that can be accessed
191+
/// through a value of type `T`.
192+
/// - Returns: The offset in bytes from a pointer to a value of type `T`
193+
/// to a pointer to the storage referenced by `key`, or `nil` if no
194+
/// such offset is available for the storage referenced by `key`, such as
195+
/// because `key` is computed, has observers, requires reabstraction, or
196+
/// overlaps storage with other properties.
197+
@_inlineable // FIXME(sil-serialize-all)
198+
@_transparent
199+
public static func offset(of key: PartialKeyPath<T>) -> Int? {
200+
return key._storedInlineOffset
201+
}
165202
}

test/stdlib/KeyPath.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,32 @@ keyPath.test("subscripts") {
666666
expectEqual(base[keyPath: ints_be], (17 + 38).bigEndian)
667667
}
668668

669+
struct NonOffsetableProperties {
670+
// observers
671+
var x: Int { didSet {} }
672+
// reabstracted
673+
var y: () -> ()
674+
// computed
675+
var z: Int { return 0 }
676+
}
677+
678+
keyPath.test("offsets") {
679+
let SLayout = MemoryLayout<S<Int>>.self
680+
expectNotNil(SLayout.offset(of: \S<Int>.x))
681+
expectNotNil(SLayout.offset(of: \S<Int>.y))
682+
expectNotNil(SLayout.offset(of: \S<Int>.z))
683+
expectNotNil(SLayout.offset(of: \S<Int>.p))
684+
expectNotNil(SLayout.offset(of: \S<Int>.p.x))
685+
expectNotNil(SLayout.offset(of: \S<Int>.p.y))
686+
expectNotNil(SLayout.offset(of: \S<Int>.c))
687+
expectNil(SLayout.offset(of: \S<Int>.c.x))
688+
689+
let NOPLayout = MemoryLayout<NonOffsetableProperties>.self
690+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.x))
691+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.y))
692+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.z))
693+
}
694+
669695
// SR-6096
670696

671697
protocol Protocol6096 {}

0 commit comments

Comments
 (0)