1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
// Copyright (C) 2015-2025 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#ifndef FOONATHAN_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
#define FOONATHAN_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref foonathan::memory::temporary_allocator and related functions.
#include "config.hpp"
#include "memory_stack.hpp"
#if FOONATHAN_MEMORY_TEMPORARY_STACK_MODE >= 2
#include <atomic>
#endif
namespace foonathan
{
namespace memory
{
class temporary_allocator;
class temporary_stack;
namespace detail
{
class temporary_block_allocator
{
public:
explicit temporary_block_allocator(std::size_t block_size) noexcept;
memory_block allocate_block();
void deallocate_block(memory_block block);
std::size_t next_block_size() const noexcept
{
return block_size_;
}
using growth_tracker = void (*)(std::size_t size);
growth_tracker set_growth_tracker(growth_tracker t) noexcept;
growth_tracker get_growth_tracker() noexcept;
private:
growth_tracker tracker_;
std::size_t block_size_;
};
using temporary_stack_impl = memory_stack<temporary_block_allocator>;
class temporary_stack_list;
#if FOONATHAN_MEMORY_TEMPORARY_STACK_MODE >= 2
class temporary_stack_list_node
{
public:
// doesn't add into list
temporary_stack_list_node() noexcept : in_use_(true) {}
temporary_stack_list_node(int) noexcept;
~temporary_stack_list_node() noexcept {}
private:
temporary_stack_list_node* next_ = nullptr;
std::atomic<bool> in_use_;
friend temporary_stack_list;
};
static class temporary_allocator_dtor_t
{
public:
temporary_allocator_dtor_t() noexcept;
~temporary_allocator_dtor_t() noexcept;
} temporary_allocator_dtor;
#else
class temporary_stack_list_node
{
protected:
temporary_stack_list_node() noexcept {}
temporary_stack_list_node(int) noexcept {}
~temporary_stack_list_node() noexcept {}
};
#endif
} // namespace detail
/// A wrapper around the \ref memory_stack that is used by the \ref temporary_allocator.
/// There should be at least one per-thread.
/// \ingroup allocator
class temporary_stack : FOONATHAN_EBO(detail::temporary_stack_list_node)
{
public:
/// The type of the handler called when the internal \ref memory_stack grows.
/// It gets the size of the new block that will be allocated.
/// \requiredbe The handler shall log the growth, throw an exception or aborts the program.
/// If this function does not return, the growth is prevented but the allocator unusable until memory is freed.
/// \defaultbe The default handler does nothing.
using growth_tracker = detail::temporary_block_allocator::growth_tracker;
/// \effects Sets \c h as the new \ref growth_tracker.
/// A \c nullptr sets the default \ref growth_tracker.
/// Each thread has its own, separate tracker.
/// \returns The previous \ref growth_tracker. This is never \c nullptr.
growth_tracker set_growth_tracker(growth_tracker t) noexcept
{
return stack_.get_allocator().set_growth_tracker(t);
}
/// \returns The current \ref growth_tracker. This is never \c nullptr.
growth_tracker get_growth_tracker() noexcept
{
return stack_.get_allocator().get_growth_tracker();
}
/// \effects Creates it with a given initial size of the stack.
/// It can grow if needed, although that is expensive.
/// \requires `initial_size` must be greater than `0`.
explicit temporary_stack(std::size_t initial_size) : stack_(initial_size), top_(nullptr)
{
}
/// \returns `next_capacity()` of the internal `memory_stack`.
std::size_t next_capacity() const noexcept
{
return stack_.next_capacity();
}
private:
temporary_stack(int i, std::size_t initial_size)
: detail::temporary_stack_list_node(i), stack_(initial_size), top_(nullptr)
{
}
using marker = detail::temporary_stack_impl::marker;
marker top() const noexcept
{
return stack_.top();
}
void unwind(marker m) noexcept
{
stack_.unwind(m);
}
detail::temporary_stack_impl stack_;
temporary_allocator* top_;
#if !defined(DOXYGEN)
friend temporary_allocator;
friend memory_stack_raii_unwind<temporary_stack>;
friend detail::temporary_stack_list;
#endif
};
/// Manually takes care of the lifetime of the per-thread \ref temporary_stack.
/// The constructor will create it, if not already done, and the destructor will destroy it, if not already done.
/// \note If there are multiple objects in a thread,
/// this will lead to unnecessary construction and destruction of the stack.
/// It is thus adviced to create one object on the top-level function of the thread, e.g. in `main()`.
/// \note If `FOONATHAN_MEMORY_TEMPORARY_STACK_MODE == 2`, it is not necessary to use this class,
/// the nifty counter will clean everything upon program termination.
/// But it can still be used as an optimization if you have a thread that is terminated long before program exit.
/// The automatic clean up will only occur much later.
/// \note If `FOONATHAN_MEMORY_TEMPORARY_STACK_MODE == 0`, the use of this class has no effect,
/// because the per-thread stack is disabled.
/// \relatesalso temporary_stack
class temporary_stack_initializer
{
public:
static constexpr std::size_t default_stack_size = 4096u;
static const struct defer_create_t
{
defer_create_t() noexcept {}
} defer_create;
/// \effects Does not create the per-thread stack.
/// It will be created by the first call to \ref get_temporary_stack() in the current thread.
/// \note If `FOONATHAN_MEMORY_TEMPORARY_STACK_MODE == 0`, this function has no effect.
temporary_stack_initializer(defer_create_t) noexcept {}
/// \effects Creates the per-thread stack with the given default size if it wasn't already created.
/// \requires `initial_size` must not be `0` if `FOONATHAN_MEMORY_TEMPORARY_STACK_MODE != 0`.
/// \note If `FOONATHAN_MEMORY_TEMPORARY_STACK_MODE == 0`, this function will issue a warning in debug mode.
/// This can be disabled by passing `0` as the initial size.
temporary_stack_initializer(std::size_t initial_size = default_stack_size);
/// \effects Destroys the per-thread stack if it isn't already destroyed.
~temporary_stack_initializer() noexcept;
temporary_stack_initializer(temporary_stack_initializer&&) = delete;
temporary_stack_initializer& operator=(temporary_stack_initializer&&) = delete;
};
/// \effects Creates the per-thread \ref temporary_stack with the given initial size,
/// if it wasn't already created.
/// \returns The per-thread \ref temporary_stack.
/// \requires There must be a per-thread temporary stack (\ref FOONATHAN_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
/// \note If \ref FOONATHAN_MEMORY_TEMPORARY_STACK_MODE is equal to `1`,
/// this function can create the temporary stack.
/// But if there is no \ref temporary_stack_initializer, it won't be destroyed.
/// \relatesalso temporary_stack
temporary_stack& get_temporary_stack(
std::size_t initial_size = temporary_stack_initializer::default_stack_size);
/// A stateful \concept{concept_rawallocator,RawAllocator} that handles temporary allocations.
/// It works similar to \c alloca() but uses a seperate \ref memory_stack for the allocations,
/// instead of the actual program stack.
/// This avoids the stack overflow error and is portable,
/// with a similar speed.
/// All allocations done in the scope of the allocator object are automatically freed when the object is destroyed.
/// \ingroup allocator
class temporary_allocator
{
public:
/// \effects Creates it by using the \ref get_temporary_stack() to get the temporary stack.
/// \requires There must be a per-thread temporary stack (\ref FOONATHAN_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
temporary_allocator();
/// \effects Creates it by giving it the \ref temporary_stack it uses for allocation.
explicit temporary_allocator(temporary_stack& stack);
~temporary_allocator() noexcept;
temporary_allocator(temporary_allocator&&) = delete;
temporary_allocator& operator=(temporary_allocator&&) = delete;
/// \effects Allocates memory from the internal \ref memory_stack by forwarding to it.
/// \returns The result of \ref memory_stack::allocate().
/// \requires `is_active()` must return `true`.
void* allocate(std::size_t size, std::size_t alignment);
/// \returns Whether or not the allocator object is active.
/// \note The active allocator object is the last object created for one stack.
/// Moving changes the active allocator.
bool is_active() const noexcept;
/// \effects Instructs it to release unnecessary memory after automatic unwinding occurs.
/// This will effectively forward to \ref memory_stack::shrink_to_fit() of the internal stack.
/// \note Like the use of the \ref temporary_stack_initializer this can be used as an optimization,
/// to tell when the thread's \ref temporary_stack isn't needed anymore and can be destroyed.
/// \note It doesn't call shrink to fit immediately, only in the destructor!
void shrink_to_fit() noexcept;
/// \returns The internal stack the temporary allocator is using.
/// \requires `is_active()` must return `true`.
temporary_stack& get_stack() const noexcept
{
return unwind_.get_stack();
}
private:
memory_stack_raii_unwind<temporary_stack> unwind_;
temporary_allocator* prev_;
bool shrink_to_fit_;
};
template <class Allocator>
class allocator_traits;
/// Specialization of the \ref allocator_traits for \ref temporary_allocator classes.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref temporary_allocator::allocate() and this \c allocate_node().
/// \ingroup allocator
template <>
class allocator_traits<temporary_allocator>
{
public:
using allocator_type = temporary_allocator;
using is_stateful = std::true_type;
/// \returns The result of \ref temporary_allocator::allocate().
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
detail::check_allocation_size<bad_node_size>(size,
[&] { return max_node_size(state); },
{FOONATHAN_MEMORY_LOG_PREFIX
"::temporary_allocator",
&state});
return state.allocate(size, alignment);
}
/// \returns The result of \ref temporary_allocator::allocate().
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
return allocate_node(state, count * size, alignment);
}
/// @{
/// \effects Does nothing besides bookmarking for leak checking, if that is enabled.
/// Actual deallocation will be done automatically if the allocator object goes out of scope.
static void deallocate_node(const allocator_type&, void*, std::size_t,
std::size_t) noexcept
{
}
static void deallocate_array(const allocator_type&, void*, std::size_t, std::size_t,
std::size_t) noexcept
{
}
/// @}
/// @{
/// \returns The maximum size which is \ref memory_stack::next_capacity() of the internal stack.
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.get_stack().next_capacity();
}
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return max_node_size(state);
}
/// @}
/// \returns The maximum possible value since there is no alignment restriction
/// (except indirectly through \ref memory_stack::next_capacity()).
static std::size_t max_alignment(const allocator_type&) noexcept
{
return std::size_t(-1);
}
};
} // namespace memory
} // namespace foonathan
#endif // FOONATHAN_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
|