diff --git a/examples/ex_1.c b/examples/ex_1.c index 5fa1ae4..d84744a 100644 --- a/examples/ex_1.c +++ b/examples/ex_1.c @@ -158,7 +158,7 @@ main() assert(sparsemap_select(map, i) == i * 10); } - // split and move, aligned to MiniMap capacity + // split and move, aligned to chunk map capacity sparsemap_t _sm2, *sm2 = &_sm2; sparsemap_init(sm2, buffer2, sizeof(buffer2)); sparsemap_clear(sm2); diff --git a/examples/ex_2.c b/examples/ex_2.c index 97f1c82..6ae3689 100644 --- a/examples/ex_2.c +++ b/examples/ex_2.c @@ -31,6 +31,7 @@ main(void) // create the sparse bitmap sparsemap_t *map = sparsemap(buf, sizeof(uint8_t) * 1024); + map->resize = sparsemap_on_heap_resize_fn; // Set every other bit (pathologically worst case) to see what happens // when the map is full. @@ -42,7 +43,8 @@ main(void) } // On 1024 KiB of buffer with every other bit set the map holds 7744 bits // and then runs out of space. This next _set() call will fail/abort. - sparsemap_set(map, ++i, true); + i++; + sparsemap_set(map, i, true); assert(sparsemap_is_set(map, i) == true); return 0; } diff --git a/include/sparsemap.h b/include/sparsemap.h index 548aa3d..e5abba4 100644 --- a/include/sparsemap.h +++ b/include/sparsemap.h @@ -91,6 +91,22 @@ typedef struct sparsemap sparsemap_t; typedef uint32_t sm_idx_t; typedef uint64_t sm_bitvec_t; +typedef enum { + /* return code for set(): needs to grow this __sm_chunk_t */ + SM_NEEDS_TO_GROW = 1, + + /* return code for set(): needs to shrink this __sm_chunk_t */ + SM_NEEDS_TO_SHRINK = 2 +} sparsemap_adaptations_t; + +typedef struct sparsemap { + uint8_t *m_data; /* The serialized bitmap data */ + size_t m_data_size; /* The total size of m_data */ + size_t m_data_used; /* The used size of m_data */ + int (*resize)(struct sparsemap *, sparsemap_adaptations_t, size_t, size_t *); +} sparsemap_t; + + /* Allocate on a sparsemap_t on the heap and initialize it. */ sparsemap_t *sparsemap(uint8_t *data, size_t size); @@ -116,7 +132,7 @@ size_t sparsemap_get_capacity(sparsemap_t *map); bool sparsemap_is_set(sparsemap_t *map, size_t idx); /* Sets the bit at index |idx| to true or false, depending on |value|. */ -void sparsemap_set(sparsemap_t *map, size_t idx, bool value); +int sparsemap_set(sparsemap_t *map, size_t idx, bool value); /* Returns the offset of the very first bit. */ sm_idx_t sparsemap_get_start_offset(sparsemap_t *map); @@ -131,12 +147,21 @@ void sparsemap_scan(sparsemap_t *map, void (*scanner)(sm_idx_t[], size_t), size_ reduces the chunk map-count appropriately. */ void sparsemap_split(sparsemap_t *map, size_t sstart, sparsemap_t *other); +#if 0 // TODO +/* Sets/clears bits starting at |ssize| in other in |map| possibly invoking the resize function. */ +void sparsemap_combine(sparsemap_t *map, size_t sstart, sparsemap_t *other); +#endif + /* Returns the index of the n'th set bit; uses a 0-based index. */ size_t sparsemap_select(sparsemap_t *map, size_t n); /* Counts the set bits in the range [offset, idx]. */ size_t sparsemap_rank(sparsemap_t *map, size_t offset, size_t idx); -size_t sparsemap_span(sparsemap_t *map, size_t loc, size_t len); +/* Returns the 0-based index of a span of the first set bits of at least |len| starting after |offset|. */ +size_t sparsemap_span(sparsemap_t *map, size_t offset, size_t len); + +/* This isn't API, it's the default implementation for a resize function. */ +int sparsemap_on_heap_resize_fn(sparsemap_t *map, sparsemap_adaptations_t desire, size_t cur_size, size_t *new_size); #endif diff --git a/src/sparsemap.c b/src/sparsemap.c index eb7fd43..2e247cd 100644 --- a/src/sparsemap.c +++ b/src/sparsemap.c @@ -43,7 +43,7 @@ void __attribute__((format(printf, 4, 5))) __sm_diag_(const char *file, int line va_end(args); } #else -#define __sm_diag(file, line, func, format, ...) ((void)0) +#define __sm_diag(...) ((void)0) #endif #ifndef SPARSEMAP_ASSERT @@ -87,13 +87,7 @@ enum __SM_CHUNK_INFO { SM_FLAG_MASK = 3, /* return code for set(): ok, no further action required */ - SM_OK = 0, - - /* return code for set(): needs to grow this __sm_chunk_t */ - SM_NEEDS_TO_GROW = 1, - - /* return code for set(): needs to shrink this __sm_chunk_t */ - SM_NEEDS_TO_SHRINK = 2 + SM_OK = 0 }; #define SM_CHUNK_GET_FLAGS(from, at) (((from)) & ((sm_bitvec_t)SM_FLAG_MASK << ((at) * 2))) >> ((at) * 2) @@ -581,7 +575,7 @@ __sm_chunk_map_scan(__sm_chunk_t *map, sm_idx_t start, void (*scanner)(sm_idx_t[ /** * Returns the number of chunk maps. */ -static size_t +static inline size_t __sm_get_chunk_map_count(sparsemap_t *map) { return (*(uint32_t *)&map->m_data[0]); @@ -636,7 +630,7 @@ __sm_get_size_impl(sparsemap_t *map) /** * Returns the aligned offset (aligned to sm_bitvec_t capacity). */ -static sm_idx_t +static inline sm_idx_t __sm_get_aligned_offset(size_t idx) { const size_t capacity = SM_BITS_PER_VECTOR; @@ -660,11 +654,11 @@ __sm_get_chunk_map_offset(sparsemap_t *map, size_t idx) uint8_t *p = start; for (size_t i = 0; i < count - 1; i++) { - sm_idx_t start = *(sm_idx_t *)p; - __sm_assert(start == __sm_get_aligned_offset(start)); + sm_idx_t bytes = *(sm_idx_t *)p; + __sm_assert(bytes == __sm_get_aligned_offset(bytes)); __sm_chunk_t chunk; __sm_chunk_map_init(&chunk, p + sizeof(sm_idx_t)); - if (start >= idx || idx < start + __sm_chunk_map_get_capacity(&chunk)) { + if (bytes >= idx || idx < bytes + __sm_chunk_map_get_capacity(&chunk)) { break; } p += sizeof(sm_idx_t) + __sm_chunk_map_get_size(&chunk); @@ -676,7 +670,7 @@ __sm_get_chunk_map_offset(sparsemap_t *map, size_t idx) /** * Returns the aligned offset (aligned to __sm_chunk_t capacity). */ -static sm_idx_t +static inline sm_idx_t __sm_get_fully_aligned_offset(size_t idx) { const size_t capacity = SM_CHUNK_MAX_CAPACITY; @@ -686,7 +680,7 @@ __sm_get_fully_aligned_offset(size_t idx) /** * Sets the number of __sm_chunk_t's. */ -static void +static inline void __sm_set_chunk_map_count(sparsemap_t *map, size_t new_count) { *(uint32_t *)&map->m_data[0] = (uint32_t)new_count; @@ -702,22 +696,64 @@ __sm_append_data(sparsemap_t *map, uint8_t *buffer, size_t buffer_size) map->m_data_used += buffer_size; } +int +sparsemap_on_heap_resize_fn(sparsemap_t *map, sparsemap_adaptations_t desire, size_t cur_size, size_t *new_size) { + int rc = 0; + size_t nsz; + uint8_t *new, *formerly = map->m_data; + + if (desire == SM_NEEDS_TO_GROW) { + nsz = (cur_size * 2) * sizeof(uint8_t); + new = realloc(map->m_data, nsz); + if (!new) { + map->m_data = formerly; + rc = errno; + } else { + map->m_data = new; + *new_size = nsz; + rc = -(int)(nsz - cur_size); + } + } + + // TODO SM_NEEDS_TO_SHRINK + return rc; +} + /** * Inserts data somewhere in the middle of m_data. */ static int __sm_insert_data(sparsemap_t *map, size_t offset, uint8_t *buffer, size_t buffer_size) { - if (map->m_data_used + buffer_size > map->m_capacity) { - __sm_assert(!"buffer overflow"); - abort(); + int rc = 0; + size_t osz, nsz; + + if (map->m_data_used + buffer_size > map->m_data_size) { + /* attempt to grow the heap buffer */ + if (map->resize) { + osz = map->m_data_size; + rc = map->resize(map, SM_NEEDS_TO_GROW, osz, &nsz); + if (rc <= 0) { + sparsemap_set_data_size(map, nsz); + memset(map->m_data + osz, 0, nsz - osz); + return rc; + } + } + goto fail; } uint8_t *p = __sm_get_chunk_map_data(map, offset); memmove(p + buffer_size, p, map->m_data_used - offset); memcpy(p, buffer, buffer_size); map->m_data_used += buffer_size; - return 0; + + return rc; +fail:; + __sm_assert(!"buffer overflow"); +#ifdef DEBUG + abort(); +#endif + return rc; } /** @@ -733,9 +769,9 @@ __sm_remove_data(sparsemap_t *map, size_t offset, size_t gap_size) } /** - * Clears the whole buffer + * Clears the whole buffer. */ -void +inline void sparsemap_clear(sparsemap_t *map) { memset(map->m_data, 0, map->m_capacity); @@ -771,7 +807,7 @@ sparsemap_init(sparsemap_t *map, uint8_t *data, size_t size) /** * Opens an existing sparsemap at the specified buffer. */ -void +inline void sparsemap_open(sparsemap_t *map, uint8_t *data, size_t data_size) { map->m_data = data; @@ -785,7 +821,7 @@ sparsemap_open(sparsemap_t *map, uint8_t *data, size_t data_size) * TODO/NOTE: This is a dangerous operation because we cannot verify that * data_size is not exceeding the size of the underlying buffer. */ -void +inline void sparsemap_set_data_size(sparsemap_t *map, size_t data_size) { map->m_capacity = data_size; @@ -810,8 +846,8 @@ sparsemap_capacity_remaining(sparsemap_t *map) /** * Returns the size of the underlying byte array. */ -size_t -sparsemap_get_capacity(sparsemap_t *map) +inline size_t +sparsemap_get_range_size(sparsemap_t *map) { return (map->m_capacity); } @@ -851,9 +887,11 @@ sparsemap_is_set(sparsemap_t *map, size_t idx) /** * Sets the bit at index |idx| to true or false, depending on |value|. */ -void +int sparsemap_set(sparsemap_t *map, size_t idx, bool value) { + int rc = 0; + __sm_assert(sparsemap_get_size(map) >= SM_SIZEOF_OVERHEAD); /* Get the __sm_chunk_t which manages this index */ @@ -864,7 +902,7 @@ sparsemap_set(sparsemap_t *map, size_t idx, bool value) immediately; otherwise create an initial __sm_chunk_t. */ if (offset == -1) { if (value == false) { - return; + return 0; } uint8_t buf[sizeof(sm_idx_t) + sizeof(sm_bitvec_t) * 2] = { 0 }; @@ -890,7 +928,7 @@ sparsemap_set(sparsemap_t *map, size_t idx, bool value) if (idx < start) { if (value == false) { /* nothing to do */ - return; + return 0; } uint8_t buf[sizeof(sm_idx_t) + sizeof(sm_bitvec_t) * 2] = { 0 }; @@ -920,7 +958,7 @@ sparsemap_set(sparsemap_t *map, size_t idx, bool value) if (idx - start >= __sm_chunk_map_get_capacity(&chunk)) { if (value == false) { /* nothing to do */ - return; + return 0; } size_t size = __sm_chunk_map_get_size(&chunk); @@ -957,10 +995,17 @@ sparsemap_set(sparsemap_t *map, size_t idx, bool value) break; case SM_NEEDS_TO_GROW: if (!dont_grow) { - offset += (ssize_t)(sizeof(sm_idx_t) + position * sizeof(sm_bitvec_t)); - __sm_insert_data(map, offset, (uint8_t *)&fill, sizeof(sm_bitvec_t)); + offset += sizeof(sm_idx_t) + position * sizeof(sm_bitvec_t); + rc = __sm_insert_data(map, offset, (uint8_t *)&fill, sizeof(sm_bitvec_t)); + if (rc > 0) { + return rc; + } else if (rc < 0) { + __sm_diag("added %d bytes to the map", -(rc)); + return sparsemap_set(map, idx, false); + } } code = __sm_chunk_map_set(&chunk, idx - start, value, &position, &fill, true); + ((void)code); __sm_assert(code == SM_OK); break; case SM_NEEDS_TO_SHRINK: @@ -982,6 +1027,7 @@ sparsemap_set(sparsemap_t *map, size_t idx, bool value) break; } __sm_assert(sparsemap_get_size(map) >= SM_SIZEOF_OVERHEAD); + return rc; } /**