Skip to content

Commit 66ef259

Browse files
committed
Add memory-buffer access support
1 parent e513972 commit 66ef259

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

Sources/EmbeddedIntegerCollection/EmbeddedIntegerCollection.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,49 @@ extension EmbeddedIntegerCollection: RandomAccessCollection, MutableCollection {
227227
public func distance(from start: Index, to end: Index) -> Int {
228228
return indices.distance(from: start, to: end)
229229
}
230+
231+
public func withContiguousStorageIfAvailable<R>(
232+
_ body: (UnsafeBufferPointer<Element>) throws -> R
233+
) rethrows -> R? {
234+
var copy = self
235+
guard
236+
let result = try copy.withContiguousMutableStorageIfAvailable({ buffer in
237+
try body(.init(buffer))
238+
})
239+
else { return nil }
240+
241+
assert(copy.word == word) // Check against accidental mutation
242+
return result
243+
}
244+
public mutating func withContiguousMutableStorageIfAvailable<R>(
245+
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
246+
) rethrows -> R? {
247+
guard Element.self is UInt8.Type else { return nil }
248+
guard word is _ExpressibleByBuiltinIntegerLiteral else { return nil }
249+
250+
var storage =
251+
switch initialBitRange {
252+
case .mostSignificantFirst:
253+
word.bigEndian
254+
case .leastSignificantFirst:
255+
word.littleEndian
256+
}
257+
defer {
258+
word =
259+
switch initialBitRange {
260+
case .mostSignificantFirst:
261+
Wrapped(bigEndian: storage)
262+
case .leastSignificantFirst:
263+
Wrapped(littleEndian: storage)
264+
}
265+
}
266+
return try withUnsafeMutableBytes(of: &storage) { rawBuffer in
267+
return try rawBuffer.withMemoryRebound(to: Element.self) { buffer in
268+
var bufferCopy = buffer
269+
return try body(&bufferCopy)
270+
}
271+
}
272+
}
230273
}
231274

232275
/// The `Indices` type for all `EmbeddedIntegerCollection` instantiations.

Tests/EmbeddedIntegerCollectionTests/EmbeddedIntegerCollectionTests.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,86 @@ func normalPrint(_ input: (base: UInt32, isBigEndian: Bool), expected: String)
312312
)
313313
#expect(String(describing: collection) == expected)
314314
}
315+
316+
@Test("Mutate contiguous storage")
317+
func mutateContiguousStorage() async throws {
318+
// No usage with elements aren't raw octets.
319+
var nonOctets = EmbeddedIntegerCollection(
320+
embedding: UInt16.self, within: 0x1234_5678_9ABC_DEF0 as UInt64,
321+
iteratingFrom: .mostSignificantFirst
322+
)
323+
let nonOctetCount = nonOctets.withContiguousMutableStorageIfAvailable(
324+
flipAndCount(_:)
325+
)
326+
#expect(nonOctetCount == nil)
327+
328+
// Octet elements
329+
var bigOctets = EmbeddedIntegerCollection(
330+
embedding: UInt8.self, within: 0x0123_4567 as UInt32,
331+
iteratingFrom: .mostSignificantFirst
332+
)
333+
#expect(bigOctets.word == 0x0123_4567)
334+
#expect(bigOctets.elementsEqual([0x01, 0x23, 0x45, 0x67]))
335+
336+
let bigOctetCount = bigOctets.withContiguousMutableStorageIfAvailable(
337+
flipAndCount(_:)
338+
)
339+
#expect(bigOctetCount == 4)
340+
#expect(bigOctets.word == 0xFEDC_BA98)
341+
#expect(bigOctets.elementsEqual([0xFE, 0xDC, 0xBA, 0x98]))
342+
343+
var littleOctets = EmbeddedIntegerCollection(
344+
embedding: UInt8.self, within: 0x0123_4567 as UInt32,
345+
iteratingFrom: .leastSignificantFirst
346+
)
347+
#expect(littleOctets.word == 0x0123_4567)
348+
#expect(littleOctets.elementsEqual([0x67, 0x45, 0x23, 0x01]))
349+
350+
let littleOctetCount = littleOctets.withContiguousMutableStorageIfAvailable(
351+
flipAndCount(_:)
352+
)
353+
#expect(littleOctetCount == 4)
354+
#expect(littleOctets.word == 0xFEDC_BA98)
355+
#expect(littleOctets.elementsEqual([0x98, 0xBA, 0xDC, 0xFE]))
356+
}
357+
358+
/// Return the given collection's length after mutating each element.
359+
private func flipAndCount<T: MutableCollection>(_ collection: inout T) -> Int
360+
where T.Element: BinaryInteger {
361+
for i in collection.indices {
362+
collection[i] = ~collection[i]
363+
}
364+
return collection.count
365+
}
366+
367+
@Test(
368+
"Inspect contiguous storage",
369+
arguments: [0, 0x0123_4567, 0x89AB_CDEF], [false, true]
370+
)
371+
func inspectContiguousStorage(wrapped: UInt32, isBigEndian: Bool) async throws {
372+
let collection = EmbeddedIntegerCollection(
373+
embedding: UInt8.self,
374+
within: wrapped,
375+
iteratingFrom: isBigEndian ? .mostSignificantFirst : .leastSignificantFirst
376+
)
377+
let bitsCount = try #require(
378+
collection.withContiguousStorageIfAvailable {
379+
return $0.map(\.nonzeroBitCount).reduce(0, +)
380+
}
381+
)
382+
#expect(bitsCount == collection.word.nonzeroBitCount)
383+
}
384+
385+
@Test("Inspecting non-octet contiguous storage")
386+
func nonOctetInspectContiguousStorage() async throws {
387+
let collection = EmbeddedIntegerCollection(
388+
embedding: UInt16.self,
389+
within: 0x0123_4567 as UInt32,
390+
iteratingFrom: .leastSignificantFirst
391+
)
392+
#expect(
393+
collection.withContiguousStorageIfAvailable {
394+
return $0.map(\.nonzeroBitCount).reduce(0, +)
395+
} == nil
396+
)
397+
}

0 commit comments

Comments
 (0)