diff --git a/include/koishi.h b/include/koishi.h index 37a3f2e..06d62c6 100644 --- a/include/koishi.h +++ b/include/koishi.h @@ -113,7 +113,7 @@ typedef struct koishi_coroutine { * @brief Private data reserved for the implementation. Don't mess with it. * @private */ - void *_private[8]; + void *_private[16]; #ifdef BUILDING_KOISHI }; #else diff --git a/koishi_test.c b/koishi_test.c index edf256c..2ea412f 100644 --- a/koishi_test.c +++ b/koishi_test.c @@ -103,6 +103,40 @@ void cancelled_caller_test(int *result) { koishi_deinit(&outer); } +void *counter(void *a) { + (void)a; + + for(int i = 0;; ++i, koishi_yield(NULL)) { + printf("%i\n", i); + } +} + +void *counter_creator(void *a) { + koishi_coroutine_t *co_counter = a; + koishi_init(co_counter, 0, counter); + + printf("Counting from creator:\n"); + koishi_resume(co_counter, NULL); + koishi_resume(co_counter, NULL); + koishi_resume(co_counter, NULL); + + return NULL; +} + +void resume_from_another_test(void) { + koishi_coroutine_t co_counter, co_creator; + koishi_init(&co_creator, 0, counter_creator); + koishi_resume(&co_creator, &co_counter); + + printf("Counting from main:\n"); + koishi_resume(&co_counter, NULL); + koishi_resume(&co_counter, NULL); + koishi_resume(&co_counter, NULL); + + koishi_deinit(&co_creator); + koishi_deinit(&co_counter); +} + int main(int argc, char **argv) { if(argc != 1) { printf("%s takes no arguments.\n", argv[0]); @@ -135,6 +169,8 @@ int main(int argc, char **argv) { cancelled_caller_test(&result); assert(result == 42); + resume_from_another_test(); + printf("Done\n"); return 0; } diff --git a/meson_options.txt b/meson_options.txt index 5c8f826..1f23e7b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,6 +5,7 @@ option( 'auto', 'fcontext', 'boost_fcontext', + 'boost_callcc', 'win32fiber', 'emscripten', 'ucontext_e2k', diff --git a/src/boost_callcc/boost_callcc.cc b/src/boost_callcc/boost_callcc.cc new file mode 100644 index 0000000..fcf9a9a --- /dev/null +++ b/src/boost_callcc/boost_callcc.cc @@ -0,0 +1,115 @@ + +#include +#include + +extern "C" { + #include "../stack_alloc.h" +} + +#include +namespace bctx = boost::context; + +typedef struct boost_fiber { + bctx::continuation cc; + struct boost_fiber *from; + struct boost_fiber *next; + char *stack; + size_t stack_size; +} koishi_fiber_t; + +#include "../fiber.h" + +struct fake_allocator { + bctx::stack_context sctx = {}; + + fake_allocator(void *sp, size_t size) noexcept { + sctx.sp = (char*)sp + size; + sctx.size = size; + } + + bctx::stack_context allocate() { return sctx; } + void deallocate(bctx::stack_context&) noexcept { } +}; + +static void koishi_fiber_init_callcc(koishi_fiber_t *fiber) { + fiber->from = nullptr; + fiber->next = nullptr; + new (&fiber->cc) bctx::continuation(); + + fiber->from = &koishi_active()->fiber; + fiber->cc = bctx::callcc(std::allocator_arg, fake_allocator(fiber->stack, fiber->stack_size), + [fiber](bctx::continuation && c) { + c = c.resume(); + fiber->from->cc = std::move(c); + + auto co = reinterpret_cast(fiber); + co->userdata = co->entry(co->userdata); + + koishi_return_to_caller(co, KOISHI_DEAD); + + KOISHI_UNREACHABLE; + return bctx::continuation(); + } + ); +} + +static void koishi_fiber_init(koishi_fiber_t *fiber, size_t min_stack_size) { + fiber->stack = (char*)alloc_stack(min_stack_size, &fiber->stack_size); + koishi_fiber_init_callcc(fiber); +} + +static void koishi_fiber_init_main(koishi_fiber_t *fiber) { + new (&fiber->cc) bctx::continuation(); + fiber->next = fiber; +} + +static void koishi_fiber_recycle(koishi_fiber_t *fiber) { + fiber->cc.~continuation(); + koishi_fiber_init_callcc(fiber); +} + +static void koishi_fiber_deinit(koishi_fiber_t *fiber) { + fiber->cc.~continuation(); + + if(fiber->stack) { + free_stack(fiber->stack, fiber->stack_size); + fiber->stack = nullptr; + } +} + +static void do_jump(koishi_fiber_t *from, koishi_fiber_t *to) { + to->from = from; + to->cc = to->cc.resume(); +} + +// TODO: Figure out how to avoid this stupid hack. +// The resume-from-another test fails without this. +static void stupid_trampoline(koishi_fiber_t *main) { + for(;;) { + auto next = main->next; + + if(next == main) { + break; + } + + main->next = main; + do_jump(main, next); + } +} + +static void koishi_fiber_swap(koishi_fiber_t *from, koishi_fiber_t *to) { + auto main_fiber = &co_main.fiber; + + if(main_fiber == from) { + do_jump(from, to); + stupid_trampoline(main_fiber); + } else { + main_fiber->next = to; + do_jump(from, main_fiber); + } +} + +KOISHI_API void *koishi_get_stack(koishi_coroutine_t *co, size_t *stack_size) { + if(stack_size) *stack_size = co->fiber.stack_size; + return co->fiber.stack; +} diff --git a/src/boost_callcc/meson.build b/src/boost_callcc/meson.build new file mode 100644 index 0000000..701ea8d --- /dev/null +++ b/src/boost_callcc/meson.build @@ -0,0 +1,20 @@ + +boost_callcc_supported = false + +if not add_languages('cpp', required : false) + subdir_done() +endif + +boostctx_dep = dependency('boost', modules : ['context'], required : false) + +if not boostctx_dep.found() + subdir_done() +endif + +boost_callcc_supported = true +boost_callcc_src = files('boost_callcc.cc') +boost_callcc_deps = [boostctx_dep] +boost_callcc_args = [] +boost_callcc_external_args = [] +boost_callcc_external_link_args = [] + diff --git a/src/meson.build b/src/meson.build index 74ee3ef..1d26578 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ koishi_deps = [] backends = [ 'fcontext', 'boost_fcontext', + 'boost_callcc', 'win32fiber', 'emscripten', 'ucontext_e2k',