separate stack stuff into its own file, add support for segmented stacks on POSIX+gcc/clang

master
Daniel Kolesa 2017-03-08 18:11:39 +01:00
parent 81feac59d1
commit 8e97f7fdfd
3 changed files with 341 additions and 217 deletions

View File

@ -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`.

View File

@ -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 <cstdlib>
#include <exception>
#include <algorithm>
#include <mutex>
#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 <unistd.h>
# include <sys/mman.h>
# include <sys/resource.h>
# include <sys/time.h>
# include <signal.h>
#endif
#ifdef OSTD_USE_VALGRIND
# include <valgrind/valgrind.h>
#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<typename TR, bool Protected>
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<byte *>(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<byte *>(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<stack_traits, false>;
using protected_fixedsize_stack = basic_fixedsize_stack<stack_traits, true>;
#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<typename TR>
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<byte *>(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<stack_traits>;
#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

View File

@ -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 <cstdlib>
#include <memory>
#include <exception>
#include <algorithm>
#include <utility>
#include "ostd/types.hh"
#include "ostd/platform.hh"
#include "ostd/internal/win32.hh"
#ifdef OSTD_PLATFORM_POSIX
# include <unistd.h>
# include <sys/mman.h>
# include <sys/resource.h>
# include <sys/time.h>
# include <signal.h>
#endif
#ifdef OSTD_USE_VALGRIND
# include <valgrind/valgrind.h>
#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<typename SA>
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<byte *>(p_stack.ptr) - static_cast<byte *>(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<bool Protected>
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<byte *>(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<byte *>(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<true>(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<false>(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