/// @file dpf/vec.hpp /// @brief `dpf::vec`, one output of `N` lanes added componentwise. /// @details Each lane lives in the ring of `T` (`+` and `-` wrap the way `T` /// wraps). A leaf adds every lane and does not carry from one lane /// into the next. `T` is an ordinary output type: an integer, /// `modint`, `fixedpoint`, `twobit`, `nyble`, or `xor_wrapper`. /// @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_VEC_HPP__ #define LIBDPF_INCLUDE_DPF_VEC_HPP__ #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/leaf_arithmetic.hpp" #include "dpf/utils.hpp" namespace dpf { /// @brief `N` lanes of `T`, combined componentwise with no carry between lanes. /// @tparam T lane type. Its `+`, `-`, and `*` are used per lane /// @tparam N number of lanes template struct vec { static_assert(N > 0, "dpf::vec needs at least one lane"); static constexpr bool dpf_vec = true; /// @brief Number of lanes. static constexpr std::size_t lane_count = N; /// @brief Type of one lane. using lane_type = T; /// @brief Lane storage, index 0 in the least-significant lane. std::array lanes{}; /// @brief Value-initialize every lane. HEDLEY_NO_THROW constexpr vec() noexcept = default; /// @brief Mutable lane `i`. /// @param i the lane index /// @return a reference to that lane HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr T & operator[](std::size_t i) noexcept { return lanes[i]; } /// @brief Lane `i`. /// @param i the lane index /// @return a reference to that lane HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr const T & operator[](std::size_t i) const noexcept { return lanes[i]; } /// @brief Negate every lane. /// @return the negated vector HEDLEY_ALWAYS_INLINE constexpr vec operator-() const noexcept(noexcept(static_cast(-std::declval()))) { vec out; for (std::size_t i = 0; i < N; ++i) out.lanes[i] = static_cast(-lanes[i]); return out; } /// @brief Add each lane, with no carry into the next lane. /// @param rhs the right-hand vector /// @return the lane-wise sum HEDLEY_ALWAYS_INLINE constexpr vec operator+(const vec & rhs) const noexcept(noexcept(std::declval() + std::declval())) { vec out; for (std::size_t i = 0; i < N; ++i) out.lanes[i] = static_cast(lanes[i] + rhs.lanes[i]); return out; } /// @brief Subtract each lane, with no borrow from the next lane. /// @param rhs the right-hand vector /// @return the lane-wise difference HEDLEY_ALWAYS_INLINE constexpr vec operator-(const vec & rhs) const noexcept(noexcept(std::declval() - std::declval())) { vec out; for (std::size_t i = 0; i < N; ++i) out.lanes[i] = static_cast(lanes[i] - rhs.lanes[i]); return out; } /// @brief Multiply each lane. /// @param rhs the right-hand vector /// @return the lane-wise product HEDLEY_ALWAYS_INLINE constexpr vec operator*(const vec & rhs) const noexcept(noexcept(std::declval() * std::declval())) { vec out; for (std::size_t i = 0; i < N; ++i) out.lanes[i] = static_cast(lanes[i] * rhs.lanes[i]); return out; } /// @brief Lane-wise equality. /// @param rhs the right-hand vector /// @return `true` when every lane matches HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bool operator==(const vec & rhs) const noexcept { for (std::size_t i = 0; i < N; ++i) { if (!(lanes[i] == rhs.lanes[i])) return false; } return true; } /// @brief Lane-wise inequality. /// @param rhs the right-hand vector /// @return `true` when some lane differs HEDLEY_NO_THROW HEDLEY_ALWAYS_INLINE constexpr bool operator!=(const vec & rhs) const noexcept { return !(*this == rhs); } }; namespace utils { template struct bitlength_of> : std::integral_constant * N> {}; } // namespace utils namespace leaf_arithmetic { namespace vec_detail { template struct is_std_array : std::false_type {}; template struct is_std_array> : std::true_type {}; /// @brief Add or subtract every stored lane. A node wider than the vector is a /// sequence of vectors, then a tail of leftover lanes. Bytes that do not /// fill a lane are XOR-combined so a random pad still cancels. /// @tparam T lane type /// @tparam N number of lanes /// @tparam Op lane operation /// @param dst the destination /// @param a the `a` /// @param b the `b` /// @param nbytes the number of bytes /// @param op the `op` template HEDLEY_ALWAYS_INLINE void apply_bytes(unsigned char * dst, const unsigned char * a, const unsigned char * b, std::size_t nbytes, Op op) { using V = dpf::vec; std::size_t off = 0; const std::size_t nvec = nbytes / sizeof(V); for (std::size_t i = 0; i < nvec; ++i) { V va, vb; std::memcpy(&va, a + off, sizeof(V)); std::memcpy(&vb, b + off, sizeof(V)); V vc = op(va, vb); std::memcpy(dst + off, &vc, sizeof(V)); off += sizeof(V); } while (off + sizeof(T) <= nbytes) { T va, vb; std::memcpy(&va, a + off, sizeof(T)); std::memcpy(&vb, b + off, sizeof(T)); T vc = op(va, vb); std::memcpy(dst + off, &vc, sizeof(T)); off += sizeof(T); } for (; off < nbytes; ++off) dst[off] = static_cast(a[off] ^ b[off]); } template HEDLEY_ALWAYS_INLINE NodeT apply_node(const NodeT & a, const NodeT & b, Op op) { alignas(NodeT) unsigned char ca[sizeof(NodeT)]; alignas(NodeT) unsigned char cb[sizeof(NodeT)]; alignas(NodeT) unsigned char cc[sizeof(NodeT)]; std::memcpy(ca, &a, sizeof(NodeT)); std::memcpy(cb, &b, sizeof(NodeT)); apply_bytes(cc, ca, cb, sizeof(NodeT), op); NodeT out; std::memcpy(&out, cc, sizeof(NodeT)); return out; } } // namespace vec_detail template struct add_t, NodeT, std::enable_if_t && !vec_detail::is_std_array::value>> { HEDLEY_ALWAYS_INLINE auto operator()(const NodeT & a, const NodeT & b) const { return vec_detail::apply_node(a, b, [](const auto & x, const auto & y) { return x + y; }); } }; template struct add_t, std::array> { auto operator()(const std::array & a, const std::array & b) const { std::array c{}; auto * dst = reinterpret_cast(c.data()); vec_detail::apply_bytes(dst, reinterpret_cast(a.data()), reinterpret_cast(b.data()), sizeof(c), [](const auto & x, const auto & y) { return x + y; }); return c; } }; template struct subtract_t, NodeT, std::enable_if_t && !vec_detail::is_std_array::value>> { HEDLEY_ALWAYS_INLINE auto operator()(const NodeT & a, const NodeT & b) const { return vec_detail::apply_node(a, b, [](const auto & x, const auto & y) { return x - y; }); } }; template struct subtract_t, std::array> { auto operator()(const std::array & a, const std::array & b) const { std::array c{}; auto * dst = reinterpret_cast(c.data()); vec_detail::apply_bytes(dst, reinterpret_cast(a.data()), reinterpret_cast(b.data()), sizeof(c), [](const auto & x, const auto & y) { return x - y; }); return c; } }; } // namespace leaf_arithmetic } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_VEC_HPP__