mirror of
https://github.com/nlohmann/json.git
synced 2026-10-21 13:38:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c634c24877 |
No files matched your search
@@ -37,7 +37,6 @@ cc_library(
|
|||||||
"include/nlohmann/detail/input/parser.hpp",
|
"include/nlohmann/detail/input/parser.hpp",
|
||||||
"include/nlohmann/detail/input/position_t.hpp",
|
"include/nlohmann/detail/input/position_t.hpp",
|
||||||
"include/nlohmann/detail/input/pow5_table.hpp",
|
"include/nlohmann/detail/input/pow5_table.hpp",
|
||||||
"include/nlohmann/detail/input/simd_scan.hpp",
|
|
||||||
"include/nlohmann/detail/input/string_scan.hpp",
|
"include/nlohmann/detail/input/string_scan.hpp",
|
||||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||||
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
||||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
- [**JSON_NO_SIMD**](json_no_simd.md) - use only portable code instead of vector instructions (SSE2, SSSE3, NEON)
|
|
||||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
# JSON_NO_SIMD
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_NO_SIMD
|
|
||||||
```
|
|
||||||
|
|
||||||
When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning
|
|
||||||
strings during parsing, and when writing floating-point numbers during serialization.
|
|
||||||
|
|
||||||
The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or
|
|
||||||
analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values,
|
|
||||||
exceptions, and error positions are the same either way. Only the speed differs.
|
|
||||||
|
|
||||||
## Default definition
|
|
||||||
|
|
||||||
By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are used where the target has them.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#undef JSON_NO_SIMD
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
!!! note "Which instructions are used"
|
|
||||||
|
|
||||||
- **x86-64:** SSE2, which every x86-64 CPU has, finds the end of runs of plain string characters 16 bytes at a time
|
|
||||||
and writes the digits of floating-point numbers. The UTF-8 check of non-ASCII text needs SSSE3, which is not part
|
|
||||||
of x86-64: it is compiled for SSSE3 with a function attribute and used when the CPU has SSSE3 (every x86-64 CPU
|
|
||||||
since about 2011), else the portable check. Code compiled for SSSE3 anyway (e.g., with `-mssse3` or `-march=native`)
|
|
||||||
skips this check of the CPU. 32-bit x86 uses SSE2 for strings when the compiler targets it.
|
|
||||||
- **AArch64:** NEON, which every 64-bit Arm CPU has, checks UTF-8 text and long runs of plain string characters 16
|
|
||||||
bytes at a time, and writes the digits of floating-point numbers. With MSVC, only the floating-point digits use
|
|
||||||
NEON.
|
|
||||||
- **Other targets:** portable code only.
|
|
||||||
|
|
||||||
Strings are only scanned this way for contiguous input and for the characters a `#!cpp std::istream` holds in its
|
|
||||||
stream buffer (see [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other inputs are read one
|
|
||||||
character at a time.
|
|
||||||
|
|
||||||
!!! note "Interaction with `JSON_USE_SIMDUTF`"
|
|
||||||
|
|
||||||
With [`JSON_USE_SIMDUTF`](json_use_simdutf.md), runs of string characters are first validated by simdutf; the code
|
|
||||||
described here is used for what simdutf does not accept, so that errors are reported at the same position.
|
|
||||||
`JSON_NO_SIMD` does not affect simdutf.
|
|
||||||
|
|
||||||
!!! warning "Define consistently"
|
|
||||||
|
|
||||||
The macro selects between two definitions of the library's internal scanning and formatting functions. It must
|
|
||||||
therefore be defined identically for **every** translation unit that includes the library; mixing translation units
|
|
||||||
that define it with ones that do not is an ODR violation. Prefer setting it as a compile definition on the target
|
|
||||||
rather than with `#!cpp #define` in individual source files.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
??? example "Example: switching off vector instructions"
|
|
||||||
|
|
||||||
The program parses and serializes the same values with and without the macro:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#define JSON_NO_SIMD
|
|
||||||
#include <iostream>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
using json = nlohmann::json;
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
const json j = json::parse(R"({"name": "日本語 text", "pi": 3.141592653589793})");
|
|
||||||
std::cout << j.dump() << '\n'; // {"name":"日本語 text","pi":3.141592653589793}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
|
||||||
- [**dump**](../basic_json/dump.md) - serialization
|
|
||||||
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
|
||||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
|
||||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
|
||||||
|
|
||||||
## Version history
|
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
|
||||||
@@ -6,9 +6,8 @@
|
|||||||
|
|
||||||
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
||||||
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
||||||
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in validator (which uses SSSE3 or NEON where
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
||||||
available, see [`JSON_NO_SIMD`](json_no_simd.md)). On text with many non-ASCII characters (e.g. CJK or emoji) this can
|
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||||
validate faster, depending on the CPU.
|
|
||||||
|
|
||||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||||
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||||
|
|||||||
@@ -174,13 +174,6 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
|||||||
|
|
||||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||||
|
|
||||||
## `JSON_NO_SIMD`
|
|
||||||
|
|
||||||
When defined, the library uses only portable code instead of vector instructions (SSE2, SSSE3, NEON) for scanning
|
|
||||||
strings and writing floating-point numbers. Results are the same either way. It is not defined by default.
|
|
||||||
|
|
||||||
See [full documentation of `JSON_NO_SIMD`](../api/macros/json_no_simd.md).
|
|
||||||
|
|
||||||
## `JSON_NO_THREAD_LOCAL`
|
## `JSON_NO_THREAD_LOCAL`
|
||||||
|
|
||||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||||
|
|||||||
@@ -79,10 +79,6 @@ follow the usual `std::streambuf` contract, and
|
|||||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
||||||
thread must use the same `#!cpp FILE` object while it is parsed.
|
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||||
|
|
||||||
Where the target has them, strings are scanned with vector instructions: SSE2 on x86-64 (and SSSE3 for UTF-8 text, when
|
|
||||||
the CPU has it) and NEON on AArch64. [`JSON_NO_SIMD`](../../api/macros/json_no_simd.md) selects portable code instead,
|
|
||||||
again without changing any result.
|
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|
||||||
The library offers two parsing models:
|
The library offers two parsing models:
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ below are relative to `include/nlohmann`.
|
|||||||
|-----------|----------|
|
|-----------|----------|
|
||||||
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
||||||
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
||||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp), [`detail/input/simd_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/simd_scan.hpp) |
|
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) |
|
||||||
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
||||||
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
||||||
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
||||||
|
|||||||
@@ -383,7 +383,6 @@ nav:
|
|||||||
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
||||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||||
- 'JSON_NO_SIMD': api/macros/json_no_simd.md
|
|
||||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||||
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
||||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||||
|
|||||||
@@ -14,11 +14,24 @@
|
|||||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||||
#endif
|
#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
|
#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
|
// Portable bit-level helpers for the number and string scanners. They use
|
||||||
// compiler builtins or platform-specific intrinsics where available and plain
|
// compiler builtins or platform-specific intrinsics where available, the
|
||||||
// C++ otherwise, so they work regardless of byte order.
|
// 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
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
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
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanReverse64(&index, x);
|
_BitScanReverse64(&index, x);
|
||||||
return 63 - static_cast<int>(index);
|
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
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
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
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanForward64(&index, x);
|
_BitScanForward64(&index, x);
|
||||||
return static_cast<int>(index);
|
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
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|||||||
#endif
|
#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
|
/// the 128-bit product of two 64-bit numbers
|
||||||
struct uint128_parts
|
struct uint128_parts
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,17 +18,14 @@
|
|||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <type_traits> // conditional
|
#include <type_traits> // conditional
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#include <cstdlib> // _byteswap_uint64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||||
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
// digits of a double at once
|
||||||
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
#define JSON_DTOA_SSE2 1
|
#define JSON_DTOA_SSE2 1
|
||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#elif !defined(JSON_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#define JSON_DTOA_SSE2 0
|
#define JSON_DTOA_SSE2 0
|
||||||
#define JSON_DTOA_NEON 1
|
#define JSON_DTOA_NEON 1
|
||||||
@@ -37,6 +34,7 @@
|
|||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/conversions/zmij.hpp>
|
#include <nlohmann/detail/conversions/zmij.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
@@ -66,16 +64,6 @@ For a detailed description of the algorithm see:
|
|||||||
namespace dtoa_impl
|
namespace dtoa_impl
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename Target, typename Source>
|
|
||||||
Target reinterpret_bits(const Source source)
|
|
||||||
{
|
|
||||||
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
|
|
||||||
|
|
||||||
Target target;
|
|
||||||
std::memcpy(&target, &source, sizeof(Source));
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct diyfp // f * 2^e
|
struct diyfp // f * 2^e
|
||||||
{
|
{
|
||||||
static constexpr int kPrecision = 64; // = q
|
static constexpr int kPrecision = 64; // = q
|
||||||
@@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value)
|
|||||||
|
|
||||||
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
||||||
|
|
||||||
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
|
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
|
||||||
const std::uint64_t E = bits >> (kPrecision - 1);
|
const std::uint64_t E = bits >> (kPrecision - 1);
|
||||||
const std::uint64_t F = bits & (kHiddenBit - 1);
|
const std::uint64_t F = bits & (kHiddenBit - 1);
|
||||||
|
|
||||||
@@ -1084,14 +1072,11 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
|
|||||||
/// the byte stores once this is inlined)
|
/// the byte stores once this is inlined)
|
||||||
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
#if (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || defined(_MSC_VER) // (MSVC: little-endian on all its targets)
|
||||||
v = __builtin_bswap64(v);
|
v = byteswap(v);
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#elif defined(_MSC_VER) // (little-endian on all its targets)
|
|
||||||
v = _byteswap_uint64(v);
|
|
||||||
std::memcpy(p, &v, sizeof(v));
|
|
||||||
#else
|
#else
|
||||||
for (unsigned i = 0; i < 8; ++i)
|
for (unsigned i = 0; i < 8; ++i)
|
||||||
{
|
{
|
||||||
@@ -1462,8 +1447,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
|
|||||||
{
|
{
|
||||||
static_assert(is_binary64<FloatType>::value,
|
static_assert(is_binary64<FloatType>::value,
|
||||||
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
||||||
std::uint64_t bits = 0;
|
const auto bits = bit_cast<std::uint64_t>(value);
|
||||||
std::memcpy(&bits, &value, sizeof(bits));
|
|
||||||
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
||||||
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cstring> // memcmp
|
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
@@ -418,43 +417,30 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
||||||
/// read the first byte that needs individual handling (the closing quote, an
|
/// the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
||||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
void scan_string_bulk(std::true_type /*bulk*/)
|
||||||
/// Apple Clang merged this read with the other get() calls of the scan
|
|
||||||
/// function, which load the read position again right after it was stored
|
|
||||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
|
||||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
return get();
|
return;
|
||||||
}
|
}
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
return get();
|
return;
|
||||||
}
|
}
|
||||||
const auto* const raw = ia.bulk_data();
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
|
||||||
|
|
||||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
const std::size_t pos = string_bulk_run(data, remaining);
|
||||||
if (pos != 0)
|
if (pos == 0)
|
||||||
{
|
{
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
return;
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
|
||||||
}
|
}
|
||||||
if (pos == remaining)
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
{
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
return get(); // end of the contiguous input
|
|
||||||
}
|
|
||||||
// what get() does, with the byte at hand
|
|
||||||
advance_position();
|
|
||||||
ia.bulk_skip(1);
|
|
||||||
current = char_traits<char_type>::to_int_type(raw[pos]);
|
|
||||||
return track_after_read();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||||
@@ -487,10 +473,7 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
||||||
{
|
|
||||||
return get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
@@ -518,8 +501,11 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// get the next character, the one that ends the run
|
// handle the next special byte through the switch below
|
||||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
|
||||||
|
// get the next character
|
||||||
|
switch (get())
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -1855,23 +1841,18 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] rest the literal text after its first character, which
|
@param[in] literal_text the literal text to expect
|
||||||
is current (its length is a template parameter, so
|
@param[in] length the length of the passed literal text
|
||||||
that the comparison of the contiguous fast path is
|
@param[in] return_type the token type to return on success
|
||||||
a fixed-size load, not a call of memcmp)
|
|
||||||
@param[in] return_type the token type to return on success
|
|
||||||
*/
|
*/
|
||||||
template<std::size_t N>
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
||||||
|
token_type return_type)
|
||||||
{
|
{
|
||||||
constexpr std::size_t length = N - 1;
|
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
||||||
if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
|
for (std::size_t i = 1; i < length; ++i)
|
||||||
{
|
{
|
||||||
return return_type;
|
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < length; ++i)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
|
|
||||||
{
|
{
|
||||||
error_message = "invalid literal";
|
error_message = "invalid literal";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -1880,27 +1861,6 @@ scan_number_done:
|
|||||||
return return_type;
|
return return_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: compare the rest of a literal at once; false (and
|
|
||||||
/// nothing consumed) if it does not match, so that the byte path reports
|
|
||||||
/// the error at the right character
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
|
||||||
{
|
|
||||||
if (next_unget || ia.bulk_remaining() < length
|
|
||||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
|
|
||||||
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
|
||||||
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// input management
|
// input management
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -2526,14 +2486,21 @@ scan_number_done:
|
|||||||
return token_type::value_separator;
|
return token_type::value_separator;
|
||||||
|
|
||||||
// literals
|
// literals
|
||||||
// (the text after the first character; no local arrays, which
|
|
||||||
// would give this function a stack canary on every token)
|
|
||||||
case 't':
|
case 't':
|
||||||
return scan_literal("rue", token_type::literal_true);
|
{
|
||||||
|
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
|
||||||
|
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
|
||||||
|
}
|
||||||
case 'f':
|
case 'f':
|
||||||
return scan_literal("alse", token_type::literal_false);
|
{
|
||||||
|
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
|
||||||
|
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
|
||||||
|
}
|
||||||
case 'n':
|
case 'n':
|
||||||
return scan_literal("ull", token_type::literal_null);
|
{
|
||||||
|
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
|
||||||
|
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
|
||||||
|
}
|
||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint64_t
|
#include <cstdint> // int64_t, uint64_t
|
||||||
#include <cstdlib> // strtof, strtod, strtold
|
#include <cstdlib> // strtof, strtod, strtold
|
||||||
#include <cstring> // memcpy
|
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||||
@@ -758,15 +757,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
|
|||||||
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
std::memcpy(&value, &bits, sizeof(value));
|
value = bit_cast<double>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
||||||
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
const auto bits32 = static_cast<std::uint32_t>(bits);
|
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
|
||||||
std::memcpy(&value, &bits32, sizeof(bits32));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the powers of ten that are exact in binary64 (up to 10^22)
|
/// the powers of ten that are exact in binary64 (up to 10^22)
|
||||||
|
|||||||
@@ -244,9 +244,8 @@ class parser
|
|||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
// true = array; false = object
|
||||||
// push_back() is a call with GCC)
|
std::vector<bool> states;
|
||||||
std::vector<std::uint8_t> states;
|
|
||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
@@ -291,7 +290,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// remember we are now inside an object
|
||||||
states.push_back(0);
|
states.push_back(false);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -316,7 +315,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an array
|
// remember we are now inside an array
|
||||||
states.push_back(1);
|
states.push_back(true);
|
||||||
|
|
||||||
// parse values (no need to call get_token)
|
// parse values (no need to call get_token)
|
||||||
continue;
|
continue;
|
||||||
@@ -433,7 +432,7 @@ class parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.back() != 0) // array
|
if (states.back()) // array
|
||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
|
|||||||
@@ -1,316 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-FileCopyrightText: 2018-2025 The simdjson authors <https://github.com/simdjson/simdjson>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array> // array
|
|
||||||
#include <atomic> // atomic
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t, uint64_t
|
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.hpp>
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
|
||||||
|
|
||||||
// Vector code for runs of string bytes, used by the lexer for contiguous input
|
|
||||||
// (see string_bulk_run()). NEON (AArch64) and SSE2 (x86-64) belong to the
|
|
||||||
// baseline instruction sets and are used by default. The vector UTF-8 check
|
|
||||||
// needs NEON or SSSE3. SSSE3 is not part of x86-64, and the code must not
|
|
||||||
// depend on the flags of a translation unit (two translation units with
|
|
||||||
// different flags would have different definitions of the same inline
|
|
||||||
// functions): the check is compiled for SSSE3 with a function attribute and
|
|
||||||
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
|
|
||||||
// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the
|
|
||||||
// CPU check. JSON_NO_SIMD selects the portable code everywhere.
|
|
||||||
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
|
||||||
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
|
||||||
// without disabling the SSE2 code.
|
|
||||||
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
|
||||||
#include <arm_neon.h>
|
|
||||||
#define JSON_SCAN_NEON 1
|
|
||||||
#else
|
|
||||||
#define JSON_SCAN_NEON 0
|
|
||||||
#endif
|
|
||||||
// (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no <cpuid.h>)
|
|
||||||
#if !defined(JSON_NO_SIMD) && !JSON_SCAN_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
|
|
||||||
#include <emmintrin.h>
|
|
||||||
#define JSON_SCAN_SSE2 1
|
|
||||||
#else
|
|
||||||
#define JSON_SCAN_SSE2 0
|
|
||||||
#endif
|
|
||||||
#if JSON_SCAN_SSE2 && defined(__SSSE3__)
|
|
||||||
#include <tmmintrin.h>
|
|
||||||
#define JSON_SCAN_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
|
||||||
#else
|
|
||||||
#define JSON_SCAN_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
|
||||||
#endif
|
|
||||||
#if JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && !defined(NLOHMANN_JSON_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && !defined(__cpp_modules)))
|
|
||||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3; GCC with C++20
|
|
||||||
// modules drops the target attribute from functions it reads from a module
|
|
||||||
// interface and then rejects the intrinsics: "needs isa option -mssse3")
|
|
||||||
#include <cpuid.h>
|
|
||||||
#include <tmmintrin.h>
|
|
||||||
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
|
||||||
#define JSON_SCAN_SSSE3_TARGET __attribute__((target("ssse3")))
|
|
||||||
#elif JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && defined(_MSC_VER)
|
|
||||||
// (MSVC compiles intrinsics of any instruction set)
|
|
||||||
#include <intrin.h>
|
|
||||||
#include <tmmintrin.h>
|
|
||||||
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
|
||||||
#define JSON_SCAN_SSSE3_TARGET
|
|
||||||
#else
|
|
||||||
#define JSON_SCAN_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
|
||||||
#define JSON_SCAN_SSSE3_TARGET
|
|
||||||
#endif
|
|
||||||
#define JSON_SCAN_VECTOR (JSON_SCAN_NEON || JSON_SCAN_SSE2)
|
|
||||||
#define JSON_SCAN_VECTOR_UTF8 (JSON_SCAN_NEON || JSON_SCAN_SSSE3 || JSON_SCAN_SSSE3_DISPATCH)
|
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
|
|
||||||
#if JSON_SCAN_VECTOR
|
|
||||||
/*!
|
|
||||||
@brief the first byte of a string run that is a quote, a backslash, a control
|
|
||||||
character, or not ASCII, 16 bytes per step
|
|
||||||
|
|
||||||
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
|
||||||
the two apart). A signed compare with 0x20 finds control characters and
|
|
||||||
non-ASCII bytes at once.
|
|
||||||
*/
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
while (e - p >= 16)
|
|
||||||
{
|
|
||||||
#if JSON_SCAN_NEON
|
|
||||||
const uint8x16_t in = vld1q_u8(p);
|
|
||||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
|
||||||
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
|
||||||
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
|
||||||
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
|
||||||
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
|
||||||
if (bits != 0)
|
|
||||||
{
|
|
||||||
return p + (count_trailing_zeros(bits) >> 2u);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
||||||
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
|
||||||
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
|
||||||
if (bits != 0)
|
|
||||||
{
|
|
||||||
return p + count_trailing_zeros(bits);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
p += 16;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_SCAN_VECTOR
|
|
||||||
/// vector_plain_run() for runs that are already longer than a few words:
|
|
||||||
/// inline with SSE2, out of line with NEON, where a larger string loop slows
|
|
||||||
/// down the rest of the lexer
|
|
||||||
#if JSON_SCAN_NEON
|
|
||||||
JSON_HEDLEY_NEVER_INLINE inline
|
|
||||||
#else
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
|
||||||
#endif
|
|
||||||
const unsigned char* vector_plain_run_long(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
return vector_plain_run(p, e);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_SCAN_SSSE3_DISPATCH
|
|
||||||
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
|
|
||||||
inline bool cpu_ssse3() noexcept
|
|
||||||
{
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__)
|
|
||||||
std::array<int, 4> regs {{}};
|
|
||||||
__cpuid(regs.data(), 1);
|
|
||||||
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
|
|
||||||
#else
|
|
||||||
unsigned eax = 0;
|
|
||||||
unsigned ebx = 0;
|
|
||||||
unsigned ecx = 0;
|
|
||||||
unsigned edx = 0;
|
|
||||||
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
|
|
||||||
/// that is initialized at compile time, so that neither a guard of a local
|
|
||||||
/// static nor a global constructor is needed (threads that ask at the same
|
|
||||||
/// time all store the same answer)
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
|
|
||||||
{
|
|
||||||
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
|
|
||||||
int state = known.load(std::memory_order_relaxed);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(state == 0))
|
|
||||||
{
|
|
||||||
state = cpu_ssse3() ? 2 : 1;
|
|
||||||
known.store(state, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
return state == 2;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_SCAN_VECTOR_UTF8
|
|
||||||
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
|
|
||||||
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
|
|
||||||
/// maps a nibble (high and low nibble of the previous byte, high nibble of the
|
|
||||||
/// current byte) to the errors it allows; a byte pair is ill-formed if all
|
|
||||||
/// three have an error bit in common.
|
|
||||||
template<typename Dummy = void>
|
|
||||||
struct utf8_lookup4
|
|
||||||
{
|
|
||||||
static constexpr std::uint8_t too_short = 1u << 0u, too_long = 1u << 1u, overlong_3 = 1u << 2u, too_large = 1u << 3u;
|
|
||||||
static constexpr std::uint8_t surrogate = 1u << 4u, overlong_2 = 1u << 5u, too_large_1000 = 1u << 6u, overlong_4 = 1u << 6u;
|
|
||||||
static constexpr std::uint8_t two_conts = 1u << 7u, carry = too_short | too_long | two_conts;
|
|
||||||
static const std::array<std::uint8_t, 16> byte_1_high;
|
|
||||||
static const std::array<std::uint8_t, 16> byte_1_low;
|
|
||||||
static const std::array<std::uint8_t, 16> byte_2_high;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Dummy>
|
|
||||||
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_high =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
too_long, too_long, too_long, too_long, too_long, too_long, too_long, too_long,
|
|
||||||
two_conts, two_conts, two_conts, two_conts,
|
|
||||||
too_short | overlong_2, too_short, too_short | overlong_3 | surrogate, too_short | too_large | too_large_1000 | overlong_4
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Dummy>
|
|
||||||
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_low =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
carry | overlong_3 | overlong_2 | overlong_4, carry | overlong_2, carry, carry,
|
|
||||||
carry | too_large, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
|
||||||
carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
|
||||||
carry | too_large | too_large_1000, carry | too_large | too_large_1000 | surrogate, carry | too_large | too_large_1000, carry | too_large | too_large_1000
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Dummy>
|
|
||||||
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_2_high =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
too_short, too_short, too_short, too_short, too_short, too_short, too_short, too_short,
|
|
||||||
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large_1000 | overlong_4),
|
|
||||||
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large),
|
|
||||||
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
|
||||||
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
|
||||||
too_short, too_short, too_short, too_short
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief the run of plain string bytes and well-formed UTF-8 at p (a character
|
|
||||||
boundary), 16 bytes per step
|
|
||||||
|
|
||||||
The first quote, backslash, or control character is found with vector
|
|
||||||
compares, and the UTF-8 check of J. Keiser and D. Lemire covers the bytes up
|
|
||||||
to it. Returns true with @a block at that byte; returns false with @a block at
|
|
||||||
the start of the 16 bytes where the check found ill-formed UTF-8 or where
|
|
||||||
fewer than 16 bytes are left, for the caller to finish one sequence at a time.
|
|
||||||
Out of line, so that no constants of the check occupy registers in the
|
|
||||||
caller's loop. On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
|
|
||||||
*/
|
|
||||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(const unsigned char* p, const unsigned char* e, const unsigned char*& block_out) noexcept
|
|
||||||
{
|
|
||||||
using lookup = utf8_lookup4<>;
|
|
||||||
const unsigned char* block = p;
|
|
||||||
#if JSON_SCAN_NEON
|
|
||||||
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
|
||||||
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
|
||||||
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
|
||||||
uint8x16_t prev = vdupq_n_u8(0);
|
|
||||||
while (e - block >= 16)
|
|
||||||
{
|
|
||||||
const uint8x16_t in = vld1q_u8(block);
|
|
||||||
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
|
||||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
|
||||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
|
||||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
|
||||||
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
|
||||||
const std::uint64_t special_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
|
||||||
const std::uint64_t err_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(err, err)), 4)), 0);
|
|
||||||
if (special_bits != 0)
|
|
||||||
{
|
|
||||||
// errors up to the special byte count (an incomplete sequence
|
|
||||||
// before a quote shows at the quote); the bytes after it do not
|
|
||||||
const unsigned k = static_cast<unsigned>(count_trailing_zeros(special_bits)) >> 2u;
|
|
||||||
const std::uint64_t upto = k == 15 ? ~std::uint64_t{0} :
|
|
||||||
(std::uint64_t{1} << (4u * (k + 1u))) - 1u;
|
|
||||||
if ((err_bits & upto) == 0)
|
|
||||||
{
|
|
||||||
block_out = block + k;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (err_bits != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prev = in;
|
|
||||||
block += 16;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// the same with SSSE3 (pshufb for the table lookups; nibbles from 16-bit
|
|
||||||
// shifts, as there are no byte shifts)
|
|
||||||
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i nibble = _mm_set1_epi8(0x0F);
|
|
||||||
const __m128i zero = _mm_setzero_si128();
|
|
||||||
__m128i prev = zero;
|
|
||||||
while (e - block >= 16)
|
|
||||||
{
|
|
||||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
|
||||||
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
|
||||||
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
|
||||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
|
||||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
|
||||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
|
||||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
|
||||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
|
||||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
|
||||||
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
|
||||||
const auto special_bits = static_cast<unsigned>(_mm_movemask_epi8(special));
|
|
||||||
const auto err_bits = ~static_cast<unsigned>(_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero))) & 0xFFFFu;
|
|
||||||
if (special_bits != 0)
|
|
||||||
{
|
|
||||||
const auto k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
|
|
||||||
if ((err_bits & ((2u << k) - 1u)) == 0)
|
|
||||||
{
|
|
||||||
block_out = block + k;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (err_bits != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prev = in;
|
|
||||||
block += 16;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
block_out = block;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace detail
|
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.hpp>
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/input/simd_scan.hpp>
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||||
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||||
// lexer's template parameters) so they are free functions, keeping the lexer
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||||
// itself focused on the state machine; see lexer::get_after_string_run().
|
// itself focused on the state machine; see lexer::scan_string_bulk().
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -67,7 +66,7 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
|||||||
|
|
||||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||||
// n if every byte is ordinary; scans 8 bytes at a time
|
// n if every byte is ordinary; scans 8 bytes at a time
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
@@ -236,143 +235,35 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_SCAN_VECTOR_UTF8
|
// Scalar (C++11) computation of the bulk run length: the number of leading
|
||||||
/// the end of scan_string_vector from block, where the vector loop stopped
|
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||||
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
|
||||||
/// time, from the start of a sequence that crosses into the block
|
|
||||||
inline const unsigned char* scan_string_finish(const unsigned char* p, const unsigned char* block, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
for (int i = 1; i <= 3 && block - i >= p; ++i)
|
|
||||||
{
|
|
||||||
const unsigned char c = block[-i];
|
|
||||||
if (c < 0x80)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (c >= 0xC0)
|
|
||||||
{
|
|
||||||
const int len = 2 + static_cast<int>(c >= 0xE0) + static_cast<int>(c >= 0xF0);
|
|
||||||
if (len > i)
|
|
||||||
{
|
|
||||||
block -= i;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (p = block; p != e;)
|
|
||||||
{
|
|
||||||
if (*p < 0x80)
|
|
||||||
{
|
|
||||||
if (is_string_special(*p))
|
|
||||||
{
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
++p;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
|
||||||
if (n == 0)
|
|
||||||
{
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
p += n;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// the rest of a string from p (a character boundary), with the vector UTF-8
|
|
||||||
/// check and the scalar check for the end of the block where it stopped
|
|
||||||
inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
const unsigned char* block = p;
|
|
||||||
if (vector_utf8_run(p, e, block))
|
|
||||||
{
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
return scan_string_finish(p, block, e);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_SCAN_VECTOR
|
|
||||||
/// the end of the run of plain string characters at @a p: the first quote,
|
|
||||||
/// backslash, control character, or non-ASCII byte, or @a e. The first words
|
|
||||||
/// are checked with SWAR (one; four with NEON, where the vector mask costs
|
|
||||||
/// more), as most keys and short values end there; longer runs continue 16
|
|
||||||
/// bytes at a time.
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char* p, const unsigned char* e) noexcept
|
|
||||||
{
|
|
||||||
constexpr int prefix_words = JSON_SCAN_NEON ? 4 : 1;
|
|
||||||
for (int words = prefix_words; words > 0 && e - p >= 8; --words)
|
|
||||||
{
|
|
||||||
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
|
||||||
if (special != 0)
|
|
||||||
{
|
|
||||||
return p + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
|
||||||
}
|
|
||||||
p += 8;
|
|
||||||
}
|
|
||||||
// the vector loop only for 16 bytes or more: the serializer passes a
|
|
||||||
// string without its closing quote, so most of its runs end at e, and a
|
|
||||||
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
|
||||||
// 10% slower
|
|
||||||
if (e - p >= 16)
|
|
||||||
{
|
|
||||||
p = vector_plain_run_long(p, e);
|
|
||||||
if (e - p >= 16)
|
|
||||||
{
|
|
||||||
return p; // the vector loop stopped at a special byte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Computation of the bulk run length: the number of leading bytes in
|
|
||||||
// [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
|
||||||
// sequences, stopping before the first byte that needs individual handling (the
|
// sequences, stopping before the first byte that needs individual handling (the
|
||||||
// closing quote, an escape, a control character, or an ill-formed/truncated
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||||
// sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
|
// sequence). ASCII is skipped 8 bytes at a time.
|
||||||
// time otherwise; runs of non-ASCII text use the vector UTF-8 check where
|
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
// available.
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
|
||||||
{
|
{
|
||||||
const unsigned char* const e = data + n;
|
std::size_t pos = 0;
|
||||||
const unsigned char* p = data;
|
while (pos < n)
|
||||||
while (p != e)
|
|
||||||
{
|
{
|
||||||
#if JSON_SCAN_VECTOR
|
pos += find_string_special(data + pos, n - pos);
|
||||||
p = plain_run_end(p, e);
|
if (pos >= n || data[pos] < 0x80u)
|
||||||
#else
|
|
||||||
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
|
||||||
#endif
|
|
||||||
if (p == e || *p < 0x80u)
|
|
||||||
{
|
{
|
||||||
break; // end of buffer, or a quote/escape/control byte
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
}
|
}
|
||||||
#if JSON_SCAN_VECTOR_UTF8
|
|
||||||
#if JSON_SCAN_SSSE3_DISPATCH
|
|
||||||
if (JSON_HEDLEY_LIKELY(cpu_has_ssse3()))
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
return static_cast<std::size_t>(scan_string_vector(p, e) - data);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#if !JSON_SCAN_VECTOR_UTF8 || JSON_SCAN_SSSE3_DISPATCH
|
|
||||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
||||||
// by sequence without searching for the next special byte in between
|
// by sequence without searching for the next special byte in between
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||||
if (seq == 0)
|
if (seq == 0)
|
||||||
{
|
{
|
||||||
return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
|
return pos; // ill-formed or truncated: let the byte path diagnose it
|
||||||
}
|
}
|
||||||
p += seq;
|
pos += seq;
|
||||||
}
|
}
|
||||||
while (p != e && *p >= 0x80u);
|
while (pos < n && data[pos] >= 0x80u);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(p - data);
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
@@ -413,7 +304,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
|||||||
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
||||||
// scalar helper recomputes the exact valid prefix so the byte path still
|
// scalar helper recomputes the exact valid prefix so the byte path still
|
||||||
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
const std::size_t run = find_string_delimiter(data, n);
|
const std::size_t run = find_string_delimiter(data, n);
|
||||||
@@ -425,42 +316,6 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data,
|
|||||||
return scalar_string_bulk_run(data, n);
|
return scalar_string_bulk_run(data, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
|
||||||
/// scalar_string_bulk_run() from @a start on, out of line (see
|
|
||||||
/// lexer_string_bulk_run())
|
|
||||||
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
return start + scalar_string_bulk_run(data + start, n - start);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
|
||||||
// checked inline (most keys and short values end there), and longer runs and
|
|
||||||
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
|
||||||
// token, and the vector code inlined into it needs more registers, which every
|
|
||||||
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
|
||||||
// Apple M1, even though no string of that file reaches the vector code. The
|
|
||||||
// serializer keeps string_bulk_run() inline, which is faster there.
|
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
|
||||||
{
|
|
||||||
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
|
||||||
std::size_t i = 0;
|
|
||||||
for (int words = 4; words > 0 && n - i >= 8; --words)
|
|
||||||
{
|
|
||||||
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
|
||||||
if (special != 0)
|
|
||||||
{
|
|
||||||
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
|
||||||
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
|
||||||
}
|
|
||||||
i += 8;
|
|
||||||
}
|
|
||||||
return string_bulk_run_from(data, i, n);
|
|
||||||
#else
|
|
||||||
return string_bulk_run(data, n);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||||
|
|||||||
@@ -24,13 +24,6 @@
|
|||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
#undef JSON_SCAN_NEON
|
|
||||||
#undef JSON_SCAN_SSE2
|
|
||||||
#undef JSON_SCAN_SSSE3
|
|
||||||
#undef JSON_SCAN_SSSE3_DISPATCH
|
|
||||||
#undef JSON_SCAN_SSSE3_TARGET
|
|
||||||
#undef JSON_SCAN_VECTOR
|
|
||||||
#undef JSON_SCAN_VECTOR_UTF8
|
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
|
|||||||
@@ -10,17 +10,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||||
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <algorithm> // clamp
|
||||||
|
#endif
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename T>
|
#ifdef __cpp_lib_remove_cvref
|
||||||
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
template<typename T>
|
||||||
|
using uncvref_t = std::remove_cvref_t<T>;
|
||||||
|
#else
|
||||||
|
template<typename T>
|
||||||
|
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_14
|
#ifdef JSON_HAS_CPP_14
|
||||||
|
|
||||||
@@ -160,5 +169,18 @@ struct static_const
|
|||||||
constexpr T static_const<T>::value;
|
constexpr T static_const<T>::value;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
|
||||||
|
/// but by value, so that static constexpr members can be passed without an
|
||||||
|
/// out-of-line definition before C++17
|
||||||
|
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
|
||||||
|
constexpr T clamp(T v, T lo, T hi) noexcept
|
||||||
|
{
|
||||||
|
#ifdef __cpp_lib_clamp
|
||||||
|
return std::clamp(v, lo, hi);
|
||||||
|
#else
|
||||||
|
return v < lo ? lo : (hi < v ? hi : v);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -313,6 +313,14 @@ struct char_traits<std::byte> : std::char_traits<char>
|
|||||||
// is_ functions //
|
// is_ functions //
|
||||||
///////////////////
|
///////////////////
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_logical_traits
|
||||||
|
|
||||||
|
using std::conjunction;
|
||||||
|
using std::disjunction;
|
||||||
|
using std::negation;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/types/conjunction
|
// https://en.cppreference.com/w/cpp/types/conjunction
|
||||||
template<class...> struct conjunction : std::true_type { };
|
template<class...> struct conjunction : std::true_type { };
|
||||||
template<class B> struct conjunction<B> : B { };
|
template<class B> struct conjunction<B> : B { };
|
||||||
@@ -330,6 +338,8 @@ struct disjunction<B, Bn...>
|
|||||||
// https://en.cppreference.com/w/cpp/types/negation
|
// https://en.cppreference.com/w/cpp/types/negation
|
||||||
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
||||||
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
||||||
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
||||||
@@ -1060,8 +1070,12 @@ constexpr bool value_in_range_of(T val)
|
|||||||
return value_in_range_of_impl1<OfType, T>::test(val);
|
return value_in_range_of_impl1<OfType, T>::test(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool Value>
|
#ifdef __cpp_lib_bool_constant
|
||||||
using bool_constant = std::integral_constant<bool, Value>;
|
using std::bool_constant;
|
||||||
|
#else
|
||||||
|
template<bool Value>
|
||||||
|
using bool_constant = std::integral_constant<bool, Value>;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||||
|
|||||||
@@ -19,10 +19,6 @@
|
|||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.hpp>
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||||
#include <nlohmann/detail/input/string_scan.hpp>
|
#include <nlohmann/detail/input/string_scan.hpp>
|
||||||
@@ -2073,7 +2069,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
out[0] = to_char_type(first_byte);
|
out[0] = to_char_type(first_byte);
|
||||||
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
|
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
|
||||||
const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits;
|
const std::uint64_t tail = is_little_endian ? byteswap(value) >> unused_bits : value << unused_bits;
|
||||||
std::memcpy(out + 1, &tail, sizeof(tail));
|
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||||
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||||
}
|
}
|
||||||
@@ -3517,53 +3513,14 @@ class binary_writer
|
|||||||
// Utility functions //
|
// Utility functions //
|
||||||
///////////////////////
|
///////////////////////
|
||||||
|
|
||||||
// single-instruction byte swaps (compilers lower these to bswap/rev/movbe);
|
|
||||||
// used to emit big-endian numbers without a per-byte std::reverse loop
|
|
||||||
static std::uint16_t byte_swap(std::uint16_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap16(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_ushort(x);
|
|
||||||
#else
|
|
||||||
return static_cast<std::uint16_t>((x >> 8) | (x << 8));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::uint32_t byte_swap(std::uint32_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap32(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_ulong(x);
|
|
||||||
#else
|
|
||||||
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
|
|
||||||
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::uint64_t byte_swap(std::uint64_t x) noexcept
|
|
||||||
{
|
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
|
||||||
return __builtin_bswap64(x);
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
return _byteswap_uint64(x);
|
|
||||||
#else
|
|
||||||
x = ((x & 0x00000000FFFFFFFFull) << 32) | ((x & 0xFFFFFFFF00000000ull) >> 32);
|
|
||||||
x = ((x & 0x0000FFFF0000FFFFull) << 16) | ((x & 0xFFFF0000FFFF0000ull) >> 16);
|
|
||||||
x = ((x & 0x00FF00FF00FF00FFull) << 8) | ((x & 0xFF00FF00FF00FF00ull) >> 8);
|
|
||||||
return x;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reverse the bytes of a buffer by byte-swapping it as UIntType
|
@brief reverse the bytes of a buffer by byte-swapping it as UIntType
|
||||||
|
|
||||||
Loading the buffer into an unsigned integer of the same width and swapping
|
Loading the buffer into an unsigned integer of the same width and swapping
|
||||||
that is what lets the compiler emit a single bswap/rev/movbe; reversing the
|
that is what lets the compiler emit a single bswap/rev/movbe; reversing the
|
||||||
buffer element by element does not reliably get there (clang keeps a scalar
|
buffer element by element does not reliably get there (clang keeps a scalar
|
||||||
shuffle). The two memcpy calls are the only portable way to reinterpret the
|
shuffle). (The two memcpy calls, rather than bit_cast, keep GCC from
|
||||||
bytes and are folded away by every optimizer.
|
inlining differently in C++20, which made writing slower.)
|
||||||
*/
|
*/
|
||||||
template<typename UIntType, std::size_t N>
|
template<typename UIntType, std::size_t N>
|
||||||
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
|
static void byte_swap_buffer(std::array<CharType, N>& a) noexcept
|
||||||
@@ -3571,11 +3528,11 @@ class binary_writer
|
|||||||
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
|
static_assert(sizeof(UIntType) == N, "swap width must match the buffer size");
|
||||||
UIntType v{};
|
UIntType v{};
|
||||||
std::memcpy(&v, a.data(), sizeof(v));
|
std::memcpy(&v, a.data(), sizeof(v));
|
||||||
v = byte_swap(v);
|
v = byteswap(v);
|
||||||
std::memcpy(a.data(), &v, sizeof(v));
|
std::memcpy(a.data(), &v, sizeof(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
// reverse the bytes of a fixed-size buffer; a single byte_swap() for the
|
// reverse the bytes of a fixed-size buffer; a single byteswap() for the
|
||||||
// common 2/4/8-byte number payloads, std::reverse for any other size
|
// common 2/4/8-byte number payloads, std::reverse for any other size
|
||||||
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
|
static void reverse_bytes(std::array<CharType, 2>& a) noexcept
|
||||||
{
|
{
|
||||||
@@ -3700,9 +3657,7 @@ class binary_writer
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
|
static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
|
||||||
CharType result;
|
return bit_cast<CharType>(x);
|
||||||
std::memcpy(&result, &x, sizeof(x));
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename C = CharType,
|
template<typename C = CharType,
|
||||||
|
|||||||
@@ -105,17 +105,13 @@ class output_buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the size of a growth step (a function: std::min() takes a reference,
|
/// the size of a growth step
|
||||||
/// which a static constexpr member does not have before C++17)
|
static constexpr std::size_t step = 0x10000;
|
||||||
static constexpr std::size_t step() noexcept
|
|
||||||
{
|
|
||||||
return 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.reserve(estimate);
|
out.reserve(estimate);
|
||||||
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +120,7 @@ class output_buffer
|
|||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation)
|
// estimate is never copied to a larger allocation)
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_out.capacity())
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
@@ -794,8 +790,7 @@ class view_serializer
|
|||||||
static char* write_double_value_at(char* w, double x)
|
static char* write_double_value_at(char* w, double x)
|
||||||
{
|
{
|
||||||
// (from the bits: without the checks of to_chars())
|
// (from the bits: without the checks of to_chars())
|
||||||
std::uint64_t bits = 0;
|
auto bits = bit_cast<std::uint64_t>(x);
|
||||||
std::memcpy(&bits, &x, sizeof(bits));
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
{
|
{
|
||||||
return write_text_at(w, "null", 4);
|
return write_text_at(w, "null", 4);
|
||||||
|
|||||||
Generated
+214
-651
File diff suppressed because it is too large.
Load diff
Generated
+5
-10
@@ -6166,17 +6166,13 @@ class output_buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the size of a growth step (a function: std::min() takes a reference,
|
/// the size of a growth step
|
||||||
/// which a static constexpr member does not have before C++17)
|
static constexpr std::size_t step = 0x10000;
|
||||||
static constexpr std::size_t step() noexcept
|
|
||||||
{
|
|
||||||
return 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.reserve(estimate);
|
out.reserve(estimate);
|
||||||
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6185,7 +6181,7 @@ class output_buffer
|
|||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation)
|
// estimate is never copied to a larger allocation)
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_out.capacity())
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
@@ -6855,8 +6851,7 @@ class view_serializer
|
|||||||
static char* write_double_value_at(char* w, double x)
|
static char* write_double_value_at(char* w, double x)
|
||||||
{
|
{
|
||||||
// (from the bits: without the checks of to_chars())
|
// (from the bits: without the checks of to_chars())
|
||||||
std::uint64_t bits = 0;
|
auto bits = bit_cast<std::uint64_t>(x);
|
||||||
std::memcpy(&bits, &x, sizeof(bits));
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
{
|
{
|
||||||
return write_text_at(w, "null", 4);
|
return write_text_at(w, "null", 4);
|
||||||
|
|||||||
@@ -373,25 +373,6 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
# the lexer again with the portable string scanning instead of SSE2/NEON, and on
|
|
||||||
# x86-64 with SSSE3 compiled in (no check of the CPU)
|
|
||||||
json_test_set_test_options(test-class_lexer_no_simd
|
|
||||||
COMPILE_DEFINITIONS JSON_NO_SIMD
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-class_lexer.cpp
|
|
||||||
NAME test-class_lexer_no_simd
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
|
|
||||||
json_test_set_test_options(test-class_lexer_ssse3
|
|
||||||
COMPILE_OPTIONS -mssse3
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-class_lexer.cpp
|
|
||||||
NAME test-class_lexer_ssse3
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# test reading from a std::istream again without scanning the get area in bulk
|
# test reading from a std::istream again without scanning the get area in bulk
|
||||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
json_test_set_test_options(test-stream-bulk-scan_disabled
|
||||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cctype> // toupper
|
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
#include <cmath> // isfinite
|
#include <cmath> // isfinite
|
||||||
@@ -600,67 +599,6 @@ TEST_CASE("lexer number fast path")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
// this test case compares the errors of both lexer paths, which abort when
|
|
||||||
// exceptions are off
|
|
||||||
TEST_CASE("lexer literal fast path")
|
|
||||||
{
|
|
||||||
// For contiguous input, the rest of a literal is compared at once, and a
|
|
||||||
// mismatch is left to the byte path, which reports the error at the
|
|
||||||
// character that differs. Every truncation and every wrong character of
|
|
||||||
// each literal, at the end of the input or followed by more text, must
|
|
||||||
// give the byte path's result (a std::list is read one character at a
|
|
||||||
// time).
|
|
||||||
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
|
||||||
{
|
|
||||||
JSON_TRY
|
|
||||||
{
|
|
||||||
const std::list<char> chars(doc.begin(), doc.end());
|
|
||||||
return (byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc)).dump();
|
|
||||||
}
|
|
||||||
JSON_CATCH (const json::exception & e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::string> mismatches;
|
|
||||||
for (const std::string literal :
|
|
||||||
{"true", "false", "null"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::vector<std::string> variants;
|
|
||||||
for (std::size_t length = 1; length <= literal.size(); ++length)
|
|
||||||
{
|
|
||||||
variants.push_back(literal.substr(0, length));
|
|
||||||
}
|
|
||||||
for (std::size_t i = 1; i < literal.size(); ++i)
|
|
||||||
{
|
|
||||||
std::string changed = literal;
|
|
||||||
changed[i] = 'X';
|
|
||||||
variants.push_back(changed);
|
|
||||||
changed[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(literal[i])));
|
|
||||||
variants.push_back(changed);
|
|
||||||
}
|
|
||||||
for (const auto& variant : variants)
|
|
||||||
{
|
|
||||||
for (const std::string& doc :
|
|
||||||
{
|
|
||||||
variant, "[" + variant + "]", "[" + variant, variant + " ", variant + "1", "{\"k\":" + variant + "}"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
if (outcome(doc, false) != outcome(doc, true))
|
|
||||||
{
|
|
||||||
mismatches.push_back(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CAPTURE(mismatches)
|
|
||||||
CHECK(mismatches.empty());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("lexer string fast path")
|
TEST_CASE("lexer string fast path")
|
||||||
{
|
{
|
||||||
// Build a byte string from explicit values: a hex escape in a string
|
// Build a byte string from explicit values: a hex escape in a string
|
||||||
@@ -781,73 +719,6 @@ TEST_CASE("lexer string fast path")
|
|||||||
CAPTURE(mismatches)
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("special bytes at every offset of the vector blocks")
|
|
||||||
{
|
|
||||||
// With SSE2 or NEON, runs are scanned 16 bytes at a time (after one
|
|
||||||
// SWAR word, or four with NEON), and runs of non-ASCII text are
|
|
||||||
// checked 16 bytes at a time as well. Place every kind of byte that
|
|
||||||
// ends a run at each offset across several blocks, after ASCII and
|
|
||||||
// after CJK text, so sequences also straddle the block boundaries.
|
|
||||||
// The reference is a std::list, which has no contiguous storage and
|
|
||||||
// is therefore read one character at a time.
|
|
||||||
const auto byte_path = [](const std::string & doc) -> std::string
|
|
||||||
{
|
|
||||||
const std::list<char> chars(doc.begin(), doc.end());
|
|
||||||
JSON_TRY
|
|
||||||
{
|
|
||||||
return json::parse(chars.begin(), chars.end()).dump();
|
|
||||||
}
|
|
||||||
JSON_CATCH (const json::exception & e)
|
|
||||||
{
|
|
||||||
return e.what();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const std::vector<std::string> specials =
|
|
||||||
{
|
|
||||||
"\"", "\\", "\\u00e9", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
|
|
||||||
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
|
|
||||||
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8}), bytes({0xED, 0xA0, 0x80}), bytes({0xC0, 0x80})
|
|
||||||
};
|
|
||||||
const std::string cjk = bytes({0xE4, 0xB8, 0xAD}); // U+4E2D
|
|
||||||
|
|
||||||
std::vector<std::string> mismatches;
|
|
||||||
for (std::size_t offset = 0; offset <= 80; ++offset)
|
|
||||||
{
|
|
||||||
std::string text;
|
|
||||||
while (text.size() < offset)
|
|
||||||
{
|
|
||||||
text += cjk;
|
|
||||||
}
|
|
||||||
for (const auto& prefix :
|
|
||||||
{
|
|
||||||
std::string(offset, 'a'), text
|
|
||||||
})
|
|
||||||
{
|
|
||||||
for (const auto& special : specials)
|
|
||||||
{
|
|
||||||
// the closing quote right behind, or in a later block
|
|
||||||
for (const int tail :
|
|
||||||
{
|
|
||||||
0, 1, 3, 20
|
|
||||||
})
|
|
||||||
{
|
|
||||||
std::string doc = "[\"";
|
|
||||||
doc += prefix;
|
|
||||||
doc += special;
|
|
||||||
doc.append(static_cast<std::size_t>(tail), 'b');
|
|
||||||
doc += "\"]";
|
|
||||||
if (outcome(doc, false) != byte_path(doc))
|
|
||||||
{
|
|
||||||
mismatches.push_back(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CAPTURE(mismatches)
|
|
||||||
CHECK(mismatches.empty());
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// json::accept() never throws, so the ranges stay covered without exceptions
|
// json::accept() never throws, so the ranges stay covered without exceptions
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// The detail helpers that stand in for standard library functions of newer
|
||||||
|
// standards: each uses the standard function where the library has it and a
|
||||||
|
// C++11 implementation otherwise, so this file is also built with
|
||||||
|
// JSON_HAS_CPP_17, JSON_HAS_CPP_20, and JSON_HAS_CPP_23 to test both.
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct with_constexpr_member
|
||||||
|
{
|
||||||
|
static constexpr std::size_t lower = 64; // no out-of-line definition
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("detail::clamp")
|
||||||
|
{
|
||||||
|
using nlohmann::detail::clamp;
|
||||||
|
|
||||||
|
SECTION("integers")
|
||||||
|
{
|
||||||
|
CHECK(clamp(5, 1, 10) == 5);
|
||||||
|
CHECK(clamp(0, 1, 10) == 1);
|
||||||
|
CHECK(clamp(11, 1, 10) == 10);
|
||||||
|
CHECK(clamp(1, 1, 10) == 1);
|
||||||
|
CHECK(clamp(10, 1, 10) == 10);
|
||||||
|
CHECK(clamp(7, 7, 7) == 7);
|
||||||
|
CHECK(clamp(-5, -3, 3) == -3);
|
||||||
|
CHECK(clamp(std::size_t{0}, std::size_t{64}, std::size_t{0x10000}) == 64);
|
||||||
|
CHECK(clamp(std::size_t{0x20000}, std::size_t{64}, std::size_t{0x10000}) == 0x10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("floating-point numbers")
|
||||||
|
{
|
||||||
|
CHECK(clamp(0.5, 0.0, 1.0) == 0.5);
|
||||||
|
CHECK(clamp(-0.5, 0.0, 1.0) == 0.0);
|
||||||
|
CHECK(clamp(1.5, 0.0, 1.0) == 1.0);
|
||||||
|
CHECK(clamp(-std::numeric_limits<double>::infinity(), 0.0, 1.0) == 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("by value: a static constexpr member needs no definition")
|
||||||
|
{
|
||||||
|
CHECK(clamp(std::size_t{1}, with_constexpr_member::lower, std::size_t{100}) == 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("constexpr and noexcept")
|
||||||
|
{
|
||||||
|
static_assert(clamp(3, 1, 2) == 2, "clamp must be constexpr");
|
||||||
|
static_assert(noexcept(clamp(3, 1, 2)), "clamp must be noexcept");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("detail::bit_cast")
|
||||||
|
{
|
||||||
|
using nlohmann::detail::bit_cast;
|
||||||
|
|
||||||
|
CHECK(bit_cast<std::uint64_t>(1.0) == 0x3FF0000000000000u);
|
||||||
|
CHECK(bit_cast<std::uint64_t>(-2.0) == 0xC000000000000000u);
|
||||||
|
CHECK(bit_cast<std::uint32_t>(1.0f) == 0x3F800000u);
|
||||||
|
CHECK(bit_cast<double>(std::uint64_t{0x4009000000000000u}) == 3.125);
|
||||||
|
CHECK(bit_cast<float>(std::uint32_t{0xC0490000u}) == -3.140625f);
|
||||||
|
CHECK(bit_cast<std::int8_t>(std::uint8_t{0xFF}) == -1);
|
||||||
|
|
||||||
|
// to and from byte arrays
|
||||||
|
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
||||||
|
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
|
||||||
|
|
||||||
|
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("detail::byteswap")
|
||||||
|
{
|
||||||
|
using nlohmann::detail::byteswap;
|
||||||
|
|
||||||
|
CHECK(byteswap(std::uint16_t{0x0102u}) == 0x0201u);
|
||||||
|
CHECK(byteswap(std::uint16_t{0xFF00u}) == 0x00FFu);
|
||||||
|
CHECK(byteswap(std::uint32_t{0x01020304u}) == 0x04030201u);
|
||||||
|
CHECK(byteswap(std::uint32_t{0xFF000080u}) == 0x800000FFu);
|
||||||
|
CHECK(byteswap(std::uint64_t{0x0102030405060708u}) == 0x0807060504030201u);
|
||||||
|
CHECK(byteswap(std::uint64_t{0xFF00000000000080u}) == 0x80000000000000FFu);
|
||||||
|
|
||||||
|
// an involution
|
||||||
|
for (std::uint64_t x :
|
||||||
|
{
|
||||||
|
std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{0x123456789ABCDEF0u}, (std::numeric_limits<std::uint64_t>::max)()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CHECK(byteswap(byteswap(x)) == x);
|
||||||
|
CHECK(byteswap(byteswap(static_cast<std::uint32_t>(x))) == static_cast<std::uint32_t>(x));
|
||||||
|
CHECK(byteswap(byteswap(static_cast<std::uint16_t>(x))) == static_cast<std::uint16_t>(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(std::is_same < decltype(byteswap(std::uint16_t{})), std::uint16_t >::value, "byteswap keeps the type");
|
||||||
|
static_assert(std::is_same < decltype(byteswap(std::uint32_t{})), std::uint32_t >::value, "byteswap keeps the type");
|
||||||
|
static_assert(std::is_same < decltype(byteswap(std::uint64_t{})), std::uint64_t >::value, "byteswap keeps the type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("detail::count_leading_zeros and count_trailing_zeros")
|
||||||
|
{
|
||||||
|
using nlohmann::detail::count_leading_zeros;
|
||||||
|
using nlohmann::detail::count_trailing_zeros;
|
||||||
|
|
||||||
|
// a single bit, and that bit with all lower (resp. higher) bits set: both
|
||||||
|
// halves of the 32-bit fallbacks
|
||||||
|
for (int k = 0; k < 64; ++k)
|
||||||
|
{
|
||||||
|
const std::uint64_t bit = std::uint64_t{1} << k;
|
||||||
|
CAPTURE(k)
|
||||||
|
CHECK(count_leading_zeros(bit) == 63 - k);
|
||||||
|
CHECK(count_leading_zeros(bit | (bit - 1)) == 63 - k);
|
||||||
|
CHECK(count_trailing_zeros(bit) == k);
|
||||||
|
CHECK(count_trailing_zeros(bit | ~(bit | (bit - 1))) == k);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -171,8 +171,7 @@ std::vector<std::string> corpus()
|
|||||||
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
|
R"("unterminated)", "\"control\x01character\"", "\"ill-formed \xc3\x28 UTF-8\"", R"("bad \x escape")",
|
||||||
R"("\ud800 lone surrogate")",
|
R"("\ud800 lone surrogate")",
|
||||||
// literals
|
// literals
|
||||||
"true", "false", "null", "tru", "nul", "falsey", "[true,false,null]", "nulL", "truex", "[fals e]", "t",
|
"true", "false", "null", "tru", "nul", "falsey",
|
||||||
"true1", "null null", "[false]",
|
|
||||||
// structures
|
// structures
|
||||||
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
|
"[]", "{}", "[1,2,3]", R"({"a":1,"b":[true,false,null],"c":{"d":"e"}})", "[1,]", R"({"a" 1})", "[[[[[]]]]]",
|
||||||
// pretty-printed
|
// pretty-printed
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::detail::dtoa_impl::reinterpret_bits;
|
using nlohmann::detail::bit_cast;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -44,7 +44,7 @@ float make_float(uint32_t sign_bit, uint32_t biased_exponent, uint32_t significa
|
|||||||
bits |= biased_exponent << 23;
|
bits |= biased_exponent << 23;
|
||||||
bits |= significand;
|
bits |= significand;
|
||||||
|
|
||||||
return reinterpret_bits<float>(bits);
|
return bit_cast<float>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ldexp -- convert f * 2^e to IEEE single precision
|
// ldexp -- convert f * 2^e to IEEE single precision
|
||||||
@@ -81,7 +81,7 @@ float make_float(uint64_t f, int e)
|
|||||||
: static_cast<uint64_t>(e + kExponentBias);
|
: static_cast<uint64_t>(e + kExponentBias);
|
||||||
|
|
||||||
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
|
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
|
||||||
return reinterpret_bits<float>(static_cast<uint32_t>(bits));
|
return bit_cast<float>(static_cast<uint32_t>(bits));
|
||||||
}
|
}
|
||||||
|
|
||||||
double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand)
|
double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t significand)
|
||||||
@@ -96,7 +96,7 @@ double make_double(uint64_t sign_bit, uint64_t biased_exponent, uint64_t signifi
|
|||||||
bits |= biased_exponent << 52;
|
bits |= biased_exponent << 52;
|
||||||
bits |= significand;
|
bits |= significand;
|
||||||
|
|
||||||
return reinterpret_bits<double>(bits);
|
return bit_cast<double>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ldexp -- convert f * 2^e to IEEE double precision
|
// ldexp -- convert f * 2^e to IEEE double precision
|
||||||
@@ -133,7 +133,7 @@ double make_double(uint64_t f, int e)
|
|||||||
: static_cast<uint64_t>(e + kExponentBias);
|
: static_cast<uint64_t>(e + kExponentBias);
|
||||||
|
|
||||||
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
|
const uint64_t bits = (f & kSignificandMask) | (biased_exponent << kPhysicalSignificandSize);
|
||||||
return reinterpret_bits<double>(bits);
|
return bit_cast<double>(bits);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -652,7 +652,7 @@ bool reads_back(const std::string& digits, int e, double v)
|
|||||||
{
|
{
|
||||||
const std::string text = digits + "e" + std::to_string(e);
|
const std::string text = digits + "e" + std::to_string(e);
|
||||||
// (compared bit for bit: v is positive and finite, and -Wfloat-equal)
|
// (compared bit for bit: v is positive and finite, and -Wfloat-equal)
|
||||||
return reinterpret_bits<std::uint64_t>(parse_double(text)) == reinterpret_bits<std::uint64_t>(v);
|
return bit_cast<std::uint64_t>(parse_double(text)) == bit_cast<std::uint64_t>(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check the representation of a positive finite double: it reads back as
|
/// Check the representation of a positive finite double: it reads back as
|
||||||
@@ -665,7 +665,7 @@ void check_shortest(double v)
|
|||||||
CAPTURE(text)
|
CAPTURE(text)
|
||||||
CHECK(parse_double(text) == v);
|
CHECK(parse_double(text) == v);
|
||||||
// the layout is that of format_buffer() for the digits of Zmij
|
// the layout is that of format_buffer() for the digits of Zmij
|
||||||
const auto sd = nlohmann::detail::zmij::to_shortest(reinterpret_bits<std::uint64_t>(v));
|
const auto sd = nlohmann::detail::zmij::to_shortest(bit_cast<std::uint64_t>(v));
|
||||||
const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral;
|
const std::uint64_t significand = sd.has_digit ? (sd.integral * 10) + sd.digit : sd.integral;
|
||||||
int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1;
|
int exponent = sd.has_digit ? sd.exponent : sd.exponent + 1;
|
||||||
std::string significand_digits = std::to_string(significand);
|
std::string significand_digits = std::to_string(significand);
|
||||||
@@ -779,7 +779,7 @@ TEST_CASE("shortest digits of doubles")
|
|||||||
for (int i = 0; i < 100000; ++i)
|
for (int i = 0; i < 100000; ++i)
|
||||||
{
|
{
|
||||||
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
|
const std::uint64_t bits = rng() & 0x7FFFFFFFFFFFFFFFu;
|
||||||
const auto v = reinterpret_bits<double>(bits);
|
const auto v = bit_cast<double>(bits);
|
||||||
if (std::isfinite(v) && bits != 0)
|
if (std::isfinite(v) && bits != 0)
|
||||||
{
|
{
|
||||||
check_shortest(v);
|
check_shortest(v);
|
||||||
|
|||||||
Reference in new issue
Block a user