// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // ScratchArena.hpp // 一時バッファの高速アリーナアロケータ // Karatsuba/Toom-Cook の再帰で malloc を回避するために使用 // // VirtualAlloc ベース: 仮想アドレス空間を大きく予約し、 // 物理メモリはオンデマンドでコミット。リアロケーションが発生しない // ためポインタが無効化されない。 #pragma once #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #else #include #include #endif namespace calx { class ScratchArena { uint64_t* base_ = nullptr; size_t offset_ = 0; // uint64_t 単位の使用済みオフセット size_t committed_ = 0; // uint64_t 単位のコミット済みサイズ size_t reserved_ = 0; // uint64_t 単位の予約サイズ static constexpr size_t RESERVE_WORDS = size_t(1) << 29; // 4GB (512M words * 8 bytes) static constexpr size_t COMMIT_GRANULARITY = size_t(1) << 16; // 512KB 単位でコミット public: ScratchArena() { reserved_ = RESERVE_WORDS; size_t bytes = reserved_ * sizeof(uint64_t); #ifdef _WIN32 base_ = static_cast( VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS)); #else base_ = static_cast( mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); if (base_ == MAP_FAILED) base_ = nullptr; #endif if (!base_) { // フォールバック: reserve が失敗した場合は小さめに再試行 reserved_ = size_t(1) << 24; // 128MB bytes = reserved_ * sizeof(uint64_t); #ifdef _WIN32 base_ = static_cast( VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS)); #else base_ = static_cast( mmap(nullptr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); if (base_ == MAP_FAILED) base_ = nullptr; #endif } } ~ScratchArena() { if (base_) { #ifdef _WIN32 VirtualFree(base_, 0, MEM_RELEASE); #else munmap(base_, reserved_ * sizeof(uint64_t)); #endif } } ScratchArena(const ScratchArena&) = delete; ScratchArena& operator=(const ScratchArena&) = delete; uint64_t* alloc_limbs(size_t n) { if (n == 0) return nullptr; // 8-word alignment (64 bytes, cache line) size_t aligned_n = (n + 7) & ~size_t(7); size_t new_end = offset_ + aligned_n; // 必要に応じて物理メモリをコミット (アドレスは変わらない) if (new_end > committed_) { size_t need = new_end - committed_; size_t commit = ((need + COMMIT_GRANULARITY - 1) / COMMIT_GRANULARITY) * COMMIT_GRANULARITY; if (committed_ + commit > reserved_) { commit = reserved_ - committed_; if (new_end > reserved_) { throw std::runtime_error("ScratchArena: exceeded reserved address space"); } } #ifdef _WIN32 void* p = VirtualAlloc(base_ + committed_, commit * sizeof(uint64_t), MEM_COMMIT, PAGE_READWRITE); if (!p) throw std::runtime_error("ScratchArena: VirtualAlloc commit failed"); #else int ret = mprotect(base_ + committed_, commit * sizeof(uint64_t), PROT_READ | PROT_WRITE); if (ret != 0) throw std::runtime_error("ScratchArena: mprotect failed"); #endif committed_ += commit; } uint64_t* p = base_ + offset_; offset_ = new_end; return p; } size_t mark() const { return offset_; } void rewind(size_t saved) { offset_ = saved; } }; // thread_local アリーナへのアクセス(static local で ODR 問題を回避) inline ScratchArena& getThreadArena() { static thread_local ScratchArena arena; return arena; } class ScratchScope { size_t saved_; public: ScratchScope() : saved_(getThreadArena().mark()) {} ~ScratchScope() { getThreadArena().rewind(saved_); } ScratchScope(const ScratchScope&) = delete; ScratchScope& operator=(const ScratchScope&) = delete; }; } // namespace calx