mirror of
https://github.com/nlohmann/json.git
synced 2026-10-14 18:27:13 +00:00
Write MessagePack integers without branching on their sign or size
Like CBOR before #5824, MessagePack integer arrays were no faster than in 3.12.0: the type switch (number_integer vs. number_unsigned), the sign test, and the fixint/8/16/32/64-bit ladder mispredict on mixed data. They now take the CBOR approach: - number_integer and number_unsigned share one case of write_msgpack. - The format comes from the size class of x, where x is the value itself if it is not negative and 2 * (-1 - n) + 1 for a negative n: n fits int 8/16/32 exactly if that fits uint 8/16/32, so one table lookup serves both signs. The fixint bound (127, or -32) is selected through a mask, and the marker is 0xCB + class for uint and 0xCF + class for int. - Arrays are written by an out-of-line write_msgpack_array() with runs of integers encoded straight into space reserved from the sink. The CBOR integer code is factored into helpers both formats share (integer_bits, integer_size_class, encode_first_byte_and_tail, write_array_elements). The new path picks the format from the low 64 bits, so number types wider than that keep the previous range checks. The output is unchanged; the integer-array test now also checks MessagePack against an independent encoder. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+606
-233
No files matched your search
@@ -403,54 +403,12 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_msgpack_integer(j, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -468,13 +426,7 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
write_msgpack_array(j, depth);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1935,22 +1887,16 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head; up to @ref cbor_integer_head_space
|
||||
characters at @a out are overwritten
|
||||
@brief size class of an unsigned integer from its bit length
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
|
||||
of leading zero bits instead of found by branches: the sizes of a stream
|
||||
of numbers are hard to predict, and a mispredicted branch costs more than
|
||||
writing the number. (A table, as comparisons are vectorized by clang into
|
||||
slower code.)
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
static unsigned integer_size_class(const std::uint64_t x) noexcept
|
||||
{
|
||||
// The size class - 0 if the argument fits the first byte, else 1 to 4
|
||||
// for 1, 2, 4, or 8 following bytes - is looked up from the number of
|
||||
// leading zero bits instead of found by branches: the sizes of a stream
|
||||
// of numbers are hard to predict, and a mispredicted branch costs more
|
||||
// than writing the head.
|
||||
static constexpr std::uint8_t size_class_by_leading_zeros[64] =
|
||||
{
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
@@ -1958,25 +1904,79 @@ class binary_writer
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
};
|
||||
// an argument of at most 23 is in the table's class 1 (one byte) and is
|
||||
// moved to class 0 by subtracting 1; argument | 1 keeps
|
||||
// count_leading_zeros() away from 0
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = size_class_by_leading_zeros[count_leading_zeros(argument | 1u)] - is_immediate;
|
||||
const std::size_t length = (std::size_t{1} << size_class) >> 1u;
|
||||
// x | 1 keeps count_leading_zeros() away from 0
|
||||
return size_class_by_leading_zeros[count_leading_zeros(x | 1u)];
|
||||
}
|
||||
|
||||
// The first byte - the argument itself if it is immediate, else
|
||||
// 0x17 + size_class - selected through a mask, then the length
|
||||
// low-order bytes of the argument in network order, written as one
|
||||
// 8-byte word whose bytes after the argument are scratch that
|
||||
// the caller drops. For an immediate argument the word is all
|
||||
// scratch; it is shifted as if length were 1, avoiding a shift by 64.
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
out[0] = to_char_type(static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask))));
|
||||
const unsigned unused_bits = 64u - 8u * static_cast<unsigned>(length + is_immediate);
|
||||
const std::uint64_t tail = is_little_endian ? byte_swap(argument) >> unused_bits : argument << unused_bits;
|
||||
/*!
|
||||
@brief encode @a first_byte and the low-order bytes of @a value that
|
||||
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
|
||||
to @a out
|
||||
@return the number of characters of the encoding; up to
|
||||
@ref integer_encoding_space characters at @a out are overwritten
|
||||
|
||||
The bytes are written as one byte and one 8-byte word whose bytes after
|
||||
the value are scratch that the caller drops, so that writing them does not
|
||||
depend on the size class. For class 0 the word is all scratch; it is
|
||||
shifted by 60 bits rather than 64, which would be undefined.
|
||||
*/
|
||||
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
|
||||
{
|
||||
out[0] = to_char_type(first_byte);
|
||||
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;
|
||||
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||
return length + 1;
|
||||
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||
}
|
||||
|
||||
/// characters encode_first_byte_and_tail() may overwrite: the first byte
|
||||
/// and one 8-byte word
|
||||
static constexpr std::size_t integer_encoding_space = 9;
|
||||
|
||||
/*!
|
||||
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
|
||||
that is all ones if it is negative, else zero
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
{
|
||||
// an argument of at most 23 is in size class 1 (one byte) and is moved
|
||||
// to class 0, the first byte, by subtracting 1
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = integer_size_class(argument) - is_immediate;
|
||||
|
||||
// the first byte: the argument itself if it is immediate, else
|
||||
// 0x17 + size_class, selected through a mask
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1991,29 +1991,45 @@ class binary_writer
|
||||
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_cbor_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Runs of integers are encoded straight into space reserved from the
|
||||
// sink for a whole run: the write pointer stays a local, where it would
|
||||
// otherwise be reloaded from the sink for every value, as a store of a
|
||||
// character may alias it.
|
||||
constexpr std::size_t max_run = max_reserve_size / cbor_integer_head_space;
|
||||
/*!
|
||||
@brief write the elements of the array @a j, runs of integers with
|
||||
@a encode_integer and all other values with @a write_value
|
||||
|
||||
Runs of integers are encoded straight into space reserved from the sink
|
||||
for a whole run: the write pointer stays a local, where it would otherwise
|
||||
be reloaded from the sink for every value, as a store of a character may
|
||||
alias it.
|
||||
*/
|
||||
template<typename EncodeInteger, typename WriteValue>
|
||||
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
|
||||
{
|
||||
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
|
||||
auto it = j.m_data.m_value.array->begin();
|
||||
const auto end = j.m_data.m_value.array->end();
|
||||
while (it != end)
|
||||
{
|
||||
if (it->is_number_integer())
|
||||
{
|
||||
CharType* const start = oa.reserve(max_run * cbor_integer_head_space);
|
||||
CharType* const start = oa.reserve(max_run * integer_encoding_space);
|
||||
CharType* out = start;
|
||||
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
|
||||
{
|
||||
out += encode_cbor_integer(out, *it);
|
||||
out += encode_integer(out, *it);
|
||||
}
|
||||
oa.commit(static_cast<std::size_t>(out - start));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_cbor(*it, depth + 1);
|
||||
write_value(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
@@ -2023,37 +2039,160 @@ class binary_writer
|
||||
@brief write a number_integer or number_unsigned value as a CBOR integer
|
||||
|
||||
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
|
||||
The sign and the type select the major type and the argument through a
|
||||
mask rather than a branch, as they alternate unpredictably in an array of
|
||||
mixed signs.
|
||||
Kept out of line, like the other per-value paths, so that write_cbor stays
|
||||
small (runs of integers in an array are encoded inline instead).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_integer(const BasicJsonType& j)
|
||||
{
|
||||
oa.commit(encode_cbor_integer(oa.reserve(cbor_integer_head_space), j));
|
||||
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_cbor_integer) and return the number of characters
|
||||
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
// All ones for a negative integer, else zero. Up to 64 bits, the sign is
|
||||
// bit 63 of raw (a narrower integer is sign-extended); computing it
|
||||
// with masks keeps GCC from branching on is_signed. A wider integer
|
||||
// has to be compared.
|
||||
const std::uint64_t sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
// ~raw is -1 - n in the 64 bits that are written
|
||||
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
|
||||
}
|
||||
|
||||
/// characters encode_cbor_integer_head() may overwrite: the first byte and
|
||||
/// one 8-byte word
|
||||
static constexpr std::size_t cbor_integer_head_space = 9;
|
||||
/*!
|
||||
@brief whether the MessagePack integers of BasicJsonType are written by
|
||||
@ref encode_msgpack_integer
|
||||
|
||||
It picks the format from the low 64 bits; a wider number type keeps the
|
||||
range checks of @ref write_msgpack on the whole value.
|
||||
*/
|
||||
static constexpr bool msgpack_integers_fit_64_bits() noexcept
|
||||
{
|
||||
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a MessagePack
|
||||
integer without branching on its sign or size
|
||||
|
||||
Same output as @ref write_msgpack: a non-negative integer as positive fixint
|
||||
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
|
||||
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_msgpack_integer) and return the number of characters
|
||||
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
|
||||
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
|
||||
// uint 8/16/32, so x gives the size class of either sign.
|
||||
const std::uint64_t is_negative = sign_mask & 1u;
|
||||
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
|
||||
|
||||
// A fixint is at most 127, or at least -32 (x at most 63). It is in
|
||||
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
|
||||
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
|
||||
const unsigned size_class = integer_size_class(x) - is_fixint;
|
||||
|
||||
// the first byte: the low byte of the integer for a fixint, else the
|
||||
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
|
||||
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
|
||||
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
|
||||
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a MessagePack integer, with range checks on the whole value
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as MessagePack
|
||||
|
||||
See @ref write_cbor_array.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
write_msgpack_array_elements(j, depth, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_msgpack_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
|
||||
Generated
+248
-109
@@ -22435,54 +22435,12 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_msgpack_integer(j, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22500,13 +22458,7 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
write_msgpack_array(j, depth);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23967,22 +23919,16 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head; up to @ref cbor_integer_head_space
|
||||
characters at @a out are overwritten
|
||||
@brief size class of an unsigned integer from its bit length
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
|
||||
of leading zero bits instead of found by branches: the sizes of a stream
|
||||
of numbers are hard to predict, and a mispredicted branch costs more than
|
||||
writing the number. (A table, as comparisons are vectorized by clang into
|
||||
slower code.)
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
static unsigned integer_size_class(const std::uint64_t x) noexcept
|
||||
{
|
||||
// The size class - 0 if the argument fits the first byte, else 1 to 4
|
||||
// for 1, 2, 4, or 8 following bytes - is looked up from the number of
|
||||
// leading zero bits instead of found by branches: the sizes of a stream
|
||||
// of numbers are hard to predict, and a mispredicted branch costs more
|
||||
// than writing the head.
|
||||
static constexpr std::uint8_t size_class_by_leading_zeros[64] =
|
||||
{
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
@@ -23990,25 +23936,79 @@ class binary_writer
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
};
|
||||
// an argument of at most 23 is in the table's class 1 (one byte) and is
|
||||
// moved to class 0 by subtracting 1; argument | 1 keeps
|
||||
// count_leading_zeros() away from 0
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = size_class_by_leading_zeros[count_leading_zeros(argument | 1u)] - is_immediate;
|
||||
const std::size_t length = (std::size_t{1} << size_class) >> 1u;
|
||||
// x | 1 keeps count_leading_zeros() away from 0
|
||||
return size_class_by_leading_zeros[count_leading_zeros(x | 1u)];
|
||||
}
|
||||
|
||||
// The first byte - the argument itself if it is immediate, else
|
||||
// 0x17 + size_class - selected through a mask, then the length
|
||||
// low-order bytes of the argument in network order, written as one
|
||||
// 8-byte word whose bytes after the argument are scratch that
|
||||
// the caller drops. For an immediate argument the word is all
|
||||
// scratch; it is shifted as if length were 1, avoiding a shift by 64.
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
out[0] = to_char_type(static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask))));
|
||||
const unsigned unused_bits = 64u - 8u * static_cast<unsigned>(length + is_immediate);
|
||||
const std::uint64_t tail = is_little_endian ? byte_swap(argument) >> unused_bits : argument << unused_bits;
|
||||
/*!
|
||||
@brief encode @a first_byte and the low-order bytes of @a value that
|
||||
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
|
||||
to @a out
|
||||
@return the number of characters of the encoding; up to
|
||||
@ref integer_encoding_space characters at @a out are overwritten
|
||||
|
||||
The bytes are written as one byte and one 8-byte word whose bytes after
|
||||
the value are scratch that the caller drops, so that writing them does not
|
||||
depend on the size class. For class 0 the word is all scratch; it is
|
||||
shifted by 60 bits rather than 64, which would be undefined.
|
||||
*/
|
||||
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
|
||||
{
|
||||
out[0] = to_char_type(first_byte);
|
||||
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;
|
||||
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||
return length + 1;
|
||||
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||
}
|
||||
|
||||
/// characters encode_first_byte_and_tail() may overwrite: the first byte
|
||||
/// and one 8-byte word
|
||||
static constexpr std::size_t integer_encoding_space = 9;
|
||||
|
||||
/*!
|
||||
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
|
||||
that is all ones if it is negative, else zero
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
{
|
||||
// an argument of at most 23 is in size class 1 (one byte) and is moved
|
||||
// to class 0, the first byte, by subtracting 1
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = integer_size_class(argument) - is_immediate;
|
||||
|
||||
// the first byte: the argument itself if it is immediate, else
|
||||
// 0x17 + size_class, selected through a mask
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -24023,29 +24023,45 @@ class binary_writer
|
||||
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_cbor_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Runs of integers are encoded straight into space reserved from the
|
||||
// sink for a whole run: the write pointer stays a local, where it would
|
||||
// otherwise be reloaded from the sink for every value, as a store of a
|
||||
// character may alias it.
|
||||
constexpr std::size_t max_run = max_reserve_size / cbor_integer_head_space;
|
||||
/*!
|
||||
@brief write the elements of the array @a j, runs of integers with
|
||||
@a encode_integer and all other values with @a write_value
|
||||
|
||||
Runs of integers are encoded straight into space reserved from the sink
|
||||
for a whole run: the write pointer stays a local, where it would otherwise
|
||||
be reloaded from the sink for every value, as a store of a character may
|
||||
alias it.
|
||||
*/
|
||||
template<typename EncodeInteger, typename WriteValue>
|
||||
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
|
||||
{
|
||||
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
|
||||
auto it = j.m_data.m_value.array->begin();
|
||||
const auto end = j.m_data.m_value.array->end();
|
||||
while (it != end)
|
||||
{
|
||||
if (it->is_number_integer())
|
||||
{
|
||||
CharType* const start = oa.reserve(max_run * cbor_integer_head_space);
|
||||
CharType* const start = oa.reserve(max_run * integer_encoding_space);
|
||||
CharType* out = start;
|
||||
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
|
||||
{
|
||||
out += encode_cbor_integer(out, *it);
|
||||
out += encode_integer(out, *it);
|
||||
}
|
||||
oa.commit(static_cast<std::size_t>(out - start));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_cbor(*it, depth + 1);
|
||||
write_value(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
@@ -24055,37 +24071,160 @@ class binary_writer
|
||||
@brief write a number_integer or number_unsigned value as a CBOR integer
|
||||
|
||||
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
|
||||
The sign and the type select the major type and the argument through a
|
||||
mask rather than a branch, as they alternate unpredictably in an array of
|
||||
mixed signs.
|
||||
Kept out of line, like the other per-value paths, so that write_cbor stays
|
||||
small (runs of integers in an array are encoded inline instead).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_integer(const BasicJsonType& j)
|
||||
{
|
||||
oa.commit(encode_cbor_integer(oa.reserve(cbor_integer_head_space), j));
|
||||
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_cbor_integer) and return the number of characters
|
||||
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
// All ones for a negative integer, else zero. Up to 64 bits, the sign is
|
||||
// bit 63 of raw (a narrower integer is sign-extended); computing it
|
||||
// with masks keeps GCC from branching on is_signed. A wider integer
|
||||
// has to be compared.
|
||||
const std::uint64_t sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
// ~raw is -1 - n in the 64 bits that are written
|
||||
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
|
||||
}
|
||||
|
||||
/// characters encode_cbor_integer_head() may overwrite: the first byte and
|
||||
/// one 8-byte word
|
||||
static constexpr std::size_t cbor_integer_head_space = 9;
|
||||
/*!
|
||||
@brief whether the MessagePack integers of BasicJsonType are written by
|
||||
@ref encode_msgpack_integer
|
||||
|
||||
It picks the format from the low 64 bits; a wider number type keeps the
|
||||
range checks of @ref write_msgpack on the whole value.
|
||||
*/
|
||||
static constexpr bool msgpack_integers_fit_64_bits() noexcept
|
||||
{
|
||||
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a MessagePack
|
||||
integer without branching on its sign or size
|
||||
|
||||
Same output as @ref write_msgpack: a non-negative integer as positive fixint
|
||||
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
|
||||
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_msgpack_integer) and return the number of characters
|
||||
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
|
||||
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
|
||||
// uint 8/16/32, so x gives the size class of either sign.
|
||||
const std::uint64_t is_negative = sign_mask & 1u;
|
||||
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
|
||||
|
||||
// A fixint is at most 127, or at least -32 (x at most 63). It is in
|
||||
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
|
||||
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
|
||||
const unsigned size_class = integer_size_class(x) - is_fixint;
|
||||
|
||||
// the first byte: the low byte of the integer for a fixint, else the
|
||||
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
|
||||
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
|
||||
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
|
||||
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a MessagePack integer, with range checks on the whole value
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as MessagePack
|
||||
|
||||
See @ref write_cbor_array.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
write_msgpack_array_elements(j, depth, std::integral_constant<bool, msgpack_integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_msgpack_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
|
||||
@@ -240,13 +240,79 @@ void reference_cbor_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||
}
|
||||
}
|
||||
|
||||
// integers at every boundary of the CBOR head sizes, signed and unsigned
|
||||
// the MessagePack encoding of an integer value, encoded independently of
|
||||
// binary_writer
|
||||
void reference_msgpack_integer(std::vector<std::uint8_t>& out, const json& j)
|
||||
{
|
||||
const auto put = [&out](const std::uint8_t marker, const std::uint64_t bits, const int length)
|
||||
{
|
||||
out.push_back(marker);
|
||||
for (int i = length - 1; i >= 0; --i)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(bits >> (8 * i)));
|
||||
}
|
||||
};
|
||||
|
||||
if (j.is_number_unsigned() || j.get<std::int64_t>() >= 0)
|
||||
{
|
||||
const auto u = j.get<std::uint64_t>();
|
||||
if (u < 128)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(u));
|
||||
}
|
||||
else if (u <= 0xFF)
|
||||
{
|
||||
put(0xCC, u, 1);
|
||||
}
|
||||
else if (u <= 0xFFFF)
|
||||
{
|
||||
put(0xCD, u, 2);
|
||||
}
|
||||
else if (u <= 0xFFFFFFFF)
|
||||
{
|
||||
put(0xCE, u, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
put(0xCF, u, 8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto n = j.get<std::int64_t>();
|
||||
const auto bits = static_cast<std::uint64_t>(n);
|
||||
if (n >= -32)
|
||||
{
|
||||
out.push_back(static_cast<std::uint8_t>(bits));
|
||||
}
|
||||
else if (n >= -128)
|
||||
{
|
||||
put(0xD0, bits, 1);
|
||||
}
|
||||
else if (n >= -32768)
|
||||
{
|
||||
put(0xD1, bits, 2);
|
||||
}
|
||||
else if (n >= -2147483648LL)
|
||||
{
|
||||
put(0xD2, bits, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
put(0xD3, bits, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// integers at every boundary of the CBOR and MessagePack sizes, signed and
|
||||
// unsigned
|
||||
std::vector<json> boundary_integers()
|
||||
{
|
||||
std::vector<json> result;
|
||||
const std::uint64_t boundaries[] =
|
||||
{
|
||||
0, 1, 22, 23, 24, 25, 254, 255, 256, 257, 65534, 65535, 65536, 65537,
|
||||
0, 1, 22, 23, 24, 25, 31, 32, 33, 126, 127, 128, 129, 254, 255, 256, 257,
|
||||
32766, 32767, 32768, 32769, 65534, 65535, 65536, 65537,
|
||||
2147483646ULL, 2147483647ULL, 2147483648ULL, 2147483649ULL,
|
||||
4294967294ULL, 4294967295ULL, 4294967296ULL, 4294967297ULL,
|
||||
9223372036854775806ULL, 9223372036854775807ULL, 9223372036854775808ULL,
|
||||
18446744073709551614ULL, 18446744073709551615ULL
|
||||
@@ -268,10 +334,10 @@ std::vector<json> boundary_integers()
|
||||
} // namespace
|
||||
|
||||
// Arrays of integers are written in runs straight into space reserved from the
|
||||
// sink, with a branch-free head; check them against an independent encoder for
|
||||
// sink, with branch-free encodings; check them against independent encoders for
|
||||
// runs shorter and longer than one reservation, broken up by other values, and
|
||||
// through each sink.
|
||||
TEST_CASE("CBOR integer arrays")
|
||||
TEST_CASE("CBOR and MessagePack integer arrays")
|
||||
{
|
||||
const std::vector<json> integers = boundary_integers();
|
||||
|
||||
@@ -289,52 +355,81 @@ TEST_CASE("CBOR integer arrays")
|
||||
CAPTURE(break_every)
|
||||
|
||||
json array = json::array();
|
||||
std::vector<std::uint8_t> expected;
|
||||
std::vector<std::uint8_t> expected_cbor;
|
||||
std::vector<std::uint8_t> expected_msgpack;
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
{
|
||||
if (break_every != 0 && i % (break_every + 1) == break_every)
|
||||
{
|
||||
array.push_back("x");
|
||||
expected.push_back(0x61);
|
||||
expected.push_back('x');
|
||||
expected_cbor.push_back(0x61);
|
||||
expected_cbor.push_back('x');
|
||||
expected_msgpack.push_back(0xA1);
|
||||
expected_msgpack.push_back('x');
|
||||
}
|
||||
else
|
||||
{
|
||||
const json& value = integers[(i * 7) % integers.size()];
|
||||
array.push_back(value);
|
||||
reference_cbor_integer(expected, value);
|
||||
reference_cbor_integer(expected_cbor, value);
|
||||
reference_msgpack_integer(expected_msgpack, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> head;
|
||||
reference_cbor_head(head, 0x80, length);
|
||||
expected.insert(expected.begin(), head.begin(), head.end());
|
||||
expected_cbor.insert(expected_cbor.begin(), head.begin(), head.end());
|
||||
|
||||
CHECK(json::to_cbor(array) == expected);
|
||||
head.clear();
|
||||
if (length <= 15)
|
||||
{
|
||||
head.push_back(static_cast<std::uint8_t>(0x90 + length));
|
||||
}
|
||||
else
|
||||
{
|
||||
head = {0xDC, static_cast<std::uint8_t>(length >> 8), static_cast<std::uint8_t>(length)};
|
||||
}
|
||||
expected_msgpack.insert(expected_msgpack.begin(), head.begin(), head.end());
|
||||
|
||||
CHECK(json::to_cbor(array) == expected_cbor);
|
||||
CHECK(json::to_msgpack(array) == expected_msgpack);
|
||||
|
||||
std::vector<std::uint8_t> through_adapter = {0xAA};
|
||||
json::to_cbor(array, through_adapter);
|
||||
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected);
|
||||
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_cbor);
|
||||
through_adapter = {0xAA};
|
||||
json::to_msgpack(array, through_adapter);
|
||||
CHECK(std::vector<std::uint8_t>(through_adapter.begin() + 1, through_adapter.end()) == expected_msgpack);
|
||||
|
||||
std::ostringstream stream;
|
||||
json::to_cbor(array, stream);
|
||||
const std::string streamed = stream.str();
|
||||
CHECK(std::vector<std::uint8_t>(streamed.begin(), streamed.end()) == expected);
|
||||
std::string streamed = stream.str();
|
||||
CHECK(std::vector<std::uint8_t>(streamed.begin(), streamed.end()) == expected_cbor);
|
||||
stream.str("");
|
||||
json::to_msgpack(array, stream);
|
||||
streamed = stream.str();
|
||||
CHECK(std::vector<std::uint8_t>(streamed.begin(), streamed.end()) == expected_msgpack);
|
||||
|
||||
CHECK(json::from_cbor(expected) == array);
|
||||
CHECK(json::from_cbor(expected_cbor) == array);
|
||||
CHECK(json::from_msgpack(expected_msgpack) == array);
|
||||
|
||||
// the same array as an object member, and as single values
|
||||
// the same array as object members
|
||||
const json object = {{"a", array}, {"b", array}};
|
||||
CHECK(json::from_cbor(json::to_cbor(object)) == object);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(object)) == object);
|
||||
}
|
||||
}
|
||||
|
||||
// single values, outside an array
|
||||
for (const auto& value : integers)
|
||||
{
|
||||
CAPTURE(value)
|
||||
std::vector<std::uint8_t> expected;
|
||||
reference_cbor_integer(expected, value);
|
||||
CHECK(json::to_cbor(value) == expected);
|
||||
expected.clear();
|
||||
reference_msgpack_integer(expected, value);
|
||||
CHECK(json::to_msgpack(value) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user