Adding initial coroutine library with simple example/test.

This commit is contained in:
Justin R. Cutler 2016-03-31 02:31:46 -04:00
parent 46abdfefe2
commit 770462d0d8
4 changed files with 319 additions and 0 deletions

10
CMakeLists.txt Normal file
View File

@ -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)

View File

@ -0,0 +1,70 @@
#ifndef THREADLESS_COROUTINE_H
#define THREADLESS_COROUTINE_H
/* bool, true, false */
#include <stdbool.h>
/* NULL, size_t */
#include <stddef.h>
/** 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 */

147
lib/coroutine.c Normal file
View File

@ -0,0 +1,147 @@
/* stack_t (in ucontext.h) */
#define _BSD_SOURCE
/* errno, ENOMEM */
#include <errno.h>
/* calloc, free */
#include <stdlib.h>
/* malloc, munmap, PROT_*, MAP_* */
#include <sys/mman.h>
/* ucontext_t, getcontext, makecontext, swapcontext */
#include <ucontext.h>
/*sysconf, _SC_PAGE_SIZE */
#include <unistd.h>
/* ... */
#include <threadless/coroutine.h>
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;
}

92
test/coroutine.c Normal file
View File

@ -0,0 +1,92 @@
/* printf, perror */
#include <stdio.h>
/* EXIT_SUCCESS, EXIT_FAILURE */
#include <stdlib.h>
/* ... */
#include <threadless/coroutine.h>
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;
}