#include #include #include #include "dpf.hpp" #include "simde/simde/x86/avx2.h" namespace { template struct scalar_acc { using mod = dpf::bitmore_mod; unsigned acc = 0; unsigned bound = 0; void add(unsigned x, unsigned addend_max) { x &= mod::slot_max; 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_slot(acc); bound = mod::partial_bound; continue; } if (addend_max > mod::partial_bound) { x = mod::partial_reduce_slot(x); addend_max = mod::partial_bound; continue; } if (bound > mod::stable_bound) { acc = mod::partial_reduce_slot(acc); bound = mod::stable_bound; continue; } if (addend_max > mod::stable_bound) { x = mod::partial_reduce_slot(x); addend_max = mod::stable_bound; continue; } break; } acc += x; bound += addend_max; } void insert(unsigned bit) { while (bound > (mod::slot_max >> 1)) { acc = mod::partial_reduce_slot(acc); bound = bound > mod::partial_bound ? mod::partial_bound : mod::stable_bound; } acc = ((acc << 1) | (bit & 1u)) & mod::slot_max; bound = bound * 2u + 1u; } }; template void expect_bytes() { using mod = dpf::bitmore_mod; unsigned span = mod::resume_bound + 1u; unsigned k = 0; while ((span << 1) <= 256u) { span <<= 1; ++k; } EXPECT_EQ(mod::shift_budget, k); EXPECT_EQ(mod::add_budget, 255u - mod::resume_bound); EXPECT_LE((mod::resume_bound + 1u) * (1u << k) - 1u, 255u); EXPECT_GT(span << 1, 256u); constexpr int n = static_cast(sizeof(Reg)); for (unsigned base = 0; base < 256u; ++base) { alignas(32) unsigned char in[32]{}; alignas(32) unsigned char out[32]{}; for (int i = 0; i < n; ++i) in[i] = static_cast((base + static_cast(i) * 13u) & 255u); Reg x; std::memcpy(&x, in, sizeof(Reg)); auto partial = mod::partial_reduce(x); std::memcpy(out, &partial, sizeof(Reg)); for (int i = 0; i < n; ++i) { const unsigned expect = mod::partial_reduce_byte(in[i]); if (out[i] != expect || expect > mod::partial_bound || expect % M != in[i] % M) { ADD_FAILURE() << "partial M=" << M << " in=" << static_cast(in[i]) << " got=" << static_cast(out[i]) << " expect=" << expect; return; } } auto full = mod::full_reduce(x); std::memcpy(out, &full, sizeof(Reg)); for (int i = 0; i < n; ++i) { if (out[i] != in[i] % M) { ADD_FAILURE() << "full M=" << M << " in=" << static_cast(in[i]) << " got=" << static_cast(out[i]); return; } } } } template void expect_accumulator() { constexpr int n = static_cast(sizeof(Reg)); dpf::bitmore_accumulator acc; scalar_acc slots[32]{}; unsigned math[32]{}; for (int step = 0; step < 96; ++step) { alignas(32) unsigned char bytes[32]{}; Reg packed; if (step % 3 == 0) { for (int i = 0; i < n; ++i) { bytes[i] = static_cast(((step * 17 + i * 3) & 1u) | (i & 0xf0)); slots[i].insert(bytes[i]); math[i] = (math[i] * 2u + (bytes[i] & 1u)) % M; } std::memcpy(&packed, bytes, sizeof(Reg)); acc.insert_bit(packed); } else if (step % 3 == 1) { for (int i = 0; i < n; ++i) { bytes[i] = static_cast((step + i) % M); slots[i].add(bytes[i], M - 1u); math[i] = (math[i] + bytes[i]) % M; } std::memcpy(&packed, bytes, sizeof(Reg)); acc.add(packed, M - 1u); } else { for (int i = 0; i < n; ++i) { bytes[i] = static_cast(200u + (i & 15u)); slots[i].add(bytes[i], 255u); math[i] = (math[i] + bytes[i]) % M; } std::memcpy(&packed, bytes, sizeof(Reg)); acc.add(packed, 255u); } if (acc.bound() != slots[0].bound || acc.bound() > 255u) { ADD_FAILURE() << "bound M=" << M << " step=" << step << " simd=" << acc.bound() << " scalar=" << slots[0].bound; return; } alignas(32) unsigned char raw[32]{}; const auto value = acc.value(); std::memcpy(raw, &value, sizeof(Reg)); for (int i = 0; i < n; ++i) { if (raw[i] != slots[i].acc || raw[i] > acc.bound() || raw[i] % M != math[i]) { ADD_FAILURE() << "lane M=" << M << " step=" << step << " i=" << i << " got=" << static_cast(raw[i]) << " expect=" << slots[i].acc << " math=" << math[i] << " bound=" << acc.bound(); return; } } } alignas(32) unsigned char red[32]{}; const auto reduced = acc.reduced(); std::memcpy(red, &reduced, sizeof(Reg)); for (int i = 0; i < n; ++i) { if (red[i] != math[i]) { ADD_FAILURE() << "reduced M=" << M << " i=" << i << " got=" << static_cast(red[i]) << " expect=" << math[i]; return; } } } template void expect_all() { expect_bytes(); expect_bytes(); if constexpr (M <= 128u) { expect_accumulator(); expect_accumulator(); } if constexpr (M < 255u) expect_all(); } } // namespace template void expect_epi16_slots() { using mod = dpf::bitmore_mod; constexpr int lanes = static_cast(sizeof(Reg) / 2u); unsigned span = mod::resume_bound + 1u; unsigned k = 0; const unsigned half = (mod::slot_max + 1u) >> 1; while (span <= half) { span *= 2u; ++k; } EXPECT_EQ(mod::shift_budget, k); EXPECT_EQ(mod::add_budget, mod::slot_max - mod::resume_bound); for (unsigned base = 0; base < 65536u; base += static_cast(lanes)) { alignas(32) std::uint16_t in[16]{}; alignas(32) std::uint16_t out[16]{}; for (int i = 0; i < lanes; ++i) in[i] = static_cast(base + static_cast(i)); Reg x; std::memcpy(&x, in, sizeof(Reg)); auto partial = mod::partial_reduce(x); std::memcpy(out, &partial, sizeof(Reg)); for (int i = 0; i < lanes; ++i) { const unsigned expect = mod::partial_reduce_slot(in[i]); if (out[i] != expect || expect > mod::partial_bound || expect % M != in[i] % M) { ADD_FAILURE() << "partial16 M=" << M << " in=" << in[i] << " got=" << out[i] << " expect=" << expect; return; } } auto full = mod::full_reduce(x); std::memcpy(out, &full, sizeof(Reg)); for (int i = 0; i < lanes; ++i) { if (out[i] != in[i] % M) { ADD_FAILURE() << "full16 M=" << M << " in=" << in[i] << " got=" << out[i]; return; } } } } template void expect_epi16_accumulator() { constexpr int lanes = static_cast(sizeof(Reg) / 2u); dpf::bitmore_accumulator acc; scalar_acc slots[16]{}; unsigned math[16]{}; for (int step = 0; step < 64; ++step) { alignas(32) std::uint16_t words[16]{}; Reg packed; if (step % 3 == 0) { for (int i = 0; i < lanes; ++i) { words[i] = static_cast(((step * 17 + i * 3) & 1) | ((i * 0x1111) & 0xfff0)); slots[i].insert(words[i]); math[i] = (math[i] * 2u + (words[i] & 1u)) % M; } std::memcpy(&packed, words, sizeof(Reg)); acc.insert_bit(packed); } else if (step % 3 == 1) { for (int i = 0; i < lanes; ++i) { words[i] = static_cast((step * 100 + i * 17) % M); slots[i].add(words[i], M - 1u); math[i] = (math[i] + words[i]) % M; } std::memcpy(&packed, words, sizeof(Reg)); acc.add(packed, M - 1u); } else { for (int i = 0; i < lanes; ++i) { words[i] = static_cast(40000u + static_cast(i) * 97u); slots[i].add(words[i], 65535u); math[i] = (math[i] + words[i]) % M; } std::memcpy(&packed, words, sizeof(Reg)); acc.add(packed, 65535u); } if (acc.bound() != slots[0].bound || acc.bound() > 65535u) { ADD_FAILURE() << "bound16 M=" << M << " step=" << step << " simd=" << acc.bound() << " scalar=" << slots[0].bound; return; } alignas(32) std::uint16_t raw[16]{}; const auto value = acc.value(); std::memcpy(raw, &value, sizeof(Reg)); for (int i = 0; i < lanes; ++i) { if (raw[i] != slots[i].acc || raw[i] > acc.bound() || raw[i] % M != math[i]) { ADD_FAILURE() << "lane16 M=" << M << " step=" << step << " i=" << i << " got=" << raw[i] << " expect=" << slots[i].acc << " math=" << math[i] << " bound=" << acc.bound(); return; } } } alignas(32) std::uint16_t red[16]{}; const auto reduced = acc.reduced(); std::memcpy(red, &reduced, sizeof(Reg)); for (int i = 0; i < lanes; ++i) { if (red[i] != math[i]) { ADD_FAILURE() << "reduced16 M=" << M << " i=" << i << " got=" << red[i] << " expect=" << math[i]; return; } } } template void expect_wide() { expect_epi16_slots(); expect_epi16_slots(); if constexpr (M <= 32768u) { expect_epi16_accumulator(); expect_epi16_accumulator(); } } TEST(BitmoreMod, BytesAndBudget) { expect_all<2>(); } TEST(BitmoreMod, Epi16) { expect_wide<2>(); expect_wide<3>(); expect_wide<15>(); expect_wide<127>(); expect_wide<128>(); expect_wide<255>(); expect_wide<256>(); expect_wide<257>(); expect_wide<4095>(); expect_wide<4096>(); expect_wide<4097>(); expect_wide<16384>(); expect_wide<16385>(); expect_wide<32765>(); expect_wide<32767>(); expect_wide<32768>(); expect_wide<32769>(); expect_wide<40000>(); expect_wide<61440>(); expect_wide<65535>(); }