blt-gp/include/blt/gp/stack.h

338 lines
13 KiB
C
Raw Permalink Normal View History

2024-06-06 02:25:42 -04:00
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BLT_GP_STACK_H
#define BLT_GP_STACK_H
#include <blt/std/types.h>
2024-08-22 02:10:55 -04:00
#include <blt/std/bump_allocator.h>
2024-07-12 21:58:05 -04:00
#include <blt/std/assert.h>
2024-06-26 20:24:58 -04:00
#include <blt/std/logging.h>
2024-07-14 20:38:08 -04:00
#include <blt/std/allocator.h>
2024-08-16 21:27:33 -04:00
#include <blt/std/ranges.h>
2024-09-06 00:21:55 -04:00
#include <blt/meta/meta.h>
2024-06-30 03:20:56 -04:00
#include <blt/gp/fwdecl.h>
#include <blt/gp/stats.h>
2024-06-06 02:25:42 -04:00
#include <utility>
#include <stdexcept>
#include <cstdlib>
#include <memory>
2024-06-23 14:13:50 -04:00
#include <type_traits>
2024-06-30 03:20:56 -04:00
#include <cstring>
2024-07-16 14:13:23 -04:00
#include <iostream>
2024-06-06 02:25:42 -04:00
namespace blt::gp
{
2024-08-11 13:33:24 -04:00
namespace detail
{
BLT_META_MAKE_FUNCTION_CHECK(drop);
}
2024-06-06 02:25:42 -04:00
class stack_allocator
2024-08-16 21:27:33 -04:00
{
2024-08-23 22:40:13 -04:00
constexpr static blt::size_t PAGE_SIZE = 0x100;
2024-08-16 21:27:33 -04:00
constexpr static blt::size_t MAX_ALIGNMENT = 8;
template<typename T>
using NO_REF_T = std::remove_cv_t<std::remove_reference_t<T>>;
2024-08-19 12:13:07 -04:00
using Allocator = aligned_allocator;
2024-08-16 21:27:33 -04:00
public:
2024-08-19 12:13:07 -04:00
static Allocator& get_allocator();
2024-08-20 21:58:29 -04:00
2024-08-17 00:06:28 -04:00
struct size_data_t
{
blt::size_t total_size_bytes = 0;
blt::size_t total_used_bytes = 0;
blt::size_t total_remaining_bytes = 0;
friend std::ostream& operator<<(std::ostream& stream, const size_data_t& data)
{
stream << "[";
stream << data.total_used_bytes << " / " << data.total_size_bytes;
stream << " ("
<< (data.total_size_bytes != 0 ? (static_cast<double>(data.total_used_bytes) / static_cast<double>(data.total_size_bytes) *
100) : 0) << "%); space left: " << data.total_remaining_bytes << "]";
2024-08-17 00:06:28 -04:00
return stream;
}
};
template<typename T>
static inline constexpr blt::size_t aligned_size() noexcept
{
return aligned_size(sizeof(NO_REF_T<T>));
}
static inline constexpr blt::size_t aligned_size(blt::size_t size) noexcept
{
return (size + (MAX_ALIGNMENT - 1)) & ~(MAX_ALIGNMENT - 1);
}
2024-08-16 21:27:33 -04:00
stack_allocator() = default;
stack_allocator(const stack_allocator& copy)
{
2024-08-17 00:06:28 -04:00
if (copy.data_ == nullptr || copy.bytes_stored == 0)
return;
2024-08-16 21:27:33 -04:00
expand(copy.size_);
2024-08-17 00:06:28 -04:00
std::memcpy(data_, copy.data_, copy.bytes_stored);
bytes_stored = copy.bytes_stored;
2024-08-16 21:27:33 -04:00
}
stack_allocator(stack_allocator&& move) noexcept:
2024-08-22 02:45:10 -04:00
data_(std::exchange(move.data_, nullptr)), bytes_stored(std::exchange(move.bytes_stored, 0)), size_(std::exchange(move.size_, 0))
2024-08-16 21:27:33 -04:00
{}
stack_allocator& operator=(const stack_allocator& copy) = delete;
stack_allocator& operator=(stack_allocator&& move) noexcept
{
data_ = std::exchange(move.data_, data_);
size_ = std::exchange(move.size_, size_);
2024-08-17 00:06:28 -04:00
bytes_stored = std::exchange(move.bytes_stored, bytes_stored);
2024-08-16 21:27:33 -04:00
return *this;
}
~stack_allocator()
{
2024-08-19 12:13:07 -04:00
get_allocator().deallocate(data_, size_);
2024-08-16 21:27:33 -04:00
}
void insert(const stack_allocator& stack)
{
if (stack.empty())
2024-08-18 01:28:23 -04:00
return;
2024-09-03 17:51:54 -04:00
if (stack.bytes_stored + bytes_stored > size_)
2024-08-19 22:05:42 -04:00
expand(stack.bytes_stored + size_);
2024-08-17 00:06:28 -04:00
std::memcpy(data_ + bytes_stored, stack.data_, stack.bytes_stored);
bytes_stored += stack.bytes_stored;
2024-08-16 21:27:33 -04:00
}
void copy_from(const stack_allocator& stack, blt::size_t bytes)
{
if (bytes == 0)
2024-08-18 01:28:23 -04:00
return;
2024-09-03 17:51:54 -04:00
if (bytes + bytes_stored > size_)
2024-08-19 22:05:42 -04:00
expand(bytes + size_);
2024-08-17 00:06:28 -04:00
std::memcpy(data_ + bytes_stored, stack.data_ + (stack.bytes_stored - bytes), bytes);
bytes_stored += bytes;
2024-08-16 21:27:33 -04:00
}
void copy_from(blt::u8* data, blt::size_t bytes)
{
2024-08-18 01:28:23 -04:00
if (bytes == 0 || data == nullptr)
return;
2024-09-03 17:51:54 -04:00
if (bytes + bytes_stored > size_)
2024-08-19 22:05:42 -04:00
expand(bytes + size_);
2024-08-17 00:06:28 -04:00
std::memcpy(data_ + bytes_stored, data, bytes);
bytes_stored += bytes;
2024-08-16 21:27:33 -04:00
}
void copy_to(blt::u8* data, blt::size_t bytes)
{
2024-08-18 01:28:23 -04:00
if (bytes == 0 || data == nullptr)
return;
2024-08-17 00:06:28 -04:00
std::memcpy(data, data_ + (bytes_stored - bytes), bytes);
2024-08-16 21:27:33 -04:00
}
template<typename T, typename NO_REF = NO_REF_T<T>>
void push(const T& t)
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
auto ptr = allocate_bytes_for_size(sizeof(NO_REF));
std::memcpy(ptr, &t, sizeof(NO_REF));
}
template<typename T, typename NO_REF = NO_REF_T<T>>
T pop()
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
constexpr auto size = aligned_size(sizeof(NO_REF));
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < size)
BLT_ABORT("Not enough bytes left to pop!");
#endif
2024-08-17 00:06:28 -04:00
bytes_stored -= size;
2024-08-17 01:59:13 -04:00
return *reinterpret_cast<T*>(data_ + bytes_stored);
2024-08-16 21:27:33 -04:00
}
2024-08-20 21:58:29 -04:00
[[nodiscard]] blt::u8* from(blt::size_t bytes) const
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to reference " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
#endif
return data_ + (bytes_stored - bytes);
}
2024-08-16 21:27:33 -04:00
template<typename T, typename NO_REF = NO_REF_T<T>>
T& from(blt::size_t bytes)
{
static_assert(std::is_trivially_copyable_v<NO_REF> && "Type must be bitwise copyable!");
static_assert(alignof(NO_REF) <= MAX_ALIGNMENT && "Type alignment must not be greater than the max alignment!");
2024-08-20 21:58:29 -04:00
return *reinterpret_cast<NO_REF*>(from(aligned_size(sizeof(NO_REF)) + bytes));
2024-08-16 21:27:33 -04:00
}
2024-08-17 00:06:28 -04:00
void pop_bytes(blt::size_t bytes)
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to pop " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
#endif
2024-08-17 00:06:28 -04:00
bytes_stored -= bytes;
}
2024-08-16 21:27:33 -04:00
2024-08-17 00:06:28 -04:00
void transfer_bytes(stack_allocator& to, blt::size_t bytes)
{
#if BLT_DEBUG_LEVEL > 0
if (bytes_stored < bytes)
BLT_ABORT(("Not enough bytes in stack to transfer " + std::to_string(bytes) + " bytes requested but " + std::to_string(bytes) +
" bytes stored!").c_str());
#endif
auto alg = aligned_size(bytes);
to.copy_from(*this, alg);
pop_bytes(alg);
2024-08-17 00:06:28 -04:00
}
template<typename... Args>
2024-08-21 20:40:42 -04:00
void call_destructors()
2024-08-17 00:06:28 -04:00
{
if constexpr (sizeof...(Args) > 0)
{
blt::size_t offset = (stack_allocator::aligned_size(sizeof(NO_REF_T<Args>)) + ...) -
stack_allocator::aligned_size(sizeof(NO_REF_T<typename blt::meta::arg_helper<Args...>::First>));
2024-08-21 20:40:42 -04:00
((call_drop<Args>(offset), offset -= stack_allocator::aligned_size(sizeof(NO_REF_T<Args>))), ...);
2024-08-17 00:06:28 -04:00
}
}
2024-08-16 21:27:33 -04:00
[[nodiscard]] bool empty() const noexcept
{
2024-08-17 00:06:28 -04:00
return bytes_stored == 0;
2024-08-16 21:27:33 -04:00
}
2024-08-17 00:06:28 -04:00
[[nodiscard]] blt::ptrdiff_t remaining_bytes_in_block() const noexcept
2024-08-16 21:27:33 -04:00
{
2024-08-17 00:06:28 -04:00
return static_cast<blt::ptrdiff_t>(size_ - bytes_stored);
2024-08-16 21:27:33 -04:00
}
2024-08-17 00:06:28 -04:00
[[nodiscard]] blt::ptrdiff_t bytes_in_head() const noexcept
2024-08-16 21:27:33 -04:00
{
2024-08-17 00:06:28 -04:00
return static_cast<blt::ptrdiff_t>(bytes_stored);
}
[[nodiscard]] size_data_t size() const noexcept
{
size_data_t data;
data.total_used_bytes = bytes_stored;
data.total_size_bytes = size_;
data.total_remaining_bytes = remaining_bytes_in_block();
return data;
2024-08-16 21:27:33 -04:00
}
2024-08-20 21:58:29 -04:00
void reserve(blt::size_t bytes)
{
if (bytes > size_)
2024-08-22 02:10:55 -04:00
expand_raw(bytes);
}
[[nodiscard]] blt::size_t stored() const
{
return bytes_stored;
}
[[nodiscard]] blt::size_t internal_storage_size() const
{
return size_;
}
void reset()
{
bytes_stored = 0;
2024-08-20 21:58:29 -04:00
}
2024-08-16 21:27:33 -04:00
private:
void expand(blt::size_t bytes)
{
2024-08-23 22:40:13 -04:00
//bytes = to_nearest_page_size(bytes);
2024-08-22 02:10:55 -04:00
expand_raw(bytes);
}
void expand_raw(blt::size_t bytes)
{
2024-08-19 12:13:07 -04:00
auto new_data = static_cast<blt::u8*>(get_allocator().allocate(bytes));
2024-08-17 00:06:28 -04:00
if (bytes_stored > 0)
std::memcpy(new_data, data_, bytes_stored);
2024-08-19 12:13:07 -04:00
get_allocator().deallocate(data_, size_);
2024-08-16 21:27:33 -04:00
data_ = new_data;
size_ = bytes;
}
static size_t to_nearest_page_size(blt::size_t bytes) noexcept
{
constexpr static blt::size_t MASK = ~(PAGE_SIZE - 1);
return (bytes & MASK) + PAGE_SIZE;
}
void* get_aligned_pointer(blt::size_t bytes) noexcept
{
if (data_ == nullptr)
return nullptr;
blt::size_t remaining_bytes = remaining_bytes_in_block();
2024-08-17 00:06:28 -04:00
auto* pointer = static_cast<void*>(data_ + bytes_stored);
2024-08-16 21:27:33 -04:00
return std::align(MAX_ALIGNMENT, bytes, pointer, remaining_bytes);
}
void* allocate_bytes_for_size(blt::size_t bytes)
{
2024-08-19 22:05:42 -04:00
auto used_bytes = aligned_size(bytes);
auto aligned_ptr = get_aligned_pointer(used_bytes);
2024-08-16 21:27:33 -04:00
if (aligned_ptr == nullptr)
{
2024-08-19 22:05:42 -04:00
expand(size_ + used_bytes);
aligned_ptr = get_aligned_pointer(used_bytes);
2024-08-16 21:27:33 -04:00
}
if (aligned_ptr == nullptr)
throw std::bad_alloc();
2024-08-17 00:06:28 -04:00
bytes_stored += used_bytes;
2024-08-16 21:27:33 -04:00
return aligned_ptr;
}
2024-08-17 00:06:28 -04:00
template<typename T>
2024-08-21 20:40:42 -04:00
inline void call_drop(blt::size_t offset)
2024-08-17 00:06:28 -04:00
{
if constexpr (detail::has_func_drop_v<T>)
{
from<NO_REF_T<T >>(offset).drop();
2024-08-17 00:06:28 -04:00
}
}
2024-08-16 21:27:33 -04:00
blt::u8* data_ = nullptr;
// place in the data_ array which has a free spot.
2024-08-17 00:06:28 -04:00
blt::size_t bytes_stored = 0;
2024-08-16 21:27:33 -04:00
blt::size_t size_ = 0;
};
2024-08-19 12:13:07 -04:00
2024-06-06 02:25:42 -04:00
}
#endif //BLT_GP_STACK_H