mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 14:58:54 +00:00
Use standard library helpers where available, with C++11 fallbacks (#5876)
* Use standard library helpers where available, with C++11 fallbacks Add detail helpers that call the standard library function when the library provides it (checked with the __cpp_lib_* feature-test macros, so no new ABI-tag macros) and a C++11 implementation otherwise: - detail::clamp (std::clamp): by value, so that static constexpr members need no out-of-line definition before C++17; used in json_view's dump() buffer sizing, where step() can now be a static constexpr member. - detail::bit_cast (std::bit_cast): replaces dtoa_impl::reinterpret_bits and the memcpy bit copies in the number parser, to_chars, the json_view serializer, and binary_writer::to_char_type. - detail::byteswap (std::byteswap): one implementation instead of the binary writer's own byte_swap() overloads and the bswap chain in to_chars' store_msb_first(). - count_leading_zeros/count_trailing_zeros: two 32-bit _BitScanReverse/ _BitScanForward calls for MSVC x86/ARM32 instead of the loop, and std::countl_zero/countr_zero for compilers without builtins. - conjunction, disjunction, negation, bool_constant, and uncvref_t map to their std counterparts in C++17/C++20. Builtins and intrinsics stay ahead of the <bit> functions, as MSVC's std::countl_zero checks for LZCNT at run time unless AVX2 is enabled. The generated code is unchanged with Clang (C++11/20/23, arm64 and x86-64) and GCC 16 for parsing, dumping, and the binary formats. The binary reader keeps its own byte_swap() and the writer its memcpy calls: the smaller code from detail::byteswap/std::bit_cast there made GCC 16 inline get_number()/write_number() differently, which slowed reading by 10-18 % and writing by about 1 %. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Name the target byte order once (JSON_LITTLE_ENDIAN_TARGET, JSON_BIG_ENDIAN_TARGET) to_chars.hpp and bit_ops.hpp repeated the long __BYTE_ORDER__/_MSC_VER conditions; macro_scope.hpp now defines them once (undefined again in macro_unscope.hpp). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Explain why the serializer growth bound is not a clamp Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
7c26e99ac5
commit
3e031b06a5
13 files changed
+496
-202
No files matched your search
@@ -14,11 +14,24 @@
|
||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||
#endif
|
||||
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
|
||||
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
||||
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
||||
#endif
|
||||
|
||||
// Portable bit-level helpers for the number and string scanners. They use
|
||||
// compiler builtins or platform-specific intrinsics where available and plain
|
||||
// C++ otherwise, so they work regardless of byte order.
|
||||
// compiler builtins or platform-specific intrinsics where available, the
|
||||
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
||||
// of byte order. (The builtins and intrinsics come first: MSVC's
|
||||
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -33,6 +46,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanReverse64(&index, x);
|
||||
return 63 - static_cast<int>(index);
|
||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
||||
{
|
||||
return 31 - static_cast<int>(index);
|
||||
}
|
||||
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
||||
return 63 - static_cast<int>(index);
|
||||
#elif defined(__cpp_lib_bitops)
|
||||
return std::countl_zero(x);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
@@ -56,6 +79,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||
_BitScanForward64(&index, x);
|
||||
return static_cast<int>(index);
|
||||
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
||||
{
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
||||
return 32 + static_cast<int>(index);
|
||||
#elif defined(__cpp_lib_bitops)
|
||||
return std::countr_zero(x);
|
||||
#else
|
||||
int n = 0;
|
||||
for (int shift = 32; shift != 0; shift >>= 1)
|
||||
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
||||
template<typename To, typename From>
|
||||
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
||||
{
|
||||
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
||||
#if defined(__cpp_lib_bit_cast)
|
||||
return std::bit_cast<To>(from);
|
||||
#else
|
||||
To to {};
|
||||
std::memcpy(&to, &from, sizeof(To));
|
||||
return to;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
||||
/// it to a single bswap/rev/movbe
|
||||
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap16(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_ushort(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap32(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_ulong(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
||||
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
||||
{
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return __builtin_bswap64(x);
|
||||
#elif defined(_MSC_VER)
|
||||
return _byteswap_uint64(x);
|
||||
#elif defined(__cpp_lib_byteswap)
|
||||
return std::byteswap(x);
|
||||
#else
|
||||
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
||||
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
||||
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
||||
return x;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the 128-bit product of two 64-bit numbers
|
||||
struct uint128_parts
|
||||
{
|
||||
@@ -107,8 +201,8 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||
{
|
||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||
// the byte order already matches (all MSVC targets are little-endian)
|
||||
#if JSON_LITTLE_ENDIAN_TARGET
|
||||
// the byte order already matches
|
||||
std::uint64_t result = 0;
|
||||
std::memcpy(&result, b, sizeof(result));
|
||||
return result;
|
||||
|
||||
Reference in new issue
Block a user