/// @file dpf/eval_interval.hpp /// @brief Evaluate every input in a closed interval. /// @details `[from, to]` is inclusive. The returned iterable yields one /// share per input, in that order. Pass a named output buffer; /// this overload binds it as a non-const reference. An interval /// memoizer is optional and comes after the buffer. /// /// Eager evaluation requires an assigned input offset: the range is /// traversed at `offset_x(from)..offset_x(to)`. When the input is /// still a wildcard, call `defer_eval_interval` instead — that fills /// a **full-domain** buffer at identity and returns a /// `deferred_rotated_subinterval` that applies the rotation after /// `assign_wildcard_input`. Interior-only prep with an assigned /// input but unassigned leaf is `defer_traverse_interval`. /// @snippet evaluation/eval_interval.cpp eval-interval /// @author Ryan Henry /// @author Christopher Jiang /// @copyright Copyright (c) 2019-2024 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. #ifndef LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__ #define LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__ #include #include #include "hedley/hedley.h" #include #include #include #include #include #include #include #include #include #include "dpf/dpf_key.hpp" #include "dpf/eval_common.hpp" #include "dpf/eval_target.hpp" #include "dpf/output_buffer.hpp" #include "dpf/interval_memoizer.hpp" #include "dpf/subinterval_iterable.hpp" #include "dpf/deferred_rotated_subinterval.hpp" #include "dpf/verifiable.hpp" #include "dpf/wildcard.hpp" namespace dpf { namespace internal { /// @brief Fold every node at `level_index` of a truncated interval tree into `pi`. /// @details Once-per-BFS-node absorption: node `i` has prefix /// `(from_node >> (depth - level_index)) + i`. Matches the contiguous /// layout built by `eval_interval_interior`. template HEDLEY_ALWAYS_INLINE void fold_interval_level(proof_token & pi, const DpfKey & dpf, std::size_t level_index, IntegralT from_node, std::size_t nodes_at_level, const NodeT * curr) { if constexpr (!DpfKey::is_verifiable) return; if (level_index == 0 || nodes_at_level == 0) return; const auto start = static_cast( utils::shift_right(from_node, DpfKey::depth - level_index)); const auto & cs = dpf.correction_seeds()[level_index - 1]; for (std::size_t i = 0; i < nodes_at_level; ++i) { detail::vdpf::fold_node(pi, level_index - 1, start + i, curr[i], cs); } } template inline auto eval_interval_interior(const DpfKey & dpf, IntegralT from_node, IntegralT to_node, IntervalMemoizer & memoizer, // NOLINT(runtime/references) std::size_t to_level = DpfKey::depth, proof_token * pi = nullptr) { using dpf_type = DpfKey; using integral_type = typename DpfKey::integral_type; using node_type = typename DpfKey::interior_node; // level_index represents the current level being built // level_index = 0 => root // level_index = depth => last layer of interior nodes // Proving needs every truncated-tree node: a warm memoizer that resumes // past level 1 would skip upper folds (and basic memoizers discard them). if (pi != nullptr) memoizer.clear_assignment(); std::size_t level_index = memoizer.assign_interval(dpf, from_node, to_node); std::size_t nodes_at_level = memoizer.get_nodes_at_level(); integral_type mask = utils::get_node_mask(dpf.msb_mask, level_index); for (; level_index <= to_level; level_index = memoizer.advance_level(), nodes_at_level = memoizer.get_nodes_at_level(), mask>>=1) { std::size_t i = 0, j = 0; bool from_offset = mask & from_node, to_offset = from_offset ^ (nodes_at_level & 1); const node_type cw[2] = { dpf.correction_word(level_index-1, 0), dpf.correction_word(level_index-1, 1) }; const bool is_last = dpf_type::tree::is_last_level(level_index - 1, dpf.depth); auto *prev = memoizer[level_index-1]; auto *curr = memoizer[level_index]; // process node which only requires a right traversal if (from_offset == true) { curr[i++] = dpf_type::traverse_interior(prev[j++], cw[1], 1, is_last); } // process all nodes which require both a left traversal and a right traversal const std::size_t both_end = nodes_at_level - to_offset; while (i + 8 <= both_end) { alignas(node_type) node_type parents[4]; alignas(node_type) node_type left[4]; alignas(node_type) node_type right[4]; DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { parents[t] = prev[j + t]; } dpf_type::traverse_interior01_x4(parents, cw[0], cw[1], left, right, is_last); DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { curr[i + 2 * t] = left[t]; curr[i + 2 * t + 1] = right[t]; } i += 8; j += 4; } DPF_UNROLL_LOOP for (; i < both_end;) { auto cur_node = prev[j++]; auto kids = dpf_type::traverse_interior01(cur_node, cw[0], cw[1], is_last); curr[i++] = kids[0]; curr[i++] = kids[1]; } // process node which only requires a left traversal if (to_offset == true) { curr[i] = dpf_type::traverse_interior(prev[j], cw[0], 0, is_last); } if (pi != nullptr) { fold_interval_level(*pi, dpf, level_index, from_node, nodes_at_level, curr); } } } template inline auto eval_interval_exterior(const DpfKey & dpf, IntegralT from_node, IntegralT to_node, OutputBuffer && outbuf, IntervalMemoizer && memoizer, std::size_t start = 0) { assert_not_wildcard_output(dpf); if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0})) throw std::runtime_error("to_node; std::size_t nodes_in_interval = static_cast(to_node - from_node); auto *nodes = memoizer[dpf_type::depth]; std::size_t j = 0, k = start; constexpr bool batch_leaves = dpf::block_length_of_leaf_v == 1 && !utils::is_packed_subbyte_v && dpf_type::outputs_per_leaf == 1; if constexpr (batch_leaves) { using leaf_ret = decltype(dpf.template traverse_exterior(nodes[0])); while (j + 8 <= nodes_in_interval) { leaf_ret leaves[8]; dpf.template traverse_exterior_x8(nodes + j, leaves); for (std::size_t t = 0; t < 8; ++t, ++k) { utils::raw_memcpy(&outbuf[k], &leaves[t], sizeof(output_type)); } j += 8; } } for (; j < nodes_in_interval; ++j, ++k) { // 1-arg member works for classic and verifiable/incr keys; the static // 2-arg form is classic-only. auto leaf = dpf.template traverse_exterior(nodes[j]); if constexpr (utils::is_packed_subbyte_v) { store_leaf_bytes(outbuf, k, leaf); } else { utils::raw_memcpy(&outbuf[k*dpf_type::outputs_per_leaf], &leaf, sizeof(output_type) * dpf_type::outputs_per_leaf); } } } template HEDLEY_ALWAYS_INLINE HEDLEY_NO_THROW void store_interval_leaf(OutputBuffer && outbuf, std::size_t k, const LeafT & leaf) noexcept { using dpf_type = DpfKey; using output_type = typename DpfKey::concrete_output_type; if constexpr (utils::is_packed_subbyte_v) { store_leaf_bytes(outbuf, k, leaf); } else { utils::raw_memcpy(&outbuf[k * dpf_type::outputs_per_leaf], &leaf, sizeof(output_type) * dpf_type::outputs_per_leaf); } } /// @brief One pass over the leaf-level interior nodes. When the selected output /// indices occupy a contiguous PRG-position range, a single batched /// `ExteriorPRG::eval` produces every output's leaf mask. /// @tparam Is is /// @tparam DpfKey DPF key type /// @tparam OutputBuffers tuple of output buffers /// @tparam IntervalMemoizer interval memoizer type /// @tparam IntegralT integral type /// @tparam IIs iis /// @param dpf the DPF key /// @param from_node the `from_node` /// @param to_node the `to_node` /// @param outbufs the named output buffers /// @param memoizer the memoizer built for this key /// @param start the start of the range /// @throws std::runtime_error if `to_node inline void eval_interval_exterior_fused(const DpfKey & dpf, IntegralT from_node, IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer, std::index_sequence, std::size_t start = 0) { assert_not_wildcard_output(dpf); if (HEDLEY_UNLIKELY(to_node < from_node && to_node != IntegralT{0})) throw std::runtime_error("to_node; HEDLEY_PRAGMA(GCC diagnostic pop) std::size_t nodes_in_interval = static_cast(to_node - from_node); auto *nodes = memoizer[DpfKey::depth]; auto cws = std::make_tuple(std::get(dpf.leaf_nodes).get()...); HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") auto apply_masks = [&](std::size_t k, const node_type & node, const node_type * HEDLEY_RESTRICT masks) { auto apply_output = [&](auto out_index, auto buf_index) { constexpr std::size_t out_i = decltype(out_index)::value; constexpr std::size_t buf_i = decltype(buf_index)::value; using output_type = typename DpfKey::concrete_output_type; using leaf_type = dpf::leaf_node_t; constexpr auto pos = block_offset_of_leaf_v; leaf_type mask; std::memcpy(&mask, masks + (pos - range::pos_min), sizeof(leaf_type)); // Subtractive share: CW_if_t − mask so reconstruct(y0, y1) = y0 − y1 = β. auto leaf = dpf::subtract_leaf( get_if_lo_bit(std::get(cws), node), mask); store_interval_leaf(utils::get(outbufs), k, leaf); }; (apply_output(std::integral_constant{}, std::integral_constant{}), ...); }; std::size_t j = 0, k = start; if constexpr (range::count == 2 && range::pos_min == 0) { for (; j + 4 <= nodes_in_interval; j += 4, k += 4) { alignas(node_type) node_type seeds[4]; alignas(node_type) node_type left[4]; alignas(node_type) node_type right[4]; DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { seeds[t] = utils::to_exterior_node( unset_lo_2bits(nodes[j + t])); } DpfKey::exterior_prg::eval01_x4(seeds, left, right); DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { node_type masks[2] = {left[t], right[t]}; apply_masks(k + t, nodes[j + t], masks); } } } else if constexpr (range::count == 1) { const auto pos = static_cast(range::pos_min); for (; j + 8 <= nodes_in_interval; j += 8, k += 8) { alignas(node_type) node_type seeds[8]; alignas(node_type) node_type masks[8]; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Warray-bounds") DPF_UNROLL_LOOP for (std::size_t t = 0; t < 8; ++t) { seeds[t] = utils::to_exterior_node( unset_lo_2bits(nodes[j + t])); } HEDLEY_PRAGMA(GCC diagnostic pop) DpfKey::exterior_prg::eval_x8(seeds, masks, pos); DPF_UNROLL_LOOP for (std::size_t t = 0; t < 8; ++t) { apply_masks(k + t, nodes[j + t], &masks[t]); } } for (; j + 4 <= nodes_in_interval; j += 4, k += 4) { alignas(node_type) node_type seeds[4]; alignas(node_type) node_type masks[4]; DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { seeds[t] = utils::to_exterior_node( unset_lo_2bits(nodes[j + t])); } DpfKey::exterior_prg::eval_x4(seeds, masks, pos); DPF_UNROLL_LOOP for (std::size_t t = 0; t < 4; ++t) { apply_masks(k + t, nodes[j + t], &masks[t]); } } } DPF_UNROLL_LOOP for (; j < nodes_in_interval; ++j, ++k) { const auto & node = nodes[j]; auto seed = utils::to_exterior_node(unset_lo_2bits(node)); std::array masks; DpfKey::exterior_prg::eval(seed, masks.data(), static_cast(range::count), static_cast(range::pos_min)); apply_masks(k, node, masks.data()); } HEDLEY_PRAGMA(GCC diagnostic pop) } template HEDLEY_ALWAYS_INLINE void eval_interval_exterior_all(const DpfKey & dpf, IntegralT from_node, IntegralT to_node, OutputBuffers && outbufs, IntervalMemoizer && memoizer, std::index_sequence idxs, std::size_t start = 0) { // Fused exterior needs classic leaf packing (`concrete_outputs_tuple` + // contiguous PRG lanes). Multi-level / cmp keys use the per-slot walk. // Extractable keys stretch leaves with `extractable_leaf_prg` (leaf XOF), // not `exterior_prg`. The fused path expands with AES and breaks the // programmed packed leaf (cold opens still cancel; hot lanes do not). if constexpr (!is_multilevel_key_v && !DpfKey::is_extractable) { using node_type = typename DpfKey::exterior_node; using outputs_tuple = typename DpfKey::concrete_outputs_tuple; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using range = leaf_prg_range; HEDLEY_PRAGMA(GCC diagnostic pop) if constexpr (range::is_contiguous) { eval_interval_exterior_fused(dpf, from_node, to_node, outbufs, memoizer, idxs, start); return; } } (eval_interval_exterior(dpf, from_node, to_node, utils::get(outbufs), memoizer, start), ...); } template auto eval_interval_impl(const DpfKey & dpf, InputT from, InputT to, OutputBuffers && outbufs, IntervalMemoizer && memoizer, std::index_sequence, proof_token * pi = nullptr) { using dpf_type = DpfKey; using integral_type = typename DpfKey::integral_type; utils::flip_msb_if_signed_integral(from); utils::flip_msb_if_signed_integral(to); integral_type from_node = utils::get_from_node(from), to_node = utils::get_to_node(to); constexpr auto to_int = utils::to_integral_type{}; const bool wraps = utils::interval_wraps( static_cast(to_int(from)), static_cast(to_int(to)), utils::bitlength_of_v); auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth, wraps); auto idxs = std::index_sequence{}; std::size_t start = 0; for (std::size_t s = 0; s < segs.n; ++s) { const auto & seg = segs.seg[s]; internal::eval_interval_interior(dpf, seg.from_node, seg.to_node, memoizer, DpfKey::depth, pi); eval_interval_exterior_all(dpf, seg.from_node, seg.to_node, outbufs, memoizer, idxs, start); start += seg.count; } } template auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers && outbufs, IntervalMemoizer && memoizer, std::index_sequence, proof_token * pi = nullptr) { using dpf_type = DpfKey; constexpr auto mod_pow_2 = utils::mod_pow_2{}; constexpr auto to_integral_t = utils::to_integral_type{}; constexpr auto bits = utils::bitlength_of_v; eval_interval_impl(dpf, from, to, outbufs, memoizer, std::make_index_sequence(), pi); // `to_integral_type` widens to at least `size_t`. Subtracting in that // wider type loses wrap-around of a narrower input domain (e.g. int16 // intervals that increment across 0). Mask back to the domain width so // `subinterval_iterable` length matches the inclusive [from, to] walk. auto from_i = to_integral_t(from); auto span = to_integral_t(to) - from_i; if constexpr (bits < utils::bitlength_of_v) { span &= (decltype(span){1} << bits) - 1; } auto from_sz = static_cast(from_i); auto to_sz = from_sz + static_cast(span); return utils::make_tuple(subinterval_iterable(std::begin(utils::get(outbufs)), utils::size(utils::get(outbufs)), from_sz, to_sz, mod_pow_2(from, dpf_type::lg_outputs_per_leaf), dpf_type::outputs_per_leaf)...); } } // namespace internal /// @name Closed-interval evaluation /// @tparam I output index /// @tparam Is the remaining output indices /// @tparam DpfKey DPF key type /// @tparam InputT input domain type /// @param dpf the DPF key /// @param from the inclusive start of the range /// @param to the inclusive end of the range /// @{ /// @brief Write outputs `I, Is...` for `[from, to]` into `outbufs`. /// @tparam OutputBuffers tuple of output buffers /// @tparam IntervalMemoizer interval memoizer type /// @param dpf the DPF key /// @param from the inclusive start of the range /// @param to the inclusive end of the range /// @param outbufs named buffer, or a tuple of buffers when several outputs /// are selected. Must outlive the returned iterable. /// @param memoizer workspace sized for at least this interval /// @param pi proof token folded along the interval, or null /// @return an iterable over the written outputs /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template , std::enable_if_t && !is_multilevel_key_v, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, IntervalMemoizer && memoizer, // NOLINT(runtime/references) proof_token * pi = nullptr) { assert_not_wildcard_output(dpf); return internal::eval_interval(dpf, dpf.offset_x(from), dpf.offset_x(to), outbufs, memoizer, std::make_index_sequence<1+sizeof...(Is)>(), pi); } /// @brief Evaluate `[from, to]` and fold a once-per-BFS-node VDPF proof. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template , std::enable_if_t && !is_multilevel_key_v, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, IntervalMemoizer && memoizer, prove_ref pr) // NOLINT(runtime/references) { static_assert(DpfKey::is_verifiable, "eval_interval(..., prove(π)): key must carry dpf::verifiable"); detail::vdpf::init_proof(pr.token, dpf); auto out = eval_interval(dpf, from, to, outbufs, std::forward(memoizer), &pr.token); detail::vdpf::fold_output_binding(pr.token, dpf); return out; } /// @brief Evaluate `[from, to]` and fold each written output into a sketch. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template , std::enable_if_t && !is_multilevel_key_v, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, IntervalMemoizer && memoizer, sketch_ref & sk) // NOLINT(runtime/references) { static_assert(DpfKey::is_extractable, "eval_interval(..., sketch(σ)): key must carry dpf::extractable"); auto ret = eval_interval(dpf, from, to, outbufs, std::forward(memoizer)); if constexpr (sizeof...(Is) == 0) { for (std::size_t k = 0; k < utils::size(outbufs); ++k) sk.absorb(outbufs[k]); } return ret; } /// @brief Evaluate `[from, to]` into `outbufs`, allocating a basic interval memoizer. /// @tparam OutputBuffers tuple of output buffers /// @param dpf the DPF key /// @param from the inclusive start of the range /// @param to the inclusive end of the range /// @param outbufs the named output buffers /// @return an iterable over the written outputs /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template && !is_multilevel_key_v, bool> = true, std::enable_if_t>, std::decay_t>, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs) // NOLINT(runtime/references) { return eval_interval(dpf, from, to, outbufs, dpf::make_basic_interval_memoizer(dpf, from, to)); } /// @brief Evaluate `[from, to]` into `outbufs` and fold a VDPF proof. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template && !is_multilevel_key_v, bool> = true, std::enable_if_t>, std::decay_t>, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, prove_ref pr) // NOLINT(runtime/references) { static_assert(DpfKey::is_verifiable, "eval_interval(..., prove(π)): key must carry dpf::verifiable"); return eval_interval(dpf, from, to, outbufs, dpf::make_basic_interval_memoizer(dpf, from, to), pr); } /// @brief Evaluate `[from, to]` with a caller-supplied memoizer. /// @tparam IntervalMemoizer interval memoizer type /// @param dpf the DPF key /// @param from the inclusive start of the range /// @param to the inclusive end of the range /// @param memoizer the memoizer built for this key /// @return `std::pair` of a new buffer (or tuple of buffers) and an iterable /// into that buffer. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template && !is_multilevel_key_v, bool> = true, std::enable_if_t>, std::decay_t>, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, IntervalMemoizer && memoizer) { auto outbufs = utils::make_tuple( make_output_buffer_for_interval(dpf, from, to), make_output_buffer_for_interval(dpf, from, to)...); // moving `outbufs` is allowed as the `outbufs` are `std::vectors` // the underlying data remains on the heap // and thus the data the iterable refers to is still valid auto iterable = eval_interval(dpf, from, to, outbufs, memoizer); return std::make_pair(std::move(outbufs), std::move(iterable)); } /// @brief Evaluate `[from, to]` with a memoizer and fold a VDPF proof. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template && !is_multilevel_key_v, bool> = true, std::enable_if_t>, std::decay_t>, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to, IntervalMemoizer && memoizer, prove_ref pr) { static_assert(DpfKey::is_verifiable, "eval_interval(..., prove(π)): key must carry dpf::verifiable"); auto outbufs = utils::make_tuple( make_output_buffer_for_interval(dpf, from, to), make_output_buffer_for_interval(dpf, from, to)...); auto iterable = eval_interval(dpf, from, to, outbufs, memoizer, pr); return std::make_pair(std::move(outbufs), std::move(iterable)); } /// @brief Evaluate `[from, to]`, allocating a basic interval memoizer and a buffer. /// @return `std::pair` of a new buffer (or tuple of buffers) and an iterable /// into that buffer. /// \complexity O(L) interior traversals and O(L) workspace in the basic memoizer. L is the number of leaf nodes covering the closed interval (`get_nodes_at_level` at `depth`). Level k expands `(to >> (n-k)) - (from >> (n-k)) + 1` nodes; those counts sum to Θ(L). The output buffer holds one slot per input in the interval. n is `depth`. template && !is_multilevel_key_v, bool> = true> HEDLEY_ALWAYS_INLINE auto eval_interval(const DpfKey & dpf, InputT from, InputT to) { return eval_interval(dpf, from, to, dpf::make_basic_interval_memoizer(dpf, from, to)); } /// @brief Fold every truncated-tree node of `[from, to]` into `pi`. /// @details Once per BFS node — same absorption as `eval_interval(..., prove(π))`. /// Caller must `init_proof` first, or use `prove_interval` below. template void prove_fold_interval(const KeyT & key, InputT from, InputT to, proof_token & pi) { static_assert(KeyT::is_verifiable, "prove_fold_interval: key must carry dpf::verifiable"); using dpf_type = KeyT; using input_type = typename KeyT::input_type; using integral_type = typename KeyT::integral_type; auto from_x = key.offset_x(static_cast(from)); auto to_x = key.offset_x(static_cast(to)); utils::flip_msb_if_signed_integral(from_x); utils::flip_msb_if_signed_integral(to_x); integral_type from_node = utils::get_from_node(from_x); integral_type to_node = utils::get_to_node(to_x); constexpr auto to_int = utils::to_integral_type{}; const bool wraps = utils::interval_wraps( static_cast(to_int(from_x)), static_cast(to_int(to_x)), utils::bitlength_of_v); auto segs = utils::split_leaf_nodes(from_node, to_node, key.depth, wraps); auto memo = make_basic_interval_memoizer(key, from, to); for (std::size_t s = 0; s < segs.n; ++s) { const auto & seg = segs.seg[s]; internal::eval_interval_interior(key, seg.from_node, seg.to_node, memo, KeyT::depth, &pi); } } /// @brief Initialise `pr.token` and fold `[from, to]` once per BFS node. /// @tparam KeyT verifiable key /// @tparam InputT input domain type /// @param key the party key /// @param from inclusive start /// @param to inclusive end /// @param pr proof token replaced with the interval fold template void prove_interval(const KeyT & key, InputT from, InputT to, prove_ref pr) { detail::vdpf::init_proof(pr.token, key); prove_fold_interval(key, from, to, pr.token); detail::vdpf::fold_output_binding(pr.token, key); } /// @brief Initialise `pr.token` and fold the full domain once per BFS node. /// @tparam KeyT verifiable key /// @param key the party key /// @param pr proof token replaced with the full-domain fold template void prove_full(const KeyT & key, prove_ref pr) { static_assert(KeyT::is_verifiable, "prove_full: key must carry dpf::verifiable"); using input_type = typename KeyT::input_type; prove_interval(key, std::numeric_limits::min(), std::numeric_limits::max(), pr); } /// @} namespace internal { template auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, IntervalMemoizer && memoizer, std::index_sequence) { using dpf_type = DpfKey; using input_type = typename dpf_type::input_type; const auto min = std::numeric_limits::min(); const auto max = std::numeric_limits::max(); // Full-domain identity traversal (offset unknown). Same interior/exterior // path as eager eval over `[min, max]` without folding `offset_x`. eval_interval_impl(dpf, min, max, outbufs, memoizer, std::make_index_sequence()); return utils::make_tuple( deferred_rotated_subinterval(dpf, std::begin(utils::get(outbufs)), std::end(utils::get(outbufs)), from, to, dpf_type::outputs_per_leaf)...); } } // namespace internal /// @name Deferred (pre-assign) evaluation /// @{ /// @brief Full-domain eval while the input offset is still unset. /// @details Requires a wildcard input that is not yet ready, and assigned /// leaf outputs `I, Is...`. `outbufs` must be sized for the **full** /// input domain (`make_output_buffer_for_full`). After /// `assign_wildcard_input`, call `.get()` on each returned view. /// @return one `deferred_rotated_subinterval` per selected output template && !is_multilevel_key_v, bool> = true> auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs, IntervalMemoizer && memoizer) // NOLINT(runtime/references) { static_assert(is_wildcard_v, "defer_eval_interval: key input must be a wildcard_value"); assert_wildcard_input(dpf); assert_not_wildcard_output(dpf); return internal::defer_eval_interval(dpf, from, to, outbufs, std::forward(memoizer), std::make_index_sequence<1 + sizeof...(Is)>()); } /// @brief `defer_eval_interval` with a basic full-domain memoizer. template && !is_multilevel_key_v, bool> = true, std::enable_if_t>, std::decay_t>, bool> = true> auto defer_eval_interval(const DpfKey & dpf, InputT from, InputT to, OutputBuffers & outbufs) // NOLINT(runtime/references) { return defer_eval_interval(dpf, from, to, outbufs, dpf::make_basic_full_memoizer(dpf)); } /// @brief Interior-only traverse of `[offset_x(from), offset_x(to)]`. /// @details Requires an assigned input. Skips exterior so the leaf may still /// be a wildcard; finish with `eval_interval` / exterior once the /// leaf is assigned (memoizer retains the interior). template && !is_multilevel_key_v, bool> = true> void defer_traverse_interval(const DpfKey & dpf, InputT from, InputT to, IntervalMemoizer & memoizer) // NOLINT(runtime/references) { assert_not_wildcard_input(dpf); using dpf_type = DpfKey; using input_type = typename dpf_type::input_type; using integral_type = typename dpf_type::integral_type; auto tfrom = dpf.offset_x(from); auto tto = dpf.offset_x(to); utils::flip_msb_if_signed_integral(tfrom); utils::flip_msb_if_signed_integral(tto); integral_type from_node = utils::get_from_node(tfrom); integral_type to_node = utils::get_to_node(tto); constexpr auto to_int = utils::to_integral_type{}; const bool wraps = utils::interval_wraps( static_cast(to_int(tfrom)), static_cast(to_int(tto)), utils::bitlength_of_v); auto segs = utils::split_leaf_nodes(from_node, to_node, dpf.depth, wraps); for (std::size_t s = 0; s < segs.n; ++s) { const auto & seg = segs.seg[s]; internal::eval_interval_interior(dpf, seg.from_node, seg.to_node, memoizer); } } /// @brief Interior-only full-domain traverse (input and/or leaf may be unset). /// @details Fills the memoizer for every interior node. Complete exterior /// (and any input rotation) after the missing wildcards are assigned. template && !is_multilevel_key_v, bool> = true> void defer_traverse_full(const DpfKey & dpf, IntervalMemoizer & memoizer) // NOLINT(runtime/references) { using dpf_type = DpfKey; using input_type = typename dpf_type::input_type; using integral_type = typename dpf_type::integral_type; auto from = std::numeric_limits::min(); auto to = std::numeric_limits::max(); utils::flip_msb_if_signed_integral(from); utils::flip_msb_if_signed_integral(to); integral_type from_node = utils::get_from_node(from); integral_type to_node = utils::get_to_node(to); internal::eval_interval_interior(dpf, from_node, to_node, memoizer); } /// @} } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_EVAL_INTERVAL_HPP__