Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
}
Loading