Skip to content

Commit

Permalink
Fixed issues using std::to_chars on Apple platforms
Browse files Browse the repository at this point in the history
The float-specialized versions of `std::to_chars` aren't available in
libc++ before macOS 13.3 or iOS 16.3, and unfortunately you can't
even weak-link to them and check at runtime. So I've switched to a
compile-time check against the target's deployment OS version.
  • Loading branch information
snej committed Jul 7, 2024
1 parent 4fc4ef0 commit 565c2b1
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 51 deletions.
6 changes: 6 additions & 0 deletions crouton.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@
2755E8642C3B05520092702E /* libllhttp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 279D5E302A954627005C3066 /* libllhttp.a */; };
2755E86F2C3B0CBB0092702E /* empty.c in Sources */ = {isa = PBXBuildFile; fileRef = 2755E86E2C3B0CBB0092702E /* empty.c */; };
2755E8702C3B0CBB0092702E /* empty.c in Sources */ = {isa = PBXBuildFile; fileRef = 2755E86E2C3B0CBB0092702E /* empty.c */; };
2755E8722C3B13110092702E /* Availability.hh in Headers */ = {isa = PBXBuildFile; fileRef = 2755E8712C3B13110092702E /* Availability.hh */; };
2755E8732C3B13110092702E /* Availability.hh in Headers */ = {isa = PBXBuildFile; fileRef = 2755E8712C3B13110092702E /* Availability.hh */; };
275A5F152AAFB3B1009791E8 /* Memoized.hh in Headers */ = {isa = PBXBuildFile; fileRef = 275A5F132AAFB3B1009791E8 /* Memoized.hh */; };
275A5F162AAFB3B1009791E8 /* Memoized.cc in Sources */ = {isa = PBXBuildFile; fileRef = 275A5F142AAFB3B1009791E8 /* Memoized.cc */; };
275A5F172AB105EE009791E8 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 278F7E312AA015B0005B12F2 /* CoreFoundation.framework */; };
Expand Down Expand Up @@ -503,6 +505,7 @@
2755E81D2B0947D40005AC35 /* MiniOStream.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MiniOStream.cc; sourceTree = "<group>"; };
2755E8692C3B05520092702E /* libcrouton-nodeps.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libcrouton-nodeps.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2755E86E2C3B0CBB0092702E /* empty.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = empty.c; sourceTree = "<group>"; };
2755E8712C3B13110092702E /* Availability.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Availability.hh; sourceTree = "<group>"; };
275A5F132AAFB3B1009791E8 /* Memoized.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Memoized.hh; sourceTree = "<group>"; };
275A5F142AAFB3B1009791E8 /* Memoized.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Memoized.cc; sourceTree = "<group>"; };
275EB3E92ACC7AC10022ED53 /* docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = docs; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1713,6 +1716,7 @@
279D5D572A9146DA005C3066 /* support */ = {
isa = PBXGroup;
children = (
2755E8712C3B13110092702E /* Availability.hh */,
27F49CD52A9E4E8E00BFB24C /* Backtrace.cc */,
277B652D2AD9FCDC006F053D /* Backtrace+Unix.cc */,
277B652F2AD9FD47006F053D /* Backtrace+Windows.cc */,
Expand Down Expand Up @@ -1814,6 +1818,7 @@
27B330532AB2763B0066C8DA /* BLIPIO.hh in Headers */,
27B330682AB384880066C8DA /* Codec.hh in Headers */,
275A5F152AAFB3B1009791E8 /* Memoized.hh in Headers */,
2755E8722C3B13110092702E /* Availability.hh in Headers */,
278F7F242AAA98FE005B12F2 /* Logging.hh in Headers */,
272A852A2A97B2090083D947 /* HTTPConnection.hh in Headers */,
277E8A412ABA4AA200E78CB1 /* CoCondition.hh in Headers */,
Expand Down Expand Up @@ -1856,6 +1861,7 @@
2755E8202C3B05520092702E /* BLIPIO.hh in Headers */,
2755E8212C3B05520092702E /* Codec.hh in Headers */,
2755E8222C3B05520092702E /* Memoized.hh in Headers */,
2755E8732C3B13110092702E /* Availability.hh in Headers */,
2755E8232C3B05520092702E /* Logging.hh in Headers */,
2755E8242C3B05520092702E /* HTTPConnection.hh in Headers */,
2755E8252C3B05520092702E /* CoCondition.hh in Headers */,
Expand Down
5 changes: 5 additions & 0 deletions include/crouton/util/MiniFormat.hh
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
- Only 10 arguments are allowed. (You can change this by changing BaseFormatString::kMaxSpecs.)
- Field width and precision are limited to 255.
Compatibility:
- If building for Apple platforms, and your deployment version is earlier than macOS 13.3 or
iOS/watchOS/tvOS 16.3, floating-point formatting specifiers are ignored. This is because the
floating-point versions of `std::to_chars()` aren't available in older versions of libc++.
Known bugs:
- When a number is zero-padded, the zeroes go before the sign character, not afterward.
- When the alternate ('#') form of a float adds a decimal point, it will go after any exponent,
Expand Down
29 changes: 29 additions & 0 deletions src/support/Availability.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// Availability.hh
//
//
//

#pragma once

// Older versions of Apple's libc++ don't have the floating point versions of std::to_chars.
// I used to check at runtime with:
// if (__builtin_available(macOS 13.3, iOS 16.3, tvOS 16.3, watchOS 9.3, *)) { ... }
// Sadly, this doesn't work, because to_chars' availability attributes use the "strict" flag.
// The Clang docs say:
// > The flag strict disallows using API when deploying back to a platform version prior to
// > when the declaration was introduced. An attempt to use such API before its introduction
// > causes a hard error.
#ifdef __APPLE__
# include <Availability.h>
# include <TargetConditionals.h>
# if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 130300)
# define FLOAT_TO_CHARS_AVAILABLE 1
# elif (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= 160300)
# define FLOAT_TO_CHARS_AVAILABLE 1
# else
# define FLOAT_TO_CHARS_AVAILABLE 0
# endif
#else
# define FLOAT_TO_CHARS_AVAILABLE 1
#endif
73 changes: 34 additions & 39 deletions src/support/MiniFormat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "crouton/util/MiniFormat.hh"
#include "crouton/util/MiniOStream.hh"
#include "support/Availability.hh"
#include <charconv>

namespace crouton::mini {
Expand Down Expand Up @@ -51,8 +52,8 @@ namespace crouton::mini {


// non-localized ASCII-specific equivalents of ctypes:
static bool isupper(char c) {return c >= 'A' && c <= 'Z';}
static char toupper(char c) {return (c >= 'a' && c <= 'z') ? (c - 32) : c;}
__unused static bool isupper(char c) {return c >= 'A' && c <= 'Z';}
__unused static char toupper(char c) {return (c >= 'a' && c <= 'z') ? (c - 32) : c;}

static void upperize(char *begin, char *end) {
for (char *c = begin; c != end; ++c)
Expand Down Expand Up @@ -131,47 +132,41 @@ namespace crouton::mini {
if (d >= 0.0)
writeNonNegativeSign(out, spec.sign);
char buf[60]; // big enough for all but absurdly large precisions
#ifdef __APPLE__ // Apple's libc++ may not have the floating point versions of std::to_chars
if (__builtin_available(macOS 13.3, iOS 16.3, tvOS 16.3, watchOS 9.3, *)) {
#endif

std::to_chars_result result;
if (spec.type == 0 && spec.precision == BaseFormatString::kDefaultPrecision) {
result = std::to_chars(&buf[0], &buf[sizeof(buf)], d);
} else {
std::chars_format format;
int precision = (spec.precision != BaseFormatString::kDefaultPrecision) ? spec.precision : 6;
switch (spec.type) {
case 'a': case 'A': format = std::chars_format::hex; break;
case 'e': case 'E': format = std::chars_format::scientific; break;
case 'f': case 'F': format = std::chars_format::fixed; break;
case 0:
case 'g': case 'G': format = std::chars_format::general; break;
default: throw format_error("invalid type for floating-point arg");
}
result = std::to_chars(&buf[0], &buf[sizeof(buf)], d, format, precision);
if (result.ec != std::errc{}) {
out.write("FIELD OVERFLOW"); // FIXME: Use bigger buffer?
return;
}
if (isupper(spec.type))
upperize(buf, result.ptr);
#if FLOAT_TO_CHARS_AVAILABLE
std::to_chars_result result;
if (spec.type == 0 && spec.precision == BaseFormatString::kDefaultPrecision) {
result = std::to_chars(&buf[0], &buf[sizeof(buf)], d);
} else {
std::chars_format format;
int precision = (spec.precision != BaseFormatString::kDefaultPrecision) ? spec.precision : 6;
switch (spec.type) {
case 'a': case 'A': format = std::chars_format::hex; break;
case 'e': case 'E': format = std::chars_format::scientific; break;
case 'f': case 'F': format = std::chars_format::fixed; break;
case 0:
case 'g': case 'G': format = std::chars_format::general; break;
default: throw format_error("invalid type for floating-point arg");
}

if (spec.alternate) {
if (string_view(buf, result.ptr).find('.') == string::npos)
*result.ptr++ = '.'; // append a '.' if there isn't one
//FIXME: In scientific notation it should go before the 'e'/'E'
result = std::to_chars(&buf[0], &buf[sizeof(buf)], d, format, precision);
if (result.ec != std::errc{}) {
out.write("FIELD OVERFLOW"); // FIXME: Use bigger buffer?
return;
}
if (isupper(spec.type))
upperize(buf, result.ptr);
}

out.write(&buf[0], result.ptr);

#ifdef __APPLE__
} else {
// lame fallback if to_chars isn't available:
snprintf(buf, sizeof(buf), "%g", d);
out.write(buf);
if (spec.alternate) {
if (string_view(buf, result.ptr).find('.') == string::npos)
*result.ptr++ = '.'; // append a '.' if there isn't one
//FIXME: In scientific notation it should go before the 'e'/'E'
}

out.write(&buf[0], result.ptr);
#else
// lame fallback if to_chars isn't available:
snprintf(buf, sizeof(buf), "%g", d);
out.write(buf);
#endif
}

Expand Down
19 changes: 8 additions & 11 deletions src/support/MiniOStream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

#include "crouton/util/MiniOStream.hh"
#include "support/Availability.hh"
#include <charconv>
#include <cstdio>

Expand Down Expand Up @@ -44,17 +45,13 @@ namespace crouton::mini {
}

ostream& ostream::writeDouble(double f) {
char buf[30];
#ifdef __APPLE__ // Apple's libc++ didn't add this method until later
if (__builtin_available(macOS 13.3, iOS 16.3, tvOS 16.3, watchOS 9.3, *)) {
#endif
auto result = std::to_chars(&buf[0], &buf[sizeof(buf)], f);
return write(&buf[0], result.ptr - buf);
#ifdef __APPLE__
} else {
snprintf(buf, sizeof(buf), "%g", f);
return write(buf);
}
char buf[60];
#if FLOAT_TO_CHARS_AVAILABLE
auto result = std::to_chars(&buf[0], &buf[sizeof(buf)], f);
return write(&buf[0], result.ptr - buf);
#else
snprintf(buf, sizeof(buf), "%g", f);
return write(buf);
#endif
}

Expand Down
2 changes: 1 addition & 1 deletion xcconfigs/Project.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ SUPPORTED_PLATFORMS = macosx iphonesimulator iphoneos
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES

IPHONEOS_DEPLOYMENT_TARGET = 15.0
MACOSX_DEPLOYMENT_TARGET = 13.3 //TEMP: Was 10.15
MACOSX_DEPLOYMENT_TARGET = 10.15

// Search paths
HEADER_SEARCH_PATHS = $(inherited) include src
Expand Down

0 comments on commit 565c2b1

Please sign in to comment.