Skip to content

Commit efdd008

Browse files
authored
Merge pull request #85797 from tbkka/tbkka-float-parsing
Reimplement Floating-Point Parsing in pure Swift
2 parents 96cfec4 + 313c4a6 commit efdd008

File tree

14 files changed

+3640
-32
lines changed

14 files changed

+3640
-32
lines changed

Runtimes/Core/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ add_library(swiftCore
8080
FlatMap.swift
8181
Flatten.swift
8282
FloatingPoint.swift
83+
FloatingPointFromString.swift
8384
FloatingPointToString.swift
8485
Hashable.swift
8586
AnyHashable.swift # ORDER DEPENDENCY

stdlib/public/SwiftShims/swift/shims/RuntimeShims.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ extern "C" {
3232
SWIFT_RUNTIME_STDLIB_API
3333
void *_swift_objCMirrorSummary(const void * nsObject);
3434

35+
// Caveat: _swift_stdlib_strto{f,d,ld}_clocale() was called directly from inlineable
36+
// code until Feb 2020 (commit 4d0e2adbef4d changed this), so they need to be
37+
// exported with the same C-callable ABI for as long as we support code compiled
38+
// with Swift 5.3.
39+
40+
// _swift_stdlib_strto{d,f,f16}_clocale were reimplemented in
41+
// Swift in Dec 2025. See FloatingPointFromString.swift
42+
// _swift_stdlib_strtold_clocale was not reimplemented at that time.
43+
3544
/// Call strtold_l with the C locale, swapping argument and return
3645
/// types so we can operate on Float80.
3746
SWIFT_RUNTIME_STDLIB_API
@@ -46,6 +55,8 @@ SWIFT_RUNTIME_STDLIB_API
4655
const char *_swift_stdlib_strtof_clocale(const char *nptr, float *outResult);
4756
/// Call strtof_l with the C locale, swapping argument and return
4857
/// types so we can operate consistently on Float80.
58+
// FIXME? Can this be deleted? _swift_stdlib_strtof16_clocale was never
59+
// actually called from inlineable code.
4960
SWIFT_RUNTIME_STDLIB_API
5061
const char *_swift_stdlib_strtof16_clocale(const char *nptr, __fp16 *outResult);
5162

stdlib/public/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ split_embedded_sources(
261261
OUT_LIST_NORMAL SWIFTLIB_GYB_SOURCES
262262

263263
NORMAL AtomicInt.swift.gyb
264+
EMBEDDED FloatingPointFromString.swift
264265
EMBEDDED FloatingPointParsing.swift.gyb
265266
EMBEDDED FloatingPointToString.swift
266267
EMBEDDED FloatingPointTypes.swift.gyb

stdlib/public/core/FloatingPointFromString.swift

Lines changed: 2520 additions & 0 deletions
Large diffs are not rendered by default.

stdlib/public/core/FloatingPointParsing.swift.gyb

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
//
13+
// This defines the public APIs for parsing floating point numbers.
14+
//
15+
// The backing implementation is in `FloatingPointFromString.swift`
16+
//
17+
//===----------------------------------------------------------------------===//
18+
1219

1320
import SwiftShims
1421

@@ -122,6 +129,10 @@ extension ${Self}: LosslessStringConvertible {
122129
/// // p?.isNaN == true
123130
/// // String(p!) == "nan(0x10)"
124131
///
132+
/// - An input string of `"snan"` (case insensitive) is converted
133+
/// into a *signaling NaN* value. This form permits an optional
134+
/// payload in the same format as for a non-signaling NaN.
135+
///
125136
/// A string in any other format than those described above
126137
/// or containing additional characters
127138
/// results in a `nil` value. For example, the following conversions
@@ -134,7 +145,7 @@ extension ${Self}: LosslessStringConvertible {
134145
/// A decimal or hexadecimal string is converted to a `${Self}`
135146
/// instance using the IEEE 754 roundTiesToEven (default) rounding
136147
/// attribute.
137-
/// Values with absolute value smaller than `${Self}.leastNonzeroMagnitude`
148+
/// Values with absolute value smaller than one-half of `${Self}.leastNonzeroMagnitude`
138149
/// are rounded to plus or minus zero.
139150
/// Values with absolute value larger than `${Self}.greatestFiniteMagnitude`
140151
/// are rounded to plus or minus infinity.
@@ -161,13 +172,26 @@ extension ${Self}: LosslessStringConvertible {
161172
/// - Parameter text: An input string to convert to a `${Self}?` instance.
162173
///
163174
@inlinable
175+
// This is inlinable because the unspecialized generic takes a protocol
176+
// argument; inlining might be able to specialize away the existential.
164177
public init?<S: StringProtocol>(_ text: S) {
165178
%if bits == 16:
179+
// Float16 has only been supported since Swift 5.3, so it can
180+
// unconditionally delegate to init(_:Substring)
166181
self.init(Substring(text))
167182
%else:
168-
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { //SwiftStdlib 5.3
183+
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
184+
// On SwiftStdlib 5.3 and later, we can just delegate to the
185+
// init(_:Substring) initializer:
169186
self.init(Substring(text))
170187
} else {
188+
// When running on SwiftStdlib <5.3, we can't just delegate to
189+
// the init(_:Substring) initializer, so we have to inline the whole thing...
190+
191+
// This means we must preserve the _swift_stdlib_strto{f,d,ld}_clocale
192+
// ABI for as long as we support code compiled for Swift < 5.3, which
193+
// includes macOS < 11.0, iOS/tvOS < 14.0, or watchOS < 7.0
194+
171195
self = 0.0
172196
let success = unsafe _withUnprotectedUnsafeMutablePointer(to: &self) { p -> Bool in
173197
unsafe text.withCString { chars -> Bool in
@@ -191,14 +215,77 @@ extension ${Self}: LosslessStringConvertible {
191215
%end
192216
}
193217

194-
// Caveat: This implementation used to be inlineable.
195-
// In particular, we still have to export
196-
// _swift_stdlib_strtoXYZ_clocale()
197-
// as ABI to support old compiled code that still requires it.
218+
% if bits in [16, 32, 64]:
219+
220+
// Various entry points that take concrete types. Note that these
221+
// are deliberately NOT inlinable:
222+
// * There is no performance advantage to inlining
223+
// * There is a code size disadvantage to inlining
224+
// * We don't want to use the `StringProtocol` API for these common cases as
225+
// that pessimizes either performance or code size depending on whether it
226+
// gets inlined.
227+
228+
// TODO: Expose a concrete `String` API to optimize the common case
229+
/*
230+
@available(SwiftStdlib 6.4, *)
231+
public init?(_ text: String) {
232+
if let d = parse_float(text.utf8.span) {
233+
self = d
234+
} else {
235+
return nil
236+
}
237+
}
238+
*/
239+
240+
// TODO: Expose Span-taking API publicly
241+
// TODO: Some applications would like to restrict the patterns supported
242+
// here. We could do that by adding an option set here that indicates
243+
// the supported patterns, or we could expose additional entry points to
244+
// support common use cases (e.g., JSON).
245+
/*
246+
@available(SwiftStdlib 6.4, *)
247+
public init?(_ text: Span<UInt8>) {
248+
if let d = parse_float(text) {
249+
self = d
250+
} else {
251+
return nil
252+
}
253+
}
254+
*/
255+
256+
// Use the all-Swift `parse_float${bits}()` implementation for Float16/32/64
257+
@available(SwiftStdlib 5.3, *)
258+
public init?(_ text: Substring) {
259+
// TODO: Someday, this whole function should simplify down to just:
260+
// ${Self}(text.utf8.span)
261+
#if _pointerBitWidth(_16)
262+
// Always fail on 16-bit targets
263+
return nil
264+
#else
265+
// Work around span availability limits
266+
let parsed = unsafe text.base._guts.withFastUTF8 { chars -> ${Self}? in
267+
unsafe parse_float${bits}(chars.span)
268+
}
269+
270+
if let parsed {
271+
self = parsed
272+
} else {
273+
return nil
274+
}
275+
#endif
276+
}
277+
278+
% elif bits in [80]:
279+
280+
// For now, continue relying on libc-based implementation for Float80 support
281+
// TODO: Implement `parse_float80(_:Span)` so this can
282+
// be implemented the same as above.
198283
@available(SwiftStdlib 5.3, *)
199284
public init?(_ text: Substring) {
200285
self = 0.0
201286
let success = unsafe _withUnprotectedUnsafeMutablePointer(to: &self) { p -> Bool in
287+
// Make a copy of `text` to ensure we have a null-terminated C string
288+
// to pass to the libc API:
202289
unsafe text.withCString { chars -> Bool in
203290
switch unsafe chars[0] {
204291
case 9, 10, 11, 12, 13, 32:
@@ -217,8 +304,10 @@ extension ${Self}: LosslessStringConvertible {
217304
return nil
218305
}
219306
}
307+
% end
220308
}
221309

310+
222311
% if bits in [16,80]:
223312
#endif
224313
% end

stdlib/public/core/GroupInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
"UInt128.swift"],
179179
"Floating": [
180180
"FloatingPoint.swift",
181+
"FloatingPointFromString.swift",
181182
"FloatingPointParsing.swift",
182183
"FloatingPointToString.swift",
183184
"FloatingPointTypes.swift",

stdlib/public/stubs/Stubs.cpp

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -329,30 +329,9 @@ const char *_swift_stdlib_strtold_clocale(const char *nptr, void *outResult) {
329329
#endif
330330
}
331331

332-
const char *_swift_stdlib_strtod_clocale(const char *nptr, double *outResult) {
333-
#if SWIFT_STDLIB_HAS_LOCALE
334-
return _swift_stdlib_strtoX_clocale_impl(nptr, outResult, HUGE_VAL, strtod_l);
335-
#else
336-
return _swift_stdlib_strtoX_impl(nptr, outResult, strtod);
337-
#endif
338-
}
339-
340-
const char *_swift_stdlib_strtof_clocale(const char *nptr, float *outResult) {
341-
#if SWIFT_STDLIB_HAS_LOCALE
342-
return _swift_stdlib_strtoX_clocale_impl(nptr, outResult, HUGE_VALF,
343-
strtof_l);
344-
#else
345-
return _swift_stdlib_strtoX_impl(nptr, outResult, strtof);
346-
#endif
347-
}
348-
349-
const char *_swift_stdlib_strtof16_clocale(const char *nptr,
350-
__fp16 *outResult) {
351-
float tmp;
352-
const char *result = _swift_stdlib_strtof_clocale(nptr, &tmp);
353-
*outResult = tmp;
354-
return result;
355-
}
332+
// _swift_stdlib_strto{d,f,f16}_clocale were reimplemented in Swift
333+
// in December 2025. See FloatingPointFromString.swift.
334+
// _swift_stdlib_strtold_clocale was not reimplemented in Swift at that time.
356335

357336
void _swift_stdlib_flockfile_stdout() {
358337
#if defined(_WIN32)

test/abi/Inputs/macOS/x86_64/stdlib/baseline

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13412,7 +13412,6 @@ __swift_stdlib_reportFatalErrorInFile
1341213412
__swift_stdlib_reportUnimplementedInitializer
1341313413
__swift_stdlib_reportUnimplementedInitializerInFile
1341413414
__swift_stdlib_strtod_clocale
13415-
__swift_stdlib_strtof16_clocale
1341613415
__swift_stdlib_strtof_clocale
1341713416
__swift_stdlib_strtold_clocale
1341813417
__swift_tryRetain

test/abi/Inputs/macOS/x86_64/stdlib/baseline-asserts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13507,7 +13507,6 @@ __swift_stdlib_reportFatalErrorInFile
1350713507
__swift_stdlib_reportUnimplementedInitializer
1350813508
__swift_stdlib_reportUnimplementedInitializerInFile
1350913509
__swift_stdlib_strtod_clocale
13510-
__swift_stdlib_strtof16_clocale
1351113510
__swift_stdlib_strtof_clocale
1351213511
__swift_stdlib_strtold_clocale
1351313512
__swift_tryRetain

0 commit comments

Comments
 (0)