2026-09-24 14:08:32 -06:00
|
|
|
|
/// @file dpf/dcf.hpp
|
|
|
|
|
|
/// @brief Comparison-channel specs and GGM path-sum helpers for libdpf.
|
|
|
|
|
|
/// @details `lt`/`leq`/`gt`/`geq` (+ `_at`) take `(if_true, if_false=0)`.
|
|
|
|
|
|
/// Eval walks the same GGM tree as the DPF (per-level value CWs).
|
|
|
|
|
|
/// `eq` / `eq_at` are synonyms for ordinary point placements.
|
|
|
|
|
|
/// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors)
|
|
|
|
|
|
/// @license Released under a GNU General Public v2.0 (GPLv2) license.
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef LIBDPF_INCLUDE_DPF_DCF_HPP__
|
|
|
|
|
|
#define LIBDPF_INCLUDE_DPF_DCF_HPP__
|
|
|
|
|
|
|
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
#include <limits>
|
|
|
|
|
|
|
|
|
|
|
|
#include "hedley/hedley.h"
|
|
|
|
|
|
#include "simde/simde/x86/avx2.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "dpf/utils.hpp"
|
|
|
|
|
|
#include "dpf/bit.hpp"
|
|
|
|
|
|
#include "dpf/xor_wrapper.hpp"
|
|
|
|
|
|
#include "dpf/twiddle.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
namespace dpf
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
struct ic_pack;
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
struct is_ic_pack : std::false_type {};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
struct is_ic_pack<ic_pack<Beta>> : std::true_type {};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename ...Ts>
|
|
|
|
|
|
inline constexpr bool no_ic_pack_v =
|
|
|
|
|
|
(!is_ic_pack<std::decay_t<Ts>>::value && ...);
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// Comparison kind for the optional DCF channel on a key.
|
2026-09-24 20:44:07 -06:00
|
|
|
|
/// `lt`/`leq`/`gt`/`geq` are the comparison predicates. The later kinds are
|
|
|
|
|
|
/// path paints: one constant on each sibling subtree of the secret point,
|
|
|
|
|
|
/// evaluated by the same value-correction walk.
|
2026-09-24 14:08:32 -06:00
|
|
|
|
enum class cmp_kind : uint8_t
|
|
|
|
|
|
{
|
|
|
|
|
|
lt = 0,
|
|
|
|
|
|
leq = 1,
|
|
|
|
|
|
gt = 2,
|
2026-09-24 20:44:07 -06:00
|
|
|
|
geq = 3,
|
|
|
|
|
|
lcp = 4, // common-prefix length
|
|
|
|
|
|
prefix = 5, // matched prefix, in the lane's high bits
|
|
|
|
|
|
mask = 6, // high-bit mask of that length
|
|
|
|
|
|
one_hot = 7, // 2^{length} (0 when the bit does not fit)
|
|
|
|
|
|
break_bit = 8, // secret bit at the first difference
|
|
|
|
|
|
prefix_with_length = 9, // (low-aligned prefix << length_bits) | length
|
|
|
|
|
|
paint = 10 // caller-supplied unit plant
|
2026-09-24 14:08:32 -06:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
/// True for the path-paint kinds. Comparisons stay `lt`/`leq`/`gt`/`geq`.
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline constexpr bool is_paint_kind(cmp_kind kind) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
|
|
|
|
|
case cmp_kind::lcp:
|
|
|
|
|
|
case cmp_kind::prefix:
|
|
|
|
|
|
case cmp_kind::mask:
|
|
|
|
|
|
case cmp_kind::one_hot:
|
|
|
|
|
|
case cmp_kind::break_bit:
|
|
|
|
|
|
case cmp_kind::prefix_with_length:
|
|
|
|
|
|
case cmp_kind::paint:
|
|
|
|
|
|
return true;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Unit plant for `path_paint`. `prefix` is the in-lane matched prefix.
|
|
|
|
|
|
using paint_callback = uint64_t (*)(std::size_t matched, uint64_t prefix,
|
|
|
|
|
|
bool leaf, const void * ctx);
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
enum class cmp_trivial : uint8_t
|
|
|
|
|
|
{
|
|
|
|
|
|
none = 0,
|
|
|
|
|
|
always_true = 1,
|
|
|
|
|
|
always_false = 2
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
namespace detail
|
|
|
|
|
|
{
|
|
|
|
|
|
namespace dcf_impl
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
Beta default_false() noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_same_v<Beta, dpf::bit>)
|
|
|
|
|
|
return dpf::bit::zero;
|
|
|
|
|
|
else
|
|
|
|
|
|
return Beta{};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
uint64_t beta_delta_u64(const Beta & if_true, const Beta & if_false,
|
|
|
|
|
|
uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_same_v<Beta, dpf::bit>)
|
|
|
|
|
|
{
|
|
|
|
|
|
const uint64_t t = static_cast<bool>(if_true) ? 1ULL : 0ULL;
|
|
|
|
|
|
const uint64_t f = static_cast<bool>(if_false) ? 1ULL : 0ULL;
|
|
|
|
|
|
return (t ^ f) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
|
|
|
|
|
|
{
|
|
|
|
|
|
return (static_cast<uint64_t>(if_true) ^ static_cast<uint64_t>(if_false))
|
|
|
|
|
|
& mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return (static_cast<uint64_t>(if_true)
|
|
|
|
|
|
- static_cast<uint64_t>(if_false)) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
uint64_t beta_to_u64_simple(const Beta & beta, uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_same_v<Beta, dpf::bit>)
|
|
|
|
|
|
return (static_cast<bool>(beta) ? 1ULL : 0ULL) & mask;
|
|
|
|
|
|
else
|
|
|
|
|
|
return static_cast<uint64_t>(beta) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
Beta sub_beta(const Beta & a, const Beta & b) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_same_v<Beta, dpf::bit>)
|
|
|
|
|
|
return dpf::bit{static_cast<bool>(a) ^ static_cast<bool>(b)};
|
|
|
|
|
|
else if constexpr (dpf::utils::is_xor_wrapper_v<Beta>)
|
|
|
|
|
|
return Beta{static_cast<uint64_t>(a) ^ static_cast<uint64_t>(b)};
|
|
|
|
|
|
else
|
|
|
|
|
|
return static_cast<Beta>(a - b);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
Beta u64_to_beta(uint64_t v) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
if constexpr (std::is_same_v<Beta, dpf::bit>)
|
|
|
|
|
|
return dpf::bit{static_cast<bool>(v & 1u)};
|
|
|
|
|
|
else
|
|
|
|
|
|
return static_cast<Beta>(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_CONST
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
constexpr uint64_t default_mask_for_bits(std::size_t out_bits) noexcept
|
2026-09-24 14:08:32 -06:00
|
|
|
|
{
|
|
|
|
|
|
if (out_bits >= 64)
|
|
|
|
|
|
return ~0ULL;
|
|
|
|
|
|
if (out_bits == 0)
|
|
|
|
|
|
return 0ULL;
|
|
|
|
|
|
return (1ULL << out_bits) - 1ULL;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_CONST
|
|
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
2026-09-24 20:44:07 -06:00
|
|
|
|
constexpr uint64_t neg_m(uint64_t x, uint64_t mask) noexcept
|
2026-09-24 14:08:32 -06:00
|
|
|
|
{
|
|
|
|
|
|
return (0ULL - x) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_CONST
|
|
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
2026-09-24 20:44:07 -06:00
|
|
|
|
constexpr uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept
|
2026-09-24 14:08:32 -06:00
|
|
|
|
{
|
|
|
|
|
|
return t1 ? neg_m(x, mask) : x;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Convert a GGM node to a group element (low 64 bits, control bits cleared).
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
|
|
|
|
|
uint64_t convert_node(simde__m128i n, uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
return static_cast<uint64_t>(
|
|
|
|
|
|
simde_mm_cvtsi128_si64(dpf::unset_lo_2bits(n))) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Draw the group-width blind `r` used to split the `cmp_addend` share.
|
|
|
|
|
|
/// `sample` yields one interior block; only `popcount(mask)` live bits are
|
|
|
|
|
|
/// kept, so the blind (and thus the addend share) never needs a full padded
|
|
|
|
|
|
/// `uint64_t` on the wire. Dealer and Doerner–Shelat gen call this with the
|
|
|
|
|
|
/// same block source so their keys stay byte-identical (matched tapes).
|
|
|
|
|
|
template <typename BlockSampler>
|
|
|
|
|
|
HEDLEY_ALWAYS_INLINE
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
return convert_node(dpf::unset_lo_2bits(sample()), mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// One level of value CW on GGM children. Updates running `Va`.
|
|
|
|
|
|
/// `ai` is the keep-path bit of the (effective) threshold.
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
inline uint64_t make_value_cw(simde__m128i c0L, simde__m128i c0R,
|
|
|
|
|
|
simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai,
|
|
|
|
|
|
uint64_t & Va, uint64_t beta, uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)t0;
|
|
|
|
|
|
uint64_t v0K, v1K, v0Lo, v1Lo;
|
|
|
|
|
|
if (ai == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
v0K = convert_node(c0L, mask);
|
|
|
|
|
|
v1K = convert_node(c1L, mask);
|
|
|
|
|
|
v0Lo = convert_node(c0R, mask);
|
|
|
|
|
|
v1Lo = convert_node(c1R, mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
v0K = convert_node(c0R, mask);
|
|
|
|
|
|
v1K = convert_node(c1R, mask);
|
|
|
|
|
|
v0Lo = convert_node(c0L, mask);
|
|
|
|
|
|
v1Lo = convert_node(c1L, mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
uint64_t vcw = sgn_m(t1,
|
|
|
|
|
|
(v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask);
|
|
|
|
|
|
// Lose-left (ai==1) is the x<α diverge: plant β there.
|
|
|
|
|
|
if (ai == 1)
|
|
|
|
|
|
vcw = (vcw + sgn_m(t1, beta, mask)) & mask;
|
|
|
|
|
|
Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask;
|
|
|
|
|
|
return vcw;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
/// Same recurrence as `make_value_cw`, planting `plant` on the lose child
|
|
|
|
|
|
/// in both directions. `plant == 0` leaves the correction unchanged.
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline uint64_t make_value_cw_planted(simde__m128i c0L, simde__m128i c0R,
|
|
|
|
|
|
simde__m128i c1L, simde__m128i c1R, uint8_t t0, uint8_t t1, int ai,
|
|
|
|
|
|
uint64_t & Va, uint64_t plant, uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
(void)t0;
|
|
|
|
|
|
uint64_t v0K, v1K, v0Lo, v1Lo;
|
|
|
|
|
|
if (ai == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
v0K = convert_node(c0L, mask);
|
|
|
|
|
|
v1K = convert_node(c1L, mask);
|
|
|
|
|
|
v0Lo = convert_node(c0R, mask);
|
|
|
|
|
|
v1Lo = convert_node(c1R, mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
v0K = convert_node(c0R, mask);
|
|
|
|
|
|
v1K = convert_node(c1R, mask);
|
|
|
|
|
|
v0Lo = convert_node(c0L, mask);
|
|
|
|
|
|
v1Lo = convert_node(c1L, mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
uint64_t vcw = sgn_m(t1,
|
|
|
|
|
|
(v1Lo + neg_m(v0Lo, mask) + neg_m(Va, mask)) & mask, mask);
|
|
|
|
|
|
vcw = (vcw + sgn_m(t1, plant, mask)) & mask;
|
|
|
|
|
|
Va = (Va + neg_m(v1K, mask) + v0K + sgn_m(t1, vcw, mask)) & mask;
|
|
|
|
|
|
return vcw;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline unsigned __int128 paint_lane_mask(std::size_t nbits) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
using u128 = unsigned __int128;
|
|
|
|
|
|
if (nbits == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
if (nbits >= 128)
|
|
|
|
|
|
return ~u128{0};
|
|
|
|
|
|
return (u128{1} << nbits) - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// High `d` bits of an `nbits`-wide lane, in that lane's own positions.
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline unsigned __int128 paint_high_bits(unsigned __int128 alpha,
|
|
|
|
|
|
std::size_t nbits, std::size_t d) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
alpha &= paint_lane_mask(nbits);
|
|
|
|
|
|
if (d == 0 || nbits == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
if (d >= nbits)
|
|
|
|
|
|
return alpha;
|
|
|
|
|
|
const std::size_t drop = nbits - d;
|
|
|
|
|
|
return (alpha >> drop) << drop;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline unsigned __int128 paint_low_aligned(unsigned __int128 alpha,
|
|
|
|
|
|
std::size_t nbits, std::size_t d) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
alpha &= paint_lane_mask(nbits);
|
|
|
|
|
|
if (d == 0 || nbits == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
if (d >= nbits)
|
|
|
|
|
|
return alpha;
|
|
|
|
|
|
return alpha >> (nbits - d);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Unit (β = 1) lose-subtree or leaf plant. The caller scales by δ.
|
|
|
|
|
|
/// `matched` is the number of leading bits already shared with α. A lose
|
|
|
|
|
|
/// subtree at that depth reconstructs to this value; `leaf` is the full match.
|
|
|
|
|
|
inline uint64_t paint_unit(cmp_kind kind, std::size_t matched,
|
|
|
|
|
|
unsigned __int128 alpha, std::size_t nbits, std::size_t length_bits,
|
|
|
|
|
|
bool leaf, paint_callback fn, const void * ctx)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (nbits == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
const std::size_t d = leaf ? nbits : matched;
|
|
|
|
|
|
switch (kind)
|
|
|
|
|
|
{
|
|
|
|
|
|
case cmp_kind::lcp:
|
|
|
|
|
|
return static_cast<uint64_t>(d);
|
|
|
|
|
|
case cmp_kind::prefix:
|
|
|
|
|
|
return static_cast<uint64_t>(paint_high_bits(alpha, nbits, d));
|
|
|
|
|
|
case cmp_kind::mask:
|
|
|
|
|
|
return static_cast<uint64_t>(
|
|
|
|
|
|
paint_high_bits(paint_lane_mask(nbits), nbits, d));
|
|
|
|
|
|
case cmp_kind::one_hot:
|
|
|
|
|
|
return d >= 64 ? 0ULL : (1ULL << d);
|
|
|
|
|
|
case cmp_kind::break_bit:
|
|
|
|
|
|
if (leaf || matched >= nbits)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
return static_cast<uint64_t>(
|
|
|
|
|
|
(alpha >> (nbits - 1 - matched)) & 1);
|
|
|
|
|
|
case cmp_kind::prefix_with_length:
|
|
|
|
|
|
{
|
|
|
|
|
|
if (length_bits >= 128)
|
|
|
|
|
|
return static_cast<uint64_t>(d);
|
|
|
|
|
|
unsigned __int128 packed =
|
|
|
|
|
|
paint_low_aligned(alpha, nbits, d) << length_bits;
|
|
|
|
|
|
packed |= static_cast<unsigned __int128>(d);
|
|
|
|
|
|
return static_cast<uint64_t>(packed);
|
|
|
|
|
|
}
|
|
|
|
|
|
case cmp_kind::paint:
|
|
|
|
|
|
if (fn == nullptr)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
return fn(d, static_cast<uint64_t>(paint_high_bits(alpha, nbits, d)),
|
|
|
|
|
|
leaf, ctx);
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HEDLEY_NO_THROW
|
|
|
|
|
|
inline uint64_t scale_plant(uint64_t unit, uint64_t scale, uint64_t mask) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
return (unit * scale) & mask;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
/// Final leaf value CW. `on_path` is the payload reconstructed when the query
|
|
|
|
|
|
/// stays on α's path through all levels (0 for strict lt/geq; β for leq/gt).
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
inline uint64_t make_final_cw(simde__m128i s0, simde__m128i s1, uint8_t t1,
|
|
|
|
|
|
uint64_t Va, uint64_t mask, uint64_t on_path = 0) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
uint64_t c0 = convert_node(s0, mask);
|
|
|
|
|
|
uint64_t c1 = convert_node(s1, mask);
|
|
|
|
|
|
return sgn_m(t1,
|
|
|
|
|
|
(c1 + neg_m(c0, mask) + neg_m(Va, mask) + on_path) & mask, mask);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace dcf_impl
|
|
|
|
|
|
|
|
|
|
|
|
/// Comparison metadata on an incremental key (value CWs live on the key).
|
|
|
|
|
|
/// Payload δ = if_true − if_false is dealer-known and baked into `value_cw` /
|
|
|
|
|
|
/// `cw_last` only — never stored clear on the key (traditional DPF hiding).
|
|
|
|
|
|
/// The second output value (`if_false`) is held as a per-party additive share
|
|
|
|
|
|
/// on the key (`cmp_addend`), not as a public constant.
|
|
|
|
|
|
struct cmp_meta
|
|
|
|
|
|
{
|
|
|
|
|
|
int nbits = 0; // comparison prefix length
|
|
|
|
|
|
uint64_t mask = 0;
|
|
|
|
|
|
cmp_kind kind = cmp_kind::lt;
|
|
|
|
|
|
cmp_trivial trivial = cmp_trivial::none;
|
|
|
|
|
|
bool eval_as_ge = false; // invert path-sum (geq / gt)
|
|
|
|
|
|
bool include_eq = false; // plant δ on the α-path leaf (leq / gt)
|
|
|
|
|
|
bool active = false;
|
2026-09-24 20:44:07 -06:00
|
|
|
|
bool incremental = false; // final correction saved at every depth
|
|
|
|
|
|
int block_width = 0; // 0 = per-level path-sum
|
|
|
|
|
|
int tail_bits = 0; // residual q; 0 when the tree covers every bit
|
2026-09-24 14:08:32 -06:00
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
HEDLEY_NO_THROW
|
2026-09-24 14:08:32 -06:00
|
|
|
|
bool empty() const noexcept { return !active; }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// Backward-compatible alias while call sites migrate.
|
|
|
|
|
|
using cmp_channel = cmp_meta;
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace detail
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Comparison specs: lt/leq/gt/geq (+ _at)
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
template <cmp_kind Kind, typename Beta>
|
|
|
|
|
|
struct cmp_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = Kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = 0;
|
2026-09-24 20:44:07 -06:00
|
|
|
|
static constexpr std::size_t block_width = 0;
|
2026-09-24 14:08:32 -06:00
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit cmp_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t N, cmp_kind Kind, typename Beta>
|
|
|
|
|
|
struct cmp_at_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = Kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = N;
|
2026-09-24 20:44:07 -06:00
|
|
|
|
static constexpr std::size_t block_width = 0;
|
2026-09-24 14:08:32 -06:00
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit cmp_at_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
inline auto lt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_pack<cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
inline auto leq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_pack<cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
inline auto gt(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_pack<cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
inline auto geq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_pack<cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
inline auto lt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_at_pack<N, cmp_kind::lt, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
inline auto leq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_at_pack<N, cmp_kind::leq, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
inline auto gt_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_at_pack<N, cmp_kind::gt, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
inline auto geq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return cmp_at_pack<N, cmp_kind::geq, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Path paints. Same channel and same (if_true, if_false) scale as a comparison:
|
|
|
|
|
|
// the reconstructed value is if_false + (if_true − if_false) · unit(x).
|
|
|
|
|
|
// `unit` is the common-prefix length, the matched prefix, a mask, and so on.
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T, typename = void>
|
|
|
|
|
|
struct spec_is_incremental : std::false_type {};
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
struct spec_is_incremental<T, std::void_t<decltype(T::incremental)>>
|
|
|
|
|
|
: std::bool_constant<T::incremental> {};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T, typename = void>
|
|
|
|
|
|
struct spec_length_bits : std::integral_constant<std::size_t, 0> {};
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
struct spec_length_bits<T, std::void_t<decltype(T::length_bits)>>
|
|
|
|
|
|
: std::integral_constant<std::size_t, T::length_bits> {};
|
|
|
|
|
|
|
|
|
|
|
|
template <cmp_kind Kind, typename Beta, std::size_t LengthBits = 0>
|
|
|
|
|
|
struct paint_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = Kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = 0;
|
|
|
|
|
|
static constexpr std::size_t block_width = 0;
|
|
|
|
|
|
static constexpr std::size_t length_bits = LengthBits;
|
|
|
|
|
|
static constexpr bool incremental = false;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit paint_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t N, cmp_kind Kind, typename Beta, std::size_t LengthBits = 0>
|
|
|
|
|
|
struct paint_at_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = Kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = N;
|
|
|
|
|
|
static constexpr std::size_t block_width = 0;
|
|
|
|
|
|
static constexpr std::size_t length_bits = LengthBits;
|
|
|
|
|
|
static constexpr bool incremental = false;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit paint_at_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta = uint64_t>
|
|
|
|
|
|
inline auto lcp(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::lcp, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto lcp_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::lcp, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta = uint64_t>
|
|
|
|
|
|
inline auto common_prefix(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::prefix, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto common_prefix_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::prefix, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta = uint64_t>
|
|
|
|
|
|
inline auto prefix_mask(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::mask, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto prefix_mask_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::mask, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta = uint64_t>
|
|
|
|
|
|
inline auto diverge_one_hot(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::one_hot, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto diverge_one_hot_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::one_hot, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta = uint64_t>
|
|
|
|
|
|
inline auto break_bit(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::break_bit, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto break_bit_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::break_bit, Beta>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Low `LengthBits` hold the common-prefix length. Above them sits the
|
|
|
|
|
|
/// matched prefix packed into the low bits of the lane (`α >> (N − d)`).
|
|
|
|
|
|
template <std::size_t LengthBits = 8, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto prefix_with_length(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_pack<cmp_kind::prefix_with_length, Beta, LengthBits>(
|
|
|
|
|
|
std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, std::size_t LengthBits = 8, typename Beta = uint64_t>
|
|
|
|
|
|
inline auto prefix_with_length_at(Beta t = Beta{1},
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_at_pack<N, cmp_kind::prefix_with_length, Beta, LengthBits>(
|
|
|
|
|
|
std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1
|
|
|
|
|
|
/// value of that sibling subtree (`leaf` is the full match, `matched == N`).
|
|
|
|
|
|
/// The result is scaled by `if_true − if_false` like the canned recipes.
|
|
|
|
|
|
template <typename Beta, typename Fn>
|
|
|
|
|
|
struct paint_fn_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = cmp_kind::paint;
|
|
|
|
|
|
static constexpr std::size_t prefix = 0;
|
|
|
|
|
|
static constexpr std::size_t block_width = 0;
|
|
|
|
|
|
static constexpr std::size_t length_bits = 0;
|
|
|
|
|
|
static constexpr bool incremental = false;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
Fn fn;
|
|
|
|
|
|
|
|
|
|
|
|
paint_fn_pack(Beta t, Beta f, Fn g)
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)}, fn{std::move(g)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t N, typename Beta, typename Fn>
|
|
|
|
|
|
struct paint_fn_at_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = cmp_kind::paint;
|
|
|
|
|
|
static constexpr std::size_t prefix = N;
|
|
|
|
|
|
static constexpr std::size_t block_width = 0;
|
|
|
|
|
|
static constexpr std::size_t length_bits = 0;
|
|
|
|
|
|
static constexpr bool incremental = false;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
Fn fn;
|
|
|
|
|
|
|
|
|
|
|
|
paint_fn_at_pack(Beta t, Beta f, Fn g)
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)}, fn{std::move(g)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Fn, typename Beta>
|
|
|
|
|
|
inline auto path_paint(Fn fn, Beta t,
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_fn_pack<std::decay_t<Beta>, std::decay_t<Fn>>(
|
|
|
|
|
|
std::move(t), std::move(f), std::move(fn));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <typename Fn>
|
|
|
|
|
|
inline auto path_paint(Fn fn)
|
|
|
|
|
|
{
|
|
|
|
|
|
return path_paint(std::move(fn), uint64_t{1});
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Fn, typename Beta>
|
|
|
|
|
|
inline auto path_paint_at(Fn fn, Beta t,
|
|
|
|
|
|
Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return paint_fn_at_pack<N, std::decay_t<Beta>, std::decay_t<Fn>>(
|
|
|
|
|
|
std::move(t), std::move(f), std::move(fn));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Incremental comparison: the same predicate, correct at every prefix length.
|
|
|
|
|
|
/// Evaluate the full point with `cmp`, and a prefix with `cmp_prefix<L>`.
|
|
|
|
|
|
template <typename Spec>
|
|
|
|
|
|
struct idcf_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr bool incremental = true;
|
|
|
|
|
|
static constexpr cmp_kind kind = Spec::kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = Spec::prefix;
|
|
|
|
|
|
static constexpr std::size_t block_width = Spec::block_width;
|
|
|
|
|
|
static constexpr std::size_t length_bits = spec_length_bits<Spec>::value;
|
|
|
|
|
|
using beta_type = typename Spec::beta_type;
|
|
|
|
|
|
beta_type if_true;
|
|
|
|
|
|
beta_type if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit idcf_pack(Spec spec)
|
|
|
|
|
|
: if_true{std::move(spec.if_true)}, if_false{std::move(spec.if_false)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Spec>
|
|
|
|
|
|
inline auto idcf(Spec spec)
|
|
|
|
|
|
{
|
|
|
|
|
|
static_assert(is_paint_kind(Spec::kind) == false,
|
|
|
|
|
|
"idcf wraps lt/leq/gt/geq; path paints are already one full-domain value");
|
|
|
|
|
|
static_assert(Spec::block_width == 0,
|
|
|
|
|
|
"idcf uses the per-level path, not blocked checkpoints");
|
|
|
|
|
|
return idcf_pack<Spec>{std::move(spec)};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// Equality specs: eq / eq_at
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
struct eq_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_eq = true;
|
|
|
|
|
|
static constexpr std::size_t prefix = 0;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit eq_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
struct eq_at_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static constexpr bool is_eq = true;
|
|
|
|
|
|
static constexpr std::size_t prefix = N;
|
|
|
|
|
|
using beta_type = Beta;
|
|
|
|
|
|
Beta if_true;
|
|
|
|
|
|
Beta if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit eq_at_pack(Beta t, Beta f = detail::dcf_impl::default_false<Beta>())
|
|
|
|
|
|
: if_true{std::move(t)}, if_false{std::move(f)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <typename Beta>
|
|
|
|
|
|
inline auto eq(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return eq_pack<std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
template <std::size_t N, typename Beta>
|
|
|
|
|
|
inline auto eq_at(Beta t, Beta f = detail::dcf_impl::default_false<std::decay_t<Beta>>())
|
|
|
|
|
|
{
|
|
|
|
|
|
return eq_at_pack<N, std::decay_t<Beta>>(std::move(t), std::move(f));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-24 20:44:07 -06:00
|
|
|
|
template <std::size_t BlockWidth, typename Spec>
|
|
|
|
|
|
struct block_width_pack
|
|
|
|
|
|
{
|
|
|
|
|
|
static_assert(BlockWidth >= 1, "block_width<B> needs B >= 1");
|
|
|
|
|
|
static_assert(Spec::block_width == 0, "comparison is already block_width");
|
|
|
|
|
|
static constexpr bool is_cmp = true;
|
|
|
|
|
|
static constexpr std::size_t block_width = BlockWidth;
|
|
|
|
|
|
static constexpr cmp_kind kind = Spec::kind;
|
|
|
|
|
|
static constexpr std::size_t prefix = Spec::prefix;
|
|
|
|
|
|
static constexpr bool incremental = spec_is_incremental<Spec>::value;
|
|
|
|
|
|
static constexpr std::size_t length_bits = spec_length_bits<Spec>::value;
|
|
|
|
|
|
using beta_type = typename Spec::beta_type;
|
|
|
|
|
|
beta_type if_true;
|
|
|
|
|
|
beta_type if_false;
|
|
|
|
|
|
|
|
|
|
|
|
explicit block_width_pack(Spec spec)
|
|
|
|
|
|
: if_true{std::move(spec.if_true)}, if_false{std::move(spec.if_false)} { }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t BlockWidth>
|
|
|
|
|
|
struct block_width_fn
|
|
|
|
|
|
{
|
|
|
|
|
|
template <typename Spec>
|
|
|
|
|
|
constexpr auto operator()(Spec spec) const
|
|
|
|
|
|
{
|
|
|
|
|
|
return block_width_pack<BlockWidth, Spec>{std::move(spec)};
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t BlockWidth>
|
|
|
|
|
|
inline constexpr block_width_fn<BlockWidth> block_width{};
|
|
|
|
|
|
|
2026-09-24 14:08:32 -06:00
|
|
|
|
template <typename T> struct is_cmp_spec : std::false_type {};
|
|
|
|
|
|
template <cmp_kind K, typename B> struct is_cmp_spec<cmp_pack<K, B>> : std::true_type {};
|
|
|
|
|
|
template <std::size_t N, cmp_kind K, typename B>
|
|
|
|
|
|
struct is_cmp_spec<cmp_at_pack<N, K, B>> : std::true_type {};
|
2026-09-24 20:44:07 -06:00
|
|
|
|
template <cmp_kind K, typename B, std::size_t L>
|
|
|
|
|
|
struct is_cmp_spec<paint_pack<K, B, L>> : std::true_type {};
|
|
|
|
|
|
template <std::size_t N, cmp_kind K, typename B, std::size_t L>
|
|
|
|
|
|
struct is_cmp_spec<paint_at_pack<N, K, B, L>> : std::true_type {};
|
|
|
|
|
|
template <typename B, typename Fn>
|
|
|
|
|
|
struct is_cmp_spec<paint_fn_pack<B, Fn>> : std::true_type {};
|
|
|
|
|
|
template <std::size_t N, typename B, typename Fn>
|
|
|
|
|
|
struct is_cmp_spec<paint_fn_at_pack<N, B, Fn>> : std::true_type {};
|
|
|
|
|
|
template <typename Spec>
|
|
|
|
|
|
struct is_cmp_spec<idcf_pack<Spec>> : std::true_type {};
|
|
|
|
|
|
template <std::size_t B, typename Spec>
|
|
|
|
|
|
struct is_cmp_spec<block_width_pack<B, Spec>> : std::true_type {};
|
2026-09-24 14:08:32 -06:00
|
|
|
|
template <typename T>
|
|
|
|
|
|
inline constexpr bool is_cmp_spec_v = is_cmp_spec<T>::value;
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T> struct is_eq_spec : std::false_type {};
|
|
|
|
|
|
template <typename B> struct is_eq_spec<eq_pack<B>> : std::true_type {};
|
|
|
|
|
|
template <std::size_t N, typename B>
|
|
|
|
|
|
struct is_eq_spec<eq_at_pack<N, B>> : std::true_type {};
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
inline constexpr bool is_eq_spec_v = is_eq_spec<T>::value;
|
|
|
|
|
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
|
|
inline constexpr bool is_dcf_spec_v = is_cmp_spec_v<T>;
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace dpf
|
|
|
|
|
|
|
|
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_DCF_HPP__
|