diff --git a/change/react-native-windows-4080fb51-cbec-4c0c-b2e6-3a14a8be8965.json b/change/react-native-windows-4080fb51-cbec-4c0c-b2e6-3a14a8be8965.json
new file mode 100644
index 00000000000..712e44dc708
--- /dev/null
+++ b/change/react-native-windows-4080fb51-cbec-4c0c-b2e6-3a14a8be8965.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "merge in folly 2024.10.14.00",
+ "packageName": "react-native-windows",
+ "email": "tatianakapos@microsoft.com",
+ "dependentChangeType": "patch"
+}
diff --git a/vnext/Desktop.DLL/react-native-win32.x64.def b/vnext/Desktop.DLL/react-native-win32.x64.def
index 9c2f22b9a29..95ce42f28f0 100644
--- a/vnext/Desktop.DLL/react-native-win32.x64.def
+++ b/vnext/Desktop.DLL/react-native-win32.x64.def
@@ -5,12 +5,9 @@
; **************************************************************************************************
EXPORTS
-??0dynamic@folly@@QEAA@$$QEAU01@@Z
??8folly@@YA_NAEBUdynamic@0@0@Z
??4dynamic@folly@@QEAAAEAU01@$$QEAU01@@Z
-?hash@dynamic@folly@@QEBA_KXZ
??Mfolly@@YA_NAEBUdynamic@0@0@Z
-?destroy@dynamic@folly@@AEAAXXZ
??0TypeError@folly@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@W4Type@dynamic@1@@Z
??0dynamic@folly@@QEAA@AEBU01@@Z
??$safe_assert_terminate@$0A@@detail@folly@@YAXPEBUsafe_assert_arg@01@ZZ
@@ -29,7 +26,6 @@ EXPORTS
?Make@IWebSocketResource@Networking@React@Microsoft@@SA?AV?$shared_ptr@UIWebSocketResource@Networking@React@Microsoft@@@std@@XZ
?Make@JSBigAbiString@react@facebook@@SA?AV?$unique_ptr@$$CBUJSBigAbiString@react@facebook@@U?$default_delete@$$CBUJSBigAbiString@react@facebook@@@std@@@std@@$$QEAV?$unique_ptr@U?$IAbiArray@D@AbiSafe@@UAbiObjectDeleter@2@@5@@Z
?at@dynamic@folly@@QEGBAAEBU12@V?$Range@PEBD@2@@Z
-?atImpl@dynamic@folly@@AEGBAAEBU12@AEBU12@@Z
?getRuntimeExecutor@Instance@react@facebook@@QEAA?AV?$function@$$A6AX$$QEAV?$function@$$A6AXAEAVRuntime@jsi@facebook@@@Z@std@@@Z@std@@XZ
?callJSFunction@Instance@react@facebook@@QEAAX$$QEAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0$$QEAUdynamic@folly@@@Z
?createI18nModule@windows@react@@YA?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@V?$unique_ptr@UII18nModule@windows@react@@U?$default_delete@UII18nModule@windows@react@@@std@@@4@@Z
@@ -40,13 +36,11 @@ EXPORTS
?get_ptr@dynamic@folly@@QEGBAPEBU12@V?$Range@PEBD@2@@Z
?get_ptrImpl@dynamic@folly@@AEGBAPEBU12@AEBU12@@Z
?loadScriptFromString@Instance@react@facebook@@QEAAXV?$unique_ptr@$$CBVJSBigString@react@facebook@@U?$default_delete@$$CBVJSBigString@react@facebook@@@std@@@std@@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@5@_N@Z
-?makeConversionError@folly@@YA?AVConversionError@1@W4ConversionCode@1@V?$Range@PEBD@1@@Z
?moduleNames@ModuleRegistry@react@facebook@@QEAA?AV?$vector@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$allocator@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@XZ
?assertionFailure@detail@folly@@YAXPEBD00I0H@Z
?parseJson@folly@@YA?AUdynamic@1@V?$Range@PEBD@1@@Z
?setGlobalVariable@Instance@react@facebook@@QEAAXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$unique_ptr@$$CBVJSBigString@react@facebook@@U?$default_delete@$$CBVJSBigString@react@facebook@@@std@@@5@@Z
?size@dynamic@folly@@QEBA_KXZ
-?str_to_bool@detail@folly@@YA?AV?$Expected@_NW4ConversionCode@folly@@@2@PEAV?$Range@PEBD@2@@Z
?toJson@folly@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBUdynamic@1@@Z
?typeName@dynamic@folly@@QEBAPEBDXZ
?Utf16ToUtf8@Unicode@Common@Microsoft@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@5@@Z
diff --git a/vnext/Desktop.DLL/react-native-win32.x86.def b/vnext/Desktop.DLL/react-native-win32.x86.def
index 4cd8ba317cc..68547f272f0 100644
--- a/vnext/Desktop.DLL/react-native-win32.x86.def
+++ b/vnext/Desktop.DLL/react-native-win32.x86.def
@@ -5,12 +5,9 @@
; **************************************************************************************************
EXPORTS
-??0dynamic@folly@@QAE@$$QAU01@@Z
??8folly@@YG_NABUdynamic@0@0@Z
??Mfolly@@YG_NABUdynamic@0@0@Z
??4dynamic@folly@@QAEAAU01@$$QAU01@@Z
-?hash@dynamic@folly@@QBEIXZ
-?destroy@dynamic@folly@@AAEXXZ
??0TypeError@folly@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@W4Type@dynamic@1@@Z
??0dynamic@folly@@QAE@ABU01@@Z
??4dynamic@folly@@QAEAAU01@ABU01@@Z
@@ -30,7 +27,6 @@ EXPORTS
?Make@JSBigAbiString@react@facebook@@SG?AV?$unique_ptr@$$CBUJSBigAbiString@react@facebook@@U?$default_delete@$$CBUJSBigAbiString@react@facebook@@@std@@@std@@$$QAV?$unique_ptr@U?$IAbiArray@D@AbiSafe@@UAbiObjectDeleter@2@@5@@Z
?moduleNames@ModuleRegistry@react@facebook@@QAE?AV?$vector@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$allocator@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@XZ
?at@dynamic@folly@@QGBEABU12@V?$Range@PBD@2@@Z
-?atImpl@dynamic@folly@@AGBEABU12@ABU12@@Z
?getRuntimeExecutor@Instance@react@facebook@@QAE?AV?$function@$$A6GX$$QAV?$function@$$A6GXAAVRuntime@jsi@facebook@@@Z@std@@@Z@std@@XZ
?callJSFunction@Instance@react@facebook@@QAEX$$QAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@0$$QAUdynamic@folly@@@Z
?createI18nModule@windows@react@@YG?AV?$unique_ptr@VCxxModule@module@xplat@facebook@@U?$default_delete@VCxxModule@module@xplat@facebook@@@std@@@std@@V?$unique_ptr@UII18nModule@windows@react@@U?$default_delete@UII18nModule@windows@react@@@std@@@4@@Z
@@ -40,11 +36,9 @@ EXPORTS
?get_ptr@dynamic@folly@@QGBEPBU12@V?$Range@PBD@2@@Z
?get_ptrImpl@dynamic@folly@@AGBEPBU12@ABU12@@Z
?loadScriptFromString@Instance@react@facebook@@QAEXV?$unique_ptr@$$CBVJSBigString@react@facebook@@U?$default_delete@$$CBVJSBigString@react@facebook@@@std@@@std@@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@5@_N@Z
-?makeConversionError@folly@@YG?AVConversionError@1@W4ConversionCode@1@V?$Range@PBD@1@@Z
?parseJson@folly@@YG?AUdynamic@1@V?$Range@PBD@1@@Z
?setGlobalVariable@Instance@react@facebook@@QAEXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$unique_ptr@$$CBVJSBigString@react@facebook@@U?$default_delete@$$CBVJSBigString@react@facebook@@@std@@@5@@Z
?size@dynamic@folly@@QBEIXZ
-?str_to_bool@detail@folly@@YG?AV?$Expected@_NW4ConversionCode@folly@@@2@PAV?$Range@PBD@2@@Z
?toJson@folly@@YG?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABUdynamic@1@@Z
?typeName@dynamic@folly@@QBEPBDXZ
?Utf16ToUtf8@Unicode@Common@Microsoft@@YG?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@ABV?$basic_string@_WU?$char_traits@_W@std@@V?$allocator@_W@2@@5@@Z
diff --git a/vnext/Directory.Build.props b/vnext/Directory.Build.props
index 373ea4d5bdb..52be548f0b3 100644
--- a/vnext/Directory.Build.props
+++ b/vnext/Directory.Build.props
@@ -19,9 +19,11 @@
-->
true
false
-
- 2024.01.01.00
- 234d39a36a43106747d10cc19efada72fd810dd3
+
+ 2024.10.14.00
+ 6.1.4
+ ad90720829db5ba0c3d0e44994856dcce33d7940
+ ad90720829db5ba0c3d0e44994856dcce33d7940
10.1.0
ca2e3685b160617d3d95fcd9e789c4e06ca88
diff --git a/vnext/Folly/Folly.vcxproj b/vnext/Folly/Folly.vcxproj
index d98cb6d6b0e..1174fffa021 100644
--- a/vnext/Folly/Folly.vcxproj
+++ b/vnext/Folly/Folly.vcxproj
@@ -64,7 +64,7 @@
-
+
@@ -73,8 +73,8 @@
$(IntDir)\portabilityString.obj
-
-
+
+
@@ -106,7 +106,7 @@
-
+
@@ -127,6 +127,9 @@
+
+
+
@@ -151,7 +154,7 @@
-
+
@@ -220,6 +223,10 @@
+
+
+
+
@@ -234,7 +241,9 @@
+
+
@@ -262,7 +271,7 @@
Use
false
true
- $(ReactNativeWindowsDir)stubs;$(FollyDir);$(FmtDir)\include;%(AdditionalIncludeDirectories)
+ $(ReactNativeWindowsDir)stubs;$(FollyDir);$(FollyDir)\..\fast_float-6.1.4\include;$(FmtDir)\include;%(AdditionalIncludeDirectories)
FOLLY_CFG_NO_COROUTINES;FOLLY_NO_CONFIG;NOMINMAX;_CRT_SECURE_NO_WARNINGS;WINAPI_PARTITION_APP;%(PreprocessorDefinitions)
4251;4293;4305;4800;4804;4310;%(DisableSpecificWarnings)
@@ -282,6 +291,10 @@
$(FollyZipDir)\folly-$(FollyVersion).zip
$(MSBuildThisFileDirectory)cgmanifest.json
+
+ $(FollyDir)..\.fastfloatzip
+ $(FollyDir)..\.fastfloatzip\fastfloat.zip
+
+
+
+
+
+
+
+
+
{
@@ -312,6 +341,16 @@
}
},
"DevelopmentDependency": false
+ },
+ {
+ "Component": {
+ "Type": "git",
+ "Git": {
+ "RepositoryUrl": "https://github.com/fastfloat/fast_float",
+ "CommitHash": "$(FastFloatCommitHash)"
+ }
+ },
+ "DevelopmentDependency": false
}
]
}
diff --git a/vnext/Folly/Folly.vcxproj.filters b/vnext/Folly/Folly.vcxproj.filters
index 64fe8659cfd..67f1bd0e4a1 100644
--- a/vnext/Folly/Folly.vcxproj.filters
+++ b/vnext/Folly/Folly.vcxproj.filters
@@ -48,16 +48,16 @@
Source Files
-
+
Source Files
Source Files\container\detail
-
+
Source Files
-
+
Source Files
@@ -151,6 +151,9 @@
Header Files
+
+ Header Files
+
Header Files\portability
@@ -183,10 +186,19 @@
Header Files
+
+
+ Header Files
+
+
+ Header Files
Header Files
+
+ Header Files
+
Header Files\portability
@@ -268,7 +280,7 @@
Header Files
-
+
Header Files
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/ConstexprMath.h b/vnext/Folly/TEMP_UntilFollyUpdate/ConstexprMath.h
index 707eb604e8f..eddc4fe39bd 100644
--- a/vnext/Folly/TEMP_UntilFollyUpdate/ConstexprMath.h
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/ConstexprMath.h
@@ -24,6 +24,8 @@
#include
#include
+#include
+#include
namespace folly {
@@ -42,25 +44,25 @@ using enable_if_floating_t =
///
/// mimic: std::numbers::e_v, C++20
template
-FOLLY_INLINE_VARIABLE constexpr T e_v = detail::enable_if_floating_t(
+inline constexpr T e_v = detail::enable_if_floating_t(
2.71828182845904523536028747135266249775724709369995L);
/// ln2_v
///
/// mimic: std::numbers::ln2_v, C++20
template
-FOLLY_INLINE_VARIABLE constexpr T ln2_v = detail::enable_if_floating_t(
+inline constexpr T ln2_v = detail::enable_if_floating_t(
0.69314718055994530941723212145817656807550013436025L);
/// e
///
/// mimic: std::numbers::e, C++20
-FOLLY_INLINE_VARIABLE constexpr double e = e_v;
+inline constexpr double e = e_v;
/// ln2
///
/// mimic: std::numbers::ln2, C++20
-FOLLY_INLINE_VARIABLE constexpr double ln2 = ln2_v;
+inline constexpr double ln2 = ln2_v;
} // namespace numbers
@@ -75,11 +77,6 @@ struct floating_point_integral_constant {
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; }
};
-#if FOLLY_CPLUSPLUS < 201703L
-template
-constexpr typename floating_point_integral_constant::value_type
- floating_point_integral_constant::value;
-#endif
// ----
@@ -105,7 +102,7 @@ constexpr size_t constexpr_iterated_squares_desc_size_(T const base) {
///
/// For use with constexpr_iterated_squares_desc below.
template
-FOLLY_INLINE_VARIABLE constexpr size_t constexpr_iterated_squares_desc_size_v =
+inline constexpr size_t constexpr_iterated_squares_desc_size_v =
detail::constexpr_iterated_squares_desc_size_(Base::value);
/// constexpr_iterated_squares_desc
@@ -225,18 +222,13 @@ struct constexpr_iterated_squares_desc {
return {power, scale};
}
};
-#if FOLLY_CPLUSPLUS < 201703L
-template
-constexpr typename constexpr_iterated_squares_desc::size_type
- constexpr_iterated_squares_desc::size;
-#endif
/// constexpr_iterated_squares_desc_v
///
/// An instance of constexpr_iterated_squares_desc of max size with the given
/// base.
template
-FOLLY_INLINE_VARIABLE constexpr auto constexpr_iterated_squares_desc_v =
+inline constexpr auto constexpr_iterated_squares_desc_v =
constexpr_iterated_squares_desc<
typename Base::value_type,
constexpr_iterated_squares_desc_size_v>{Base::value};
@@ -422,7 +414,7 @@ template
constexpr T constexpr_ceil(T t, T round) {
return round == T(0)
? t
- : ((t + (t < T(0) ? T(0) : round - T(1))) / round) * round;
+ : ((t + (t <= T(0) ? T(0) : round - T(1))) / round) * round;
}
/// constexpr_mult
@@ -765,6 +757,22 @@ constexpr T constexpr_add_overflow_clamped(T a, T b) {
static_assert(
!std::is_integral::value || sizeof(T) <= sizeof(M),
"Integral type too large!");
+ if (!folly::is_constant_evaluated_or(true)) {
+ if constexpr (std::is_integral_v) {
+ T ret{};
+ if (FOLLY_UNLIKELY(!checked_add(&ret, a, b))) {
+ if constexpr (std::is_signed_v) {
+ // Could be either overflow or underflow for signed types.
+ // Can only be underflow if both inputs are negative.
+ if (a < 0 && b < 0) {
+ return L::min();
+ }
+ }
+ return L::max();
+ }
+ return ret;
+ }
+ }
// clang-format off
return
// don't do anything special for non-integral types.
@@ -780,7 +788,7 @@ constexpr T constexpr_add_overflow_clamped(T a, T b) {
// a < 0 && b >= 0, `a + b` will always be in valid range of type T.
!(b < 0) ? a + b :
// a < 0 && b < 0, keep the result >= MIN.
- a + constexpr_max(b, T(L::min() - a));
+ a + constexpr_max(b, T(L::min() - a));
// clang-format on
}
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/Conv.cpp b/vnext/Folly/TEMP_UntilFollyUpdate/Conv.cpp
new file mode 100644
index 00000000000..44b2851409c
--- /dev/null
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/Conv.cpp
@@ -0,0 +1,1205 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+
+#include
+#include
+
+#include
+
+#include // @manual=fbsource//third-party/fast_float:fast_float
+
+namespace folly {
+namespace detail {
+
+namespace {
+
+/**
+ * Finds the first non-digit in a string. The number of digits
+ * searched depends on the precision of the Tgt integral. Assumes the
+ * string starts with NO whitespace and NO sign.
+ *
+ * The semantics of the routine is:
+ * for (;; ++b) {
+ * if (b >= e || !isdigit(*b)) return b;
+ * }
+ *
+ * Complete unrolling marks bottom-line (i.e. entire conversion)
+ * improvements of 20%.
+ */
+inline const char* findFirstNonDigit(const char* b, const char* e) {
+ for (; b < e; ++b) {
+ auto const c = static_cast(*b) - '0';
+ if (c >= 10) {
+ break;
+ }
+ }
+ return b;
+}
+
+// Maximum value of number when represented as a string
+template
+struct MaxString {
+ static const char* const value;
+};
+
+template <>
+const char* const MaxString::value = "255";
+template <>
+const char* const MaxString::value = "65535";
+template <>
+const char* const MaxString::value = "4294967295";
+#if __SIZEOF_LONG__ == 4
+template <>
+const char* const MaxString::value = "4294967295";
+#else
+template <>
+const char* const MaxString::value = "18446744073709551615";
+#endif
+static_assert(
+ sizeof(unsigned long) >= 4,
+ "Wrong value for MaxString::value,"
+ " please update.");
+template <>
+const char* const MaxString::value = "18446744073709551615";
+static_assert(
+ sizeof(unsigned long long) >= 8,
+ "Wrong value for MaxString::value"
+ ", please update.");
+
+#if FOLLY_HAVE_INT128_T
+template <>
+const char* const MaxString<__uint128_t>::value =
+ "340282366920938463463374607431768211455";
+#endif
+
+/*
+* Lookup tables that converts from a decimal character value to an integral
+* binary value, shifted by a decimal "shift" multiplier.
+* For all character values in the range '0'..'9', the table at those
+* index locations returns the actual decimal value shifted by the multiplier.
+* For all other values, the lookup table returns an invalid OOR value.
+*/
+// Out-of-range flag value, larger than the largest value that can fit in
+// four decimal bytes (9999), but four of these added up together should
+// still not overflow uint16_t.
+constexpr int32_t OOR = 10000;
+
+alignas(16) constexpr uint16_t shift1[] = {
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 1, // 40
+ 2, 3, 4, 5, 6, 7, 8, 9, OOR, OOR,
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+ OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift10[] = {
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 10, // 40
+ 20, 30, 40, 50, 60, 70, 80, 90, OOR, OOR,
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+ OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift100[] = {
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 100, // 40
+ 200, 300, 400, 500, 600, 700, 800, 900, OOR, OOR,
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+ OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+alignas(16) constexpr uint16_t shift1000[] = {
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 1000, // 40
+ 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, OOR, OOR,
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230
+ OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240
+ OOR, OOR, OOR, OOR, OOR, OOR // 250
+};
+
+struct ErrorString {
+ const char* string;
+ bool quote;
+};
+
+// Keep this in sync with ConversionCode in Conv.h
+constexpr const std::array<
+ ErrorString,
+ static_cast(ConversionCode::NUM_ERROR_CODES)>
+ kErrorStrings{{
+ {"Success", true},
+ {"Empty input string", true},
+ {"No digits found in input string", true},
+ {"Integer overflow when parsing bool (must be 0 or 1)", true},
+ {"Invalid value for bool", true},
+ {"Non-digit character found", true},
+ {"Invalid leading character", true},
+ {"Overflow during conversion", true},
+ {"Negative overflow during conversion", true},
+ {"Unable to convert string to floating point value", true},
+ {"Non-whitespace character found after end of conversion", true},
+ {"Overflow during arithmetic conversion", false},
+ {"Negative overflow during arithmetic conversion", false},
+ {"Loss of precision during arithmetic conversion", false},
+ }};
+
+// Check if ASCII is really ASCII
+using IsAscii =
+ std::bool_constant<'A' == 65 && 'Z' == 90 && 'a' == 97 && 'z' == 122>;
+
+// The code in this file that uses tolower() really only cares about
+// 7-bit ASCII characters, so we can take a nice shortcut here.
+inline char tolower_ascii(char in) {
+ return IsAscii::value ? in | 0x20 : char(std::tolower(in));
+}
+
+inline bool bool_str_cmp(const char** b, size_t len, const char* value) {
+ // Can't use strncasecmp, since we want to ensure that the full value matches
+ const char* p = *b;
+ const char* e = *b + len;
+ const char* v = value;
+ while (*v != '\0') {
+ if (p == e || tolower_ascii(*p) != *v) { // value is already lowercase
+ return false;
+ }
+ ++p;
+ ++v;
+ }
+
+ *b = p;
+ return true;
+}
+
+} // namespace
+
+Expected str_to_bool(StringPiece* src) noexcept {
+ auto b = src->begin(), e = src->end();
+ for (;; ++b) {
+ if (b >= e) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+ if ((*b < '\t' || *b > '\r') && *b != ' ') {
+ break;
+ }
+ }
+
+ bool result;
+ auto len = size_t(e - b);
+ switch (*b) {
+ case '0':
+ case '1': {
+ result = false;
+ for (; b < e && isdigit(*b); ++b) {
+ if (result || (*b != '0' && *b != '1')) {
+ return makeUnexpected(ConversionCode::BOOL_OVERFLOW);
+ }
+ result = (*b == '1');
+ }
+ break;
+ }
+ case 'y':
+ case 'Y':
+ result = true;
+ if (!bool_str_cmp(&b, len, "yes")) {
+ ++b; // accept the single 'y' character
+ }
+ break;
+ case 'n':
+ case 'N':
+ result = false;
+ if (!bool_str_cmp(&b, len, "no")) {
+ ++b;
+ }
+ break;
+ case 't':
+ case 'T':
+ result = true;
+ if (!bool_str_cmp(&b, len, "true")) {
+ ++b;
+ }
+ break;
+ case 'f':
+ case 'F':
+ result = false;
+ if (!bool_str_cmp(&b, len, "false")) {
+ ++b;
+ }
+ break;
+ case 'o':
+ case 'O':
+ if (bool_str_cmp(&b, len, "on")) {
+ result = true;
+ } else if (bool_str_cmp(&b, len, "off")) {
+ result = false;
+ } else {
+ return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
+ }
+ break;
+ default:
+ return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE);
+ }
+
+ src->assign(b, e);
+
+ return result;
+}
+
+/// Uses `double_conversion` library to convert from string to a floating
+/// point.
+template
+Expected str_to_floating_double_conversion(
+ StringPiece* src) noexcept {
+ using namespace double_conversion;
+ static StringToDoubleConverter conv(
+ StringToDoubleConverter::ALLOW_TRAILING_JUNK |
+ StringToDoubleConverter::ALLOW_LEADING_SPACES,
+ 0.0,
+ // return this for junk input string
+ std::numeric_limits::quiet_NaN(),
+ nullptr,
+ nullptr);
+
+ if (src->empty()) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+
+ int length; // processed char count
+ auto result = std::is_same::value
+ ? conv.StringToFloat(src->data(), static_cast(src->size()), &length)
+ : static_cast(conv.StringToDouble(
+ src->data(), static_cast(src->size()), &length));
+
+ if (!std::isnan(result)) {
+ // If we get here with length = 0, the input string is empty.
+ // If we get here with result = 0.0, it's either because the string
+ // contained only whitespace, or because we had an actual zero value
+ // (with potential trailing junk). If it was only whitespace, we
+ // want to raise an error; length will point past the last character
+ // that was processed, so we need to check if that character was
+ // whitespace or not.
+ if (length == 0 ||
+ (result == 0.0 && std::isspace((*src)[size_t(length) - 1]))) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+ if (length >= 2) {
+ const char* suffix = src->data() + length - 1;
+ // double_conversion doesn't update length correctly when there is an
+ // incomplete exponent specifier. Converting "12e-f-g" shouldn't consume
+ // any more than "12", but it will consume "12e-".
+
+ // "123-" should only parse "123"
+ if (*suffix == '-' || *suffix == '+') {
+ --suffix;
+ --length;
+ }
+ // "12e-f-g" or "12euro" should only parse "12"
+ if (*suffix == 'e' || *suffix == 'E') {
+ --length;
+ }
+ }
+ src->advance(size_t(length));
+ return Tgt(result);
+ }
+
+ auto* e = src->end();
+ auto* b = std::find_if_not(src->begin(), e, [](char c) {
+ return (c >= '\t' && c <= '\r') || c == ' ';
+ });
+ if (b == e) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+ auto size = size_t(e - b);
+
+ bool negative = false;
+ if (*b == '-') {
+ negative = true;
+ ++b;
+ --size;
+ if (size == 0) {
+ return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
+ }
+ }
+ assert(size > 0);
+
+ result = 0.0;
+
+ switch (tolower_ascii(*b)) {
+ case 'i':
+ if (size >= 3 && tolower_ascii(b[1]) == 'n' &&
+ tolower_ascii(b[2]) == 'f') {
+ if (size >= 8 && tolower_ascii(b[3]) == 'i' &&
+ tolower_ascii(b[4]) == 'n' && tolower_ascii(b[5]) == 'i' &&
+ tolower_ascii(b[6]) == 't' && tolower_ascii(b[7]) == 'y') {
+ b += 8;
+ } else {
+ b += 3;
+ }
+ result = std::numeric_limits::infinity();
+ }
+ break;
+
+ case 'n':
+ if (size >= 3 && tolower_ascii(b[1]) == 'a' &&
+ tolower_ascii(b[2]) == 'n') {
+ b += 3;
+ result = std::numeric_limits::quiet_NaN();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (result == 0.0) {
+ // All bets are off
+ return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
+ }
+
+ if (negative) {
+ result = -result;
+ }
+
+ src->assign(b, e);
+
+ return Tgt(result);
+}
+
+/// Uses `fast_float::from_chars` to convert from string to an integer.
+template
+Expected str_to_floating_fast_float_from_chars(
+ StringPiece* src) noexcept {
+ if (src->empty()) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+
+ // move through leading whitespace characters
+ auto* e = src->end();
+ auto* b = std::find_if_not(src->begin(), e, [](char c) {
+ return (c >= '\t' && c <= '\r') || c == ' ';
+ });
+ if (b == e) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+
+ Tgt result;
+ auto [ptr, ec] = fast_float::from_chars(b, e, result);
+ bool isOutOfRange{ec == std::errc::result_out_of_range};
+ bool isOk{ec == std::errc()};
+ if (!isOk && !isOutOfRange) {
+ return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR);
+ }
+
+ auto numMatchedChars = ptr - src->data();
+ src->advance(numMatchedChars);
+
+ if (isOutOfRange) {
+ if (*b == '-') {
+ return -std::numeric_limits::infinity();
+ } else {
+ return std::numeric_limits::infinity();
+ }
+ }
+
+ return result;
+}
+
+template Expected
+str_to_floating_fast_float_from_chars(StringPiece* src) noexcept;
+template Expected
+str_to_floating_fast_float_from_chars(StringPiece* src) noexcept;
+
+/**
+* StringPiece to double, with progress information. Alters the
+* StringPiece parameter to munch the already-parsed characters.
+*/
+template
+Expected str_to_floating(StringPiece* src) noexcept {
+#if defined(FOLLY_CONV_ATOD_MODE) && FOLLY_CONV_ATOD_MODE == 1
+ return detail::str_to_floating_fast_float_from_chars(src);
+#else
+ return detail::str_to_floating_double_conversion(src);
+#endif
+}
+
+template Expected str_to_floating(
+ StringPiece* src) noexcept;
+template Expected str_to_floating(
+ StringPiece* src) noexcept;
+
+namespace {
+
+/**
+* This class takes care of additional processing needed for signed values,
+* like leading sign character and overflow checks.
+*/
+template >
+class SignedValueHandler;
+
+template
+class SignedValueHandler {
+public:
+ ConversionCode init(const char*& b) {
+ negative_ = false;
+ if (!std::isdigit(*b)) {
+ if (*b == '-') {
+ negative_ = true;
+ } else if (FOLLY_UNLIKELY(*b != '+')) {
+ return ConversionCode::INVALID_LEADING_CHAR;
+ }
+ ++b;
+ }
+ return ConversionCode::SUCCESS;
+ }
+
+ ConversionCode overflow() {
+ return negative_ ? ConversionCode::NEGATIVE_OVERFLOW
+ : ConversionCode::POSITIVE_OVERFLOW;
+ }
+
+ template
+ Expected finalize(U value) {
+ T rv;
+ if (negative_) {
+ FOLLY_PUSH_WARNING
+ FOLLY_MSVC_DISABLE_WARNING(4146)
+
+ // unary minus operator applied to unsigned type, result still unsigned
+ rv = T(-value);
+
+ FOLLY_POP_WARNING
+
+ if (FOLLY_UNLIKELY(rv > 0)) {
+ return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW);
+ }
+ } else {
+ rv = T(value);
+ if (FOLLY_UNLIKELY(rv < 0)) {
+ return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW);
+ }
+ }
+ return rv;
+ }
+
+private:
+ bool negative_;
+};
+
+// For unsigned types, we don't need any extra processing
+template
+class SignedValueHandler {
+public:
+ ConversionCode init(const char*&) { return ConversionCode::SUCCESS; }
+
+ ConversionCode overflow() { return ConversionCode::POSITIVE_OVERFLOW; }
+
+ Expected finalize(T value) { return value; }
+};
+
+} // namespace
+
+/**
+* String represented as a pair of pointers to char to signed/unsigned
+* integrals. Assumes NO whitespace before or after, and also that the
+* string is composed entirely of digits (and an optional sign only for
+* signed types). String may be empty, in which case digits_to returns
+* an appropriate error.
+*/
+template
+inline Expected digits_to(
+ const char* b, const char* const e) noexcept {
+ using UT = make_unsigned_t;
+ assert(b <= e);
+
+ SignedValueHandler sgn;
+
+ auto err = sgn.init(b);
+ if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) {
+ return makeUnexpected(err);
+ }
+
+ auto size = size_t(e - b);
+
+ /* Although the string is entirely made of digits, we still need to
+ * check for overflow.
+ */
+ if (size > std::numeric_limits::digits10) {
+ // Leading zeros?
+ if (b < e && *b == '0') {
+ for (++b;; ++b) {
+ if (b == e) {
+ return Tgt(0); // just zeros, e.g. "0000"
+ }
+ if (*b != '0') {
+ size = size_t(e - b);
+ break;
+ }
+ }
+ }
+ if (size > std::numeric_limits::digits10 &&
+ (size != std::numeric_limits::digits10 + 1 ||
+ strncmp(b, MaxString::value, size) > 0)) {
+ return makeUnexpected(sgn.overflow());
+ }
+ }
+
+ // Here we know that the number won't overflow when
+ // converted. Proceed without checks.
+
+ UT result = 0;
+
+ for (; e - b >= 4; b += 4) {
+ result *= UT(10000);
+ const int32_t r0 = shift1000[static_cast(b[0])];
+ const int32_t r1 = shift100[static_cast(b[1])];
+ const int32_t r2 = shift10[static_cast(b[2])];
+ const int32_t r3 = shift1[static_cast(b[3])];
+ const auto sum = r0 + r1 + r2 + r3;
+ if (sum >= OOR) {
+ goto outOfRange;
+ }
+ result += UT(sum);
+ }
+
+ switch (e - b) {
+ case 3: {
+ const int32_t r0 = shift100[static_cast(b[0])];
+ const int32_t r1 = shift10[static_cast(b[1])];
+ const int32_t r2 = shift1[static_cast(b[2])];
+ const auto sum = r0 + r1 + r2;
+ if (sum >= OOR) {
+ goto outOfRange;
+ }
+ result = UT(1000 * result + sum);
+ break;
+ }
+ case 2: {
+ const int32_t r0 = shift10[static_cast(b[0])];
+ const int32_t r1 = shift1[static_cast(b[1])];
+ const auto sum = r0 + r1;
+ if (sum >= OOR) {
+ goto outOfRange;
+ }
+ result = UT(100 * result + sum);
+ break;
+ }
+ case 1: {
+ const int32_t sum = shift1[static_cast(b[0])];
+ if (sum >= OOR) {
+ goto outOfRange;
+ }
+ result = UT(10 * result + sum);
+ break;
+ }
+ default:
+ assert(b == e);
+ if (size == 0) {
+ return makeUnexpected(ConversionCode::NO_DIGITS);
+ }
+ break;
+ }
+
+ return sgn.finalize(result);
+
+outOfRange:
+ return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
+}
+
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected digits_to(
+ const char*, const char*) noexcept;
+
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected digits_to(
+ const char*, const char*) noexcept;
+
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected digits_to(
+ const char*, const char*) noexcept;
+
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected digits_to(
+ const char*, const char*) noexcept;
+
+template Expected digits_to(
+ const char*, const char*) noexcept;
+template Expected
+digits_to(const char*, const char*) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+template Expected<__int128, ConversionCode> digits_to<__int128>(
+ const char*, const char*) noexcept;
+template Expected
+digits_to(const char*, const char*) noexcept;
+#endif
+
+/**
+* StringPiece to integrals, with progress information. Alters the
+* StringPiece parameter to munch the already-parsed characters.
+*/
+template
+Expected str_to_integral(StringPiece* src) noexcept {
+ using UT = make_unsigned_t;
+
+ auto b = src->data(), past = src->data() + src->size();
+
+ for (;; ++b) {
+ if (FOLLY_UNLIKELY(b >= past)) {
+ return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING);
+ }
+ if ((*b < '\t' || *b > '\r') && *b != ' ') {
+ break;
+ }
+ }
+
+ SignedValueHandler sgn;
+ auto err = sgn.init(b);
+
+ if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) {
+ return makeUnexpected(err);
+ }
+ if (is_signed_v && FOLLY_UNLIKELY(b >= past)) {
+ return makeUnexpected(ConversionCode::NO_DIGITS);
+ }
+ if (FOLLY_UNLIKELY(!isdigit(*b))) {
+ return makeUnexpected(ConversionCode::NON_DIGIT_CHAR);
+ }
+
+ auto m = findFirstNonDigit(b + 1, past);
+
+ auto tmp = digits_to(b, m);
+
+ if (FOLLY_UNLIKELY(!tmp.hasValue())) {
+ return makeUnexpected(
+ tmp.error() == ConversionCode::POSITIVE_OVERFLOW ? sgn.overflow()
+ : tmp.error());
+ }
+
+ auto res = sgn.finalize(tmp.value());
+
+ if (res.hasValue()) {
+ src->advance(size_t(m - src->data()));
+ }
+
+ return res;
+}
+
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected
+str_to_integral(StringPiece* src) noexcept;
+
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+
+template Expected str_to_integral(
+ StringPiece* src) noexcept;
+template Expected
+str_to_integral(StringPiece* src) noexcept;
+
+#if FOLLY_HAVE_INT128_T
+template Expected<__int128, ConversionCode> str_to_integral<__int128>(
+ StringPiece* src) noexcept;
+template Expected
+str_to_integral(StringPiece* src) noexcept;
+#endif
+
+#if defined(FOLLY_CONV_AVALIABILITY_TO_CHARS_FLOATING_POINT) && \
+ FOLLY_CONV_AVALIABILITY_TO_CHARS_FLOATING_POINT == 1
+DtoaFlagsSet::DtoaFlagsSet(DtoaFlags flags) : flags_(flags) {}
+
+bool DtoaFlagsSet::isSet(DtoaFlags flag) const {
+ return (flags_ & flag) == flag;
+}
+
+bool DtoaFlagsSet::emitPositiveExponentSign() const {
+ return isSet(DtoaFlags::EMIT_POSITIVE_EXPONENT_SIGN);
+}
+
+bool DtoaFlagsSet::emitTrailingDecimalPoint() const {
+ return isSet(DtoaFlags::EMIT_TRAILING_DECIMAL_POINT);
+}
+
+bool DtoaFlagsSet::emitTrailingZeroAfterPoint() const {
+ return isSet(DtoaFlags::EMIT_TRAILING_ZERO_AFTER_POINT);
+}
+
+bool DtoaFlagsSet::uniqueZero() const {
+ return isSet(DtoaFlags::UNIQUE_ZERO);
+}
+
+bool DtoaFlagsSet::noTrailingZero() const {
+ return isSet(DtoaFlags::NO_TRAILING_ZERO);
+}
+
+int ParsedDecimal::numPrecisionFigures() const {
+ int numInts = 0;
+
+ bool intIsZero = true;
+ int numLeadingIntZeros = 0;
+ bool isLeadingIntZero = true;
+ for (char* p = integerBegin; p && p != integerEnd; p++) {
+ if (*p == '0') {
+ if (isLeadingIntZero) {
+ numLeadingIntZeros += 1;
+ } else {
+ numInts += 1;
+ }
+ } else if (std::isdigit(*p)) {
+ intIsZero = false;
+ isLeadingIntZero = false;
+ numInts += 1;
+ } else {
+ folly::throw_exception("non-numeric int");
+ }
+ }
+
+ bool fractionalIsZero = true;
+ int numFractional = 0;
+ int numLeadingFractionalZeros = 0;
+ bool isLeadingFractionalZero = true;
+ for (char* p = fractionalBegin; p && p != fractionalEnd; p++) {
+ if (*p == '0') {
+ if (isLeadingFractionalZero) {
+ numLeadingFractionalZeros += 1;
+ } else {
+ numFractional += 1;
+ }
+ } else if (std::isdigit(*p)) {
+ fractionalIsZero = false;
+ isLeadingFractionalZero = false;
+ numFractional += 1;
+ } else {
+ folly::throw_exception("non-numeric frac");
+ }
+ }
+
+ if (intIsZero && fractionalIsZero) {
+ return numLeadingIntZeros + numLeadingFractionalZeros;
+ } else if (intIsZero) {
+ return numLeadingFractionalZeros + numFractional;
+ } else if (fractionalIsZero) {
+ return numInts + numLeadingFractionalZeros + numFractional;
+ } else {
+ return numInts + numLeadingFractionalZeros + numFractional;
+ }
+}
+
+std::optional
+ParsedDecimal::fractionalSuffix() const {
+ if (exponentSymbol) {
+ if (exponentEnd) {
+ return std::make_pair(exponentSymbol, exponentEnd);
+ } else if (exponentSign) {
+ return std::make_pair(exponentSymbol, exponentSign);
+ } else {
+ return std::make_pair(exponentSymbol, exponentSymbol + 1);
+ }
+ } else if (exponentSign) {
+ if (exponentEnd) {
+ return std::make_pair(exponentSign, exponentEnd);
+ } else {
+ return std::make_pair(exponentSign, exponentSign + 1);
+ }
+ } else if (exponentBegin) {
+ if (exponentEnd) {
+ return std::make_pair(exponentEnd, exponentEnd);
+ } else {
+ return std::make_pair(exponentBegin, exponentSign + 1);
+ }
+ } else {
+ return std::nullopt;
+ }
+}
+
+void ParsedDecimal::shiftFractionalSuffixPtrs(size_t amount) {
+ if (exponentSymbol) {
+ exponentSymbol += amount;
+ }
+ if (exponentSign) {
+ exponentSign += amount;
+ }
+ if (exponentBegin) {
+ exponentBegin += amount;
+ }
+ if (exponentEnd) {
+ exponentEnd += amount;
+ }
+}
+
+namespace {
+
+struct Stream : std::istream {
+ struct CharBuf : std::streambuf {
+ CharBuf(char* begin, char* end) { setg(begin, begin, end); }
+
+ char* pos() const { return gptr(); }
+ };
+ CharBuf& buf_;
+
+ explicit Stream(CharBuf& buf) : std::istream(&buf), buf_(buf) {}
+
+ char* pos() { return buf_.pos(); }
+
+ void advance() { get(); }
+};
+
+} // namespace
+
+ParsedDecimal::ParsedDecimal(char* begin, char* end) {
+ if (!begin || !end || begin >= end) {
+ folly::throw_exception("invalid args");
+ }
+
+ Stream::CharBuf buf(begin, end);
+ Stream stream(buf);
+ if (stream.peek() == '-') {
+ negativeSign = stream.pos();
+ stream.advance();
+ }
+
+ if (char c = static_cast(stream.peek()); std::isdigit(c)) { // [Windows - fix conversion from 'int' to 'char']
+ integerBegin = stream.pos();
+
+ while (!stream.eof() && std::isdigit(stream.peek())) {
+ stream.advance();
+ }
+
+ integerEnd = stream.pos();
+ }
+
+ if (stream.eof()) {
+ if (!integerBegin) {
+ folly::throw_exception("no int part");
+ }
+
+ return;
+ }
+
+ if (stream.peek() == '.') {
+ decimalPoint = stream.pos();
+ stream.advance();
+ }
+
+ if (stream.eof()) {
+ if (!integerBegin) {
+ folly::throw_exception("no int part");
+ }
+ return;
+ }
+
+ if (char c = static_cast(stream.peek()); std::isdigit(c)) { // [Windows - fix conversion from 'int' to 'char']
+ fractionalBegin = stream.pos();
+
+ while (!stream.eof() && std::isdigit(stream.peek())) {
+ stream.advance();
+ }
+
+ fractionalEnd = stream.pos();
+ }
+
+ if (!integerBegin && !fractionalBegin) {
+ // there was no integer or fractional part.
+ folly::throw_exception("no int or frac part");
+ }
+
+ if (stream.eof()) {
+ return;
+ }
+
+ if (stream.peek() == 'e') {
+ exponentSymbol = stream.pos();
+ stream.advance();
+
+ if (stream.eof()) {
+ return;
+ }
+
+ if (char c = static_cast(stream.peek()); c == '-' || c == '+') { // [Windows - fix conversion from 'int' to 'char']
+ exponentSign = stream.pos();
+ stream.advance();
+ }
+
+ if (char c = static_cast(stream.peek()); std::isdigit(c)) { // [Windows - fix conversion from 'int' to 'char']
+ exponentBegin = stream.pos();
+ while (!stream.eof() && std::isdigit(stream.peek())) {
+ stream.advance();
+ }
+
+ exponentEnd = stream.pos();
+ }
+ }
+
+ while (!stream.eof()) {
+ int c = stream.get();
+ if (c != '\0' && !std::isspace(c)) {
+ folly::throw_exception("unexpected chars");
+ }
+ }
+}
+
+std::pair formatAsDoubleConversion(
+ bool valueIsZero,
+ DtoaMode mode,
+ unsigned int numDigits,
+ DtoaFlags flags,
+ char* resultBegin,
+ char* resultEnd,
+ char* bufferEnd) {
+ detail::ParsedDecimal parsedDecimal(resultBegin, resultEnd);
+ detail::DtoaFlagsSet flagsSet{flags};
+ if (parsedDecimal.negativeSign && flagsSet.uniqueZero() && valueIsZero) {
+ // skip the negative sign (-) if it's a zero and UNIQUE_ZERO is set
+ resultBegin += 1;
+ }
+
+ unsigned int numTrailingZerosToAdd = 0;
+ if (!flagsSet.noTrailingZero() && mode == DtoaMode::PRECISION) {
+ // std::to_chars outputs no trailing zeros, so if it's not set, add
+ // trailing zeros
+ unsigned int numPrecisionFigures = parsedDecimal.numPrecisionFigures();
+ if (numDigits > numPrecisionFigures) {
+ numTrailingZerosToAdd = numDigits - numPrecisionFigures;
+ }
+ }
+
+ bool insertDecimalPoint = false;
+ char* insertionPoint;
+ if (parsedDecimal.fractionalEnd) {
+ insertionPoint = parsedDecimal.fractionalEnd;
+ } else if (parsedDecimal.decimalPoint) {
+ insertionPoint = parsedDecimal.decimalPoint + 1;
+ } else {
+ insertionPoint = parsedDecimal.integerEnd;
+ if (flagsSet.emitTrailingDecimalPoint() || numTrailingZerosToAdd > 0) {
+ insertDecimalPoint = true;
+ }
+
+ if (flagsSet.emitTrailingZeroAfterPoint()) {
+ numTrailingZerosToAdd += 1;
+ }
+ }
+
+ unsigned int numCharsToInsert =
+ numTrailingZerosToAdd + (insertDecimalPoint ? 1 : 0);
+
+ if (numCharsToInsert > 0) {
+ if (resultEnd + numCharsToInsert > bufferEnd) {
+ folly::throw_exception("buffer too small");
+ }
+
+ std::optional fractionalsuffix =
+ parsedDecimal.fractionalSuffix();
+ if (fractionalsuffix.has_value()) {
+ auto [fractionalSuffixBegin, fractionalSuffixEnd] = *fractionalsuffix;
+ std::memmove(
+ insertionPoint + numCharsToInsert,
+ fractionalSuffixBegin,
+ fractionalSuffixEnd - fractionalSuffixBegin);
+ parsedDecimal.shiftFractionalSuffixPtrs(numCharsToInsert);
+ }
+
+ resultEnd += numCharsToInsert;
+ }
+
+ if (insertDecimalPoint) {
+ *insertionPoint++ = '.';
+ }
+
+ while (numTrailingZerosToAdd) {
+ *insertionPoint++ = '0';
+ numTrailingZerosToAdd -= 1;
+ }
+
+ if (parsedDecimal.exponentSymbol) {
+ // std::tochars outputs a lowercase e and it needs to be uppercase.
+ *parsedDecimal.exponentSymbol = 'E';
+ }
+
+ size_t charsToRemove = 0;
+ char* removalBegin = nullptr;
+ if (!flagsSet.emitPositiveExponentSign() && parsedDecimal.exponentSign &&
+ *parsedDecimal.exponentSign == '+') {
+ // std::to_chars outputs a + sign, remove it if the flag wasn't set.
+ // e.g., 1.23e+45 -> 1.23e45
+ removalBegin = parsedDecimal.exponentSign;
+ charsToRemove += 1;
+ }
+
+ if (char* p = parsedDecimal.exponentBegin; p && *p == '0') {
+ // std::to_chars outputs a leading zero, remove it to match
+ // double_conversion formating. e.g., 1.23e+04 -> 1.23e4
+ if (!removalBegin) {
+ removalBegin = p;
+ }
+
+ while (p != parsedDecimal.exponentEnd && *p == '0') {
+ charsToRemove += 1;
+ p += 1;
+ }
+
+ if (p == parsedDecimal.exponentEnd) {
+ // they all were 0 digits. keep a single 0.
+ charsToRemove -= 1;
+ p -= 1;
+ if (p == removalBegin) {
+ // there was only one 0, keep it.
+ removalBegin = nullptr;
+ }
+ }
+ }
+
+ if (charsToRemove && removalBegin) {
+ size_t len = resultEnd - (removalBegin + charsToRemove);
+ std::memmove(removalBegin, removalBegin + charsToRemove, len);
+ resultEnd -= charsToRemove;
+ }
+
+ return std::pair{resultBegin, resultEnd};
+}
+#endif // FOLLY_CONV_AVALIABILITY_TO_CHARS_FLOATING_POINT
+} // namespace detail
+
+ConversionError makeConversionError(ConversionCode code, StringPiece input) {
+ using namespace detail;
+ static_assert(
+ std::is_unsigned::type>::value,
+ "ConversionCode should be unsigned");
+ auto index = static_cast(code);
+ FOLLY_SAFE_CHECK(index < kErrorStrings.size(), "code=", uint64_t(index));
+ const ErrorString& err = kErrorStrings[index];
+ if (code == ConversionCode::EMPTY_INPUT_STRING && input.empty()) {
+ return {err.string, code};
+ }
+ std::string tmp(err.string);
+ tmp.append(": ");
+ if (err.quote) {
+ tmp.append(1, '"');
+ }
+ if (!input.empty()) {
+ tmp.append(input.data(), input.size());
+ }
+ if (err.quote) {
+ tmp.append(1, '"');
+ }
+ return {tmp, code};
+}
+
+} // namespace folly
\ No newline at end of file
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/chrono/Hardware.h b/vnext/Folly/TEMP_UntilFollyUpdate/chrono/Hardware.h
new file mode 100644
index 00000000000..ff93f2d6c5f
--- /dev/null
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/chrono/Hardware.h
@@ -0,0 +1,155 @@
+/*
+* Copyright (c) Meta Platforms, Inc. and affiliates.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#pragma once
+
+#include
+
+#include
+#include
+
+// Description of and implementation of sequences for precise measurement:
+// https://github.com/abseil/abseil-cpp/blob/20240116.2/absl/random/internal/nanobenchmark.cc#L164-L277
+
+#if defined(_MSC_VER) && !defined(__clang__) && \
+(defined(_M_IX86) || defined(_M_X64)) && !defined(_M_ARM64EC) // [Windows]
+extern "C" std::uint64_t __rdtsc();
+extern "C" std::uint64_t __rdtscp(unsigned int*);
+extern "C" void _ReadWriteBarrier();
+extern "C" void _mm_lfence();
+#pragma intrinsic(__rdtsc)
+#pragma intrinsic(__rdtscp)
+#pragma intrinsic(_ReadWriteBarrier)
+#pragma intrinsic(_mm_lfence)
+#endif
+
+namespace folly {
+
+inline std::uint64_t hardware_timestamp() {
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+ return __rdtsc();
+#elif defined(__GNUC__) && (defined(__i386__) || FOLLY_X64)
+ return __builtin_ia32_rdtsc();
+#elif FOLLY_AARCH64 && !FOLLY_MOBILE
+ uint64_t cval;
+ asm volatile("mrs %0, cntvct_el0" : "=r"(cval));
+ return cval;
+#else
+ // use steady_clock::now() as an approximation for the timestamp counter on
+ // non-x86 systems
+ return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+/// hardware_timestamp_measurement_start
+/// hardware_timestamp_measurement_stop
+///
+/// Suitable for beginning precise measurement of a region of code.
+///
+/// Prevents the compiler from reordering instructions across the call. This is
+/// in contrast with hardware_timestamp(), which may not prevent the compiler
+/// from reordering instructions across the call.
+///
+/// Prevents the processor from pipelining the call with loads. This is in
+/// contrast with hardware_timestamp(), which allows the processor to pipeline
+/// the call with surrounding loads.
+///
+/// Does not prevent instruction pipelines from continuing execution across the
+/// call. For example, does not prevent a store issued before the call from
+/// continuing execution (becoming globally visible) across the call. This means
+/// that this form may not be suitable for certain measurement use-cases which
+/// would require such prevention. However, this would be suitable for typical
+/// measurement use-cases which do not specifically need to measure background
+/// work such as store execution.
+std::uint64_t hardware_timestamp_measurement_start() noexcept;
+std::uint64_t hardware_timestamp_measurement_stop() noexcept;
+
+inline std::uint64_t hardware_timestamp_measurement_start() noexcept {
+#if defined(_MSC_VER) && !defined(__clang__) && \
+ (defined(_M_IX86) || defined(_M_X64))
+ // msvc does not have embedded assembly
+ _ReadWriteBarrier();
+ _mm_lfence();
+ _ReadWriteBarrier();
+ auto const ret = __rdtsc();
+ _ReadWriteBarrier();
+ _mm_lfence();
+ _ReadWriteBarrier();
+ return ret;
+#elif defined(__GNUC__) && FOLLY_X64
+ uint64_t ret = 0;
+#if !defined(__clang_major__) || __clang_major__ >= 11
+ asm volatile inline(
+#else
+ asm volatile(
+#endif
+ "lfence\n"
+ "rdtsc\n" // loads 64-bit tsc into edx:eax
+ "shl $32, %%rdx\n" // prep rdx for combine into rax
+ "or %%rdx, %[ret]\n" // combine rdx into rax
+ "lfence\n"
+ : [ret] "=a"(ret) // bind ret to rax for output
+ : // no inputs
+ : "rdx", // rdtsc loads into edx:eax
+ "cc", // shl clobbers condition-code
+ "memory" // memory clobber asks gcc/clang not to reorder
+ );
+ return ret;
+#else
+ // use steady_clock::now() as an approximation for the timestamp counter on
+ // non-x64 systems
+ return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+inline std::uint64_t hardware_timestamp_measurement_stop() noexcept {
+#if defined(_MSC_VER) && !defined(__clang__) && \
+ (defined(_M_IX86) || defined(_M_X64))
+ // msvc does not have embedded assembly
+ _ReadWriteBarrier();
+ unsigned int aux;
+ auto const ret = __rdtscp(&aux);
+ _ReadWriteBarrier();
+ _mm_lfence();
+ _ReadWriteBarrier();
+ return ret;
+#elif defined(__GNUC__) && FOLLY_X64
+ uint64_t ret = 0;
+#if !defined(__clang_major__) || __clang_major__ >= 11
+ asm volatile inline(
+#else
+ asm volatile(
+#endif
+ "rdtscp\n" // loads 64-bit tsc into edx:eax, clobbers ecx
+ "shl $32, %%rdx\n" // prep rdx for combine into rax
+ "or %%rdx, %[ret]\n" // combine rdx into rax
+ "lfence\n"
+ : [ret] "=a"(ret) // bind ret to rax for output
+ : // no inputs
+ : "rdx", // rdtscp loads into edx:eax
+ "rcx", // rdtscp clobbers rcx
+ "cc", // shl clobbers condition-code
+ "memory" // memory clobber asks gcc/clang not to reorder
+ );
+ return ret;
+#else
+ // use steady_clock::now() as an approximation for the timestamp counter on
+ // non-x64 systems
+ return std::chrono::steady_clock::now().time_since_epoch().count();
+#endif
+}
+
+} // namespace folly
\ No newline at end of file
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/concurrency/CacheLocality.cpp b/vnext/Folly/TEMP_UntilFollyUpdate/concurrency/CacheLocality.cpp
new file mode 100644
index 00000000000..ab91cbab19e
--- /dev/null
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/concurrency/CacheLocality.cpp
@@ -0,0 +1,633 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+
+#ifndef _MSC_VER
+#define _GNU_SOURCE 1 // for RTLD_NOLOAD
+#include
+#endif
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace folly {
+
+///////////// CacheLocality
+
+/// Returns the CacheLocality information best for this machine
+static CacheLocality getSystemLocalityInfo() {
+ if (kIsLinux) {
+ // First try to parse /proc/cpuinfo.
+ // If that fails, then try to parse /sys/devices/.
+ // The latter is slower but more accurate.
+ try {
+ return CacheLocality::readFromProcCpuinfo();
+ } catch (...) {
+ // /proc/cpuinfo might be non-standard
+ // lets try with sysfs /sys/devices/cpu
+ }
+
+ try {
+ return CacheLocality::readFromSysfs();
+ } catch (...) {
+ // keep trying
+ }
+ }
+
+ long numCpus = sysconf(_SC_NPROCESSORS_CONF);
+ if (numCpus <= 0) {
+ // This shouldn't happen, but if it does we should try to keep
+ // going. We are probably not going to be able to parse /sys on
+ // this box either (although we will try), which means we are going
+ // to fall back to the SequentialThreadId splitter. On my 16 core
+ // (x hyperthreading) dev box 16 stripes is enough to get pretty good
+ // contention avoidance with SequentialThreadId, and there is little
+ // improvement from going from 32 to 64. This default gives us some
+ // wiggle room
+ numCpus = 32;
+ }
+ return CacheLocality::uniform(size_t(numCpus));
+}
+
+template <>
+const CacheLocality& CacheLocality::system() {
+ static std::atomic cache;
+ auto value = cache.load(std::memory_order_acquire);
+ if (value != nullptr) {
+ return *value;
+ }
+ auto next = new CacheLocality(getSystemLocalityInfo());
+ if (cache.compare_exchange_strong(value, next, std::memory_order_acq_rel)) {
+ return *next;
+ }
+ delete next;
+ return *value;
+}
+
+CacheLocality::CacheLocality(std::vector> equivClasses) {
+ numCpus = equivClasses.size();
+
+ for (size_t cpu = 0; cpu < numCpus; ++cpu) {
+ for (size_t level = 0; level < equivClasses[cpu].size(); ++level) {
+ if (equivClasses[cpu][level] == cpu) {
+ // we only want to count the equiv classes once, so we do it when we
+ // are processing their representative.
+ while (numCachesByLevel.size() <= level) {
+ numCachesByLevel.push_back(0);
+ }
+ numCachesByLevel[level]++;
+ }
+ }
+ }
+
+ std::vector cpus(numCpus);
+ std::iota(cpus.begin(), cpus.end(), 0);
+
+ std::sort(cpus.begin(), cpus.end(), [&](size_t lhs, size_t rhs) -> bool {
+ auto& lhsEquiv = equivClasses[lhs];
+ auto& rhsEquiv = equivClasses[rhs];
+
+ // If different cpus have different numbers of caches group first by number
+ // of caches to guarantee strict weak ordering, even though the resulting
+ // order may be sub-optimal.
+ if (lhsEquiv.size() != rhsEquiv.size()) {
+ return lhsEquiv.size() < rhsEquiv.size();
+ }
+
+ // Order by equiv class of cache with highest index, direction doesn't
+ // matter.
+ for (size_t i = lhsEquiv.size(); i > 0; --i) {
+ auto idx = i - 1;
+ if (lhsEquiv[idx] != rhsEquiv[idx]) {
+ return lhsEquiv[idx] < rhsEquiv[idx];
+ }
+ }
+
+ // Break ties deterministically by cpu.
+ return lhs < rhs;
+ });
+
+ // The cpus are now sorted by locality, with neighboring entries closer
+ // to each other than entries that are far away. For striping we want
+ // the inverse map, since we are starting with the cpu.
+ localityIndexByCpu.resize(numCpus);
+ for (size_t i = 0; i < cpus.size(); ++i) {
+ localityIndexByCpu[cpus[i]] = i;
+ }
+
+ equivClassesByCpu = std::move(equivClasses);
+}
+
+// Each level of cache has sharing sets, which are the set of cpus that share a
+// common cache at that level. These are available in a hex bitset form
+// (/sys/devices/system/cpu/cpu0/cache/index0/shared_cpu_map, for example).
+// They are also available in human-readable form in the shared_cpu_list file in
+// the same directory. The list is a comma-separated list of numbers and
+// ranges, where the ranges are pairs of decimal numbers separated by a '-'.
+//
+// To sort the cpus for optimum locality we don't really need to parse the
+// sharing sets, we just need a unique representative from the equivalence
+// class. The smallest value works fine, and happens to be the first decimal
+// number in the file. We load all of the equivalence class information from
+// all of the cpu*/index* directories, order the cpus first by increasing
+// last-level cache equivalence class, then by the smaller caches. Finally, we
+// break ties with the cpu number itself.
+
+/// Returns the first decimal number in the line, or throws an exception if the
+/// line does not start with a number terminated by ',', '-', '\n', or EOS.
+static size_t parseLeadingNumber(const std::string& line) {
+ auto raw = line.c_str();
+ char* end;
+ unsigned long val = strtoul(raw, &end, 10);
+ if (end == raw || (*end != ',' && *end != '-' && *end != '\n' && *end != 0)) {
+ throw std::runtime_error(fmt::format("error parsing list '{}'", line));
+ }
+ return val;
+}
+
+CacheLocality CacheLocality::readFromSysfsTree(
+ const std::function& mapping) {
+ // the list of cache equivalence classes, where equivalence classes
+ // are named by the smallest cpu in the class
+ std::vector> equivClassesByCpu;
+
+ for (size_t cpu = 0;; ++cpu) {
+ std::vector levels;
+ for (size_t index = 0;; ++index) {
+ auto dir = fmt::format(
+ "/sys/devices/system/cpu/cpu{}/cache/index{}/", cpu, index);
+ auto cacheType = mapping(dir + "type");
+ auto equivStr = mapping(dir + "shared_cpu_list");
+ if (cacheType.empty() || equivStr.empty()) {
+ // no more caches
+ break;
+ }
+ if (cacheType[0] == 'I') {
+ // cacheType in { "Data", "Instruction", "Unified" }. skip icache
+ continue;
+ }
+ auto equiv = parseLeadingNumber(equivStr);
+ levels.push_back(equiv);
+ }
+
+ if (levels.empty()) {
+ // no levels at all for this cpu, we must be done
+ break;
+ }
+ equivClassesByCpu.emplace_back(std::move(levels));
+ }
+
+ if (equivClassesByCpu.empty()) {
+ throw std::runtime_error("unable to load cache sharing info");
+ }
+
+ return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+CacheLocality CacheLocality::readFromSysfs() {
+ return readFromSysfsTree([](std::string const& name) {
+ std::ifstream xi(name.c_str());
+ std::string rv;
+ std::getline(xi, rv);
+ return rv;
+ });
+}
+
+namespace {
+
+static bool procCpuinfoLineRelevant(std::string const& line) {
+ return line.size() > 4 && (line[0] == 'p' || line[0] == 'c');
+}
+
+std::vector> parseProcCpuinfoLines(
+ std::vector const& lines) {
+ std::vector> cpus;
+ size_t physicalId = 0;
+ size_t coreId = 0;
+ size_t maxCpu = 0;
+ size_t numberOfPhysicalIds = 0;
+ size_t numberOfCoreIds = 0;
+ for (auto iter = lines.rbegin(); iter != lines.rend(); ++iter) {
+ auto& line = *iter;
+ if (!procCpuinfoLineRelevant(line)) {
+ continue;
+ }
+
+ auto sepIndex = line.find(':');
+ if (sepIndex == std::string::npos || sepIndex + 2 > line.size()) {
+ continue;
+ }
+ auto arg = line.substr(sepIndex + 2);
+
+ // "physical id" is socket, which is the most important locality
+ // context. "core id" is a real core, so two "processor" entries with
+ // the same physical id and core id are hyperthreads of each other.
+ // "processor" is the top line of each record, so when we hit it in
+ // the reverse order then we can emit a record.
+ if (line.find("physical id") == 0) {
+ physicalId = parseLeadingNumber(arg);
+ ++numberOfPhysicalIds;
+ } else if (line.find("core id") == 0) {
+ coreId = parseLeadingNumber(arg);
+ ++numberOfCoreIds;
+ } else if (line.find("processor") == 0) {
+ auto cpu = parseLeadingNumber(arg);
+ maxCpu = std::max(cpu, maxCpu);
+ cpus.emplace_back(physicalId, coreId, cpu);
+ }
+ }
+
+ if (cpus.empty()) {
+ throw std::runtime_error("no CPUs parsed from /proc/cpuinfo");
+ }
+ if (maxCpu != cpus.size() - 1) {
+ throw std::runtime_error(
+ "offline CPUs not supported for /proc/cpuinfo cache locality source");
+ }
+ if (numberOfPhysicalIds == 0) {
+ throw std::runtime_error("no physical ids found");
+ }
+ if (numberOfCoreIds == 0) {
+ throw std::runtime_error("no core ids found");
+ }
+
+ return cpus;
+}
+
+} // namespace
+
+CacheLocality CacheLocality::readFromProcCpuinfoLines(
+ std::vector const& lines) {
+ // (physicalId, coreId, cpu)
+ std::vector> cpus =
+ parseProcCpuinfoLines(lines);
+ // Sort to make equivalence classes contiguous.
+ std::sort(cpus.begin(), cpus.end());
+
+ // We can't tell the real cache hierarchy from /proc/cpuinfo, but it works
+ // well enough to assume there are 3 levels, L1 and L2 per-core and L3 per
+ // socket.
+ std::vector> equivClassesByCpu(cpus.size());
+ size_t l1Equiv = 0;
+ size_t l3Equiv = 0;
+ for (size_t i = 0; i < cpus.size(); ++i) {
+ auto [physicalId, coreId, cpu] = cpus[i];
+ // The representative for each L1 and L3 equivalence class is the first cpu
+ // in the class.
+ if (i == 0 || physicalId != std::get<0>(cpus[i - 1]) ||
+ coreId != std::get<1>(cpus[i - 1])) {
+ l1Equiv = cpu;
+ }
+ if (i == 0 || physicalId != std::get<0>(cpus[i - 1])) {
+ l3Equiv = cpu;
+ }
+ equivClassesByCpu[cpu] = {l1Equiv, l1Equiv, l3Equiv};
+ }
+
+ return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+CacheLocality CacheLocality::readFromProcCpuinfo() {
+ std::vector lines;
+ {
+ std::ifstream xi("/proc/cpuinfo");
+ if (xi.fail()) {
+ throw std::runtime_error("unable to open /proc/cpuinfo");
+ }
+ char buf[8192];
+ while (xi.good() && lines.size() < 20000) {
+ xi.getline(buf, sizeof(buf));
+ std::string str(buf);
+ if (procCpuinfoLineRelevant(str)) {
+ lines.emplace_back(std::move(str));
+ }
+ }
+ }
+ return readFromProcCpuinfoLines(lines);
+}
+
+CacheLocality CacheLocality::uniform(size_t numCpus) {
+ // One cache shared by all cpus.
+ std::vector> equivClassesByCpu(numCpus, {0});
+ return CacheLocality{std::move(equivClassesByCpu)};
+}
+
+////////////// Getcpu
+
+Getcpu::Func Getcpu::resolveVdsoFunc() {
+#if !defined(FOLLY_HAVE_LINUX_VDSO) || defined(FOLLY_SANITIZE_MEMORY)
+ return nullptr;
+#else
+ void* h = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
+ if (h == nullptr) {
+ return nullptr;
+ }
+
+ auto func = Getcpu::Func(dlsym(h, "__vdso_getcpu"));
+ if (func == nullptr) {
+ // technically a null result could either be a failure or a successful
+ // lookup of a symbol with the null value, but the second can't actually
+ // happen for this symbol. No point holding the handle forever if
+ // we don't need the code
+ dlclose(h);
+ }
+
+ return func;
+#endif
+}
+
+/////////////// SequentialThreadId
+unsigned SequentialThreadId::get() {
+ static std::atomic global{0};
+ static thread_local unsigned local{0};
+ return FOLLY_LIKELY(local) ? local : (local = ++global);
+}
+
+/////////////// HashingThreadId
+unsigned HashingThreadId::get() {
+ return hash::twang_32from64(getCurrentThreadID());
+}
+
+namespace detail {
+
+int AccessSpreaderBase::degenerateGetcpu(unsigned* cpu, unsigned* node, void*) {
+ if (cpu != nullptr) {
+ *cpu = 0;
+ }
+ if (node != nullptr) {
+ *node = 0;
+ }
+ return 0;
+}
+
+struct AccessSpreaderStaticInit {
+ static AccessSpreaderStaticInit instance;
+ AccessSpreaderStaticInit() { (void)AccessSpreader<>::current(~size_t(0)); }
+};
+AccessSpreaderStaticInit AccessSpreaderStaticInit::instance;
+
+bool AccessSpreaderBase::initialize(
+ GlobalState& state,
+ Getcpu::Func (&pickGetcpuFunc)(),
+ const CacheLocality& (&system)()) {
+ (void)AccessSpreaderStaticInit::instance; // ODR-use it so it is not dropped
+ constexpr auto relaxed = std::memory_order_relaxed;
+ auto& cacheLocality = system();
+ auto n = cacheLocality.numCpus;
+ for (size_t width = 0; width <= kMaxCpus; ++width) {
+ auto& row = state.table[width];
+ auto numStripes = std::max(size_t{1}, width);
+ for (size_t cpu = 0; cpu < kMaxCpus && cpu < n; ++cpu) {
+ auto index = cacheLocality.localityIndexByCpu[cpu];
+ assert(index < n);
+ // as index goes from 0..n, post-transform value goes from
+ // 0..numStripes
+ make_atomic_ref(row[cpu]).store(
+ static_cast((index * numStripes) / n), relaxed);
+ assert(make_atomic_ref(row[cpu]).load(relaxed) < numStripes);
+ }
+ size_t filled = n;
+ while (filled < kMaxCpus) {
+ size_t len = std::min(filled, kMaxCpus - filled);
+ for (size_t i = 0; i < len; ++i) {
+ make_atomic_ref(row[filled + i])
+ .store(make_atomic_ref(row[i]).load(relaxed), relaxed);
+ }
+ filled += len;
+ }
+ for (size_t cpu = n; cpu < kMaxCpus; ++cpu) {
+ assert(
+ make_atomic_ref(row[cpu]).load(relaxed) ==
+ make_atomic_ref(row[cpu - n]).load(relaxed));
+ }
+ }
+ state.getcpu.exchange(pickGetcpuFunc(), std::memory_order_acq_rel);
+ return true;
+}
+
+} // namespace detail
+
+namespace {
+
+/**
+* A simple freelist allocator. Allocates things of size sz, from slabs of size
+* kAllocSize. Takes a lock on each allocation/deallocation.
+*/
+class SimpleAllocator {
+public:
+ // To support array aggregate initialization without an implicit constructor.
+ struct Ctor {};
+
+ SimpleAllocator(Ctor, size_t sz) : sz_(sz) {}
+ ~SimpleAllocator() {
+ std::lock_guard g(m_);
+ for (auto& block : blocks_) {
+ folly::aligned_free(block);
+ }
+ }
+
+ void* allocate() {
+ std::lock_guard g(m_);
+ // Freelist allocation.
+ if (freelist_) {
+ auto mem = freelist_;
+ freelist_ = *static_cast(freelist_);
+ return mem;
+ }
+
+ if (mem_) {
+ // Bump-ptr allocation.
+ if (intptr_t(mem_) % kMallocAlign == 0) {
+ // Avoid allocating pointers that may look like malloc
+ // pointers.
+ mem_ += std::min(sz_, max_align_v);
+ }
+ if (mem_ + sz_ <= end_) {
+ auto mem = mem_;
+ mem_ += sz_;
+
+ assert(intptr_t(mem) % kMallocAlign != 0);
+ return mem;
+ }
+ }
+
+ return allocateHard();
+ }
+
+ static void deallocate(void* ptr) {
+ assert(intptr_t(ptr) % kMallocAlign != 0);
+ // Find the allocator instance.
+ auto addr =
+ reinterpret_cast(intptr_t(ptr) & ~intptr_t(kAllocSize - 1));
+ auto allocator = *static_cast(addr);
+
+ std::lock_guard g(allocator->m_);
+ *static_cast(ptr) = allocator->freelist_;
+ if constexpr (kIsSanitizeAddress) {
+ // If running under ASAN, scrub the memory on deallocation, so we don't
+ // leave pointers that could hide leaks at shutdown, since the backing
+ // slabs may not be deallocated if the instance is a leaky singleton.
+ auto* base = static_cast(ptr);
+ std::fill(
+ base + sizeof(void*), base + allocator->sz_, static_cast(0));
+ }
+ allocator->freelist_ = ptr;
+ }
+
+ constexpr static size_t kMallocAlign = 128;
+ static_assert(
+ kMallocAlign % hardware_destructive_interference_size == 0,
+ "Large allocations should be cacheline-aligned");
+
+private:
+ constexpr static size_t kAllocSize = 4096;
+
+ void* allocateHard() {
+ // Allocate a new slab.
+ mem_ = static_cast(folly::aligned_malloc(kAllocSize, kAllocSize));
+ if (!mem_) {
+ throw_exception();
+ }
+ end_ = mem_ + kAllocSize;
+ blocks_.push_back(mem_);
+
+ // Install a pointer to ourselves as the allocator.
+ *reinterpret_cast(mem_) = this;
+ static_assert(
+ max_align_v >= sizeof(SimpleAllocator*), "alignment too small");
+ mem_ += std::min(sz_, max_align_v);
+
+ // New allocation.
+ auto mem = mem_;
+ mem_ += sz_;
+ assert(intptr_t(mem) % kMallocAlign != 0);
+ return mem;
+ }
+
+ std::mutex m_;
+ uint8_t* mem_{nullptr};
+ uint8_t* end_{nullptr};
+ void* freelist_{nullptr};
+ size_t sz_;
+ std::vector blocks_;
+};
+
+class Allocator {
+public:
+ void* allocate(size_t size) {
+ if (auto cl = sizeClass(size)) {
+ return allocators_[*cl].allocate();
+ }
+
+ // Fall back to malloc, returning a kMallocAlign-aligned allocation so it
+ // can be distinguished from SimpleAllocator allocations.
+ size = size + (SimpleAllocator::kMallocAlign - 1);
+ size &= ~size_t(SimpleAllocator::kMallocAlign - 1);
+ void* mem = aligned_malloc(size, SimpleAllocator::kMallocAlign);
+ if (!mem) {
+ throw_exception();
+ }
+ return mem;
+ }
+
+ static void deallocate(void* ptr) {
+ if (!ptr) {
+ return;
+ }
+
+ // See if it came from SimpleAllocator or malloc.
+ if (intptr_t(ptr) % SimpleAllocator::kMallocAlign != 0) {
+ SimpleAllocator::deallocate(ptr);
+ } else {
+ aligned_free(ptr);
+ }
+ }
+
+private:
+ std::optional sizeClass(size_t size) {
+ if (size <= 8) {
+ return static_cast(0); // [Windows]
+ } else if (size <= 16) {
+ return static_cast(1); // [Windows]
+ } else if (size <= 32) {
+ return static_cast(2); // [Windows]
+ } else if (size <= 64) {
+ return static_cast(3); // [Windows]
+ } else {
+ return std::nullopt;
+ }
+ }
+
+ std::array allocators_{
+ {{SimpleAllocator::Ctor{}, 8},
+ {SimpleAllocator::Ctor{}, 16},
+ {SimpleAllocator::Ctor{}, 32},
+ {SimpleAllocator::Ctor{}, 64}}};
+};
+
+} // namespace
+
+void* coreMalloc(size_t size, size_t numStripes, size_t stripe) {
+ static folly::Indestructible
+ allocators[AccessSpreader<>::maxLocalityIndexValue()];
+ auto index = AccessSpreader<>::localityIndexForStripe(numStripes, stripe);
+ return allocators[index]->allocate(size);
+}
+
+void coreFree(void* ptr) {
+ Allocator::deallocate(ptr);
+}
+
+namespace {
+thread_local CoreAllocatorGuard* gCoreAllocatorGuard = nullptr;
+}
+
+CoreAllocatorGuard::CoreAllocatorGuard(size_t numStripes, size_t stripe)
+ : numStripes_(numStripes), stripe_(stripe) {
+ CHECK(gCoreAllocatorGuard == nullptr)
+ << "CoreAllocator::Guard cannot be used recursively";
+ gCoreAllocatorGuard = this;
+}
+
+CoreAllocatorGuard::~CoreAllocatorGuard() {
+ gCoreAllocatorGuard = nullptr;
+}
+
+namespace detail {
+
+void* coreMallocFromGuard(size_t size) {
+ CHECK(gCoreAllocatorGuard != nullptr)
+ << "CoreAllocator::allocator called without an active Guard";
+ return coreMalloc(
+ size, gCoreAllocatorGuard->numStripes_, gCoreAllocatorGuard->stripe_);
+}
+
+} // namespace detail
+
+} // namespace folly
\ No newline at end of file
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/dynamic-inl.h b/vnext/Folly/TEMP_UntilFollyUpdate/json/dynamic-inl.h
similarity index 99%
rename from vnext/Folly/TEMP_UntilFollyUpdate/dynamic-inl.h
rename to vnext/Folly/TEMP_UntilFollyUpdate/json/dynamic-inl.h
index 4495e2ac88b..ee000cdd7de 100644
--- a/vnext/Folly/TEMP_UntilFollyUpdate/dynamic-inl.h
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/json/dynamic-inl.h
@@ -268,8 +268,6 @@ struct dynamic::const_item_iterator
const_item_iterator() = default;
/* implicit */ const_item_iterator(dynamic::ObjectImpl::const_iterator b)
: Super(b) {}
- /* implicit */ const_item_iterator(const_item_iterator const& i)
- : Super(i.base()) {}
/* implicit */ const_item_iterator(item_iterator i) : Super(i.base()) {}
using object_type = dynamic::ObjectImpl const;
@@ -389,7 +387,8 @@ dynamic::dynamic(T t) {
}
template
-dynamic::dynamic(Iterator first, Iterator last) : type_(ARRAY) {
+dynamic::dynamic(array_range_construct_t, Iterator first, Iterator last)
+ : type_(ARRAY) {
new (&u_.array) Array(first, last);
}
@@ -1436,4 +1435,4 @@ class FormatValue> {
} // namespace folly
-#undef FB_DYNAMIC_APPLY
+#undef FB_DYNAMIC_APPLY
\ No newline at end of file
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/json.cpp b/vnext/Folly/TEMP_UntilFollyUpdate/json/json.cpp
similarity index 98%
rename from vnext/Folly/TEMP_UntilFollyUpdate/json.cpp
rename to vnext/Folly/TEMP_UntilFollyUpdate/json/json.cpp
index e6a1718af01..b8696322386 100644
--- a/vnext/Folly/TEMP_UntilFollyUpdate/json.cpp
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/json/json.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include
+#include
#include
#include
@@ -28,7 +28,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -104,7 +103,7 @@ struct Printer {
}
void operator()(dynamic const& v, const Context* context) const {
switch (v.type()) {
- case dynamic::DOUBLE:
+ case dynamic::DOUBLE: {
if (!opts_.allow_nan_inf) {
if (std::isnan(v.asDouble())) {
throw json::print_error(
@@ -120,10 +119,11 @@ struct Printer {
toAppend(
v.asDouble(),
&out_,
- opts_.double_mode,
+ opts_.dtoa_mode,
opts_.double_num_digits,
- opts_.double_flags);
+ opts_.dtoa_flags);
break;
+ }
case dynamic::INT64: {
auto intval = v.asInt();
if (opts_.javascript_safe) {
@@ -409,9 +409,13 @@ struct Input {
return range_.subpiece(0, 16 /* arbitrary */).toString();
}
- [[noreturn]] dynamic error(char const* what) const {
+ [[noreturn]] void error(char const* what) const {
throw json::make_parse_error(lineNum_, context(), what);
}
+ template
+ R error(char const* what) const {
+ error(what);
+ }
json::serialization_opts const& getOpts() { return opts_; }
@@ -620,7 +624,7 @@ void decodeUnicodeEscape(Input& in, std::string& out) {
c >= '0' && c <= '9' ? c - '0' :
c >= 'a' && c <= 'f' ? c - 'a' + 10 :
c >= 'A' && c <= 'F' ? c - 'A' + 10 :
- (in.error("invalid hex digit"), 0));
+ in.error("invalid hex digit"));
// clang-format on
};
@@ -737,7 +741,7 @@ dynamic parseValue(Input& in, json::metadata_map* map) {
in.consume("NaN") ?
(in.getOpts().parse_numbers_as_strings ? (dynamic)"NaN" :
(dynamic)std::numeric_limits::quiet_NaN()) :
- in.error("expected json value");
+ in.error("expected json value");
// clang-format on
}
@@ -796,7 +800,7 @@ size_t firstEscapableInWord(T s, const serialization_opts& opts) {
// times. However, for the case where 0 or a handful of bits are set,
// looping will be minimal through use of findFirstSet.
for (size_t i = 0, e = opts.extra_ascii_to_escape_bitmap.size(); i < e;
- ++i) {
+ ++i) {
const auto offset = i * 64;
// Clear first 32 characters if this is the first index, since those are
// always escaped.
@@ -1111,4 +1115,4 @@ void PrintTo(const dynamic& dyn, std::ostream* os) {
//////////////////////////////////////////////////////////////////////
-} // namespace folly
+} // namespace folly
\ No newline at end of file
diff --git a/vnext/Folly/TEMP_UntilFollyUpdate/lang/SafeAssert.h b/vnext/Folly/TEMP_UntilFollyUpdate/lang/SafeAssert.h
index d565dff810b..0d9ff21b55d 100644
--- a/vnext/Folly/TEMP_UntilFollyUpdate/lang/SafeAssert.h
+++ b/vnext/Folly/TEMP_UntilFollyUpdate/lang/SafeAssert.h
@@ -126,7 +126,7 @@ struct safe_assert_msg_types_one_fn {
c operator()(char const*) const;
c operator()(uint64_t) const;
};
-FOLLY_INLINE_VARIABLE constexpr safe_assert_msg_types_one_fn
+inline constexpr safe_assert_msg_types_one_fn
safe_assert_msg_types_one{}; // a function object to prevent extensions
template
@@ -141,13 +141,6 @@ struct safe_assert_msg_types> {
static constexpr value_type value = {{A..., safe_assert_msg_type::term}};
};
-#if FOLLY_CPLUSPLUS < 201703L
-template
-constexpr
- typename safe_assert_msg_types>::value_type
- safe_assert_msg_types