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:
parent
c74c3bbabf
commit
74cba26b6f
|
@ -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
|
||||
|
|
|
@ -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)!
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue