Skip to content

Commit 79bac4e

Browse files
authored
Merge pull request swiftlang#30244 from milseman/string_shrink
[string] Shrink storage class sizes
2 parents be141e6 + 89f0a12 commit 79bac4e

10 files changed

+410
-199
lines changed

stdlib/public/core/LegacyABI.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,11 @@ extension String {
7979
range: Range<String.Index>
8080
) { fatalError() }
8181
}
82+
83+
extension String.UTF16View {
84+
// Swift 5.x: This was accidentally shipped as inlinable, but was never used
85+
// from an inlinable context. The definition is kept around for techincal ABI
86+
// compatibility (even though it shouldn't matter), but is unused.
87+
@inlinable @inline(__always)
88+
internal var _shortHeuristic: Int { return 32 }
89+
}

stdlib/public/core/StringBreadcrumbs.swift

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313

1414
// @opaque
1515
internal final class _StringBreadcrumbs {
16-
static var breadcrumbStride: Int { return 32 }
16+
internal static var breadcrumbStride: Int { 64 }
1717

18-
var utf16Length: Int
18+
internal var utf16Length: Int
1919

2020
// TODO: does this need to be a pair?.... Can we be smaller than Int?
21-
var crumbs: [String.Index]
21+
internal var crumbs: [String.Index]
2222

2323
// TODO: Does this need to be inout, unique, or how will we be enforcing
2424
// atomicity?
25-
init(_ str: String) {
25+
internal init(_ str: String) {
2626
let stride = _StringBreadcrumbs.breadcrumbStride
2727

2828
self.crumbs = []
@@ -62,7 +62,7 @@ internal final class _StringBreadcrumbs {
6262
}
6363

6464
extension _StringBreadcrumbs {
65-
var stride: Int {
65+
internal var stride: Int {
6666
@inline(__always) get { return _StringBreadcrumbs.breadcrumbStride }
6767
}
6868

@@ -108,37 +108,3 @@ extension _StringBreadcrumbs {
108108
#endif // INTERNAL_CHECKS_ENABLED
109109
}
110110

111-
extension _StringGuts {
112-
@_effects(releasenone)
113-
internal func getBreadcrumbsPtr() -> UnsafePointer<_StringBreadcrumbs> {
114-
_internalInvariant(hasBreadcrumbs)
115-
116-
let mutPtr: UnsafeMutablePointer<_StringBreadcrumbs?>
117-
if hasNativeStorage {
118-
mutPtr = _object.nativeStorage._breadcrumbsAddress
119-
} else {
120-
mutPtr = UnsafeMutablePointer(
121-
Builtin.addressof(&_object.sharedStorage._breadcrumbs))
122-
}
123-
124-
if _slowPath(mutPtr.pointee == nil) {
125-
populateBreadcrumbs(mutPtr)
126-
}
127-
128-
_internalInvariant(mutPtr.pointee != nil)
129-
// assuming optional class reference and class reference can alias
130-
return UnsafeRawPointer(mutPtr).assumingMemoryBound(to: _StringBreadcrumbs.self)
131-
}
132-
133-
@inline(never) // slow-path
134-
@_effects(releasenone)
135-
internal func populateBreadcrumbs(
136-
_ mutPtr: UnsafeMutablePointer<_StringBreadcrumbs?>
137-
) {
138-
// Thread-safe compare-and-swap
139-
let crumbs = _StringBreadcrumbs(String(self))
140-
_stdlib_atomicInitializeARCRef(
141-
object: UnsafeMutableRawPointer(mutPtr).assumingMemoryBound(to: Optional<AnyObject>.self),
142-
desired: crumbs)
143-
}
144-
}

stdlib/public/core/StringBridge.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ extension String {
462462
// and bridge that instead. Also avoids CF deleting any BOM that may be
463463
// present
464464
var copy = self
465+
// TODO: small capacity minimum is lifted, just need to make native
465466
copy._guts.grow(_SmallString.capacity + 1)
466467
_internalInvariant(!copy._guts.isSmall)
467468
return copy._bridgeToObjectiveCImpl()

stdlib/public/core/StringCreate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ extension String {
109109
) throws -> Int
110110
) rethrows -> String {
111111
let result = try __StringStorage.create(
112-
uninitializedCapacity: capacity,
112+
uninitializedCodeUnitCapacity: capacity,
113113
initializingUncheckedUTF8With: initializer)
114114

115115
switch validateUTF8(result.codeUnits) {

stdlib/public/core/StringGuts.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ extension _StringGuts {
120120

121121
internal var hasSharedStorage: Bool { return _object.hasSharedStorage }
122122

123+
// Whether this string has breadcrumbs
123124
internal var hasBreadcrumbs: Bool {
124-
return hasNativeStorage || hasSharedStorage
125+
return hasSharedStorage
126+
|| (hasNativeStorage && _object.nativeStorage.hasBreadcrumbs)
125127
}
126128
}
127129

stdlib/public/core/StringGutsRangeReplaceable.swift

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension _StringGuts {
5858
internal init(_initialCapacity capacity: Int) {
5959
self.init()
6060
if _slowPath(capacity > _SmallString.capacity) {
61-
self.grow(capacity)
61+
self.grow(capacity) // TODO: no factor should be applied
6262
}
6363
}
6464

@@ -68,7 +68,7 @@ extension _StringGuts {
6868
if let currentCap = self.uniqueNativeCapacity, currentCap >= n { return }
6969

7070
// Grow
71-
self.grow(n)
71+
self.grow(n) // TODO: no factor should be applied
7272
}
7373

7474
// Grow to accomodate at least `n` code units
@@ -79,13 +79,16 @@ extension _StringGuts {
7979
_internalInvariant(
8080
self.uniqueNativeCapacity == nil || self.uniqueNativeCapacity! < n)
8181

82+
// TODO: Dont' do this! Growth should only happen for append...
8283
let growthTarget = Swift.max(n, (self.uniqueNativeCapacity ?? 0) * 2)
8384

8485
if _fastPath(isFastUTF8) {
8586
let isASCII = self.isASCII
8687
let storage = self.withFastUTF8 {
8788
__StringStorage.create(
88-
initializingFrom: $0, capacity: growthTarget, isASCII: isASCII)
89+
initializingFrom: $0,
90+
codeUnitCapacity: growthTarget,
91+
isASCII: isASCII)
8992
}
9093

9194
self = _StringGuts(storage)
@@ -128,11 +131,11 @@ extension _StringGuts {
128131
} else {
129132
sufficientCapacity = false
130133
}
131-
134+
132135
if self.isUniqueNative && sufficientCapacity {
133136
return
134137
}
135-
138+
136139
// If we have to resize anyway, and we fit in smol, we should have made one
137140
_internalInvariant(totalCount > _SmallString.capacity)
138141

@@ -145,7 +148,7 @@ extension _StringGuts {
145148
growthTarget = Swift.max(
146149
totalCount, _growArrayCapacity(nativeCapacity ?? 0))
147150
}
148-
self.grow(growthTarget)
151+
self.grow(growthTarget) // NOTE: this already has exponential growth...
149152
}
150153

151154
internal mutating func append(_ other: _StringGuts) {
@@ -157,7 +160,7 @@ extension _StringGuts {
157160
}
158161
append(_StringGutsSlice(other))
159162
}
160-
163+
161164
@inline(never)
162165
@_effects(readonly)
163166
private func _foreignConvertedToSmall() -> _SmallString {
@@ -170,7 +173,7 @@ extension _StringGuts {
170173
_internalInvariant(smol._guts.isSmall)
171174
return smol._guts.asSmall
172175
}
173-
176+
174177
private func _convertedToSmall() -> _SmallString {
175178
_internalInvariant(utf8Count <= _SmallString.capacity)
176179
if _fastPath(isSmall) {
@@ -186,25 +189,25 @@ extension _StringGuts {
186189
defer { self._invariantCheck() }
187190

188191
let otherCount = slicedOther.utf8Count
189-
192+
190193
let totalCount = utf8Count + otherCount
191-
194+
192195
/*
193196
Goal: determine if we need to allocate new native capacity
194197
Possible scenarios in which we need to allocate:
195198
• Not uniquely owned and native: we can't use the capacity to grow into,
196199
have to become unique + native by allocating
197200
• Not enough capacity: have to allocate to grow
198-
201+
199202
Special case: a non-smol String that can fit in a smol String but doesn't
200203
meet the above criteria shouldn't throw away its buffer just to be smol.
201204
The reasoning here is that it may be bridged or have reserveCapacity'd
202205
in preparation for appending more later, in which case we would end up
203206
have to allocate anyway to convert back from smol.
204-
207+
205208
If we would have to re-allocate anyway then that's not a problem and we
206209
should just be smol.
207-
210+
208211
e.g. consider
209212
var str = "" // smol
210213
str.reserveCapacity(100) // large native unique
@@ -215,15 +218,15 @@ extension _StringGuts {
215218
nativeUnusedCapacity! >= otherCount
216219
let shouldBeSmol = totalCount <= _SmallString.capacity &&
217220
(isSmall || !hasEnoughUsableSpace)
218-
221+
219222
if shouldBeSmol {
220223
let smolSelf = _convertedToSmall()
221224
let smolOther = String(Substring(slicedOther))._guts._convertedToSmall()
222225
// TODO: In-register slicing
223226
self = _StringGuts(_SmallString(smolSelf, appending: smolOther)!)
224227
return
225228
}
226-
229+
227230
prepareForAppendInPlace(totalCount: totalCount, otherUTF8Count: otherCount)
228231

229232
if slicedOther.isFastUTF8 {

0 commit comments

Comments
 (0)