swift 6.0+ performance tweaks 3x-6x (#9067)
* start/stop measurements
* tweaks
* add 6.0 annotations
* remove some inlines
* address feedback
* address wasi + let
* adopt flex buffers
* flex buffers benchmarks
diff --git a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift
index 4607f58..aa08568 100644
--- a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift
+++ b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift
@@ -97,6 +97,7 @@
for _ in benchmark.scaledIterations {
blackHole(ByteBuffer(assumingMemoryBound: memory, capacity: Int(oneGB)))
}
+ benchmark.stopMeasurement()
}
Benchmark("Clearing 1GB", configuration: singleConfiguration) { benchmark in
@@ -105,6 +106,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.clear())
}
+ benchmark.stopMeasurement()
}
Benchmark("Strings 10") { benchmark in
@@ -113,6 +115,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.create(string: str10))
}
+ benchmark.stopMeasurement()
}
Benchmark("Strings 100") { benchmark in
@@ -121,6 +124,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.create(string: str100))
}
+ benchmark.stopMeasurement()
}
Benchmark("Vector 1 Bytes") { benchmark in
@@ -129,6 +133,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.createVector(bytes: bytes))
}
+ benchmark.stopMeasurement()
}
Benchmark("Vector 1 Ints") { benchmark in
@@ -137,6 +142,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.createVector(ints))
}
+ benchmark.stopMeasurement()
}
Benchmark("Vector 100 Ints") { benchmark in
@@ -145,6 +151,7 @@
for i in benchmark.scaledIterations {
blackHole(fb.createVector(ints))
}
+ benchmark.stopMeasurement()
}
Benchmark("Vector 100 Bytes") { benchmark in
@@ -153,6 +160,7 @@
for i in benchmark.scaledIterations {
blackHole(fb.createVector(bytes))
}
+ benchmark.stopMeasurement()
}
Benchmark("Vector 100 ContiguousBytes") { benchmark in
@@ -161,6 +169,7 @@
for i in benchmark.scaledIterations {
blackHole(fb.createVector(bytes: bytes))
}
+ benchmark.stopMeasurement()
}
Benchmark(
@@ -178,6 +187,7 @@
fb.add(offset: off, at: 8)
blackHole(fb.endTable(at: s))
}
+ benchmark.stopMeasurement()
}
Benchmark(
@@ -190,6 +200,7 @@
let s = fb.startTable(with: 4)
blackHole(fb.endTable(at: s))
}
+ benchmark.stopMeasurement()
}
Benchmark("Struct") { benchmark in
@@ -198,6 +209,7 @@
for _ in benchmark.scaledIterations {
blackHole(fb.create(struct: array.first!))
}
+ benchmark.stopMeasurement()
}
Benchmark("Structs") { benchmark in
@@ -219,6 +231,7 @@
fb.add(offset: vector, at: 4)
let root = Offset(offset: fb.endTable(at: start))
blackHole(fb.finish(offset: root))
+ benchmark.stopMeasurement()
}
Benchmark("Vector of Offsets") { benchmark in
@@ -239,12 +252,15 @@
fb.add(offset: off, at: 2)
blackHole(fb.endTable(at: s))
}
+ benchmark.stopMeasurement()
}
Benchmark("Reading Doubles") { benchmark in
let byteBuffer = ByteBuffer(data: data)
+ benchmark.startMeasurement()
for _ in benchmark.scaledIterations {
blackHole(byteBuffer.read(def: Double.self, position: 0))
}
+ benchmark.stopMeasurement()
}
}
diff --git a/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift
new file mode 100644
index 0000000..22d0d54
--- /dev/null
+++ b/benchmarks/swift/Benchmarks/FlexBuffersBenchmarks/FlexBuffersBenchmarks.swift
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2024 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Benchmark
+import FlexBuffers
+import Foundation
+
+let benchmarks = {
+ let data = {
+ var array = [8888.88, 8888.88]
+ var data = Data()
+ array.withUnsafeBytes { ptr in
+ data.append(contentsOf: ptr)
+ }
+ return data
+ }()
+ let ints: [Int32] = Array(repeating: 42, count: 100)
+ let str10 = (0...9).map { _ -> String in "x" }.joined()
+ let str100 = (0...99).map { _ -> String in "x" }.joined()
+
+ // A representative map: 50 keyed scalars, a keyed string and a keyed vector
+ // of 100 scalars. Used for the realistic decode benchmarks.
+ let mapBuffer: ByteBuffer = {
+ var fbx = FlexBuffersWriter(initialSize: 1 << 16)
+ fbx.map {
+ for i in 0..<50 { $0.add(int: i, key: "i\(i)") }
+ $0.add(string: "hello world", key: "s")
+ $0.vector(key: "v") { v in
+ for x in 0..<100 { v.add(int: x) }
+ }
+ }
+ fbx.finish()
+ return fbx.sizedByteBuffer
+ }()
+
+ let metrics: [BenchmarkMetric] = [
+ .cpuTotal,
+ .wallClock,
+ .mallocCountTotal,
+ .releaseCount,
+ .peakMemoryResident,
+ ]
+ let maxIterations = 1_000_000
+ let maxDuration: Duration = .seconds(3)
+ let megaConfiguration: Benchmark.Configuration = .init(
+ metrics: metrics,
+ warmupIterations: 1,
+ scalingFactor: .mega,
+ maxDuration: maxDuration,
+ maxIterations: maxIterations)
+
+ Benchmark.defaultConfiguration = megaConfiguration
+
+ // Decode (read path)
+
+ // Raw scalar read: isolates `read<T: BitwiseCopyable>` and the `let` blob.
+ Benchmark("Reading Doubles") { benchmark in
+ let byteBuffer = ByteBuffer(data: data)
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ blackHole(byteBuffer.read(def: Double.self, position: 0))
+ }
+ benchmark.stopMeasurement()
+ }
+
+ // Realistic decode: resolve root map and read a keyed scalar.
+ Benchmark("Decode Map Scalar") { benchmark in
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ let map = try! getRoot(buffer: mapBuffer)!.map!
+ blackHole(map["i25"]?.int)
+ }
+ benchmark.stopMeasurement()
+ }
+
+ // Realistic decode: resolve root map and read a keyed string.
+ Benchmark("Decode Map String") { benchmark in
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ let map = try! getRoot(buffer: mapBuffer)!.map!
+ blackHole(map["s"]?.string())
+ }
+ benchmark.stopMeasurement()
+ }
+
+ // Realistic decode: resolve a nested vector and sum its scalars.
+ Benchmark("Decode Vector") { benchmark in
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ let map = try! getRoot(buffer: mapBuffer)!.map!
+ let vector = map["v"]!.vector!
+ var sum: Int64 = 0
+ for i in 0..<vector.count {
+ sum &+= vector[i]?.int ?? 0
+ }
+ blackHole(sum)
+ }
+ benchmark.stopMeasurement()
+ }
+
+ // Encode (write path)
+ // Writers are reused with `reset(keepingCapacity:)` so per-iteration
+ // allocation does not dominate and mask the write-path cost (which is what
+ // the `@exclusivity(unchecked)` storage pointer affects).
+
+ Benchmark("Strings 10") { benchmark in
+ var fbx = FlexBuffersWriter(initialSize: 1 << 20)
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ fbx.add(string: str10)
+ fbx.finish()
+ blackHole(fbx.sizedByteBuffer)
+ fbx.reset(keepingCapacity: true)
+ }
+ benchmark.stopMeasurement()
+ }
+
+ Benchmark("Strings 100") { benchmark in
+ var fbx = FlexBuffersWriter(initialSize: 1 << 20)
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ fbx.add(string: str100)
+ fbx.finish()
+ blackHole(fbx.sizedByteBuffer)
+ fbx.reset(keepingCapacity: true)
+ }
+ benchmark.stopMeasurement()
+ }
+
+ Benchmark("Encoding Vector Of Ints") { benchmark in
+ var fbx = FlexBuffersWriter(initialSize: 1 << 20)
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ fbx.vector {
+ $0.create(vector: ints)
+ }
+ fbx.finish()
+ blackHole(fbx.sizedByteBuffer)
+ fbx.reset(keepingCapacity: true)
+ }
+ benchmark.stopMeasurement()
+ }
+
+ Benchmark("Encoding Map") { benchmark in
+ var fbx = FlexBuffersWriter(initialSize: 1 << 20)
+ benchmark.startMeasurement()
+ for _ in benchmark.scaledIterations {
+ fbx.map {
+ for i in 0..<50 { $0.add(int: i, key: "i\(i)") }
+ $0.add(string: "hello world", key: "s")
+ $0.vector(key: "v") { v in
+ for x in 0..<100 { v.add(int: x) }
+ }
+ }
+ fbx.finish()
+ blackHole(fbx.sizedByteBuffer)
+ fbx.reset(keepingCapacity: true)
+ }
+ benchmark.stopMeasurement()
+ }
+}
diff --git a/benchmarks/swift/Package.swift b/benchmarks/swift/Package.swift
index 1187329..41b536c 100644
--- a/benchmarks/swift/Package.swift
+++ b/benchmarks/swift/Package.swift
@@ -39,4 +39,14 @@
plugins: [
.plugin(name: "BenchmarkPlugin", package: "package-benchmark"),
]),
+ .executableTarget(
+ name: "FlexBuffersBenchmarks",
+ dependencies: [
+ .product(name: "FlexBuffers", package: "flatbuffers"),
+ .product(name: "Benchmark", package: "package-benchmark"),
+ ],
+ path: "Benchmarks/FlexBuffersBenchmarks",
+ plugins: [
+ .plugin(name: "BenchmarkPlugin", package: "package-benchmark"),
+ ]),
])
diff --git a/swift/Sources/Common/Scalar.swift b/swift/Sources/Common/Scalar.swift
index 518580a..5c6a6c8 100644
--- a/swift/Sources/Common/Scalar.swift
+++ b/swift/Sources/Common/Scalar.swift
@@ -28,7 +28,7 @@
/// Protocol that All Scalars should conform to
///
/// Scalar is used to conform all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
-public protocol Scalar: Equatable {
+public protocol Scalar: Equatable, BitwiseCopyable {
associatedtype NumericValue
var convertedEndian: NumericValue { get }
}
diff --git a/swift/Sources/FlatBuffers/ByteBuffer.swift b/swift/Sources/FlatBuffers/ByteBuffer.swift
index f8b59ee..f22ecff 100644
--- a/swift/Sources/FlatBuffers/ByteBuffer.swift
+++ b/swift/Sources/FlatBuffers/ByteBuffer.swift
@@ -27,7 +27,7 @@
@usableFromInline
final class Storage {
@usableFromInline
- enum Blob {
+ @frozen enum Blob: ~Copyable {
#if !os(WASI)
case data(Data)
case bytes(any ContiguousBytes)
@@ -36,6 +36,40 @@
case byteBuffer(_InternalByteBuffer)
case array([UInt8])
case pointer(UnsafeMutableRawPointer)
+
+ init(_ other: borrowing Blob) {
+ switch other {
+ #if !os(WASI)
+ case .data(let data):
+ self = .data(data)
+ case .bytes(let contiguousBytes):
+ self = .bytes(contiguousBytes)
+ #endif
+ case .byteBuffer(let internalByteBuffer):
+ self = .byteBuffer(internalByteBuffer)
+ case .array(let array):
+ self = .array(array)
+ case .pointer(let unsafeMutableRawPointer):
+ self = .pointer(unsafeMutableRawPointer)
+ }
+ }
+
+ var description: String {
+ switch self {
+ #if !os(WASI)
+ case .data(let data):
+ "data: \(data)"
+ case .bytes(let contiguousBytes):
+ "bytes: \(contiguousBytes)"
+ #endif
+ case .byteBuffer(let internalByteBuffer):
+ "byteBuffer: \(internalByteBuffer)"
+ case .array(let array):
+ "array: \(array)"
+ case .pointer(let unsafeMutableRawPointer):
+ "pointer: \(unsafeMutableRawPointer)"
+ }
+ }
}
/// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
@@ -44,7 +78,7 @@
private let capacity: Int
/// Retained blob of data that requires the storage to retain a pointer to.
@usableFromInline
- var retainedBlob: Blob
+ let retainedBlob: Blob
@usableFromInline
init(count: Int) {
@@ -57,9 +91,9 @@
}
@usableFromInline
- init(blob: Blob, capacity count: Int) {
+ init(blob: borrowing Blob, capacity count: Int) {
capacity = count
- retainedBlob = blob
+ retainedBlob = .init(blob)
isOwned = false
}
@@ -150,6 +184,7 @@
@discardableResult
@inline(__always)
+ @inlinable
func readWithUnsafeRawPointer<T>(
position: Int,
_ body: (UnsafeRawPointer) throws -> T) rethrows -> T
@@ -278,7 +313,7 @@
/// - removeBytes: Removes a number of bytes from the current size
@inline(__always)
init(
- blob: Storage.Blob,
+ blob: borrowing Storage.Blob,
count: Int,
removing removeBytes: Int)
{
@@ -318,7 +353,8 @@
/// - def: Type of the object
/// - position: the index of the object in the buffer
@inline(__always)
- public func read<T>(def: T.Type, position: Int) -> T {
+ @inlinable
+ public func read<T: BitwiseCopyable>(def: T.Type, position: Int) -> T {
_storage.readWithUnsafeRawPointer(position: position) {
$0.bindMemory(to: T.self, capacity: 1)
.pointee
@@ -412,7 +448,7 @@
/// - Parameter removeBytes: the amount of bytes to remove from the current Size
@inline(__always)
public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
- assert(removeBytes > 0, "Can NOT remove negative bytes")
+ assert(removeBytes >= 0, "Can NOT remove negative bytes")
assert(
removeBytes < capacity,
"Can NOT remove more bytes than the ones allocated")
@@ -464,8 +500,9 @@
extension ByteBuffer: CustomDebugStringConvertible {
public var debugDescription: String {
- """
- buffer located at: \(_storage.retainedBlob),
+ let blobDescription = _storage.retainedBlob.description
+ return """
+ buffer located at: \(blobDescription),
with capacity of \(capacity),
{ writtenSize: \(_readerIndex), readerSize: \(reader),
size: \(size) }
diff --git a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift
index b8e49bd..d9cc0a9 100644
--- a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift
+++ b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift
@@ -47,7 +47,8 @@
/// A check to see if finish(::) was ever called to retreive data object
private(set) var finished = false
/// A check to see if the buffer should serialize Default values
- private var serializeDefaults: Bool
+ @usableFromInline
+ let serializeDefaults: Bool
/// Current alignment for the buffer
var _minAlignment: Int = 0 {
@@ -756,6 +757,7 @@
/// - offset: ``Offset`` of another object to be written
/// - position: The predefined position of the object
@inline(__always)
+ @inlinable
mutating public func add(offset: Offset, at position: VOffset) {
if offset.isEmpty { return }
add(element: refer(to: offset.o), def: 0, at: position)
@@ -794,6 +796,7 @@
/// - def: Default value for that element
/// - position: The predefined position of the element
@inline(__always)
+ @inlinable
mutating public func add<T: Scalar>(
element: T,
def: T,
@@ -813,6 +816,7 @@
/// - element: Optional element of type scalar
/// - position: The predefined position of the element
@inline(__always)
+ @inlinable
mutating public func add<T: Scalar>(element: T?, at position: VOffset) {
guard let element = element else { return }
track(offset: push(element: element), at: position)
@@ -825,6 +829,7 @@
/// - Parameter element: Element to insert
/// - returns: position of the Element
@inline(__always)
+ @inlinable
@discardableResult
mutating public func push<T: Scalar>(element: T) -> UOffset {
let size = MemoryLayout<T>.size
@@ -836,7 +841,8 @@
}
@inline(__always)
- public func read<T>(def: T.Type, position: Int) -> T {
+ @inlinable
+ public func read<T: BitwiseCopyable>(def: T.Type, position: Int) -> T {
_bb.read(def: def, position: position)
}
}
diff --git a/swift/Sources/FlatBuffers/FlatBufferObject.swift b/swift/Sources/FlatBuffers/FlatBufferObject.swift
index a85f107..dce4512 100644
--- a/swift/Sources/FlatBuffers/FlatBufferObject.swift
+++ b/swift/Sources/FlatBuffers/FlatBufferObject.swift
@@ -18,7 +18,7 @@
/// NativeStruct is a protocol that indicates if the struct is a native `swift` struct
/// since now we will be serializing native structs into the buffer.
-public protocol NativeStruct {}
+public protocol NativeStruct: BitwiseCopyable {}
public protocol FlatBufferVerifiableNativeStruct: NativeStruct, Verifiable {}
diff --git a/swift/Sources/FlatBuffers/Table.swift b/swift/Sources/FlatBuffers/Table.swift
index f29a51e..de87626 100644
--- a/swift/Sources/FlatBuffers/Table.swift
+++ b/swift/Sources/FlatBuffers/Table.swift
@@ -82,7 +82,7 @@
/// - Parameters:
/// - type: Type of Element that needs to be read from the buffer
/// - o: Offset of the Element
- public func readBuffer<T>(of type: T.Type, at o: Int32) -> T {
+ public func readBuffer<T: BitwiseCopyable>(of type: T.Type, at o: Int32) -> T {
bb.read(def: T.self, position: Int(o &+ position))
}
diff --git a/swift/Sources/FlatBuffers/Verifier.swift b/swift/Sources/FlatBuffers/Verifier.swift
index b0ef396..525e149 100644
--- a/swift/Sources/FlatBuffers/Verifier.swift
+++ b/swift/Sources/FlatBuffers/Verifier.swift
@@ -163,7 +163,7 @@
/// - Parameter position: Current position to be read
/// - Throws: `inBuffer` errors
/// - Returns: a value of type `T` usually a `VTable` or a table offset
- internal func getValue<T>(at position: Int) throws -> T {
+ internal func getValue<T: BitwiseCopyable>(at position: Int) throws -> T {
try inBuffer(position: position, of: T.self)
return _buffer.read(def: T.self, position: position)
}
diff --git a/swift/Sources/FlatBuffers/_InternalByteBuffer.swift b/swift/Sources/FlatBuffers/_InternalByteBuffer.swift
index e4ed2f0..a3d7308 100644
--- a/swift/Sources/FlatBuffers/_InternalByteBuffer.swift
+++ b/swift/Sources/FlatBuffers/_InternalByteBuffer.swift
@@ -30,7 +30,7 @@
@usableFromInline
final class Storage {
/// pointer to the start of the buffer object in memory
- private(set) var memory: UnsafeMutableRawPointer
+ @exclusivity(unchecked) private(set) var memory: UnsafeMutableRawPointer
@usableFromInline
init(count: Int, alignment: Int) {
@@ -333,6 +333,7 @@
@discardableResult
@inline(__always)
+ @usableFromInline
func readWithUnsafeRawPointer<T>(
position: Int,
_ body: (UnsafeRawPointer) throws -> T) rethrows -> T
diff --git a/swift/Sources/FlexBuffers/ByteBuffer.swift b/swift/Sources/FlexBuffers/ByteBuffer.swift
index f4dd2ad..6789618 100644
--- a/swift/Sources/FlexBuffers/ByteBuffer.swift
+++ b/swift/Sources/FlexBuffers/ByteBuffer.swift
@@ -27,7 +27,7 @@
@usableFromInline
final class Storage {
@usableFromInline
- enum Blob {
+ @frozen enum Blob: ~Copyable {
#if !os(WASI)
case data(Data)
case bytes(any ContiguousBytes)
@@ -36,6 +36,23 @@
case byteBuffer(_InternalByteBuffer)
case array([UInt8])
case pointer(UnsafeMutableRawPointer)
+
+ init(_ other: borrowing Blob) {
+ switch other {
+ #if !os(WASI)
+ case .data(let data):
+ self = .data(data)
+ case .bytes(let contiguousBytes):
+ self = .bytes(contiguousBytes)
+ #endif
+ case .byteBuffer(let internalByteBuffer):
+ self = .byteBuffer(internalByteBuffer)
+ case .array(let array):
+ self = .array(array)
+ case .pointer(let unsafeMutableRawPointer):
+ self = .pointer(unsafeMutableRawPointer)
+ }
+ }
}
/// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
@@ -44,7 +61,7 @@
private let capacity: Int
/// Retained blob of data that requires the storage to retain a pointer to.
@usableFromInline
- var retainedBlob: Blob
+ let retainedBlob: Blob
@usableFromInline
init(count: Int) {
@@ -57,9 +74,9 @@
}
@usableFromInline
- init(blob: Blob, capacity count: Int) {
+ init(blob: borrowing Blob, capacity count: Int) {
capacity = count
- retainedBlob = blob
+ retainedBlob = .init(blob)
isOwned = false
}
@@ -276,7 +293,7 @@
/// - removeBytes: Removes a number of bytes from the current size
@inline(__always)
init(
- blob: Storage.Blob,
+ blob: borrowing Storage.Blob,
count: Int,
removing removeBytes: Int)
{
@@ -316,7 +333,7 @@
/// - def: Type of the object
/// - position: the index of the object in the buffer
@inline(__always)
- public func read<T>(def: T.Type, position: Int) -> T {
+ public func read<T: BitwiseCopyable>(def: T.Type, position: Int) -> T {
_storage.readWithUnsafeRawPointer(position: position) {
$0.bindMemory(to: T.self, capacity: 1)
.pointee
@@ -360,10 +377,10 @@
@inline(__always)
func readSizedScalar<
T: BinaryInteger,
- T1: BinaryInteger,
- T2: BinaryInteger,
- T3: BinaryInteger,
- T4: BinaryInteger
+ T1: BinaryInteger & BitwiseCopyable,
+ T2: BinaryInteger & BitwiseCopyable,
+ T3: BinaryInteger & BitwiseCopyable,
+ T4: BinaryInteger & BitwiseCopyable
>(
def: T.Type,
t1: T1.Type,
diff --git a/swift/Sources/FlexBuffers/_InternalByteBuffer.swift b/swift/Sources/FlexBuffers/_InternalByteBuffer.swift
index 5df4ff3..e99f95a 100644
--- a/swift/Sources/FlexBuffers/_InternalByteBuffer.swift
+++ b/swift/Sources/FlexBuffers/_InternalByteBuffer.swift
@@ -31,7 +31,7 @@
@usableFromInline
final class Storage {
/// pointer to the start of the buffer object in memory
- var memory: UnsafeMutableRawPointer
+ @exclusivity(unchecked) private(set) var memory: UnsafeMutableRawPointer
@usableFromInline
init(count: Int, alignment: Int) {