libdpf/include/dpf/dcf.hpp
Ryan Henry 0d22946a0e Checkpoint the party/runtime stack before share-program and malicious-mode work.
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>
2026-09-28 05:59:19 -06:00

993 lines
34 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// @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.
/// @note Following Boyle, Chandran, Gilboa, Gupta, Ishai, Kumar, and Rathee, EUROCRYPT 2021 (ePrint 2020/1392): a comparison is a DPF spine plus one value-correction word per level. `eq` is a point payload, not that DCF.
/// @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"
#include "dpf/cmp_group.hpp"
namespace dpf
{
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 && ...);
/// @brief Comparison kind for the optional DCF channel on a key.
/// @details `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.
enum class cmp_kind : uint8_t
{
lt = 0,
leq = 1,
gt = 2,
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
};
/// @brief True for the path-paint kinds. Comparisons stay `lt`/`leq`/`gt`/`geq`.
/// @param kind the comparison or paint kind
/// @return True for the path-paint kinds
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;
}
}
/// @brief 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);
enum class cmp_trivial : uint8_t
{
none = 0,
always_true = 1,
always_false = 2
};
namespace detail
{
namespace dcf_impl
{
template <typename Beta>
HEDLEY_NO_THROW
Beta default_false() noexcept
{
if constexpr (std::is_same_v<Beta, dpf::bit>)
return dpf::bit::zero;
else
return Beta{};
}
template <typename Beta>
HEDLEY_NO_THROW
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 if constexpr (detail::has_integral_representation<Beta>::value)
{
using raw_type = typename Beta::integral_type;
using unsigned_type = utils::make_unsigned_t<raw_type>;
const auto t = static_cast<uint64_t>(
static_cast<unsigned_type>(if_true.integral_representation()));
const auto f = static_cast<uint64_t>(
static_cast<unsigned_type>(if_false.integral_representation()));
return (t - f) & mask;
}
else
{
return (static_cast<uint64_t>(if_true)
- static_cast<uint64_t>(if_false)) & mask;
}
}
template <typename Beta>
HEDLEY_NO_THROW
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 if constexpr (detail::has_integral_representation<Beta>::value)
{
using raw_type = typename Beta::integral_type;
using unsigned_type = utils::make_unsigned_t<raw_type>;
return static_cast<uint64_t>(static_cast<unsigned_type>(
beta.integral_representation())) & mask;
}
else
return static_cast<uint64_t>(beta) & mask;
}
template <typename Beta>
HEDLEY_NO_THROW
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 a ^ b;
else
return static_cast<Beta>(a - b);
}
template <typename Beta>
HEDLEY_NO_THROW
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 if constexpr (detail::has_integral_representation<Beta>::value)
return Beta::from_raw(static_cast<typename Beta::integral_type>(v));
else
return static_cast<Beta>(v);
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t default_mask_for_bits(std::size_t out_bits) noexcept
{
if (out_bits >= 64)
return ~0ULL;
if (out_bits == 0)
return 0ULL;
return (1ULL << out_bits) - 1ULL;
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t neg_m(uint64_t x, uint64_t mask) noexcept
{
return (0ULL - x) & mask;
}
HEDLEY_CONST
HEDLEY_NO_THROW
HEDLEY_ALWAYS_INLINE
constexpr uint64_t sgn_m(uint8_t t1, uint64_t x, uint64_t mask) noexcept
{
return t1 ? neg_m(x, mask) : x;
}
/// @brief Convert a GGM node to a group element (low 64 bits, control bits cleared).
/// @param n the `n`
/// @param mask the bit mask
/// @return the returned `uint64_t`
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;
}
/// @brief Draw the group-width blind `r` used to split the `cmp_addend` share.
/// @details `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).
/// @tparam BlockSampler block sampler
/// @param mask the bit mask
/// @param sample the `sample`
/// @return the returned `uint64_t`
template <typename BlockSampler>
HEDLEY_ALWAYS_INLINE
HEDLEY_NO_THROW
uint64_t sample_addend_blind(uint64_t mask, BlockSampler && sample) noexcept
{
return convert_node(dpf::unset_lo_2bits(sample()), mask);
}
/// @brief One level of value CW on GGM children. Updates running `Va`.
/// @details `ai` is the keep-path bit of the (effective) threshold.
/// @param c0L the `c0L`
/// @param c0R the `c0R`
/// @param c1L the `c1L`
/// @param c1R the `c1R`
/// @param t0 the `t0`
/// @param t1 the `t1`
/// @param ai the `ai`
/// @param Va the `Va`
/// @param beta the payload
/// @param mask the bit mask
/// @return One level of value CW on GGM children
HEDLEY_NO_THROW
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;
}
/// @brief Same recurrence as `make_value_cw`, planting `plant` on the lose child
/// in both directions. `plant == 0` leaves the correction unchanged.
/// @param c0L the `c0L`
/// @param c0R the `c0R`
/// @param c1L the `c1L`
/// @param c1R the `c1R`
/// @param t0 the `t0`
/// @param t1 the `t1`
/// @param ai the `ai`
/// @param Va the `Va`
/// @param plant the unit plant
/// @param mask the bit mask
/// @return Same recurrence as `make_value_cw`, planting `plant` on the lose child in both
/// directions
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;
}
/// @brief High `d` bits of an `nbits`-wide lane, in that lane's own positions.
/// @param alpha the secret input point
/// @param nbits the width in bits
/// @param d the `d`
/// @return 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);
}
/// @brief Unit (β = 1) lose-subtree or leaf plant. The caller scales by δ.
/// @details `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.
/// @param kind the comparison or paint kind
/// @param matched the number of leading bits shared with the secret point
/// @param alpha the secret input point
/// @param nbits the width in bits
/// @param length_bits the bits used to store the prefix length
/// @param leaf the leaf value
/// @param fn the `fn`
/// @param ctx the `ctx`
/// @return Unit (β = 1) lose-subtree or leaf plant
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;
}
/// @brief 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).
/// @param s0 the `s0`
/// @param s1 the `s1`
/// @param t1 the `t1`
/// @param Va the `Va`
/// @param mask the bit mask
/// @param on_path the value reconstructed on the secret path
/// @return Final leaf value CW
HEDLEY_NO_THROW
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
/// @brief Comparison metadata on an incremental key (value CWs live on the key).
/// @details 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;
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
HEDLEY_NO_THROW
bool empty() const noexcept { return !active; }
};
/// @brief 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;
static constexpr std::size_t block_width = 0;
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;
static constexpr std::size_t block_width = 0;
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)} { }
};
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
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));
}
// ---------------------------------------------------------------------------
// 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));
}
/// @brief Low `LengthBits` hold the common-prefix length. Above them sits the
/// matched prefix packed into the low bits of the lane (`α >> (N − d)`).
/// @tparam LengthBits length bits
/// @tparam Beta payload type
/// @param t the `t`
/// @param f the `f`
/// @return Low `LengthBits` hold the common-prefix length
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));
}
/// @brief Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1
/// value of that sibling subtree (`leaf` is the full match, `matched == N`).
/// @details The result is scaled by `if_true − if_false` like the canned recipes.
/// @tparam Beta payload type
/// @tparam Fn fn
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));
}
/// @brief Incremental comparison: the same predicate, correct at every prefix length.
/// @details Evaluate the full point with `cmp`, and a prefix with `cmp_prefix<L>`.
/// @tparam Spec comparison or interval specification
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 inner_spec = Spec;
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)};
}
// ---------------------------------------------------------------------------
// 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));
}
template <typename T, typename = void>
struct pack_has_fn : std::false_type {};
template <typename T>
struct pack_has_fn<T, std::void_t<decltype(std::declval<T &>().fn)>>
: std::true_type {};
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 inner_spec = Spec;
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{};
// ---------------------------------------------------------------------------
// Same comparison shape, wildcard payload. Wrappers (`idcf`, `block_width`,
// `*_at`, path paints) keep their outer type; only the leaf beta changes.
// ---------------------------------------------------------------------------
template <typename T> struct is_idcf_pack : std::false_type {};
template <typename Spec>
struct is_idcf_pack<idcf_pack<Spec>> : std::true_type {};
template <typename T>
inline constexpr bool is_idcf_pack_v = is_idcf_pack<std::decay_t<T>>::value;
template <typename T> struct is_block_width_pack : std::false_type {};
template <std::size_t B, typename Spec>
struct is_block_width_pack<block_width_pack<B, Spec>> : std::true_type {};
template <typename T>
inline constexpr bool is_block_width_pack_v =
is_block_width_pack<std::decay_t<T>>::value;
namespace detail
{
template <typename Spec>
auto cmp_wildcard_shape();
template <cmp_kind K, typename B>
auto cmp_wildcard_shape(cmp_pack<K, B>)
{
using W = wildcard_value<concrete_type_t<B>>;
return cmp_pack<K, W>(W{}, W{});
}
template <std::size_t N, cmp_kind K, typename B>
auto cmp_wildcard_shape(cmp_at_pack<N, K, B>)
{
using W = wildcard_value<concrete_type_t<B>>;
return cmp_at_pack<N, K, W>(W{}, W{});
}
template <cmp_kind K, typename B, std::size_t L>
auto cmp_wildcard_shape(paint_pack<K, B, L>)
{
using W = wildcard_value<concrete_type_t<B>>;
return paint_pack<K, W, L>(W{}, W{});
}
template <std::size_t N, cmp_kind K, typename B, std::size_t L>
auto cmp_wildcard_shape(paint_at_pack<N, K, B, L>)
{
using W = wildcard_value<concrete_type_t<B>>;
return paint_at_pack<N, K, W, L>(W{}, W{});
}
template <typename B, typename Fn>
auto cmp_wildcard_shape(paint_fn_pack<B, Fn> spec)
{
using W = wildcard_value<concrete_type_t<B>>;
return paint_fn_pack<W, std::decay_t<Fn>>(W{}, W{}, std::move(spec.fn));
}
template <std::size_t N, typename B, typename Fn>
auto cmp_wildcard_shape(paint_fn_at_pack<N, B, Fn> spec)
{
using W = wildcard_value<concrete_type_t<B>>;
return paint_fn_at_pack<N, W, std::decay_t<Fn>>(W{}, W{}, std::move(spec.fn));
}
template <typename Spec>
auto cmp_wildcard_shape()
{
if constexpr (is_idcf_pack_v<Spec>)
return idcf(cmp_wildcard_shape<typename Spec::inner_spec>());
else if constexpr (is_block_width_pack_v<Spec>)
return block_width<Spec::block_width>(
cmp_wildcard_shape<typename Spec::inner_spec>());
else
{
using B = typename Spec::beta_type;
return cmp_wildcard_shape(Spec{B{}, B{}});
}
}
} // namespace detail
/// @brief The same comparison or paint spec, with a wildcard payload.
/// @details `idcf`, `block_width`, and `*_at` stay wrapped. A spec that is
/// already wildcard is returned unchanged. `path_paint` keeps `fn`.
template <typename Spec>
auto cmp_spec_as_wildcard(Spec spec)
{
using S = std::decay_t<Spec>;
if constexpr (is_wildcard_v<typename S::beta_type>)
return spec;
else if constexpr (pack_has_fn<S>::value)
return detail::cmp_wildcard_shape(std::move(spec));
else
return detail::cmp_wildcard_shape<S>();
}
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 {};
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 {};
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__