/* threadless.io * Copyright (c) 2016 Justin R. Cutler * Licensed under the MIT License. See LICENSE file in the project root for * full license information. */ /** @file * coroutine interface implementation * @author Justin R. Cutler */ /* 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_t *, coroutine_function_t *) __attribute__ ((noreturn)); static void coroutine_entry_point(coroutine_t *c, coroutine_function_t *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_t *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_t *coro) { if (NULL != coro->context.uc_stack.ss_sp) { (void) munmap(coro->context.uc_stack.ss_sp, coro->context.uc_stack.ss_size); } } coroutine_t *coroutine_create(coroutine_function_t *function, size_t stack_pages) { coroutine_t *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_t *coro) { if (NULL != coro) { stack_free(coro); free(coro); } } bool coroutine_ended(const coroutine_t *coro) { return (NULL == coro) || !!(coro->status & COROUTINE_ENDED); } void *coroutine_resume(coroutine_t *coro, void *value) { if (NULL == coro) { return NULL; } coro->data = value; (void) swapcontext(&coro->caller, &coro->context); return coro->data; } void *coroutine_yield(coroutine_t *coro, void *value) { if (NULL == coro) { return NULL; } coro->data = value; (void) swapcontext(&coro->context, &coro->caller); return coro->data; }