/// @file dpf/path_memoizer.hpp /// @brief /// @details /// @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_PATH_MEMOIZER_HPP__ #define LIBDPF_INCLUDE_DPF_PATH_MEMOIZER_HPP__ #include "hedley/hedley.h" #include #include #include #include #include #include #include #include #include "dpf/utils.hpp" #include "dpf/secret_share.hpp" namespace dpf { /// Path memoizers key on the underlying DPF key type. `party_key` wrappers /// share the same tree layout, so a memoizer built for party 0 also accepts /// party 1 (and bare keys). template using path_memoizer_key_t = unwrap_party_key_t; template struct path_memoizer_base { public: using dpf_type = DpfKey; using input_type = typename DpfKey::input_type; using node_type = typename DpfKey::interior_node; using return_type = ReturnT; using iterator_type = return_type; virtual std::size_t assign_x(const dpf_type &, input_type) noexcept = 0; virtual node_type & operator[](std::size_t) noexcept = 0; virtual return_type begin() const noexcept = 0; virtual return_type end() const noexcept = 0; }; template struct alignas(alignof(typename path_memoizer_key_t::interior_node)) basic_path_memoizer final : public path_memoizer_base> { public: using dpf_type = path_memoizer_key_t; using input_type = typename dpf_type::input_type; using node_type = typename dpf_type::interior_node; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using return_type = std::add_pointer_t>; HEDLEY_PRAGMA(GCC diagnostic pop) using iterator_type = return_type; static constexpr auto depth = dpf_type::depth; basic_path_memoizer() : dpf_{std::nullopt}, x_{std::nullopt}, filled_to_{0} { } basic_path_memoizer(basic_path_memoizer &&) noexcept = default; basic_path_memoizer(const basic_path_memoizer &) = default; basic_path_memoizer & operator=(basic_path_memoizer &&) noexcept = default; basic_path_memoizer & operator=(const basic_path_memoizer &) = default; ~basic_path_memoizer() = default; std::size_t assign_x(const dpf_type & dpf, input_type new_x) noexcept override { static constexpr auto clz_xor = utils::countl_zero_symmetric_difference{}; if (dpf_.has_value() == true && std::memcmp(&dpf_root_, &dpf.root(), sizeof(node_type)) == 0 && std::memcmp(&dpf_common_part_hash_, &dpf.common_part_hash(), sizeof(digest_type)) == 0) { static constexpr auto complement_of = std::bit_not{}; input_type old_x = x_.value_or(complement_of(new_x)); x_ = new_x; const std::size_t resume = clz_xor(old_x, new_x) + 1; // Path slots below `resume` remain valid; invalidate the rest. if (filled_to_ >= resume) filled_to_ = resume - 1; return resume; } this->operator[](0) = dpf.root(); dpf_ = std::cref(dpf); dpf_root_ = dpf.root(); dpf_common_part_hash_ = dpf.common_part_hash(); x_ = new_x; filled_to_ = 0; return 1; } node_type & operator[](std::size_t i) noexcept override { return arr_[i]; } return_type begin() const noexcept override { if (x_.has_value() == true) { return std::addressof(arr_[depth]); } else { return end(); } } return_type end() const noexcept override { return std::addressof(arr_[depth+1]); } /// Inclusive high-water: `arr_[0..filled_to_]` are valid for the current x. std::size_t filled_to() const noexcept { return filled_to_; } void note_filled(std::size_t level) noexcept { if (level > filled_to_) filled_to_ = level; } private: std::optional> dpf_; node_type dpf_root_; digest_type dpf_common_part_hash_; std::optional x_; std::size_t filled_to_; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") std::array arr_; HEDLEY_PRAGMA(GCC diagnostic pop) }; template struct nonmemoizing_path_memoizer final : public path_memoizer_base> { public: using dpf_type = path_memoizer_key_t; using input_type = typename dpf_type::input_type; using node_type = typename dpf_type::interior_node; HEDLEY_PRAGMA(GCC diagnostic push) HEDLEY_PRAGMA(GCC diagnostic ignored "-Wignored-attributes") using return_type = std::add_pointer_t>; HEDLEY_PRAGMA(GCC diagnostic pop) using iterator_type = return_type; nonmemoizing_path_memoizer() : dpf_{std::nullopt} { } nonmemoizing_path_memoizer(nonmemoizing_path_memoizer &&) noexcept = default; nonmemoizing_path_memoizer(const nonmemoizing_path_memoizer &) = default; nonmemoizing_path_memoizer & operator=(nonmemoizing_path_memoizer &&) noexcept = default; nonmemoizing_path_memoizer & operator=(const nonmemoizing_path_memoizer &) = default; ~nonmemoizing_path_memoizer() = default; HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE std::size_t assign_x(const dpf_type & dpf, input_type) noexcept override { dpf_ = dpf; v = dpf.root(); return 1; } HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE node_type & operator[](std::size_t) noexcept override { return v; } return_type begin() const noexcept override { return std::addressof(v); } return_type end() const noexcept override { return std::addressof(v) + 1; } private: std::optional> dpf_; node_type v; }; namespace detail { template HEDLEY_ALWAYS_INLINE auto make_path_memoizer() { return MemoizerT(); } template struct has_path_high_water : std::false_type {}; template struct has_path_high_water().filled_to()), decltype(std::declval().note_filled(std::size_t{}))>> : std::true_type {}; template std::size_t path_resume_for_level(PathMemoizer & path, const DpfKey & dpf, typename DpfKey::input_type x, std::size_t to_level) { auto resume = path.assign_x(dpf, x); if constexpr (has_path_high_water::value) { resume = std::min(resume, path.filled_to() + 1); if (resume == 0) resume = 1; } else if (resume > to_level) { resume = 1; } return resume; } template void path_note_filled_to(PathMemoizer & path, std::size_t level) { if constexpr (has_path_high_water::value) path.note_filled(level); } /// Walk interior nodes so `path[0..to_level]` is valid for `x`. template void ensure_level(const DpfKey & dpf, typename DpfKey::input_type x, PathMemoizer & path, std::size_t to_level) { auto level_index = path_resume_for_level(path, dpf, x, to_level); DPF_UNROLL_LOOP for (auto mask = dpf.msb_mask >> (level_index - 1); level_index <= to_level; ++level_index, mask >>= 1) { bool bit = !!(mask & x); auto cw = dpf.correction_word(level_index - 1, bit); path[level_index] = DpfKey::traverse_interior(path[level_index - 1], cw, bit); } path_note_filled_to(path, to_level); } } // namespace detail template auto make_basic_path_memoizer() { return detail::make_path_memoizer>(); } template auto make_basic_path_memoizer(const DpfKey &) { return make_basic_path_memoizer(); } template auto make_nonmemoizing_path_memoizer() { return detail::make_path_memoizer>(); } template auto make_nonmemoizing_path_memoizer(const DpfKey &) { return make_nonmemoizing_path_memoizer(); } } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_PATH_MEMOIZER_HPP__