Skip to content

Commit 301dcc4

Browse files
committed
---
yaml --- r: 318429 b: refs/heads/master-rebranch c: ab857ba h: refs/heads/master i: 318427: c89cab1
1 parent 677cffc commit 301dcc4

File tree

5 files changed

+104
-3
lines changed

5 files changed

+104
-3
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,4 +1457,4 @@ refs/tags/swift-DEVELOPMENT-SNAPSHOT-2019-08-02-a: ddd2b2976aa9bfde5f20fe37f6bd2
14571457
refs/tags/swift-DEVELOPMENT-SNAPSHOT-2019-08-03-a: 171cc166f2abeb5ca2a4003700a8a78a108bd300
14581458
refs/heads/benlangmuir-patch-1: baaebaf39d52f3bf36710d4fe40cf212e996b212
14591459
refs/heads/i-do-redeclare: 8c4e6d5de5c1e3f0a2cedccf319df713ea22c48e
1460-
refs/heads/master-rebranch: 90c188bab7306542a3edd79df2696d0807516f8e
1460+
refs/heads/master-rebranch: ab857ba4c7ea0cb4e2a321a53e500d4809c94c42

branches/master-rebranch/docs/StandardLibraryProgrammersManual.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,65 @@ The standard library utilizes thread local storage (TLS) to cache expensive comp
171171
172172
See [ThreadLocalStorage.swift](https://github.com/apple/swift/blob/master/stdlib/public/core/ThreadLocalStorage.swift) for more details.
173173
174+
175+
## Working with Resilience
176+
177+
Maintaining ABI compatibility with previously released versions of the standard library makes things more complicated. This section details some of the extra rules to remember and patterns to use.
178+
179+
### The Curiously Recursive Inlinable Switch Pattern (CRISP)
180+
181+
When inlinable code switches over a non-frozen enum, it has to handle possible future cases (since it will be inlined into a module outside the standard library). You can see this in action with the implementation of `round(_:)` in FloatingPointTypes.swift.gyb, which takes a FloatingPointRoundingRule. It looks something like this:
182+
183+
```swift
184+
@_transparent
185+
public mutating func round(_ rule: FloatingPointRoundingRule) {
186+
switch rule {
187+
case .toNearestOrAwayFromZero:
188+
_value = Builtin.int_round_FPIEEE${bits}(_value)
189+
case .toNearestOrEven:
190+
_value = Builtin.int_rint_FPIEEE${bits}(_value)
191+
// ...
192+
@unknown default:
193+
self._roundSlowPath(rule)
194+
}
195+
}
196+
```
197+
198+
Making `round(_:)` inlinable but still have a default case is an attempt to get the best of both worlds: if the rounding rule is known at compile time, the call will compile down to a single instruction in optimized builds; and if it dynamically turns out to be a new kind of rounding rule added in Swift 25 (e.g. `.towardFortyTwo`), there's a fallback function, `_roundSlowPath(_:)`, that can handle it.
199+
200+
So what does `_roundSlowPath(_:)` look like? Well, it can't be inlinable, because that would defeat the purpose. It *could* just look like this:
201+
202+
```swift
203+
@usableFromInline
204+
internal mutating func _roundSlowPath(_ rule: FloatingPointRoundingRule) {
205+
switch rule {
206+
case .toNearestOrAwayFromZero:
207+
_value = Builtin.int_round_FPIEEE${bits}(_value)
208+
case .toNearestOrEven:
209+
_value = Builtin.int_rint_FPIEEE${bits}(_value)
210+
// ...
211+
}
212+
}
213+
```
214+
215+
...i.e. exactly the same as `round(_:)` but with no `default` case. That's guaranteed to be up to date if any new cases are added in the future. But it seems a little silly, since it's duplicating code that's in `round(_:)`. We *could* omit cases that have always existed, but there's a better answer:
216+
217+
```swift
218+
// Slow path for new cases that might have been inlined into an old
219+
// ABI-stable version of round(_:) called from a newer version. If this is
220+
// the case, this non-inlinable function will call into the _newer_ version
221+
// which _will_ support this rounding rule.
222+
@usableFromInline
223+
internal mutating func _roundSlowPath(_ rule: FloatingPointRoundingRule) {
224+
self.round(rule)
225+
}
226+
```
227+
228+
Because `_roundSlowPath(_:)` isn't inlinable, the version of `round(_:)` that gets called at run time will always be the version implemented in the standard library dylib. And since FloatingPointRoundingRule is *also* defined in the standard library, we know it'll never be out of sync with this version of `round(_:)`.
229+
230+
Maybe some day we'll have special syntax in the language to say "call this method without allowing inlining" to get the same effect, but for now, this Curiously Recursive Inlinable Switch Pattern allows for safe inlining of switches over non-frozen enums with less boilerplate than you might otherwise have. Not none, but less.
231+
232+
174233
## Productivity Hacks
175234

176235
### Be a Ninja

branches/master-rebranch/stdlib/public/core/Character.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ extension Character {
8181
internal func _invariantCheck() {
8282
_internalInvariant(_str.count == 1)
8383
_internalInvariant(_str._guts.isFastUTF8)
84+
85+
// TODO(@eject): Switch to a helper property on StringObject/StringGuts.
86+
_internalInvariant(
87+
_str._guts.isSmall || _str._guts._object._countAndFlags.isTailAllocated)
8488
}
8589
#endif // INTERNAL_CHECKS_ENABLED
8690
}
@@ -173,7 +177,17 @@ extension Character :
173177
"Can't form a Character from an empty String")
174178
_debugPrecondition(s.index(after: s.startIndex) == s.endIndex,
175179
"Can't form a Character from a String containing more than one extended grapheme cluster")
176-
self.init(unchecked: s)
180+
181+
// TODO(@eject): Switch to a helper property on StringObject/StringGuts.
182+
if _fastPath(
183+
s._guts.isSmall || s._guts._object._countAndFlags.isTailAllocated
184+
) {
185+
self.init(unchecked: s)
186+
return
187+
}
188+
189+
// TODO(@eject): Outline this
190+
self.init(unchecked: s._withUTF8 { String._uncheckedFromUTF8($0) })
177191
}
178192
}
179193

branches/master-rebranch/stdlib/public/core/StringObject.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,9 +617,10 @@ extension _StringObject {
617617
isNativelyStored: set for native stored strings
618618
- `largeAddressBits` holds an instance of `_StringStorage`.
619619
- I.e. the start of the code units is at the stored address + `nativeBias`
620-
isTailAllocated: start of the code units is at the stored address + `nativeBias`
620+
isTailAllocated: contiguous UTF-8 code units starts at address + `nativeBias`
621621
- `isNativelyStored` always implies `isTailAllocated`, but not vice versa
622622
(e.g. literals)
623+
- `isTailAllocated` always implies `isFastUTF8`
623624
TBD: Reserved for future usage
624625
- Setting a TBD bit to 1 must be semantically equivalent to 0
625626
- I.e. it can only be used to "cache" fast-path information in the future
@@ -1073,6 +1074,9 @@ extension _StringObject {
10731074
} else {
10741075
_internalInvariant(isLarge)
10751076
_internalInvariant(largeCount == count)
1077+
if _countAndFlags.isTailAllocated {
1078+
_internalInvariant(providesFastUTF8)
1079+
}
10761080
if providesFastUTF8 && largeFastIsTailAllocated {
10771081
_internalInvariant(!isSmall)
10781082
_internalInvariant(!largeIsCocoa)

branches/master-rebranch/test/stdlib/StringBridge.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,29 @@ StringBridgeTests.test("Tagged NSString") {
7575
#endif // not 32bit
7676
}
7777

78+
func returnOne<T>(_ t: T) -> Int { return 1 }
79+
StringBridgeTests.test("Character from NSString") {
80+
// NOTE: Using hard-coded literals to directly construct NSStrings
81+
let ns1 = "A" as NSString
82+
let ns2 = "A\u{301}" as NSString
83+
let ns3 = "𓁹͇͈͉͍͎͊͋͌ͧͨͩͪͫͬͭͮ͏̛͓͔͕͖͙͚̗̘̙̜̹̺̻̼͐͑͒͗͛ͣͤͥͦ̽̾̿̀́͂̓̈́͆ͧͨͩͪͫͬͭͮ͘̚͜͟͢͝͞͠͡ͅ" as NSString
84+
85+
let c1 = Character(ns1 as String)
86+
let c2 = Character(ns2 as String)
87+
let c3 = Character(ns3 as String)
88+
89+
expectEqual("A", String(c1))
90+
expectNotNil(String(c1).utf8.withContiguousStorageIfAvailable(returnOne))
91+
92+
expectEqual("A\u{301}", String(c2))
93+
expectNotNil(String(c2).utf8.withContiguousStorageIfAvailable(returnOne))
94+
expectNil((ns2 as String).utf8.withContiguousStorageIfAvailable(returnOne))
95+
96+
expectEqual("𓁹͇͈͉͍͎͊͋͌ͧͨͩͪͫͬͭͮ͏̛͓͔͕͖͙͚̗̘̙̜̹̺̻̼͐͑͒͗͛ͣͤͥͦ̽̾̿̀́͂̓̈́͆ͧͨͩͪͫͬͭͮ͘̚͜͟͢͝͞͠͡ͅ", String(c3))
97+
expectNotNil(String(c3).utf8.withContiguousStorageIfAvailable(returnOne))
98+
expectNil((ns3 as String).utf8.withContiguousStorageIfAvailable(returnOne))
99+
}
100+
101+
78102
runAllTests()
79103

0 commit comments

Comments
 (0)