Ship the TLS mesh, composer, Beaver/Yao/leaf MPC, prep/online paths, apps, and docs so the tree is pushable before elevating share_expr, security_mode, and prep resume. Co-authored-by: Cursor <cursoragent@cursor.com>
2210 lines
89 KiB
C++
2210 lines
89 KiB
C++
#include <gtest/gtest.h>
|
||
#include <tuple>
|
||
|
||
#include "dpf.hpp"
|
||
#include "grotto/fixedpoint.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <array>
|
||
#include <cstdint>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
#include <type_traits>
|
||
#include <vector>
|
||
|
||
namespace
|
||
{
|
||
|
||
simde__m128i g_roots[16];
|
||
int g_ri = 0;
|
||
simde__m128i take_root() { return g_roots[g_ri++]; }
|
||
|
||
std::vector<unsigned char> g_tape(1 << 18);
|
||
std::size_t g_ti = 0;
|
||
void tape_fill(void * p, std::size_t n)
|
||
{
|
||
if (g_ti + n > g_tape.size())
|
||
std::abort();
|
||
std::memcpy(p, g_tape.data() + g_ti, n);
|
||
g_ti += n;
|
||
}
|
||
|
||
struct PadA
|
||
{
|
||
uint64_t n = 1;
|
||
simde__m128i block()
|
||
{
|
||
auto v = simde_mm_set_epi64x(static_cast<long long>(n),
|
||
static_cast<long long>(n * 9 + 3));
|
||
n += 2;
|
||
return v;
|
||
}
|
||
uint8_t bit() { return static_cast<uint8_t>(n++ & 1u); }
|
||
};
|
||
|
||
struct PadB
|
||
{
|
||
uint64_t n = 99;
|
||
simde__m128i block()
|
||
{
|
||
auto v = simde_mm_set_epi64x(static_cast<long long>(n * 7),
|
||
static_cast<long long>(n ^ 0x5a5a));
|
||
n += 3;
|
||
return v;
|
||
}
|
||
uint8_t bit() { return static_cast<uint8_t>((n++ >> 2) & 1u); }
|
||
};
|
||
|
||
void reset_tape_roots()
|
||
{
|
||
g_ri = 0;
|
||
g_ti = 0;
|
||
}
|
||
|
||
void seed_fixed_rng()
|
||
{
|
||
for (int i = 0; i < 16; ++i)
|
||
g_roots[i] = simde_mm_set_epi64x(0x1111 * (i + 1), 0xABCD0000u + i * 17);
|
||
for (std::size_t i = 0; i < g_tape.size(); ++i)
|
||
g_tape[i] = static_cast<unsigned char>(i * 17 + 3);
|
||
}
|
||
|
||
bool same_bytes(const void * a, const void * b, std::size_t n)
|
||
{
|
||
return std::memcmp(a, b, n) == 0;
|
||
}
|
||
|
||
template <typename T>
|
||
constexpr bool is_xor_out_v =
|
||
std::is_same_v<T, dpf::bit> || dpf::utils::is_xor_wrapper_v<T>;
|
||
|
||
template <typename A, typename B>
|
||
auto recon(const A & a, const B & b)
|
||
{
|
||
if constexpr (dpf::is_secret_share_v<A> && dpf::is_secret_share_v<B>)
|
||
return dpf::reconstruct(a, b);
|
||
else if constexpr (is_xor_out_v<A>)
|
||
return static_cast<A>(a ^ b);
|
||
else
|
||
return static_cast<A>(a - b); // subtractive leaf: party0 - party1
|
||
}
|
||
|
||
template <typename Key, std::size_t I = 0>
|
||
bool same_leaf_beaver(const Key & a, const Key & b)
|
||
{
|
||
if constexpr (I < Key::num_outputs)
|
||
{
|
||
const auto & la = a.template leaf<I>();
|
||
const auto & lb = b.template leaf<I>();
|
||
if (!same_bytes(&la, &lb, sizeof(la)))
|
||
return false;
|
||
const auto & ba = a.template beaver<I>();
|
||
const auto & bb = b.template beaver<I>();
|
||
using out_t = typename Key::template output_type_t<I>;
|
||
if constexpr (dpf::is_wildcard_v<out_t>)
|
||
{
|
||
return same_bytes(&ba.output_blind, &bb.output_blind,
|
||
sizeof(ba.output_blind))
|
||
&& same_bytes(&ba.vector_blind, &bb.vector_blind,
|
||
sizeof(ba.vector_blind))
|
||
&& same_bytes(&ba.blinded_vector, &bb.blinded_vector,
|
||
sizeof(ba.blinded_vector))
|
||
&& same_leaf_beaver<Key, I + 1>(a, b);
|
||
}
|
||
return same_bytes(&ba, &bb, sizeof(ba))
|
||
&& same_leaf_beaver<Key, I + 1>(a, b);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
template <typename Key>
|
||
bool same_incr_key(const Key & a, const Key & b)
|
||
{
|
||
return same_bytes(&a.root(), &b.root(), sizeof(a.root()))
|
||
&& same_bytes(a.correction_words().data(), b.correction_words().data(),
|
||
sizeof(a.correction_words()))
|
||
&& same_bytes(a.correction_advice().data(), b.correction_advice().data(),
|
||
sizeof(a.correction_advice()))
|
||
&& same_leaf_beaver(a, b);
|
||
}
|
||
|
||
template <typename Key, std::size_t I = 0>
|
||
bool same_leaf_beaver_classic(const Key & a, const Key & b)
|
||
{
|
||
if constexpr (I < std::tuple_size_v<typename Key::outputs_tuple>)
|
||
{
|
||
const auto & la = a.template leaf<I>();
|
||
const auto & lb = b.template leaf<I>();
|
||
if (!same_bytes(&la, &lb, sizeof(la)))
|
||
return false;
|
||
const auto & ba = a.template beaver<I>();
|
||
const auto & bb = b.template beaver<I>();
|
||
using out_t = typename Key::template output_type_t<I>;
|
||
if constexpr (dpf::is_wildcard_v<out_t>)
|
||
{
|
||
return same_bytes(&ba.output_blind, &bb.output_blind,
|
||
sizeof(ba.output_blind))
|
||
&& same_bytes(&ba.vector_blind, &bb.vector_blind,
|
||
sizeof(ba.vector_blind))
|
||
&& same_bytes(&ba.blinded_vector, &bb.blinded_vector,
|
||
sizeof(ba.blinded_vector))
|
||
&& same_leaf_beaver_classic<Key, I + 1>(a, b);
|
||
}
|
||
return same_bytes(&ba, &bb, sizeof(ba))
|
||
&& same_leaf_beaver_classic<Key, I + 1>(a, b);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
template <typename Key>
|
||
bool same_classic_key(const Key & a, const Key & b)
|
||
{
|
||
if (!same_bytes(&a.root(), &b.root(), sizeof(a.root())))
|
||
return false;
|
||
if (!same_bytes(a.correction_words().data(), b.correction_words().data(),
|
||
sizeof(a.correction_words())))
|
||
return false;
|
||
if (!same_bytes(a.correction_advice().data(), b.correction_advice().data(),
|
||
sizeof(a.correction_advice())))
|
||
return false;
|
||
return same_leaf_beaver_classic(a, b);
|
||
}
|
||
|
||
/// Flip the low bit of the N-bit MSB prefix (neighbor lane for packed leaves).
|
||
template <typename InputT>
|
||
InputT flip_lane_lsb(InputT x, std::size_t prefix, std::size_t bitlen)
|
||
{
|
||
return static_cast<InputT>(x ^ (InputT{1} << (bitlen - prefix)));
|
||
}
|
||
|
||
} // namespace
|
||
|
||
class IncrementalDpfTest : public ::testing::Test
|
||
{
|
||
protected:
|
||
void SetUp() override
|
||
{
|
||
seed_fixed_rng();
|
||
dpf::detail::uniform_bytes_hook = tape_fill;
|
||
reset_tape_roots();
|
||
}
|
||
void TearDown() override
|
||
{
|
||
dpf::detail::uniform_bytes_hook = nullptr;
|
||
}
|
||
};
|
||
|
||
TEST_F(IncrementalDpfTest, ClassicPathByteIdentical)
|
||
{
|
||
uint32_t x = 0x00abcdefu;
|
||
uint32_t y = 0x55555555u;
|
||
reset_tape_roots();
|
||
auto via_args = dpf::make_dpf(dpf::make_dpfargs(x, y), take_root);
|
||
reset_tape_roots();
|
||
auto via_conv = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
|
||
|
||
using KT = std::decay_t<decltype(via_args.first)>;
|
||
static_assert(std::is_same_v<KT, std::decay_t<decltype(via_conv.first)>>);
|
||
EXPECT_TRUE(same_classic_key(via_args.first, via_conv.first));
|
||
EXPECT_TRUE(same_classic_key(via_args.second, via_conv.second));
|
||
|
||
auto a0 = dpf::eval_point(via_conv.first, x);
|
||
auto a1 = dpf::eval_point(via_conv.second, x);
|
||
EXPECT_EQ(static_cast<uint32_t>(recon(*a0, *a1)), y);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, At10BitDepthAndLanes)
|
||
{
|
||
uint32_t x = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<10>(dpf::bit::one));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::depth, 3u); // 10 - lg(128)=7
|
||
EXPECT_EQ(KT::meta[0].prefix, 10u);
|
||
EXPECT_EQ(KT::meta[0].tree_level, 3u);
|
||
EXPECT_EQ(KT::meta[0].pos_base, 0u); // only / deepest group
|
||
|
||
auto y0 = dpf::eval_point(dpf::out<0, 10>, k0, x);
|
||
auto y1 = dpf::eval_point(dpf::out<0, 10>, k1, x);
|
||
EXPECT_TRUE(static_cast<bool>(recon(*y0, *y1)));
|
||
|
||
uint32_t nb = flip_lane_lsb(x, 10, 32);
|
||
auto z0 = dpf::eval_point(dpf::out<0, 10>, k0, nb);
|
||
auto z1 = dpf::eval_point(dpf::out<0, 10>, k1, nb);
|
||
EXPECT_FALSE(static_cast<bool>(recon(*z0, *z1)));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, SameWidthPackedInOneGroup)
|
||
{
|
||
uint32_t x = 0x12345678u;
|
||
auto [k0, k1] =
|
||
dpf::make_dpf(x, dpf::at<12>(uint8_t{3}, uint8_t{5}, uint8_t{7}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 3u);
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
|
||
EXPECT_EQ(KT::meta[1].group_id, KT::meta[2].group_id);
|
||
EXPECT_EQ(KT::meta[0].index_in_group, 0u);
|
||
EXPECT_EQ(KT::meta[1].index_in_group, 1u);
|
||
EXPECT_EQ(KT::meta[2].index_in_group, 2u);
|
||
// 12 - lg(16)=4 => level 8
|
||
EXPECT_EQ(KT::depth, 8u);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)),
|
||
uint8_t{3});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)),
|
||
uint8_t{5});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)),
|
||
uint8_t{7});
|
||
|
||
uint32_t nb = flip_lane_lsb(x, 12, 32);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)),
|
||
uint8_t{0});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, MixedWidthsSamePrefixSeparateGroups)
|
||
{
|
||
uint32_t x = 0x0f0f0f0fu;
|
||
auto [k0, k1] =
|
||
dpf::make_dpf(x, dpf::at<12>(dpf::bit::one, uint8_t{9}, uint16_t{0xabcd}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
// bit level 12-7=5, u8 level 12-4=8, u16 level 12-3=9 — wait, same prefix
|
||
// different lg => different tree levels!
|
||
EXPECT_EQ(KT::meta[0].tree_level, 5u);
|
||
EXPECT_EQ(KT::meta[1].tree_level, 8u);
|
||
EXPECT_EQ(KT::meta[2].tree_level, 9u);
|
||
EXPECT_NE(KT::meta[0].group_id, KT::meta[1].group_id);
|
||
EXPECT_NE(KT::meta[1].group_id, KT::meta[2].group_id);
|
||
EXPECT_EQ(KT::depth, 9u);
|
||
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)),
|
||
uint8_t{9});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 12>, k0, x), *dpf::eval_point(dpf::out<2, 12>, k1, x)),
|
||
uint16_t{0xabcd});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, ManyLevelsManyTypes)
|
||
{
|
||
uint32_t x = 0xa5a5a5a5u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<14>(uint8_t{2}, uint8_t{4}),
|
||
dpf::at<18>(uint16_t{1000}),
|
||
dpf::at<22>(uint32_t{0x11111111u}),
|
||
uint64_t{0x2222222233333333ull},
|
||
dpf::xor_wrapper<uint32_t>{0xdeadbeefu},
|
||
dpf::bit{true});
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 8u);
|
||
// deepest is full-domain u64/xor/bit at level 32-lg
|
||
EXPECT_EQ(KT::deepest_prefix, 32u);
|
||
EXPECT_EQ(KT::meta[0].tree_level, 3u); // at<10>(bit): 10-7
|
||
EXPECT_EQ(KT::meta[1].tree_level, 10u); // at<14>(u8): 14-4
|
||
EXPECT_EQ(KT::meta[3].tree_level, 15u); // at<18>(u16): 18-3
|
||
EXPECT_EQ(KT::meta[4].tree_level, 20u); // at<22>(u32): 22-2
|
||
EXPECT_EQ(KT::depth, 31u); // u64: 32-1
|
||
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)),
|
||
uint8_t{2});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)),
|
||
uint8_t{4});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 18>, k0, x), *dpf::eval_point(dpf::out<3, 18>, k1, x)),
|
||
uint16_t{1000});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 22>, k0, x), *dpf::eval_point(dpf::out<4, 22>, k1, x)),
|
||
uint32_t{0x11111111u});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)),
|
||
uint64_t{0x2222222233333333ull});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 32>, k0, x), *dpf::eval_point(dpf::out<6, 32>, k1, x)),
|
||
dpf::xor_wrapper<uint32_t>{0xdeadbeefu});
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x))));
|
||
|
||
// Leave the 10-bit MSB prefix entirely.
|
||
uint32_t off_pref = x ^ (1u << 31);
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_pref), *dpf::eval_point(dpf::out<0, 10>, k1, off_pref))));
|
||
// Neighbor lane within the same prefix node.
|
||
uint32_t off_lane = flip_lane_lsb(x, 10, 32);
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, off_lane), *dpf::eval_point(dpf::out<0, 10>, k1, off_lane))));
|
||
// Full-domain off-point.
|
||
uint32_t off = x ^ 1u;
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, off), *dpf::eval_point(dpf::out<5, 32>, k1, off)),
|
||
uint64_t{0});
|
||
|
||
// eval_point() defaults to deepest output (first at prefix 32 = slot 5)
|
||
EXPECT_EQ(KT::deepest_output, 5u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, x), *dpf::eval_point(k1, x)),
|
||
uint64_t{0x2222222233333333ull});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6>, k0, x), *dpf::eval_point(dpf::out<6>, k1, x)),
|
||
dpf::xor_wrapper<uint32_t>{0xdeadbeefu});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, ManySameLevelGroupsHighPosBase)
|
||
{
|
||
// Same tree level for several widths by choosing N = level + lg(opl).
|
||
// level 10: bit needs N=17, u8 needs N=14, u16 needs N=13, u32 needs N=12
|
||
// Different levels. To share a level use matching N-lg.
|
||
// Force many groups at the *deepest* level with distinct widths — pos starts 0.
|
||
// For non-final: put many groups on a shallow shared level.
|
||
// bit@10 (lvl3), and also use at<10> with only bits in multiple at<>? same group.
|
||
// Use distinct prefixes that collide on level via different types:
|
||
// at<10>(bit) lvl 3, at<7>(u8) lvl 3, at<6>(u16) lvl 3, at<5>(u32) lvl 3
|
||
uint32_t x = 0x7f3a9c1bu;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<7>(uint8_t{11}),
|
||
dpf::at<6>(uint16_t{22}),
|
||
dpf::at<5>(uint32_t{33}),
|
||
// another batch at level 8: u8@12, u16@11, u32@10 — wait u32@10 is lvl 8
|
||
dpf::at<12>(uint8_t{44}),
|
||
dpf::at<11>(uint16_t{55}),
|
||
dpf::at<10>(uint32_t{66}),
|
||
// deepest full domain
|
||
uint64_t{77});
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].tree_level, 3u);
|
||
EXPECT_EQ(KT::meta[1].tree_level, 3u);
|
||
EXPECT_EQ(KT::meta[2].tree_level, 3u);
|
||
EXPECT_EQ(KT::meta[3].tree_level, 3u);
|
||
// four groups at level 3: pos bases 2,3,4,5 (each 1 block)
|
||
EXPECT_EQ(KT::meta[0].pos_base, 2u);
|
||
EXPECT_EQ(KT::meta[1].pos_base, 3u);
|
||
EXPECT_EQ(KT::meta[2].pos_base, 4u);
|
||
EXPECT_EQ(KT::meta[3].pos_base, 5u);
|
||
|
||
EXPECT_EQ(KT::meta[4].tree_level, 8u);
|
||
EXPECT_EQ(KT::meta[5].tree_level, 8u);
|
||
EXPECT_EQ(KT::meta[6].tree_level, 8u);
|
||
EXPECT_EQ(KT::meta[4].pos_base, 2u);
|
||
EXPECT_EQ(KT::meta[5].pos_base, 3u);
|
||
EXPECT_EQ(KT::meta[6].pos_base, 4u);
|
||
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 7>, k0, x), *dpf::eval_point(dpf::out<1, 7>, k1, x)),
|
||
uint8_t{11});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 6>, k0, x), *dpf::eval_point(dpf::out<2, 6>, k1, x)),
|
||
uint16_t{22});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 5>, k0, x), *dpf::eval_point(dpf::out<3, 5>, k1, x)),
|
||
uint32_t{33});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 12>, k0, x), *dpf::eval_point(dpf::out<4, 12>, k1, x)),
|
||
uint8_t{44});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 11>, k0, x), *dpf::eval_point(dpf::out<5, 11>, k1, x)),
|
||
uint16_t{55});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<6, 10>, k0, x), *dpf::eval_point(dpf::out<6, 10>, k1, x)),
|
||
uint32_t{66});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 32>, k0, x), *dpf::eval_point(dpf::out<7, 32>, k1, x)),
|
||
uint64_t{77});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, NinePlusGroupsScales)
|
||
{
|
||
// 9 distinct (prefix,width) groups — previously the hard 8-group cap.
|
||
uint32_t x = 0x10203040u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<11>(dpf::bit::one),
|
||
dpf::at<12>(dpf::bit::one),
|
||
dpf::at<13>(dpf::bit::one),
|
||
dpf::at<14>(dpf::bit::one),
|
||
dpf::at<15>(dpf::bit::one),
|
||
dpf::at<16>(dpf::bit::one),
|
||
dpf::at<17>(dpf::bit::one),
|
||
dpf::at<18>(dpf::bit::one));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 9u);
|
||
std::size_t ng = 0;
|
||
for (std::size_t i = 0; i < 9; ++i)
|
||
ng = std::max(ng, KT::meta[i].group_id + 1);
|
||
EXPECT_EQ(ng, 9u);
|
||
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<8, 18>, k0, x), *dpf::eval_point(dpf::out<8, 18>, k1, x))));
|
||
uint32_t off = x ^ (1u << 20);
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<8, 18>, k0, off), *dpf::eval_point(dpf::out<8, 18>, k1, off))));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DealerMatchesDoernerShelat)
|
||
{
|
||
uint32_t x = 0x00abcdefu;
|
||
uint32_t x0 = 0x12345678u;
|
||
uint32_t x1 = x ^ x0;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<14>(uint8_t{3}, uint8_t{5}),
|
||
uint32_t{9},
|
||
dpf::xor_wrapper<uint16_t>{0xcafe});
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<14>(uint8_t{3}, uint8_t{5}),
|
||
uint32_t{9},
|
||
dpf::xor_wrapper<uint16_t>{0xcafe});
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadB> rngb{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds2 = dpf::make_dpf_doerner_shelat(x0, x1, rngb,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<14>(uint8_t{3}, uint8_t{5}),
|
||
uint32_t{9},
|
||
dpf::xor_wrapper<uint16_t>{0xcafe});
|
||
EXPECT_TRUE(same_incr_key(ds.first, ds2.first));
|
||
EXPECT_TRUE(same_incr_key(ds.second, ds2.second));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, IntermediateWildcardSameKey)
|
||
{
|
||
uint32_t x = 0x55aa55aau;
|
||
uint32_t x0 = 0x0f0f0f0fu;
|
||
uint32_t x1 = x ^ x0;
|
||
dpf::wildcard_value<uint32_t> wc;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<16>(wc),
|
||
uint64_t{42});
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<10>(dpf::bit::one), dpf::at<16>(wc), uint64_t{42});
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
|
||
// concrete outputs still reconstruct
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, x), *dpf::eval_point(dpf::out<0, 10>, dealer.second, x))));
|
||
EXPECT_EQ(
|
||
recon(*dpf::eval_point(dpf::out<2, 32>, dealer.first, x),
|
||
*dpf::eval_point(dpf::out<2, 32>, dealer.second, x)),
|
||
uint64_t{42});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EvalSweepAroundPoint)
|
||
{
|
||
uint16_t x = 0x1234;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<8>(dpf::bit::one),
|
||
uint32_t{0xabcdef01u});
|
||
|
||
using path_t = dpf::nonmemoizing_path_memoizer<std::decay_t<decltype(k0)>>;
|
||
path_t p0{}, p1{};
|
||
|
||
auto prefix8 = [](uint16_t q) {
|
||
return static_cast<uint16_t>(q >> (16 - 8));
|
||
};
|
||
const auto xp = prefix8(x);
|
||
|
||
for (int d = -64; d <= 64; ++d)
|
||
{
|
||
uint16_t q = static_cast<uint16_t>(x + d);
|
||
auto b0 = dpf::eval_point(dpf::out<0, 8>, k0, q, p0);
|
||
auto b1 = dpf::eval_point(dpf::out<0, 8>, k1, q, p1);
|
||
auto u0 = dpf::eval_point(dpf::out<1, 16>, k0, q, p0);
|
||
auto u1 = dpf::eval_point(dpf::out<1, 16>, k1, q, p1);
|
||
|
||
EXPECT_EQ(static_cast<bool>(recon(*b0, *b1)), prefix8(q) == xp) << "q=" << q;
|
||
uint32_t expect = (q == x) ? 0xabcdef01u : 0u;
|
||
EXPECT_EQ(recon(*u0, *u1), expect) << "q=" << q;
|
||
}
|
||
|
||
// Explicitly leave the 8-bit MSB bucket.
|
||
uint16_t other = static_cast<uint16_t>(x ^ 0x8000);
|
||
EXPECT_NE(prefix8(other), xp);
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 8>, k0, other), *dpf::eval_point(dpf::out<0, 8>, k1, other))));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, SignedInput)
|
||
{
|
||
int32_t x = -1000;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<12>(uint16_t{7}),
|
||
int32_t{42});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)),
|
||
uint16_t{7});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x), *dpf::eval_point(dpf::out<1, 32>, k1, x)),
|
||
int32_t{42});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, k0, x + 1), *dpf::eval_point(dpf::out<1, 32>, k1, x + 1)),
|
||
int32_t{0});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, FullDomainAtEqualsBare)
|
||
{
|
||
uint32_t x = 0x9999u;
|
||
uint32_t y = 12345u;
|
||
reset_tape_roots();
|
||
auto bare = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, y);
|
||
reset_tape_roots();
|
||
// at<32> same width alone should still be classic path
|
||
auto placed = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::at<32>(y));
|
||
using BareT = std::decay_t<decltype(bare.first)>;
|
||
using PlacedT = std::decay_t<decltype(placed.first)>;
|
||
static_assert(std::is_same_v<BareT, PlacedT>);
|
||
EXPECT_TRUE(same_classic_key(bare.first, placed.first));
|
||
EXPECT_TRUE(same_classic_key(bare.second, placed.second));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DepthZeroLeaf)
|
||
{
|
||
// at<7>(bit): tree level 0, no correction words.
|
||
uint32_t x = 0x00ffffffu;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<7>(dpf::bit::one));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::depth, 0u);
|
||
EXPECT_EQ(KT::meta[0].pos_base, 0u);
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 7>, k0, x), *dpf::eval_point(dpf::out<0, 7>, k1, x))));
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 7>, k0, flip_lane_lsb(x, 7, 32)), *dpf::eval_point(dpf::out<0, 7>, k1, flip_lane_lsb(x, 7, 32)))));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, MemoizedPathEval)
|
||
{
|
||
uint32_t x = 0xabcdef01u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one), uint32_t{1234});
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
dpf::basic_path_memoizer<KT> p0{}, p1{};
|
||
|
||
for (uint32_t q : {x, x ^ 1u, x ^ 0x80000000u, 0u})
|
||
{
|
||
auto e0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q, p0),
|
||
*dpf::eval_point(dpf::out<0, 10>, k1, q, p1));
|
||
auto e1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q, p0),
|
||
*dpf::eval_point(dpf::out<1, 32>, k1, q, p1));
|
||
auto r0 = recon(*dpf::eval_point(dpf::out<0, 10>, k0, q),
|
||
*dpf::eval_point(dpf::out<0, 10>, k1, q));
|
||
auto r1 = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q),
|
||
*dpf::eval_point(dpf::out<1, 32>, k1, q));
|
||
EXPECT_EQ(static_cast<bool>(e0), static_cast<bool>(r0));
|
||
EXPECT_EQ(e1, r1);
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DsEvalAgreesWithDealer)
|
||
{
|
||
uint32_t x = 0x31415926u;
|
||
uint32_t x0 = 0x27182818u;
|
||
uint32_t x1 = x ^ x0;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<10>(dpf::bit::one), uint32_t{99});
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<10>(dpf::bit::one), uint32_t{99});
|
||
|
||
uint32_t pts[] = {x, x ^ 1u, 0u, 0xffffffffu, 0x80000000u};
|
||
for (uint32_t q : pts)
|
||
{
|
||
EXPECT_EQ(static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, dealer.first, q), *dpf::eval_point(dpf::out<0, 10>, dealer.second, q))),
|
||
static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, ds.first, q), *dpf::eval_point(dpf::out<0, 10>, ds.second, q))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, dealer.first, q),
|
||
*dpf::eval_point(dpf::out<1, 32>, dealer.second, q)),
|
||
recon(*dpf::eval_point(dpf::out<1, 32>, ds.first, q),
|
||
*dpf::eval_point(dpf::out<1, 32>, ds.second, q)));
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, SixteenUint8PackedOneLevel)
|
||
{
|
||
// u8 has opl=16: fill an entire packed node at at<12> (level 8).
|
||
uint32_t x = 0x4c1d2e3fu;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<12>(
|
||
uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4},
|
||
uint8_t{5}, uint8_t{6}, uint8_t{7}, uint8_t{8},
|
||
uint8_t{9}, uint8_t{10}, uint8_t{11}, uint8_t{12},
|
||
uint8_t{13}, uint8_t{14}, uint8_t{15}, uint8_t{16}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 16u);
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[15].group_id);
|
||
EXPECT_EQ(KT::meta[15].index_in_group, 15u);
|
||
EXPECT_EQ(KT::depth, 8u);
|
||
EXPECT_EQ(KT::meta[0].pos_base, 0u);
|
||
|
||
auto check = [&](auto idx, uint8_t expect) {
|
||
constexpr std::size_t I = decltype(idx)::value;
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<I, 12>, k0, x), *dpf::eval_point(dpf::out<I, 12>, k1, x)),
|
||
expect);
|
||
};
|
||
check(std::integral_constant<std::size_t, 0>{}, uint8_t{1});
|
||
check(std::integral_constant<std::size_t, 1>{}, uint8_t{2});
|
||
check(std::integral_constant<std::size_t, 7>{}, uint8_t{8});
|
||
check(std::integral_constant<std::size_t, 15>{}, uint8_t{16});
|
||
|
||
uint32_t nb = flip_lane_lsb(x, 12, 32);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, nb), *dpf::eval_point(dpf::out<0, 12>, k1, nb)),
|
||
uint8_t{0});
|
||
uint32_t off_pref = x ^ (1u << 31);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 12>, k0, off_pref),
|
||
*dpf::eval_point(dpf::out<15, 12>, k1, off_pref)),
|
||
uint8_t{0});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EightUint16AndFourUint32Packed)
|
||
{
|
||
uint32_t x = 0x11121314u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<15>(
|
||
uint16_t{100}, uint16_t{200}, uint16_t{300}, uint16_t{400},
|
||
uint16_t{500}, uint16_t{600}, uint16_t{700}, uint16_t{800}),
|
||
dpf::at<20>(
|
||
uint32_t{1000}, uint32_t{2000}, uint32_t{3000}, uint32_t{4000}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 12u);
|
||
// u16@15 => level 12; u32@20 => level 18
|
||
EXPECT_EQ(KT::meta[0].tree_level, 12u);
|
||
EXPECT_EQ(KT::meta[8].tree_level, 18u);
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
|
||
EXPECT_EQ(KT::meta[8].group_id, KT::meta[11].group_id);
|
||
EXPECT_NE(KT::meta[0].group_id, KT::meta[8].group_id);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 15>, k0, x), *dpf::eval_point(dpf::out<0, 15>, k1, x)),
|
||
uint16_t{100});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 15>, k0, x), *dpf::eval_point(dpf::out<7, 15>, k1, x)),
|
||
uint16_t{800});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 20>, k0, x), *dpf::eval_point(dpf::out<8, 20>, k1, x)),
|
||
uint32_t{1000});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<11, 20>, k0, x), *dpf::eval_point(dpf::out<11, 20>, k1, x)),
|
||
uint32_t{4000});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, ManyBitsOnePrefix)
|
||
{
|
||
uint32_t x = 0x00c0ffeeu;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(
|
||
dpf::bit::one, dpf::bit::one, dpf::bit{false}, dpf::bit::one,
|
||
dpf::bit{false}, dpf::bit::one, dpf::bit::one, dpf::bit{false}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[7].group_id);
|
||
EXPECT_EQ(KT::depth, 3u);
|
||
|
||
bool expect[] = {true, true, false, true, false, true, true, false};
|
||
auto check_bit = [&](auto idx, bool e) {
|
||
constexpr std::size_t I = decltype(idx)::value;
|
||
EXPECT_EQ(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<I, 10>, k0, x), *dpf::eval_point(dpf::out<I, 10>, k1, x))),
|
||
e);
|
||
};
|
||
check_bit(std::integral_constant<std::size_t, 0>{}, expect[0]);
|
||
check_bit(std::integral_constant<std::size_t, 2>{}, expect[2]);
|
||
check_bit(std::integral_constant<std::size_t, 7>{}, expect[7]);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, FixedpointIntermediateAndFullDomain)
|
||
{
|
||
using fp16 = grotto::fixedpoint<16>;
|
||
using fp8 = grotto::fixedpoint<8, int32_t>;
|
||
uint32_t x = 0x2a2b2c2du;
|
||
fp16 a = fp16::from_raw(0x00010000); // 1.0
|
||
fp16 b = fp16::from_raw(0x00008000); // 0.5
|
||
fp8 c = fp8::from_raw(0x00000100); // 1.0 in Q8.8? frac=8 on int32
|
||
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<12>(a, b),
|
||
dpf::at<18>(c),
|
||
fp16::from_raw(0x00020000)); // 2.0 full-domain
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
// fp16 is 64-bit packable: lg=1 => at<12> level 11; fp8 32-bit lg=2 => at<18> level 16
|
||
EXPECT_EQ(KT::meta[0].tree_level, 11u);
|
||
EXPECT_EQ(KT::meta[1].tree_level, 11u);
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
|
||
EXPECT_EQ(KT::meta[2].tree_level, 16u);
|
||
EXPECT_EQ(KT::depth, 31u); // full-domain fp16: 32-1
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, x), *dpf::eval_point(dpf::out<0, 12>, k1, x)), a);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, k0, x), *dpf::eval_point(dpf::out<1, 12>, k1, x)), b);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 18>, k0, x), *dpf::eval_point(dpf::out<2, 18>, k1, x)), c);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x), *dpf::eval_point(dpf::out<3, 32>, k1, x)),
|
||
fp16::from_raw(0x00020000));
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, k0, flip_lane_lsb(x, 12, 32)),
|
||
*dpf::eval_point(dpf::out<0, 12>, k1, flip_lane_lsb(x, 12, 32))),
|
||
fp16{});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, k0, x ^ 1u), *dpf::eval_point(dpf::out<3, 32>, k1, x ^ 1u)),
|
||
fp16{});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, FixedpointMixedWithIntegralAndBit)
|
||
{
|
||
using fp16 = grotto::fixedpoint<16>;
|
||
uint32_t x = 0x55aa00ffu;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<14>(fp16::from_raw(0x00004000), uint8_t{9}),
|
||
dpf::at<20>(uint32_t{42}, fp16::from_raw(0xffff0000)),
|
||
int64_t{-7});
|
||
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 14>, k0, x), *dpf::eval_point(dpf::out<1, 14>, k1, x)),
|
||
fp16::from_raw(0x00004000));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<2, 14>, k0, x), *dpf::eval_point(dpf::out<2, 14>, k1, x)),
|
||
uint8_t{9});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 20>, k0, x), *dpf::eval_point(dpf::out<3, 20>, k1, x)),
|
||
uint32_t{42});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 20>, k0, x), *dpf::eval_point(dpf::out<4, 20>, k1, x)),
|
||
fp16::from_raw(0xffff0000));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<5, 32>, k0, x), *dpf::eval_point(dpf::out<5, 32>, k1, x)),
|
||
int64_t{-7});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, FixedpointDealerMatchesDoernerShelat)
|
||
{
|
||
using fp16 = grotto::fixedpoint<16>;
|
||
using fp8 = grotto::fixedpoint<8, int32_t>;
|
||
uint32_t x = 0x0abcdef0u;
|
||
uint32_t x0 = 0x11111111u;
|
||
uint32_t x1 = x ^ x0;
|
||
fp16 y = fp16::from_raw(0x00018000);
|
||
fp8 z = fp8::from_raw(0x00000200);
|
||
dpf::wildcard_value<fp16> wc;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<12>(y, z),
|
||
dpf::at<16>(wc),
|
||
fp16::from_raw(0x00030000));
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<12>(y, z), dpf::at<16>(wc), fp16::from_raw(0x00030000));
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 12>, dealer.first, x),
|
||
*dpf::eval_point(dpf::out<0, 12>, dealer.second, x)),
|
||
y);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 12>, dealer.first, x),
|
||
*dpf::eval_point(dpf::out<1, 12>, dealer.second, x)),
|
||
z);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 32>, dealer.first, x),
|
||
*dpf::eval_point(dpf::out<3, 32>, dealer.second, x)),
|
||
fp16::from_raw(0x00030000));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DenseSameLevelManyGroups)
|
||
{
|
||
// 12 groups on tree level 5 via matching N - lg(opl):
|
||
// bit@12, u8@9, u16@8, u32@7, u64@6, xu32@7 — only a few unique.
|
||
// Use distinct bit prefixes all at level 5: N = 5+7 = 12 for bits only,
|
||
// and pad with other widths at level 5.
|
||
// bit N=12, u8 N=9, u16 N=8, u32 N=7, fp8(i32) N=7, xor u32 N=7 — still few.
|
||
// Stack many *bit* prefixes that share level by using N=12 only once...
|
||
// Instead: many groups of different widths all non-final at level 10,
|
||
// each with several packed leaves to push pos_base up.
|
||
uint32_t x = 0x6f5e4d3cu;
|
||
using fp8 = grotto::fixedpoint<8, int32_t>;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
// level 3: four bit prefixes (10,11,12,13) — wait different levels
|
||
// Force level 10 for: u8@14, u16@13, u32@12, u64@11, fp8@12, xu32@12
|
||
dpf::at<14>(uint8_t{1}, uint8_t{2}, uint8_t{3}, uint8_t{4}),
|
||
dpf::at<13>(uint16_t{10}, uint16_t{20}, uint16_t{30}, uint16_t{40}),
|
||
dpf::at<12>(uint32_t{100}, uint32_t{200}),
|
||
dpf::at<11>(uint64_t{1000}, uint64_t{2000}),
|
||
dpf::at<12>(fp8::from_raw(0x10), fp8::from_raw(0x20)),
|
||
dpf::at<12>(dpf::xor_wrapper<uint32_t>{0xaa},
|
||
dpf::xor_wrapper<uint32_t>{0xbb}),
|
||
// deepest
|
||
uint32_t{999});
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].tree_level, 10u); // 14-4
|
||
EXPECT_EQ(KT::meta[4].tree_level, 10u); // 13-3
|
||
EXPECT_EQ(KT::meta[8].tree_level, 10u); // 12-2
|
||
EXPECT_EQ(KT::meta[10].tree_level, 10u); // 11-1
|
||
// groups at level 10 take consecutive pos bases starting at 2
|
||
EXPECT_EQ(KT::meta[0].pos_base, 2u);
|
||
EXPECT_GE(KT::meta[10].pos_base, 2u);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)),
|
||
uint8_t{1});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<3, 14>, k0, x), *dpf::eval_point(dpf::out<3, 14>, k1, x)),
|
||
uint8_t{4});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<4, 13>, k0, x), *dpf::eval_point(dpf::out<4, 13>, k1, x)),
|
||
uint16_t{10});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<7, 13>, k0, x), *dpf::eval_point(dpf::out<7, 13>, k1, x)),
|
||
uint16_t{40});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<8, 12>, k0, x), *dpf::eval_point(dpf::out<8, 12>, k1, x)),
|
||
uint32_t{100});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<10, 11>, k0, x), *dpf::eval_point(dpf::out<10, 11>, k1, x)),
|
||
uint64_t{1000});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 12>, k0, x), *dpf::eval_point(dpf::out<12, 12>, k1, x)),
|
||
fp8::from_raw(0x10));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<14, 12>, k0, x), *dpf::eval_point(dpf::out<14, 12>, k1, x)),
|
||
dpf::xor_wrapper<uint32_t>{0xaa});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 32>, k0, x), *dpf::eval_point(dpf::out<16, 32>, k1, x)),
|
||
uint32_t{999});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, TwelveBitPrefixesPlusFullDomain)
|
||
{
|
||
// 12 distinct bit prefixes (12 groups) plus a full-domain payload.
|
||
uint32_t x = 0x13579bdFu;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::at<11>(dpf::bit::one),
|
||
dpf::at<12>(dpf::bit::one),
|
||
dpf::at<13>(dpf::bit::one),
|
||
dpf::at<14>(dpf::bit::one),
|
||
dpf::at<15>(dpf::bit::one),
|
||
dpf::at<16>(dpf::bit::one),
|
||
dpf::at<17>(dpf::bit::one),
|
||
dpf::at<18>(dpf::bit::one),
|
||
dpf::at<19>(dpf::bit::one),
|
||
dpf::at<20>(dpf::bit::one),
|
||
dpf::at<21>(dpf::bit::one),
|
||
uint64_t{0x1122334455667788ull});
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 13u);
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, x), *dpf::eval_point(dpf::out<0, 10>, k1, x))));
|
||
EXPECT_TRUE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<11, 21>, k0, x), *dpf::eval_point(dpf::out<11, 21>, k1, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<12, 32>, k0, x), *dpf::eval_point(dpf::out<12, 32>, k1, x)),
|
||
uint64_t{0x1122334455667788ull});
|
||
|
||
// Off-prefix for the deepest bit leaf.
|
||
uint32_t off = x ^ (1u << (32 - 21));
|
||
EXPECT_FALSE(static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<11, 21>, k0, off), *dpf::eval_point(dpf::out<11, 21>, k1, off))));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, TwoFullPackedNodesSameLevel)
|
||
{
|
||
// Two separate at<14>(16 x u8) would be same prefix+width => one group.
|
||
// Use at<14> and at<15> both u8: levels 10 and 11.
|
||
// Same level two full nodes: at<14>(16 u8) is one group of 16 (= 16 blocks
|
||
// if each u8 is 1 block — yes blen=1, so 16 blocks in one make_leaves).
|
||
// Add at<13>(8 u16) at level 10 as second group on same level.
|
||
uint32_t x = 0x01020304u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<14>(
|
||
uint8_t{0}, uint8_t{1}, uint8_t{2}, uint8_t{3},
|
||
uint8_t{4}, uint8_t{5}, uint8_t{6}, uint8_t{7},
|
||
uint8_t{8}, uint8_t{9}, uint8_t{10}, uint8_t{11},
|
||
uint8_t{12}, uint8_t{13}, uint8_t{14}, uint8_t{15}),
|
||
dpf::at<13>(
|
||
uint16_t{0x10}, uint16_t{0x20}, uint16_t{0x30}, uint16_t{0x40},
|
||
uint16_t{0x50}, uint16_t{0x60}, uint16_t{0x70}, uint16_t{0x80}));
|
||
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].tree_level, 10u);
|
||
EXPECT_EQ(KT::meta[16].tree_level, 10u);
|
||
EXPECT_EQ(KT::meta[0].pos_base, 0u); // deepest (only) level
|
||
EXPECT_EQ(KT::meta[16].pos_base, 16u); // after 16 u8 blocks
|
||
EXPECT_EQ(KT::num_outputs, 24u);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 14>, k0, x), *dpf::eval_point(dpf::out<0, 14>, k1, x)),
|
||
uint8_t{0});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<15, 14>, k0, x), *dpf::eval_point(dpf::out<15, 14>, k1, x)),
|
||
uint8_t{15});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<16, 13>, k0, x), *dpf::eval_point(dpf::out<16, 13>, k1, x)),
|
||
uint16_t{0x10});
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<23, 13>, k0, x), *dpf::eval_point(dpf::out<23, 13>, k1, x)),
|
||
uint16_t{0x80});
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, IntervalAtMatchesPointEval)
|
||
{
|
||
// at<12>(uint8) => lg_opl=4, tree_level=8, lane domain 2^12.
|
||
uint32_t x = 0x00a5b6c7u;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].tree_level, 8u);
|
||
|
||
const uint32_t lane = x >> (32 - 12);
|
||
const uint32_t from = (lane & ~0xffu);
|
||
const uint32_t to = from + 255u;
|
||
|
||
auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to);
|
||
auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to);
|
||
(void)it0;
|
||
(void)it1;
|
||
|
||
auto y0 = dpf::eval_point(dpf::out<0, 12>, k0, x);
|
||
auto y1 = dpf::eval_point(dpf::out<0, 12>, k1, x);
|
||
const auto expect = recon(*y0, *y1);
|
||
|
||
const std::size_t opl = KT::template outputs_per_leaf_of<0>;
|
||
const std::size_t idx = static_cast<std::size_t>(lane - from);
|
||
const std::size_t leaf = idx / opl;
|
||
const std::size_t off = idx % opl;
|
||
EXPECT_EQ(recon(buf0[leaf * opl + off], buf1[leaf * opl + off]), expect);
|
||
|
||
const uint32_t other_lane = (lane ^ 1u);
|
||
if (other_lane >= from && other_lane <= to)
|
||
{
|
||
const std::size_t oidx = static_cast<std::size_t>(other_lane - from);
|
||
EXPECT_EQ(recon(buf0[(oidx / opl) * opl + (oidx % opl)],
|
||
buf1[(oidx / opl) * opl + (oidx % opl)]),
|
||
uint8_t{0});
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, FullAtDeepestAndSequenceAt)
|
||
{
|
||
uint32_t x = 0x11121314u;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<8>(uint8_t{7}), uint32_t{99});
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
constexpr auto I = KT::deepest_output;
|
||
constexpr auto N = KT::meta[I].prefix;
|
||
EXPECT_EQ(N, 32u);
|
||
|
||
std::array<uint32_t, 3> pts{{x, x ^ 1u, x ^ 0x100u}};
|
||
auto buf0 = dpf::make_output_buffer_for<I>(k0, pts.size());
|
||
auto buf1 = dpf::make_output_buffer_for<I>(k1, pts.size());
|
||
dpf::eval_sequence(dpf::out<I, N>, k0, pts.begin(), pts.end(), buf0);
|
||
dpf::eval_sequence(dpf::out<I, N>, k1, pts.begin(), pts.end(), buf1);
|
||
|
||
constexpr auto opl = KT::template outputs_per_leaf_of<I>;
|
||
auto e0 = dpf::eval_point(dpf::out<I, N>, k0, pts[0]);
|
||
auto e1 = dpf::eval_point(dpf::out<I, N>, k1, pts[0]);
|
||
EXPECT_EQ(recon(buf0[e0.offset], buf1[e1.offset]), recon(*e0, *e1));
|
||
auto f0 = dpf::eval_point(dpf::out<I, N>, k0, pts[1]);
|
||
auto f1 = dpf::eval_point(dpf::out<I, N>, k1, pts[1]);
|
||
EXPECT_EQ(recon(buf0[opl + f0.offset], buf1[opl + f1.offset]),
|
||
recon(*f0, *f1));
|
||
|
||
auto buf0b = dpf::make_output_buffer_for<I>(k0, pts.size());
|
||
dpf::eval_sequence(k0, pts.begin(), pts.end(), buf0b);
|
||
EXPECT_EQ(buf0b[e0.offset], buf0[e0.offset]);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, LocalCwProtocolMatchesDsRandomness)
|
||
{
|
||
uint32_t x = 0xabcdef01u;
|
||
uint32_t x0 = 0x11111111u;
|
||
uint32_t x1 = x ^ x0;
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto via_rng = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<10>(dpf::bit::one), uint32_t{5});
|
||
|
||
reset_tape_roots();
|
||
PadA pads{};
|
||
dpf::local_cw_protocol<PadA> proto{pads};
|
||
auto via_proto = dpf::make_dpf_doerner_shelat(x0, x1, take_root, proto,
|
||
dpf::at<10>(dpf::bit::one), uint32_t{5});
|
||
|
||
EXPECT_EQ(static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, via_rng.first, x), *dpf::eval_point(dpf::out<0, 10>, via_rng.second, x))),
|
||
static_cast<bool>(recon(*dpf::eval_point(dpf::out<0, 10>, via_proto.first, x), *dpf::eval_point(dpf::out<0, 10>, via_proto.second, x))));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1, 32>, via_rng.first, x),
|
||
*dpf::eval_point(dpf::out<1, 32>, via_rng.second, x)),
|
||
recon(*dpf::eval_point(dpf::out<1, 32>, via_proto.first, x),
|
||
*dpf::eval_point(dpf::out<1, 32>, via_proto.second, x)));
|
||
}
|
||
|
||
|
||
TEST_F(IncrementalDpfTest, DpfAndCmpSameKey)
|
||
{
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
const uint64_t yt = 42u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(yt));
|
||
EXPECT_TRUE(k0.has_cmp());
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u),
|
||
*dpf::eval_point(k1, alpha ^ 1u)),
|
||
0u);
|
||
|
||
const uint64_t mask = k0.cmp().mask;
|
||
auto recon_cmp = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(recon_cmp(alpha - 1u), yt);
|
||
EXPECT_EQ(recon_cmp(0u), yt);
|
||
EXPECT_EQ(recon_cmp(alpha), 0u);
|
||
EXPECT_EQ(recon_cmp(alpha + 1u), 0u);
|
||
|
||
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt));
|
||
auto recon_ge = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(recon_ge(alpha - 1u), 0u);
|
||
EXPECT_EQ(recon_ge(alpha), yt);
|
||
EXPECT_EQ(recon_ge(alpha + 1u), yt);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpPairAndRelations)
|
||
{
|
||
const uint32_t alpha = 100u;
|
||
const uint64_t yt = 5u, yf = 9u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
|
||
const uint64_t mask = k0.cmp().mask;
|
||
auto r = [&](uint32_t q) {
|
||
return dpf::reconstruct(
|
||
dpf::eval_point(dpf::cmp, k0, q),
|
||
dpf::eval_point(dpf::cmp, k1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(r(50u), yt);
|
||
EXPECT_EQ(r(100u), yf);
|
||
EXPECT_EQ(r(200u), yf);
|
||
|
||
auto [a0, a1] = dpf::make_dpf(alpha, dpf::leq(yt, yf));
|
||
auto rq = [&](auto &x0, auto &x1, uint32_t q) {
|
||
return dpf::reconstruct(
|
||
dpf::eval_point(dpf::cmp, x0, q),
|
||
dpf::eval_point(dpf::cmp, x1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(rq(a0, a1, 100u), yt);
|
||
EXPECT_EQ(rq(a0, a1, 101u), yf);
|
||
|
||
auto [b0, b1] = dpf::make_dpf(alpha, dpf::gt(yt, yf));
|
||
EXPECT_EQ(rq(b0, b1, 100u), yf);
|
||
EXPECT_EQ(rq(b0, b1, 101u), yt);
|
||
|
||
auto [c0, c1] = dpf::make_dpf(alpha, dpf::geq(yt, yf));
|
||
EXPECT_EQ(rq(c0, c1, 99u), yf);
|
||
EXPECT_EQ(rq(c0, c1, 100u), yt);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EqSynonymAndEqAt)
|
||
{
|
||
const uint32_t alpha = 0x12345678u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7}));
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u),
|
||
*dpf::eval_point(k1, alpha ^ 1u)),
|
||
0u);
|
||
|
||
auto [p0, p1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{7}, uint32_t{3}));
|
||
EXPECT_EQ(recon(*dpf::eval_point(p0, alpha), *dpf::eval_point(p1, alpha)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(p0, alpha ^ 1u),
|
||
*dpf::eval_point(p1, alpha ^ 1u)),
|
||
3u);
|
||
|
||
auto [q0, q1] = dpf::make_dpf(alpha, dpf::eq_at<16>(uint16_t{9}),
|
||
dpf::lt_at<8>(uint64_t{5}));
|
||
EXPECT_TRUE(q0.has_cmp());
|
||
EXPECT_EQ(q0.cmp().nbits, 8);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, q0, alpha),
|
||
*dpf::eval_point(dpf::out<0, 16>, q1, alpha)),
|
||
9u);
|
||
const uint64_t m8 = q0.cmp().mask;
|
||
auto top8 = [](uint32_t v) { return v >> 24; };
|
||
auto recon8 = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, q0, q), dpf::eval_point(dpf::cmp, q1, q)) & m8;
|
||
};
|
||
EXPECT_EQ(recon8(alpha), 0u);
|
||
EXPECT_EQ(recon8((top8(alpha) - 1u) << 24), 5u);
|
||
EXPECT_EQ(recon8((top8(alpha) + 1u) << 24), 0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpWithDoernerShelat)
|
||
{
|
||
const uint32_t alpha = 0x01020304u;
|
||
const uint32_t x0 = 0x11111111u;
|
||
const uint32_t x1 = alpha ^ x0;
|
||
auto [k0, k1] = dpf::make_dpf_doerner_shelat(x0, x1, uint32_t{3},
|
||
dpf::lt(uint64_t{11}));
|
||
EXPECT_TRUE(k0.has_cmp());
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1), dpf::eval_point(dpf::cmp, k1, alpha - 1)) & mask,
|
||
11u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha), *dpf::eval_point(k1, alpha)),
|
||
3u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpDealerMatchesDoernerShelat)
|
||
{
|
||
uint32_t x = 0x00abcdefu;
|
||
uint32_t x0 = 0x12345678u;
|
||
uint32_t x1 = x ^ x0;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::lt(uint64_t{42}));
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng,
|
||
dpf::at<10>(dpf::bit::one),
|
||
dpf::lt(uint64_t{42}));
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
|
||
// Comparison channel must match (same tree value CWs; δ not clear on keys).
|
||
EXPECT_EQ(dealer.first.cmp().nbits, ds.first.cmp().nbits);
|
||
EXPECT_EQ(dealer.first.cmp().kind, ds.first.cmp().kind);
|
||
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
|
||
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
|
||
ds.first.value_cw().data(),
|
||
dealer.first.value_cw().size() * sizeof(uint64_t)));
|
||
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
|
||
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
|
||
// Shares reconstruct if_false (=0 here).
|
||
EXPECT_EQ(dpf::reconstruct(dealer.first.cmp_addend(), dealer.second.cmp_addend())
|
||
& dealer.first.cmp().mask,
|
||
0u);
|
||
|
||
const uint64_t mask = dealer.first.cmp().mask;
|
||
for (uint32_t q : {x - 1u, x, x + 1u, 0u})
|
||
{
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q))
|
||
& mask,
|
||
dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask);
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpIntervalAndSequenceBuffers)
|
||
{
|
||
const uint16_t alpha = 0x00aau;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
|
||
EXPECT_TRUE(k0.has_cmp());
|
||
const uint64_t mask = k0.cmp().mask;
|
||
|
||
auto buf0 = dpf::make_output_buffer(dpf::cmp, k0, uint8_t{0x00},
|
||
uint8_t{0xff});
|
||
auto buf1 = dpf::make_output_buffer(dpf::cmp, k1, uint8_t{0x00},
|
||
uint8_t{0xff});
|
||
dpf::eval_interval(dpf::cmp, k0, uint8_t{0x00}, uint8_t{0xff}, buf0);
|
||
dpf::eval_interval(dpf::cmp, k1, uint8_t{0x00}, uint8_t{0xff}, buf1);
|
||
|
||
// alpha top-8 is 0x00; wait alpha=0x00aa so top 8 of 16-bit is 0x00.
|
||
// lt_at<8>: compare on top 8 bits of 16-bit domain = 0x00.
|
||
// Lane 0x00 == alpha prefix => not < => if_false=1
|
||
// Lane < 0x00: none for uint8
|
||
EXPECT_EQ(recon(buf0[0], buf1[0]) & mask, 1u);
|
||
EXPECT_EQ(recon(buf0[1], buf1[1]) & mask, 1u); // 0x01 > 0x00
|
||
|
||
// Use a mid alpha so both sides of the cut appear.
|
||
const uint16_t a2 = 0x8000u;
|
||
auto [p0, p1] = dpf::make_dpf(a2, dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
|
||
auto b0 = dpf::eval_interval(dpf::cmp, p0, uint8_t{0x7f}, uint8_t{0x81});
|
||
auto b1 = dpf::eval_interval(dpf::cmp, p1, uint8_t{0x7f}, uint8_t{0x81});
|
||
// top8(a2)=0x80; lanes 0x7f,0x80,0x81 -> 5, 1, 1
|
||
EXPECT_EQ(recon(b0[0], b1[0]) & mask, 5u);
|
||
EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u);
|
||
EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u);
|
||
|
||
std::array<uint16_t, 3> pts{{static_cast<uint16_t>(a2 - 1), a2,
|
||
static_cast<uint16_t>(a2 + 1)}};
|
||
auto s0 = dpf::make_output_buffer(dpf::cmp, p0, pts.size());
|
||
auto s1 = dpf::make_output_buffer(dpf::cmp, p1, pts.size());
|
||
dpf::eval_sequence(dpf::cmp, p0, pts.begin(), pts.end(), s0);
|
||
dpf::eval_sequence(dpf::cmp, p1, pts.begin(), pts.end(), s1);
|
||
EXPECT_EQ(recon(s0[0], s1[0]) & mask, 5u);
|
||
EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u);
|
||
EXPECT_EQ(recon(s0[2], s1[2]) & mask, 1u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpSharesPathMemoizerWithEvalAt)
|
||
{
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(uint64_t{3}));
|
||
using key_t = std::decay_t<decltype(k0)>;
|
||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||
*dpf::eval_point(k1, alpha, path1)),
|
||
7u);
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u, path0), dpf::eval_point(dpf::cmp, k1, alpha - 1u, path1))
|
||
& mask,
|
||
3u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0), dpf::eval_point(dpf::cmp, k1, alpha, path1))
|
||
& mask,
|
||
0u);
|
||
// Re-eval point with the same memoizers (resume / reuse).
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||
*dpf::eval_point(k1, alpha, path1)),
|
||
7u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpOnlyZeroOutputs)
|
||
{
|
||
const uint32_t alpha = 0x42u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{9}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::num_outputs, 0u);
|
||
EXPECT_TRUE(k0.has_cmp());
|
||
EXPECT_EQ(k0.cmp().nbits, 32);
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u)) & mask,
|
||
9u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, 0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpDomainEdgeTrivial)
|
||
{
|
||
// leq at α = 2^n-1 is always-true; gt there is always-false.
|
||
const uint8_t alpha = 0xffu;
|
||
auto [l0, l1] = dpf::make_dpf(alpha, dpf::leq(uint64_t{7}, uint64_t{1}));
|
||
EXPECT_EQ(l0.cmp().trivial, dpf::cmp_trivial::always_true);
|
||
const uint64_t mask = l0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, uint8_t{0}), dpf::eval_point(dpf::cmp, l1, uint8_t{0})) & mask,
|
||
7u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, l0, alpha), dpf::eval_point(dpf::cmp, l1, alpha)) & mask, 7u);
|
||
// if_true = 7 is split across addends, not stored clear.
|
||
EXPECT_EQ(dpf::reconstruct(l0.cmp_addend(), l1.cmp_addend()) & mask, 7u);
|
||
|
||
auto [g0, g1] = dpf::make_dpf(alpha, dpf::gt(uint64_t{7}, uint64_t{1}));
|
||
EXPECT_EQ(g0.cmp().trivial, dpf::cmp_trivial::always_false);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, uint8_t{0}), dpf::eval_point(dpf::cmp, g1, uint8_t{0})) & mask,
|
||
1u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, 1u);
|
||
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, 1u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpValueCwGroupWidth)
|
||
{
|
||
// uint64 payload → 8-byte value_cw words (the classic full width).
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(uint64_t{42}));
|
||
using K64 = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(sizeof(typename K64::value_cw_word), 8u);
|
||
EXPECT_EQ(K64::cmp_out_bits, 64u);
|
||
|
||
// uint16 payload → 2-byte value_cw words (no padded uint64 on the wire).
|
||
auto [u0, u1] = dpf::make_dpf(alpha, dpf::lt(uint16_t{42}));
|
||
using K16 = std::decay_t<decltype(u0)>;
|
||
EXPECT_EQ(sizeof(typename K16::value_cw_word), 2u);
|
||
EXPECT_EQ(K16::cmp_out_bits, 16u);
|
||
|
||
// bit payload → 1-byte value_cw words (no 8-byte-per-level waste).
|
||
auto [b0, b1] = dpf::make_dpf(alpha, dpf::lt(dpf::bit::one));
|
||
using KB = std::decay_t<decltype(b0)>;
|
||
EXPECT_EQ(sizeof(typename KB::value_cw_word), 1u);
|
||
EXPECT_EQ(KB::cmp_out_bits, 1u);
|
||
// The narrow-width value CWs still reconstruct to the same (public) bytes.
|
||
EXPECT_TRUE(same_bytes(b0.value_cw().data(), b1.value_cw().data(),
|
||
b0.value_cw().size() * sizeof(typename KB::value_cw_word)));
|
||
|
||
// uint16 comparison reconstructs correctly through the narrow words.
|
||
const uint64_t m16 = u0.cmp().mask;
|
||
auto r16 = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, u0, q), dpf::eval_point(dpf::cmp, u1, q)) & m16;
|
||
};
|
||
EXPECT_EQ(r16(alpha - 1u), 42u);
|
||
EXPECT_EQ(r16(alpha), 0u);
|
||
EXPECT_EQ(r16(alpha + 1u), 0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpValueCwGroupWidthDsParity)
|
||
{
|
||
// Narrow-width value CWs must stay byte-identical dealer↔Doerner–Shelat.
|
||
uint32_t x = 0x00abcdefu;
|
||
uint32_t x0 = 0x12345678u;
|
||
uint32_t x1 = x ^ x0;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(x,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::lt(uint16_t{42}));
|
||
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0, x1, rng, dpf::lt(uint16_t{42}));
|
||
|
||
using KT = std::decay_t<decltype(dealer.first)>;
|
||
EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u);
|
||
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
|
||
ds.first.value_cw().data(),
|
||
dealer.first.value_cw().size() * sizeof(typename KT::value_cw_word)));
|
||
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
|
||
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
|
||
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
|
||
|
||
const uint64_t mask = dealer.first.cmp().mask;
|
||
for (uint32_t q : {x - 1u, x, x + 1u, 0u})
|
||
{
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, dealer.first, q), dpf::eval_point(dpf::cmp, dealer.second, q)) & mask,
|
||
dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask);
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpPayloadHiddenOnKeys)
|
||
{
|
||
const uint32_t alpha = 0x55u;
|
||
const uint64_t yt = 42u, yf = 7u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::lt(yt, yf));
|
||
const uint64_t mask = k0.cmp().mask;
|
||
const uint64_t delta = (yt - yf) & mask;
|
||
// δ is not a clear field; if_false is only as additive shares.
|
||
EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf);
|
||
// value CWs are identical (public) and encode δ, not a readable beta field.
|
||
EXPECT_TRUE(same_bytes(k0.value_cw().data(), k1.value_cw().data(),
|
||
k0.value_cw().size() * sizeof(uint64_t)));
|
||
EXPECT_EQ(k0.cw_last(), k1.cw_last());
|
||
(void)delta;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u))
|
||
& mask,
|
||
yt);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha)) & mask, yf);
|
||
|
||
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(yt, yf));
|
||
// geq absorb target is δ + if_false = if_true.
|
||
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha), dpf::eval_point(dpf::cmp, g1, alpha)) & mask, yt);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, alpha - 1u), dpf::eval_point(dpf::cmp, g1, alpha - 1u))
|
||
& mask,
|
||
yf);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, UnifiedEvalTargetSurface)
|
||
{
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||
dpf::at<16>(uint16_t{9}),
|
||
uint32_t{7},
|
||
dpf::lt(uint64_t{3}, uint64_t{1}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
|
||
EXPECT_TRUE(dpf::is_out_v<decltype(dpf::out<0>)>);
|
||
EXPECT_TRUE(dpf::is_cmp_target_v<decltype(dpf::cmp)>);
|
||
|
||
// Point via out<I> / out<I,N> matches out<> point eval.
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
|
||
9u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<1>, k1, alpha)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
|
||
9u);
|
||
|
||
// Cmp via eval_point(cmp).
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha - 1u), dpf::eval_point(dpf::cmp, k1, alpha - 1u))
|
||
& mask,
|
||
3u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha), dpf::eval_point(dpf::cmp, k1, alpha))
|
||
& mask,
|
||
1u);
|
||
|
||
// Interval / sequence target-first for cmp.
|
||
auto b0 = dpf::eval_interval(dpf::cmp, k0,
|
||
static_cast<uint32_t>(alpha - 1u), static_cast<uint32_t>(alpha + 1u));
|
||
auto b1 = dpf::eval_interval(dpf::cmp, k1,
|
||
static_cast<uint32_t>(alpha - 1u), static_cast<uint32_t>(alpha + 1u));
|
||
EXPECT_EQ(recon(b0[0], b1[0]) & mask, 3u);
|
||
EXPECT_EQ(recon(b0[1], b1[1]) & mask, 1u);
|
||
EXPECT_EQ(recon(b0[2], b1[2]) & mask, 1u);
|
||
|
||
std::array<uint32_t, 2> pts{{alpha - 1u, alpha}};
|
||
auto s0 = dpf::make_output_buffer(dpf::cmp, k0, pts.size());
|
||
auto s1 = dpf::make_output_buffer(dpf::cmp, k1, pts.size());
|
||
dpf::eval_sequence(dpf::cmp, k0, pts.begin(), pts.end(), s0);
|
||
dpf::eval_sequence(dpf::cmp, k1, pts.begin(), pts.end(), s1);
|
||
EXPECT_EQ(recon(s0[0], s1[0]) & mask, 3u);
|
||
EXPECT_EQ(recon(s0[1], s1[1]) & mask, 1u);
|
||
|
||
// Point interval via out<> matches out<> interval eval.
|
||
constexpr std::size_t N = 16;
|
||
constexpr std::size_t I = 0;
|
||
auto lane = static_cast<uint16_t>(alpha >> (32 - N));
|
||
auto [buf0, it0] = dpf::eval_interval(dpf::out<I, N>, k0, lane, lane);
|
||
auto [buf1, it1] = dpf::eval_interval(dpf::out<I, N>, k1, lane, lane);
|
||
(void)it0;
|
||
(void)it1;
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<I, N>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<I, N>, k1, alpha)),
|
||
9u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, ClassicKeyHasSlotMetaAndOutEval)
|
||
{
|
||
// Every key now carries a constexpr `slot_meta` table. A classic
|
||
// (single-level, equal-width, no-cmp) key keeps `is_multilevel == false`
|
||
// and still routes through the classic eval fast path, but the unified
|
||
// `out<I>` surface works on it too.
|
||
uint32_t x = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(x, uint32_t{7}, uint32_t{9});
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
|
||
// Foundation traits: has slot meta (so "incremental" trait is true) but is
|
||
// not multi-level.
|
||
EXPECT_TRUE(dpf::is_incremental_dpf_key_v<KT>);
|
||
EXPECT_FALSE(dpf::is_multilevel_key_v<KT>);
|
||
EXPECT_EQ(KT::num_outputs, 2u);
|
||
EXPECT_EQ(KT::cmp_depth, 0u);
|
||
EXPECT_EQ(KT::deepest_output, 0u);
|
||
EXPECT_EQ(KT::meta[0].prefix, 32u);
|
||
EXPECT_EQ(KT::meta[1].prefix, 32u);
|
||
EXPECT_EQ(KT::meta[0].tree_level, KT::depth);
|
||
EXPECT_EQ(KT::meta[1].tree_level, KT::depth);
|
||
// Equal-width classic packing: one group, consecutive block positions.
|
||
// Effective leaf position = pos_base + index_in_group * block_len, which
|
||
// matches the classic `block_offset_of_leaf` layout.
|
||
EXPECT_EQ(KT::meta[0].group_id, KT::meta[1].group_id);
|
||
EXPECT_EQ(KT::meta[0].pos_base, 0u);
|
||
EXPECT_EQ(KT::meta[0].index_in_group, 0u);
|
||
EXPECT_EQ(KT::meta[1].index_in_group, 1u);
|
||
const std::size_t eff0 = KT::meta[0].pos_base
|
||
+ KT::meta[0].index_in_group * KT::meta[0].block_len;
|
||
const std::size_t eff1 = KT::meta[1].pos_base
|
||
+ KT::meta[1].index_in_group * KT::meta[1].block_len;
|
||
EXPECT_EQ(eff0, 0u);
|
||
EXPECT_EQ(eff1, KT::meta[1].block_len);
|
||
|
||
// Unified out<I> eval matches the classic per-slot eval_point.
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x),
|
||
*dpf::eval_point(dpf::out<0>, k1, x)),
|
||
recon(*dpf::eval_point<0>(k0, x), *dpf::eval_point<0>(k1, x)));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0>, k0, x),
|
||
*dpf::eval_point(dpf::out<0>, k1, x)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x),
|
||
*dpf::eval_point(dpf::out<1>, k1, x)),
|
||
9u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, x ^ 1u),
|
||
*dpf::eval_point(dpf::out<1>, k1, x ^ 1u)),
|
||
0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpWildcardAssignMatchesConcrete)
|
||
{
|
||
// Gen the comparison with a wildcard payload (δ opened at 0), then
|
||
// `assign_cmp` the concrete `lt(42)` and check both the (public) value CWs
|
||
// match a byte-for-byte concrete gen on the same tape and that the channel
|
||
// reconstructs the comparison.
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
const uint64_t yt = 42u;
|
||
|
||
reset_tape_roots();
|
||
auto wc = dpf::make_dpf(alpha,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::lt(dpf::wildcard_value<uint64_t>{}));
|
||
using WT = std::decay_t<decltype(wc.first)>;
|
||
static_assert(WT::cmp_is_wildcard, "expected a wildcard cmp key");
|
||
EXPECT_TRUE(wc.first.has_cmp());
|
||
EXPECT_FALSE(wc.first.cmp_assigned());
|
||
|
||
// Evaluating before assignment must throw.
|
||
EXPECT_THROW(dpf::eval_point(dpf::cmp, wc.first, alpha), std::exception);
|
||
|
||
reset_tape_roots();
|
||
auto cc = dpf::make_dpf(alpha,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::lt(yt));
|
||
using CT = std::decay_t<decltype(cc.first)>;
|
||
static_assert(!CT::cmp_is_wildcard, "concrete key must not be wildcard");
|
||
|
||
dpf::assign_cmp(wc.first, wc.second, yt);
|
||
EXPECT_TRUE(wc.first.cmp_assigned());
|
||
EXPECT_TRUE(wc.second.cmp_assigned());
|
||
|
||
// Value CWs / cw_last are public and now identical to the concrete gen.
|
||
EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), cc.first.value_cw().data(),
|
||
wc.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
|
||
EXPECT_TRUE(same_bytes(wc.first.value_cw().data(), wc.second.value_cw().data(),
|
||
wc.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
|
||
EXPECT_EQ(wc.first.cw_last(), cc.first.cw_last());
|
||
|
||
const uint64_t mask = wc.first.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(wc.first.cmp_addend(), wc.second.cmp_addend()) & mask, 0u);
|
||
auto r = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, wc.first, q), dpf::eval_point(dpf::cmp, wc.second, q)) & mask;
|
||
};
|
||
EXPECT_EQ(r(alpha - 1u), yt);
|
||
EXPECT_EQ(r(0u), yt);
|
||
EXPECT_EQ(r(alpha), 0u);
|
||
EXPECT_EQ(r(alpha + 1u), 0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpWildcardPairAndGeqAndInterval)
|
||
{
|
||
// Wildcard with both if_true and if_false, and a geq relation.
|
||
const uint32_t alpha = 100u;
|
||
const uint64_t yt = 5u, yf = 9u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||
dpf::lt(dpf::wildcard_value<uint64_t>{}));
|
||
dpf::assign_cmp(k0, k1, yt, yf);
|
||
const uint64_t mask = k0.cmp().mask;
|
||
auto r = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(r(50u), yt);
|
||
EXPECT_EQ(r(100u), yf);
|
||
EXPECT_EQ(r(200u), yf);
|
||
|
||
// if_false reconstructs from the split addend shares.
|
||
EXPECT_EQ(dpf::reconstruct(k0.cmp_addend(), k1.cmp_addend()) & mask, yf);
|
||
|
||
auto [g0, g1] = dpf::make_dpf(alpha, dpf::geq(dpf::wildcard_value<uint64_t>{}));
|
||
dpf::assign_cmp(g0, g1, yt, yf);
|
||
auto rg = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, g0, q), dpf::eval_point(dpf::cmp, g1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(rg(99u), yf);
|
||
EXPECT_EQ(rg(100u), yt);
|
||
EXPECT_EQ(rg(101u), yt);
|
||
// geq absorb target is δ + if_false = if_true.
|
||
EXPECT_EQ(dpf::reconstruct(g0.cmp_addend(), g1.cmp_addend()) & mask, yt);
|
||
|
||
// Interval eval on an assigned wildcard cmp still works.
|
||
auto b0 = dpf::eval_interval(dpf::cmp, k0,
|
||
static_cast<uint32_t>(99u), static_cast<uint32_t>(101u));
|
||
auto b1 = dpf::eval_interval(dpf::cmp, k1,
|
||
static_cast<uint32_t>(99u), static_cast<uint32_t>(101u));
|
||
EXPECT_EQ(recon(b0[0], b1[0]) & mask, yt); // 99 < 100
|
||
EXPECT_EQ(recon(b0[1], b1[1]) & mask, yf); // 100 !< 100
|
||
EXPECT_EQ(recon(b0[2], b1[2]) & mask, yf); // 101 !< 100
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpWildcardNarrowPayloadAndAt)
|
||
{
|
||
// Narrow (uint16) wildcard payload keeps the group-width value CW words,
|
||
// and `lt_at<N>` prefixes work through the wildcard path.
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||
dpf::at<16>(uint16_t{9}),
|
||
dpf::lt(dpf::wildcard_value<uint16_t>{}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(sizeof(typename KT::value_cw_word), 2u);
|
||
EXPECT_TRUE(KT::cmp_is_wildcard);
|
||
|
||
// The concrete output slot is unaffected by the wildcard cmp.
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<0, 16>, k1, alpha)),
|
||
9u);
|
||
|
||
dpf::assign_cmp(k0, k1, uint16_t{42});
|
||
const uint64_t mask = k0.cmp().mask;
|
||
auto r = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q), dpf::eval_point(dpf::cmp, k1, q)) & mask;
|
||
};
|
||
EXPECT_EQ(r(alpha - 1u), 42u);
|
||
EXPECT_EQ(r(alpha), 0u);
|
||
EXPECT_EQ(r(alpha + 1u), 0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DsWildcardCmpMatchesDealerThenAssign)
|
||
{
|
||
// Local Doerner–Shelat gen of a wildcard comparison payload must be
|
||
// byte-identical to the dealer (δ = 0 CWs + addend blinds), and after
|
||
// `assign_cmp` the patched public CWs / reconstruction must match a
|
||
// concrete `lt(β)` keygen on the same tape.
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
const uint32_t a0 = 0x12345678u;
|
||
const uint32_t a1 = alpha ^ a0;
|
||
const uint64_t yt = 42u;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(alpha,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::lt(dpf::wildcard_value<uint64_t>{}));
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng,
|
||
dpf::lt(dpf::wildcard_value<uint64_t>{}));
|
||
|
||
using WT = std::decay_t<decltype(dealer.first)>;
|
||
static_assert(WT::cmp_is_wildcard);
|
||
EXPECT_FALSE(dealer.first.cmp_assigned());
|
||
EXPECT_FALSE(ds.first.cmp_assigned());
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
|
||
ds.first.value_cw().data(),
|
||
dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
|
||
EXPECT_EQ(dealer.first.cw_last(), ds.first.cw_last());
|
||
EXPECT_EQ(dealer.first.cmp_addend(), ds.first.cmp_addend());
|
||
EXPECT_EQ(dealer.second.cmp_addend(), ds.second.cmp_addend());
|
||
|
||
reset_tape_roots();
|
||
auto concrete = dpf::make_dpf(alpha,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root}, dpf::lt(yt));
|
||
|
||
dpf::assign_cmp(dealer.first, dealer.second, yt);
|
||
dpf::assign_cmp(ds.first, ds.second, yt);
|
||
EXPECT_TRUE(dealer.first.cmp_assigned());
|
||
EXPECT_TRUE(ds.first.cmp_assigned());
|
||
|
||
EXPECT_TRUE(same_bytes(dealer.first.value_cw().data(),
|
||
concrete.first.value_cw().data(),
|
||
dealer.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
|
||
EXPECT_TRUE(same_bytes(ds.first.value_cw().data(),
|
||
concrete.first.value_cw().data(),
|
||
ds.first.value_cw().size() * sizeof(typename WT::value_cw_word)));
|
||
EXPECT_EQ(dealer.first.cw_last(), concrete.first.cw_last());
|
||
EXPECT_EQ(ds.first.cw_last(), concrete.first.cw_last());
|
||
|
||
const uint64_t mask = dealer.first.cmp().mask;
|
||
auto r_ds = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask;
|
||
};
|
||
auto r_cc = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, concrete.first, q), dpf::eval_point(dpf::cmp, concrete.second, q)) & mask;
|
||
};
|
||
for (uint32_t q : {alpha - 1u, alpha, alpha + 1u, 0u})
|
||
EXPECT_EQ(r_ds(q), r_cc(q)) << "q=" << q;
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, DsWildcardCmpWithAtAndNarrowPayload)
|
||
{
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
const uint32_t a0 = 0x0f0f0f0fu;
|
||
const uint32_t a1 = alpha ^ a0;
|
||
|
||
reset_tape_roots();
|
||
auto dealer = dpf::make_dpf(alpha,
|
||
dpf::root_sampler_t<dpf::prg::aes128>{take_root},
|
||
dpf::at<16>(uint16_t{9}),
|
||
dpf::lt(dpf::wildcard_value<uint16_t>{}));
|
||
reset_tape_roots();
|
||
HEDLEY_PRAGMA(GCC diagnostic push)
|
||
HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes")
|
||
dpf::ds_randomness<simde__m128i (*)(), PadA> rng{take_root, {}};
|
||
HEDLEY_PRAGMA(GCC diagnostic pop)
|
||
auto ds = dpf::make_dpf_doerner_shelat(a0, a1, rng,
|
||
dpf::at<16>(uint16_t{9}),
|
||
dpf::lt(dpf::wildcard_value<uint16_t>{}));
|
||
|
||
EXPECT_TRUE(same_incr_key(dealer.first, ds.first));
|
||
EXPECT_TRUE(same_incr_key(dealer.second, ds.second));
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, ds.first, alpha),
|
||
*dpf::eval_point(dpf::out<0, 16>, ds.second, alpha)),
|
||
uint16_t{9});
|
||
|
||
dpf::assign_cmp(ds.first, ds.second, uint16_t{7}, uint16_t{1});
|
||
const uint64_t mask = ds.first.cmp().mask;
|
||
auto r = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, ds.first, q), dpf::eval_point(dpf::cmp, ds.second, q)) & mask;
|
||
};
|
||
EXPECT_EQ(r(alpha - 1u), 7u);
|
||
EXPECT_EQ(r(alpha), 1u);
|
||
EXPECT_EQ(dpf::reconstruct(ds.first.cmp_addend(), ds.second.cmp_addend()) & mask, 1u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, MultilevelInnerProductMatchesInterval)
|
||
{
|
||
// Inner product of a prefix-slot (`at<12>(u8)`) against a public weight
|
||
// vector must reconstruct to the dot of the interval outputs with the
|
||
// weights. Additive output => recon(ip) == Σ recon(interval)[j] * w[j].
|
||
uint32_t x = 0x00a5b6c7u;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{99});
|
||
|
||
const uint32_t lane = x >> (32 - 12);
|
||
const uint32_t from = (lane & ~0xffu);
|
||
const uint32_t to = from + 255u;
|
||
|
||
auto [buf0, it0] = dpf::eval_interval(dpf::out<0, 12>, k0, from, to);
|
||
auto [buf1, it1] = dpf::eval_interval(dpf::out<0, 12>, k1, from, to);
|
||
(void)it0;
|
||
(void)it1;
|
||
|
||
std::vector<uint64_t> w(buf0.size());
|
||
uint64_t expect = 0;
|
||
for (std::size_t i = 0; i < w.size(); ++i)
|
||
{
|
||
w[i] = (i * 7u + 1u) & 0x3fu;
|
||
expect += static_cast<uint64_t>(recon(buf0[i], buf1[i])) * w[i];
|
||
}
|
||
expect &= 0xffu; // u8 output wraps at 8 bits
|
||
|
||
auto a = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w);
|
||
auto b = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w);
|
||
EXPECT_EQ(static_cast<uint8_t>(recon(a, b)), static_cast<uint8_t>(expect));
|
||
|
||
// Same result when driving a caller-supplied stop-level memoizer.
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
auto m0 = dpf::make_basic_interval_memoizer<KT, 0>(from, to);
|
||
auto m1 = dpf::make_basic_interval_memoizer<KT, 0>(from, to);
|
||
auto am = dpf::eval_inner_product(dpf::out<0, 12>, k0, from, to, w, m0);
|
||
auto bm = dpf::eval_inner_product(dpf::out<0, 12>, k1, from, to, w, m1);
|
||
EXPECT_EQ(am, a);
|
||
EXPECT_EQ(bm, b);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CmpInnerProductMatchesInterval)
|
||
{
|
||
// Cmp inner product: Σ (path-sum share) * w over the interval; the two
|
||
// parties' results sum to Σ value[i] * w[i].
|
||
const uint16_t alpha = 0x00aau;
|
||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||
dpf::lt_at<8>(uint64_t{5}, uint64_t{1}));
|
||
const uint64_t mask = k0.cmp().mask;
|
||
|
||
const uint8_t from = 0x00, to = 0x0f;
|
||
auto b0 = dpf::eval_interval(dpf::cmp, k0, from, to);
|
||
auto b1 = dpf::eval_interval(dpf::cmp, k1, from, to);
|
||
|
||
std::vector<uint64_t> w(b0.size());
|
||
uint64_t expect = 0;
|
||
for (std::size_t i = 0; i < w.size(); ++i)
|
||
{
|
||
w[i] = (i * 3u + 2u) & 0xffu;
|
||
const uint64_t value = recon(b0[i], b1[i]) & mask;
|
||
expect = (expect + value * (w[i] & mask)) & mask;
|
||
}
|
||
|
||
auto a = dpf::eval_inner_product(dpf::cmp, k0, from, to, w);
|
||
auto b = dpf::eval_inner_product(dpf::cmp, k1, from, to, w);
|
||
EXPECT_EQ((a + b) & mask, expect);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, BreadthFirstAtNonFinalSlot)
|
||
{
|
||
// Breadth-first sequence eval stopping at a prefix slot's tree level.
|
||
uint32_t x = 0x00a5b6c7u;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{77}), uint32_t{99});
|
||
constexpr std::size_t N = 12, I = 0;
|
||
|
||
const uint32_t xlane = x >> (32 - N);
|
||
std::vector<uint32_t> lanes = {
|
||
(xlane & ~0xfu), xlane,
|
||
static_cast<uint32_t>(xlane ^ 1u),
|
||
static_cast<uint32_t>((xlane + 5u) & 0xfffu),
|
||
static_cast<uint32_t>((xlane + 300u) & 0xfffu)};
|
||
std::sort(lanes.begin(), lanes.end());
|
||
lanes.erase(std::unique(lanes.begin(), lanes.end()), lanes.end());
|
||
|
||
auto bf0 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k0,
|
||
lanes.begin(), lanes.end());
|
||
auto bf1 = dpf::eval_sequence_breadth_first(dpf::out<I, N>, k1,
|
||
lanes.begin(), lanes.end());
|
||
|
||
for (std::size_t i = 0; i < lanes.size(); ++i)
|
||
{
|
||
const uint32_t q = lanes[i] << (32 - N);
|
||
const auto expect = recon(*dpf::eval_point(dpf::out<I, N>, k0, q),
|
||
*dpf::eval_point(dpf::out<I, N>, k1, q));
|
||
EXPECT_EQ(recon(bf0[i], bf1[i]), expect) << "i=" << i;
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, CrossLevelPathMemoizerShallowThenDeep)
|
||
{
|
||
// A single path memoizer reused shallow (prefix slot) then deep (full
|
||
// domain) must agree with independent non-memoized evals at both levels.
|
||
uint32_t x = 0xabcdef01u;
|
||
auto [k0, k1] = dpf::make_dpf(x,
|
||
dpf::at<10>(dpf::bit::one), uint32_t{1234});
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
dpf::basic_path_memoizer<KT> p0{}, p1{};
|
||
|
||
for (uint32_t q : {x, static_cast<uint32_t>(x ^ 1u),
|
||
static_cast<uint32_t>(x ^ 0x80000000u), 0u})
|
||
{
|
||
// Shallow first, then deep, sharing the same memoizer.
|
||
auto s0 = dpf::eval_point(dpf::out<0, 10>, k0, q, p0);
|
||
auto s1 = dpf::eval_point(dpf::out<0, 10>, k1, q, p1);
|
||
auto d0 = dpf::eval_point(dpf::out<1, 32>, k0, q, p0);
|
||
auto d1 = dpf::eval_point(dpf::out<1, 32>, k1, q, p1);
|
||
|
||
auto sref = static_cast<bool>(
|
||
recon(*dpf::eval_point(dpf::out<0, 10>, k0, q), *dpf::eval_point(dpf::out<0, 10>, k1, q)));
|
||
auto dref = recon(*dpf::eval_point(dpf::out<1, 32>, k0, q),
|
||
*dpf::eval_point(dpf::out<1, 32>, k1, q));
|
||
EXPECT_EQ(static_cast<bool>(recon(*s0, *s1)), sref) << "q=" << q;
|
||
EXPECT_EQ(recon(*d0, *d1), dref) << "q=" << q;
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, SequenceRecipeAtPrefixSlot)
|
||
{
|
||
// Recipe depth follows the slot's tree_level, not the full key depth.
|
||
uint32_t x = 0x00a5b6c7u;
|
||
auto [k0, k1] = dpf::make_dpf(x, dpf::at<12>(uint8_t{42}), uint32_t{7});
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_EQ(KT::meta[0].tree_level, 8u);
|
||
|
||
const uint32_t lane = x >> (32 - 12);
|
||
std::array<uint32_t, 3> pts{{
|
||
static_cast<uint32_t>(lane & ~1u), lane,
|
||
static_cast<uint32_t>((lane & ~1u) + 1u)}};
|
||
std::sort(pts.begin(), pts.end());
|
||
|
||
auto recipe = dpf::make_sequence_recipe(dpf::out<0, 12>, k0,
|
||
pts.begin(), pts.end());
|
||
EXPECT_EQ(recipe.depth(), KT::meta[0].tree_level);
|
||
EXPECT_EQ(recipe.output_indices().size(), pts.size());
|
||
|
||
// Breadth-first at the same slot reconstructs the point payloads.
|
||
auto b0 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k0,
|
||
pts.begin(), pts.end());
|
||
auto b1 = dpf::eval_sequence_breadth_first(dpf::out<0, 12>, k1,
|
||
pts.begin(), pts.end());
|
||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||
{
|
||
const uint32_t q = static_cast<uint32_t>(pts[i] << (32 - 12));
|
||
EXPECT_EQ(recon(b0[i], b1[i]),
|
||
recon(*dpf::eval_point(dpf::out<0, 12>, k0, q),
|
||
*dpf::eval_point(dpf::out<0, 12>, k1, q)));
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Regressions for bugs found under ASan/UBSan:
|
||
// * eq(..., if_false) must absorb on every eval surface, not only point
|
||
// * path memoizer resume past a full-width cmp depth must not shift by nbits
|
||
// ---------------------------------------------------------------------------
|
||
|
||
TEST_F(IncrementalDpfTest, EqIfFalseAbsorbsOnEveryEvalSurface)
|
||
{
|
||
const uint8_t alpha = 5;
|
||
const uint8_t yt = 7;
|
||
const uint8_t yf = 3;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(yt, yf));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
EXPECT_TRUE(KT::is_multilevel);
|
||
EXPECT_EQ(std::get<0>(k0.public_addends), yf);
|
||
constexpr auto opl = KT::template outputs_per_leaf_of<0>;
|
||
|
||
auto expect_at = [&](uint8_t q) {
|
||
return (q == alpha) ? yt : yf;
|
||
};
|
||
|
||
for (unsigned q = 0; q < 8u; ++q)
|
||
{
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, uint8_t(q)),
|
||
*dpf::eval_point(k1, uint8_t(q))),
|
||
expect_at(uint8_t(q)))
|
||
<< "point q=" << q;
|
||
}
|
||
|
||
auto [f0, itf0] = dpf::eval_full(k0);
|
||
auto [f1, itf1] = dpf::eval_full(k1);
|
||
(void)itf0;
|
||
(void)itf1;
|
||
for (unsigned q = 0; q < 8u; ++q)
|
||
{
|
||
EXPECT_EQ(recon(f0[q], f1[q]), expect_at(uint8_t(q)))
|
||
<< "full q=" << q;
|
||
}
|
||
|
||
// Leaf-aligned interval so buffer index == lane.
|
||
constexpr uint8_t from = 0;
|
||
constexpr uint8_t to = 15; // one full packing leaf when opl==16
|
||
auto [iv0, ii0] = dpf::eval_interval(dpf::out<0>, k0, from, to);
|
||
auto [iv1, ii1] = dpf::eval_interval(dpf::out<0>, k1, from, to);
|
||
(void)ii0;
|
||
(void)ii1;
|
||
ASSERT_EQ(iv0.size(), static_cast<std::size_t>(to - from + 1));
|
||
for (unsigned q = from; q <= to; ++q)
|
||
{
|
||
const uint8_t want = (q < 8u) ? expect_at(uint8_t(q)) : yf;
|
||
EXPECT_EQ(recon(iv0[q - from], iv1[q - from]), want)
|
||
<< "interval q=" << q;
|
||
}
|
||
|
||
std::array<uint8_t, 8> pts{{7, 0, 5, 2, 1, 6, 3, 4}};
|
||
auto s0 = dpf::make_output_buffer_for(k0, pts.size());
|
||
auto s1 = dpf::make_output_buffer_for(k1, pts.size());
|
||
dpf::eval_sequence(dpf::out<0>, k0, pts.begin(), pts.end(), s0);
|
||
dpf::eval_sequence(dpf::out<0>, k1, pts.begin(), pts.end(), s1);
|
||
for (std::size_t i = 0; i < pts.size(); ++i)
|
||
{
|
||
auto e0 = dpf::eval_point(dpf::out<0>, k0, pts[i]);
|
||
auto e1 = dpf::eval_point(dpf::out<0>, k1, pts[i]);
|
||
EXPECT_EQ(recon(s0[i * opl + e0.offset], s1[i * opl + e1.offset]),
|
||
expect_at(pts[i]))
|
||
<< "sequence i=" << i;
|
||
}
|
||
|
||
std::array<uint8_t, 8> sorted{{0, 1, 2, 3, 4, 5, 6, 7}};
|
||
auto b0 = dpf::eval_sequence_breadth_first(dpf::out<0>, k0, sorted.begin(),
|
||
sorted.end());
|
||
auto b1 = dpf::eval_sequence_breadth_first(dpf::out<0>, k1, sorted.begin(),
|
||
sorted.end());
|
||
for (std::size_t i = 0; i < sorted.size(); ++i)
|
||
{
|
||
EXPECT_EQ(recon(b0[i], b1[i]), expect_at(sorted[i]))
|
||
<< "breadth i=" << i;
|
||
}
|
||
|
||
// Weights must cover every packing lane the interval exterior materialises.
|
||
std::vector<uint64_t> w(iv0.size(), 1);
|
||
uint64_t expect_ip = 0;
|
||
for (std::size_t i = 0; i < iv0.size(); ++i)
|
||
expect_ip += static_cast<uint64_t>(recon(iv0[i], iv1[i])) * w[i];
|
||
const auto ip0 = dpf::eval_inner_product(dpf::out<0>, k0, from, to, w);
|
||
const auto ip1 = dpf::eval_inner_product(dpf::out<0>, k1, from, to, w);
|
||
EXPECT_EQ(static_cast<uint8_t>(recon(ip0, ip1)),
|
||
static_cast<uint8_t>(expect_ip));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EqAtIfFalseWithPackedIntervalAndXor)
|
||
{
|
||
const uint32_t alpha = 0x00c0ffeeu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha,
|
||
dpf::eq_at<16>(uint16_t{99}, uint16_t{7}),
|
||
dpf::eq(dpf::xor_wrapper<uint32_t>{0x00ff00ffu},
|
||
dpf::xor_wrapper<uint32_t>{0x00001111u}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
constexpr auto opl = KT::template outputs_per_leaf_of<0>;
|
||
|
||
const uint16_t lane = static_cast<uint16_t>(alpha >> 16);
|
||
// Align to a packing leaf so buffer index math is exact.
|
||
const uint16_t from = static_cast<uint16_t>(lane & ~(opl - 1u));
|
||
const uint16_t to = static_cast<uint16_t>(from + opl - 1u);
|
||
auto [buf0, iit0] = dpf::eval_interval(dpf::out<0, 16>, k0, from, to);
|
||
auto [buf1, iit1] = dpf::eval_interval(dpf::out<0, 16>, k1, from, to);
|
||
(void)iit0;
|
||
(void)iit1;
|
||
for (uint16_t q = from; q <= to; ++q)
|
||
{
|
||
const std::size_t idx = static_cast<std::size_t>(q - from);
|
||
const uint16_t want = (q == lane) ? uint16_t{99} : uint16_t{7};
|
||
EXPECT_EQ(recon(buf0[idx], buf1[idx]), want) << "eq_at lane=" << q;
|
||
const uint32_t full = static_cast<uint32_t>(q) << 16;
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<0, 16>, k0, full),
|
||
*dpf::eval_point(dpf::out<0, 16>, k1, full)),
|
||
want);
|
||
}
|
||
|
||
// Spot-check the full-width xor_wrapper eq (avoid 2^32 full-domain walk).
|
||
const auto on = dpf::xor_wrapper<uint32_t>{0x00ff00ffu};
|
||
const auto off = dpf::xor_wrapper<uint32_t>{0x00001111u};
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha),
|
||
*dpf::eval_point(dpf::out<1>, k1, alpha)),
|
||
on);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, alpha ^ 1u),
|
||
*dpf::eval_point(dpf::out<1>, k1, alpha ^ 1u)),
|
||
off);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dpf::out<1>, k0, 0u),
|
||
*dpf::eval_point(dpf::out<1>, k1, 0u)),
|
||
off);
|
||
// Leaf-aligned interval around α for the xor slot.
|
||
using KT1 = std::decay_t<decltype(k0)>;
|
||
constexpr auto opl1 = KT1::template outputs_per_leaf_of<1>;
|
||
const uint32_t xfrom = alpha & ~(opl1 - 1u);
|
||
const uint32_t xto = xfrom + static_cast<uint32_t>(opl1 - 1u);
|
||
auto [xf0, xi0] = dpf::eval_interval(dpf::out<1>, k0, xfrom, xto);
|
||
auto [xf1, xi1] = dpf::eval_interval(dpf::out<1>, k1, xfrom, xto);
|
||
(void)xi0;
|
||
(void)xi1;
|
||
for (uint32_t q = xfrom; q <= xto; ++q)
|
||
{
|
||
const auto want = (q == alpha) ? on : off;
|
||
EXPECT_EQ(recon(xf0[q - xfrom], xf1[q - xfrom]), want) << "xor q=" << q;
|
||
}
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EqIfFalseDealerMatchesDoernerShelatSurfaces)
|
||
{
|
||
const uint8_t alpha = 0x2au;
|
||
const uint8_t x0s = 0x11u;
|
||
const uint8_t x1s = static_cast<uint8_t>(alpha ^ x0s);
|
||
auto dealer = dpf::make_dpf(alpha, dpf::eq(uint8_t{9}, uint8_t{4}));
|
||
auto ds = dpf::make_dpf_doerner_shelat(x0s, x1s,
|
||
dpf::eq(uint8_t{9}, uint8_t{4}));
|
||
|
||
for (unsigned q = 0; q < 256u; q += 17u)
|
||
{
|
||
const uint8_t qq = static_cast<uint8_t>(q);
|
||
EXPECT_EQ(recon(*dpf::eval_point(dealer.first, qq),
|
||
*dpf::eval_point(dealer.second, qq)),
|
||
recon(*dpf::eval_point(ds.first, qq),
|
||
*dpf::eval_point(ds.second, qq)));
|
||
}
|
||
auto [fa0, ia0] = dpf::eval_full(dealer.first);
|
||
auto [fa1, ia1] = dpf::eval_full(dealer.second);
|
||
auto [fb0, ib0] = dpf::eval_full(ds.first);
|
||
auto [fb1, ib1] = dpf::eval_full(ds.second);
|
||
(void)ia0;
|
||
(void)ia1;
|
||
(void)ib0;
|
||
(void)ib1;
|
||
for (unsigned q = 0; q < 256u; q += 17u)
|
||
EXPECT_EQ(recon(fa0[q], fa1[q]), recon(fb0[q], fb1[q]));
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, PathMemoizerResumePastFullWidthCmpDepth)
|
||
{
|
||
const uint32_t alpha = 0x00abcdefu;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, uint32_t{7}, dpf::lt(uint64_t{3}));
|
||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0),
|
||
dpf::eval_point(dpf::cmp, k1, alpha, path1))
|
||
& mask,
|
||
0u);
|
||
EXPECT_EQ(dpf::reconstruct(
|
||
dpf::eval_point(dpf::cmp, k0, alpha - 1u, path0),
|
||
dpf::eval_point(dpf::cmp, k1, alpha - 1u, path1))
|
||
& mask,
|
||
3u);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||
*dpf::eval_point(k1, alpha, path1)),
|
||
7u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 1u, path0),
|
||
*dpf::eval_point(k1, alpha ^ 1u, path1)),
|
||
0u);
|
||
|
||
auto p0 = dpf::make_basic_path_memoizer(k0);
|
||
auto p1 = dpf::make_basic_path_memoizer(k1);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, p0),
|
||
*dpf::eval_point(k1, alpha, p1)),
|
||
7u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, p0),
|
||
dpf::eval_point(dpf::cmp, k1, alpha, p1))
|
||
& mask,
|
||
0u);
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha ^ 2u, p0),
|
||
*dpf::eval_point(k1, alpha ^ 2u, p1)),
|
||
0u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, PathMemoizerResumeCmpOnlyFullWidth)
|
||
{
|
||
const uint32_t alpha = 0x80000001u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::geq(uint64_t{5}, uint64_t{1}));
|
||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||
const uint64_t mask = k0.cmp().mask;
|
||
auto rq = [&](uint32_t q) {
|
||
return dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, q, path0),
|
||
dpf::eval_point(dpf::cmp, k1, q, path1))
|
||
& mask;
|
||
};
|
||
EXPECT_EQ(rq(alpha), 5u);
|
||
EXPECT_EQ(rq(alpha), 5u);
|
||
EXPECT_EQ(rq(alpha - 1u), 1u);
|
||
EXPECT_EQ(rq(alpha + 1u), 5u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, EqMixedWithBlockedCmpSharesMemoizer)
|
||
{
|
||
const uint8_t alpha = 0x5au;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint8_t{11}, uint8_t{2}),
|
||
dpf::block_width<3>(dpf::lt(uint64_t{9}, uint64_t{1})));
|
||
auto path0 = dpf::make_basic_path_memoizer(k0);
|
||
auto path1 = dpf::make_basic_path_memoizer(k1);
|
||
|
||
EXPECT_EQ(recon(*dpf::eval_point(k0, alpha, path0),
|
||
*dpf::eval_point(k1, alpha, path1)),
|
||
11u);
|
||
const uint64_t mask = k0.cmp().mask;
|
||
EXPECT_EQ(dpf::reconstruct(
|
||
dpf::eval_point(dpf::cmp, k0, uint8_t(alpha - 1), path0),
|
||
dpf::eval_point(dpf::cmp, k1, uint8_t(alpha - 1), path1))
|
||
& mask,
|
||
9u);
|
||
EXPECT_EQ(dpf::reconstruct(dpf::eval_point(dpf::cmp, k0, alpha, path0),
|
||
dpf::eval_point(dpf::cmp, k1, alpha, path1))
|
||
& mask,
|
||
1u);
|
||
|
||
auto [f0, i0] = dpf::eval_full(k0);
|
||
auto [f1, i1] = dpf::eval_full(k1);
|
||
(void)i0;
|
||
(void)i1;
|
||
EXPECT_EQ(recon(f0[alpha], f1[alpha]), 11u);
|
||
EXPECT_EQ(recon(f0[uint8_t(alpha ^ 1)], f1[uint8_t(alpha ^ 1)]), 2u);
|
||
}
|
||
|
||
TEST_F(IncrementalDpfTest, IntervalMemoizerReuseAcrossEqAndShallowAt)
|
||
{
|
||
const uint32_t alpha = 0x00a1b2c3u;
|
||
auto [k0, k1] = dpf::make_dpf(alpha, dpf::eq(uint32_t{8}, uint32_t{1}),
|
||
dpf::at<8>(uint8_t{42}));
|
||
using KT = std::decay_t<decltype(k0)>;
|
||
constexpr auto opl0 = KT::template outputs_per_leaf_of<0>;
|
||
|
||
const uint32_t efrom = alpha & ~(opl0 - 1u);
|
||
const uint32_t eto = efrom + static_cast<uint32_t>(opl0 - 1u);
|
||
auto [f0, fi0] = dpf::eval_interval(dpf::out<0>, k0, efrom, eto);
|
||
auto [f1, fi1] = dpf::eval_interval(dpf::out<0>, k1, efrom, eto);
|
||
(void)fi0;
|
||
(void)fi1;
|
||
for (uint32_t q = efrom; q <= eto; ++q)
|
||
{
|
||
const uint32_t want = (q == alpha) ? 8u : 1u;
|
||
EXPECT_EQ(recon(f0[q - efrom], f1[q - efrom]), want) << "eq q=" << q;
|
||
}
|
||
|
||
const uint8_t top = static_cast<uint8_t>(alpha >> 24);
|
||
auto [ait0, ai0] = dpf::eval_interval(dpf::out<1, 8>, k0, uint8_t{0},
|
||
uint8_t{255});
|
||
auto [ait1, ai1] = dpf::eval_interval(dpf::out<1, 8>, k1, uint8_t{0},
|
||
uint8_t{255});
|
||
(void)ai0;
|
||
(void)ai1;
|
||
EXPECT_EQ(recon(ait0[top], ait1[top]), 42u);
|
||
EXPECT_EQ(recon(ait0[uint8_t(top ^ 1)], ait1[uint8_t(top ^ 1)]), 0u);
|
||
|
||
// Re-walk a different eq window after the shallow interval.
|
||
const uint32_t from = alpha - 3u;
|
||
const uint32_t to = alpha + 3u;
|
||
const uint32_t afrom = from & ~(opl0 - 1u);
|
||
const uint32_t ato = (to + opl0 - 1u) & ~(opl0 - 1u);
|
||
const uint32_t ato_inclusive = ato + static_cast<uint32_t>(opl0 - 1u);
|
||
auto [eit0, ee0] = dpf::eval_interval(dpf::out<0>, k0, afrom,
|
||
ato_inclusive);
|
||
auto [eit1, ee1] = dpf::eval_interval(dpf::out<0>, k1, afrom,
|
||
ato_inclusive);
|
||
(void)ee0;
|
||
(void)ee1;
|
||
for (uint32_t q = from; q <= to; ++q)
|
||
{
|
||
const uint32_t want = (q == alpha) ? 8u : 1u;
|
||
EXPECT_EQ(recon(eit0[q - afrom], eit1[q - afrom]), want) << "q=" << q;
|
||
}
|
||
}
|
||
|