@@ -97,18 +97,19 @@ extension _StringGuts {
97
97
98
98
@inline ( never) // slow-path
99
99
private mutating func _foreignGrow( _ n: Int ) {
100
- // TODO(String performance): Skip intermediary array, transcode directly
101
- // into a StringStorage space.
102
- let selfUTF8 = Array ( String ( self ) . utf8)
103
- selfUTF8. withUnsafeBufferPointer {
104
- self = _StringGuts ( __StringStorage. create (
105
- initializingFrom: $0, capacity: n, isASCII: self . isASCII) )
100
+ let newString = String ( uninitializedCapacity: n) { buffer in
101
+ guard let count = _foreignCopyUTF8 ( into: buffer) else {
102
+ fatalError ( " String capacity was smaller than required " )
103
+ }
104
+ return count
106
105
}
106
+ self = newString. _guts
107
107
}
108
108
109
109
// Ensure unique native storage with sufficient capacity for the following
110
110
// append.
111
111
private mutating func prepareForAppendInPlace(
112
+ totalCount: Int ,
112
113
otherUTF8Count otherCount: Int
113
114
) {
114
115
defer {
@@ -127,11 +128,13 @@ extension _StringGuts {
127
128
} else {
128
129
sufficientCapacity = false
129
130
}
131
+
130
132
if self . isUniqueNative && sufficientCapacity {
131
133
return
132
134
}
133
-
134
- let totalCount = self . utf8Count + otherCount
135
+
136
+ // If we have to resize anyway, and we fit in smol, we should have made one
137
+ _internalInvariant ( totalCount > _SmallString. capacity)
135
138
136
139
// Non-unique storage: just make a copy of the appropriate size, otherwise
137
140
// grow like an array.
@@ -152,25 +155,76 @@ extension _StringGuts {
152
155
return
153
156
}
154
157
}
155
-
156
158
append ( _StringGutsSlice ( other) )
157
159
}
160
+
161
+ @inline ( never)
162
+ @_effects ( readonly)
163
+ private func _foreignConvertedToSmall( ) -> _SmallString {
164
+ let smol = String ( uninitializedCapacity: _SmallString. capacity) { buffer in
165
+ guard let count = _foreignCopyUTF8 ( into: buffer) else {
166
+ fatalError ( " String capacity was smaller than required " )
167
+ }
168
+ return count
169
+ }
170
+ _internalInvariant ( smol. _guts. isSmall)
171
+ return smol. _guts. asSmall
172
+ }
173
+
174
+ private func _convertedToSmall( ) -> _SmallString {
175
+ _internalInvariant ( utf8Count <= _SmallString. capacity)
176
+ if _fastPath ( isSmall) {
177
+ return asSmall
178
+ }
179
+ if isFastUTF8 {
180
+ return withFastUTF8 { _SmallString ( $0) ! }
181
+ }
182
+ return _foreignConvertedToSmall ( )
183
+ }
158
184
159
185
internal mutating func append( _ slicedOther: _StringGutsSlice ) {
160
186
defer { self . _invariantCheck ( ) }
161
187
162
- if self . isSmall && slicedOther. _guts. isSmall {
188
+ let otherCount = slicedOther. utf8Count
189
+
190
+ let totalCount = utf8Count + otherCount
191
+
192
+ /*
193
+ Goal: determine if we need to allocate new native capacity
194
+ Possible scenarios in which we need to allocate:
195
+ • Not uniquely owned and native: we can't use the capacity to grow into,
196
+ have to become unique + native by allocating
197
+ • Not enough capacity: have to allocate to grow
198
+
199
+ Special case: a non-smol String that can fit in a smol String but doesn't
200
+ meet the above criteria shouldn't throw away its buffer just to be smol.
201
+ The reasoning here is that it may be bridged or have reserveCapacity'd
202
+ in preparation for appending more later, in which case we would end up
203
+ have to allocate anyway to convert back from smol.
204
+
205
+ If we would have to re-allocate anyway then that's not a problem and we
206
+ should just be smol.
207
+
208
+ e.g. consider
209
+ var str = "" // smol
210
+ str.reserveCapacity(100) // large native unique
211
+ str += "<html>" // don't convert back to smol here!
212
+ str += htmlContents // because this would have to anyway!
213
+ */
214
+ let hasEnoughUsableSpace = isUniqueNative &&
215
+ nativeUnusedCapacity! >= otherCount
216
+ let shouldBeSmol = totalCount <= _SmallString. capacity &&
217
+ ( isSmall || !hasEnoughUsableSpace)
218
+
219
+ if shouldBeSmol {
220
+ let smolSelf = _convertedToSmall ( )
221
+ let smolOther = String ( Substring ( slicedOther) ) . _guts. _convertedToSmall ( )
163
222
// TODO: In-register slicing
164
- let smolSelf = self . asSmall
165
- if let smol = slicedOther. withFastUTF8 ( { otherUTF8 in
166
- return _SmallString ( smolSelf, appending: _SmallString ( otherUTF8) !)
167
- } ) {
168
- self = _StringGuts ( smol)
169
- return
170
- }
223
+ self = _StringGuts ( _SmallString ( smolSelf, appending: smolOther) !)
224
+ return
171
225
}
172
-
173
- prepareForAppendInPlace ( otherUTF8Count: slicedOther . utf8Count )
226
+
227
+ prepareForAppendInPlace ( totalCount : totalCount , otherUTF8Count: otherCount )
174
228
175
229
if slicedOther. isFastUTF8 {
176
230
let otherIsASCII = slicedOther. isASCII
@@ -242,7 +296,6 @@ extension _StringGuts {
242
296
self = result. _guts
243
297
}
244
298
245
- @inline ( __always) // Always-specialize
246
299
internal mutating func replaceSubrange< C> (
247
300
_ bounds: Range < Index > ,
248
301
with newElements: C
0 commit comments