diff --git a/src/alloc.c b/src/alloc.c index 0703fea..e03b100 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -6,6 +6,7 @@ #include "apfl.h" #include "alloc.h" +#include "intrinsics.h" #define ALLOC_DEBUG 1 @@ -83,17 +84,6 @@ apfl_allocator_default(void) : libc_allocator; } -#ifdef __GNUC__ -# define MULTIPLY_CHECK_OVERFLOW(res, a, b) __builtin_mul_overflow((a), (b), &(res)) -#else -# define MULTIPLY_CHECK_OVERFLOW(res, a, b) \ - ( \ - (b) != 0 && (a) > SIZE_MAX / (b) \ - ? true \ - : ((res = (a) * (b)), false) \ - ) -#endif - void * apfl_alloc_realloc_array( struct apfl_allocator allocator, @@ -105,10 +95,10 @@ apfl_alloc_realloc_array( size_t oldsize; size_t newsize; - if (MULTIPLY_CHECK_OVERFLOW(oldsize, elem_size, oldlen)) { + if (MULTIPLY_CHECK_OVERFLOW_SIZE(oldsize, elem_size, oldlen)) { return NULL; } - if (MULTIPLY_CHECK_OVERFLOW(newsize, elem_size, newlen)) { + if (MULTIPLY_CHECK_OVERFLOW_SIZE(newsize, elem_size, newlen)) { return NULL; } diff --git a/src/intrinsics.h b/src/intrinsics.h new file mode 100644 index 0000000..ae1e82e --- /dev/null +++ b/src/intrinsics.h @@ -0,0 +1,29 @@ +#ifndef APFL_INTRINSICS_H +#define APFL_INTRINSICS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#if defined(__GNUC__) && !defined(APFL_NO_INTRINSICS) +#define APFL_USE_INTRINSICS +#endif + +#ifdef APFL_USE_INTRINSICS +# define MULTIPLY_CHECK_OVERFLOW_SIZE(res, a, b) __builtin_mul_overflow((a), (b), &(res)) +#else +# define MULTIPLY_CHECK_OVERFLOW_SIZE(res, a, b) \ + ( \ + (b) != 0 && (a) > SIZE_MAX / (b) \ + ? true \ + : ((res = (a) * (b)), false) \ + ) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/parser_test.c b/src/parser_test.c index 5df40cd..1405e98 100644 --- a/src/parser_test.c +++ b/src/parser_test.c @@ -172,23 +172,28 @@ enum listbuilder_cmd { #define LISTBUILDER_NAME(n) listbuilder_##n #define LISTBUILDER_ITEM_NAME(n) listbuilder_item_##n -#define LISTBUILDER_FILL_LIST(pt, itemtype, items, list_items, list_len, cap) \ - apfl_resizable_init((void **)&list_items, &list_len, &cap); \ - \ - for (items++; items->has_item; items++) { \ - if (!apfl_resizable_append( \ - pt->allocator, \ - sizeof(itemtype), \ - (void **)&list_items, \ - &list_len, \ - &cap, \ - &items->item, \ - 1 \ - )) { \ - test_fatalf(pt->t, "Failed appending"); \ - assert(false); \ - } \ - } \ +#define LISTBUILDER_FILL_LIST(pt, itemtype, items, list_items, list_len, cap) \ + apfl_resizable_init((void **)&list_items, &list_len, &cap); \ + \ + size_t len = 0; \ + for (items++; items->has_item; items++) { \ + len++; \ + } \ + \ + if (len > 0) { \ + if ((list_items = ALLOC_LIST(pt->allocator, itemtype, len)) == NULL) { \ + test_fatalf(pt->t, "Could not allocate memory"); \ + assert(false); \ + } \ + \ + cap = len; \ + list_len = len; \ + for (; len-- > 0; ) { \ + items--; \ + list_items[len] = items->item; \ + } \ + } \ + #define MKLISTBUILDER(name, listtype, itemtype, items_memb) \ struct LISTBUILDER_ITEM_NAME(name) { \ diff --git a/src/resizable.c b/src/resizable.c index a549ede..7bb642f 100644 --- a/src/resizable.c +++ b/src/resizable.c @@ -5,8 +5,13 @@ #include #include "alloc.h" +#include "intrinsics.h" #include "resizable.h" +#define DOUBLE_THRESHOLD 100 +#define LINEAR_STEP 10 +#define SHRINK_TOLERANCE 10 + void apfl_resizable_init(void **mem, size_t *len, size_t *cap) { @@ -15,6 +20,28 @@ apfl_resizable_init(void **mem, size_t *len, size_t *cap) *cap = 0; } +static void +shrink_cap( + struct apfl_allocator allocator, + size_t elem_size, + void **mem, + size_t *cap, + size_t new_min_cap +) { + assert(new_min_cap <= *cap); + + if (*cap - new_min_cap < SHRINK_TOLERANCE) { + return; + } + + void *new_mem = REALLOC_UNTYPED_ARRAY(allocator, *mem, elem_size, *cap, new_min_cap); + // TODO: Check other shrinking reallocs. Some wrongly assume this will never fail. + if (new_mem != NULL || new_min_cap == 0) { + *mem = new_mem; + *cap = new_min_cap; + } +} + bool apfl_resizable_resize( struct apfl_allocator allocator, @@ -24,9 +51,9 @@ apfl_resizable_resize( size_t *cap, size_t newlen ) { - // TODO: We're wasteful here by never actually shrinking the memory. if (newlen <= *len || newlen < *cap) { *len = newlen; + shrink_cap(allocator, elem_size, mem, cap, newlen); return true; } @@ -52,16 +79,37 @@ apfl_resizable_ensure_cap( return true; } - // TODO: We currently simply grow the memory to have space for exactly - // want_cap elements. It would probably be smarter to grow the memory - // a bit larger to reduce calls to realloc. - void *newmem = REALLOC_UNTYPED_ARRAY(allocator, *mem, elem_size, *cap, want_cap); + size_t new_cap = *cap || 1; + while ( + new_cap < DOUBLE_THRESHOLD + && new_cap < want_cap + ) { + size_t tmp; + if (MULTIPLY_CHECK_OVERFLOW_SIZE(tmp, new_cap, 2)) { + break; + } + new_cap = tmp; + } + + if (new_cap < want_cap) { + if (SIZE_MAX - LINEAR_STEP > want_cap) { + new_cap = want_cap + LINEAR_STEP; + } else { + // New size would overflow. Last chance is to use the want_cap + // without additional LINEAR_STEP + new_cap = want_cap; + } + } + + assert(new_cap >= want_cap); + + void *newmem = REALLOC_UNTYPED_ARRAY(allocator, *mem, elem_size, *cap, new_cap); if (newmem == NULL) { return false; } *mem = newmem; - *cap = want_cap; + *cap = new_cap; return true; }