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.
2026-09-28 05:59:19 -06:00
/// @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.
2026-09-24 14:08:32 -06:00
/// @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"
2026-09-24 23:18:10 -06:00
# include "dpf/cmp_group.hpp"
2026-09-24 14:08:32 -06:00
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 23:18:10 -06:00
/// @brief Comparison kind for the optional DCF channel on a key.
/// @details `lt`/`leq`/`gt`/`geq` are the comparison predicates. The later kinds are
2026-09-24 20:44:07 -06:00
/// 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 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 ;
}
}
2026-09-24 23:18:10 -06:00
/// @brief Unit plant for `path_paint`. `prefix` is the in-lane matched prefix.
2026-09-24 20:44:07 -06:00
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 ;
}
2026-09-24 23:18:10 -06:00
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 ;
}
2026-09-24 14:08:32 -06:00
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 ;
2026-09-24 23:18:10 -06:00
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 ;
}
2026-09-24 14:08:32 -06:00
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 > )
2026-09-28 05:59:19 -06:00
return a ^ b ;
2026-09-24 14:08:32 -06:00
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 ) } ;
2026-09-24 23:18:10 -06:00
else if constexpr ( detail : : has_integral_representation < Beta > : : value )
return Beta : : from_raw ( static_cast < typename Beta : : integral_type > ( v ) ) ;
2026-09-24 14:08:32 -06:00
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 ;
}
2026-09-24 23:18:10 -06:00
/// @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`
2026-09-24 14:08:32 -06:00
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 ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 14:08:32 -06:00
/// 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).
2026-09-24 23:18:10 -06:00
/// @tparam BlockSampler block sampler
/// @param mask the bit mask
/// @param sample the `sample`
/// @return the returned `uint64_t`
2026-09-24 14:08:32 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
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 23:18:10 -06:00
/// @brief Same recurrence as `make_value_cw`, planting `plant` on the lose child
2026-09-24 20:44:07 -06:00
/// in both directions. `plant == 0` leaves the correction unchanged.
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
/// subtree at that depth reconstructs to this value; `leaf` is the full match.
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 23:18:10 -06:00
/// @brief Final leaf value CW. `on_path` is the payload reconstructed when the query
2026-09-24 14:08:32 -06:00
/// stays on α 's path through all levels (0 for strict lt/geq; β for leq/gt).
2026-09-24 23:18:10 -06:00
/// @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
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
2026-09-24 23:18:10 -06:00
/// @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` /
2026-09-24 14:08:32 -06:00
/// `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 ; }
} ;
2026-09-24 23:18:10 -06:00
/// @brief Backward-compatible alias while call sites migrate.
2026-09-24 14:08:32 -06:00
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 ) } { }
} ;
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-28 05:59:19 -06:00
/// \complexity O(1). Builds a spec object. The n-level walk is `make_dpf` or `ds_advance_level`, not this function.
2026-09-24 14:08:32 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Low `LengthBits` hold the common-prefix length. Above them sits the
2026-09-24 20:44:07 -06:00
/// matched prefix packed into the low bits of the lane (`α >> (N − d)`).
2026-09-24 23:18:10 -06:00
/// @tparam LengthBits length bits
/// @tparam Beta payload type
/// @param t the `t`
/// @param f the `f`
/// @return Low `LengthBits` hold the common-prefix length
2026-09-24 20:44:07 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @brief Arbitrary unit plant. `fn(matched, in_lane_prefix, leaf)` returns the β = 1
2026-09-24 20:44:07 -06:00
/// value of that sibling subtree (`leaf` is the full match, `matched == N`).
2026-09-24 23:18:10 -06:00
/// @details The result is scaled by `if_true − if_false` like the canned recipes.
/// @tparam Beta payload type
/// @tparam Fn fn
2026-09-24 20:44:07 -06:00
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 ) ) ;
}
2026-09-24 23:18:10 -06:00
/// @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
2026-09-24 20:44:07 -06:00
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 ;
2026-09-28 05:59:19 -06:00
using inner_spec = Spec ;
2026-09-24 20:44:07 -06:00
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-28 05:59:19 -06:00
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 { } ;
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 ;
2026-09-28 05:59:19 -06:00
using inner_spec = Spec ;
2026-09-24 20:44:07 -06:00
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-28 05:59:19 -06:00
// ---------------------------------------------------------------------------
// 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 > ( ) ;
}
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__