forked from OctaForge/libostd
document context_stack.hh
This commit is contained in:
parent
f2a78ad589
commit
6d0c0a91f4
|
@ -1,7 +1,18 @@
|
||||||
/* Stack allocation for coroutine contexts.
|
/** @addtogroup Concurrency
|
||||||
* API more or less compatible with the Boost.Context library.
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file context_stack.hh
|
||||||
*
|
*
|
||||||
* This file is part of OctaSTD. See COPYING.md for futher information.
|
* @brief Stack allocation for coroutines and other context-using types.
|
||||||
|
*
|
||||||
|
* Contexts, which are used by coroutines, generators and tasks in certain
|
||||||
|
* concurrency scheduler types, need stacks to work. This file provides
|
||||||
|
* several types of stack allocators to suit their needs.
|
||||||
|
*
|
||||||
|
* This API is mostly inspired by Boost.Context stack API.
|
||||||
|
*
|
||||||
|
* @copyright See COPYING.md in the project tree for further information.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef OSTD_CONTEXT_STACK_HH
|
#ifndef OSTD_CONTEXT_STACK_HH
|
||||||
|
@ -23,19 +34,78 @@
|
||||||
|
|
||||||
namespace ostd {
|
namespace ostd {
|
||||||
|
|
||||||
|
/** @addtogroup Concurrency
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @brief An allocated stack.
|
||||||
|
*
|
||||||
|
* This represents a stack allocated by a stack allocator. It doesn't
|
||||||
|
* release itself so it has to be deallocated using the same stack allocator.
|
||||||
|
*
|
||||||
|
* On architectures where the stack grows down, the stack pointer is actually
|
||||||
|
* `allocated_memory + stack_size`. This is the majority of architectures and
|
||||||
|
* all architectures this module supports.
|
||||||
|
*/
|
||||||
struct stack_context {
|
struct stack_context {
|
||||||
void *ptr = nullptr;
|
void *ptr = nullptr; ///< The stack pointer.
|
||||||
size_t size = 0;
|
size_t size = 0; ///< The stack size.
|
||||||
#ifdef OSTD_USE_VALGRIND
|
#ifdef OSTD_USE_VALGRIND
|
||||||
int valgrind_id = 0;
|
int valgrind_id = 0;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief The stack traits to check properties of stacks.
|
||||||
|
*
|
||||||
|
* This structure allows stack allocators (and potentially others) to check
|
||||||
|
* various properties of stacks on your system, mainly sizing-wise.
|
||||||
|
*/
|
||||||
struct OSTD_EXPORT stack_traits {
|
struct OSTD_EXPORT stack_traits {
|
||||||
|
/** @brief Checks if the stack is unbounded.
|
||||||
|
*
|
||||||
|
* This checks whether the stack is limited in size. If it's not, it
|
||||||
|
* means the stack you can allocate can take up as much memory as you
|
||||||
|
* want. Otherwise it means that there is some limit to it, which you
|
||||||
|
* can query with maximum_size().
|
||||||
|
*/
|
||||||
static bool is_unbounded() noexcept;
|
static bool is_unbounded() noexcept;
|
||||||
|
|
||||||
|
/** @brief Gets the page size on your system.
|
||||||
|
*
|
||||||
|
* The returned size is in bytes. Typically this is 4 KiB, but can be
|
||||||
|
* some other value too. Stack sizes are typically a multiple of page
|
||||||
|
* size.
|
||||||
|
*/
|
||||||
static size_t page_size() noexcept;
|
static size_t page_size() noexcept;
|
||||||
|
|
||||||
|
/** @brief Gets the minimum size a stack can have.
|
||||||
|
*
|
||||||
|
* You need at least this for coroutine stacks. On POSIX systems, this
|
||||||
|
* typically defaults to `SIGSTKSZ`. On Windows, we specify 8 KiB as
|
||||||
|
* the minimum size. The returned size is in bytes.
|
||||||
|
*
|
||||||
|
* @see maximum_size(), default_size()
|
||||||
|
*/
|
||||||
static size_t minimum_size() noexcept;
|
static size_t minimum_size() noexcept;
|
||||||
|
|
||||||
|
/** @brief Gets the maximum size a stack can have.
|
||||||
|
*
|
||||||
|
* Please remember that if is_unbounded() returns `true`, the result
|
||||||
|
* of claling this is undefined. Therefore, only use this if there is
|
||||||
|
* a stack limit in place. The returned size is in bytes.
|
||||||
|
*
|
||||||
|
* @see minimum_size(), default_size()
|
||||||
|
*/
|
||||||
static size_t maximum_size() noexcept;
|
static size_t maximum_size() noexcept;
|
||||||
|
|
||||||
|
/** @brief Gets the default size for stacks.
|
||||||
|
*
|
||||||
|
* Returns a reasonable size for a coroutine stack (not the main stack).
|
||||||
|
* Currently this is set to be 64 KiB, unless minimum_size() returns a
|
||||||
|
* bigger value (in which case it's the result of minimum_size()).
|
||||||
|
*
|
||||||
|
* @see minimum_size(), maximum_size()
|
||||||
|
*/
|
||||||
static size_t default_size() noexcept;
|
static size_t default_size() noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,26 +116,64 @@ namespace detail {
|
||||||
OSTD_EXPORT size_t stack_main_size() noexcept;
|
OSTD_EXPORT size_t stack_main_size() noexcept;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TR, bool Protected>
|
/** @brief A fixed size stack.
|
||||||
|
*
|
||||||
|
* A normal stack with a fixed size. The size of stacks alloated by this
|
||||||
|
* is always at least one page. Protected stacks add an extra guard page
|
||||||
|
* to the end, which is not usable by the stack. The size of the stack
|
||||||
|
* is always a multiple of page size. If the requested size is not a
|
||||||
|
* multiple, it's rounded up to the nearest multiple.
|
||||||
|
*
|
||||||
|
* System specific facilities are used to allocate the stacks. On POSIX
|
||||||
|
* systems, this tends to be `mmap()` with `MAP_ANON` or `MAP_ANONYMOUS`,
|
||||||
|
* unless not available (in which case the fallback is just `malloc`).
|
||||||
|
* On Windows, `VirtualAlloc()` is used.
|
||||||
|
*
|
||||||
|
* This allocator can also be used in places a stack pool is necessary,
|
||||||
|
* allocating single stacks (not from a pool). See ostd::basic_stack_pool
|
||||||
|
* for more information.
|
||||||
|
*
|
||||||
|
* @tparam Traits The stack traits to use (typically ostd::stack_traits).
|
||||||
|
* @tparam Protected Whether to protect the stack.
|
||||||
|
*/
|
||||||
|
template<typename Traits, bool Protected>
|
||||||
struct basic_fixedsize_stack {
|
struct basic_fixedsize_stack {
|
||||||
using traits_type = TR;
|
/** @brief The traits type used for the stacks. */
|
||||||
|
using traits_type = Traits;
|
||||||
|
|
||||||
|
/** @brief The allocator type, here it's the allocator itself.
|
||||||
|
*
|
||||||
|
* This allows the plain allocator to be used in cases where a stack
|
||||||
|
* pool is expected to be used for allocations. See get_allocator().
|
||||||
|
*/
|
||||||
using allocator_type = basic_fixedsize_stack;
|
using allocator_type = basic_fixedsize_stack;
|
||||||
|
|
||||||
|
/** @brief Fixed size stacks are thread safe by default.
|
||||||
|
*
|
||||||
|
* You can allocate stacks from multiple threads using the same
|
||||||
|
* structure without any locking, there is no mutable shared state.
|
||||||
|
*/
|
||||||
static constexpr bool is_thread_safe = true;
|
static constexpr bool is_thread_safe = true;
|
||||||
|
|
||||||
basic_fixedsize_stack(size_t ss = TR::default_size()) noexcept:
|
/** @brief Constructs the stack allocator.
|
||||||
|
*
|
||||||
|
* The provided argument is the size used for the stacks. It defaults
|
||||||
|
* to the default size used for the stacks according to the traits.
|
||||||
|
*/
|
||||||
|
basic_fixedsize_stack(size_t ss = Traits::default_size()) noexcept:
|
||||||
p_size(
|
p_size(
|
||||||
TR::is_unbounded()
|
Traits::is_unbounded()
|
||||||
? std::max(ss, TR::minimum_size())
|
? std::max(ss, Traits::minimum_size())
|
||||||
: std::clamp(ss, TR::minimum_size(), TR::maximum_size())
|
: std::clamp(ss, Traits::minimum_size(), Traits::maximum_size())
|
||||||
)
|
)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
/** @brief Allocates a stack. */
|
||||||
stack_context allocate() {
|
stack_context allocate() {
|
||||||
size_t ss = p_size;
|
size_t ss = p_size;
|
||||||
|
|
||||||
size_t pgs = TR::page_size();
|
size_t pgs = Traits::page_size();
|
||||||
size_t npg = std::max(ss / pgs, size_t(size_t(Protected) + 1));
|
size_t asize = ss + pgs - 1 - (ss - 1) % pgs + (pgs * Protected);
|
||||||
size_t asize = npg * pgs;
|
|
||||||
|
|
||||||
void *p = detail::stack_alloc(asize);
|
void *p = detail::stack_alloc(asize);
|
||||||
if constexpr(Protected) {
|
if constexpr(Protected) {
|
||||||
|
@ -73,13 +181,14 @@ struct basic_fixedsize_stack {
|
||||||
detail::stack_protect(p, pgs);
|
detail::stack_protect(p, pgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack_context ret{static_cast<byte *>(p) + ss, ss};
|
stack_context ret{static_cast<byte *>(p) + asize, asize};
|
||||||
#ifdef OSTD_USE_VALGRIND
|
#ifdef OSTD_USE_VALGRIND
|
||||||
ret.valgrind_id = VALGRIND_STACK_REGISTER(ret.ptr, p);
|
ret.valgrind_id = VALGRIND_STACK_REGISTER(ret.ptr, p);
|
||||||
#endif
|
#endif
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Deallocates a stack. */
|
||||||
void deallocate(stack_context &st) noexcept {
|
void deallocate(stack_context &st) noexcept {
|
||||||
if (!st.ptr) {
|
if (!st.ptr) {
|
||||||
return;
|
return;
|
||||||
|
@ -91,9 +200,21 @@ struct basic_fixedsize_stack {
|
||||||
st.ptr = nullptr;
|
st.ptr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief A no-op function for stack pool uses.
|
||||||
|
*
|
||||||
|
* When a stack pool is used to allocate stacks somewhere, multiple
|
||||||
|
* stacks can be reserved ahead of time. This allocates only one stack
|
||||||
|
* at a time, so this function does nothing.
|
||||||
|
*/
|
||||||
void reserve(size_t) {}
|
void reserve(size_t) {}
|
||||||
|
|
||||||
basic_fixedsize_stack get_allocator() noexcept {
|
/** @brief Gets the allocator for stack pool uses.
|
||||||
|
*
|
||||||
|
* Since this is not a stack pool, this function returns a copy of
|
||||||
|
* this object. This allows the stack allocator to be used as if
|
||||||
|
* it was a pool.
|
||||||
|
*/
|
||||||
|
allocator_type get_allocator() noexcept {
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,10 +222,33 @@ private:
|
||||||
size_t p_size;
|
size_t p_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief An unprotected fixed size stack using ostd::stack_traits. */
|
||||||
using fixedsize_stack = basic_fixedsize_stack<stack_traits, false>;
|
using fixedsize_stack = basic_fixedsize_stack<stack_traits, false>;
|
||||||
|
|
||||||
|
/** @brief A protected fixed size stack using ostd::stack_traits. */
|
||||||
using protected_fixedsize_stack = basic_fixedsize_stack<stack_traits, true>;
|
using protected_fixedsize_stack = basic_fixedsize_stack<stack_traits, true>;
|
||||||
|
|
||||||
template<typename TR, bool Protected>
|
/** @brief A stack pool.
|
||||||
|
*
|
||||||
|
* A stack pool allocates multiple stacks at a time and gives them out as
|
||||||
|
* requested. When the preallocated stacks run out, a new chunk of stacks
|
||||||
|
* is allocated. You can also preallocate as many stacks as you want in
|
||||||
|
* the pool to prevent allocations from being done at later time.
|
||||||
|
*
|
||||||
|
* When a stack is "freed" using the pool or a stack allocator requested
|
||||||
|
* from the pool, the stack is not actually freed, just returned to the pool
|
||||||
|
* and reused next time something else requests a stack.
|
||||||
|
*
|
||||||
|
* The allocated stacks are fixed size and allocated exactly the same as
|
||||||
|
* ostd::basic_fixedsize_stack would.
|
||||||
|
*
|
||||||
|
* Keep in mind that stack pools are not thread safe, so external locking
|
||||||
|
* has to be done (see is_thread_safe).
|
||||||
|
*
|
||||||
|
* @tparam Traits The stack traits to use (typically ostd::stack_traits).
|
||||||
|
* @tparam Protected Whether to protect the stack.
|
||||||
|
*/
|
||||||
|
template<typename Traits, bool Protected>
|
||||||
struct basic_stack_pool {
|
struct basic_stack_pool {
|
||||||
private:
|
private:
|
||||||
struct allocator {
|
struct allocator {
|
||||||
|
@ -124,34 +268,83 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/** @brief The default number of stacks to store in each chunk. */
|
||||||
static constexpr size_t DEFAULT_CHUNK_SIZE = 32;
|
static constexpr size_t DEFAULT_CHUNK_SIZE = 32;
|
||||||
|
|
||||||
using traits_type = TR;
|
/** @brief The traits type used for the stacks. */
|
||||||
|
using traits_type = Traits;
|
||||||
|
|
||||||
|
/** @brief The allocator type for the pool.
|
||||||
|
*
|
||||||
|
* Using get_allocator(), you can request an allocator from the pool
|
||||||
|
* which will be just a regular stack allocator except sourcing stacks
|
||||||
|
* from the pool. This is the allocator type used for that.
|
||||||
|
*/
|
||||||
using allocator_type = allocator;
|
using allocator_type = allocator;
|
||||||
|
|
||||||
|
/** @brief Stack pools are not thread safe.
|
||||||
|
*
|
||||||
|
* There is some shared state, so it's necessary to lock when requesting
|
||||||
|
* stacks from multiple threads. This is typically not a problem, as in
|
||||||
|
* the typical case of doing this some lock needs to be there anyway
|
||||||
|
* (typically to handle creation of the objects the stacks will be
|
||||||
|
* used in), but sometimes it might be necessary to check this.
|
||||||
|
*/
|
||||||
static constexpr bool is_thread_safe = false;
|
static constexpr bool is_thread_safe = false;
|
||||||
|
|
||||||
|
/** @brief Creates a stack pool.
|
||||||
|
*
|
||||||
|
* The parameters are optional. The stack size defaults to the default
|
||||||
|
* size used for stacks according to the traits. The number of stacks
|
||||||
|
* in each chunk defaults to `DEFAULT_CHUNK_SIZE`.
|
||||||
|
*
|
||||||
|
* @param ss The stack size used for the individual stacks.
|
||||||
|
* @param cs The number of stacks in each chunk.
|
||||||
|
*/
|
||||||
basic_stack_pool(
|
basic_stack_pool(
|
||||||
size_t ss = TR::default_size(), size_t cs = DEFAULT_CHUNK_SIZE
|
size_t ss = Traits::default_size(), size_t cs = DEFAULT_CHUNK_SIZE
|
||||||
) {
|
) {
|
||||||
/* precalculate the sizes */
|
/* precalculate the sizes */
|
||||||
size_t pgs = TR::page_size();
|
size_t pgs = Traits::page_size();
|
||||||
size_t npg = std::max(ss / pgs, size_t(size_t(Protected) + 1));
|
size_t asize = ss + pgs - 1 - (ss - 1) % pgs + (pgs * Protected);
|
||||||
size_t asize = npg * pgs;
|
|
||||||
p_stacksize = asize;
|
p_stacksize = asize;
|
||||||
p_chunksize = cs * asize;
|
p_chunksize = cs * asize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Stack pools are not copy constructible. */
|
||||||
basic_stack_pool(basic_stack_pool const &) = delete;
|
basic_stack_pool(basic_stack_pool const &) = delete;
|
||||||
|
|
||||||
|
/** @brief Moves the stack pool.
|
||||||
|
*
|
||||||
|
* Moves all state from the other pool to this one. The other pool
|
||||||
|
* is emptied (no allocated chunks, no capacity) with its stack and
|
||||||
|
* chunk sizes remaining the same.
|
||||||
|
*/
|
||||||
basic_stack_pool(basic_stack_pool &&p) noexcept {
|
basic_stack_pool(basic_stack_pool &&p) noexcept {
|
||||||
swap(p);
|
p_chunk = p.p_chunk;
|
||||||
|
p_unused = p.p_unused;
|
||||||
|
p_chunksize = p.p_chunksize;
|
||||||
|
p_stacksize = p.p_stacksize;
|
||||||
|
p_capacity = p.p_capacity;
|
||||||
|
p.p_chunk = nullptr;
|
||||||
|
p.p_unused = nullptr;
|
||||||
|
p.p_capacity = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Stack pools are not copy assignable. */
|
||||||
basic_stack_pool &operator=(basic_stack_pool const &) = delete;
|
basic_stack_pool &operator=(basic_stack_pool const &) = delete;
|
||||||
|
|
||||||
|
/** @brief Move assigns another pool to this one.
|
||||||
|
*
|
||||||
|
* Basically performs swap(basic_stack_pool &). If the other pool
|
||||||
|
* is not used, the former state of this one is destroyed with it.
|
||||||
|
*/
|
||||||
basic_stack_pool &operator=(basic_stack_pool &&p) noexcept {
|
basic_stack_pool &operator=(basic_stack_pool &&p) noexcept {
|
||||||
swap(p);
|
swap(p);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Destroys the stack pool and all memory managed by it. */
|
||||||
~basic_stack_pool() {
|
~basic_stack_pool() {
|
||||||
size_t ss = p_stacksize;
|
size_t ss = p_stacksize;
|
||||||
size_t cs = p_chunksize;
|
size_t cs = p_chunksize;
|
||||||
|
@ -163,6 +356,12 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Reserves a number of stacks.
|
||||||
|
*
|
||||||
|
* The given number is the actual number of stacks the pool is supposed
|
||||||
|
* to contain. If it already contains that number or more, this function
|
||||||
|
* does nothing. Otherwise it potentially reserves some extra chunks.
|
||||||
|
*/
|
||||||
void reserve(size_t n) {
|
void reserve(size_t n) {
|
||||||
size_t cap = p_capacity;
|
size_t cap = p_capacity;
|
||||||
if (n <= cap) {
|
if (n <= cap) {
|
||||||
|
@ -172,12 +371,18 @@ public:
|
||||||
p_unused = alloc_chunks(p_unused, (n - cap + cnum - 1) / cnum);
|
p_unused = alloc_chunks(p_unused, (n - cap + cnum - 1) / cnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Requests a stack directly from the pool.
|
||||||
|
*
|
||||||
|
* As stack allocators are copyable and pools are not, this might not
|
||||||
|
* be prectical in most cases. Instead, the pool will be stored somewhere
|
||||||
|
* and allocations from it will be done via get_allocator().
|
||||||
|
*/
|
||||||
stack_context allocate() {
|
stack_context allocate() {
|
||||||
stack_node *nd = request();
|
stack_node *nd = request();
|
||||||
size_t ss = p_stacksize - sizeof(stack_node);
|
size_t ss = p_stacksize - sizeof(stack_node);
|
||||||
auto *p = reinterpret_cast<unsigned char *>(nd) - ss;
|
auto *p = reinterpret_cast<unsigned char *>(nd) - ss;
|
||||||
if constexpr(Protected) {
|
if constexpr(Protected) {
|
||||||
detail::stack_protect(p, TR::page_size());
|
detail::stack_protect(p, Traits::page_size());
|
||||||
}
|
}
|
||||||
stack_context ret{nd, ss};
|
stack_context ret{nd, ss};
|
||||||
#ifdef OSTD_USE_VALGRIND
|
#ifdef OSTD_USE_VALGRIND
|
||||||
|
@ -186,6 +391,11 @@ public:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Returns a stack back to the pool.
|
||||||
|
*
|
||||||
|
* This returns the given stack back to the pool for reuse. Stack pool
|
||||||
|
* only frees all of its memory when it's destroyed.
|
||||||
|
*/
|
||||||
void deallocate(stack_context &st) noexcept {
|
void deallocate(stack_context &st) noexcept {
|
||||||
if (!st.ptr) {
|
if (!st.ptr) {
|
||||||
return;
|
return;
|
||||||
|
@ -199,14 +409,22 @@ public:
|
||||||
p_unused = nd;
|
p_unused = nd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Swaps two stack pools. */
|
||||||
void swap(basic_stack_pool &p) noexcept {
|
void swap(basic_stack_pool &p) noexcept {
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(p_chunk, p.p_chunk);
|
swap(p_chunk, p.p_chunk);
|
||||||
swap(p_unused, p.p_unused);
|
swap(p_unused, p.p_unused);
|
||||||
swap(p_chunksize, p.p_chunksize);
|
swap(p_chunksize, p.p_chunksize);
|
||||||
swap(p_stacksize, p.p_stacksize);
|
swap(p_stacksize, p.p_stacksize);
|
||||||
|
swap(p_capacity, p.p_capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Gets a stack allocator that uses the pool.
|
||||||
|
*
|
||||||
|
* The returned allocator will use allocate() and
|
||||||
|
* deallocate(stack_context &) to manage the stacks.
|
||||||
|
* You will typically want to use this wherever pools are used.
|
||||||
|
*/
|
||||||
allocator_type get_allocator() noexcept {
|
allocator_type get_allocator() noexcept {
|
||||||
return allocator{*this};
|
return allocator{*this};
|
||||||
}
|
}
|
||||||
|
@ -268,16 +486,27 @@ private:
|
||||||
size_t p_capacity = 0;
|
size_t p_capacity = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename TR, bool P>
|
/** @brief Swaps two stack pools. */
|
||||||
inline void swap(basic_stack_pool<TR, P> &a, basic_stack_pool<TR, P> &b) noexcept {
|
template<typename Traits, bool P>
|
||||||
|
inline void swap(
|
||||||
|
basic_stack_pool<Traits, P> &a, basic_stack_pool<Traits, P> &b
|
||||||
|
) noexcept {
|
||||||
a.swap(b);
|
a.swap(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief An unprotected stack pool using ostd::stack_traits. */
|
||||||
using stack_pool = basic_stack_pool<stack_traits, false>;
|
using stack_pool = basic_stack_pool<stack_traits, false>;
|
||||||
|
|
||||||
|
/** @brief A protected stack pool using ostd::stack_traits. */
|
||||||
using protected_stack_pool = basic_stack_pool<stack_traits, true>;
|
using protected_stack_pool = basic_stack_pool<stack_traits, true>;
|
||||||
|
|
||||||
|
/** @brief The default stack allocator to use when none is provided. */
|
||||||
using default_stack = fixedsize_stack;
|
using default_stack = fixedsize_stack;
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
||||||
} /* namespace ostd */
|
} /* namespace ostd */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
Loading…
Reference in a new issue