Skip to content

Commit

Permalink
Add UnsafeHeterogeneousBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyle-Ye committed Dec 19, 2024
1 parent 03b8a6a commit 458ab5a
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
//
// Audited for iOS 15.5
// Status: Complete
// ID: 68550FF604D39F05971FE35A26EE75B0
// ID: 68550FF604D39F05971FE35A26EE75B0 (SwiftUI)
// ID: F3A89CF4357225EF49A7DD673FDFEE02 (SwiftUICore)

import OpenGraphShims

Expand Down
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 {}
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)
}
}
}

0 comments on commit 458ab5a

Please sign in to comment.