diff --git a/README.md b/README.md index f679ba4..c841c30 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ OctaSTD is an extension of the C++17 standard library which mainly provides ranges (to replace iterators) but also various other utilities like proper -streams, string formatting, concurrency utilities and others. It's meant -to replace the more poorly designed parts of the C++ standard library to +streams, string formatting, coroutines, concurrency utilities and others. It's +meant to replace the more poorly designed parts of the C++ standard library to make the language easier and more convenient to use. It is not feature complete right now, as most things are still being worked on. @@ -44,11 +44,33 @@ beyond the minimum supported compiler are necessary to use the library. ## Supported operating systems Most of OctaSTD is entirely platform independent and relies only on the -standard library. Therefore it can be used on any operating system that -provides the right toolchain. +standard library. Therefore it could in theory be used on any operating +system that provides the right toolchain. However, to make things easier +to deal with, it currently assumes either Windows or POSIX environment. +Some parts (such as filesystem and context/coroutines) also use platform +specific code that assumes these two. -There are certain parts (currently the filesystem module) that however do rely -on system specific APIs. These are restricted to POSIX compliant operating -systems and Windows, with testing done on Linux, FreeBSD, macOS and Windows - -they should work on other POSIX compliant operating systems as well, and -potential patches are welcome. +OctaSTD is actively supported on Windows (x86 and x86\_64) as well as Linux, +FreeBSD and macOS. It should also work on other POSIX systems such as the other +BSDs or Solaris - if it doesn't, please report your problem or better, send +patches. + +### Coroutine platform support + +Coroutines work on POSIX and Windows systems. Context switching is done with +platform specific assembly taken from Boost.Context, the provided assembly +code should be enough for all supported platforms, but you need to compile +the correct ones. + +There is also support for stack allocators inspired again by the Boost.Context +library, with fixed size protected and unprotected allocators available on all +platforms and segmented stacks available on POSIX platforms with GCC and Clang. +In order to use segmented stacks, there are 2 things you have to do: + +* Enable `OSTD_USE_SEGMENTED_STACKS` +* Build with `-fsplit-stack -static-libgcc` + +Segmented stacks are used by default when enabled, otherwise unprotected fixed +size stacks are used (on Windows the latter is always used by default). + +There is also Valgrind support, enabled with `OSTD_USE_VALGRIND`. \ No newline at end of file diff --git a/ostd/context_stack.hh b/ostd/context_stack.hh new file mode 100644 index 0000000..0eb7a99 --- /dev/null +++ b/ostd/context_stack.hh @@ -0,0 +1,303 @@ +/* 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 +#include + +#include "ostd/types.hh" +#include "ostd/platform.hh" +#include "ostd/internal/win32.hh" + +#if !defined(OSTD_PLATFORM_POSIX) && !defined(OSTD_PLATFORM_WIN32) +# error "Unsupported platform" +#endif + +/* we can do this as we only support clang 4+ and gcc 7+ + * which always have support for segmented stacks + */ +#ifdef OSTD_USE_SEGMENTED_STACKS +# if !defined(OSTD_PLATFORM_POSIX) || \ + (!defined(OSTD_TOOLCHAIN_GNU) && !defined(OSTD__TOOLCHAIN_CLANG)) +# error "compiler/toolchain does not support segmented_stack stacks" +# endif +# define OSTD_CONTEXT_SEGMENTS 10 +#endif + + +#ifdef OSTD_PLATFORM_POSIX +# include +# include +# include +# include +# include +#endif + +#ifdef OSTD_USE_VALGRIND +# include +#endif + +namespace ostd { + +namespace detail { + +#ifdef OSTD_PLATFORM_POSIX +# if defined(MAP_ANON) || defined(MAP_ANONYMOUS) + constexpr bool CONTEXT_USE_MMAP = true; +# ifdef MAP_ANON + constexpr auto CONTEXT_MAP_ANON = MAP_ANON; +# else +constexpr auto CONTEXT_MAP_ANON = MAP_ANONYMOUS; +# endif +# else +constexpr bool CONTEXT_USE_MMAP = false; +# endif +#endif + +} /* namespace detail */ + +struct stack_context { +#ifdef OSTD_USE_SEGMENTED_STACKS + using segments_context = void *[OSTD_CONTEXT_SEGMENTS]; +#endif + void *ptr = nullptr; + size_t size = 0; +#ifdef OSTD_USE_SEGMENTED_STACKS + segments_context segments_ctx = {}; +#endif +#ifdef OSTD_USE_VALGRIND + int valgrind_id = 0; +#endif +}; + +namespace detail { + inline void ctx_pagesize(size_t *s) noexcept { +#if defined(OSTD_PLATFORM_WIN32) + SYSTEM_INFO si; + GetSystemInfo(&si); + *s = size_t(si.dwPageSize); +#elif defined(OSTD_PLATFORM_POSIX) + *s = size_t(sysconf(_SC_PAGESIZE)); +#endif + } + +#ifdef OSTD_PLATFORM_POSIX + inline void ctx_rlimit_get(rlimit *l) noexcept { + getrlimit(RLIMIT_STACK, l); + } + + inline rlimit ctx_rlimit() noexcept { + static rlimit l; + static std::once_flag fl; + std::call_once(fl, ctx_rlimit_get, &l); + return l; + } +#endif +} + +struct stack_traits { + static bool is_unbounded() noexcept { +#if defined(OSTD_PLATFORM_WIN32) + return true; +#elif defined(OSTD_PLATFORM_POSIX) + return detail::ctx_rlimit().rlim_max == RLIM_INFINITY; +#endif + } + + static size_t page_size() noexcept { + static size_t size = 0; + static std::once_flag fl; + std::call_once(fl, detail::ctx_pagesize, &size); + return size; + } + + static size_t minimum_size() noexcept { +#if defined(OSTD_PLATFORM_WIN32) + /* no func on windows, sane default */ + return sizeof(void *) * 1024; +#elif defined(OSTD_PLATFORM_POSIX) + return SIGSTKSZ; +#endif + } + + static size_t maximum_size() noexcept { +#if defined(OSTD_PLATFORM_WIN32) + /* no func on windows either */ + return 1024 * 1024 * 1024; +#elif defined(OSTD_PLATFORM_POSIX) + return size_t(detail::ctx_rlimit().rlim_max); +#endif + } + + static size_t default_size() noexcept { +#if defined(OSTD_PLATFORM_WIN32) + /* no func on windows either */ + return sizeof(void *) * minimum_size(); +#elif defined(OSTD_PLATFORM_POSIX) + size_t r = sizeof(void *) * minimum_size(); + if (is_unbounded()) { + return r; + } + size_t m = maximum_size(); + if (r > m) { + return m; + } + return r; +#endif + } +}; + +template +struct basic_fixedsize_stack { + using traits_type = TR; + + basic_fixedsize_stack(size_t ss = 0) noexcept: + p_size(ss ? ss : TR::default_size()) + {} + + stack_context allocate() { + size_t ss = p_size; + + ss = std::clamp(ss, TR::minimum_size(), TR::maximum_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; + +#if defined(OSTD_PLATFORM_WIN32) + void *p = VirtualAlloc(0, asize, MEM_COMMIT, PAGE_READWRITE); + if (!p) { + throw std::bad_alloc{} + } + DWORD oo; + if constexpr(Protected) { + VirtualProtect(p, pgs, PAGE_READWRITE | PAGE_GUARD, &oo); + } +#elif defined(OSTD_PLATFORM_POSIX) + void *p = nullptr; + if constexpr(detail::CONTEXT_USE_MMAP) { + void *mp = mmap( + 0, asize, PROT_READ | PROT_WRITE, + MAP_PRIVATE | detail::CONTEXT_MAP_ANON, -1, 0 + ); + if (mp != MAP_FAILED) { + p = mp; + } + } else { + p = std::malloc(asize); + } + if (!p) { + throw std::bad_alloc{}; + } + if constexpr(Protected) { + mprotect(p, pgs, PROT_NONE); + } +#endif + + 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 + + auto p = static_cast(st.ptr) - st.size; +#if defined(OSTD_PLATFORM_WIN32) + VirtualFree(p, 0, MEM_RELEASE); +#elif defined(OSTD_PLATFORM_POSIX) + if constexpr(detail::CONTEXT_USE_MMAP) { + munmap(p, st.size); + } else { + std::free(p); + } +#endif + + st.ptr = nullptr; + } + +private: + size_t p_size; +}; + +using fixedsize_stack = basic_fixedsize_stack; +using protected_fixedsize_stack = basic_fixedsize_stack; + +#ifdef OSTD_USE_SEGMENTED_STACKS +namespace detail { + extern "C" { + /* from libgcc */ + void *__splitstack_makecontext( + size_t st_size, void *ctx[OSTD_CONTEXT_SEGMENTS], size_t *size + ); + void __splitstack_releasecontext(void *ctx[OSTD_CONTEXT_SEGMENTS]); + void __splitstack_resetcontext(void *ctx[OSTD_CONTEXT_SEGMENTS]); + void __splitstack_block_signals_context( + void *ctx[OSTD_CONTEXT_SEGMENTS], int *new_val, int *old_val + ); + } +} + +template +struct basic_segmented_stack { + using traits_type = TR; + + basic_segmented_stack(size_t ss = 0) noexcept: + p_size(ss ? ss : TR::default_size()) + {} + + stack_context allocate() { + size_t ss = p_size; + + stack_context ret; + void *p = detail::__splitstack_makecontext( + ss, ret.segments_ctx, &ret.size + ); + if (!p) { + throw std::bad_alloc{}; + } + + ret.ptr = static_cast(p) + ret.size; + + int off = 0; + detail::__splitstack_block_signals_context(ret.segments_ctx, &off, 0); + + return ret; + } + + void deallocate(stack_context &st) noexcept { + detail::__splitstack_releasecontext(st.segments_ctx); + } + +private: + size_t p_size; +}; + +using segmented_stack = basic_segmented_stack; +#endif /* OSTD_USE_SEGMENTED_STACKS */ + +#ifdef OSTD_USE_SEGMENTED_STACKS +using default_stack = segmented_stack; +#else +using default_stack = fixedsize_stack; +#endif + +} /* namespace ostd */ + +#endif diff --git a/ostd/internal/context.hh b/ostd/internal/context.hh index 952a85d..c4408d2 100644 --- a/ostd/internal/context.hh +++ b/ostd/internal/context.hh @@ -1,4 +1,4 @@ -/* Context switching and stack allocation. +/* Context switching for coroutines. * * This file is part of OctaSTD. See COPYING.md for futher information. */ @@ -6,37 +6,14 @@ #ifndef OSTD_INTERNAL_CONTEXT_HH #define OSTD_INTERNAL_CONTEXT_HH -#include -#include #include -#include +#include #include "ostd/types.hh" #include "ostd/platform.hh" -#include "ostd/internal/win32.hh" - -#ifdef OSTD_PLATFORM_POSIX -# include -# include -# include -# include -# include -#endif - -#ifdef OSTD_USE_VALGRIND -# include -#endif +#include "ostd/context_stack.hh" namespace ostd { - -struct stack_context { - void *ptr; - size_t size; -#ifdef OSTD_USE_VALGRIND - int valgrind_id; -#endif -}; - namespace detail { /* from boost.fcontext */ @@ -62,9 +39,6 @@ transfer_t OSTD_CDECL ostd_ontop_fcontext( fcontext_t const to, void *vp, transfer_t (*fn)(transfer_t) ); -stack_context context_stack_alloc(size_t ss); -void context_stack_free(stack_context &st); - struct coroutine_context { protected: struct forced_unwind { @@ -149,7 +123,7 @@ protected: } template - void make_context(SA sa, void (*callp)(transfer_t)) { + void make_context(SA &sa, void (*callp)(transfer_t)) { p_stack = sa.allocate(); constexpr size_t salign = alignof(SA); @@ -161,11 +135,10 @@ protected: (static_cast(p_stack.ptr) - static_cast(sp)); p_coro = ostd_make_fcontext(sp, asize, callp); - p_sa = new (sp) SA(sa); + p_sa = new (sp) SA(std::move(sa)); } - /* TODO: new'ing the stack is sub-optimal */ - stack_context p_stack = { nullptr, 0 }; + stack_context p_stack; fcontext_t p_coro; fcontext_t p_orig; std::exception_ptr p_except; @@ -173,181 +146,7 @@ protected: void *p_sa; }; -/* stack allocator */ - -#if defined(OSTD_PLATFORM_WIN32) - -inline size_t context_get_page_size() { - SYSTEM_INFO si; - GetSystemInfo(&si); - return size_t(si.dwPageSize); -} - -inline size_t context_stack_get_min_size() { - /* no func on windows, sane default */ - return sizeof(void *) * 1024; -} - -inline size_t context_stack_get_max_size() { - return 1024 * 1024 * 1024; -} - -inline size_t context_stack_get_def_size() { - return sizeof(void *) * context_stack_get_min_size(); -} - -#elif defined(OSTD_PLATFORM_POSIX) - -inline size_t context_get_page_size() { - return size_t(sysconf(_SC_PAGESIZE)); -} - -inline size_t context_stack_get_min_size() { - return SIGSTKSZ; -} - -inline size_t context_stack_get_max_size() { - rlimit l; - getrlimit(RLIMIT_STACK, &l); - return size_t(l.rlim_max); -} - -inline size_t context_stack_get_def_size() { - rlimit l; - size_t r = sizeof(void *) * SIGSTKSZ; - - getrlimit(RLIMIT_STACK, &l); - if ((l.rlim_max != RLIM_INFINITY) && (l.rlim_max < r)) { - return size_t(l.rlim_max); - } - - return r; -} - -#if defined(MAP_ANON) || defined(MAP_ANONYMOUS) -constexpr bool CONTEXT_USE_MMAP = true; -# ifdef MAP_ANON -constexpr auto CONTEXT_MAP_ANON = MAP_ANON; -# else -constexpr auto CONTEXT_MAP_ANON = MAP_ANONYMOUS; -# endif -#else -constexpr bool CONTEXT_USE_MMAP = false; -#endif - -#else /* OSTD_PLATFORM_POSIX */ -# error "Unsupported platform" -#endif - -template -inline stack_context context_stack_alloc(size_t ss) { - if (!ss) { - ss = context_stack_get_def_size(); - } else { - ss = std::clamp( - ss, context_stack_get_min_size(), context_stack_get_max_size() - ); - } - size_t pgs = context_get_page_size(); - size_t npg = std::max(ss / pgs, size_t(size_t(Protected) + 1)); - size_t asize = npg * pgs; - -#if defined(OSTD_PLATFORM_WIN32) - void *p = VirtualAlloc(0, asize, MEM_COMMIT, PAGE_READWRITE); - if (!p) { - throw std::bad_alloc{} - } - DWORD oo; - if constexpr(Protected) { - VirtualProtect(p, pgs, PAGE_READWRITE | PAGE_GUARD, &oo); - } -#elif defined(OSTD_PLATFORM_POSIX) - void *p = nullptr; - if constexpr(CONTEXT_USE_MMAP) { - void *mp = mmap( - 0, asize, PROT_READ | PROT_WRITE, - MAP_PRIVATE | CONTEXT_MAP_ANON, -1, 0 - ); - if (mp != MAP_FAILED) { - p = mp; - } - } else { - p = std::malloc(asize); - } - if (!p) { - throw std::bad_alloc{}; - } - if constexpr(Protected) { - mprotect(p, pgs, PROT_NONE); - } -#endif - - stack_context ret{static_cast(p) + ss, ss}; - -#ifdef OSTD_USE_VALGRIND - ret.valgrind_id = VALGRIND_STACK_REGISTER(ret.ptr, p); -#endif - - return ret; -} - -inline void context_stack_free(stack_context &st) { - if (!st.ptr) { - return; - } - -#ifdef OSTD_USE_VALGRIND - VALGRIND_STACK_DEREGISTER(st.valgrind_id); -#endif - - auto p = static_cast(st.ptr) - st.size; -#if defined(OSTD_PLATFORM_WIN32) - VirtualFree(p, 0, MEM_RELEASE); -#elif defined(OSTD_PLATFORM_POSIX) - if constexpr(CONTEXT_USE_MMAP) { - munmap(p, st.size); - } else { - std::free(p); - } -#endif - - st.ptr = nullptr; -} - } /* namespace detail */ - -struct protected_fixedsize_stack { - protected_fixedsize_stack(size_t ss = 0): p_size(ss) {} - - stack_context allocate() { - return detail::context_stack_alloc(p_size); - } - - void deallocate(stack_context &st) { - detail::context_stack_free(st); - } - -private: - size_t p_size; -}; - -struct fixedsize_stack { - fixedsize_stack(size_t ss = 0): p_size(ss) {} - - stack_context allocate() { - return detail::context_stack_alloc(p_size); - } - - void deallocate(stack_context &st) { - detail::context_stack_free(st); - } - -private: - size_t p_size; -}; - -using default_stack = fixedsize_stack; - -} +} /* namespace ostd */ #endif