/* Stack allocation for coroutine contexts. * API more or less compatible with the Boost.Context library. * * This file is part of OctaSTD. See COPYING.md for futher information. */ #ifndef OSTD_CONTEXT_STACK_HH #define OSTD_CONTEXT_STACK_HH #include #include #include "ostd/types.hh" #include "ostd/platform.hh" #if !defined(OSTD_PLATFORM_POSIX) && !defined(OSTD_PLATFORM_WIN32) # error "Unsupported platform" #endif #ifdef OSTD_USE_VALGRIND # include #endif namespace ostd { struct stack_context { void *ptr = nullptr; size_t size = 0; #ifdef OSTD_USE_VALGRIND int valgrind_id = 0; #endif }; struct OSTD_EXPORT stack_traits { static bool is_unbounded() noexcept; static size_t page_size() noexcept; static size_t minimum_size() noexcept; static size_t maximum_size() noexcept; static size_t default_size() noexcept; }; namespace detail { OSTD_EXPORT void *stack_alloc(size_t sz); OSTD_EXPORT void stack_free(void *p, size_t sz) noexcept; OSTD_EXPORT void stack_protect(void *p, size_t sz) noexcept; OSTD_EXPORT size_t stack_main_size() noexcept; } template struct basic_fixedsize_stack { using traits_type = TR; using allocator_type = basic_fixedsize_stack; static constexpr bool is_thread_safe = true; basic_fixedsize_stack(size_t ss = TR::default_size()) noexcept: p_size( TR::is_unbounded() ? std::max(ss, TR::minimum_size()) : std::clamp(ss, TR::minimum_size(), TR::maximum_size()) ) {} stack_context allocate() { size_t ss = p_size; size_t pgs = TR::page_size(); size_t npg = std::max(ss / pgs, size_t(size_t(Protected) + 1)); size_t asize = npg * pgs; void *p = detail::stack_alloc(asize); if constexpr(Protected) { /* a single guard page */ detail::stack_protect(p, pgs); } stack_context ret{static_cast(p) + ss, ss}; #ifdef OSTD_USE_VALGRIND ret.valgrind_id = VALGRIND_STACK_REGISTER(ret.ptr, p); #endif return ret; } void deallocate(stack_context &st) noexcept { if (!st.ptr) { return; } #ifdef OSTD_USE_VALGRIND VALGRIND_STACK_DEREGISTER(st.valgrind_id); #endif detail::stack_free(static_cast(st.ptr) - st.size, st.size); st.ptr = nullptr; } void reserve(size_t) {} basic_fixedsize_stack get_allocator() noexcept { return *this; } private: size_t p_size; }; using fixedsize_stack = basic_fixedsize_stack; using protected_fixedsize_stack = basic_fixedsize_stack; template struct basic_stack_pool { private: struct allocator { allocator() = delete; allocator(basic_stack_pool &p) noexcept: p_pool(&p) {} stack_context allocate() { return p_pool->allocate(); } void deallocate(stack_context &st) noexcept { p_pool->deallocate(st); } private: basic_stack_pool *p_pool; }; public: static constexpr size_t DEFAULT_CHUNK_SIZE = 32; using traits_type = TR; using allocator_type = allocator; static constexpr bool is_thread_safe = false; basic_stack_pool( size_t ss = TR::default_size(), size_t cs = DEFAULT_CHUNK_SIZE ) { /* precalculate the sizes */ size_t pgs = TR::page_size(); size_t npg = std::max(ss / pgs, size_t(size_t(Protected) + 1)); size_t asize = npg * pgs; p_stacksize = asize; p_chunksize = cs * asize; } basic_stack_pool(basic_stack_pool const &) = delete; basic_stack_pool(basic_stack_pool &&p) noexcept { swap(p); } basic_stack_pool &operator=(basic_stack_pool const &) = delete; basic_stack_pool &operator=(basic_stack_pool &&p) noexcept { swap(p); return *this; } ~basic_stack_pool() { size_t ss = p_stacksize; size_t cs = p_chunksize; void *pc = p_chunk; while (pc) { void *p = pc; pc = get_node(p, ss, 1)->next_chunk; detail::stack_free(p, cs); } } void reserve(size_t n) { size_t cap = p_capacity; if (n <= cap) { return; } size_t cnum = p_chunksize / p_stacksize; p_unused = alloc_chunks(p_unused, (n - cap + cnum - 1) / cnum); } stack_context allocate() { stack_node *nd = request(); size_t ss = p_stacksize - sizeof(stack_node); auto *p = reinterpret_cast(nd) - ss; if constexpr(Protected) { detail::stack_protect(p, TR::page_size()); } stack_context ret{nd, ss}; #ifdef OSTD_USE_VALGRIND ret.valgrind_id = VALGRIND_STACK_REGISTER(ret.ptr, p); #endif return ret; } void deallocate(stack_context &st) noexcept { if (!st.ptr) { return; } #ifdef OSTD_USE_VALGRIND VALGRIND_STACK_DEREGISTER(st.valgrind_id); #endif stack_node *nd = static_cast(st.ptr); stack_node *unused = p_unused; nd->next = unused; p_unused = nd; } void swap(basic_stack_pool &p) noexcept { using std::swap; swap(p_chunk, p.p_chunk); swap(p_unused, p.p_unused); swap(p_chunksize, p.p_chunksize); swap(p_stacksize, p.p_stacksize); } allocator_type get_allocator() noexcept { return allocator{*this}; } private: struct stack_node { void *next_chunk; stack_node *next; }; stack_node *alloc_chunks(stack_node *un, size_t n) { size_t ss = p_stacksize; size_t cs = p_chunksize; size_t cnum = cs / ss; for (size_t ci = 0; ci < n; ++ci) { void *chunk = detail::stack_alloc(cs); stack_node *prevn = un; for (size_t i = cnum; i >= 2; --i) { auto nd = get_node(chunk, ss, i); nd->next_chunk = nullptr; nd->next = prevn; prevn = nd; } auto *fnd = get_node(chunk, ss, 1); fnd->next_chunk = p_chunk; /* write every time so that a potential failure results * in all previously allocated chunks being freed in dtor */ p_chunk = chunk; fnd->next = prevn; un = fnd; } p_capacity += (n * cnum); return un; } stack_node *request() { stack_node *r = p_unused; if (!r) { r = alloc_chunks(nullptr, 1); } p_unused = r->next; return r; } stack_node *get_node(void *chunk, size_t ssize, size_t n) { return reinterpret_cast( static_cast(chunk) + (ssize * n) - sizeof(stack_node) ); } void *p_chunk = nullptr; stack_node *p_unused = nullptr; size_t p_chunksize; size_t p_stacksize; size_t p_capacity = 0; }; template inline void swap(basic_stack_pool &a, basic_stack_pool &b) noexcept { a.swap(b); } using stack_pool = basic_stack_pool; using protected_stack_pool = basic_stack_pool; using default_stack = fixedsize_stack; } /* namespace ostd */ #endif