/// @file dpf/bitmore_mod.hpp /// @brief Byte-slot reduction for BitMore moduli through 255. /// @details Hafiz and Henry (PoPETs 2019 §5.3) fold each server's DPF bits /// into an integer and reduce modulo the server count. When that /// count is not a power of two the integer does not fit in a byte /// for long, so each byte is only partially reduced until the end. /// /// A partial step reads the high nibble `h`. Every byte with that /// nibble is at least `16*h`, so `floor(16*h / M) * M` is the largest /// multiple of `M` that is safe to subtract. `pshufb` selects it and /// `sub_epi8` removes it. The byte stays congruent modulo `M` and is /// at most `partial_bound`. For moduli 121..127 that ceiling is 128 /// or more, so a second partial step is what leaves `stable_bound` /// (at most 127). `resume_bound` is the ceiling the accumulator /// actually resumes from: one step through modulus 120 and 128, two /// steps for 121..127. /// /// `add_budget = 255 - resume_bound` is how much can still be added /// before a byte might reach 256. `shift_budget` is how many /// `2*acc + bit` insertions fit in that slack. Both stay positive /// through modulus 128, which is as far as a byte can hold two /// resumed slots or one more bit. `partial_reduce` and `full_reduce` /// themselves stay correct through 255: the two nibble residues sum /// to at most 255 and to less than `2*M`, and one unsigned compare /// subtracts `M`. Above 128 there is no slack left to defer that /// correction across another add. /// /// `bitmore_mod` is the same idea on 16-bit lanes, still on /// AVX2. The top nibble (bits 12..15) selects `floor(4096*h / M)*M` /// through two `pshufb`s, one per byte of that multiple, and /// `sub_epi16` removes it. The accumulator has slack through modulus /// 32768 (`resume_bound` at most 32767, so one more bit fits in the /// lane). A full reduction sums the four nibble residues, which fit /// in the lane for every modulus through 65535, then subtracts `M` /// up to three times. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license. #ifndef LIBDPF_INCLUDE_DPF_BITMORE_MOD_HPP__ #define LIBDPF_INCLUDE_DPF_BITMORE_MOD_HPP__ #include #include #include #include #include #include "hedley/hedley.h" #include "simde/simde/x86/avx2.h" namespace dpf { namespace bitmore_detail { template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg zero() noexcept { if constexpr (std::is_same_v) return simde_mm256_setzero_si256(); else return simde_mm_setzero_si128(); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg load_lut(const std::array & lut) noexcept { if constexpr (std::is_same_v) { simde__m128i table; std::memcpy(&table, lut.data(), 16); return table; } else { alignas(32) unsigned char both[32]; std::memcpy(both, lut.data(), 16); std::memcpy(both + 16, lut.data(), 16); simde__m256i table; std::memcpy(&table, both, 32); return table; } } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg high_nibble(Reg x) noexcept { // `srli_epi16` moves the neighbouring byte's low nibble into bits 4..7. // Masking with `0x0f` leaves this byte's own high nibble. if constexpr (std::is_same_v) { const auto m = simde_mm_set1_epi8(0x0f); return simde_mm_and_si128(simde_mm_srli_epi16(x, 4), m); } else { const auto m = simde_mm256_set1_epi8(0x0f); return simde_mm256_and_si256(simde_mm256_srli_epi16(x, 4), m); } } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg low_nibble(Reg x) noexcept { if constexpr (std::is_same_v) return simde_mm_and_si128(x, simde_mm_set1_epi8(0x0f)); else return simde_mm256_and_si256(x, simde_mm256_set1_epi8(0x0f)); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg shuffle(Reg table, Reg index) noexcept { if constexpr (std::is_same_v) return simde_mm_shuffle_epi8(table, index); else return simde_mm256_shuffle_epi8(table, index); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg add_bytes(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_add_epi8(a, b); else return simde_mm256_add_epi8(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg sub_bytes(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_sub_epi8(a, b); else return simde_mm256_sub_epi8(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg and_bytes(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_and_si128(a, b); else return simde_mm256_and_si256(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg xor_bytes(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_xor_si128(a, b); else return simde_mm256_xor_si256(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg splat_epi8(unsigned char v) noexcept { const auto s = static_cast(v); if constexpr (std::is_same_v) return simde_mm_set1_epi8(s); else return simde_mm256_set1_epi8(s); } /// @brief Unsigned `a > b` per byte. `cmpgt_epi8` is signed; XOR `0x80` maps /// unsigned order onto that signed order. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg cmpgt_epu8(Reg a, Reg b) noexcept { const auto bias = splat_epi8(0x80); const auto aa = xor_bytes(a, bias); const auto bb = xor_bytes(b, bias); if constexpr (std::is_same_v) return simde_mm_cmpgt_epi8(aa, bb); else return simde_mm256_cmpgt_epi8(aa, bb); } /// @brief Shift each byte left by 1 and set bit 0 from `bit`. /// @details `slli_epi16` spills bit 7 into the next byte of the 16-bit lane. /// Clearing bit 0 afterwards drops that spill. Bit 7 of the odd byte /// shifts out of the lane, which is the byte-local shift. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg shift_in_bit(Reg acc, Reg bit) noexcept { const auto one = splat_epi8(1); const auto keep = splat_epi8(0xfe); bit = and_bytes(bit, one); Reg shifted; if constexpr (std::is_same_v) shifted = simde_mm_and_si128(simde_mm_slli_epi16(acc, 1), keep); else shifted = simde_mm256_and_si256(simde_mm256_slli_epi16(acc, 1), keep); if constexpr (std::is_same_v) return simde_mm_or_si128(shifted, bit); else return simde_mm256_or_si256(shifted, bit); } template HEDLEY_CONST constexpr unsigned partial_bound() noexcept { static_assert(LaneBits == 8u || LaneBits == 16u, "bitmore lane width is 8 or 16"); constexpr unsigned block = LaneBits == 8u ? 16u : 4096u; constexpr unsigned tail = block - 1u; unsigned u = 0; for (unsigned h = 0; h < 16u; ++h) { const unsigned q = (block * h / Modulus) * Modulus; const unsigned top = block * h + tail - q; if (top > u) u = top; } return u; } template HEDLEY_CONST constexpr unsigned stable_bound() noexcept { constexpr unsigned block = LaneBits == 8u ? 16u : 4096u; const unsigned lim = partial_bound(); unsigned u = 0; for (unsigned h = 0; h < 16u; ++h) { const unsigned lo = block * h; if (lo > lim) break; unsigned hi = lo + block - 1u; if (hi > lim) hi = lim; const unsigned q = (lo / Modulus) * Modulus; const unsigned top = hi - q; if (top > u) u = top; } return u; } template HEDLEY_CONST constexpr unsigned shift_budget(unsigned bound, unsigned capacity = 256u) noexcept { unsigned k = 0; unsigned span = bound + 1u; const unsigned half = capacity >> 1; while (span <= half) { span *= 2u; ++k; } return k; } template HEDLEY_CONST constexpr std::array partial_lut() noexcept { std::array lut{}; for (unsigned h = 0; h < 16u; ++h) lut[h] = static_cast((16u * h / Modulus) * Modulus); return lut; } template HEDLEY_CONST constexpr std::array low_residue_lut() noexcept { std::array lut{}; for (unsigned n = 0; n < 16u; ++n) lut[n] = static_cast(n % Modulus); return lut; } template HEDLEY_CONST constexpr std::array high_residue_lut() noexcept { std::array lut{}; for (unsigned h = 0; h < 16u; ++h) lut[h] = static_cast((16u * h) % Modulus); return lut; } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg splat_epi16(unsigned v) noexcept { const auto s = static_cast(static_cast(v)); if constexpr (std::is_same_v) return simde_mm_set1_epi16(s); else return simde_mm256_set1_epi16(s); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg add_epi16(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_add_epi16(a, b); else return simde_mm256_add_epi16(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg sub_epi16(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_sub_epi16(a, b); else return simde_mm256_sub_epi16(a, b); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg srli_epi16(Reg a, int imm) noexcept { if constexpr (std::is_same_v) return simde_mm_srli_epi16(a, imm); else return simde_mm256_srli_epi16(a, imm); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg slli_epi16(Reg a, int imm) noexcept { if constexpr (std::is_same_v) return simde_mm_slli_epi16(a, imm); else return simde_mm256_slli_epi16(a, imm); } /// @brief Unsigned `a > b` per 16-bit lane. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg cmpgt_epu16(Reg a, Reg b) noexcept { const auto bias = splat_epi16(0x8000u); const auto aa = xor_bytes(a, bias); const auto bb = xor_bytes(b, bias); if constexpr (std::is_same_v) return simde_mm_cmpgt_epi16(aa, bb); else return simde_mm256_cmpgt_epi16(aa, bb); } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg or_bytes(Reg a, Reg b) noexcept { if constexpr (std::is_same_v) return simde_mm_or_si128(a, b); else return simde_mm256_or_si256(a, b); } /// @brief Look up a 16-bit table entry. `idx` holds the nibble in the low byte /// of each lane and zero in the high byte, so `pshufb` writes `lut[h]` /// into the low byte and `lut[0]` into the high byte. `lut[0]` is 0. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg lookup_u16(Reg idx, const std::array & lo, const std::array & hi) noexcept { const auto lo_v = shuffle(load_lut(lo), idx); const auto hi_v = slli_epi16(shuffle(load_lut(hi), idx), 8); return or_bytes(lo_v, hi_v); } constexpr std::array u16_lo(const std::array & v) noexcept { std::array out{}; for (unsigned i = 0; i < 16u; ++i) out[i] = static_cast(v[i] & 0xffu); return out; } constexpr std::array u16_hi(const std::array & v) noexcept { std::array out{}; for (unsigned i = 0; i < 16u; ++i) out[i] = static_cast(v[i] >> 8); return out; } template HEDLEY_CONST constexpr std::array wide_partial_lut() noexcept { std::array lut{}; for (unsigned h = 0; h < 16u; ++h) lut[h] = static_cast((4096u * h / Modulus) * Modulus); return lut; } template HEDLEY_CONST constexpr std::array wide_residue_lut() noexcept { std::array lut{}; for (unsigned n = 0; n < 16u; ++n) lut[n] = static_cast( (static_cast(Place) * n) % Modulus); return lut; } /// @brief `acc = 2*acc + bit0` inside each 16-bit lane. `slli_epi16` does not /// cross lanes. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW Reg shift_in_bit16(Reg acc, Reg bit) noexcept { const auto one = splat_epi16(1u); bit = and_bytes(bit, one); if constexpr (std::is_same_v) return simde_mm_or_si128(simde_mm_slli_epi16(acc, 1), bit); else return simde_mm256_or_si256(simde_mm256_slli_epi16(acc, 1), bit); } } // namespace bitmore_detail /// @brief Partial and full reduction of `LaneBits`-wide slots modulo `Modulus`. /// @tparam Modulus server count. `2` through `255` for bytes, `2` through `65535` for 16-bit lanes. /// @tparam LaneBits `8` (one byte per slot) or `16` (one AVX2 `epi16` lane per slot) template struct bitmore_mod { static_assert(LaneBits == 8u || LaneBits == 16u, "bitmore lane width is 8 or 16"); static_assert(Modulus >= 2u, "bitmore modulus must be at least 2"); static_assert(LaneBits == 16u || Modulus <= 255u, "bitmore byte reduction: modulus must be in 2..255"); static_assert(LaneBits == 8u || Modulus <= 65535u, "bitmore 16-bit reduction: modulus must be in 2..65535"); static constexpr unsigned modulus = Modulus; static constexpr unsigned lane_bits = LaneBits; static constexpr unsigned slot_max = LaneBits == 8u ? 255u : 65535u; static constexpr unsigned nibble_block = LaneBits == 8u ? 16u : 4096u; static constexpr unsigned nibble_shift = LaneBits == 8u ? 4u : 12u; /// @brief Largest slot one top-nibble partial reduction can leave. static constexpr unsigned partial_bound = bitmore_detail::partial_bound(); /// @brief Largest slot a second partial step can leave, starting from `partial_bound`. static constexpr unsigned stable_bound = bitmore_detail::stable_bound(); /// @brief Ceiling the accumulator resumes from. One step when that already /// fits in the lower half of the lane; otherwise the second step. static constexpr unsigned resume_bound = partial_bound <= (slot_max >> 1) ? partial_bound : stable_bound; /// @brief How much can be added to a resumed slot before it might reach `slot_max + 1`. static constexpr unsigned add_budget = slot_max - resume_bound; /// @brief Bit insertions that fit in `add_budget` after resuming. static constexpr unsigned shift_budget = bitmore_detail::shift_budget(resume_bound, slot_max + 1u); /// @brief `x - floor(block*(x>>shift) / M) * M` inside one slot. /// \complexity One division of a nibble index. HEDLEY_CONST HEDLEY_ALWAYS_INLINE static constexpr unsigned partial_reduce_slot(unsigned x) noexcept { x &= slot_max; const unsigned h = x >> nibble_shift; const unsigned q = (nibble_block * h / Modulus) * Modulus; return x - q; } /// @brief Byte-slot name for `partial_reduce_slot`. HEDLEY_CONST HEDLEY_ALWAYS_INLINE static constexpr unsigned partial_reduce_byte(unsigned x) noexcept { static_assert(LaneBits == 8u, "partial_reduce_byte is the 8-bit slot"); return partial_reduce_slot(x); } /// @brief `x mod Modulus` for one slot. /// \complexity One remainder of a slot. HEDLEY_CONST HEDLEY_ALWAYS_INLINE static constexpr unsigned full_reduce_slot(unsigned x) noexcept { return (x & slot_max) % Modulus; } /// @brief Byte-slot name for `full_reduce_slot`. HEDLEY_CONST HEDLEY_ALWAYS_INLINE static constexpr unsigned full_reduce_byte(unsigned x) noexcept { static_assert(LaneBits == 8u, "full_reduce_byte is the 8-bit slot"); return full_reduce_slot(x); } /// @brief Partial-reduce every byte of `x`. /// @details The high nibble selects `floor(16*h / M) * M`. Subtracting it /// preserves the residue and leaves a byte of at most `partial_bound`. /// \complexity One `pshufb` and one `sub_epi8` per register. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE static Reg partial_reduce(Reg x) noexcept { static_assert(std::is_same_v || std::is_same_v, "bitmore partial_reduce: register must be simde__m128i or simde__m256i"); if constexpr (LaneBits == 8u) { constexpr auto lut = bitmore_detail::partial_lut(); const auto q = bitmore_detail::shuffle( bitmore_detail::load_lut(lut), bitmore_detail::high_nibble(x)); return bitmore_detail::sub_bytes(x, q); } else { constexpr auto lut = bitmore_detail::wide_partial_lut(); constexpr auto lo = bitmore_detail::u16_lo(lut); constexpr auto hi = bitmore_detail::u16_hi(lut); const auto idx = bitmore_detail::srli_epi16(x, 12); const auto q = bitmore_detail::lookup_u16(idx, lo, hi); return bitmore_detail::sub_epi16(x, q); } } /// @brief Fully reduce every byte of `x` into `0 .. Modulus-1`. /// @details `pshufb` maps the low nibble to itself modulo `M` and the high /// nibble to `(16*h) mod M`. The sum is less than `2*M`, so one /// compare subtracts `M` where the sum is still too big. /// \complexity Two `pshufb`s, one byte add, one compare, one byte subtract. template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE static Reg full_reduce(Reg x) noexcept { static_assert(std::is_same_v || std::is_same_v, "bitmore full_reduce: register must be simde__m128i or simde__m256i"); if constexpr (LaneBits == 8u) { constexpr auto lo_lut = bitmore_detail::low_residue_lut(); constexpr auto hi_lut = bitmore_detail::high_residue_lut(); const auto lo = bitmore_detail::shuffle( bitmore_detail::load_lut(lo_lut), bitmore_detail::low_nibble(x)); const auto hi = bitmore_detail::shuffle( bitmore_detail::load_lut(hi_lut), bitmore_detail::high_nibble(x)); const auto sum = bitmore_detail::add_bytes(lo, hi); // Sum of the two residues is at most 255 and less than `2*M`, so one // subtraction finishes the byte. The compare is unsigned: for M > 120 // the sum can exceed 127. const auto limit = bitmore_detail::splat_epi8( static_cast(Modulus - 1u)); const auto ge = bitmore_detail::cmpgt_epu8(sum, limit); const auto corr = bitmore_detail::and_bytes( ge, bitmore_detail::splat_epi8(static_cast(Modulus))); return bitmore_detail::sub_bytes(sum, corr); } else { // Four nibble residues, each `< M`. Their sum fits in a 16-bit lane // for every modulus through 65535 and is less than `4*M`, so three // conditional subtractions finish the lane. constexpr auto r0 = bitmore_detail::wide_residue_lut(); constexpr auto r1 = bitmore_detail::wide_residue_lut(); constexpr auto r2 = bitmore_detail::wide_residue_lut(); constexpr auto r3 = bitmore_detail::wide_residue_lut(); constexpr auto r0_lo = bitmore_detail::u16_lo(r0); constexpr auto r0_hi = bitmore_detail::u16_hi(r0); constexpr auto r1_lo = bitmore_detail::u16_lo(r1); constexpr auto r1_hi = bitmore_detail::u16_hi(r1); constexpr auto r2_lo = bitmore_detail::u16_lo(r2); constexpr auto r2_hi = bitmore_detail::u16_hi(r2); constexpr auto r3_lo = bitmore_detail::u16_lo(r3); constexpr auto r3_hi = bitmore_detail::u16_hi(r3); const auto nib = bitmore_detail::splat_epi16(0x000fu); const auto n0 = bitmore_detail::and_bytes(x, nib); const auto n1 = bitmore_detail::and_bytes(bitmore_detail::srli_epi16(x, 4), nib); const auto n2 = bitmore_detail::and_bytes(bitmore_detail::srli_epi16(x, 8), nib); const auto n3 = bitmore_detail::srli_epi16(x, 12); auto sum = bitmore_detail::lookup_u16(n0, r0_lo, r0_hi); sum = bitmore_detail::add_epi16(sum, bitmore_detail::lookup_u16(n1, r1_lo, r1_hi)); sum = bitmore_detail::add_epi16(sum, bitmore_detail::lookup_u16(n2, r2_lo, r2_hi)); sum = bitmore_detail::add_epi16(sum, bitmore_detail::lookup_u16(n3, r3_lo, r3_hi)); const auto limit = bitmore_detail::splat_epi16(Modulus - 1u); const auto modv = bitmore_detail::splat_epi16(Modulus); for (int step = 0; step < 3; ++step) { const auto ge = bitmore_detail::cmpgt_epu16(sum, limit); const auto corr = bitmore_detail::and_bytes(ge, modv); sum = bitmore_detail::sub_epi16(sum, corr); } return sum; } } }; /// @brief Running slot sum modulo `Modulus`, partial-reduced on a budget. /// @details `bound()` is a proven upper bound on every slot. An add whose /// worst-case total would pass the lane maximum partial-reduces the /// accumulator, then the addend. A modulus whose first partial step /// still sets the high bit takes a second step before two slots fit /// again. `insert_bit` is the MSB-first fold `acc = 2*acc + bit`. /// `reduced()` is the full per-slot residue. The byte accumulator /// stops at modulus 128 and the 16-bit accumulator at 32768; past /// that a resumed slot no longer fits next to another or under a /// shift. `partial_reduce` and `full_reduce` cover the wider ranges. /// @tparam Modulus server count, `2` through `128` for bytes and `32768` for 16-bit lanes /// @tparam Reg `simde__m128i` or `simde__m256i` /// @tparam LaneBits `8` or `16` template class bitmore_accumulator { using mod = bitmore_mod; static_assert(std::is_same_v || std::is_same_v, "bitmore_accumulator: register must be simde__m128i or simde__m256i"); static_assert((LaneBits == 8u && Modulus <= 128u) || (LaneBits == 16u && Modulus <= 32768u), "bitmore_accumulator: no slack past modulus 128 in a byte or 32768 in a 16-bit slot"); static_assert(mod::stable_bound <= (mod::slot_max >> 1), "bitmore_accumulator: second partial step must leave room for one bit"); static_assert(mod::stable_bound * 2u <= mod::slot_max, "bitmore_accumulator: two resumed slots must fit in one lane"); static_assert(mod::shift_budget >= 1u, "bitmore_accumulator: at least one bit insertion after resuming"); public: /// \complexity One zeroed register. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW bitmore_accumulator() noexcept : acc_(bitmore_detail::zero()), bound_(0) { } /// @brief Proven upper bound on every slot in `value()`. HEDLEY_PURE HEDLEY_ALWAYS_INLINE unsigned bound() const noexcept { return bound_; } /// @brief Unreduced slots. Each is `≤ bound()` and congruent to the sum. HEDLEY_ALWAYS_INLINE Reg value() const noexcept { return acc_; } /// @brief Add one slot. /// @param x addends. Each slot must be `≤ addend_max`. /// @param addend_max worst-case slot in `x`, clamped to `slot_max`. Pass /// `slot_max` when the addend is an arbitrary lane; the accumulator /// partial-reduces it if the slack cannot absorb that. /// \complexity At most four partial reductions and one lane add. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void add(Reg x, unsigned addend_max) noexcept { if (addend_max > mod::slot_max) addend_max = mod::slot_max; for (;;) { if (bound_ + addend_max <= mod::slot_max) break; if (bound_ > mod::partial_bound) { acc_ = mod::partial_reduce(acc_); bound_ = mod::partial_bound; continue; } if (addend_max > mod::partial_bound) { x = mod::partial_reduce(x); addend_max = mod::partial_bound; continue; } if (bound_ > mod::stable_bound) { acc_ = mod::partial_reduce(acc_); bound_ = mod::stable_bound; continue; } if (addend_max > mod::stable_bound) { x = mod::partial_reduce(x); addend_max = mod::stable_bound; continue; } break; } if constexpr (LaneBits == 8u) acc_ = bitmore_detail::add_bytes(acc_, x); else acc_ = bitmore_detail::add_epi16(acc_, x); bound_ += addend_max; } /// @brief Fold one BitMore bit: `acc = 2*acc + bit0` inside each slot. /// @param bit bit 0 of each slot is the new low bit. Higher bits are ignored. /// \complexity Up to two partial reductions when the proven bound sets the /// lane's high bit, then a shift and an OR. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void insert_bit(Reg bit) noexcept { while (bound_ > (mod::slot_max >> 1)) { acc_ = mod::partial_reduce(acc_); bound_ = bound_ > mod::partial_bound ? mod::partial_bound : mod::stable_bound; } if constexpr (LaneBits == 8u) acc_ = bitmore_detail::shift_in_bit(acc_, bit); else acc_ = bitmore_detail::shift_in_bit16(acc_, bit); bound_ = bound_ * 2u + 1u; } /// @brief Full residue of every slot, in `0 .. Modulus-1`. /// \complexity One `full_reduce` of the register. HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW HEDLEY_PURE Reg reduced() const noexcept { return mod::full_reduce(acc_); } private: Reg acc_; unsigned bound_; }; } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_BITMORE_MOD_HPP__