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