Skip to content

Commit 15e865f

Browse files
committed
[Foundation] Remove inlinable shim calls
We want to enable overlays to import their shims as @_implementationOnly, so that the shims disappear from the public interface. However, this isn’t possible when a shim is called from an @inlinable func, because then the existence (and definition) of the shim needs to be available to all callers of it. Unfortunately Foundation’s Data has three instances where it calls _SwiftFoundationOverlayShims._withStackOrHeapBuffer within @inlinable code: - Data.init<S: Sequence>(_: S) - Data.append<S: Sequence>(contentsOf: S) - Data.replaceSubrange<C: Collection>(_: Range<Int>, with: C) Rewrite the first two to write sequence contents directly into the target Data instance (saving a memcpy and possibly a memory allocation). In replaceSubrange, add fast paths for contiguous collection cases, falling back to a Swift version of _withStackOrHeapBuffer with a 32-byte inline buffer. The expectation is that this will be an overall speedup in most cases, with the possible exception of replaceSubrange invocations with a large non-contiguous collection. rdar://58132561
1 parent 2007515 commit 15e865f

File tree

2 files changed

+109
-104
lines changed

2 files changed

+109
-104
lines changed

stdlib/public/Darwin/Foundation/Data.swift

Lines changed: 109 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,39 @@ internal func __NSDataIsCompact(_ data: NSData) -> Bool {
6262

6363
#endif
6464

65+
@_alwaysEmitIntoClient
66+
internal func _withStackOrHeapBuffer(capacity: Int, _ body: (UnsafeMutableBufferPointer<UInt8>) -> Void) {
67+
guard capacity > 0 else {
68+
body(UnsafeMutableBufferPointer(start: nil, count: 0))
69+
return
70+
}
71+
typealias InlineBuffer = ( // 32 bytes
72+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
73+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
74+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
75+
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8
76+
)
77+
let inlineCount = MemoryLayout<InlineBuffer>.size
78+
if capacity <= inlineCount {
79+
var buffer: InlineBuffer = (
80+
0, 0, 0, 0, 0, 0, 0, 0,
81+
0, 0, 0, 0, 0, 0, 0, 0,
82+
0, 0, 0, 0, 0, 0, 0, 0,
83+
0, 0, 0, 0, 0, 0, 0, 0
84+
)
85+
withUnsafeMutableBytes(of: &buffer) { buffer in
86+
assert(buffer.count == inlineCount)
87+
let start = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
88+
body(UnsafeMutableBufferPointer(start: start, count: capacity))
89+
}
90+
return
91+
}
92+
93+
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: capacity)
94+
defer { buffer.deallocate() }
95+
body(buffer)
96+
}
97+
6598
// Underlying storage representation for medium and large data.
6699
// Inlinability strategy: methods from here should not inline into InlineSlice or LargeSlice unless trivial.
67100
// NOTE: older overlays called this class _DataStorage. The two must
@@ -2054,44 +2087,43 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
20542087
// The sequence might still be able to provide direct access to typed memory.
20552088
// NOTE: It's safe to do this because we're already guarding on S's element as `UInt8`. This would not be safe on arbitrary sequences.
20562089
let representation = elements.withContiguousStorageIfAvailable {
2057-
return _Representation(UnsafeRawBufferPointer($0))
2090+
_Representation(UnsafeRawBufferPointer($0))
20582091
}
2059-
20602092
if let representation = representation {
20612093
_representation = representation
2062-
} else {
2063-
// Dummy assignment so we can capture below.
2064-
_representation = _Representation(capacity: 0)
2065-
2066-
// Copy as much as we can in one shot from the sequence.
2067-
let underestimatedCount = Swift.max(elements.underestimatedCount, 1)
2068-
_withStackOrHeapBuffer(underestimatedCount) { (buffer) in
2069-
// In order to copy from the sequence, we have to bind the buffer to UInt8.
2070-
// This is safe since we'll copy out of this buffer as raw memory later.
2071-
let capacity = buffer.pointee.capacity
2072-
let base = buffer.pointee.memory.bindMemory(to: UInt8.self, capacity: capacity)
2073-
var (iter, endIndex) = elements._copyContents(initializing: UnsafeMutableBufferPointer(start: base, count: capacity))
2074-
2075-
// Copy the contents of buffer...
2076-
_representation = _Representation(UnsafeRawBufferPointer(start: base, count: endIndex))
2077-
2078-
// ... and append the rest byte-wise, buffering through an InlineData.
2079-
var buffer = InlineData()
2080-
while let element = iter.next() {
2081-
buffer.append(byte: element)
2082-
if buffer.count == buffer.capacity {
2083-
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2084-
buffer.count = 0
2085-
}
2086-
}
2094+
return
2095+
}
20872096

2088-
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2089-
if buffer.count > 0 {
2090-
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2091-
buffer.count = 0
2092-
}
2097+
// Copy as much as we can in one shot from the sequence.
2098+
let underestimatedCount = elements.underestimatedCount
2099+
_representation = _Representation(count: underestimatedCount)
2100+
var (iter, endIndex): (S.Iterator, Int) = _representation.withUnsafeMutableBytes { buffer in
2101+
let start = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
2102+
let b = UnsafeMutableBufferPointer(start: start, count: buffer.count)
2103+
return elements._copyContents(initializing: b)
2104+
}
2105+
guard endIndex == _representation.count else {
2106+
// We can't trap here. We have to allow an underfilled buffer
2107+
// to emulate the previous implementation.
2108+
_representation.replaceSubrange(endIndex ..< _representation.endIndex, with: nil, count: 0)
2109+
return
2110+
}
2111+
2112+
// Append the rest byte-wise, buffering through an InlineData.
2113+
var buffer = InlineData()
2114+
while let element = iter.next() {
2115+
buffer.append(byte: element)
2116+
if buffer.count == buffer.capacity {
2117+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2118+
buffer.count = 0
20932119
}
20942120
}
2121+
2122+
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2123+
if buffer.count > 0 {
2124+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2125+
buffer.count = 0
2126+
}
20952127
}
20962128

20972129
@available(swift, introduced: 4.2)
@@ -2347,43 +2379,43 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
23472379

23482380
// The sequence might still be able to provide direct access to typed memory.
23492381
// NOTE: It's safe to do this because we're already guarding on S's element as `UInt8`. This would not be safe on arbitrary sequences.
2350-
var appended = false
2351-
elements.withContiguousStorageIfAvailable {
2382+
let appended: Void? = elements.withContiguousStorageIfAvailable {
23522383
_representation.append(contentsOf: UnsafeRawBufferPointer($0))
2353-
appended = true
23542384
}
2355-
2356-
guard !appended else { return }
2385+
guard appended == nil else { return }
23572386

23582387
// The sequence is really not contiguous.
23592388
// Copy as much as we can in one shot.
2360-
let underestimatedCount = Swift.max(elements.underestimatedCount, 1)
2361-
_withStackOrHeapBuffer(underestimatedCount) { (buffer) in
2362-
// In order to copy from the sequence, we have to bind the temporary buffer to `UInt8`.
2363-
// This is safe since we're the only owners of the buffer and we copy out as raw memory below anyway.
2364-
let capacity = buffer.pointee.capacity
2365-
let base = buffer.pointee.memory.bindMemory(to: UInt8.self, capacity: capacity)
2366-
var (iter, endIndex) = elements._copyContents(initializing: UnsafeMutableBufferPointer(start: base, count: capacity))
2367-
2368-
// Copy the contents of the buffer...
2369-
_representation.append(contentsOf: UnsafeRawBufferPointer(start: base, count: endIndex))
2370-
2371-
// ... and append the rest byte-wise, buffering through an InlineData.
2372-
var buffer = InlineData()
2373-
while let element = iter.next() {
2374-
buffer.append(byte: element)
2375-
if buffer.count == buffer.capacity {
2376-
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2377-
buffer.count = 0
2378-
}
2379-
}
2389+
let underestimatedCount = elements.underestimatedCount
2390+
let originalCount = _representation.count
2391+
_representation.count += underestimatedCount
2392+
var (iter, endIndex): (S.Iterator, Int) = _representation.withUnsafeMutableBytes { buffer in
2393+
let start = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self) + originalCount
2394+
let b = UnsafeMutableBufferPointer(start: start, count: buffer.count - originalCount)
2395+
return elements._copyContents(initializing: b)
2396+
}
2397+
guard endIndex == underestimatedCount else {
2398+
// We can't trap here. We have to allow an underfilled buffer
2399+
// to emulate the previous implementation.
2400+
_representation.replaceSubrange(originalCount + endIndex ..< _representation.endIndex, with: nil, count: 0)
2401+
return
2402+
}
23802403

2381-
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2382-
if buffer.count > 0 {
2404+
// Append the rest byte-wise, buffering through an InlineData.
2405+
var buffer = InlineData()
2406+
while let element = iter.next() {
2407+
buffer.append(byte: element)
2408+
if buffer.count == buffer.capacity {
23832409
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
23842410
buffer.count = 0
23852411
}
23862412
}
2413+
2414+
// If we've still got bytes left in the buffer (i.e. the loop ended before we filled up the buffer and cleared it out), append them.
2415+
if buffer.count > 0 {
2416+
buffer.withUnsafeBytes { _representation.append(contentsOf: $0) }
2417+
buffer.count = 0
2418+
}
23872419
}
23882420

23892421
// MARK: -
@@ -2436,15 +2468,26 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
24362468
/// - parameter newElements: The replacement bytes.
24372469
@inlinable // This is @inlinable as generic and reasonably small.
24382470
public mutating func replaceSubrange<ByteCollection : Collection>(_ subrange: Range<Index>, with newElements: ByteCollection) where ByteCollection.Iterator.Element == Data.Iterator.Element {
2471+
// If the collection is already contiguous, access the underlying raw memory directly.
2472+
if let contiguous = newElements as? ContiguousBytes {
2473+
contiguous.withUnsafeBytes { buffer in
2474+
_representation.replaceSubrange(subrange, with: buffer.baseAddress, count: buffer.count)
2475+
}
2476+
return
2477+
}
2478+
// The collection might still be able to provide direct access to typed memory.
2479+
// NOTE: It's safe to do this because we're already guarding on ByteCollection's element as `UInt8`. This would not be safe on arbitrary collections.
2480+
let replaced: Void? = newElements.withContiguousStorageIfAvailable { buffer in
2481+
_representation.replaceSubrange(subrange, with: buffer.baseAddress, count: buffer.count)
2482+
}
2483+
guard replaced == nil else { return }
2484+
24392485
let totalCount = Int(newElements.count)
2440-
_withStackOrHeapBuffer(totalCount) { conditionalBuffer in
2441-
let buffer = UnsafeMutableBufferPointer(start: conditionalBuffer.pointee.memory.assumingMemoryBound(to: UInt8.self), count: totalCount)
2486+
_withStackOrHeapBuffer(capacity: totalCount) { buffer in
24422487
var (iterator, index) = newElements._copyContents(initializing: buffer)
2443-
while let byte = iterator.next() {
2444-
buffer[index] = byte
2445-
index = buffer.index(after: index)
2446-
}
2447-
replaceSubrange(subrange, with: conditionalBuffer.pointee.memory, count: totalCount)
2488+
precondition(index == buffer.endIndex, "Collection has less elements than its count")
2489+
precondition(iterator.next() == nil, "Collection has more elements than its count")
2490+
_representation.replaceSubrange(subrange, with: buffer.baseAddress, count: totalCount)
24482491
}
24492492
}
24502493

stdlib/public/SwiftShims/FoundationOverlayShims.h

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,41 +33,3 @@
3333
#import "NSLocaleShims.h"
3434
#import "NSTimeZoneShims.h"
3535
#import "NSUndoManagerShims.h"
36-
37-
typedef struct {
38-
void *_Nonnull memory;
39-
size_t capacity;
40-
_Bool onStack;
41-
} _ConditionalAllocationBuffer;
42-
43-
static inline _Bool _resizeConditionalAllocationBuffer(_ConditionalAllocationBuffer *_Nonnull buffer, size_t amt) {
44-
size_t amount = malloc_good_size(amt);
45-
if (amount <= buffer->capacity) { return true; }
46-
void *newMemory;
47-
if (buffer->onStack) {
48-
newMemory = malloc(amount);
49-
if (newMemory == NULL) { return false; }
50-
memcpy(newMemory, buffer->memory, buffer->capacity);
51-
buffer->onStack = false;
52-
} else {
53-
newMemory = realloc(buffer->memory, amount);
54-
if (newMemory == NULL) { return false; }
55-
}
56-
if (newMemory == NULL) { return false; }
57-
buffer->memory = newMemory;
58-
buffer->capacity = amount;
59-
return true;
60-
}
61-
62-
static inline _Bool _withStackOrHeapBuffer(size_t amount, void (__attribute__((noescape)) ^ _Nonnull applier)(_ConditionalAllocationBuffer *_Nonnull)) {
63-
_ConditionalAllocationBuffer buffer;
64-
buffer.capacity = malloc_good_size(amount);
65-
buffer.onStack = (pthread_main_np() != 0 ? buffer.capacity < 2048 : buffer.capacity < 512);
66-
buffer.memory = buffer.onStack ? alloca(buffer.capacity) : malloc(buffer.capacity);
67-
if (buffer.memory == NULL) { return false; }
68-
applier(&buffer);
69-
if (!buffer.onStack) {
70-
free(buffer.memory);
71-
}
72-
return true;
73-
}

0 commit comments

Comments
 (0)