Skip to content

Commit 958481b

Browse files
authored
Add UnsafeHeterogeneousBuffer (#178)
1 parent 03b8a6a commit 958481b

File tree

3 files changed

+314
-1
lines changed

3 files changed

+314
-1
lines changed

Sources/OpenSwiftUICore/Data/DynamicProperty/DynamicPropertyBuffer.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//
55
// Audited for iOS 15.5
66
// Status: Complete
7-
// ID: 68550FF604D39F05971FE35A26EE75B0
7+
// ID: 68550FF604D39F05971FE35A26EE75B0 (SwiftUI)
8+
// ID: F3A89CF4357225EF49A7DD673FDFEE02 (SwiftUICore)
89

910
import OpenGraphShims
1011

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
//
2+
// UnsafeHeterogeneousBuffer.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for iOS 18.0
6+
// Status: Complete
7+
// ID: 568350FE259575B5E1AAA52AD722AAAC (SwiftUICore)
8+
9+
package struct UnsafeHeterogeneousBuffer: Collection {
10+
var buf: UnsafeMutableRawPointer!
11+
var available: Int32
12+
var _count: Int32
13+
14+
package typealias VTable = _UnsafeHeterogeneousBuffer_VTable
15+
package typealias Element = _UnsafeHeterogeneousBuffer_Element
16+
17+
package struct Index: Equatable, Comparable {
18+
var index: Int32
19+
var offset: Int32
20+
21+
package static func < (lhs: Index, rhs: Index) -> Bool {
22+
lhs.index < rhs.index
23+
}
24+
25+
package static func == (a: Index, b: Index) -> Bool {
26+
a.index == b.index && a.offset == b.offset
27+
}
28+
}
29+
30+
package struct Item {
31+
let vtable: _UnsafeHeterogeneousBuffer_VTable.Type
32+
let size: Int32
33+
var flags: UInt32
34+
}
35+
36+
package var count: Int { Int(_count) }
37+
package var isEmpty: Bool { _count == 0 }
38+
39+
package var startIndex: Index {
40+
Index(index: 0, offset: 0)
41+
}
42+
package var endIndex: Index {
43+
Index(index: _count, offset: 0)
44+
}
45+
46+
package init() {
47+
buf = nil
48+
available = 0
49+
_count = 0
50+
}
51+
52+
private mutating func allocate(_ bytes: Int) -> UnsafeMutableRawPointer {
53+
var count = _count
54+
var offset = 0
55+
var size = 0
56+
while count != 0 {
57+
let itemSize = buf
58+
.advanced(by: offset)
59+
.assumingMemoryBound(to: Item.self)
60+
.pointee
61+
.size
62+
offset &+= Int(itemSize)
63+
count &-= 1
64+
offset = count == 0 ? 0 : offset
65+
size &+= Int(itemSize)
66+
}
67+
// Grow buffer if needed
68+
if Int(available) < bytes {
69+
growBuffer(by: bytes, capacity: size + Int(available))
70+
}
71+
let ptr = buf.advanced(by: size)
72+
available = available - Int32(bytes)
73+
return ptr
74+
}
75+
76+
private mutating func growBuffer(by size: Int, capacity: Int) {
77+
let expectedSize = size + capacity
78+
var allocSize = Swift.max(capacity &* 2, 64)
79+
while allocSize < expectedSize {
80+
allocSize &*= 2
81+
}
82+
let allocatedBuffer = UnsafeMutableRawPointer.allocate(
83+
byteCount: allocSize,
84+
alignment: .zero
85+
)
86+
if let buf {
87+
var count = _count
88+
if count != 0 {
89+
var itemSize: Int32 = 0
90+
var oldBuffer = buf
91+
var newBuffer = allocatedBuffer
92+
repeat {
93+
count &-= 1
94+
let newItemPointer = newBuffer.assumingMemoryBound(to: Item.self)
95+
let oldItemPointer = oldBuffer.assumingMemoryBound(to: Item.self)
96+
97+
if count == 0 {
98+
itemSize = 0
99+
} else {
100+
itemSize &+= oldItemPointer.pointee.size
101+
}
102+
newItemPointer.initialize(to: oldItemPointer.pointee)
103+
oldItemPointer.pointee.vtable.moveInitialize(
104+
elt: .init(item: newItemPointer),
105+
from: .init(item: oldItemPointer)
106+
)
107+
let size = Int(oldItemPointer.pointee.size)
108+
oldBuffer += size
109+
newBuffer += size
110+
} while count != 0 || itemSize != 0
111+
112+
}
113+
buf.deallocate()
114+
}
115+
buf = allocatedBuffer
116+
available += Int32(allocSize - capacity)
117+
}
118+
119+
package func destroy() {
120+
defer { buf?.deallocate() }
121+
guard _count != 0 else {
122+
return
123+
}
124+
var count = _count
125+
var offset = 0
126+
while count != 0 {
127+
let itemPointer = buf
128+
.advanced(by: offset)
129+
.assumingMemoryBound(to: Item.self)
130+
itemPointer.pointee.vtable.deinitialize(elt: .init(item: itemPointer))
131+
offset &+= Int(itemPointer.pointee.size)
132+
count &-= 1
133+
}
134+
}
135+
136+
package func formIndex(after index: inout Index) {
137+
index = self.index(after: index)
138+
}
139+
140+
package func index(after index: Index) -> Index {
141+
let item = self[index].item.pointee
142+
let newIndex = index.index &+ 1
143+
if newIndex == _count {
144+
return Index(index: newIndex, offset: 0)
145+
} else {
146+
let newOffset = index.offset &+ item.size
147+
return Index(index: newIndex, offset: newOffset)
148+
}
149+
}
150+
151+
package subscript(index: Index) -> Element {
152+
.init(item: buf
153+
.advanced(by: Int(index.offset))
154+
.assumingMemoryBound(to: Item.self)
155+
)
156+
}
157+
158+
@discardableResult
159+
package mutating func append<T>(_ value: T, vtable: VTable.Type) -> Index {
160+
let bytes = MemoryLayout<T>.size + MemoryLayout<UnsafeHeterogeneousBuffer.Item>.size
161+
let pointer = allocate(bytes)
162+
let element = _UnsafeHeterogeneousBuffer_Element(item: pointer.assumingMemoryBound(to: Item.self))
163+
element.item.initialize(to: Item(vtable: vtable, size: Int32(bytes), flags: 0))
164+
element.body(as: T.self).initialize(to: value)
165+
let index = Index(index: _count, offset: Int32(pointer - buf))
166+
_count += 1
167+
return index
168+
}
169+
}
170+
171+
@_spi(ForOpenSwiftUIOnly)
172+
public struct _UnsafeHeterogeneousBuffer_Element {
173+
var item: UnsafeMutablePointer<UnsafeHeterogeneousBuffer.Item>
174+
175+
package func hasType<T>(_ type: T.Type) -> Bool {
176+
item.pointee.vtable.hasType(type)
177+
}
178+
179+
package func vtable<T>(as type: T.Type) -> T.Type where T: _UnsafeHeterogeneousBuffer_VTable {
180+
address.assumingMemoryBound(to: Swift.type(of: type)).pointee
181+
}
182+
183+
package func body<T>(as type: T.Type) -> UnsafeMutablePointer<T> {
184+
UnsafeMutableRawPointer(item.advanced(by: 1)).assumingMemoryBound(to: type)
185+
}
186+
187+
package var flags: UInt32 {
188+
get { item.pointee.flags }
189+
nonmutating set { item.pointee.flags = newValue }
190+
}
191+
192+
package var address: UnsafeRawPointer {
193+
UnsafeRawPointer(item)
194+
}
195+
}
196+
197+
@_spi(ForOpenSwiftUIOnly)
198+
@available(*, unavailable)
199+
extension _UnsafeHeterogeneousBuffer_Element: Sendable {}
200+
201+
@_spi(ForOpenSwiftUIOnly)
202+
open class _UnsafeHeterogeneousBuffer_VTable {
203+
open class func hasType<T>(_ type: T.Type) -> Bool {
204+
false
205+
}
206+
207+
open class func moveInitialize(elt: _UnsafeHeterogeneousBuffer_Element, from: _UnsafeHeterogeneousBuffer_Element) {
208+
preconditionFailure("")
209+
}
210+
211+
open class func deinitialize(elt: _UnsafeHeterogeneousBuffer_Element) {
212+
preconditionFailure("")
213+
}
214+
}
215+
216+
@_spi(ForOpenSwiftUIOnly)
217+
@available(*, unavailable)
218+
extension _UnsafeHeterogeneousBuffer_VTable: Sendable {}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//
2+
// UnsafeHeterogeneousBufferTests.swift
3+
// OpenSwiftUICoreTests
4+
5+
@_spi(ForOpenSwiftUIOnly) import OpenSwiftUICore
6+
@testable import OpenSwiftUICore
7+
import Testing
8+
9+
struct UnsafeHeterogeneousBufferTests {
10+
private final class VTable<Value>: _UnsafeHeterogeneousBuffer_VTable {
11+
override class func hasType<T>(_ type: T.Type) -> Bool {
12+
Value.self == T.self
13+
}
14+
15+
override class func moveInitialize(elt: _UnsafeHeterogeneousBuffer_Element, from: _UnsafeHeterogeneousBuffer_Element) {
16+
let dest = elt.body(as: Value.self)
17+
let source = from.body(as: Value.self)
18+
dest.initialize(to: source.move())
19+
}
20+
21+
override class func deinitialize(elt: _UnsafeHeterogeneousBuffer_Element) {
22+
elt.body(as: Value.self).deinitialize(count: 1)
23+
}
24+
}
25+
26+
@Test
27+
func structBuffer() {
28+
var buffer = UnsafeHeterogeneousBuffer()
29+
defer { buffer.destroy() }
30+
#expect(buffer.isEmpty == true)
31+
32+
do {
33+
let index = buffer.append(UInt32(1), vtable: VTable<Int32>.self)
34+
#expect(buffer.isEmpty == false)
35+
#expect(index == buffer.index(atOffset: 0))
36+
#expect(index.index == 0)
37+
#expect(index.offset == 0)
38+
#expect(buffer.available == 44)
39+
#expect(buffer.count == 1)
40+
let element = buffer[index]
41+
#expect(element.body(as: UInt32.self).pointee == 1)
42+
}
43+
44+
do {
45+
let index = buffer.append(Int(-1), vtable: VTable<Int>.self)
46+
#expect(buffer.isEmpty == false)
47+
#expect(index == buffer.index(atOffset: 1))
48+
#expect(index.index == 1)
49+
#expect(index.offset == 16 + 4)
50+
#expect(buffer.available == 20)
51+
#expect(buffer.count == 2)
52+
let element = buffer[index]
53+
#expect(element.body(as: Int.self).pointee == -1)
54+
}
55+
56+
do {
57+
let index = buffer.append(Double.infinity, vtable: VTable<Double>.self)
58+
#expect(buffer.isEmpty == false)
59+
#expect(index == buffer.index(atOffset: 2))
60+
#expect(index.index == 2)
61+
#expect(index.offset == 16 + 4 + 16 + 8)
62+
#expect(buffer.available == 60)
63+
#expect(buffer.count == 3)
64+
let element = buffer[index]
65+
#expect(element.body(as: Double.self).pointee == Double.infinity)
66+
}
67+
}
68+
69+
@Test
70+
func classBuffer() async throws {
71+
final class DeinitBox {
72+
let deinitBlock: () -> Void
73+
74+
init(deinitBlock: @escaping () -> Void) {
75+
self.deinitBlock = deinitBlock
76+
}
77+
78+
deinit {
79+
deinitBlock()
80+
}
81+
}
82+
83+
84+
await confirmation { confirm in
85+
var buffer = UnsafeHeterogeneousBuffer()
86+
defer { buffer.destroy() }
87+
#expect(buffer.isEmpty == true)
88+
let box = DeinitBox { confirm() }
89+
let index = buffer.append(box, vtable: VTable<DeinitBox>.self)
90+
let element = buffer[index]
91+
#expect(element.body(as: DeinitBox.self).pointee === box)
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)