From c03edbfdd3d61f15af2b872a9acac8fd3672f33e Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Mon, 25 Mar 2024 20:17:40 -0400 Subject: [PATCH] snapshot v2/p9; bugs --- Makefile | 2 + examples/slm.c | 46 ++- include/sl.h | 739 ++++++++++++++++++++++++++++--------------------- 3 files changed, 461 insertions(+), 326 deletions(-) diff --git a/Makefile b/Makefile index f642d2c..73f56c1 100644 --- a/Makefile +++ b/Makefile @@ -58,6 +58,8 @@ examples/%.o: examples/%.c examples/mls.c: examples/slm.c $(CC) $(CFLAGS) -C -E examples/slm.c | sed -e '1,7d' -e '/^# [0-9]* "/d' | clang-format > examples/mls.c +# $(CC) $(CFLAGS) -C -E examples/slm.c | sed -e '1,7d' -e 's/^#\( [0-9]* ".*$$\)/\/\* \1 \*\//' | clang-format > examples/mls.c + examples/mls: examples/mls.o $(STATIC_LIB) $(CC) $^ -o $@ $(CFLAGS) $(TEST_FLAGS) -pthread diff --git a/examples/slm.c b/examples/slm.c index 6e0ee00..4f4cff2 100644 --- a/examples/slm.c +++ b/examples/slm.c @@ -48,7 +48,13 @@ struct slex_node { SKIPLIST_DECL( slex, api_, entries, /* free node */ { free(node->value); }, - /* update node */ { node->value = new->value; }, + /* update node */ + { + //char *old = node->value; + node->value = new->value; + // In this case, don't free, we're just calling to_upper and using the same memory. + // free(old); + }, /* archive a node */ { new->key = node->key; @@ -183,8 +189,16 @@ shuffle(int *array, size_t n) } } -//define DOT -#define TEST_ARRAY_SIZE 8 +#define INTEGRITY +#ifdef INTEGRITY +#define INTEGRITY_CHK __skip_integrity_check_slex(list, 0) +#else +#define INTEGRITY_CKH ((void)0) +#endif + +//define SNAPSHOTS +#define DOT +#define TEST_ARRAY_SIZE 5 int main() @@ -192,6 +206,7 @@ main() int rc = 0; #ifdef DOT + size_t gen = 0; FILE *of = fopen("/tmp/slm.dot", "w"); if (!of) { perror("Failed to open file /tmp/slm.dot"); @@ -211,9 +226,11 @@ main() api_skip_dot_slex(of, list, gen++, sprintf_slex_node); #endif +#ifdef SNAPSHOTS /* Test creating a snapshot of an empty Skiplist */ size_t snp[TEST_ARRAY_SIZE * 2 + 10]; snp[0] = api_skip_snapshot_slex(list); +#endif /* Insert 7 key/value pairs into the list. */ int i; @@ -225,34 +242,57 @@ main() for (i = 0; i < asz; i++) { rc = api_skip_put_slex(list, array[i], to_lower(int_to_roman_numeral(array[i]))); + INTEGRITY_CHK; +#ifdef SNAPSHOTS snp[i + 1] = api_skip_snapshot_slex(list); +#endif + INTEGRITY_CHK; #ifdef DOT api_skip_dot_slex(of, list, gen++, sprintf_slex_node); + INTEGRITY_CHK; #endif char *v = api_skip_get_slex(list, array[i]); + INTEGRITY_CHK; api_skip_set_slex(list, array[i], to_upper(v)); + INTEGRITY_CHK; } +#ifdef SNAPSHOTS int r = i; +#endif api_skip_dup_slex(list, -1, int_to_roman_numeral(-1)); + INTEGRITY_CHK; #ifdef DOT api_skip_dot_slex(of, list, gen++, sprintf_slex_node); + INTEGRITY_CHK; #endif api_skip_dup_slex(list, 1, int_to_roman_numeral(1)); + INTEGRITY_CHK; #ifdef DOT api_skip_dot_slex(of, list, gen++, sprintf_slex_node); + INTEGRITY_CHK; #endif +#ifdef SNAPSHOTS snp[++i] = api_skip_snapshot_slex(list); + INTEGRITY_CHK; +#endif api_skip_del_slex(list, 0); + INTEGRITY_CHK; +#ifdef SNAPSHOTS snp[++i] = api_skip_snapshot_slex(list); + INTEGRITY_CHK; +#endif #ifdef DOT api_skip_dot_slex(of, list, gen++, sprintf_slex_node); + INTEGRITY_CHK; #endif +#ifdef SNAPSHOTS slex_t *restored = api_skip_restore_snapshot_slex(list, snp[r]); api_skip_dispose_snapshot_slex(list, r+1); api_skip_destroy_slex(restored); +#endif assert(strcmp(api_skip_pos_slex(list, SKIP_GTE, -(TEST_ARRAY_SIZE)-1)->value, int_to_roman_numeral(-(TEST_ARRAY_SIZE))) == 0); assert(strcmp(api_skip_pos_slex(list, SKIP_GTE, -2)->value, int_to_roman_numeral(-2)) == 0); diff --git a/include/sl.h b/include/sl.h index 945a67b..2e14169 100644 --- a/include/sl.h +++ b/include/sl.h @@ -94,7 +94,7 @@ * volume = {33}, number = {6}, issn = {0001-0782}, * url = {https://doi.org/10.1145/78973.78977}, * doi = {10.1145/78973.78977}, - * journal = {Commun. ACM}, month = {jun}, pages = {668–676}, numpages = {9}, + * journal = {Commun. ACM}, month = {jun}, pages = {668-676}, numpages = {9}, * keywords = {trees, searching, data structures}, * download = {https://www.cl.cam.ac.uk/teaching/2005/Algorithms/skiplists.pdf} * } @@ -241,17 +241,18 @@ fn_blk \ } -#define SKIPLIST_EACH_H2T(decl, prefix, list, elm, iter) \ +#define SKIPLIST_FOREACH_H2T(decl, prefix, list, elm, iter) \ for (iter = 0, (elm) = prefix##skip_head_##decl(list); (elm) != NULL; iter++, (elm) = prefix##skip_next_node_##decl(list, elm)) -#define SKIPLIST_EACH_T2H(decl, prefix, list, elm, iter) \ +/* Iterate from tail to head over the nodes. */ +#define SKIPLIST_FOREACH_T2H(decl, prefix, list, elm, iter) \ for (iter = prefix##skip_size_##decl(list), (elm) = prefix##skip_tailf_##decl(list); (elm) != NULL; \ iter--, (elm) = prefix##skip_prev_node_##decl(list, elm)) -#define __SKIP_NEXT_ENTRIES_T2B(field, elm) for (size_t lvl = elm->field.sle.height; lvl != (size_t)-1; lvl--) +#define __SKIP_ENTRIES_T2B(field, elm) for (size_t lvl = elm->field.sle.height; lvl != (size_t)-1; lvl--) #define __SKIP_IS_LAST_ENTRY_T2B() if (lvl == 0) -#define __SKIP_NEXT_ENTRIES_B2T(field, elm) for (size_t lvl = 0; lvl <= elm->field.sle.height; lvl++) +#define __SKIP_ENTRIES_B2T(field, elm) for (size_t lvl = 0; lvl < elm->field.sle.height + 1; lvl++) #define __SKIP_IS_LAST_ENTRY_B2T() if (lvl + 1 == elm->field.sle.height) /* @@ -439,6 +440,72 @@ return len; \ } \ \ + /* -- __skip_preserve_node_ \ + * Preserve given node in the slh_pres list. \ + * \ + * ALGORITHM: \ + * a) allocate a new node and copy this node into it without \ + * copying the user-supplied additional memory (that will \ + * happen iff an _update() is called on the node). \ + * b) determine if this is a duplicate, if so in (d) we set \ + * the sle.next[1] field to 0x1 as a reminder to re-insert \ + * this element as a duplicate in the restore function. \ + * c) zero out the next sle.prev/next[] pointers \ + * d) mark as duplicate, set sle.next[1] = 0x1 \ + * e) insert the node's copy into the slh_pres singly-linked \ + * list. \ + */ \ + static int __skip_preserve_node_##decl(decl##_t *slist, decl##_node_t *node) \ + { \ + int rc = 0; \ + size_t amt, i; \ + char *d; \ + const char *s; \ + decl##_node_t *dest, *is_dup; \ + \ + if (slist == NULL || node == NULL) \ + return 0; \ + \ + /* (a) alloc and copy */ \ + rc = prefix##skip_alloc_node_##decl(slist, &dest); \ + if (rc) \ + return rc; \ + is_dup = 0; \ + amt = sizeof(decl##_node_t); \ + s = (const char *)node; \ + d = (char *)dest; \ + for (i = 0; i < amt; i++) \ + d[i] = s[i]; \ + \ + /* (b) is this a duplicate? */ \ + if (__skip_key_compare_##decl(slist, dest, dest->field.sle.next[0], slist->aux) == 0 || \ + __skip_key_compare_##decl(slist, dest, dest->field.sle.prev, slist->aux) == 0) \ + is_dup = (decl##_node_t *)0x1; \ + \ + /* (c) zero out the next pointers */ \ + dest->field.sle.prev = NULL; \ + __SKIP_ENTRIES_B2T(field, dest) \ + { \ + dest->field.sle.next[lvl] = NULL; \ + } \ + \ + /* (d) set duplicate flag */ \ + dest->field.sle.next[0] = (decl##_node_t *)is_dup; \ + \ + /* (e) insert node into slh_pres list at head */ \ + if (slist->slh_pres == NULL) \ + slist->slh_pres = dest; \ + else { \ + /* The next[0] pointer forms the singly-linked list when \ + preserved. */ \ + dest->field.sle.next[0] = slist->slh_pres; \ + slist->slh_pres = dest; \ + } \ + rc++; \ + \ + return -rc; \ + } \ + \ /* -- __skip_preserve_ \ * Preserve nodes for snapshots if necessary. Returns > 0 are \ * errors, 0 means nothing preserved, negative number represents \ @@ -448,67 +515,36 @@ * Foreach node in `path`, if the generation in that element \ * is less than the current generation for the list then \ * that node must be preserved before being mutated for this \ - * insert. So we: \ - * a) allocate a new node and copy this node into it without \ - * copying the user-supplied additional memory (that will \ - * happen iff an _update() is called on the node). \ - * b) determine if this is a duplicate, if so we set the \ - * sle.next[1] field to 0x1 as a reminder to re-insert \ - * this element as a duplicate in the restore function. \ - * c) zero out the next sle.prev/next[] pointers \ - * d) insert the node's copy into the slh_pres singly-linked \ - * list. \ + * insert. So we preserve that node, see _preserve_node(). \ * Meanwhile, don't duplicate head and the tail nodes if they \ * are in the path[]. \ */ \ static int __skip_preserve_##decl(decl##_t *slist, decl##_node_t **path, size_t len) \ { \ - int rc = 0, n = 0; \ - size_t i = 0; \ - decl##_node_t *node; \ + int rc = 0, n; \ + size_t i; \ \ if (path == NULL) \ return 0; \ \ for (i = 0; i < len; i++) { \ - node = path[i]; \ - if (node == NULL) \ + /* This is the case when there was no match, path[0] will be NULL. */ \ + if (path[i] == NULL) \ continue; \ + /* No need to preserve the head or tail sentry nodes. */ \ + if (path[i] == slist->slh_head || path[i] == slist->slh_tail) \ + continue; \ + \ + /* When the generation of the node in the path is < the list's \ + current generation, we must preserve it. */ \ if (path[i]->field.sle.gen < slist->gen) { \ - if (path[i] == slist->slh_head || path[i] == slist->slh_tail) \ - continue; \ - decl##_node_t *src = node, *dest, *this; \ - rc = prefix##skip_alloc_node_##decl(slist, &dest); \ - if (rc) \ - return rc; \ - size_t amt = sizeof(decl##_node_t) + sizeof(struct __skiplist_##decl_idx) * slist->max; \ - char *d = NULL; \ - const char *s = (const char *)src; \ - d = (char *)dest; \ - for (size_t i = 0; i < amt; i++) \ - d[i] = s[i]; \ - \ - if (__skip_key_compare_##decl(slist, dest, dest->field.sle.next[0], slist->aux) == 0 || \ - __skip_key_compare_##decl(slist, dest, dest->field.sle.prev, slist->aux) == 0) \ - dest->field.sle.next[0] = (decl##_node_t *)0x1; \ - \ - this = dest; \ - this->field.sle.prev = NULL; \ - __SKIP_NEXT_ENTRIES_B2T(field, this) \ - { \ - this->field.sle.next[lvl] = NULL; \ - } \ - \ - if (slist->slh_pres == NULL) \ - slist->slh_pres = dest; \ - else { \ - dest->field.sle.next[0] = slist->slh_pres; \ - slist->slh_pres = dest; \ - } \ - n++; \ + n = __skip_preserve_node_##decl(slist, path[i]); \ + if (n > 0) \ + return n; \ + rc += n; \ } \ } \ - return -n; \ + return -rc; \ } \ \ /* -- __skip_insert_ */ \ @@ -516,48 +552,64 @@ { \ int rc = 0; \ static decl##_node_t apath[SKIPLIST_MAX_HEIGHT + 1]; \ - size_t i, len, level; \ + size_t i, len, cur_height, new_height; \ decl##_node_t *node, **path = (decl##_node_t **)&apath; \ \ if (slist == NULL || n == NULL) \ return ENOENT; \ \ - /* Allocate a buffer */ \ + /* Allocate a buffer, or use a static one. */ \ if (SKIPLIST_MAX_HEIGHT == 1) { \ path = malloc(sizeof(decl##_node_t *) * slist->max + 1); \ if (path == NULL) \ return ENOMEM; \ } \ \ + /* Find the node `n` and path to it of `n` steps, if matched it's in `path[0]` */ \ len = __skip_locate_##decl(slist, n, path); \ node = path[0]; \ if (len > 0) { \ if ((node != NULL) && (flags == 0)) { \ - /* Don't insert, duplicate flag not set. */ \ + /* Don't insert, duplicate if flag not set. */ \ return -1; \ } \ - level = __skip_toss_##decl(slist->max - 1); \ + /* Generation for snapshots */ \ n->field.sle.gen = slist->gen; \ - n->field.sle.height = level; \ + /* Coin toss to determine level of this new node [0, max) */ \ + cur_height = slist->slh_head->field.sle.height; \ + new_height = __skip_toss_##decl(slist->max - 1); \ + n->field.sle.height = new_height; \ + /* Trim the path to at most the new height for the new node. */ \ + if (new_height > cur_height) { \ + for (i = cur_height + 1; i <= new_height; i++) { \ + path[i + 1] = slist->slh_tail; \ + } \ + } \ + /* Snapshots preserve those nodes in the `path` that are will be \ + modified on insert. */ \ rc = __skip_preserve_##decl(slist, path, len); \ + /* > 0 is an error, 0 is no nodes were preserved, < 0 is -(rc) nodes \ + preserved. */ \ if (rc > 0) \ return rc; \ - for (i = slist->slh_head->field.sle.height + 1; i < n->field.sle.height + 1; i++) { \ - path[i + 1] = slist->slh_tail; \ - } \ - for (i = 0; i < n->field.sle.height + 1; i++) { \ + /* Adjust all forward pointers for each element in the path. */ \ + for (i = 0; i <= new_height; i++) { \ n->field.sle.next[i] = path[i + 1]->field.sle.next[i]; \ path[i + 1]->field.sle.next[i] = n; \ } \ + /* Adujust the previous pointers in the nodes. */ \ n->field.sle.prev = path[1]; \ n->field.sle.next[0]->field.sle.prev = n; \ + /* Account for insert at tail. */ \ if (n->field.sle.next[0] == slist->slh_tail) { \ slist->slh_tail->field.sle.prev = n; \ } \ - if (level > slist->slh_head->field.sle.height) { \ - slist->slh_head->field.sle.height = level; \ - slist->slh_tail->field.sle.height = level; \ + /* Adjust the head/tail boundary node heights if necessary. */ \ + if (new_height > cur_height) { \ + slist->slh_head->field.sle.height = new_height; \ + slist->slh_tail->field.sle.height = new_height; \ } \ + /* Increase our list length (aka. size, count, etc.) by one. */ \ slist->length++; \ \ if (SKIPLIST_MAX_HEIGHT == 1) \ @@ -792,14 +844,14 @@ { \ static decl##_node_t apath[SKIPLIST_MAX_HEIGHT + 1]; \ int cmp, np, rc = 0; \ - size_t len; \ + size_t len, i; \ const unsigned char *p1, *p2; \ decl##_node_t *node, *new = NULL, **path = (decl##_node_t **)&apath; \ \ if (slist == NULL || n == NULL) \ return -1; \ \ - /* Allocate a buffer */ \ + /* Allocate a buffer, or use a static one. */ \ if (SKIPLIST_MAX_HEIGHT == 1) { \ path = malloc(sizeof(decl##_node_t *) * slist->max + 1); \ if (path == NULL) \ @@ -819,26 +871,27 @@ return np; \ if (np < 0 && node->field.sle.gen < slist->gen) { \ /* < 0 is the negated amount of nodes in path[] that were \ - preserved. \ - \ \ - ALGORITHM: \ - At this point we've preserved a node but not the data \ - that we don't control in the entry (i.e. the "value" if \ - it's a pointer to some heap memory, we have copied the \ - pointer in _preserve() but not anything else). We purposly \ - delayed that allocation/copy to this point in time. \ - \ \ - So, we need to find the new node in slist->slh_pres, and \ - copy the data and then mark it. The only way to find that \ - node is to first use the comparison function and second \ - compare the bytes in the node that are not part of the entry \ - structure. */ \ + * preserved. \ + * \ + * ALGORITHM: \ + * At this point we've preserved a node but not the data \ + * that we don't control in the entry (i.e. the "value" if \ + * it's a pointer to some heap memory, we have copied the \ + * pointer in _preserve() but not anything else). We purposly \ + * delayed that allocation/copy to this point in time. \ + * \ + * So, we need to find the new node in slist->slh_pres, and \ + * copy the data and then mark it. The only way to find that \ + * node is to first use the comparison function and second \ + * compare the bytes in the node that are not part of the entry \ + * structure. \ + */ \ do { \ new = (new == NULL ? slist->slh_pres : new->field.sle.next[0]); \ cmp = __skip_key_compare_##decl(slist, node, new, slist->aux); \ p1 = (unsigned char *)((uintptr_t) new); \ p2 = (unsigned char *)((uintptr_t)node); \ - for (size_t i = 0; i < (sizeof(decl##_node_t) - sizeof(struct __skiplist_##decl_entry)); i++) { \ + for (i = 0; i < (sizeof(decl##_node_t) - sizeof(struct __skiplist_##decl_entry)); i++) { \ if (p1[i] != p2[i]) { \ cmp = p1[i] < p2[i] ? -1 : 1; \ break; \ @@ -846,11 +899,13 @@ } \ } while (cmp != 0); \ if (cmp != 0) \ - return ENOMEM; /* TODO */ \ - new->field.sle.prev = (decl##_node_t *)0x1; \ - archive_node_blk; \ - if (rc) \ - return rc; \ + return ENOMEM; /* TODO we didn't find the node, why? */ \ + if (new->field.sle.prev == NULL) { \ + new->field.sle.prev = (decl##_node_t *)0x1; \ + archive_node_blk; \ + if (rc) \ + return rc; \ + } \ } \ new = n; \ update_node_blk; \ @@ -900,7 +955,8 @@ } \ if (SKIPLIST_MAX_HEIGHT == 1) \ free(path); \ - /* if we didn't preserve any nodes we can free this one */ \ + \ + /* If we didn't preserve any nodes we can free this one. */ \ if (np == 0) \ free_node_blk; \ \ @@ -989,7 +1045,7 @@ decl##_t *prefix##skip_restore_snapshot_##decl(decl##_t *slist, unsigned gen) \ { \ size_t i; \ - decl##_node_t *node, *next, *prev; \ + decl##_node_t *node, *prev; \ \ if (slist == NULL) \ return NULL; \ @@ -1011,7 +1067,8 @@ * - the `node->field.sle.next[0]` forms a singly-linked list. \ */ \ \ - SKIPLIST_EACH_H2T(decl, prefix, slist, node, i) \ + /* (a) */ \ + SKIPLIST_FOREACH_H2T(decl, prefix, slist, node, i) \ { \ ((void)i); \ if (node->field.sle.gen > gen) \ @@ -1021,28 +1078,34 @@ prev = NULL; \ node = slist->slh_pres; \ while (node) { \ - next = node->field.sle.next[0]; \ + /* (b) */ \ if (node->field.sle.gen > gen) { \ - if (prev == NULL) \ - slist->slh_pres = next; \ - else \ - prev->field.sle.next[0] = next; \ - if (node->field.sle.next[0] == NULL) \ - prev->field.sle.next[0] = NULL; \ - else \ - prev->field.sle.next[0] = next; \ + /* remove node from slh_pres list */ \ + if (slist->slh_pres == node) \ + slist->slh_pres = node->field.sle.next[0]; \ + else { \ + if (node->field.sle.next[0] == NULL) \ + prev->field.sle.next[0] = NULL; \ + else \ + prev->field.sle.next[0] = node->field.sle.next[0]; \ + } \ + \ prefix##skip_free_node_##decl(node); \ } \ + \ + /* c */ \ prev = NULL; \ if (node->field.sle.gen == gen) { \ - if (prev == NULL) \ - slist->slh_pres = next; \ - else \ - prev->field.sle.next[0] = next; \ - if (node->field.sle.next[0] == NULL) \ - prev->field.sle.next[0] = NULL; \ - else \ - prev->field.sle.next[0] = next; \ + /* remove node from slh_pres list */ \ + if (slist->slh_pres == node) \ + slist->slh_pres = node->field.sle.next[0]; \ + else { \ + if (node->field.sle.next[0] == NULL) \ + prev->field.sle.next[0] = NULL; \ + else \ + prev->field.sle.next[0] = node->field.sle.next[0]; \ + } \ + \ node->field.sle.prev = NULL; \ if (node->field.sle.next[1] != 0) { \ node->field.sle.next[1] = NULL; \ @@ -1052,9 +1115,12 @@ } \ } \ prev = node; \ - node = next; \ + node = node->field.sle.next[0]; \ } \ + \ + /* (d) */ \ slist->max_gen = gen; \ + \ return slist; \ } \ \ @@ -1192,200 +1258,227 @@ return NULL; \ } -#define SKIPLIST_INTEGRITY_CHECK(decl, prefix, field) \ - /* -- __skip_integrity_failure_ */ \ - static void __attribute__((format(printf, 1, 2))) __skip_integrity_failure_##decl(const char *fmt, ...) \ - { \ - va_list args; \ - __skip_debugf(fmt, args); \ - } \ - \ - /* -- __skip_integrity_check_ */ \ - static int __skip_integrity_check_##decl(decl##_t *slist, int flags) \ - { \ - unsigned nth, n_err = 0; \ - decl##_node_t *node, *prev, *next; \ - struct __skiplist_##decl_idx *this; \ - \ - if (slist == NULL) { \ - __skip_integrity_failure_##decl("slist was NULL, nothing to check"); \ - n_err++; \ - return n_err; \ - } \ - \ - /* Check the Skiplist header (slh) */ \ - \ - if (slist->slh_head == NULL) { \ - __skip_integrity_failure_##decl("skiplist slh_head is NULL"); \ - n_err++; \ - return n_err; \ - } \ - \ - if (slist->slh_tail == NULL) { \ - __skip_integrity_failure_##decl("skiplist slh_tail is NULL"); \ - n_err++; \ - return n_err; \ - } \ - \ - if (slist->cmp == NULL) { \ - __skip_integrity_failure_##decl("skiplist comparison function (cmp) is NULL"); \ - n_err++; \ - return n_err; \ - } \ - \ - if (slist->max < 2) { \ - __skip_integrity_failure_##decl("skiplist max level must be 1 at minimum"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (slist->level >= slist->max) { \ - /* level is 0-based, max of 12 means level cannot be > 11 */ \ - __skip_integrity_failure_##decl("skiplist level in header was >= max"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (SKIPLIST_MAX_HEIGHT < 1) { \ - __skip_integrity_failure_##decl("SKIPLIST_MAX_HEIGHT cannot be less than 1"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (SKIPLIST_MAX_HEIGHT > 0 && slist->max > SKIPLIST_MAX_HEIGHT) { \ - __skip_integrity_failure_##decl("slist->max cannot be greater than SKIPLIST_MAX_HEIGHT"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - node = slist->slh_head; \ - for (nth = 0; nth < node->field.sle.height; nth++) { \ - if (node->field.sle.next[nth] == NULL) { \ - __skip_integrity_failure_##decl("the head's %u next node should not be NULL", nth); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - if (node->field.sle.next[nth] == slist->slh_tail) \ - break; \ - } \ - for (; nth < node->field.sle.height; nth++) { \ - if (node->field.sle.next[nth] == NULL) { \ - __skip_integrity_failure_##decl("the head's %u next node should not be NULL", nth); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - if (node->field.sle.next[nth] != slist->slh_tail) { \ - __skip_integrity_failure_##decl("after internal nodes, the head's %u next node should always be the tail", nth); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - } \ - \ - if (slist->length > 0 && slist->slh_tail->field.sle.prev == slist->slh_head) { \ - __skip_integrity_failure_##decl("slist->length is 0, but tail->prev == head, not an internal node"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - /* TODO: ensure that the entries structure is the first or last element \ - in a node so that snapshots work. */ \ - if (slist->length > 0 && slist->slh_tail->field.sle.prev == slist->slh_head) { \ - __skip_integrity_failure_##decl("slist->length is 0, but tail->prev == head, not an internal node"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - /* Validate the head node */ \ - \ - /* Validate the tail node */ \ - \ - /* Validate each node */ \ - nth = 0; \ - node = prefix##skip_head_##decl(slist); \ - while (node) { \ - this = &node->field.sle; \ - \ - if (this->next == NULL) { \ - __skip_integrity_failure_##decl("the %uth node's [%p] next field should never NULL", nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (this->prev == NULL) { \ - __skip_integrity_failure_##decl("the %u node [%p] prev field should never NULL", nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - uintptr_t a = (uintptr_t)this->next; \ - uintptr_t b = (intptr_t)((uintptr_t)node + sizeof(decl##_node_t)); \ - if (a != b) { \ - __skip_integrity_failure_##decl("the %uth node's [%p] next field isn't at the proper offset relative to the node", nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - next = this->next[0]; \ - prev = this->prev; \ - if (__skip_key_compare_##decl(slist, node, node, slist->aux) != 0) { \ - __skip_integrity_failure_##decl("the %uth node [%p] is not equal to itself", nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (__skip_key_compare_##decl(slist, node, prev, slist->aux) < 0) { \ - __skip_integrity_failure_##decl("the %uth node [%p] is not greater than the prev node [%p]", nth, (void *)node, (void *)prev); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (__skip_key_compare_##decl(slist, node, next, slist->aux) > 0) { \ - __skip_integrity_failure_##decl("the %uth node [%p] is not less than the next node [%p]", nth, (void *)node, (void *)next); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (__skip_key_compare_##decl(slist, prev, node, slist->aux) > 0) { \ - __skip_integrity_failure_##decl("the prev node [%p] is not less than the %uth node [%p]", (void *)prev, nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - if (__skip_key_compare_##decl(slist, next, node, slist->aux) < 0) { \ - __skip_integrity_failure_##decl("the next node [%p] is not greater than the %uth node [%p]", (void *)next, nth, (void *)node); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - node = prefix##skip_next_node_##decl(slist, node); \ - nth++; \ - } \ - \ - if (slist->length != nth) { \ - __skip_integrity_failure_##decl("slist->length doesn't match the count of nodes between the head and tail"); \ - n_err++; \ - if (flags) \ - return n_err; \ - } \ - \ - return 0; \ +#define SKIPLIST_INTEGRITY_CHECK(decl, prefix, field) \ + /* -- __skip_integrity_failure_ */ \ + static void __attribute__((format(printf, 1, 2))) __skip_integrity_failure_##decl(const char *fmt, ...) \ + { \ + va_list args; \ + __skip_debugf(fmt, args); \ + } \ + \ + /* -- __skip_integrity_check_ */ \ + static int __skip_integrity_check_##decl(decl##_t *slist, int flags) \ + { \ + unsigned long nth, n_err = 0; \ + decl##_node_t *node, *prev, *next; \ + struct __skiplist_##decl_idx *this; \ + \ + if (slist == NULL) { \ + __skip_integrity_failure_##decl("slist was NULL, nothing to check\n"); \ + n_err++; \ + return n_err; \ + } \ + \ + /* Check the Skiplist header (slh) */ \ + \ + if (slist->slh_head == NULL) { \ + __skip_integrity_failure_##decl("skiplist slh_head is NULL\n"); \ + n_err++; \ + return n_err; \ + } \ + \ + if (slist->slh_tail == NULL) { \ + __skip_integrity_failure_##decl("skiplist slh_tail is NULL\n"); \ + n_err++; \ + return n_err; \ + } \ + \ + if (slist->cmp == NULL) { \ + __skip_integrity_failure_##decl("skiplist comparison function (cmp) is NULL\n"); \ + n_err++; \ + return n_err; \ + } \ + \ + if (slist->max < 2) { \ + __skip_integrity_failure_##decl("skiplist max level must be 1 at minimum\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (slist->level >= slist->max) { \ + /* level is 0-based, max of 12 means level cannot be > 11 */ \ + __skip_integrity_failure_##decl("skiplist level in header was >= max\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (SKIPLIST_MAX_HEIGHT < 1) { \ + __skip_integrity_failure_##decl("SKIPLIST_MAX_HEIGHT cannot be less than 1\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (SKIPLIST_MAX_HEIGHT > 0 && slist->max > SKIPLIST_MAX_HEIGHT) { \ + __skip_integrity_failure_##decl("slist->max cannot be greater than SKIPLIST_MAX_HEIGHT\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + node = slist->slh_head; \ + for (nth = 0; nth < node->field.sle.height; nth++) { \ + if (node->field.sle.next[nth] == NULL) { \ + __skip_integrity_failure_##decl("the head's %lu next node should not be NULL", nth); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + if (node->field.sle.next[nth] == slist->slh_tail) \ + break; \ + } \ + for (; nth < node->field.sle.height; nth++) { \ + if (node->field.sle.next[nth] == NULL) { \ + __skip_integrity_failure_##decl("the head's %lu next node should not be NULL", nth); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + if (node->field.sle.next[nth] != slist->slh_tail) { \ + __skip_integrity_failure_##decl("after internal nodes, the head's %lu next node should always be the tail", nth); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + } \ + \ + if (slist->length > 0 && slist->slh_tail->field.sle.prev == slist->slh_head) { \ + __skip_integrity_failure_##decl("slist->length is 0, but tail->prev == head, not an internal node\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + /* TODO: ensure that the entries structure is the last element \ + in a node so that snapshots work. GSB */ \ + if (slist->length > 0 && slist->slh_tail->field.sle.prev == slist->slh_head) { \ + __skip_integrity_failure_##decl("slist->length is 0, but tail->prev == head, not an internal node\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + /* Validate the head node */ \ + \ + /* Validate the tail node */ \ + \ + /* Validate each node */ \ + nth = 0; \ + node = prefix##skip_head_##decl(slist); \ + while (node != slist->slh_tail && node != NULL) { \ + this = &node->field.sle; \ + \ + if (this->next == NULL) { \ + __skip_integrity_failure_##decl("the %luth node's [%p] next field should never NULL\n", nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (this->prev == NULL) { \ + __skip_integrity_failure_##decl("the %luth node [%p] prev field should never NULL\n", nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + /* TODO \ + size_t n; \ + for (n = 0; n < (this->height + 1); n++) { \ + if (this->next[n] == NULL) { \ + __skip_integrity_failure_##decl("the %luth node's next[%lu] should not be NULL\n", nth, n); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + if (this->next[n] == slist->slh_tail) \ + break; \ + } \ + for (size_t m = n; n < (this->height + 1); n++) { \ + if (this->next[n] == NULL) { \ + __skip_integrity_failure_##decl("after the %lunth the %luth node's next[%lu] should not be NULL\n", m, nth, n); \ + n_err++; \ + if (flags) \ + return n_err; \ + } else if (this->next[n] != slist->slh_tail) { \ + __skip_integrity_failure_##decl("after the %lunth the %luth node's next[%lu] should point to the tail\n", m, nth, n); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + } \ + */ \ + \ + decl##_node_t *a = (decl##_node_t *)(uintptr_t)this->next; \ + decl##_node_t *b = (decl##_node_t *)(intptr_t)((uintptr_t)node + sizeof(decl##_node_t)); \ + if (a != b) { \ + __skip_integrity_failure_##decl("the %luth node's [%p] next field isn't at the proper offset relative to the node\n", nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + next = this->next[0]; \ + prev = this->prev; \ + if (__skip_key_compare_##decl(slist, node, node, slist->aux) != 0) { \ + __skip_integrity_failure_##decl("the %luth node [%p] is not equal to itself\n", nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (__skip_key_compare_##decl(slist, node, prev, slist->aux) < 0) { \ + __skip_integrity_failure_##decl("the %luth node [%p] is not greater than the prev node [%p]\n", nth, (void *)node, (void *)prev); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (__skip_key_compare_##decl(slist, node, next, slist->aux) > 0) { \ + __skip_integrity_failure_##decl("the %luth node [%p] is not less than the next node [%p]\n", nth, (void *)node, (void *)next); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (__skip_key_compare_##decl(slist, prev, node, slist->aux) > 0) { \ + __skip_integrity_failure_##decl("the prev node [%p] is not less than the %luth node [%p]\n", (void *)prev, nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + if (__skip_key_compare_##decl(slist, next, node, slist->aux) < 0) { \ + __skip_integrity_failure_##decl("the next node [%p] is not greater than the %luth node [%p]\n", (void *)next, nth, (void *)node); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + node = prefix##skip_next_node_##decl(slist, node); \ + nth++; \ + } \ + \ + if (slist->length != nth) { \ + __skip_integrity_failure_##decl("slist->length doesn't match the count of nodes between the head and tail\n"); \ + n_err++; \ + if (flags) \ + return n_err; \ + } \ + \ + return 0; \ } #define SKIPLIST_KV_ACCESS(decl, prefix, key, ktype, value, vtype, qblk, rblk) \ @@ -1496,9 +1589,9 @@ static inline void __skip_dot_write_node_##decl(FILE *os, size_t nsg, decl##_node_t *node) \ { \ if (node) \ - fprintf(os, "\"node%zu %p\"", nsg, (void *)node); \ + fprintf(os, "\"node%lu %p\"", nsg, (void *)node); \ else \ - fprintf(os, "\"node%zu NULL\"", nsg); \ + fprintf(os, "\"node%lu NULL\"", nsg); \ } \ \ /* -- __skip_dot_node_ \ @@ -1513,11 +1606,11 @@ __skip_dot_write_node_##decl(os, nsg, node); \ fprintf(os, " [label = \""); \ fflush(os); \ - __SKIP_NEXT_ENTRIES_T2B(field, node) \ + __SKIP_ENTRIES_T2B(field, node) \ { \ next = (node->field.sle.next[lvl] == slist->slh_tail) ? NULL : node->field.sle.next[lvl]; \ width = __skip_dot_width_##decl(slist, node, next ? next : slist->slh_tail); \ - fprintf(os, " { %zu | ", lvl, width, lvl); \ + fprintf(os, " { %lu | ", lvl, width, lvl); \ if (next) \ fprintf(os, "%p } |", (void *)next); \ else \ @@ -1526,22 +1619,22 @@ } \ if (fn) { \ fn(node, buf); \ - fprintf(os, " \u219F %zu \u226B %s \"\n", node->field.sle.height + 1, buf); \ + fprintf(os, " \u219F %lu \u226B %s \"\n", node->field.sle.height + 1, buf); \ } else { \ - fprintf(os, " \u219F %zu \"\n", node->field.sle.height); \ + fprintf(os, " \u219F %lu \"\n", node->field.sle.height); \ } \ fprintf(os, "shape = \"record\"\n"); \ fprintf(os, "];\n"); \ fflush(os); \ \ /* Now edges */ \ - __SKIP_NEXT_ENTRIES_B2T(field, node) \ + __SKIP_ENTRIES_B2T(field, node) \ { \ next = (node->field.sle.next[lvl] == slist->slh_tail) ? NULL : node->field.sle.next[lvl]; \ __skip_dot_write_node_##decl(os, nsg, node); \ - fprintf(os, ":f%zu -> ", lvl); \ + fprintf(os, ":f%lu -> ", lvl); \ __skip_dot_write_node_##decl(os, nsg, next); \ - fprintf(os, ":w%zu [];\n", lvl); \ + fprintf(os, ":w%lu [];\n", lvl); \ fflush(os); \ } \ } \ @@ -1559,7 +1652,7 @@ */ \ fprintf(os, "node0 [shape=record, label = \""); \ for (i = 0; i < nsg; ++i) { \ - fprintf(os, " | ", i); \ + fprintf(os, " | ", i); \ } \ fprintf(os, "\", style=invis, width=0.01];\n"); \ \ @@ -1569,7 +1662,7 @@ * node0:f1 -> HeadNode1 [style=invis]; \ */ \ for (i = 0; i < nsg; ++i) { \ - fprintf(os, "node0:f%zu -> HeadNode%zu [style=invis];\n", i, i); \ + fprintf(os, "node0:f%lu -> HeadNode%lu [style=invis];\n", i, i); \ } \ nsg = 0; \ } \ @@ -1607,10 +1700,10 @@ fprintf(os, "node [fontsize = \"12\" shape = \"ellipse\"];\n"); \ fprintf(os, "edge [];\n\n"); \ } \ - fprintf(os, "subgraph cluster%zu {\n", nsg); \ + fprintf(os, "subgraph cluster%lu {\n", nsg); \ fprintf(os, "style=dashed\n"); \ - fprintf(os, "label=\"Skip list iteration %zu\"\n\n", nsg); \ - fprintf(os, "\"HeadNode%zu\" [\n", nsg); \ + fprintf(os, "label=\"Skip list iteration %lu\"\n\n", nsg); \ + fprintf(os, "\"HeadNode%lu\" [\n", nsg); \ fprintf(os, "label = \""); \ \ if (slist->slh_head->field.sle.height || slist->slh_head->field.sle.next[0] != slist->slh_tail) \ @@ -1619,11 +1712,11 @@ /* Write out the fields */ \ node = slist->slh_head; \ if (letitgo) { \ - __SKIP_NEXT_ENTRIES_T2B(field, node) \ + __SKIP_ENTRIES_T2B(field, node) \ { \ next = (node->field.sle.next[lvl] == slist->slh_tail) ? NULL : node->field.sle.next[lvl]; \ width = __skip_dot_width_##decl(slist, node, next ? next : slist->slh_tail); \ - fprintf(os, "{ %zu | ", width, lvl); \ + fprintf(os, "{ %lu | ", width, lvl); \ if (next) \ fprintf(os, "%p }", (void *)next); \ else \ @@ -1643,12 +1736,12 @@ node = slist->slh_head; \ if (letitgo) { \ node = slist->slh_head; \ - __SKIP_NEXT_ENTRIES_B2T(field, node) \ + __SKIP_ENTRIES_B2T(field, node) \ { \ next = (node->field.sle.next[lvl] == slist->slh_tail) ? NULL : node->field.sle.next[lvl]; \ - fprintf(os, "\"HeadNode%zu\":f%zu -> ", nsg, lvl); \ + fprintf(os, "\"HeadNode%lu\":f%lu -> ", nsg, lvl); \ __skip_dot_write_node_##decl(os, nsg, next); \ - fprintf(os, ":w%zu [];\n", lvl); \ + fprintf(os, ":w%lu [];\n", lvl); \ } \ fprintf(os, "\n"); \ } \ @@ -1657,7 +1750,7 @@ /* Now all nodes via level 0, if non-empty */ \ node = slist->slh_head; \ if (letitgo) { \ - SKIPLIST_EACH_H2T(decl, prefix, slist, next, i) \ + SKIPLIST_FOREACH_H2T(decl, prefix, slist, next, i) \ { \ ((void)i); \ __skip_dot_node_##decl(os, slist, next, nsg, fn); \ @@ -1672,10 +1765,10 @@ __skip_dot_write_node_##decl(os, nsg, NULL); \ fprintf(os, " [label = \""); \ node = slist->slh_tail; \ - size_t th = slist->slh_head->field.sle.height == slist->max ? slist->max : slist->slh_head->field.sle.height + 1; \ + size_t th = slist->slh_head->field.sle.height; \ for (size_t lvl = th; lvl != (size_t)-1; lvl--) { \ next = (node->field.sle.next[lvl] == slist->slh_tail) ? NULL : node->field.sle.next[lvl]; \ - fprintf(os, " NULL", lvl); \ + fprintf(os, " NULL", lvl); \ __SKIP_IS_LAST_ENTRY_T2B() continue; \ fprintf(os, " | "); \ } \