Skip to content

Commit ef04b24

Browse files
authored
[docs] Describe the Curiously Recursive Inlinable Switch Pattern (swiftlang#22643)
1 parent 3861c0e commit ef04b24

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

docs/StandardLibraryProgrammersManual.md

+59
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

0 commit comments

Comments
 (0)