mirror of
https://github.com/jrcutler/threadless.io.git
synced 2024-07-07 10:35:49 +00:00
Adding initial coroutine library with simple example/test.
This commit is contained in:
parent
46abdfefe2
commit
770462d0d8
10
CMakeLists.txt
Normal file
10
CMakeLists.txt
Normal 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)
|
70
include/threadless/coroutine.h
Normal file
70
include/threadless/coroutine.h
Normal 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
147
lib/coroutine.c
Normal 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
92
test/coroutine.c
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user