From 770462d0d860f705d9c4e8ea1395c2723c4eb3e6 Mon Sep 17 00:00:00 2001 From: "Justin R. Cutler" Date: Thu, 31 Mar 2016 02:31:46 -0400 Subject: [PATCH] Adding initial coroutine library with simple example/test. --- CMakeLists.txt | 10 +++ include/threadless/coroutine.h | 70 ++++++++++++++++ lib/coroutine.c | 147 +++++++++++++++++++++++++++++++++ test/coroutine.c | 92 +++++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/threadless/coroutine.h create mode 100644 lib/coroutine.c create mode 100644 test/coroutine.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..338e12b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5) + +set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Werror -std=c99") + +add_library (coroutine lib/coroutine.c) +target_include_directories (coroutine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +add_executable (test-coroutine test/coroutine.c) +target_include_directories (coroutine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_link_libraries (test-coroutine LINK_PUBLIC coroutine) diff --git a/include/threadless/coroutine.h b/include/threadless/coroutine.h new file mode 100644 index 0000000..8d688fb --- /dev/null +++ b/include/threadless/coroutine.h @@ -0,0 +1,70 @@ +#ifndef THREADLESS_COROUTINE_H +#define THREADLESS_COROUTINE_H + +/* bool, true, false */ +#include +/* NULL, size_t */ +#include + +/** Opaque coroutine type */ +typedef struct coroutine coroutine; + +/** Coroutine function type + * @param[in,out] coro coroutine + * @param[in,out] data data passed via first call to coroutine_resume() + * @returns user-defined pointer (which should correspond to value(s) passed to + * coroutine_yield()) + */ +typedef void *(coroutine_function)(coroutine *coro, void *data); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Create a coroutine + * @param function function to run in coroutine + * @param stack_pages number of pages to map for coroutine stack + * @retval non-NULL new coroutine + * @retval NULL error (check @c errno for reason) + * @post upon success, return value may be passed to coroutine_resume() + * @post upon success, return value must be passed to coroutine_destroy() + */ +coroutine *coroutine_create(coroutine_function *function, size_t stack_pages); + +/** Destroy a coroutine + * @param[in,out] coro coroutine to destroy + * @pre @p coro must have been returned by coroutine_create() + * @post @p coro may no longer be used + */ +void coroutine_destroy(coroutine *coro); + +/** Test if a coroutine has ended + * @param[in] coro coroutine to test + * @retval true coroutine has ended (or is @c NULL) + * @retval false coroutine has not ended + * @pre @p coro must have been returned by coroutine_create() + */ +bool coroutine_ended(const coroutine *coro); + +/** Resume a coroutine, passing a value to coroutine_yield() + * @param[in,out] coro coroutine to resume + * @param[in,out] value value to pass (to initial call or as return from + * coroutine_yield()) + * @returns value passed by @p coro to coroutine_yield() + * @pre @p coro must have been returned by coroutine_create() + */ +void *coroutine_resume(coroutine *coro, void *value); + +/** Yield from a coroutine, passing a value to coroutine_resume() + * @param[in,out] coro coroutine to yield from + * @param[in,out] value value to return from coroutine_resume() + * @returns value passed by @p coro to coroutine_resume() + * @pre @p coro must have been returned by coroutine_create() + */ +void *coroutine_yield(coroutine *coro, void *value); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* THREADLESS_COROUTINE_H */ diff --git a/lib/coroutine.c b/lib/coroutine.c new file mode 100644 index 0000000..aa6dc06 --- /dev/null +++ b/lib/coroutine.c @@ -0,0 +1,147 @@ +/* stack_t (in ucontext.h) */ +#define _BSD_SOURCE + +/* errno, ENOMEM */ +#include +/* calloc, free */ +#include + +/* malloc, munmap, PROT_*, MAP_* */ +#include +/* ucontext_t, getcontext, makecontext, swapcontext */ +#include +/*sysconf, _SC_PAGE_SIZE */ +#include + +/* ... */ +#include + + +enum { + COROUTINE_ENDED = 1, +}; + + +struct coroutine { + ucontext_t context; + ucontext_t caller; + void *data; + int status; +}; + + +static void coroutine_entry_point(coroutine *, coroutine_function *) + __attribute__ ((noreturn)); +static void coroutine_entry_point(coroutine *c, coroutine_function *function) +{ + /* run function until it returns */ + void *retval = function(c, c->data); + + /* mark as ended */ + c->status |= COROUTINE_ENDED; + + /* yield final return value (forever) */ + for (;;) { + (void) coroutine_yield(c, retval); + } +} + + +static int stack_allocate(coroutine *coro, size_t stack_pages) +{ + int error = -1; + size_t page_size = sysconf(_SC_PAGE_SIZE); + size_t length = page_size * stack_pages; + void *mapping = MAP_FAILED; + + if ((length / page_size) == stack_pages) { + mapping = mmap(NULL, length, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + } else { + /* integer overflow */ + errno = ENOMEM; + } + + if (MAP_FAILED != mapping) { + coro->context.uc_stack.ss_sp = mapping; + coro->context.uc_stack.ss_size = length; + error = 0; + } + + return error; +} + + +static void stack_free(coroutine *coro) +{ + if (NULL != coro->context.uc_stack.ss_sp) { + (void) munmap(coro->context.uc_stack.ss_sp, + coro->context.uc_stack.ss_size); + } +} + + +coroutine *coroutine_create(coroutine_function *function, size_t stack_pages) +{ + coroutine *coro; + + coro = calloc(1, sizeof(*coro)); + if (NULL == coro) { + goto fail; + } + + (void) getcontext(&coro->context); + + if (stack_allocate(coro, stack_pages)) { + goto fail; + } + + makecontext(&coro->context, (void (*)(void)) coroutine_entry_point, 2, + coro, function); + + return coro; + +fail: + if (NULL != coro) { + coroutine_destroy(coro); + } + + return NULL; +} + + +void coroutine_destroy(coroutine *coro) +{ + if (NULL != coro) { + stack_free(coro); + free(coro); + } +} + + +bool coroutine_ended(const coroutine *coro) +{ + return (NULL == coro) || !!(coro->status & COROUTINE_ENDED); +} + + +void *coroutine_resume(coroutine *coro, void *value) +{ + if (NULL == coro) { + return NULL; + } + coro->data = value; + (void) swapcontext(&coro->caller, &coro->context); + return coro->data; +} + + +void *coroutine_yield(coroutine *coro, void *value) +{ + if (NULL == coro) { + return NULL; + } + coro->data = value; + (void) swapcontext(&coro->context, &coro->caller); + return coro->data; +} diff --git a/test/coroutine.c b/test/coroutine.c new file mode 100644 index 0000000..a3a8a4a --- /dev/null +++ b/test/coroutine.c @@ -0,0 +1,92 @@ +/* printf, perror */ +#include +/* EXIT_SUCCESS, EXIT_FAILURE */ +#include + +/* ... */ +#include + + +static void *fibonacci_generator(coroutine *coro, void *data) +{ + size_t x = 0; + size_t y = 1; + + /* ignore initial input data */ + (void) data; + + /* generate Fibonacci sequence until overflow */ + while (x <= y) { + size_t tmp = x; + /* yield next value, ignore new input data */ + (void) coroutine_yield(coro, &tmp); + /* x, y = y, x + y */ + tmp = y; + y += x; + x = tmp; + } + + /* no more values */ + return NULL; +} + + +static void *output_coroutine(coroutine *coro, void *data) +{ + size_t *value = data; + + while (NULL != value) { + printf("%zu\n", *value); + value = coroutine_yield(coro, value); + } + + return NULL; +} + + +static int run(void) +{ + int error = -1; + coroutine *fibonacci = NULL; + coroutine *output = NULL; + + fibonacci = coroutine_create(fibonacci_generator, 1); + if (NULL == fibonacci) { + perror("coroutine_create"); + goto fail; + } + + output = coroutine_create(output_coroutine, 1); + if (NULL == output) { + perror("coroutine_create"); + goto fail; + } + + void *data; + + while (!(coroutine_ended(fibonacci) || coroutine_ended(output))) { + data = coroutine_resume(fibonacci, NULL); + data = coroutine_resume(output, data); + } + + error = 0; + +fail: + coroutine_destroy(output); + coroutine_destroy(fibonacci); + + return error; +} + + +int main(int argc, char *argv[]) +{ + int error; + + (void) argc; + (void) argv; + + error = run(); + + return !error ? EXIT_SUCCESS : EXIT_FAILURE; +}