// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // SboWords.hpp // Word array with small-buffer optimization // Used inside the Int class as a replacement for std::vector #ifndef SANGI_SBO_WORDS_HPP #define SANGI_SBO_WORDS_HPP #include #include #include #include #include namespace sangi { /** * @brief uint64_t array with SBO (Small Buffer Optimization) * * Stores up to 9 words (576 bits) in an inline buffer and avoids * heap allocation in that range. * Provides an interface compatible with std::vector. * * Reason for choosing 9 words: when adding two 512-bit (8-word) values, * a carry may make the result 9 words. Keep that case within the SBO. * * Layout (96 bytes): * m_inline[9] : 72 bytes * m_data : 8 bytes (-> m_inline or heap) * m_size : 8 bytes * m_capacity : 8 bytes */ class SboWords { public: static constexpr size_t INLINE_CAP = 9; using value_type = uint64_t; using iterator = uint64_t*; using const_iterator = const uint64_t*; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; // ---------------------------------------------------------------- // Constructors / destructor // ---------------------------------------------------------------- SboWords() noexcept : m_data(m_inline), m_size(0), m_capacity(INLINE_CAP) {} SboWords(const SboWords& other) : m_size(other.m_size) { if (other.m_size <= INLINE_CAP) { m_data = m_inline; m_capacity = INLINE_CAP; } else { m_data = new uint64_t[other.m_size]; m_capacity = other.m_size; } std::memcpy(m_data, other.m_data, m_size * sizeof(uint64_t)); } SboWords(SboWords&& other) noexcept : m_size(other.m_size) { if (other.is_inline()) { // Copy the inline data m_data = m_inline; m_capacity = INLINE_CAP; std::memcpy(m_inline, other.m_inline, m_size * sizeof(uint64_t)); } else { // Steal the heap pointer m_data = other.m_data; m_capacity = other.m_capacity; other.m_data = other.m_inline; } other.m_size = 0; other.m_capacity = INLINE_CAP; } ~SboWords() { if (!is_inline()) { delete[] m_data; } } // ---------------------------------------------------------------- // Assignment operators // ---------------------------------------------------------------- SboWords& operator=(const SboWords& other) { if (this == &other) return *this; if (other.m_size <= m_capacity) { // Fits in the existing buffer std::memcpy(m_data, other.m_data, other.m_size * sizeof(uint64_t)); m_size = other.m_size; } else { // A new buffer is needed uint64_t* new_data = new uint64_t[other.m_size]; std::memcpy(new_data, other.m_data, other.m_size * sizeof(uint64_t)); if (!is_inline()) delete[] m_data; m_data = new_data; m_size = other.m_size; m_capacity = other.m_size; } return *this; } SboWords& operator=(SboWords&& other) noexcept { if (this == &other) return *this; if (!is_inline()) delete[] m_data; m_size = other.m_size; if (other.is_inline()) { m_data = m_inline; m_capacity = INLINE_CAP; std::memcpy(m_inline, other.m_inline, m_size * sizeof(uint64_t)); } else { m_data = other.m_data; m_capacity = other.m_capacity; other.m_data = other.m_inline; } other.m_size = 0; other.m_capacity = INLINE_CAP; return *this; } // ---------------------------------------------------------------- // Size / capacity // ---------------------------------------------------------------- constexpr size_t size() const noexcept { return m_size; } constexpr bool empty() const noexcept { return m_size == 0; } size_t capacity() const noexcept { return m_capacity; } // ---------------------------------------------------------------- // Element access // ---------------------------------------------------------------- uint64_t* data() noexcept { return m_data; } const uint64_t* data() const noexcept { return m_data; } uint64_t& operator[](size_t i) { return m_data[i]; } const uint64_t& operator[](size_t i) const { return m_data[i]; } uint64_t& back() { return m_data[m_size - 1]; } const uint64_t& back() const { return m_data[m_size - 1]; } // ---------------------------------------------------------------- // Iterators // ---------------------------------------------------------------- iterator begin() noexcept { return m_data; } iterator end() noexcept { return m_data + m_size; } const_iterator begin() const noexcept { return m_data; } const_iterator end() const noexcept { return m_data + m_size; } reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } // ---------------------------------------------------------------- // Modification operations // ---------------------------------------------------------------- void clear() noexcept { m_size = 0; } void resize(size_t n) { if (n <= m_size) { m_size = n; return; } ensure_capacity(n); // Zero-initialize the new elements std::memset(m_data + m_size, 0, (n - m_size) * sizeof(uint64_t)); m_size = n; } // resize variant that skips zero-initialization (for callers that overwrite all elements) void resize_uninitialized(size_t n) { if (n <= m_capacity) { m_size = n; return; } ensure_capacity(n); m_size = n; } void resize(size_t n, uint64_t val) { if (n <= m_size) { m_size = n; return; } ensure_capacity(n); // Initialize new elements with val for (size_t i = m_size; i < n; ++i) { m_data[i] = val; } m_size = n; } void push_back(uint64_t val) { if (m_size == m_capacity) { grow(m_size + 1); } m_data[m_size++] = val; } void pop_back() noexcept { --m_size; } template void assign(InputIt first, InputIt last) { size_t n = static_cast(std::distance(first, last)); ensure_capacity(n); // When InputIt is a pointer, use memcpy if constexpr (std::is_same_v || std::is_same_v) { std::memcpy(m_data, first, n * sizeof(uint64_t)); } else { uint64_t* dst = m_data; for (auto it = first; it != last; ++it) { *dst++ = *it; } } m_size = n; } iterator erase(iterator first, iterator last) { if (first == last) return first; size_t erased = static_cast(last - first); size_t tail = static_cast(end() - last); if (tail > 0) { std::memmove(first, last, tail * sizeof(uint64_t)); } m_size -= erased; return first; } // ---------------------------------------------------------------- // swap // ---------------------------------------------------------------- void swap(SboWords& other) noexcept { // Both inline if (is_inline() && other.is_inline()) { uint64_t tmp[INLINE_CAP]; size_t max_sz = std::max(m_size, other.m_size); std::memcpy(tmp, m_inline, max_sz * sizeof(uint64_t)); std::memcpy(m_inline, other.m_inline, max_sz * sizeof(uint64_t)); std::memcpy(other.m_inline, tmp, max_sz * sizeof(uint64_t)); std::swap(m_size, other.m_size); return; } // Both on the heap if (!is_inline() && !other.is_inline()) { std::swap(m_data, other.m_data); std::swap(m_size, other.m_size); std::swap(m_capacity, other.m_capacity); return; } // One inline, the other on the heap SboWords& inl = is_inline() ? *this : other; SboWords& heap = is_inline() ? other : *this; uint64_t tmp_inline[INLINE_CAP]; size_t tmp_size = inl.m_size; std::memcpy(tmp_inline, inl.m_inline, tmp_size * sizeof(uint64_t)); // Move the heap side's pointer into the inline side inl.m_data = heap.m_data; inl.m_size = heap.m_size; inl.m_capacity = heap.m_capacity; // Copy the inline side's data to the heap side heap.m_data = heap.m_inline; heap.m_size = tmp_size; heap.m_capacity = INLINE_CAP; std::memcpy(heap.m_inline, tmp_inline, tmp_size * sizeof(uint64_t)); } // ---------------------------------------------------------------- // Comparison // ---------------------------------------------------------------- bool operator==(const SboWords& other) const noexcept { if (m_size != other.m_size) return false; return std::memcmp(m_data, other.m_data, m_size * sizeof(uint64_t)) == 0; } bool operator!=(const SboWords& other) const noexcept { return !(*this == other); } private: uint64_t m_inline[INLINE_CAP]; // 72 bytes (9 × 8) uint64_t* m_data; // 8 bytes size_t m_size; // 8 bytes size_t m_capacity; // 8 bytes // Total: 96 bytes bool is_inline() const noexcept { return m_data == m_inline; } void ensure_capacity(size_t required) { if (required <= m_capacity) return; grow(required); } void grow(size_t min_cap) { // Growth rate: max(1.5x current capacity, min_cap) size_t new_cap = std::max(min_cap, m_capacity + m_capacity / 2); uint64_t* new_data = new uint64_t[new_cap]; std::memcpy(new_data, m_data, m_size * sizeof(uint64_t)); if (!is_inline()) delete[] m_data; m_data = new_data; m_capacity = new_cap; } }; } // namespace sangi #endif // SANGI_SBO_WORDS_HPP