From 784a33ca26b582480482e53faa1729ba71cd2649 Mon Sep 17 00:00:00 2001 From: "Justin R. Cutler" Date: Sat, 2 Apr 2016 21:10:40 -0400 Subject: [PATCH] Switch to a realloc_array()-style allocator (with default implementation) --- CMakeLists.txt | 4 +- src/coroutine.c | 67 ++++++++-------------------- src/default_allocator.c | 59 +++++++++++++++++++++++++ test/coroutine.c | 13 ++++-- threadless/allocator.h | 96 +++++++++++++++++++++++++++++++++++++++++ threadless/coroutine.h | 12 ++++-- 6 files changed, 192 insertions(+), 59 deletions(-) create mode 100644 src/default_allocator.c create mode 100644 threadless/allocator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a84f9cd..7d7f9fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,9 @@ set(CMAKE_C_FLAGS "-Wall -Wextra -pedantic -Werror -std=c99") add_library (coroutine src/coroutine.c) target_include_directories (coroutine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +add_library (allocator src/default_allocator.c) +target_include_directories (allocator PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_executable (test-coroutine test/coroutine.c) target_include_directories (coroutine PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries (test-coroutine LINK_PUBLIC coroutine) +target_link_libraries (test-coroutine LINK_PUBLIC allocator coroutine) diff --git a/src/coroutine.c b/src/coroutine.c index b18cf7d..9419112 100644 --- a/src/coroutine.c +++ b/src/coroutine.c @@ -13,15 +13,11 @@ /* errno, ENOMEM */ #include -/* calloc, free */ -#include +/* memset */ +#include -/* malloc, munmap, PROT_*, MAP_* */ -#include /* ucontext_t, getcontext, makecontext, swapcontext */ #include -/* sysconf, _SC_PAGE_SIZE */ -#include /* ... */ #include @@ -33,6 +29,7 @@ enum { struct coroutine { + allocator_t *allocator; ucontext_t context; ucontext_t caller; void *data; @@ -58,55 +55,28 @@ static void coroutine_entry_point(coroutine_t *c, } -static int stack_allocate(coroutine_t *coro, size_t stack_pages) +coroutine_t *coroutine_create(allocator_t *allocator, + coroutine_function_t *function, size_t stack_size) { - int error = -1; - size_t page_size = sysconf(_SC_PAGE_SIZE); - size_t length = page_size * stack_pages; - void *mapping = MAP_FAILED; + coroutine_t *coro = NULL; + size_t alloc_size = sizeof(*coro) + stack_size; - if ((length / page_size) == stack_pages) { - mapping = mmap(NULL, length, PROT_READ|PROT_WRITE, - MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - } else { + if (alloc_size < stack_size) { /* integer overflow */ errno = ENOMEM; + goto fail; } - 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)); + coro = allocator_malloc(allocator, alloc_size); if (NULL == coro) { goto fail; } + memset(coro, 0, alloc_size); + coro->allocator = allocator; (void) getcontext(&coro->context); - - if (stack_allocate(coro, stack_pages)) { - goto fail; - } + coro->context.uc_stack.ss_sp = coro + 1; + coro->context.uc_stack.ss_size = stack_size; makecontext(&coro->context, (void (*)(void)) coroutine_entry_point, 2, coro, function); @@ -114,9 +84,7 @@ coroutine_t *coroutine_create(coroutine_function_t *function, return coro; fail: - if (NULL != coro) { - coroutine_destroy(coro); - } + coroutine_destroy(coro); return NULL; } @@ -124,9 +92,8 @@ fail: void coroutine_destroy(coroutine_t *coro) { - if (NULL != coro) { - stack_free(coro); - free(coro); + if (NULL != coro && NULL != coro->allocator) { + allocator_free(coro->allocator, coro); } } diff --git a/src/default_allocator.c b/src/default_allocator.c new file mode 100644 index 0000000..ea8ad9e --- /dev/null +++ b/src/default_allocator.c @@ -0,0 +1,59 @@ +/* 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 + * default allocator implementation + * @author Justin R. Cutler + */ +/* errno, ENOMEM */ +#include +/* realloc */ +#include + +/* ... */ +#include + + +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t)-1) +#endif + +#define SQRT_SIZE_MAX_PLUS_1 ((size_t)1 << (sizeof(size_t) * 4)) + + +static void *default_allocate(allocator_t *allocator, void *ptr, size_t nmemb, + size_t size) +{ + /* ignore allocator */ + (void) allocator; + /* test for multiplication overflow */ + if (((nmemb >= SQRT_SIZE_MAX_PLUS_1) || (size >= SQRT_SIZE_MAX_PLUS_1)) && + (nmemb != 0) && ((SIZE_MAX / nmemb) < size)) { + /* overflow detected */ + errno = ENOMEM; + return NULL; + } + /* perform allocator action */ + return realloc(ptr, size * nmemb); +} + + +static void default_destory(allocator_t *allocator) +{ + /* ignore allocator */ + (void) allocator; +} + + +static allocator_t default_allocator = { + .allocate = default_allocate, + .destroy = default_destory, +}; + + +allocator_t *allocator_get_default(void) +{ + return &default_allocator; +} diff --git a/test/coroutine.c b/test/coroutine.c index f10026b..39b9c0d 100644 --- a/test/coroutine.c +++ b/test/coroutine.c @@ -13,6 +13,8 @@ /* EXIT_SUCCESS, EXIT_FAILURE */ #include +/* allocator_t, allocator_get_default, allocator_destroy */ +#include /* ... */ #include @@ -54,19 +56,19 @@ static void *output_coroutine(coroutine_t *coro, void *data) } -static int run(void) +static int run(allocator_t *allocator) { int error = -1; coroutine_t *fibonacci = NULL; coroutine_t *output = NULL; - fibonacci = coroutine_create(fibonacci_generator, 1); + fibonacci = coroutine_create(allocator, fibonacci_generator, 4096); if (NULL == fibonacci) { perror("coroutine_create"); goto fail; } - output = coroutine_create(output_coroutine, 1); + output = coroutine_create(allocator, output_coroutine, 4096); if (NULL == output) { perror("coroutine_create"); goto fail; @@ -91,11 +93,14 @@ fail: int main(int argc, char *argv[]) { int error; + allocator_t *allocator = allocator_get_default(); (void) argc; (void) argv; - error = run(); + error = run(allocator); + + allocator_destroy(allocator); return !error ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/threadless/allocator.h b/threadless/allocator.h new file mode 100644 index 0000000..60790e4 --- /dev/null +++ b/threadless/allocator.h @@ -0,0 +1,96 @@ +/* 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 + * allocator interface definition + * @author Justin R. Cutler + */ +#ifndef THREADLESS_ALLOCATOR_H +#define THREADLESS_ALLOCATOR_H + +/* size_t, NULL */ +#include + +/** Memory allocator instance */ +typedef struct allocator allocator_t; + +/** Memory allocator function + * @param[in,out] allocator allocator instance + * @param[in,out] ptr memory to reallocate/free (or @c NULL to allocate) + * @param nmemb number of elements to allocate (or 0 to free) + * @param size size of each element to allocate (or 0 to free) + * @retval non-NULL successful allocation/reallocation + * @retval NULL successful free (@p nmemb or @p size was 0) or error + * @note Upon detection of integer overflow, this function shall return + * @c NULL (like @c calloc(3) and FreeBSD's @c realloc_array(3)) + */ +typedef void *(allocator_function_t)(allocator_t *allocator, void *ptr, + size_t nmemb, size_t size); + +/** Memory allocator destructor function + * @param[in,out] allocator allocator instance + * @post @p allocator may no longer be used + */ +typedef void (allocator_destroy_function_t)(allocator_t *allocator); + +/** Memory allocator instance structure */ +struct allocator { + /** Memory allocator function */ + allocator_function_t *const allocate; + /** Memory allocator instance destructor */ + allocator_destroy_function_t *const destroy; +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Get default allocator instance + * @returns default allocator + */ +allocator_t *allocator_get_default(void); + +/** @c malloc() helper function + * @param[in,out] allocator allocator + * @param size size to allocate + * @retval Non-NULL allocated memory + * @retval NULL error + */ +static inline void *allocator_malloc(allocator_t *allocator, size_t size) +{ + return allocator->allocate(allocator, NULL, 1, size); +} + +/** @c realloc_array() helper function + * @copydetails allocator_function_t + */ +static inline void *allocator_realloc_array(allocator_t *allocator, void *ptr, + size_t nmemb, size_t size) +{ + return allocator->allocate(allocator, ptr, nmemb, size); +} + +/** @c free() helper function + * @param[in,out] allocator allocator + * @param ptr memory to free + */ +static inline void allocator_free(allocator_t *allocator, void *ptr) +{ + (void) allocator->allocate(allocator, ptr, 0, 0); +} + +/** Memory allocator destructor helper function + * @copydetails allocator_destroy_function_t + */ +static inline void allocator_destroy(allocator_t *allocator) +{ + allocator->destroy(allocator); +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* THREADLESS_ALLOCATOR_H */ diff --git a/threadless/coroutine.h b/threadless/coroutine.h index f1105b8..5f38b4e 100644 --- a/threadless/coroutine.h +++ b/threadless/coroutine.h @@ -15,6 +15,9 @@ /* NULL, size_t */ #include +/* allocator_t */ +#include + /** Opaque coroutine type */ typedef struct coroutine coroutine_t; @@ -31,15 +34,16 @@ 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 + * @param[in,out] allocator allocator to use to create/destroy memory + * @param function function to run in coroutine + * @param stack_size coroutine stack size * @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_t *coroutine_create(coroutine_function_t *function, - size_t stack_pages); +coroutine_t *coroutine_create(allocator_t *allocator, + coroutine_function_t *function, size_t stack_size); /** Destroy a coroutine * @param[in,out] coro coroutine to destroy