Make all ByteBuffer methods inlinable (#2050)

Motivation:

We've held off on doing this for a while on the theory that function
calls are cheap, and we're happy to make them where there is no
particular need to specialize. Unfortunately, while this is true in
general, Swift is capable of optimizing ARC and exclusivity checking
when it can better understand what a method actually does.

Additionally, we'd hoped that cross-module-optimization would be a
useful addition here. Sadly, right now this still hasn't rolled out as a
default, and even if it had, it tends to be limited to smaller methods
and generic functions, all of which we've already annotated.

Modifications:

- Add @inlinable to essentially everything.

Result:

Better codegen around ByteBuffer.
This commit is contained in:
Cory Benfield 2022-02-21 11:38:38 +00:00 committed by GitHub
parent c74c3bbabf
commit 74cba26b6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 36 deletions

View File

@ -25,6 +25,7 @@ extension ByteBuffer {
/// - index: The starting index of the bytes of interest into the `ByteBuffer`.
/// - length: The number of bytes of interest.
/// - returns: A `[UInt8]` value containing the bytes of interest or `nil` if the bytes `ByteBuffer` are not readable.
@inlinable
public func getBytes(at index: Int, length: Int) -> [UInt8]? {
guard let range = self.rangeWithinReadableBytes(index: index, length: length) else {
return nil
@ -44,6 +45,7 @@ extension ByteBuffer {
/// - parameters:
/// - length: The number of bytes to be read from this `ByteBuffer`.
/// - returns: A `[UInt8]` value containing `length` bytes or `nil` if there aren't at least `length` bytes readable.
@inlinable
public mutating func readBytes(length: Int) -> [UInt8]? {
guard let result = self.getBytes(at: self.readerIndex, length: length) else {
return nil
@ -60,6 +62,7 @@ extension ByteBuffer {
/// - string: The string to write.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeStaticString(_ string: StaticString) -> Int {
let written = self.setStaticString(string, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -72,6 +75,7 @@ extension ByteBuffer {
/// - string: The string to write.
/// - index: The index for the first serialized byte.
/// - returns: The number of bytes written.
@inlinable
public mutating func setStaticString(_ string: StaticString, at index: Int) -> Int {
// please do not replace the code below with code that uses `string.withUTF8Buffer { ... }` (see SR-7541)
return self.setBytes(UnsafeRawBufferPointer(start: string.utf8Start,
@ -85,6 +89,7 @@ extension ByteBuffer {
/// - string: The string to write.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeString(_ string: String) -> Int {
let written = self.setString(string, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -97,6 +102,7 @@ extension ByteBuffer {
/// - string: The string to write.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeNullTerminatedString(_ string: String) -> Int {
let written = self.setNullTerminatedString(string, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -104,7 +110,7 @@ extension ByteBuffer {
}
@inline(never)
@usableFromInline
@inlinable
mutating func _setStringSlowpath(_ string: String, at index: Int) -> Int {
// slow path, let's try to force the string to be native
if let written = (string + "").utf8.withContiguousStorageIfAvailable({ utf8Bytes in
@ -144,6 +150,7 @@ extension ByteBuffer {
/// - string: The string to write.
/// - index: The index for the first serialized byte.
/// - returns: The number of bytes written.
@inlinable
public mutating func setNullTerminatedString(_ string: String, at index: Int) -> Int {
let length = self.setString(string, at: index)
self.setInteger(UInt8(0), at: index &+ length)
@ -158,6 +165,7 @@ extension ByteBuffer {
/// - length: The number of bytes making up the string.
/// - returns: A `String` value containing the UTF-8 decoded selected bytes from this `ByteBuffer` or `nil` if
/// the requested bytes are not readable.
@inlinable
public func getString(at index: Int, length: Int) -> String? {
guard let range = self.rangeWithinReadableBytes(index: index, length: length) else {
return nil
@ -175,14 +183,16 @@ extension ByteBuffer {
/// - index: The starting index into `ByteBuffer` containing the null terminated string of interest.
/// - returns: A `String` value deserialized from this `ByteBuffer` or `nil` if there isn't a complete null-terminated string,
/// including null-terminator, in the readable bytes after `index` in the buffer
@inlinable
public func getNullTerminatedString(at index: Int) -> String? {
guard let stringLength = self.getNullTerminatedStringLength(at: index) else {
guard let stringLength = self._getNullTerminatedStringLength(at: index) else {
return nil
}
return self.getString(at: index, length: stringLength)
}
private func getNullTerminatedStringLength(at index: Int) -> Int? {
@inlinable
func _getNullTerminatedStringLength(at index: Int) -> Int? {
guard self.readerIndex <= index && index < self.writerIndex else {
return nil
}
@ -197,6 +207,7 @@ extension ByteBuffer {
/// - parameters:
/// - length: The number of bytes making up the string.
/// - returns: A `String` value deserialized from this `ByteBuffer` or `nil` if there aren't at least `length` bytes readable.
@inlinable
public mutating func readString(length: Int) -> String? {
guard let result = self.getString(at: self.readerIndex, length: length) else {
return nil
@ -210,8 +221,9 @@ extension ByteBuffer {
///
/// - returns: A `String` value deserialized from this `ByteBuffer` or `nil` if there isn't a complete null-terminated string,
/// including null-terminator, in the readable bytes of the buffer
@inlinable
public mutating func readNullTerminatedString() -> String? {
guard let stringLength = self.getNullTerminatedStringLength(at: self.readerIndex) else {
guard let stringLength = self._getNullTerminatedStringLength(at: self.readerIndex) else {
return nil
}
let result = self.readString(length: stringLength)
@ -226,6 +238,7 @@ extension ByteBuffer {
/// - substring: The substring to write.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeSubstring(_ substring: Substring) -> Int {
let written = self.setSubstring(substring, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -261,6 +274,7 @@ extension ByteBuffer {
/// - dispatchData: The `DispatchData` instance to write to the `ByteBuffer`.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func writeDispatchData(_ dispatchData: DispatchData) -> Int {
let written = self.setDispatchData(dispatchData, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -274,6 +288,7 @@ extension ByteBuffer {
/// - index: The index for the first serialized byte.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func setDispatchData(_ dispatchData: DispatchData, at index: Int) -> Int {
let allBytesCount = dispatchData.count
self.reserveCapacity(index + allBytesCount)
@ -293,6 +308,7 @@ extension ByteBuffer {
/// - length: The number of bytes.
/// - returns: A `DispatchData` value deserialized from this `ByteBuffer` or `nil` if the requested bytes
/// are not readable.
@inlinable
public func getDispatchData(at index: Int, length: Int) -> DispatchData? {
guard let range = self.rangeWithinReadableBytes(index: index, length: length) else {
return nil
@ -307,6 +323,7 @@ extension ByteBuffer {
/// - parameters:
/// - length: The number of bytes.
/// - returns: A `DispatchData` value containing the bytes from this `ByteBuffer` or `nil` if there aren't at least `length` bytes readable.
@inlinable
public mutating func readDispatchData(length: Int) -> DispatchData? {
guard let result = self.getDispatchData(at: self.readerIndex, length: length) else {
return nil
@ -399,6 +416,7 @@ extension ByteBuffer {
/// - index: The index for the first byte.
/// - returns: The number of bytes written.
@discardableResult
@inlinable
public mutating func setBuffer(_ buffer: ByteBuffer, at index: Int) -> Int {
return buffer.withUnsafeReadableBytes{ p in
self.setBytes(p, at: index)
@ -412,6 +430,7 @@ extension ByteBuffer {
/// - buffer: The `ByteBuffer` to write.
/// - returns: The number of bytes written to this `ByteBuffer` which is equal to the number of bytes read from `buffer`.
@discardableResult
@inlinable
public mutating func writeBuffer(_ buffer: inout ByteBuffer) -> Int {
let written = self.setBuffer(buffer, at: writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -451,6 +470,7 @@ extension ByteBuffer {
/// - parameter count: How many times to repeat the given `byte`
/// - returns: How many bytes were written.
@discardableResult
@inlinable
public mutating func writeRepeatingByte(_ byte: UInt8, count: Int) -> Int {
let written = self.setRepeatingByte(byte, count: count, at: self.writerIndex)
self._moveWriterIndex(forwardBy: written)
@ -463,6 +483,7 @@ extension ByteBuffer {
/// - parameter count: How many times to repeat the given `byte`
/// - returns: How many bytes were written.
@discardableResult
@inlinable
public mutating func setRepeatingByte(_ byte: UInt8, count: Int, at index: Int) -> Int {
precondition(count >= 0, "Can't write fewer than 0 bytes")
self.reserveCapacity(index + count)
@ -480,6 +501,7 @@ extension ByteBuffer {
/// - note: Because `ByteBuffer` implements copy-on-write a copy of the storage will be automatically triggered when either of the `ByteBuffer`s sharing storage is written to.
///
/// - returns: A `ByteBuffer` sharing storage containing the readable bytes only.
@inlinable
public func slice() -> ByteBuffer {
return self.getSlice(at: self.readerIndex, length: self.readableBytes)! // must work, bytes definitely in the buffer
}
@ -496,6 +518,7 @@ extension ByteBuffer {
/// - parameters:
/// - length: The number of bytes to slice off.
/// - returns: A `ByteBuffer` sharing storage containing `length` bytes or `nil` if the not enough bytes were readable.
@inlinable
public mutating func readSlice(length: Int) -> ByteBuffer? {
guard let result = self.getSlice_inlineAlways(at: self.readerIndex, length: length) else {
return nil
@ -505,6 +528,7 @@ extension ByteBuffer {
}
@discardableResult
@inlinable
public mutating func writeImmutableBuffer(_ buffer: ByteBuffer) -> Int {
var mutable = buffer
return self.writeBuffer(&mutable)
@ -536,6 +560,7 @@ extension ByteBuffer {
/// buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeString` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(string: String) {
self = ByteBufferAllocator().buffer(string: string)
}
@ -550,6 +575,7 @@ extension ByteBuffer {
/// the buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeSubstring` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(substring string: Substring) {
self = ByteBufferAllocator().buffer(substring: string)
}
@ -564,6 +590,7 @@ extension ByteBuffer {
/// the buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeStaticString` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(staticString string: StaticString) {
self = ByteBufferAllocator().buffer(staticString: string)
}
@ -608,6 +635,7 @@ extension ByteBuffer {
/// into the buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeRepeatingByte` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(repeating byte: UInt8, count: Int) {
self = ByteBufferAllocator().buffer(repeating: byte, count: count)
}
@ -626,6 +654,7 @@ extension ByteBuffer {
/// buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeImmutableBuffer` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(buffer: ByteBuffer) {
self = ByteBufferAllocator().buffer(buffer: buffer)
}
@ -640,6 +669,7 @@ extension ByteBuffer {
/// the buffer use `channel.allocator.buffer(capacity: ...)` to allocate a `ByteBuffer` of the right
/// size followed by a `writeDispatchData` instead of using this method. This allows SwiftNIO to do
/// accounting and optimisations of resources acquired for operations on a given `Channel` in the future.
@inlinable
public init(dispatchData: DispatchData) {
self = ByteBufferAllocator().buffer(dispatchData: dispatchData)
}
@ -651,6 +681,7 @@ extension ByteBufferAllocator {
/// This will allocate a new `ByteBuffer` with enough space to fit `string` and potentially some extra space.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(string: String) -> ByteBuffer {
var buffer = self.buffer(capacity: string.utf8.count)
buffer.writeString(string)
@ -662,6 +693,7 @@ extension ByteBufferAllocator {
/// This will allocate a new `ByteBuffer` with enough space to fit `string` and potentially some extra space.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(substring string: Substring) -> ByteBuffer {
var buffer = self.buffer(capacity: string.utf8.count)
buffer.writeSubstring(string)
@ -673,6 +705,7 @@ extension ByteBufferAllocator {
/// This will allocate a new `ByteBuffer` with enough space to fit `string` and potentially some extra space.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(staticString string: StaticString) -> ByteBuffer {
var buffer = self.buffer(capacity: string.utf8CodeUnitCount)
buffer.writeStaticString(string)
@ -711,6 +744,7 @@ extension ByteBufferAllocator {
/// This will allocate a new `ByteBuffer` with at least `count` bytes.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(repeating byte: UInt8, count: Int) -> ByteBuffer {
var buffer = self.buffer(capacity: count)
buffer.writeRepeatingByte(byte, count: count)
@ -726,6 +760,7 @@ extension ByteBufferAllocator {
/// you have a `ByteBuffer` but would like the `readerIndex` to start at `0`, consider `readSlice` instead.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(buffer: ByteBuffer) -> ByteBuffer {
var newBuffer = self.buffer(capacity: buffer.readableBytes)
newBuffer.writeImmutableBuffer(buffer)
@ -738,6 +773,7 @@ extension ByteBufferAllocator {
/// some extra space.
///
/// - returns: The `ByteBuffer` containing the written bytes.
@inlinable
public func buffer(dispatchData: DispatchData) -> ByteBuffer {
var buffer = self.buffer(capacity: dispatchData.count)
buffer.writeDispatchData(dispatchData)
@ -757,6 +793,7 @@ extension Optional where Wrapped == ByteBuffer {
/// - returns: The number of bytes written to this `ByteBuffer` which is equal to the number of `readableBytes` in
/// `buffer`.
@discardableResult
@inlinable
public mutating func setOrWriteImmutableBuffer(_ buffer: ByteBuffer) -> Int {
var mutable = buffer
return self.setOrWriteBuffer(&mutable)
@ -772,6 +809,7 @@ extension Optional where Wrapped == ByteBuffer {
/// - buffer: The `ByteBuffer` to write.
/// - returns: The number of bytes written to this `ByteBuffer` which is equal to the number of bytes read from `buffer`.
@discardableResult
@inlinable
public mutating func setOrWriteBuffer(_ buffer: inout ByteBuffer) -> Int {
if self == nil {
let readableBytes = buffer.readableBytes

View File

@ -18,6 +18,7 @@ extension Array where Element == UInt8 {
/// Creates a `[UInt8]` from the given buffer. The entire readable portion of the buffer will be read.
/// - parameter buffer: The buffer to read.
@inlinable
public init(buffer: ByteBuffer) {
var buffer = buffer
self = buffer.readBytes(length: buffer.readableBytes)!
@ -29,6 +30,7 @@ extension String {
/// Creates a `String` from a given `ByteBuffer`. The entire readable portion of the buffer will be read.
/// - parameter buffer: The buffer to read.
@inlinable
public init(buffer: ByteBuffer) {
var buffer = buffer
self = buffer.readString(length: buffer.readableBytes)!
@ -40,6 +42,7 @@ extension DispatchData {
/// Creates a `DispatchData` from a given `ByteBuffer`. The entire readable portion of the buffer will be read.
/// - parameter buffer: The buffer to read.
@inlinable
public init(buffer: ByteBuffer) {
var buffer = buffer
self = buffer.readDispatchData(length: buffer.readableBytes)!

View File

@ -20,13 +20,13 @@ import Darwin
import Glibc
#endif
let sysMalloc: @convention(c) (size_t) -> UnsafeMutableRawPointer? = malloc
let sysRealloc: @convention(c) (UnsafeMutableRawPointer?, size_t) -> UnsafeMutableRawPointer? = realloc
@usableFromInline let sysMalloc: @convention(c) (size_t) -> UnsafeMutableRawPointer? = malloc
@usableFromInline let sysRealloc: @convention(c) (UnsafeMutableRawPointer?, size_t) -> UnsafeMutableRawPointer? = realloc
/// Xcode 13 GM shipped with a bug in the SDK that caused `free`'s first argument to be annotated as
/// non-nullable. To that end, we define a thunk through to `free` that matches that constraint, as we
/// never pass a `nil` pointer to it.
let sysFree: @convention(c) (UnsafeMutableRawPointer) -> Void = { free($0) }
@usableFromInline let sysFree: @convention(c) (UnsafeMutableRawPointer) -> Void = { free($0) }
extension _ByteBufferSlice: Equatable {}
@ -45,17 +45,17 @@ struct _ByteBufferSlice {
// this cannot underflow.
return Int(self.upperBound &- self.lowerBound)
}
init() {
@inlinable init() {
self._begin = .init(0)
self.upperBound = .init(0)
}
static var maxSupportedLowerBound: ByteBuffer._Index {
@inlinable static var maxSupportedLowerBound: ByteBuffer._Index {
return ByteBuffer._Index(_UInt24.max)
}
}
extension _ByteBufferSlice {
init(_ range: Range<UInt32>) {
@inlinable init(_ range: Range<UInt32>) {
self._begin = _UInt24(range.lowerBound)
self.upperBound = range.upperBound
}
@ -77,13 +77,14 @@ public struct ByteBufferAllocator {
/// Create a fresh `ByteBufferAllocator`. In the future the allocator might use for example allocation pools and
/// therefore it's recommended to reuse `ByteBufferAllocators` where possible instead of creating fresh ones in
/// many places.
public init() {
@inlinable public init() {
self.init(hookedMalloc: { sysMalloc($0) },
hookedRealloc: { sysRealloc($0, $1) },
hookedFree: { sysFree($0) },
hookedMemcpy: { $0.copyMemory(from: $1, byteCount: $2) })
}
@inlinable
internal init(hookedMalloc: @escaping @convention(c) (size_t) -> UnsafeMutableRawPointer?,
hookedRealloc: @escaping @convention(c) (UnsafeMutableRawPointer?, size_t) -> UnsafeMutableRawPointer?,
hookedFree: @escaping @convention(c) (UnsafeMutableRawPointer) -> Void,
@ -103,6 +104,7 @@ public struct ByteBufferAllocator {
///
/// - parameters:
/// - capacity: The initial capacity of the returned `ByteBuffer`.
@inlinable
public func buffer(capacity: Int) -> ByteBuffer {
precondition(capacity >= 0, "ByteBuffer capacity must be positive.")
guard capacity > 0 else {
@ -114,10 +116,10 @@ public struct ByteBufferAllocator {
@usableFromInline
internal static let zeroCapacityWithDefaultAllocator = ByteBuffer(allocator: ByteBufferAllocator(), startingCapacity: 0)
internal let malloc: @convention(c) (size_t) -> UnsafeMutableRawPointer?
internal let realloc: @convention(c) (UnsafeMutableRawPointer?, size_t) -> UnsafeMutableRawPointer?
internal let free: @convention(c) (UnsafeMutableRawPointer) -> Void
internal let memcpy: @convention(c) (UnsafeMutableRawPointer, UnsafeRawPointer, size_t) -> Void
@usableFromInline internal let malloc: @convention(c) (size_t) -> UnsafeMutableRawPointer?
@usableFromInline internal let realloc: @convention(c) (UnsafeMutableRawPointer?, size_t) -> UnsafeMutableRawPointer?
@usableFromInline internal let free: @convention(c) (UnsafeMutableRawPointer) -> Void
@usableFromInline internal let memcpy: @convention(c) (UnsafeMutableRawPointer, UnsafeRawPointer, size_t) -> Void
}
@inlinable func _toCapacity(_ value: Int) -> ByteBuffer._Capacity {
@ -229,11 +231,12 @@ public struct ByteBuffer {
// MARK: Internal _Storage for CoW
@usableFromInline final class _Storage {
private(set) var capacity: _Capacity
@usableFromInline private(set) var capacity: _Capacity
@usableFromInline private(set) var bytes: UnsafeMutableRawPointer
private let allocator: ByteBufferAllocator
@usableFromInline let allocator: ByteBufferAllocator
public init(bytesNoCopy: UnsafeMutableRawPointer, capacity: _Capacity, allocator: ByteBufferAllocator) {
@inlinable
init(bytesNoCopy: UnsafeMutableRawPointer, capacity: _Capacity, allocator: ByteBufferAllocator) {
self.bytes = bytesNoCopy
self.capacity = capacity
self.allocator = allocator
@ -243,36 +246,42 @@ public struct ByteBuffer {
self.deallocate()
}
internal var fullSlice: _ByteBufferSlice {
@inlinable
var fullSlice: _ByteBufferSlice {
return _ByteBufferSlice(0..<self.capacity)
}
private static func allocateAndPrepareRawMemory(bytes: _Capacity, allocator: Allocator) -> UnsafeMutableRawPointer {
@inlinable
static func _allocateAndPrepareRawMemory(bytes: _Capacity, allocator: Allocator) -> UnsafeMutableRawPointer {
let ptr = allocator.malloc(size_t(bytes))!
/* bind the memory so we can assume it elsewhere to be bound to UInt8 */
ptr.bindMemory(to: UInt8.self, capacity: Int(bytes))
return ptr
}
public func allocateStorage() -> _Storage {
@inlinable
func allocateStorage() -> _Storage {
return self.allocateStorage(capacity: self.capacity)
}
fileprivate func allocateStorage(capacity: _Capacity) -> _Storage {
@inlinable
func allocateStorage(capacity: _Capacity) -> _Storage {
let newCapacity = capacity == 0 ? 0 : capacity.nextPowerOf2ClampedToMax()
return _Storage(bytesNoCopy: _Storage.allocateAndPrepareRawMemory(bytes: newCapacity, allocator: self.allocator),
return _Storage(bytesNoCopy: _Storage._allocateAndPrepareRawMemory(bytes: newCapacity, allocator: self.allocator),
capacity: newCapacity,
allocator: self.allocator)
}
public func reallocSlice(_ slice: Range<ByteBuffer._Index>, capacity: _Capacity) -> _Storage {
@inlinable
func reallocSlice(_ slice: Range<ByteBuffer._Index>, capacity: _Capacity) -> _Storage {
assert(slice.count <= capacity)
let new = self.allocateStorage(capacity: capacity)
self.allocator.memcpy(new.bytes, self.bytes.advanced(by: Int(slice.lowerBound)), size_t(slice.count))
return new
}
public func reallocStorage(capacity minimumNeededCapacity: _Capacity) {
@inlinable
func reallocStorage(capacity minimumNeededCapacity: _Capacity) {
let newCapacity = minimumNeededCapacity.nextPowerOf2ClampedToMax()
let ptr = self.allocator.realloc(self.bytes, size_t(newCapacity))!
/* bind the memory so we can assume it elsewhere to be bound to UInt8 */
@ -285,15 +294,16 @@ public struct ByteBuffer {
self.allocator.free(self.bytes)
}
public static func reallocated(minimumCapacity: _Capacity, allocator: Allocator) -> _Storage {
@inlinable
static func reallocated(minimumCapacity: _Capacity, allocator: Allocator) -> _Storage {
let newCapacity = minimumCapacity == 0 ? 0 : minimumCapacity.nextPowerOf2ClampedToMax()
// TODO: Use realloc if possible
return _Storage(bytesNoCopy: _Storage.allocateAndPrepareRawMemory(bytes: newCapacity, allocator: allocator),
return _Storage(bytesNoCopy: _Storage._allocateAndPrepareRawMemory(bytes: newCapacity, allocator: allocator),
capacity: newCapacity,
allocator: allocator)
}
public func dumpBytes(slice: Slice, offset: Int, length: Int) -> String {
func dumpBytes(slice: Slice, offset: Int, length: Int) -> String {
var desc = "["
let bytes = UnsafeRawBufferPointer(start: self.bytes, count: Int(self.capacity))
for byte in bytes[Int(slice.lowerBound) + offset ..< Int(slice.lowerBound) + offset + length] {
@ -305,7 +315,9 @@ public struct ByteBuffer {
}
}
@usableFromInline mutating func _copyStorageAndRebase(capacity: _Capacity, resetIndices: Bool = false) {
@inlinable
@inline(never)
mutating func _copyStorageAndRebase(capacity: _Capacity, resetIndices: Bool = false) {
let indexRebaseAmount = resetIndices ? self._readerIndex : 0
let storageRebaseAmount = self._slice.lowerBound + indexRebaseAmount
let newSlice = storageRebaseAmount ..< min(storageRebaseAmount + _toCapacity(self._slice.count), self._slice.upperBound, storageRebaseAmount + capacity)
@ -315,11 +327,15 @@ public struct ByteBuffer {
self._slice = self._storage.fullSlice
}
@usableFromInline mutating func _copyStorageAndRebase(extraCapacity: _Capacity = 0, resetIndices: Bool = false) {
@inlinable
@inline(never)
mutating func _copyStorageAndRebase(extraCapacity: _Capacity = 0, resetIndices: Bool = false) {
self._copyStorageAndRebase(capacity: _toCapacity(self._slice.count) + extraCapacity, resetIndices: resetIndices)
}
@usableFromInline mutating func _ensureAvailableCapacity(_ capacity: _Capacity, at index: _Index) {
@inlinable
@inline(never)
mutating func _ensureAvailableCapacity(_ capacity: _Capacity, at index: _Index) {
assert(isKnownUniquelyReferenced(&self._storage))
let totalNeededCapacityWhenKeepingSlice = self._slice.lowerBound + index + capacity
@ -432,7 +448,7 @@ public struct ByteBuffer {
// MARK: Public Core API
fileprivate init(allocator: ByteBufferAllocator, startingCapacity: Int) {
@inlinable init(allocator: ByteBufferAllocator, startingCapacity: Int) {
let startingCapacity = _toCapacity(startingCapacity)
self._readerIndex = 0
self._writerIndex = 0
@ -463,6 +479,7 @@ public struct ByteBuffer {
/// The current capacity of the underlying storage of this `ByteBuffer`.
/// A COW slice of the buffer (e.g. readSlice(length: x)) will posses the same storageCapacity as the original
/// buffer until new data is written.
@inlinable
public var storageCapacity: Int {
return self._storage.fullSlice.count
}
@ -476,6 +493,7 @@ public struct ByteBuffer {
///
/// - parameters:
/// - minimumCapacity: The minimum number of bytes this buffer must be able to store.
@inlinable
public mutating func reserveCapacity(_ minimumCapacity: Int) {
guard minimumCapacity > self.capacity else {
return
@ -500,11 +518,13 @@ public struct ByteBuffer {
/// method will be a no-op.
///
/// - Parameter minimumWritableBytes: The minimum number of writable bytes this buffer must have.
@inlinable
public mutating func reserveCapacity(minimumWritableBytes: Int) {
return self.reserveCapacity(self.writerIndex + minimumWritableBytes)
}
@usableFromInline
@inlinable
@inline(never)
mutating func _copyStorageAndRebaseIfNeeded() {
if !isKnownUniquelyReferenced(&self._storage) {
self._copyStorageAndRebase()
@ -633,8 +653,9 @@ public struct ByteBuffer {
return try body(.init(self._slicedStorageBuffer), storageReference)
}
@inlinable
@inline(never)
private func copyIntoByteBufferWithSliceIndex0_slowPath(index: _Index, length: _Capacity) -> ByteBuffer {
func _copyIntoByteBufferWithSliceIndex0_slowPath(index: _Index, length: _Capacity) -> ByteBuffer {
var new = self
new._moveWriterIndex(to: index + length)
new._moveReaderIndex(to: index)
@ -654,11 +675,13 @@ public struct ByteBuffer {
/// - length: The length of the requested slice.
/// - returns: A `ByteBuffer` containing the selected bytes as readable bytes or `nil` if the selected bytes were
/// not readable in the initial `ByteBuffer`.
@inlinable
public func getSlice(at index: Int, length: Int) -> ByteBuffer? {
return self.getSlice_inlineAlways(at: index, length: length)
}
@inline(__always)
@inlinable
internal func getSlice_inlineAlways(at index: Int, length: Int) -> ByteBuffer? {
guard index >= 0 && length >= 0 && index >= self.readerIndex && length <= self.writerIndex && index <= self.writerIndex &- length else {
return nil
@ -679,7 +702,7 @@ public struct ByteBuffer {
guard sliceStartIndex <= ByteBuffer.Slice.maxSupportedLowerBound else {
// the slice's begin is past the maximum supported slice begin value (16 MiB) so the only option we have
// is copy the slice into a fresh buffer. The slice begin will then be at index 0.
return self.copyIntoByteBufferWithSliceIndex0_slowPath(index: index, length: length)
return self._copyIntoByteBufferWithSliceIndex0_slowPath(index: index, length: length)
}
var new = self
assert(sliceStartIndex == self._slice.lowerBound &+ index)
@ -700,6 +723,7 @@ public struct ByteBuffer {
/// at index `0` after the call returns.
///
/// - returns: `true` if one or more bytes have been discarded, `false` if there are no bytes to discard.
@inlinable
@discardableResult public mutating func discardReadBytes() -> Bool {
guard self._readerIndex > 0 else {
return false
@ -745,6 +769,7 @@ public struct ByteBuffer {
///
/// - note: This method will allocate if the underlying storage is referenced by another `ByteBuffer`. Even if an
/// allocation is necessary this will be cheaper as the copy of the storage is elided.
@inlinable
public mutating func clear() {
if !isKnownUniquelyReferenced(&self._storage) {
self._storage = self._storage.allocateStorage()
@ -777,6 +802,7 @@ public struct ByteBuffer {
///
/// - parameters:
/// - minimumCapacity: The minimum capacity that will be (re)allocated for this buffer
@inlinable
public mutating func clear(minimumCapacity: Int) {
precondition(minimumCapacity >= 0, "Cannot have a minimum capacity < 0")
precondition(minimumCapacity <= _Capacity.max, "Minimum capacity must be <= \(_Capacity.max)")
@ -852,6 +878,7 @@ extension ByteBuffer {
/// to the `writerIndex`. Failing to meet either of these requirements leads to undefined behaviour.
/// - parameters:
/// - offset: The number of bytes to move the reader index forward by.
@inlinable
public mutating func moveReaderIndex(forwardBy offset: Int) {
let newIndex = self._readerIndex + _toIndex(offset)
precondition(newIndex >= 0 && newIndex <= writerIndex, "new readerIndex: \(newIndex), expected: range(0, \(writerIndex))")
@ -865,6 +892,7 @@ extension ByteBuffer {
/// to the `writerIndex`. Failing to meet either of these requirements leads to undefined behaviour.
/// - parameters:
/// - offset: The offset in bytes to set the reader index to.
@inlinable
public mutating func moveReaderIndex(to offset: Int) {
let newIndex = _toIndex(offset)
precondition(newIndex >= 0 && newIndex <= writerIndex, "new readerIndex: \(newIndex), expected: range(0, \(writerIndex))")
@ -878,6 +906,7 @@ extension ByteBuffer {
/// to the `writerIndex`. Failing to meet either of these requirements leads to undefined behaviour.
/// - parameters:
/// - offset: The number of bytes to move the writer index forward by.
@inlinable
public mutating func moveWriterIndex(forwardBy offset: Int) {
let newIndex = self._writerIndex + _toIndex(offset)
precondition(newIndex >= 0 && newIndex <= _toCapacity(self._slice.count),"new writerIndex: \(newIndex), expected: range(0, \(_toCapacity(self._slice.count)))")
@ -891,6 +920,7 @@ extension ByteBuffer {
/// to the `writerIndex`. Failing to meet either of these requirements leads to undefined behaviour.
/// - parameters:
/// - offset: The offset in bytes to set the reader index to.
@inlinable
public mutating func moveWriterIndex(to offset: Int) {
let newIndex = _toIndex(offset)
precondition(newIndex >= 0 && newIndex <= _toCapacity(self._slice.count),"new writerIndex: \(newIndex), expected: range(0, \(_toCapacity(self._slice.count)))")
@ -965,6 +995,7 @@ extension ByteBuffer: Equatable {
// TODO: I don't think this makes sense. This should compare bytes 0..<writerIndex instead.
/// Compare two `ByteBuffer` values. Two `ByteBuffer` values are considered equal if the readable bytes are equal.
@inlinable
public static func ==(lhs: ByteBuffer, rhs: ByteBuffer) -> Bool {
guard lhs.readableBytes == rhs.readableBytes else {
return false
@ -986,6 +1017,7 @@ extension ByteBuffer: Equatable {
extension ByteBuffer: Hashable {
/// The hash value for the readable bytes.
@inlinable
public func hash(into hasher: inout Hasher) {
self.withUnsafeReadableBytes { ptr in
hasher.combine(bytes: ptr)

View File

@ -127,6 +127,7 @@ extension UInt32 {
/// Returns the next power of two unless that would overflow, in which case UInt32.max (on 64-bit systems) or
/// Int32.max (on 32-bit systems) is returned. The returned value is always safe to be cast to Int and passed
/// to malloc on all platforms.
@inlinable
func nextPowerOf2ClampedToMax() -> UInt32 {
guard self > 0 else {
return 1

View File

@ -221,6 +221,7 @@ extension ByteBuffer {
extension ByteBufferView: Equatable {
/// required by `Equatable`
@inlinable
public static func == (lhs: ByteBufferView, rhs: ByteBufferView) -> Bool {
guard lhs._range.count == rhs._range.count else {
@ -238,6 +239,7 @@ extension ByteBufferView: Equatable {
extension ByteBufferView: Hashable {
/// required by `Hashable`
@inlinable
public func hash(into hasher: inout Hasher) {
// A well-formed ByteBufferView can never have a range that is out-of-bounds of the backing ByteBuffer.
// As a result, this getSlice call can never fail, and we'd like to know it if it does.
@ -247,6 +249,7 @@ extension ByteBufferView: Hashable {
extension ByteBufferView: ExpressibleByArrayLiteral {
/// required by `ExpressibleByArrayLiteral`
@inlinable
public init(arrayLiteral elements: Element...) {
self.init(elements)
}