mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 23:08:56 +00:00
serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891)
1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
2 files changed
+73
-71
No files matched your search
@@ -966,6 +966,28 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// write out the bytes pending in @ref string_buffer and reset @a bytes
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE void emit_pending(std::size_t& bytes)
|
||||||
|
{
|
||||||
|
if (bytes != 0)
|
||||||
|
{
|
||||||
|
put_buffer(string_buffer, bytes);
|
||||||
|
bytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// write buffer and reset index if fewer than 13 bytes are left, as this
|
||||||
|
/// is the maximal number of bytes to be written ("\uxxxx\uxxxx\0") for one
|
||||||
|
/// code point
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE void reserve_string_buffer(std::size_t& bytes)
|
||||||
|
{
|
||||||
|
if (string_buffer.size() - bytes < 13)
|
||||||
|
{
|
||||||
|
put_buffer(string_buffer, bytes);
|
||||||
|
bytes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief worker for @ref dump_escaped
|
@brief worker for @ref dump_escaped
|
||||||
|
|
||||||
@@ -1024,11 +1046,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
// emit any bytes still pending in string_buffer first to
|
// emit any bytes still pending in string_buffer first to
|
||||||
// preserve output order, then write the run directly
|
// preserve output order, then write the run directly
|
||||||
if (bytes != 0)
|
emit_pending(bytes);
|
||||||
{
|
|
||||||
put_buffer(string_buffer, bytes);
|
|
||||||
bytes = 0;
|
|
||||||
}
|
|
||||||
put_run(s, i, run);
|
put_run(s, i, run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
@@ -1049,11 +1067,10 @@ class serializer
|
|||||||
{
|
{
|
||||||
// emit any bytes still pending in string_buffer first to
|
// emit any bytes still pending in string_buffer first to
|
||||||
// preserve output order
|
// preserve output order
|
||||||
if (bytes != 0)
|
emit_pending(bytes);
|
||||||
{
|
// deliberately not put_buffer(): a fixed 8-byte memcpy
|
||||||
put_buffer(string_buffer, bytes);
|
// has no asserts and no write-through branch, which
|
||||||
bytes = 0;
|
// matters on the hot path
|
||||||
}
|
|
||||||
const auto& escape = ascii_escapes()[data[i]];
|
const auto& escape = ascii_escapes()[data[i]];
|
||||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||||
{
|
{
|
||||||
@@ -1128,14 +1145,7 @@ class serializer
|
|||||||
string_buffer[bytes++] = s[i];
|
string_buffer[bytes++] = s[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// write buffer and reset index; there must be 13 bytes
|
reserve_string_buffer(bytes);
|
||||||
// left, as this is the maximal number of bytes to be
|
|
||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
|
||||||
if (string_buffer.size() - bytes < 13)
|
|
||||||
{
|
|
||||||
put_buffer(string_buffer, bytes);
|
|
||||||
bytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember the byte position of this accept
|
// remember the byte position of this accept
|
||||||
bytes_after_last_accept = bytes;
|
bytes_after_last_accept = bytes;
|
||||||
@@ -1187,14 +1197,7 @@ class serializer
|
|||||||
string_buffer[bytes++] = '\xBD';
|
string_buffer[bytes++] = '\xBD';
|
||||||
}
|
}
|
||||||
|
|
||||||
// write buffer and reset index; there must be 13 bytes
|
reserve_string_buffer(bytes);
|
||||||
// left, as this is the maximal number of bytes to be
|
|
||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
|
||||||
if (string_buffer.size() - bytes < 13)
|
|
||||||
{
|
|
||||||
put_buffer(string_buffer, bytes);
|
|
||||||
bytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes_after_last_accept = bytes;
|
bytes_after_last_accept = bytes;
|
||||||
}
|
}
|
||||||
@@ -1227,14 +1230,7 @@ class serializer
|
|||||||
string_buffer[bytes++] = s[i];
|
string_buffer[bytes++] = s[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// write buffer and reset index; there must be 13 bytes
|
reserve_string_buffer(bytes);
|
||||||
// left, as this is the maximal number of bytes to be
|
|
||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
|
||||||
if (string_buffer.size() - bytes < 13)
|
|
||||||
{
|
|
||||||
put_buffer(string_buffer, bytes);
|
|
||||||
bytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes_after_last_accept = bytes;
|
bytes_after_last_accept = bytes;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
@@ -1600,30 +1596,17 @@ class serializer
|
|||||||
*/
|
*/
|
||||||
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
static const std::array<std::uint8_t, 256>& ascii_copyable() noexcept
|
||||||
{
|
{
|
||||||
static const std::array<std::uint8_t, 256> copyable =
|
// constant-initialized from the predicate: no runtime loop, no guard
|
||||||
{
|
static constexpr std::array<std::uint8_t, 256> copyable = make_ascii_copyable(make_index_sequence<256> {});
|
||||||
{
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10
|
|
||||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xA0
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xB0
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xC0
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xD0
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xE0
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return copyable;
|
return copyable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<std::size_t... Is>
|
||||||
|
static constexpr std::array<std::uint8_t, 256> make_ascii_copyable(index_sequence<Is...> /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return {{ static_cast<std::uint8_t>(is_ascii_copyable(static_cast<unsigned char>(Is)) ? 1 : 0)... }};
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the JSON form of each character below 0x60
|
@brief the JSON form of each character below 0x60
|
||||||
|
|
||||||
@@ -1752,7 +1735,6 @@ class serializer
|
|||||||
*/
|
*/
|
||||||
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||||
{
|
{
|
||||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
while (i < n && data[i] >= 0x80)
|
while (i < n && data[i] >= 0x80)
|
||||||
{
|
{
|
||||||
@@ -1789,26 +1771,21 @@ class serializer
|
|||||||
char* d = write_buffer.data() + write_buffer_pos;
|
char* d = write_buffer.data() + write_buffer_pos;
|
||||||
if (codepoint > 0xFFFFu)
|
if (codepoint > 0xFFFFu)
|
||||||
{
|
{
|
||||||
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||||
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||||
}
|
}
|
||||||
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint));
|
||||||
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||||
i += length;
|
i += length;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
static char* put_u_escape(char* d, const std::uint16_t codeunit) noexcept
|
||||||
{
|
{
|
||||||
const auto unit = static_cast<std::uint32_t>(codeunit);
|
|
||||||
d[0] = '\\';
|
d[0] = '\\';
|
||||||
d[1] = 'u';
|
d[1] = 'u';
|
||||||
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
return hex4_lower(codeunit, d + 2);
|
||||||
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
|
||||||
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
|
||||||
d[5] = nibble_to_hex[unit & 0x0Fu];
|
|
||||||
return d + 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1817,7 +1794,7 @@ class serializer
|
|||||||
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
* Branch-free replacement for `snprintf(buf, 7, "\\u%04x", codeunit)` in the
|
||||||
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
* string escaping hot path. It writes exactly six characters ('\\', 'u' and
|
||||||
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
* four hex digits) at position @a pos of @a string_buffer via a nibble
|
||||||
* lookup table, avoiding the format-string parsing and locale machinery of
|
* lookup table (@ref hex4_lower), avoiding the format-string parsing and locale machinery of
|
||||||
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
* `snprintf`. Advances @a pos by the number of bytes written (6).
|
||||||
*
|
*
|
||||||
* @param[in] pos position in @a string_buffer to write at; there must
|
* @param[in] pos position in @a string_buffer to write at; there must
|
||||||
@@ -1827,13 +1804,9 @@ class serializer
|
|||||||
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
void write_u_escape(std::size_t& pos, std::uint16_t codeunit) noexcept
|
||||||
{
|
{
|
||||||
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
JSON_ASSERT(string_buffer.size() - pos >= 6);
|
||||||
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
|
||||||
string_buffer[pos + 0] = '\\';
|
string_buffer[pos + 0] = '\\';
|
||||||
string_buffer[pos + 1] = 'u';
|
string_buffer[pos + 1] = 'u';
|
||||||
string_buffer[pos + 2] = nibble_to_hex[(codeunit >> 12u) & 0x0Fu];
|
hex4_lower(codeunit, &string_buffer[pos + 2]);
|
||||||
string_buffer[pos + 3] = nibble_to_hex[(codeunit >> 8u) & 0x0Fu];
|
|
||||||
string_buffer[pos + 4] = nibble_to_hex[(codeunit >> 4u) & 0x0Fu];
|
|
||||||
string_buffer[pos + 5] = nibble_to_hex[codeunit & 0x0Fu];
|
|
||||||
pos += 6;
|
pos += 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2042,3 +2042,32 @@ TEST_CASE("string scanning kernels")
|
|||||||
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
||||||
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
|
||||||
|
{
|
||||||
|
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
|
||||||
|
// second time; check every byte value at every position of a 24-byte buffer
|
||||||
|
// (three words) and for several lengths, with the byte at the end or inside
|
||||||
|
for (unsigned int value = 0; value < 256; ++value)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position < 24; ++position)
|
||||||
|
{
|
||||||
|
std::array<unsigned char, 24> buffer{};
|
||||||
|
buffer.fill(static_cast<unsigned char>('a'));
|
||||||
|
buffer[position] = static_cast<unsigned char>(value);
|
||||||
|
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
|
||||||
|
for (const std::size_t n : {position, position + 1, std::size_t{24}})
|
||||||
|
{
|
||||||
|
if (n > 24)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t expected = (copyable || n <= position) ? n : position;
|
||||||
|
CAPTURE(value)
|
||||||
|
CAPTURE(position)
|
||||||
|
CAPTURE(n)
|
||||||
|
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user