diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a3c616..c212a1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ set(PUBLIC_HEADERS include/arena.h include/block.h include/heap.h + include/region.h include/util.h ) @@ -18,6 +19,7 @@ set(SRCS src/block.cpp src/heap.cpp src/mimir.cpp + src/region.cpp src/util.cpp ) diff --git a/include/arena.h b/include/arena.h index 159d62b..9bba7a1 100644 --- a/include/arena.h +++ b/include/arena.h @@ -33,6 +33,8 @@ #include +#include "region.h" + namespace mimir { class Arena { @@ -53,14 +55,13 @@ public: // Reset the arena, potentially also releasing committed pages void reset(); private: - std::byte* m_data; + Region m_region; size_t m_minSize; - size_t m_maxSize; - size_t m_committedSize; size_t m_usedSize; static const size_t kWatermarkLen = 8; size_t m_watermark[kWatermarkLen]; + }; // class Arena } // namespace mimir diff --git a/include/mimir.h b/include/mimir.h index c740495..4b93f43 100644 --- a/include/mimir.h +++ b/include/mimir.h @@ -34,6 +34,7 @@ #include "arena.h" #include "block.h" #include "heap.h" +#include "region.h" #include "util.h" namespace mimir { diff --git a/include/region.h b/include/region.h new file mode 100644 index 0000000..8ea5df0 --- /dev/null +++ b/include/region.h @@ -0,0 +1,55 @@ +// +// region.h +// Kraken Engine / Mimir +// +// Copyright 2026 Kearwood Gilbert. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The views and conclusions contained in the software and documentation are those of the +// authors and should not be interpreted as representing official policies, either expressed +// or implied, of Kearwood Gilbert. +// + +#pragma once + +#include + +namespace mimir { +class Region +{ +public: + Region(); + ~Region(); + + bool init(size_t maxSize = 1ULL << 32); + bool resize(size_t size); + size_t getSize() const; + size_t getMaxSize() const; + std::byte* getAddress() const; + +private: + std::byte* m_data; + size_t m_committedSize; + size_t m_maxSize; +}; + +} // namespace mimir diff --git a/src/arena.cpp b/src/arena.cpp index e3bfa5c..d6daa12 100644 --- a/src/arena.cpp +++ b/src/arena.cpp @@ -40,10 +40,7 @@ namespace mimir { Arena::Arena() : m_minSize(0) - , m_maxSize(0) - , m_data(nullptr) , m_usedSize(0) - , m_committedSize(0) { for (size_t i = 0; i < kWatermarkLen; i++) { m_watermark[i] = 0; @@ -52,73 +49,31 @@ Arena::Arena() Arena::~Arena() { - if (m_data) { -#if defined(_WIN32) || defined(_WIN64) - if (!VirtualFree((void*)m_data, 0, MEM_RELEASE)) { - ReportWindowsLastError("VirtualAlloc"); - } -#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) - munmap(m_data, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize)); -#else - static_assert(false, "Not Implemented"); -#endif - } } bool Arena::init(size_t minSize, size_t maxSize) { assert(maxSize >= minSize); - assert(m_data == nullptr); assert(m_usedSize == 0); - assert(m_committedSize == 0); m_minSize = KRAKEN_MEM_ROUND_UP_PAGE(minSize); - m_maxSize = KRAKEN_MEM_ROUND_UP_PAGE(maxSize); - -#if defined(_WIN32) || defined(_WIN64) - m_data = (std::byte*)VirtualAlloc(NULL, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize), MEM_RESERVE, PAGE_NOACCESS); - if (m_data == nullptr) { - ReportWindowsLastError("VirtualAlloc"); - return false; - } -#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) - m_data = (std::byte*)mmap(NULL, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (m_data == MAP_FAILED) { - return false; - } -#else - static_assert(false, "Not Implemented"); -#endif - return true; + return m_region.init(maxSize); } std::byte* Arena::alloc(size_t size) { size_t neededSize = m_usedSize + size; - if (neededSize > m_committedSize) + if (neededSize > m_region.getSize()) { + // Increase the size exponentially when we run out size_t newSize = std::bit_ceil(neededSize) << 1; if (newSize < m_minSize) { newSize = m_minSize; } - newSize = KRAKEN_MEM_ROUND_UP_PAGE(newSize); + m_region.resize(newSize); + } -#if defined(_WIN32) || defined(_WIN64) - if(VirtualAlloc(m_data + m_committedSize, newSize - m_committedSize, MEM_COMMIT, PAGE_READWRITE) == nullptr) { - ReportWindowsLastError("VirtualAlloc"); - return nullptr; - } -#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) - if (mprotect(m_data + m_committedSize, newSize - m_committedSize, PROT_READ | PROT_WRITE) != 0) { - return nullptr; - } -#else - static_assert(false, "Not Implemented"); -#endif - m_committedSize = newSize; - } // if (neededSize > m_committedSize) - - std::byte* ret = m_data + m_usedSize; + std::byte* ret = m_region.getAddress() + m_usedSize; m_usedSize = neededSize; return ret; } @@ -131,7 +86,7 @@ std::byte* Arena::allocA16(size_t size) return nullptr; } - return m_data + nextByte; + return m_region.getAddress() + nextByte; } std::byte* Arena::allocA64(size_t size) @@ -142,7 +97,7 @@ std::byte* Arena::allocA64(size_t size) return nullptr; } - return m_data + nextByte; + return m_region.getAddress() + nextByte; } void Arena::reset() @@ -161,28 +116,8 @@ void Arena::reset() targetSize = m_minSize; } size_t thresholdSize = targetSize << 1; // The threshold for shrinking is greater than the target to implement hysteresis - if (m_committedSize > thresholdSize) { - size_t newSize = KRAKEN_MEM_ROUND_UP_PAGE(targetSize); - - std::byte* rangeStart = m_data + newSize; - size_t rangeLen = m_committedSize - newSize; -#if defined(_WIN32) || defined(_WIN64) - if (!VirtualAlloc(rangeStart, rangeLen, MEM_RESET, PAGE_NOACCESS)) { - ReportWindowsLastError("VirtualAlloc"); - } else { - m_committedSize = newSize; - } -#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) - if (madvise(rangeStart, rangeLen, MADV_DONTNEED) != 0) { - // TODO - Log Error or debug build assert - } - if (mprotect(rangeStart, rangeLen, PROT_NONE) != 0) { - // TODO - Log Error or debug build assert - } -#else - static_assert(false, "Not Implemented"); -#endif - m_committedSize = newSize; + if (m_region.getSize() > thresholdSize) { + m_region.resize(targetSize); } m_usedSize = 0; diff --git a/src/region.cpp b/src/region.cpp new file mode 100644 index 0000000..5fa2200 --- /dev/null +++ b/src/region.cpp @@ -0,0 +1,153 @@ +// +// region.cpp +// Kraken Engine +// +// Copyright 2026 Kearwood Gilbert. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED +// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// The views and conclusions contained in the software and documentation are those of the +// authors and should not be interpreted as representing official policies, either expressed +// or implied, of Kearwood Gilbert. +// + +#include "../include/mimir.h" +#include "mimir_impl.h" + +#include +#include +#include + +namespace mimir { + +Region::Region() + : m_data(nullptr) + , m_committedSize(0) + , m_maxSize(0) +{ + +} + +Region::~Region() +{ + if (m_data) { +#if defined(_WIN32) || defined(_WIN64) + if (!VirtualFree((void*)m_data, 0, MEM_RELEASE)) { + ReportWindowsLastError("VirtualAlloc"); + } +#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) + munmap(m_data, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize)); +#else + static_assert(false, "Not Implemented"); +#endif + } +} + +bool Region::init(size_t maxSize) +{ + assert(m_data == nullptr); + assert(m_committedSize == 0); + + m_maxSize = KRAKEN_MEM_ROUND_UP_PAGE(maxSize); + +#if defined(_WIN32) || defined(_WIN64) + m_data = (std::byte*)VirtualAlloc(NULL, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize), MEM_RESERVE, PAGE_NOACCESS); + if (m_data == nullptr) { + ReportWindowsLastError("VirtualAlloc"); + return false; + } +#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) + m_data = (std::byte*)mmap(NULL, KRAKEN_MEM_ROUND_UP_PAGE(m_maxSize), PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (m_data == MAP_FAILED) { + return false; + } +#else + static_assert(false, "Not Implemented"); +#endif + return true; +} + +bool Region::resize(size_t size) +{ + size_t newSize = KRAKEN_MEM_ROUND_UP_PAGE(size); + + if (newSize == m_committedSize) { + return true; + } + + if (newSize > m_committedSize) { + // ---- Growing ---- +#if defined(_WIN32) || defined(_WIN64) + if (VirtualAlloc(m_data + m_committedSize, newSize - m_committedSize, MEM_COMMIT, PAGE_READWRITE) == nullptr) { + ReportWindowsLastError("VirtualAlloc"); + return false; + } +#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) + if (mprotect(m_data + m_committedSize, newSize - m_committedSize, PROT_READ | PROT_WRITE) != 0) { + return false; + } +#else + static_assert(false, "Not Implemented"); +#endif + m_committedSize = newSize; + return true; + } // if (newSize > m_committedSize) + + // ---- Shrinking ---- + std::byte* rangeStart = m_data + newSize; + size_t rangeLen = m_committedSize - newSize; +#if defined(_WIN32) || defined(_WIN64) + if (!VirtualAlloc(rangeStart, rangeLen, MEM_RESET, PAGE_NOACCESS)) { + ReportWindowsLastError("VirtualAlloc"); + } else { + m_committedSize = newSize; + } +#elif defined(__unix__) || defined(__APPLE__) || defined(ANDROID) + if (madvise(rangeStart, rangeLen, MADV_DONTNEED) != 0) { + // TODO - Log Error or debug build assert + } + if (mprotect(rangeStart, rangeLen, PROT_NONE) != 0) { + // TODO - Log Error or debug build assert + } +#else + static_assert(false, "Not Implemented"); +#endif + m_committedSize = newSize; + + return true; +} + +size_t Region::getSize() const +{ + return m_committedSize; +} + +size_t Region::getMaxSize() const +{ + return m_maxSize; +} + +std::byte* Region::getAddress() const +{ + return m_data; +} + +};