/// @file grotto/carry_plan.hpp /// @brief Pure planner for carry-in, carry-out, and fused bitwidth corrections. /// @details A request names the width change and what is known about the sign. /// The recipe lists only the primitives that are still live after the /// output modulus and a public incoming carry have killed the rest. /// No keys are allocated here. /// @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_GROTTO_CARRY_PLAN_HPP__ #define LIBDPF_INCLUDE_GROTTO_CARRY_PLAN_HPP__ #include #include #include "hedley/hedley.h" namespace grotto { /// @brief Kind of bitwidth change the caller wants. enum class carry_mode : std::uint8_t { /// Drop `s` low bits; result lives in `Z/2^{n-s}`. Only carry-in survives. truncate_reduce = 0, /// Arithmetic right shift that stays in `Z/2^n`. Carry-out (sign wrap). same_ring = 1, /// Signed extension from `n` bits to `out_n` bits. Biased wrap only. extend = 2, /// Digit / window of width `s` with an optional incoming carry. window = 3 }; /// @brief What the caller already knows about `msb(x)`. enum class sign_knowledge : std::uint8_t { unknown = 0, nonnegative = 1, negative = 2 }; /// @brief Inputs to the planner. Widths are in bits. struct carry_request { /// @brief Source ring bitlength. unsigned n = 0; /// @brief Truncation / window width. Unused for plain `extend`. unsigned s = 0; /// @brief Output ring bitlength. unsigned out_n = 0; /// @brief Which width change to plan. carry_mode mode = carry_mode::truncate_reduce; /// @brief Sign knowledge for same-ring carry-out. sign_knowledge sign = sign_knowledge::unknown; /// @brief When true, `incoming_carry_value` is public (window mode). bool incoming_carry_public = false; /// @brief Public incoming carry when `incoming_carry_public`. unsigned incoming_carry_value = 0; /// @brief Ask for both carries when the mode would otherwise drop one. /// A reduced-ring modulus still kills the sign term. bool fuse = false; }; /// @brief Primitives the evaluator must materialise for one request. struct carry_recipe { carry_mode mode = carry_mode::truncate_reduce; unsigned n = 0; unsigned s = 0; unsigned out_n = 0; sign_knowledge sign = sign_knowledge::unknown; /// @brief Low-limb overflow `1{v0+v1 >= 2^s}` (truncate-reduce / fused). bool use_low_lt = false; /// @brief Comparison of `x` against `2^{n-1}` (secret-sign carry-out). bool use_msb_lt = false; /// @brief Beaver AND of the two share MSBs (known-sign, or fused with msb). bool use_share_msb_and = false; /// @brief When true the AND is of the complemented share MSBs (NOR). bool and_is_nor = false; /// @brief Signed extension: wrap after the public bias `2^{n-1}`. bool use_biased_wrap = false; /// @brief Window: `1{p0+p1 >= 2^d}`. bool use_window_overflow = false; /// @brief Window: `1{p0+p1 == 2^d-1}` (only when incoming carry is secret). bool use_window_eq = false; /// @brief Window: product of equality bit and secret incoming carry. bool use_window_product = false; /// @brief Units of `±2^{n-s}` applied after the share-MSB AND. std::int64_t and_unit = 0; /// @brief Payload of the MSB comparison when it alone selects the unit. std::int64_t msb_true_payload = 0; /// @brief Payload for the biased wrap (`2^n` in the wide ring). std::int64_t wrap_payload = 0; /// @brief Window digit width (`s`). unsigned window_d = 0; /// @brief Public incoming carry absorbed locally when known. unsigned public_incoming = 0; /// @brief True when the recipe named at least one interactive step. bool interactive() const noexcept { return use_low_lt || use_msb_lt || use_share_msb_and || use_biased_wrap || use_window_overflow || use_window_eq || use_window_product; } }; namespace carry_plan_detail { inline std::int64_t unit_pow2(unsigned e) { if (e >= 63u) throw std::invalid_argument("carry_plan: unit does not fit in int64"); return std::int64_t{1} << e; } } // namespace carry_plan_detail /// @brief Select the cheapest live primitives for `req`. /// @param req the request /// @return a recipe whose flags name only the corrections that survive /// @throws std::invalid_argument on inconsistent widths /// \complexity A constant number of comparisons of `n`, `s`, `out_n`, and `sign`. No allocation beyond the returned `carry_recipe`. `Θ(1)`. /// @see grotto::make_carry_keys HEDLEY_WARN_UNUSED_RESULT inline carry_recipe plan_carry(const carry_request & req) { if (req.n == 0u) throw std::invalid_argument("carry_plan: n must be positive"); if (req.out_n == 0u) throw std::invalid_argument("carry_plan: out_n must be positive"); carry_recipe r{}; r.mode = req.mode; r.n = req.n; r.s = req.s; r.out_n = req.out_n; r.sign = req.sign; const bool reduced_ring = (req.out_n + req.s == req.n) && (req.s > 0u); const bool same_width = (req.out_n == req.n); switch (req.mode) { case carry_mode::truncate_reduce: { if (req.s == 0u || req.s >= req.n) throw std::invalid_argument("carry_plan: truncate_reduce needs 0 < s < n"); if (req.out_n != req.n - req.s) throw std::invalid_argument( "carry_plan: truncate_reduce out_n must equal n-s"); r.use_low_lt = true; // Sign wrap is 0 in Z/2^{n-s}; never emit a carry-out term. break; } case carry_mode::same_ring: { if (req.s >= req.n) throw std::invalid_argument("carry_plan: same_ring needs s < n"); if (!same_width) { // Caller asked for a fused shift that reduces the ring: drop // carry-out and keep only the low-limb correction. if (req.out_n != req.n - req.s) throw std::invalid_argument( "carry_plan: same_ring out_n must be n or n-s"); r.use_low_lt = true; break; } if (req.fuse || req.s > 0u) r.use_low_lt = (req.s > 0u) && req.fuse; if (req.sign == sign_knowledge::unknown) { r.use_msb_lt = true; r.use_share_msb_and = true; r.msb_true_payload = 1; // returns msb bit; combined with AND below r.and_unit = carry_plan_detail::unit_pow2(req.n - req.s); } else if (req.sign == sign_knowledge::nonnegative) { r.use_share_msb_and = true; r.and_is_nor = false; r.and_unit = carry_plan_detail::unit_pow2(req.n - req.s); } else { r.use_share_msb_and = true; r.and_is_nor = true; r.and_unit = -carry_plan_detail::unit_pow2(req.n - req.s); } break; } case carry_mode::extend: { if (req.out_n <= req.n) throw std::invalid_argument("carry_plan: extend needs out_n > n"); // Known sign does not switch to the share-MSB AND. r.use_biased_wrap = true; r.wrap_payload = carry_plan_detail::unit_pow2(req.n); break; } case carry_mode::window: { if (req.s == 0u || req.s > req.n) throw std::invalid_argument("carry_plan: window needs 0 < s <= n"); r.window_d = req.s; r.use_window_overflow = true; if (req.incoming_carry_public) { r.public_incoming = req.incoming_carry_value; // Public carry 0 drops the equality; a public 1 still needs the // all-ones test, but the product is local (multiply by 1). if (req.incoming_carry_value != 0u) r.use_window_eq = true; } else { r.use_window_eq = true; r.use_window_product = true; } break; } } // Fused same-ring request that somehow landed with a reduced modulus: // the planner already dropped the sign term above via `!same_width`. (void)reduced_ring; return r; } /// @brief Fill a truncate-and-reduce request and plan it. /// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`. /// @see grotto::plan_carry HEDLEY_WARN_UNUSED_RESULT inline carry_recipe plan_carry_in(unsigned n, unsigned s) { carry_request req{}; req.n = n; req.s = s; req.out_n = n - s; req.mode = carry_mode::truncate_reduce; return plan_carry(req); } /// @brief Fill a same-ring carry-out request and plan it. /// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`. /// @see grotto::plan_carry HEDLEY_WARN_UNUSED_RESULT inline carry_recipe plan_carry_out(unsigned n, unsigned s, sign_knowledge sign) { carry_request req{}; req.n = n; req.s = s; req.out_n = n; req.mode = carry_mode::same_ring; req.sign = sign; req.fuse = false; return plan_carry(req); } /// @brief Fill a fused same-ring exact-shift request and plan it. /// @details When `out_n == n-s` the sign term is dropped automatically. /// \complexity Fills a `carry_request` and calls `plan_carry`. `Θ(1)`. /// @see grotto::plan_carry HEDLEY_WARN_UNUSED_RESULT inline carry_recipe plan_carry_fused(unsigned n, unsigned s, unsigned out_n, sign_knowledge sign = sign_knowledge::unknown) { carry_request req{}; req.n = n; req.s = s; req.out_n = out_n; req.mode = carry_mode::same_ring; req.sign = sign; req.fuse = true; return plan_carry(req); } } // namespace grotto #endif // LIBDPF_INCLUDE_GROTTO_CARRY_PLAN_HPP__