Skip to content

Commit 300f4da

Browse files
committed
[stdlib] Simplify buffer pointer initialization.
UnsafeRawBufferPointer was not made self-slicing, but is instead sliced by Slice<UnsafeRawBufferPointer>. When working with UnsafeRawBufferPointer objects it is quite common to want to transform back into the underlying collection, which is why the UnsafeRawBufferPointer provides a constructor from its subsequence: UnsafeRawBufferPointer.init(rebasing:). Unfortunately for an initializer on this extremely low-level type, this initializer is surprisingly expensive. When disassembled on Linux it emits 7 separate traps and 11 branches. This relative heft means this method often gets outlined, which is a shame, as several of the branches could often be eliminated due to checks elsewhere in functions where this initializer is used. Almost all of these branches and almost all of the traps are unnecessary. We can remove them by noting that it is impossible to construct a Slice whose endIndex is earlier than its startIndex. All Slice inits are constructed with bounds expressed as Range, and Range enforces ordering on its endpoints. For this reason, we can do unchecked arithmetic on the start and end index of the slice, and remove 5 traps in one fell swoop. This greatly cheapens the cost of the initializer, improving its odds of being inlined and having even more of its branches optimised away. For what it's worth, I also considered trying to remove the last two preconditions. Unfortunately I concluded I couldn't confidently do that. I wanted to remove them based on the premise that a valid Slice must have indices that were in-bounds for its parent Collection, and so we could safely assume that if the parent base address was nil the count would have to be zero, and that adding start index to the base address would definitely not overflow. However, the existence of the Slice.init(base:bounds:) constructor led me to be a bit suspicions of removing those checks. Given that UnsafeRawBufferPointer.init(start:count:) enforces those invariants, I decided it was safest for us to continue to do that.
1 parent 887464b commit 300f4da

File tree

2 files changed

+8
-4
lines changed

2 files changed

+8
-4
lines changed

stdlib/public/core/UnsafeBufferPointer.swift.gyb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,8 @@ extension Unsafe${Mutable}BufferPointer {
499499
@inlinable // unsafe-performance
500500
public init(rebasing slice: Slice<UnsafeBufferPointer<Element>>) {
501501
let base = slice.base.baseAddress?.advanced(by: slice.startIndex)
502-
self.init(start: base, count: slice.count)
502+
let count = slice.endIndex &- slice.startIndex
503+
self.init(start: base, count: count)
503504
}
504505

505506
% end
@@ -526,7 +527,8 @@ extension Unsafe${Mutable}BufferPointer {
526527
@inlinable // unsafe-performance
527528
public init(rebasing slice: Slice<UnsafeMutableBufferPointer<Element>>) {
528529
let base = slice.base.baseAddress?.advanced(by: slice.startIndex)
529-
self.init(start: base, count: slice.count)
530+
let count = slice.endIndex &- slice.startIndex
531+
self.init(start: base, count: count)
530532
}
531533

532534
/// Deallocates the memory block previously allocated at this buffer pointer’s

stdlib/public/core/UnsafeRawBufferPointer.swift.gyb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,8 @@ extension Unsafe${Mutable}RawBufferPointer {
512512
@inlinable
513513
public init(rebasing slice: Slice<UnsafeRawBufferPointer>) {
514514
let base = slice.base.baseAddress?.advanced(by: slice.startIndex)
515-
self.init(start: base, count: slice.count)
515+
let count = slice.endIndex &- slice.startIndex
516+
self.init(start: base, count: count)
516517
}
517518
% end # !mutable
518519

@@ -539,7 +540,8 @@ extension Unsafe${Mutable}RawBufferPointer {
539540
@inlinable
540541
public init(rebasing slice: Slice<UnsafeMutableRawBufferPointer>) {
541542
let base = slice.base.baseAddress?.advanced(by: slice.startIndex)
542-
self.init(start: base, count: slice.count)
543+
let count = slice.endIndex &- slice.startIndex
544+
self.init(start: base, count: count)
543545
}
544546

545547
/// A pointer to the first byte of the buffer.

0 commit comments

Comments
 (0)