/// @file dpf/placement.hpp /// @brief Prefix placement (`at`), phantom cmp tag, and slot-meta machinery. /// @details Shared by `dpf_key` (unified key) and `incremental.hpp` (gen/eval). /// Holds the pure type-level pieces so `dpf_key.hpp` can build a /// `slot_meta` table for multi-level / cmp keys without depending on /// the generation / evaluation code that lives in `incremental.hpp`. /// @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_PLACEMENT_HPP__ #define LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__ #include #include #include #include #include #include #include "dpf/utils.hpp" #include "dpf/leaf_node.hpp" #include "dpf/wildcard.hpp" namespace dpf { // --------------------------------------------------------------------------- // Public placement sugar: `at(y, ys...)` // --------------------------------------------------------------------------- template struct at_pack { static constexpr std::size_t prefix = N; using outputs_tuple = std::tuple; outputs_tuple values; at_pack() = delete; explicit at_pack(OutputT y, OutputTs ...ys) : values{std::move(y), std::move(ys)...} { } }; template struct at_fn { template constexpr auto operator()(OutputT y, OutputTs ...ys) const { return at_pack(std::move(y), std::move(ys)...); } }; template inline constexpr at_fn at{}; template struct is_at : std::false_type {}; template struct is_at> : std::true_type {}; template inline constexpr bool is_at_v = is_at::value; /// Phantom pack element for a key's comparison (DCF) channel. Not a leaf: it /// only records the cmp prefix depth in the key's type. `Depth` is the number /// of tree levels the comparison walks (0 is reserved for "no cmp"). /// `OutBits` is the comparison output group width (bits of the β payload), /// so the value CWs / addend can be stored at group width instead of a full /// padded `uint64_t` per level. template struct cmp_channel_tag { static constexpr std::size_t depth = Depth; static constexpr std::size_t out_bits = OutBits; /// True when the comparison payload (β) is a wildcard to be assigned /// after keygen. Concrete (non-wildcard) cmp keys keep `Wild == false` /// so their layout / type name is unchanged. static constexpr bool wild = Wild; }; template struct is_cmp_channel_tag : std::false_type {}; template struct is_cmp_channel_tag> : std::true_type {}; template inline constexpr bool is_cmp_channel_tag_v = is_cmp_channel_tag>::value; namespace detail { namespace incr { // --------------------------------------------------------------------------- // A concrete placed output: an output type `OutputT` planted at prefix `N`. // --------------------------------------------------------------------------- template struct placed { static constexpr std::size_t prefix = N; using output_type = OutputT; OutputT value; OutputT addend{}; // public if_false for eq(...); party 0 absorbs at eval }; template struct is_placed : std::false_type {}; template struct is_placed> : std::true_type {}; template inline constexpr bool is_placed_v = is_placed>::value; template inline constexpr std::size_t out_bits_v = utils::bitlength_of_output_v, NodeT>; template inline constexpr std::size_t lg_opl_v = dpf::lg_outputs_per_leaf_v, NodeT>; template inline constexpr std::size_t level_of_v = N - lg_opl_v; template inline constexpr bool prefix_ok_v = (N >= lg_opl_v); // --------------------------------------------------------------------------- // Per-output slot metadata (packing / tree-level table). // --------------------------------------------------------------------------- struct slot_meta { std::size_t prefix; std::size_t tree_level; std::size_t pos_base; std::size_t group_id; std::size_t index_in_group; std::size_t out_bits; std::size_t lg_opl; std::size_t block_len; }; template constexpr std::size_t max_tree_level_impl(std::index_sequence) { std::size_t m = 0; ((m = std::max(m, level_of_v::prefix, typename std::tuple_element_t::output_type>)), ...); return m; } template inline constexpr std::size_t max_tree_level_v = max_tree_level_impl( std::make_index_sequence>{}); template constexpr bool all_prefixes_ok_impl(std::index_sequence) { return (prefix_ok_v::prefix, typename std::tuple_element_t::output_type> && ...); } template inline constexpr bool all_prefixes_ok_v = all_prefixes_ok_impl( std::make_index_sequence>{}); template constexpr void fill_slot_basics( std::array> & meta) { using P = std::tuple_element_t; using O = typename P::output_type; meta[I].prefix = P::prefix; meta[I].lg_opl = lg_opl_v; meta[I].out_bits = out_bits_v; meta[I].tree_level = P::prefix - meta[I].lg_opl; meta[I].block_len = dpf::block_length_of_leaf_v, NodeT>; meta[I].pos_base = 0; meta[I].group_id = 0; meta[I].index_in_group = 0; } template constexpr void fill_all_basics( std::array> & meta, std::index_sequence) { (fill_slot_basics(meta), ...); } template constexpr auto build_meta() { constexpr std::size_t n = std::tuple_size_v; std::array meta{}; fill_all_basics(meta, std::make_index_sequence{}); std::size_t next_gid = 0; for (std::size_t i = 0; i < n; ++i) { std::size_t gid = static_cast(-1); for (std::size_t j = 0; j < i; ++j) { if (meta[j].prefix == meta[i].prefix && meta[j].out_bits == meta[i].out_bits) { gid = meta[j].group_id; break; } } if (gid == static_cast(-1)) gid = next_gid++; meta[i].group_id = gid; } const std::size_t ngroups = next_gid; std::array group_count{}; for (std::size_t i = 0; i < n; ++i) group_count[i] = 0; for (std::size_t i = 0; i < n; ++i) meta[i].index_in_group = group_count[meta[i].group_id]++; std::array g_level{}; std::array g_blocks{}; std::array g_first{}; for (std::size_t g = 0; g < ngroups; ++g) { g_blocks[g] = 0; g_first[g] = n; g_level[g] = 0; } for (std::size_t i = 0; i < n; ++i) { const auto g = meta[i].group_id; g_level[g] = meta[i].tree_level; g_blocks[g] += meta[i].block_len; if (i < g_first[g]) g_first[g] = i; } constexpr std::size_t depth = max_tree_level_v; std::array g_done{}; for (std::size_t g = 0; g < ngroups; ++g) g_done[g] = false; for (std::size_t level = 0; level <= depth; ++level) { std::size_t cursor = (level == depth) ? 0 : 2; for (;;) { std::size_t best_g = n; std::size_t best_first = n; for (std::size_t g = 0; g < ngroups; ++g) { if (g_done[g] || g_level[g] != level) continue; if (g_first[g] < best_first) { best_first = g_first[g]; best_g = g; } } if (best_g == n) break; for (std::size_t i = 0; i < n; ++i) { if (meta[i].group_id == best_g) meta[i].pos_base = cursor; } cursor += g_blocks[best_g]; g_done[best_g] = true; } } return meta; } template constexpr auto build_group_order(const MetaArray & meta, std::size_t n) { std::array order{}; std::array used{}; for (std::size_t g = 0; g < NGroups; ++g) used[g] = false; for (std::size_t k = 0; k < NGroups; ++k) { std::size_t best = NGroups; std::size_t best_lvl = static_cast(-1); std::size_t best_first = n; for (std::size_t g = 0; g < NGroups; ++g) { if (used[g]) continue; std::size_t lvl = 0, first = n; for (std::size_t i = 0; i < n; ++i) { if (meta[i].group_id == g) { lvl = meta[i].tree_level; if (i < first) first = i; } } if (lvl < best_lvl || (lvl == best_lvl && first < best_first)) { best_lvl = lvl; best_first = first; best = g; } } order[k] = best; used[best] = true; } return order; } template constexpr InputT lane_input(InputT x, std::size_t prefix, std::size_t bitlen) { if (prefix >= bitlen) return x; // Shift on the integral representation so this works for `modint`, // `keyword` (whose `>>` yields a parent `modint`, not the keyword), and // signed/bitstring inputs. Reconstruct the input type from the shifted // integral value via `make_from_integral_value` (a friend of `keyword`). constexpr auto to_int = utils::to_integral_type{}; using FromI = typename utils::make_from_integral_value::integral_type; const auto shifted = static_cast(to_int(x) >> (bitlen - prefix)); return utils::make_from_integral_value{}(shifted); } // Concatenate index sequences template struct cat_seq; template <> struct cat_seq<> { using type = std::index_sequence<>; }; template struct cat_seq> { using type = std::index_sequence; }; template struct cat_seq, std::index_sequence, Rest...> { using type = typename cat_seq, Rest...>::type; }; template using cat_seq_t = typename cat_seq::type; template using keep_if_group = std::conditional_t< MetaHolder::value[I].group_id == G, std::index_sequence, std::index_sequence<>>; template struct filter_group; template struct filter_group> { using type = cat_seq_t...>; }; template using filter_group_t = typename filter_group>::type; template struct meta_holder { static constexpr auto value = KeyT::meta; }; // --------------------------------------------------------------------------- // Normalize a `dpf_key` output pack into (PlacedTuple, CmpDepth). // // Each pack element is one of: // - a bare output `T` -> placed // - a `placed` -> placed (from `at`) // - a `cmp_channel_tag` -> not a leaf; contributes Depth to CmpDepth // --------------------------------------------------------------------------- template struct normalize_one { using placed_tuple = std::tuple>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; }; template struct normalize_one> { using placed_tuple = std::tuple>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; }; template struct normalize_one> { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = Depth; static constexpr std::size_t cmp_out_bits = OutBits; static constexpr bool cmp_wild = Wild; }; template struct normalize_pack { using placed_tuple = decltype(std::tuple_cat( std::declval::placed_tuple>()...)); static constexpr std::size_t cmp_depth = (std::size_t{0} + ... + normalize_one::cmp_depth); // At most one comparison channel per key, so the sum is that channel's // output width (0 when there is no cmp channel). static constexpr std::size_t cmp_out_bits = (std::size_t{0} + ... + normalize_one::cmp_out_bits); // At most one comparison channel per key, so the OR is that channel's // wildcard flag (false when there is no cmp channel). static constexpr bool cmp_wild = (false || ... || normalize_one::cmp_wild); }; template struct normalize_pack { using placed_tuple = std::tuple<>; static constexpr std::size_t cmp_depth = 0; static constexpr std::size_t cmp_out_bits = 0; static constexpr bool cmp_wild = false; }; /// True iff the pack is "classic-shaped": every element is a bare output (no /// `placed<>` from `at<>` and no `cmp_channel_tag<>`). template inline constexpr bool is_classic_pack_v = !((is_placed_v || is_cmp_channel_tag_v) || ...); } // namespace incr } // namespace detail } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_PLACEMENT_HPP__