diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | Makefile | 54 | ||||
| m--------- | deps/covsrv | 0 | ||||
| -rw-r--r-- | include/conts/conts.h | 12 | ||||
| -rw-r--r-- | include/conts/map.h | 278 | ||||
| -rw-r--r-- | include/conts/sptree.h | 56 | ||||
| -rw-r--r-- | include/conts/spvec.h | 232 | ||||
| -rw-r--r-- | include/conts/vec.h | 88 | ||||
| -rwxr-xr-x | scripts/coverage | 34 | ||||
| -rwxr-xr-x | scripts/run-test | 40 | ||||
| -rw-r--r-- | tests/map.c | 60 | ||||
| -rw-r--r-- | tests/sptree.c | 68 | ||||
| -rw-r--r-- | tests/spvec.c | 84 | ||||
| -rw-r--r-- | tests/test.h | 16 | ||||
| -rw-r--r-- | tests/vec.c | 73 |
16 files changed, 905 insertions, 195 deletions
@@ -1 +1,3 @@ build +reports +coverage diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c4f7f20 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/covsrv"] + path = deps/covsrv + url = https://metanimi.dy.fi/cgit/covsrv @@ -1,36 +1,66 @@ -CFLAGS = -g -Wall -Wextra -check: check-vec check-sptree check-map +CFLAGS = -g -Wall -Wextra -O2 +check: check-vec check-spvec check-sptree check-map + +# see scripts/coverage for coverage testing +TESTITER = 1000 +BENCHITER = 10000000 check-vec: mkdir -p build - $(CC) $(CFLAGS) -Iinclude tests/vec.c -o build/vec - valgrind -q --error-exitcode=1 ./build/vec + $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \ + -Iinclude -Ideps/covsrv/include \ + deps/covsrv/src/client.c tests/vec.c -o build/vec + ./scripts/run-test ./build/vec + +check-spvec: + mkdir -p build + $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \ + -Iinclude -Ideps/covsrv/include \ + deps/covsrv/src/client.c tests/spvec.c -o build/spvec + ./scripts/run-test ./build/spvec check-sptree: mkdir -p build - $(CC) $(CFLAGS) -Iinclude tests/sptree.c -o build/sptree - valgrind -q --error-exitcode=1 ./build/sptree + $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \ + -Iinclude -Ideps/covsrv/include \ + deps/covsrv/src/client.c tests/sptree.c -o build/sptree + ./scripts/run-test ./build/sptree check-map: mkdir -p build - $(CC) $(CFLAGS) -Iinclude tests/map.c -o build/map - valgrind -q --error-exitcode=1 ./build/map + $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \ + -Iinclude -Ideps/covsrv/include \ + deps/covsrv/src/client.c tests/map.c -o build/map + ./scripts/run-test ./build/map -bench: bench-vec bench-sptree bench-map +bench: bench-vec bench-spvec bench-sptree bench-map bench-vec: mkdir -p build - $(CC) $(CFLAGS) -O2 -Iinclude tests/vec.c -o build/vec_opt + $(CC) $(CFLAGS) -DITER=$(BENCHITER) \ + -Iinclude -Ideps/covsrv/include \ + tests/vec.c -o build/vec_opt time ./build/vec_opt 2> build/vec_bench.txt +bench-spvec: + mkdir -p build + $(CC) $(CFLAGS) -DITER=$(BENCHITER)\ + -Iinclude -Ideps/covsrv/include \ + tests/spvec.c -o build/spvec_opt + time ./build/spvec_opt 2> build/spvec_bench.txt + bench-sptree: mkdir -p build - $(CC) $(CFLAGS) -O2 -Iinclude tests/sptree.c -o build/sptree_opt + $(CC) $(CFLAGS) -DITER=$(BENCHITER) \ + -Iinclude -Ideps/covsrv/include \ + tests/sptree.c -o build/sptree_opt time ./build/sptree_opt 2> build/sptree_bench.txt bench-map: mkdir -p build - $(CC) $(CFLAGS) -O2 -Iinclude tests/map.c -o build/map_opt + $(CC) $(CFLAGS) -DITER=$(BENCHITER) \ + -Iinclude -Ideps/covsrv/include \ + tests/map.c -o build/map_opt time ./build/map_opt 2> build/map_bench.txt clean: diff --git a/deps/covsrv b/deps/covsrv new file mode 160000 +Subproject 5ff642c98760e0aec4de6b334a187cb9b4484aa diff --git a/include/conts/conts.h b/include/conts/conts.h index d175d30..7f30873 100644 --- a/include/conts/conts.h +++ b/include/conts/conts.h @@ -7,16 +7,8 @@ #define CONTAINER_OF(ptr, type, member) \ (type *)((char *)(ptr) - offsetof(type, member)) -#if __STDC_VERSION__ >= 202311UL -# define CONTS_AUTO auto -#elif defined(__GNUC__) -# define CONTS_AUTO __auto_type -#else -# warning "iteration won't work with this compiler" -#endif - #define foreach(name, i, s)\ - for (CONTS_AUTO i = CONTS_JOIN(name, begin)(s);\ + for (CONTS_JOIN(name, iter) i = CONTS_JOIN(name, begin)(s);\ !CONTS_JOIN(name, end)(s, i);\ - i = CONTS_JOIN(name, next)(i)) + i = CONTS_JOIN(name, next)(s, i)) #endif /* CONTS_H */ diff --git a/include/conts/map.h b/include/conts/map.h index 6be0694..2060658 100644 --- a/include/conts/map.h +++ b/include/conts/map.h @@ -18,11 +18,21 @@ #error "Need map name" #endif -#include <stddef.h> -#include <stdlib.h> -#include <stdbool.h> +#ifndef MAP_MALLOC +#define MAP_MALLOC malloc +#endif -#include "conts.h" +#ifndef MAP_CALLOC +#define MAP_CALLOC calloc +#endif + +#ifndef MAP_REALLOC +#define MAP_REALLOC realloc +#endif + +#ifndef MAP_FREE +#define MAP_FREE free +#endif #define MAP(a) CONTS_JOIN(MAP_NAME, a) @@ -34,6 +44,13 @@ #ifndef CONTS_MAP_H #define CONTS_MAP_H +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "conts.h" + static inline size_t conts_map_generic_hash(const char *s, size_t l) { /* djb2 */ @@ -47,9 +64,11 @@ static inline size_t conts_map_generic_hash(const char *s, size_t l) #define CONTS_MAP_NO_HASH(a) (a) #define CONTS_MAP_STR_HASH(a) conts_map_generic_hash(a, strlen(a)) -/* fast modulo pow2 */ -#define CONTS_BUCKET_IDX(hash, pow2, bucket) \ - ((hash) & (((pow2) << bucket) - 1)) +enum conts_map_state { + CONTS_MAP_FREE = 0, + CONTS_MAP_USED = 1, + CONTS_MAP_GRAVE = 2 +}; #endif /* CONTS_MAP_H */ @@ -63,116 +82,140 @@ struct MAP_TUPLE { MAP_TYPE data; }; +#define MAP_SPVEC(x) CONTS_JOIN(MAP(spvec), x) +#define MAP_ITER MAP(iter) + +typedef struct { + size_t i; + struct MAP_TUPLE *t; +} MAP_ITER; + struct MAP_NODE { - struct MAP_BUCKET *bucket; - size_t hash; + uint32_t used : 2; + uint32_t hash : 30; struct MAP_TUPLE t; }; -struct MAP_BUCKET { - size_t size; /* bucket size */ - struct MAP_BUCKET *next; - struct MAP_NODE nodes[]; -}; +/* use spvec as internal buffer representation */ +#define SPVEC_TYPE struct MAP_NODE +#define SPVEC_NAME MAP(spvec) + +#define SPVEC_MALLOC MAP_MALLOC +#define SPVEC_CALLOC MAP_CALLOC +#define SPVEC_REALLOC MAP_REALLOC +#define SPVEC_FREE MAP_FREE + +#include "spvec.h" struct MAP_ROOT { - size_t len; /* number of items */ - size_t count; /* bucket count */ - size_t pow2; /* how many nodes in smallest bucket */ - struct MAP_BUCKET **buckets; + size_t n; /* number of items */ + struct MAP(spvec) buf; }; static inline struct MAP_ROOT MAP(create)(size_t count) { - size_t pow2 = 1; - while (pow2 < count) - pow2 <<= 1; - + /* ignore count for now */ + (void)count; return (struct MAP_ROOT){ - .len = 0, - .count = 0, - .pow2 = pow2, - .buckets = NULL + .n = 0, + .buf = MAP_SPVEC(create)() }; } static inline void MAP(destroy)(struct MAP_ROOT *root) { - for (size_t i = 0; i < root->count; ++i) - free(root->buckets[i]); - - free(root->buckets); + MAP_SPVEC(destroy)(&root->buf); } -static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE data) +static inline MAP_TYPE *MAP(find_hash)(struct MAP_ROOT *root, MAP_KEY key, uint32_t hash) { - size_t hash = MAP_HASH(key); - /* look through buckets in order */ - for (size_t b = 0; b < root->count; ++b) { - struct MAP_BUCKET *bucket = root->buckets[b]; - size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, b); - - struct MAP_NODE *node = &bucket->nodes[idx]; - /* free to use this slot */ - if (!node->bucket) { - node->bucket = bucket; - node->hash = hash; - node->t.data = data; - node->t.key = key; - root->len++; - return &node->t.data; - } - /* there already exists a node like this */ - if (node->hash == hash && MAP_CMP(node->t.key, key) == 0) - return &node->t.data; + for (size_t s = MAP_SPVEC(len)(&root->buf); s >= conts_spvec_size(0); s /= 2) { + bool base = s == conts_spvec_size(0); + size_t range = base ? s : s / 2; + size_t offst = base ? 0 : s / 2; + + /* linear probing for now */ + for (size_t i = 0; i < range; ++i) { + assert(__builtin_popcountll(range) == 1); + size_t idx = (hash + i) & (range - 1); + + struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, idx + offst); + + /* unused node means chain must not exist in this range */ + if (n->used == CONTS_MAP_FREE) + break; + + /* not the node we're looking for but we should + * keep following the chain*/ + if (n->hash != hash) + continue; + + /* found a match */ + if (MAP_CMP(n->t.key, key) == 0) + return &n->t.data; + } } - /* no bucket available, create new one */ - size_t size = root->pow2 << root->count; - size_t bytes = sizeof(struct MAP_BUCKET) + sizeof(struct MAP_NODE) * size; - struct MAP_BUCKET *bucket = calloc(1, bytes); - bucket->size = size; - - size_t buckets_bytes = sizeof(struct MAP_BUCKET *) * (root->count + 1); - root->buckets = realloc(root->buckets, buckets_bytes); - if (root->count != 0) - root->buckets[root->count - 1]->next = bucket; - - root->buckets[root->count] = bucket; - - /* populate node */ - size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, root->count); - struct MAP_NODE *node = &bucket->nodes[idx]; - node->bucket = bucket; - node->hash = hash; - node->t.key = key; - node->t.data = data; - root->count++; - root->len++; - return &node->t.data; + return NULL; } static inline MAP_TYPE *MAP(find)(struct MAP_ROOT *root, MAP_KEY key) { - if (root->len == 0) - return NULL; + uint32_t hash = MAP_HASH(key) & 0x3fffffff; + return MAP(find_hash)(root, key, hash); +} - size_t hash = MAP_HASH(key); - for (size_t b = 0; b < root->count; ++b) { - struct MAP_BUCKET *bucket = root->buckets[b]; - size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, b); +static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE data) +{ + /* mask top bit away to get 31 bit hash value as used by the nodes */ + uint32_t hash = MAP_HASH(key) & 0x3fffffff; + MAP_TYPE *exists = MAP(find_hash)(root, key, hash); + if (exists) + return exists; + + /* initialize */ + if (root->n == 0) { + if (!MAP_SPVEC(reserve)(&root->buf, conts_spvec_size(0))) + return NULL; + } - struct MAP_NODE *node = &bucket->nodes[idx]; - if (node->hash != hash) - continue; + /* grow as needed */ + size_t s = MAP_SPVEC(len)(&root->buf); + if (2 * root->n >= s) { + s *= 2; + if (!MAP_SPVEC(reserve)(&root->buf, s)) + return NULL; - if (MAP_CMP(node->t.key, key) != 0) + /* reserve should zero out all memory so no need to initialize + * any elements */ + } + + /* there must always be at least one free spot */ + root->n++; + bool base = s == conts_spvec_size(0); + size_t range = base ? s : s / 2; + size_t offst = base ? 0 : s / 2; + + /* linear probing for now */ + for (size_t i = 0; i < range; ++i) { + assert(__builtin_popcountll(range) == 1); + size_t idx = (hash + i) & (range - 1); + + struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, idx + offst); + if (n->used == CONTS_MAP_USED) continue; - return &node->t.data; + /* grave or free, either way we're taking it */ + n->used = CONTS_MAP_USED; + n->hash = hash; + n->t.key = key; + n->t.data = data; + return &n->t.data; } + /** @todo use __builtin_unreachable or C attributes? */ + assert(false && "should be unreachable"); return NULL; } @@ -180,8 +223,8 @@ static inline void MAP(remove_found)(struct MAP_ROOT *root, MAP_TYPE *data) { struct MAP_TUPLE *tuple = CONTAINER_OF(data, struct MAP_TUPLE, data); struct MAP_NODE *node = CONTAINER_OF(tuple, struct MAP_NODE, t); - node->bucket = NULL; - root->len--; + node->used = CONTS_MAP_GRAVE; + root->n--; } static inline void MAP(remove)(struct MAP_ROOT *root, MAP_KEY key) @@ -193,47 +236,52 @@ static inline void MAP(remove)(struct MAP_ROOT *root, MAP_KEY key) MAP(remove_found)(root, found); } -static inline struct MAP_TUPLE *MAP(find_next)(struct MAP_BUCKET *bucket, struct MAP_TUPLE *tuple) +static inline MAP_ITER MAP(next)(struct MAP_ROOT *root, MAP_ITER t) { - struct MAP_NODE *node = CONTAINER_OF(tuple, struct MAP_NODE, t); - size_t idx = node - bucket->nodes; - for (; idx < bucket->size; ++idx) { - struct MAP_NODE *candidate = &bucket->nodes[idx]; - if (candidate->bucket) - return &candidate->t; + for (size_t i = t.i + 1; i < MAP_SPVEC(len)(&root->buf); ++i) { + struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, i); + if (n->used == CONTS_MAP_USED) { + return (MAP_ITER){ + .i = i, + .t = &n->t + }; + } } - struct MAP_BUCKET *next = bucket->next; - if (!next) - return NULL; - - return MAP(find_next)(next, &next->nodes[0].t); + /* no more elements to search through */ + return (MAP_ITER){ + .i = MAP_SPVEC(len)(&root->buf), + .t = NULL + }; } -static inline struct MAP_TUPLE *MAP(begin)(struct MAP_ROOT *root) +static inline MAP_ITER MAP(begin)(struct MAP_ROOT *root) { - if (root->len == 0) - return NULL; + if (root->n == 0) { + return (MAP_ITER){ + .i = MAP_SPVEC(len)(&root->buf), + .t = NULL + }; + } - struct MAP_BUCKET *bucket = root->buckets[0]; - return MAP(find_next)(bucket, &bucket->nodes[0].t); -} + struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, 0); + assert(n); -static inline struct MAP_TUPLE *MAP(next)(struct MAP_TUPLE *t) -{ - struct MAP_NODE *node = CONTAINER_OF(t, struct MAP_NODE, t); - return MAP(find_next)(node->bucket, &(node + 1)->t); + MAP_ITER t = {.i = 0, .t = &n->t}; + if (n->used == CONTS_MAP_USED) + return t; + + return MAP(next)(root, t); } -static inline bool MAP(end)(struct MAP_ROOT *root, struct MAP_TUPLE *t) +static inline bool MAP(end)(struct MAP_ROOT *root, MAP_ITER t) { - (void)root; - return t == NULL; + return t.i >= MAP_SPVEC(len)(&root->buf); } static inline size_t MAP(len)(struct MAP_ROOT *root) { - return root->len; + return root->n; } #undef MAP @@ -246,3 +294,9 @@ static inline size_t MAP(len)(struct MAP_ROOT *root) #undef MAP_CMP #undef MAP_HASH #undef MAP_NAME +#undef MAP_ITER + +#undef MAP_MALLOC +#undef MAP_CALLOC +#undef MAP_REALLOC +#undef MAP_FREE diff --git a/include/conts/sptree.h b/include/conts/sptree.h index 0e2c7b1..765dffc 100644 --- a/include/conts/sptree.h +++ b/include/conts/sptree.h @@ -1,9 +1,3 @@ -#include <stdint.h> -#include <assert.h> -#include <stdlib.h> -#include <stddef.h> -#include <stdbool.h> - #ifndef SPTREE_TYPE #error "Need sptree type" #endif @@ -16,15 +10,37 @@ #error "Need sptree name" #endif -#include "conts.h" +#ifndef SPTREE_MALLOC +#define SPTREE_MALLOC malloc +#endif + +#ifndef SPTREE_CALLOC +#define SPTREE_CALLOC calloc +#endif + +#ifndef SPTREE_REALLOC +#define SPTREE_REALLOC realloc +#endif + +#ifndef SPTREE_FREE +#define SPTREE_FREE free +#endif #define SPTREE(a) CONTS_JOIN(SPTREE_NAME, a) #define SPNODE SPTREE(node) #define SPROOT SPTREE_NAME -#ifndef SPTREE_H -#define SPTREE_H +#ifndef CONTS_SPTREE_H +#define CONTS_SPTREE_H + +#include <stdint.h> +#include <assert.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdbool.h> + +#include "conts.h" #define sp_left(n) ((n)->left) #define sp_right(n) ((n)->right) @@ -32,7 +48,7 @@ #define sp_lparen(n) (sp_left(n)->parent) #define sp_rparen(n) (sp_right(n)->parent) -#endif /* SPTREE_H */ +#endif /* CONTS_SPTREE_H */ struct SPNODE { int_fast16_t hint; @@ -45,6 +61,8 @@ struct SPROOT { struct SPNODE *root; }; +typedef SPTREE_TYPE *SPTREE(iter); + static inline struct SPROOT SPTREE(create)() { return (struct SPROOT){.n = 0, .root = NULL}; @@ -79,8 +97,9 @@ static inline SPTREE_TYPE *SPTREE(begin)(struct SPROOT *s) return &SPTREE(first)(s->root)->data; } -static inline SPTREE_TYPE *SPTREE(next)(SPTREE_TYPE *prev) +static inline SPTREE_TYPE *SPTREE(next)(struct SPROOT *s, SPTREE_TYPE *prev) { + (void)s; struct SPNODE *n = CONTAINER_OF(prev, struct SPNODE, data); if (sp_right(n)) { n = sp_right(n); @@ -101,6 +120,7 @@ static inline SPTREE_TYPE *SPTREE(next)(SPTREE_TYPE *prev) n = p; } + /* god damn it, stop ruining my coverage you bastard */ return NULL; } @@ -218,7 +238,7 @@ static inline SPTREE_TYPE *SPTREE(insert)(struct SPROOT *s, SPTREE_TYPE data) { if (!s->root) { assert(s->n == 0); - struct SPNODE *new = malloc(sizeof(struct SPNODE)); + struct SPNODE *new = SPTREE_MALLOC(sizeof(struct SPNODE)); if (!new) return NULL; @@ -253,7 +273,7 @@ static inline SPTREE_TYPE *SPTREE(insert)(struct SPROOT *s, SPTREE_TYPE data) return &n->data; } - struct SPNODE *new = malloc(sizeof(struct SPNODE)); + struct SPNODE *new = SPTREE_MALLOC(sizeof(struct SPNODE)); if (!new) return NULL; @@ -401,7 +421,7 @@ static inline void SPTREE(free_found)(struct SPROOT *s, SPTREE_TYPE *found) { (void)s; /* unused */ struct SPNODE *del = CONTAINER_OF(found, struct SPNODE, data); - free(del); + SPTREE_FREE(del); } static inline void SPTREE(remove)(struct SPROOT *s, SPTREE_TYPE data) @@ -424,9 +444,11 @@ static inline void SPTREE(destroy)(struct SPROOT *s) } } -#undef SPTREE -#undef SPNODE -#undef SPROOT #undef SPTREE_TYPE #undef SPTREE_NAME #undef SPTREE_CMP + +#undef SPTREE_MALLOC +#undef SPTREE_CALLOC +#undef SPTREE_REALLOC +#undef SPTREE_FREE diff --git a/include/conts/spvec.h b/include/conts/spvec.h new file mode 100644 index 0000000..92105b3 --- /dev/null +++ b/include/conts/spvec.h @@ -0,0 +1,232 @@ +#ifndef SPVEC_TYPE +#error "Need vector type" +#endif + +#ifndef SPVEC_NAME +#error "Need vector name" +#endif + +#ifndef SPVEC_MALLOC +#define SPVEC_MALLOC malloc +#endif + +#ifndef SPVEC_CALLOC +#define SPVEC_CALLOC calloc +#endif + +#ifndef SPVEC_REALLOC +#define SPVEC_REALLOC realloc +#endif + +#ifndef SPVEC_FREE +#define SPVEC_FREE free +#endif + +#ifndef CONTS_SPVEC_H +#define CONTS_SPVEC_H + +/* common part */ + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#include "conts.h" + +static inline size_t conts_spvec_bucket(size_t i) +{ + if (i < 64) + return 0; + + /** @todo fallback for __builtin_clzll, C23 has stdc_leading_zeroes + * but might want to go for a simple loop for maximum compatibility? */ + return (8 * sizeof(unsigned long long)) - __builtin_clzll(i) - 6; +} + +static inline size_t conts_spvec_size(size_t b) +{ + /* all buckets are pow2 in size, skipping the lowest 64 */ + return 1 << (b + 6); +} + +static inline size_t conts_spvec_elemnt(size_t b, size_t i) +{ + if (b == 0) + return i; + + /* the adjusted index within a bucket is just the size of all previous + * buckets subtracted away, and since each bucket is twice the size of + * all previous buckets, just check the size of the previous bucket for + * the index */ + return i - conts_spvec_size(b - 1); +} + +#endif /* SPVEC_H */ + +#define SPVEC(a) CONTS_JOIN(SPVEC_NAME, a) + +#define SPVEC_STRUCT SPVEC_NAME +struct SPVEC_STRUCT { + size_t n; + /* smallest bucket is of size 64, so ignore 2^6 */ + SPVEC_TYPE *(buckets[sizeof(size_t) * 8 - 6]); +}; + +/* unfortunately I couldn't really think of a better way to implement foreach, + * as it's fairly slow to calculate the index from a pointer to an element and a + * the foreach wrapper can't really be modified to use separate element pointer + * and iterator, they have to be combined. Sorry :( */ +#define SPVEC_ITER SPVEC(iter) + +typedef struct { + size_t i; + SPVEC_TYPE *v; +} SPVEC_ITER; + + +static inline struct SPVEC_STRUCT SPVEC(create)() +{ + /** @todo a bit of a heavy structure to pass by value */ + struct SPVEC_STRUCT s; + memset(&s, 0, sizeof(s)); + return s; +} + +static inline SPVEC_TYPE *SPVEC(reserve)(struct SPVEC_STRUCT *v, size_t n) +{ + if (v->n >= n) { + v->n = n; + return &v->buckets[0][0]; + } + + size_t b = conts_spvec_bucket(n); + for (size_t i = 0; i <= b; ++i) { + if (v->buckets[i]) + continue; + + v->buckets[i] = SPVEC_CALLOC( + conts_spvec_size(i == 0 ? 0 : b - 1), + sizeof(SPVEC_TYPE) + ); + + if (!v->buckets[i]) + return NULL; + } + + v->n = n; + return &v->buckets[0][0]; +} + +static inline size_t SPVEC(len)(struct SPVEC_STRUCT *v) +{ + return v->n; +} + +static inline SPVEC_TYPE *SPVEC(at)(struct SPVEC_STRUCT *v, size_t i) +{ + assert(i < v->n && "out of vector bounds"); + size_t b = conts_spvec_bucket(i); + size_t e = conts_spvec_elemnt(b, i); + return &v->buckets[b][e]; +} + +static inline SPVEC_TYPE *SPVEC(back)(struct SPVEC_STRUCT *v) +{ + assert(v->n); + return SPVEC(at)(v, v->n - 1); +} + +static inline SPVEC_TYPE *SPVEC(pop)(struct SPVEC_STRUCT *v) +{ + assert(v->n && "attempting to pop empty vector"); + v->n--; + return SPVEC(at)(v, v->n); +} + +static inline SPVEC_TYPE *SPVEC(append)(struct SPVEC_STRUCT *v, SPVEC_TYPE n) +{ + /* don't update our size until after we've run reserve */ + size_t ns = v->n + 1; + if (!v->buckets[conts_spvec_bucket(ns)]) { + /* try to allocate new bucket */ + if (!SPVEC(reserve)(v, ns)) + return NULL; + } + + /* reserve might've already written our new size so this is kind of + * duplicate work, but the code is a bit more readable like this */ + v->n = ns; + + SPVEC_TYPE *p = SPVEC(at)(v, v->n - 1); + *p = n; + return p; +} + +static inline void SPVEC(remove)(struct SPVEC_STRUCT *v, size_t i) +{ + assert(v->n > i); + + /* very slow :( */ + for (size_t e = i; e < v->n - 1; ++e) + *SPVEC(at)(v, e) = *SPVEC(at)(v, e + 1); + + v->n--; +} + +static inline void SPVEC(reset)(struct SPVEC_STRUCT *v) +{ + v->n = 0; +} + +static inline void SPVEC(destroy)(struct SPVEC_STRUCT *v) { + for (size_t i = 0; i < 8 * sizeof(size_t) - 6; ++i) + SPVEC_FREE(v->buckets[i]); +} + +static inline void SPVEC(shrink)(struct SPVEC_STRUCT *v, size_t n) +{ + assert(v->n >= n); + v->n = n; +} + +static inline SPVEC_ITER SPVEC(begin)(struct SPVEC_STRUCT *v) +{ + if (v->n == 0) { + return (SPVEC_ITER) { + .i = 0, + .v = NULL + }; + } + + return (SPVEC_ITER){ + .i = 0, + .v = SPVEC(at)(v, 0) + }; +} + +static inline bool SPVEC(end)(struct SPVEC_STRUCT *v, SPVEC_ITER i) +{ + (void)v; + return i.v == NULL; +} + +static inline SPVEC_ITER SPVEC(next)(struct SPVEC_STRUCT *v, SPVEC_ITER i) +{ + return (SPVEC_ITER){ + .i = i.i + 1, + .v = i.i + 1 == v->n ? NULL : SPVEC(at)(v, i.i + 1) + }; +} + +#undef SPVEC +#undef SPVEC_TYPE +#undef SPVEC_NAME +#undef SPVEC_STRUCT +#undef SPVEC_ITER + +#undef SPVEC_MALLOC +#undef SPVEC_CALLOC +#undef SPVEC_REALLOC +#undef SPVEC_FREE diff --git a/include/conts/vec.h b/include/conts/vec.h index 19fd18f..2e93805 100644 --- a/include/conts/vec.h +++ b/include/conts/vec.h @@ -6,13 +6,35 @@ #error "Need vector name" #endif +#ifndef VEC_MALLOC +#define VEC_MALLOC malloc +#endif + +#ifndef VEC_CALLOC +#define VEC_CALLOC calloc +#endif + +#ifndef VEC_REALLOC +#define VEC_REALLOC realloc +#endif + +#ifndef VEC_FREE +#define VEC_FREE free +#endif + +#ifndef CONTS_VEC_H +#define CONTS_VEC_H + #include <stdbool.h> +#include <stdint.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include "conts.h" +#endif /* CONTS_VEC_H */ + #define VEC(a) CONTS_JOIN(VEC_NAME, a) #define VEC_STRUCT VEC_NAME @@ -22,23 +44,19 @@ struct VEC_STRUCT { VEC_TYPE *buf; }; +typedef VEC_TYPE *VEC(iter); + static inline struct VEC_STRUCT VEC(create)(size_t reserve) { - if (reserve == 0) - return (struct VEC_STRUCT) {.n = 0, .s = 0, .buf = NULL}; - + /* first alloc doubles size of s, so divide by two to get wanted + * reservation size. Add 1 for odd cases. */ return (struct VEC_STRUCT) { .n = 0, - .s = reserve, - .buf = malloc(reserve * sizeof(VEC_TYPE)), + .s = (reserve + 1) / 2, + .buf = NULL, }; } -static inline bool VEC(uninit)(struct VEC_STRUCT *v) -{ - return v->buf == NULL; -} - static inline size_t VEC(len)(struct VEC_STRUCT *v) { return v->n; @@ -63,15 +81,22 @@ static inline VEC_TYPE *VEC(pop)(struct VEC_STRUCT *v) return &v->buf[v->n]; } -static inline void VEC(append)(struct VEC_STRUCT *v, VEC_TYPE n) +static inline VEC_TYPE *VEC(append)(struct VEC_STRUCT *v, VEC_TYPE n) { v->n++; - if (v->n >= v->s) { + if (v->n >= v->s || !v->buf) { v->s = v->s == 0 ? 1 : 2 * v->s; - v->buf = realloc(v->buf, v->s * sizeof(VEC_TYPE)); + VEC_TYPE *new_buf = VEC_REALLOC(v->buf, v->s * sizeof(VEC_TYPE)); + if (!new_buf) { + v->n--; + return NULL; + } + + v->buf = new_buf; } v->buf[v->n - 1] = n; + return &v->buf[v->n - 1]; } static inline void VEC(reset)(struct VEC_STRUCT *v) @@ -80,28 +105,35 @@ static inline void VEC(reset)(struct VEC_STRUCT *v) } static inline void VEC(destroy)(struct VEC_STRUCT *v) { - free(v->buf); + VEC_FREE(v->buf); } typedef int (*VEC(comp_t))(VEC_TYPE *a, VEC_TYPE *b); static inline void VEC(sort)(struct VEC_STRUCT *v, VEC(comp_t) comp) { - qsort(v->buf, v->n, sizeof(VEC_TYPE), (__compar_fn_t)comp); + qsort(v->buf, v->n, sizeof(VEC_TYPE), + (int(*)(const void *, const void *))comp); } -static inline void VEC(reserve)(struct VEC_STRUCT *v, size_t n) +static inline VEC_TYPE *VEC(reserve)(struct VEC_STRUCT *v, size_t n) { - if (v->n >= n) - return; + if (v->s >= n) { + v->n = n; + return v->buf; + } - v->n = n; - if (v->s >= v->n) - return; + size_t s = v->s; + while (s <= n) + s = s == 0 ? 1 : 2 * s; - while (v->s < v->n) - v->s = v->s == 0 ? 1 : 2 * v->s; + VEC_TYPE *new_buf = VEC_REALLOC(v->buf, s * sizeof(VEC_TYPE)); + if (!new_buf) + return NULL; - v->buf = realloc(v->buf, v->s * sizeof(VEC_TYPE)); + v->n = n; + v->s = s; + v->buf = new_buf; + return v->buf; } static inline void VEC(shrink)(struct VEC_STRUCT *v, size_t n) @@ -128,8 +160,9 @@ static inline bool VEC(end)(struct VEC_STRUCT *v, VEC_TYPE *i) return &v->buf[v->n] == i; } -static inline VEC_TYPE *VEC(next)(VEC_TYPE *i) +static inline VEC_TYPE *VEC(next)(struct VEC_STRUCT *v, VEC_TYPE *i) { + (void)v; return i + 1; } @@ -137,3 +170,8 @@ static inline VEC_TYPE *VEC(next)(VEC_TYPE *i) #undef VEC_TYPE #undef VEC_NAME #undef VEC_STRUCT + +#undef VEC_MALLOC +#undef VEC_CALLOC +#undef VEC_REALLOC +#undef VEC_FREE diff --git a/scripts/coverage b/scripts/coverage new file mode 100755 index 0000000..6afc069 --- /dev/null +++ b/scripts/coverage @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu + +# build covsrv binary +make -C deps/covsrv + +# not super fantastic but most likely good enough +export COVSRV_SOCKET=$(mktemp -u) + +# server program should always be killed at the end of a test run +cleanup() { + ./deps/covsrv/build/covsrv quit +} + +# kill server program even if user interrupted us or something else exceptional +# happened +trap interrupt INT HUP TERM QUIT EXIT +interrupt () { + cleanup + exit 1 +} + +# start coverage server, should create a unix socket at COVSRV_SOCKET that test +# programs can connect to +./deps/covsrv/build/covsrv & + +# run tests, pass any flags like -j to make +make COVERAGEFLAGS="--coverage -DCOVERAGE=1" check "$@" + +mkdir -p coverage +lcov --capture --directory . --out coverage/covsrv.info +genhtml coverage/covsrv.info --out coverage + +exit 0 diff --git a/scripts/run-test b/scripts/run-test new file mode 100755 index 0000000..8f09c45 --- /dev/null +++ b/scripts/run-test @@ -0,0 +1,40 @@ +#!/bin/sh + +TEST="$1" +NAME=$(basename "$TEST") + +mkdir -p "reports/$NAME" + +try_vg() { + if which valgrind > /dev/null 2>&1; then + valgrind -q --leak-check=full --error-exitcode=1 "$1" + else + eval "$1" + fi +} + +I=0 +while :; do + # if no error happened, consider it a pass + if try_vg "./${TEST}" > "reports/$NAME/run-$I" 2>&1 + then + echo "$NAME: PASSED" + exit 0 + fi + + if grep 'loss record' "reports/$NAME/run-$I" >/dev/null; then + echo "$NAME: MEM FAILED" + cat "reports/$NAME/run-$I" + exit 1 + fi + + # an error occured, was it handled properly? + if grep 'COVSRV: EXIT' "reports/$NAME/run-$I" >/dev/null; then + I=$((I+1)) + continue + fi + + echo "$NAME: FAILED" + cat "reports/$NAME/run-$I" + exit 1 +done diff --git a/tests/map.c b/tests/map.c index 6c42787..9af0b01 100644 --- a/tests/map.c +++ b/tests/map.c @@ -1,37 +1,79 @@ #include <assert.h> #include <stdio.h> +#include "test.h" +/* required defs */ #define MAP_KEY int #define MAP_TYPE int -#define MAP_HASH(a) CONTS_MAP_NO_HASH(a) +#define MAP_HASH(a) ints_generic_hash(&(a)) #define MAP_CMP(a, b) ((a) - (b)) #define MAP_NAME ints + +/* optional defs */ +#define MAP_MALLOC mallocc +#define MAP_CALLOC callocc +#define MAP_REALLOC reallocc +#define MAP_FREE free + #include <conts/map.h> int main() { +#if defined(COVERAGE) + assert(!covsrv_init()); + atexit(covsrv_destroy); +#endif + /* heuristic, but if we know how many elements we'll need, we should * give it to the create function. */ - struct ints ints = ints_create(0); - for (int i = 0; i < 1000000; ++i) { - ints_insert(&ints, i, i); + struct ints ints = ints_create(10); + + /* check that trying to search for something in an empty map doesn*t + * crash */ + assert(ints_find(&ints, 0) == NULL); + + /* similarly, iterating an empty map doesn't do anything */ + foreach(ints, iter, &ints) { + assert(false && "iterating empty map"); } - assert(ints_len(&ints) == 1000000); - for (int i = 0; i < 1000000; ++i) { + for (int i = 0; i < ITER; ++i) { + if (!ints_insert(&ints, i, i)) { + fprintf(stderr, "failed inserting %d\n", i); + ints_destroy(&ints); + return -1; + } + } + assert(ints_len(&ints) == ITER); + + + for (int i = 0; i < ITER; ++i) { int *v = ints_find(&ints, i); assert(v && *v == i); } + /* check that trying to find something that doesn't exist doesn't crash */ + assert(ints_find(&ints, 123456789) == NULL); + + /* check that trying to remove something that doesn't exist is a noop */ + size_t len = ints_len(&ints); + ints_remove(&ints, 123456789); + assert(ints_len(&ints) == len); + + /* check that trying to insert 0 again gets us the existing 0 */ + int *orig = ints_find(&ints, 0); + int *new = ints_insert(&ints, 0, 0); + assert(orig == new); + size_t count = 0; foreach(ints, iter, &ints) { - assert(iter->key == iter->data); + assert(iter.t->key == iter.t->data); count++; } - assert(count == 1000000); + assert(count == ITER); - for (int i = 0; i < 1000000; ++i) { + for (int i = 0; i < ITER; ++i) { ints_remove(&ints, i); } diff --git a/tests/sptree.c b/tests/sptree.c index b8d1e5a..3499d61 100644 --- a/tests/sptree.c +++ b/tests/sptree.c @@ -1,24 +1,57 @@ #include <assert.h> +#include <stdlib.h> #include <stdio.h> +#include "test.h" +/* required defs */ #define SPTREE_TYPE int #define SPTREE_CMP(a, b) ((b) - (a)) #define SPTREE_NAME ints + +/* optional defs */ +#define SPTREE_MALLOC mallocc +#define SPTREE_CALLOC callocc +#define SPTREE_REALLOC reallocc +#define SPTREE_FREE free + #include <conts/sptree.h> +#define RANDITER (ITER / 100) + +int inserted[RANDITER]; + int main() { +#if defined(COVERAGE) + assert(!covsrv_init()); + atexit(covsrv_destroy); +#endif + struct ints ints = ints_create(); - for (int i = 0; i < 1000000; ++i) { - ints_insert(&ints, i); + /* check that iterating an empty tree doesn't do anything */ + foreach(ints, iter, &ints) { + assert(false && "iterating empty tree"); } - assert(ints_len(&ints) == 1000000); - for (int i = 0; i < 1000000; ++i) { + for (int i = 0; i < ITER; ++i) { + if (!ints_insert(&ints, i)) { + fprintf(stderr, "failed inserting %d\n", i); + ints_destroy(&ints); + return -1; + } + } + assert(ints_len(&ints) == ITER); + + for (int i = 0; i < ITER; ++i) { int *v = ints_find(&ints, i); assert(v && *v == i); } + /* check that inserting duplicate returns the original */ + int *orig = ints_find(&ints, 0); + ints_insert(&ints, 0); + assert(ints_find(&ints, 0) == orig); + int i = 0; foreach(ints, iter, &ints) { /* since my trees are ordered, this must hold, although you @@ -28,10 +61,35 @@ int main() i++; } - for (int i = 0; i < 1000000; ++i) { + for (int i = 0; i < ITER; ++i) { ints_remove(&ints, i); } assert(ints_len(&ints) == 0); + + /* check that removing nonexistant item (or empty tree) doesn't crash */ + ints_remove(&ints, 0); + + /* insert random integers to hopefully exercise the code a bit more */ + srand(0); + + for (int i = 0; i < RANDITER; ++i) { + inserted[i] = rand(); + + /* covsrv shouldn't fail anymore */ + assert(ints_insert(&ints, inserted[i])); + } + + for (int i = 0; i < RANDITER; ++i) { + int *v = ints_find(&ints, inserted[i]); + assert(v && *v == inserted[i]); + } + + for (int i = 0; i < RANDITER; ++i) { + ints_remove(&ints, inserted[i]); + } + + assert(ints_len(&ints) == 0); + ints_destroy(&ints); } diff --git a/tests/spvec.c b/tests/spvec.c new file mode 100644 index 0000000..b169884 --- /dev/null +++ b/tests/spvec.c @@ -0,0 +1,84 @@ +#include <stdio.h> +#include <assert.h> +#include "test.h" + +/* required defs */ +#define SPVEC_TYPE int +#define SPVEC_NAME ints + +/* optional defs */ +#define SPVEC_MALLOC mallocc +#define SPVEC_CALLOC callocc +#define SPVEC_REALLOC reallocc +#define SPVEC_FREE free + +#include <conts/spvec.h> + +int main() +{ +#if defined(COVERAGE) + assert(!covsrv_init()); + atexit(covsrv_destroy); +#endif + struct ints ints = ints_create(); + foreach(ints, iter, &ints) { + assert(false && "iterating empty spvec"); + } + + /* ensure stability before we do anything else */ + assert(ints_reserve(&ints, 1)); + int *p = ints_at(&ints, 0); + + /* should allocate at least a few extra buckets */ + assert(ints_reserve(&ints, 256)); + assert(p == ints_at(&ints, 0)); + + /* test out resetting as well while we're at it*/ + assert(ints_len(&ints) == 256); + ints_reset(&ints); + assert(ints_len(&ints) == 0); + + for (int i = 0; i < ITER; ++i) { + if (!ints_append(&ints, i)) { + fprintf(stderr, "failed appending %d to vec\n", i); + ints_destroy(&ints); + return -1; + } + } + assert(ints_len(&ints) == ITER); + + for (int i = 0; i < ITER; ++i) { + int *v = ints_at(&ints, i); + assert(v && *v == i); + } + + int i = 0; + foreach(ints, iter, &ints) { + assert(iter.v && *iter.v == i); + i++; + } + + /* TEN million !!1! */ + if (!ints_reserve(&ints, 10 * ITER)) { + fprintf(stderr, "failed reserving vec\n"); + ints_destroy(&ints); + return -1; + } + + /* set size back to keep test runtime reasonable + * (is shrink necessary when we already have reserve? I + * guess it shows intention a bit better and just asserts instead of + * maybe fails?) */ + ints_shrink(&ints, ITER); + + /* so the above is equivalent to */ + assert(ints_reserve(&ints, ITER)); + assert(ints_len(&ints) == ITER); + + for (int i = ITER - 1; i >= 0; --i) { + ints_remove(&ints, i); + } + assert(ints_len(&ints) == 0); + + ints_destroy(&ints); +} diff --git a/tests/test.h b/tests/test.h new file mode 100644 index 0000000..ea50db0 --- /dev/null +++ b/tests/test.h @@ -0,0 +1,16 @@ +#ifndef TEST_H +#define TEST_H + +#include <covsrv/covsrv.h> + +#define cover_ptr(name, ...) ({covsrv_die() ? NULL : name (__VA_ARGS__);}) + +#define mallocc(...) cover_ptr(malloc, __VA_ARGS__) +#define callocc(...) cover_ptr(calloc, __VA_ARGS__) +#define reallocc(...) cover_ptr(realloc, __VA_ARGS__) + +#ifndef ITER +#define ITER 1000000 +#endif + +#endif /* TEST_H */ diff --git a/tests/vec.c b/tests/vec.c index a84096c..09a8deb 100644 --- a/tests/vec.c +++ b/tests/vec.c @@ -1,18 +1,47 @@ +#include <stdio.h> #include <assert.h> +#include "test.h" +/* required defs */ #define VEC_TYPE int #define VEC_NAME ints + +/* optional defs */ +#define VEC_MALLOC mallocc +#define VEC_CALLOC callocc +#define VEC_REALLOC reallocc +#define VEC_FREE free + #include <conts/vec.h> +/* used for sorting testing */ +static int int_comp(int *a, int *b) +{ + return *a - *b; +} + int main() { +#if defined(COVERAGE) + assert(!covsrv_init()); + atexit(covsrv_destroy); +#endif + struct ints ints = ints_create(0); - for (int i = 0; i < 1000000; ++i) { - ints_append(&ints, i); + foreach(ints, iter, &ints) { + assert(false && "iterating empty vec"); } - assert(ints_len(&ints) == 1000000); - for (int i = 0; i < 1000000; ++i) { + for (int i = 0; i < ITER; ++i) { + if (!ints_append(&ints, i)) { + fprintf(stderr, "failed appending %d to vec\n", i); + ints_destroy(&ints); + return -1; + } + } + assert(ints_len(&ints) == ITER); + + for (int i = 0; i < ITER; ++i) { int *v = ints_at(&ints, i); assert(v && *v == i); } @@ -23,10 +52,44 @@ int main() i++; } - for (int i = 1000000 - 1; i >= 0; --i) { + if (!ints_reserve(&ints, 10 * ITER)) { + fprintf(stderr, "failed reserving vec\n"); + ints_destroy(&ints); + return -1; + } + + /* set size back to keep test runtime reasonable + * (is shrink necessary when we already have reserve? I + * guess it shows intention a bit better and just asserts instead of + * maybe fails?) */ + ints_shrink(&ints, ITER); + + /* so the above is equivalent to */ + assert(ints_reserve(&ints, ITER)); + assert(ints_len(&ints) == ITER); + + for (int i = ITER - 1; i >= 0; --i) { ints_remove(&ints, i); } assert(ints_len(&ints) == 0); + /* test out resetting as well */ + assert(ints_reserve(&ints, 10)); + assert(ints_len(&ints) == 10); + + ints_reset(&ints); + assert(ints_len(&ints) == 0); + + /* try out sorting and special accesses */ + ints_append(&ints, 3); + ints_append(&ints, 2); + ints_append(&ints, 1); + + ints_sort(&ints, int_comp); + + assert(*ints_back(&ints) == 3); + assert(*ints_pop(&ints) == 3); + assert(*ints_back(&ints) == 2); + ints_destroy(&ints); } |
