-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
314 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
Sources/OpenSwiftUICore/Data/DynamicProperty/UnsafeHeterogeneousBuffer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// | ||
// UnsafeHeterogeneousBuffer.swift | ||
// OpenSwiftUICore | ||
// | ||
// Audited for iOS 18.0 | ||
// Status: Complete | ||
// ID: 568350FE259575B5E1AAA52AD722AAAC (SwiftUICore) | ||
|
||
package struct UnsafeHeterogeneousBuffer: Collection { | ||
var buf: UnsafeMutableRawPointer! | ||
var available: Int32 | ||
var _count: Int32 | ||
|
||
package typealias VTable = _UnsafeHeterogeneousBuffer_VTable | ||
package typealias Element = _UnsafeHeterogeneousBuffer_Element | ||
|
||
package struct Index: Equatable, Comparable { | ||
var index: Int32 | ||
var offset: Int32 | ||
|
||
package static func < (lhs: Index, rhs: Index) -> Bool { | ||
lhs.index < rhs.index | ||
} | ||
|
||
package static func == (a: Index, b: Index) -> Bool { | ||
a.index == b.index && a.offset == b.offset | ||
} | ||
} | ||
|
||
package struct Item { | ||
let vtable: _UnsafeHeterogeneousBuffer_VTable.Type | ||
let size: Int32 | ||
var flags: UInt32 | ||
} | ||
|
||
package var count: Int { Int(_count) } | ||
package var isEmpty: Bool { _count == 0 } | ||
|
||
package var startIndex: Index { | ||
Index(index: 0, offset: 0) | ||
} | ||
package var endIndex: Index { | ||
Index(index: _count, offset: 0) | ||
} | ||
|
||
package init() { | ||
buf = nil | ||
available = 0 | ||
_count = 0 | ||
} | ||
|
||
private mutating func allocate(_ bytes: Int) -> UnsafeMutableRawPointer { | ||
var count = _count | ||
var offset = 0 | ||
var size = 0 | ||
while count != 0 { | ||
let itemSize = buf | ||
.advanced(by: offset) | ||
.assumingMemoryBound(to: Item.self) | ||
.pointee | ||
.size | ||
offset &+= Int(itemSize) | ||
count &-= 1 | ||
offset = count == 0 ? 0 : offset | ||
size &+= Int(itemSize) | ||
} | ||
// Grow buffer if needed | ||
if Int(available) < bytes { | ||
growBuffer(by: bytes, capacity: size + Int(available)) | ||
} | ||
let ptr = buf.advanced(by: size) | ||
available = available - Int32(bytes) | ||
return ptr | ||
} | ||
|
||
private mutating func growBuffer(by size: Int, capacity: Int) { | ||
let expectedSize = size + capacity | ||
var allocSize = Swift.max(capacity &* 2, 64) | ||
while allocSize < expectedSize { | ||
allocSize &*= 2 | ||
} | ||
let allocatedBuffer = UnsafeMutableRawPointer.allocate( | ||
byteCount: allocSize, | ||
alignment: .zero | ||
) | ||
if let buf { | ||
var count = _count | ||
if count != 0 { | ||
var itemSize: Int32 = 0 | ||
var oldBuffer = buf | ||
var newBuffer = allocatedBuffer | ||
repeat { | ||
count &-= 1 | ||
let newItemPointer = newBuffer.assumingMemoryBound(to: Item.self) | ||
let oldItemPointer = oldBuffer.assumingMemoryBound(to: Item.self) | ||
|
||
if count == 0 { | ||
itemSize = 0 | ||
} else { | ||
itemSize &+= oldItemPointer.pointee.size | ||
} | ||
newItemPointer.initialize(to: oldItemPointer.pointee) | ||
oldItemPointer.pointee.vtable.moveInitialize( | ||
elt: .init(item: newItemPointer), | ||
from: .init(item: oldItemPointer) | ||
) | ||
let size = Int(oldItemPointer.pointee.size) | ||
oldBuffer += size | ||
newBuffer += size | ||
} while count != 0 || itemSize != 0 | ||
|
||
} | ||
buf.deallocate() | ||
} | ||
buf = allocatedBuffer | ||
available += Int32(allocSize - capacity) | ||
} | ||
|
||
package func destroy() { | ||
defer { buf?.deallocate() } | ||
guard _count != 0 else { | ||
return | ||
} | ||
var count = _count | ||
var offset = 0 | ||
while count != 0 { | ||
let itemPointer = buf | ||
.advanced(by: offset) | ||
.assumingMemoryBound(to: Item.self) | ||
itemPointer.pointee.vtable.deinitialize(elt: .init(item: itemPointer)) | ||
offset &+= Int(itemPointer.pointee.size) | ||
count &-= 1 | ||
} | ||
} | ||
|
||
package func formIndex(after index: inout Index) { | ||
index = self.index(after: index) | ||
} | ||
|
||
package func index(after index: Index) -> Index { | ||
let item = self[index].item.pointee | ||
let newIndex = index.index &+ 1 | ||
if newIndex == _count { | ||
return Index(index: newIndex, offset: 0) | ||
} else { | ||
let newOffset = index.offset &+ item.size | ||
return Index(index: newIndex, offset: newOffset) | ||
} | ||
} | ||
|
||
package subscript(index: Index) -> Element { | ||
.init(item: buf | ||
.advanced(by: Int(index.offset)) | ||
.assumingMemoryBound(to: Item.self) | ||
) | ||
} | ||
|
||
@discardableResult | ||
package mutating func append<T>(_ value: T, vtable: VTable.Type) -> Index { | ||
let bytes = MemoryLayout<T>.size + MemoryLayout<UnsafeHeterogeneousBuffer.Item>.size | ||
let pointer = allocate(bytes) | ||
let element = _UnsafeHeterogeneousBuffer_Element(item: pointer.assumingMemoryBound(to: Item.self)) | ||
element.item.initialize(to: Item(vtable: vtable, size: Int32(bytes), flags: 0)) | ||
element.body(as: T.self).initialize(to: value) | ||
let index = Index(index: _count, offset: Int32(pointer - buf)) | ||
_count += 1 | ||
return index | ||
} | ||
} | ||
|
||
@_spi(ForOpenSwiftUIOnly) | ||
public struct _UnsafeHeterogeneousBuffer_Element { | ||
var item: UnsafeMutablePointer<UnsafeHeterogeneousBuffer.Item> | ||
|
||
package func hasType<T>(_ type: T.Type) -> Bool { | ||
item.pointee.vtable.hasType(type) | ||
} | ||
|
||
package func vtable<T>(as type: T.Type) -> T.Type where T: _UnsafeHeterogeneousBuffer_VTable { | ||
address.assumingMemoryBound(to: Swift.type(of: type)).pointee | ||
} | ||
|
||
package func body<T>(as type: T.Type) -> UnsafeMutablePointer<T> { | ||
UnsafeMutableRawPointer(item.advanced(by: 1)).assumingMemoryBound(to: type) | ||
} | ||
|
||
package var flags: UInt32 { | ||
get { item.pointee.flags } | ||
nonmutating set { item.pointee.flags = newValue } | ||
} | ||
|
||
package var address: UnsafeRawPointer { | ||
UnsafeRawPointer(item) | ||
} | ||
} | ||
|
||
@_spi(ForOpenSwiftUIOnly) | ||
@available(*, unavailable) | ||
extension _UnsafeHeterogeneousBuffer_Element: Sendable {} | ||
|
||
@_spi(ForOpenSwiftUIOnly) | ||
open class _UnsafeHeterogeneousBuffer_VTable { | ||
open class func hasType<T>(_ type: T.Type) -> Bool { | ||
false | ||
} | ||
|
||
open class func moveInitialize(elt: _UnsafeHeterogeneousBuffer_Element, from: _UnsafeHeterogeneousBuffer_Element) { | ||
preconditionFailure("") | ||
} | ||
|
||
open class func deinitialize(elt: _UnsafeHeterogeneousBuffer_Element) { | ||
preconditionFailure("") | ||
} | ||
} | ||
|
||
@_spi(ForOpenSwiftUIOnly) | ||
@available(*, unavailable) | ||
extension _UnsafeHeterogeneousBuffer_VTable: Sendable {} |
94 changes: 94 additions & 0 deletions
94
Tests/OpenSwiftUICoreTests/Data/DynamicProperty/UnsafeHeterogeneousBufferTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// | ||
// UnsafeHeterogeneousBufferTests.swift | ||
// OpenSwiftUICoreTests | ||
|
||
@_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore | ||
@testable import OpenSwiftUICore | ||
import Testing | ||
|
||
struct UnsafeHeterogeneousBufferTests { | ||
private final class VTable<Value>: _UnsafeHeterogeneousBuffer_VTable { | ||
override class func hasType<T>(_ type: T.Type) -> Bool { | ||
Value.self == T.self | ||
} | ||
|
||
override class func moveInitialize(elt: _UnsafeHeterogeneousBuffer_Element, from: _UnsafeHeterogeneousBuffer_Element) { | ||
let dest = elt.body(as: Value.self) | ||
let source = from.body(as: Value.self) | ||
dest.initialize(to: source.move()) | ||
} | ||
|
||
override class func deinitialize(elt: _UnsafeHeterogeneousBuffer_Element) { | ||
elt.body(as: Value.self).deinitialize(count: 1) | ||
} | ||
} | ||
|
||
@Test | ||
func structBuffer() { | ||
var buffer = UnsafeHeterogeneousBuffer() | ||
defer { buffer.destroy() } | ||
#expect(buffer.isEmpty == true) | ||
|
||
do { | ||
let index = buffer.append(UInt32(1), vtable: VTable<Int32>.self) | ||
#expect(buffer.isEmpty == false) | ||
#expect(index == buffer.index(atOffset: 0)) | ||
#expect(index.index == 0) | ||
#expect(index.offset == 0) | ||
#expect(buffer.available == 44) | ||
#expect(buffer.count == 1) | ||
let element = buffer[index] | ||
#expect(element.body(as: UInt32.self).pointee == 1) | ||
} | ||
|
||
do { | ||
let index = buffer.append(Int(-1), vtable: VTable<Int>.self) | ||
#expect(buffer.isEmpty == false) | ||
#expect(index == buffer.index(atOffset: 1)) | ||
#expect(index.index == 1) | ||
#expect(index.offset == 16 + 4) | ||
#expect(buffer.available == 20) | ||
#expect(buffer.count == 2) | ||
let element = buffer[index] | ||
#expect(element.body(as: Int.self).pointee == -1) | ||
} | ||
|
||
do { | ||
let index = buffer.append(Double.infinity, vtable: VTable<Double>.self) | ||
#expect(buffer.isEmpty == false) | ||
#expect(index == buffer.index(atOffset: 2)) | ||
#expect(index.index == 2) | ||
#expect(index.offset == 16 + 4 + 16 + 8) | ||
#expect(buffer.available == 60) | ||
#expect(buffer.count == 3) | ||
let element = buffer[index] | ||
#expect(element.body(as: Double.self).pointee == Double.infinity) | ||
} | ||
} | ||
|
||
@Test | ||
func classBuffer() async throws { | ||
final class DeinitBox { | ||
let deinitBlock: () -> Void | ||
|
||
init(deinitBlock: @escaping () -> Void) { | ||
self.deinitBlock = deinitBlock | ||
} | ||
|
||
deinit { | ||
deinitBlock() | ||
} | ||
} | ||
|
||
|
||
await confirmation { confirm in | ||
var buffer = UnsafeHeterogeneousBuffer() | ||
defer { buffer.destroy() } | ||
#expect(buffer.isEmpty == true) | ||
let box = DeinitBox { confirm() } | ||
let index = buffer.append(box, vtable: VTable<DeinitBox>.self) | ||
let element = buffer[index] | ||
#expect(element.body(as: DeinitBox.self).pointee === box) | ||
} | ||
} | ||
} |