From 8380792ff3bd2270b797b793dbca9fa69ed1ab9a Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 15 Oct 2026 22:46:36 +0200 Subject: [PATCH] Regenerate the amalgamation Signed-off-by: Niels Lohmann --- single_include/nlohmann/json.hpp | 596 +++++++++++++++++++++++++------ 1 file changed, 486 insertions(+), 110 deletions(-) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index bcc8e5831..869f6fee0 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -22634,6 +22634,8 @@ NLOHMANN_JSON_NAMESPACE_END #include // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 #endif +// #include + // #include // #include @@ -22653,7 +22655,10 @@ NLOHMANN_JSON_NAMESPACE_END +#include // max +#include // array #include // size_t +#include // memcpy #include // shared_ptr, make_shared #include // basic_string #include // conditional, integral_constant, is_same @@ -22694,32 +22699,170 @@ template struct output_adapter_protocol template using output_adapter_t = std::shared_ptr>; +/// the largest number of characters a writer may reserve() from a sink at once; +/// output_vector_sink also appends writes of at least this size with insert() +constexpr std::size_t max_reserve_size = 256; + /// @brief non-virtual output sink writing into a std::vector /// /// This sink is not part of the virtual output_adapter_protocol hierarchy: it is /// passed to binary_writer by value as a template parameter, so /// write_character()/write_characters() are ordinary (inlinable) calls with no /// vtable lookup and no shared_ptr. It is used for the common -/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below -/// wraps this same sink to provide the virtual interface. +/// `to_cbor`/`to_msgpack`/... into a std::vector. +/// +/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8 +/// bytes - and appending those with push_back() or insert() cost more than +/// encoding them: insert() is an out-of-line call into a routine that can insert +/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps +/// its own write position: it grows the vector geometrically ahead of the +/// position and copies each write to it, so a write is a capacity check and a +/// short copy. The destructor shrinks the vector to the bytes actually written, +/// also when a write throws; shrinking a vector never allocates. The sink owns +/// that trim, so it can be moved but not copied, and the vector must not be used +/// while the sink is alive. template> class output_vector_sink { public: explicit output_vector_sink(std::vector& vec) noexcept + : v(&vec), position(vec.size()) + {} + + output_vector_sink(const output_vector_sink&) = delete; + output_vector_sink& operator=(const output_vector_sink&) = delete; + output_vector_sink& operator=(output_vector_sink&&) = delete; + + output_vector_sink(output_vector_sink&& other) noexcept + : v(other.v), position(other.position) + { + other.v = nullptr; + } + + ~output_vector_sink() + { + if (v != nullptr) + { + v->resize(position); + } + } + + // always inlined: GCC otherwise keeps these out of line in a large + // translation unit, and a call per byte or number costs more than the write + JSON_HEDLEY_ALWAYS_INLINE + void write_character(CharType c) + { + if (JSON_HEDLEY_UNLIKELY(position == v->size())) + { + grow(1); + } + (*v)[position++] = c; + } + + // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null + // pointer with length 0 for empty strings/binary values; memcpy must not + // see it. + JSON_HEDLEY_ALWAYS_INLINE + void write_characters(const CharType* s, std::size_t length) + { + if (JSON_HEDLEY_UNLIKELY(v->size() - position < length)) + { + if (length >= max_reserve_size) + { + append(s, length); + return; + } + grow(length); + } + if (length != 0) + { + std::memcpy(v->data() + position, s, length * sizeof(CharType)); + position += length; + } + } + + /// @brief space for writing up to @a length characters directly + /// + /// Returns a pointer to at least @a length (at most @ref max_reserve_size) + /// writable characters at the write position; @ref commit then keeps the + /// first ones of them. A writer can store whole words there and keep only + /// the bytes it needs, and keep its write pointer in a register across many + /// values instead of updating the sink after each. + JSON_HEDLEY_ALWAYS_INLINE + CharType* reserve(const std::size_t length) + { + JSON_ASSERT(length <= max_reserve_size); + if (JSON_HEDLEY_UNLIKELY(v->size() - position < length)) + { + grow(length); + } + return v->data() + position; + } + + /// keep the first @a length characters written to the space from reserve() + JSON_HEDLEY_ALWAYS_INLINE + void commit(const std::size_t length) noexcept + { + position += length; + } + + private: + /// make room for at least @a length more characters after the position; + /// kept out of line so that the write functions stay small enough for + /// GCC to inline them into the writer + JSON_HEDLEY_NEVER_INLINE + void grow(const std::size_t length) + { + // use the capacity the vector already has, else at least double it; + // never less than 64 characters, so that a small value does not + // reallocate for every few bytes + const std::size_t needed = position + length; + v->resize((std::max)(std::size_t{64}, needed <= v->capacity() + ? v->capacity() + : (std::max)(needed, v->capacity() * 2))); + } + + /// append @a length (at least max_reserve_size) characters that do not fit + /// the space ahead of the position: insert() copies them once into the + /// grown vector, where grow() would first zero the new space and then copy + /// over it - noticeable for a large string or binary value + JSON_HEDLEY_NEVER_INLINE + void append(const CharType* s, const std::size_t length) + { + v->resize(position); + if (v->capacity() - position < length) + { + // grow geometrically, with room for the writes that follow, so + // that the next grow() need not reallocate right away + v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size)); + } + v->insert(v->end(), s, s + length); + position = v->size(); + } + + std::vector* v; + /// number of characters of *v written so far + std::size_t position; +}; + +/// output adapter for byte vectors +template> +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) noexcept : v(vec) {} - void write_character(CharType c) + void write_character(CharType c) override { v.push_back(c); } // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null - // pointer with length 0 for empty strings/binary values. Appending an empty - // range is a no-op; the type-erased path tolerates this via the (unattributed) - // virtual base, and the concrete sink must do the same. - void write_characters(const CharType* s, std::size_t length) + // pointer with length 0 for empty strings/binary values; appending an empty + // range is a no-op + void write_characters(const CharType* s, std::size_t length) override { v.insert(v.end(), s, s + length); } @@ -22728,33 +22871,6 @@ class output_vector_sink std::vector& v; }; -/// output adapter for byte vectors -/// -/// The appending itself lives in output_vector_sink; this class only adds the -/// virtual output_adapter_protocol interface on top of it, so both the -/// type-erased and the templated path share one implementation. -template> -class output_vector_adapter : public output_adapter_protocol -{ - public: - explicit output_vector_adapter(std::vector& vec) noexcept - : sink(vec) - {} - - void write_character(CharType c) override - { - sink.write_character(c); - } - - void write_characters(const CharType* s, std::size_t length) override - { - sink.write_characters(s, length); - } - - private: - output_vector_sink sink; -}; - #ifndef JSON_NO_IO /// output adapter for output streams template @@ -22834,8 +22950,24 @@ class output_adapter_sink oa->write_characters(s, length); } + /// space for writing up to @a length characters (see + /// output_vector_sink::reserve) + CharType* reserve(const std::size_t length) noexcept + { + JSON_ASSERT(length <= max_reserve_size); + static_cast(length); + return reserved.data(); + } + + /// write the first @a length characters written to the space from reserve() + void commit(const std::size_t length) + { + oa->write_characters(reserved.data(), length); + } + private: output_adapter_t oa; + std::array reserved{}; }; /// @brief whether std::basic_string has a non-deprecated std::char_traits @@ -23120,24 +23252,12 @@ class binary_writer } case value_t::number_integer: - { - if (j.m_data.m_value.number_integer >= 0) - { - // CBOR does not differentiate between positive signed - // integers and unsigned integers - write_cbor_head(0x00, static_cast(j.m_data.m_value.number_integer)); - } - else - { - // a negative integer n is encoded as -1 - n - write_cbor_head(0x20, static_cast(-1 - j.m_data.m_value.number_integer)); - } - break; - } - case value_t::number_unsigned: { - write_cbor_head(0x00, conditional_static_cast(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_cbor_integer(j); break; } @@ -23172,13 +23292,7 @@ class binary_writer case value_t::array: { - // step 1: write control byte and the array size - write_cbor_head(0x80, j.m_data.m_value.array->size()); - - for (const auto& el : *j.m_data.m_value.array) - { - write_cbor(el, depth + 1); - } + write_cbor_array(j, depth); break; } @@ -23336,54 +23450,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(j.m_data.m_value.number_integer)); - } - else - { - if (j.m_data.m_value.number_integer >= -32) - { - // negative fixnum - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 8 - oa.write_character(to_char_type(0xD0)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 16 - oa.write_character(to_char_type(0xD1)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 32 - oa.write_character(to_char_type(0xD2)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - else - { - // int 64 - oa.write_character(to_char_type(0xD3)); - write_number(static_cast(j.m_data.m_value.number_integer)); - } - } - break; - } - case value_t::number_unsigned: { - write_msgpack_unsigned(static_cast(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()); break; } @@ -23401,13 +23473,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; } @@ -24867,6 +24933,316 @@ class binary_writer } } + /*! + @brief size class of an unsigned integer from its bit length + + 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.) + */ + static unsigned integer_size_class(const std::uint64_t x) noexcept + { + static constexpr std::array size_class_by_leading_zeros = + { + { + 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, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1 + } + }; + // x | 1 keeps count_leading_zeros() away from 0 + return size_class_by_leading_zeros[static_cast(count_leading_zeros(x | 1u))]; + } + + /*! + @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 ((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(j.m_data.m_value.number_integer) + : static_cast(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(is_signed)) & (std::uint64_t{0} - (raw >> 63u)) + : std::uint64_t{0} - static_cast(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(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(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask))); + return encode_first_byte_and_tail(out, first_byte, size_class, argument); + } + + /*! + @brief write an array value (head and elements) as CBOR + + Integer elements, the most common scalar, are written without a call to + @ref write_cbor. The loop is kept out of line: inlined into write_cbor, its + registers would be saved and restored on every call of write_cbor, which + is one call per value nested in an object. + */ + JSON_HEDLEY_NEVER_INLINE + 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); + }); + } + + /*! + @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 + 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 * 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_integer(out, *it); + } + oa.commit(static_cast(out - start)); + } + else + { + write_value(*it); + ++it; + } + } + } + + /*! + @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. + 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(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 + { + 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(sign_mask & 0x20u), raw ^ sign_mask); + } + + /*! + @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(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((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(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(j.m_data.m_value.number_integer)); + } + else + { + if (j.m_data.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa.write_character(to_char_type(0xD0)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa.write_character(to_char_type(0xD1)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else if (j.m_data.m_value.number_integer >= (std::numeric_limits::min)() && + j.m_data.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa.write_character(to_char_type(0xD2)); + write_number(static_cast(j.m_data.m_value.number_integer)); + } + else + { + // int 64 + oa.write_character(to_char_type(0xD3)); + write_number(static_cast(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()); + } + + 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