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:
		
							
								
								
									
										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; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user