initial import

This commit is contained in:
Greg Burd 2024-03-07 15:53:58 -05:00
parent d49ffb2ca0
commit aec5d830a8
No known key found for this signature in database
6 changed files with 1422 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.a
*.so
src/*.o
src/example

39
Makefile Normal file
View file

@ -0,0 +1,39 @@
LDFLAGS = -pthread
CFLAGS = \
-g -D_GNU_SOURCE \
-I. -I./src -I./debug -I./include -I./examples -I./tests \
-fPIC \
CFLAGS += -Wall
CFLAGS += -O3
#CFLAGS += -fsanitize=address -fuse-ld=gold
SKIPLIST = src/skiplist.o
SHARED_LIB = libskiplist.so
STATIC_LIB = libskiplist.a
EXAMPLE = \
src/example.o \
$(STATIC_LIB) \
PROGRAMS = \
src/example \
libskiplist.so \
libskiplist.a \
all: $(PROGRAMS)
libskiplist.so: $(SKIPLIST)
$(CC) -shared $(LDBFALGS) -o $(SHARED_LIB) $(SKIPLIST)
libskiplist.a: $(SKIPLIST)
ar rcs $(STATIC_LIB) $(SKIPLIST)
src/example: $(EXAMPLE)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
format:
clang-format -i src/*.[ch]
clean:
rm -rf $(PROGRAMS) ./*.o ./*.so ./*/*.o ./*/*.so

150
src/example.c Normal file
View file

@ -0,0 +1,150 @@
#include "skiplist.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
// Define a node that contains key and value pair.
struct my_node {
// Metadata for skiplist node.
sl_node snode;
// My data here: {int, int} pair.
int key;
int value;
};
// Define a comparison function for `my_node`.
static int my_cmp(sl_node *a, sl_node *b, void *aux) {
// Get `my_node` from skiplist node `a` and `b`.
struct my_node *aa, *bb;
aa = _get_entry(a, struct my_node, snode);
bb = _get_entry(b, struct my_node, snode);
// aa < bb: return neg
// aa == bb: return 0
// aa > bb: return pos
if (aa->key < bb->key)
return -1;
if (aa->key > bb->key)
return 1;
return 0;
}
#define NUM_NODES 10000
int main() {
// seed the PRNG
srandom((unsigned)(time(NULL) | getpid()));
sl_raw slist;
// Initialize skiplist.
sl_init(&slist, my_cmp);
// << Insertion >>
// Allocate & insert NUM_NODES KV pairs: {0, 0}, {1, 10}, {2, 20}.
struct my_node *nodes[NUM_NODES];
for (int i = 0; i < NUM_NODES; ++i) {
// Allocate memory.
nodes[i] = (struct my_node *)malloc(sizeof(struct my_node));
// Initialize node.
sl_init_node(&nodes[i]->snode);
// Assign key and value.
nodes[i]->key = i;
nodes[i]->value = i * 10;
// Insert into skiplist.
sl_insert(&slist, &nodes[i]->snode);
}
// << Point lookup >>
for (int i = 0; i < NUM_NODES; ++i) {
// Define a query.
struct my_node query;
int min = 1, max = NUM_NODES - 1;
int k = min + random() / (RAND_MAX / (max - min + 1) + 1);
query.key = k;
// Find a skiplist node `cursor`.
sl_node *cursor = sl_find(&slist, &query.snode);
// If `cursor` is NULL, key doesn't exist.
if (!cursor)
continue;
// Get `my_node` from `cursor`.
// Note: found->snode == *cursor
struct my_node *found = _get_entry(cursor, struct my_node, snode);
printf("[point lookup] key: %d, value: %d\n", found->key, found->value);
if (found->key != found->value / 10) {
printf("FAILURE: key: %d * 10 != value: %d\n", found->key, found->value);
exit(-1);
}
// Release `cursor` (== &found->snode).
// Other thread cannot free `cursor` until `cursor` is released.
sl_release_node(cursor);
}
// << Erase >>
// Erase the KV pair for key 1: {1, 10}.
{
// Define a query.
struct my_node query;
query.key = 1;
// Find a skiplist node `cursor`.
sl_node *cursor = sl_find(&slist, &query.snode);
// Get `my_node` from `cursor`.
// Note: found->snode == *cursor
struct my_node *found = _get_entry(cursor, struct my_node, snode);
printf("[erase] key: %d, value: %d\n", found->key, found->value);
// Detach `found` from skiplist.
sl_erase_node(&slist, &found->snode);
// Release `found`, to free its memory.
sl_release_node(&found->snode);
// Free `found` after it becomes safe.
sl_wait_for_free(&found->snode);
sl_free_node(&found->snode);
free(found);
}
// << Iteration >>
{
// Get the first cursor.
sl_node *cursor = sl_begin(&slist);
while (cursor) {
// Get `entry` from `cursor`.
// Note: entry->snode == *cursor
struct my_node *entry = _get_entry(cursor, struct my_node, snode);
printf("[iteration] key: %d, value: %d\n", entry->key, entry->value);
// Get next `cursor`.
cursor = sl_next(&slist, cursor);
// Release `entry`.
sl_release_node(&entry->snode);
}
}
// << Destroy >>
{
// Iterate and free all nodes.
sl_node *cursor = sl_begin(&slist);
while (cursor) {
struct my_node *entry = _get_entry(cursor, struct my_node, snode);
printf("[destroy] key: %d, value: %d\n", entry->key, entry->value);
// Get next `cursor`.
cursor = sl_next(&slist, cursor);
// Detach `entry` from skiplist.
sl_erase_node(&slist, &entry->snode);
// Release `entry`, to free its memory.
sl_release_node(&entry->snode);
// Free `entry` after it becomes safe.
sl_wait_for_free(&entry->snode);
sl_free_node(&entry->snode);
free(entry);
}
}
// Free skiplist.
sl_free(&slist);
return 0;
}

989
src/skiplist.c Normal file
View file

@ -0,0 +1,989 @@
/**
* Copyright 2024-present Gregory Burd <greg@burd.me> All rights reserved.
*
* Portions of this code are derived from work copyrighted by Jung-Sang Ahn
* 2017-2024 Jung-Sang Ahn <jungsang.ahn@gmail.com> and made available under the
* MIT License. (see: https://github.com/greensky00 Skiplist version: 0.2.9)
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <sched.h>
#include <stdlib.h>
#include "sl.h"
#define __SL_DEBUG (0)
#if __SL_DEBUG > 0
#include <assert.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#if __SL_DEBUG >= 1
#define __SLD_ASSERT(cond) assert(cond)
#define __SLD_(b) b
#endif
#if __SL_DEBUG >= 2
#define __SLD_P(args...) printf(args)
#endif
#if __SL_DEBUG >= 3
typedef struct dbg_node {
sl_node snode;
int value;
} dbg_node_t;
inline void __sld_rt_ins(int error_code, sl_node *node, int top_layer,
int cur_layer) {
dbg_node_t *ddd = _get_entry(node, dbg_node_t, snode);
printf("[INS] retry (code %d) "
"%p (top %d, cur %d) %d\n",
error_code, node, top_layer, cur_layer, ddd->value);
}
inline void __sld_nc_ins(sl_node *node, sl_node *next_node, int top_layer,
int cur_layer) {
dbg_node_t *ddd = _get_entry(node, dbg_node_t, snode);
dbg_node_t *ddd_next = _get_entry(next_node, dbg_node_t, snode);
printf("[INS] next node changed, "
"%p %p (top %d, cur %d) %d %d\n",
node, next_node, top_layer, cur_layer, ddd->value, ddd_next->value);
}
inline void __sld_rt_rmv(int error_code, sl_node *node, int top_layer,
int cur_layer) {
dbg_node_t *ddd = _get_entry(node, dbg_node_t, snode);
printf("[RMV] retry (code %d) "
"%p (top %d, cur %d) %d\n",
error_code, node, top_layer, cur_layer, ddd->value);
}
inline void __sld_nc_rmv(sl_node *node, sl_node *next_node, int top_layer,
int cur_layer) {
dbg_node_t *ddd = _get_entry(node, dbg_node_t, snode);
dbg_node_t *ddd_next = _get_entry(next_node, dbg_node_t, snode);
printf("[RMV] next node changed, "
"%p %p (top %d, cur %d) %d %d\n",
node, next_node, top_layer, cur_layer, ddd->value, ddd_next->value);
}
inline void __sld_bm(sl_node *node) {
dbg_node_t *ddd = _get_entry(node, dbg_node_t, snode);
printf("[RMV] node is being modified %d\n", ddd->value);
}
#define __SLD_RT_INS(e, n, t, c) __sld_rt_ins(e, n, t, c)
#define __SLD_NC_INS(n, nn, t, c) __sld_nc_ins(n, nn, t, c)
#define __SLD_RT_RMV(e, n, t, c) __sld_rt_rmv(e, n, t, c)
#define __SLD_NC_RMV(n, nn, t, c) __sld_nc_rmv(n, nn, t, c)
#define __SLD_BM(n) __sld_bm(n)
#endif
#if __SL_DEBUG >= 4 || __SL_DEBUG <= 0
#define __SLD_RT_INS(e, n, t, c)
#define __SLD_NC_INS(n, nn, t, c)
#define __SLD_RT_RMV(e, n, t, c)
#define __SLD_NC_RMV(n, nn, t, c)
#define __SLD_BM(n)
#define __SLD_ASSERT(cond)
#define __SLD_P(args...)
#define __SLD_(b)
#endif
#define YIELD() sched_yield()
// C-style atomic operations
typedef uint8_t bool;
#ifndef true
#define true 1
#endif
#ifndef false
#define false 0
#endif
/* Implies no inter-thread ordering constraints. */
#define MOR __ATOMIC_RELAXED
#define ATM_GET(var) (var)
#define ATM_LOAD(var, val) __atomic_load(&(var), &(val), MOR)
#define ATM_STORE(var, val) __atomic_store(&(var), &(val), MOR)
#define ATM_CAS(var, exp, val) \
__atomic_compare_exchange(&(var), &(exp), &(val), 1, MOR, MOR)
#define ATM_FETCH_ADD(var, val) __atomic_fetch_add(&(var), (val), MOR)
#define ATM_FETCH_SUB(var, val) __atomic_fetch_sub(&(var), (val), MOR)
#define ALLOC_(type, var, count) (var) = (type *)calloc(count, sizeof(type))
#define FREE_(var) free(var)
static inline void _sl_node_init(sl_node *node, size_t top_layer) {
if (top_layer > UINT8_MAX)
top_layer = UINT8_MAX;
__SLD_ASSERT(node->is_fully_linked == false);
__SLD_ASSERT(node->being_modified == false);
bool bool_val = false;
ATM_STORE(node->is_fully_linked, bool_val);
ATM_STORE(node->being_modified, bool_val);
ATM_STORE(node->removed, bool_val);
if (node->top_layer != top_layer || node->next == NULL) {
node->top_layer = top_layer;
if (node->next)
FREE_(node->next);
ALLOC_(atm_node_ptr, node->next, top_layer + 1);
}
}
void sl_init(sl_raw *slist, sl_cmp_t *cmp_func) {
slist->cmp_func = NULL;
slist->aux = NULL;
// fanout 4 + layer 12: 4^12 ~= upto 17M items under O(lg n) complexity.
// for +17M items, complexity will grow linearly: O(k lg n).
slist->fanout = 4;
slist->max_layer = 12;
slist->num_entries = 0;
ALLOC_(atm_uint32_t, slist->layer_entries, slist->max_layer);
slist->top_layer = 0;
sl_init_node(&slist->head);
sl_init_node(&slist->tail);
_sl_node_init(&slist->head, slist->max_layer);
_sl_node_init(&slist->tail, slist->max_layer);
size_t layer;
for (layer = 0; layer < slist->max_layer; ++layer) {
slist->head.next[layer] = &slist->tail;
slist->tail.next[layer] = NULL;
}
bool bool_val = true;
ATM_STORE(slist->head.is_fully_linked, bool_val);
ATM_STORE(slist->tail.is_fully_linked, bool_val);
slist->cmp_func = cmp_func;
}
void sl_free(sl_raw *slist) {
sl_free_node(&slist->head);
sl_free_node(&slist->tail);
FREE_(slist->layer_entries);
slist->layer_entries = NULL;
slist->aux = NULL;
slist->cmp_func = NULL;
}
void sl_init_node(sl_node *node) {
node->next = NULL;
bool bool_false = false;
ATM_STORE(node->is_fully_linked, bool_false);
ATM_STORE(node->being_modified, bool_false);
ATM_STORE(node->removed, bool_false);
node->accessing_next = 0;
node->top_layer = 0;
node->ref_count = 0;
}
void sl_free_node(sl_node *node) {
FREE_(node->next);
node->next = NULL;
}
size_t sl_get_size(sl_raw *slist) {
uint32_t val;
ATM_LOAD(slist->num_entries, val);
return val;
}
sl_raw_config sl_get_default_config() {
sl_raw_config ret;
ret.fanout = 4;
ret.maxLayer = 12;
ret.aux = NULL;
return ret;
}
sl_raw_config sl_get_config(sl_raw *slist) {
sl_raw_config ret;
ret.fanout = slist->fanout;
ret.maxLayer = slist->max_layer;
ret.aux = slist->aux;
return ret;
}
void sl_set_config(sl_raw *slist, sl_raw_config config) {
slist->fanout = config.fanout;
slist->max_layer = config.maxLayer;
if (slist->layer_entries)
FREE_(slist->layer_entries);
ALLOC_(atm_uint32_t, slist->layer_entries, slist->max_layer);
slist->aux = config.aux;
}
static inline int _sl_cmp(sl_raw *slist, sl_node *a, sl_node *b) {
if (a == b)
return 0;
if (a == &slist->head || b == &slist->tail)
return -1;
if (a == &slist->tail || b == &slist->head)
return 1;
return slist->cmp_func(a, b, slist->aux);
}
static inline bool _sl_valid_node(sl_node *node) {
bool is_fully_linked = false;
ATM_LOAD(node->is_fully_linked, is_fully_linked);
return is_fully_linked;
}
static inline void _sl_read_lock_an(sl_node *node) {
for (;;) {
// Wait for active writer to release the lock
uint32_t accessing_next = 0;
ATM_LOAD(node->accessing_next, accessing_next);
while (accessing_next & 0xfff00000) {
YIELD();
ATM_LOAD(node->accessing_next, accessing_next);
}
ATM_FETCH_ADD(node->accessing_next, 0x1);
ATM_LOAD(node->accessing_next, accessing_next);
if ((accessing_next & 0xfff00000) == 0) {
return;
}
ATM_FETCH_SUB(node->accessing_next, 0x1);
}
}
static inline void _sl_read_unlock_an(sl_node *node) {
ATM_FETCH_SUB(node->accessing_next, 0x1);
}
static inline void _sl_write_lock_an(sl_node *node) {
for (;;) {
// Wait for active writer to release the lock
uint32_t accessing_next = 0;
ATM_LOAD(node->accessing_next, accessing_next);
while (accessing_next & 0xfff00000) {
YIELD();
ATM_LOAD(node->accessing_next, accessing_next);
}
ATM_FETCH_ADD(node->accessing_next, 0x100000);
ATM_LOAD(node->accessing_next, accessing_next);
if ((accessing_next & 0xfff00000) == 0x100000) {
// Wait until there's no more readers
while (accessing_next & 0x000fffff) {
YIELD();
ATM_LOAD(node->accessing_next, accessing_next);
}
return;
}
ATM_FETCH_SUB(node->accessing_next, 0x100000);
}
}
static inline void _sl_write_unlock_an(sl_node *node) {
ATM_FETCH_SUB(node->accessing_next, 0x100000);
}
// Note: it increases the `ref_count` of returned node.
// Caller is responsible to decrease it.
static inline sl_node *_sl_next(sl_raw *slist, sl_node *cur_node, int layer,
sl_node *node_to_find, bool *found) {
sl_node *next_node = NULL;
// Turn on `accessing_next`:
// now `cur_node` is not removable from skiplist,
// which means that `cur_node->next` will be consistent
// until clearing `accessing_next`.
_sl_read_lock_an(cur_node);
{
if (!_sl_valid_node(cur_node)) {
_sl_read_unlock_an(cur_node);
return NULL;
}
ATM_LOAD(cur_node->next[layer], next_node);
// Increase ref count of `next_node`:
// now `next_node` is not destroyable.
// << Remaining issue >>
// 1) initially: A -> B
// 2) T1: call _sl_next(A):
// A.accessing_next := true;
// next_node := B;
// ----- context switch happens here -----
// 3) T2: insert C:
// A -> C -> B
// 4) T2: and then erase B, and free B.
// A -> C B(freed)
// ----- context switch back again -----
// 5) T1: try to do something with B,
// but crash happens.
//
// ... maybe resolved using RW spinlock (Aug 21, 2017).
__SLD_ASSERT(next_node);
ATM_FETCH_ADD(next_node->ref_count, 1);
__SLD_ASSERT(next_node->top_layer >= layer);
}
_sl_read_unlock_an(cur_node);
size_t num_nodes = 0;
sl_node *nodes[256];
while ((next_node && !_sl_valid_node(next_node)) ||
next_node == node_to_find) {
if (found && node_to_find == next_node)
*found = true;
sl_node *temp = next_node;
_sl_read_lock_an(temp);
{
__SLD_ASSERT(next_node);
if (!_sl_valid_node(temp)) {
_sl_read_unlock_an(temp);
ATM_FETCH_SUB(temp->ref_count, 1);
next_node = NULL;
break;
}
ATM_LOAD(temp->next[layer], next_node);
ATM_FETCH_ADD(next_node->ref_count, 1);
nodes[num_nodes++] = temp;
__SLD_ASSERT(next_node->top_layer >= layer);
}
_sl_read_unlock_an(temp);
}
for (size_t ii = 0; ii < num_nodes; ++ii) {
ATM_FETCH_SUB(nodes[ii]->ref_count, 1);
}
return next_node;
}
static inline size_t _sl_decide_top_layer(sl_raw *slist) {
size_t layer = 0;
while (layer + 1 < slist->max_layer) {
// coin filp
if (rand() % slist->fanout == 0) {
// grow: 1/fanout probability
layer++;
} else {
// stop: 1 - 1/fanout probability
break;
}
}
return layer;
}
static inline void _sl_clr_flags(sl_node **node_arr, int start_layer,
int top_layer) {
int layer;
for (layer = start_layer; layer <= top_layer; ++layer) {
if (layer == top_layer || node_arr[layer] != node_arr[layer + 1]) {
bool exp = true;
bool bool_false = false;
if (!ATM_CAS(node_arr[layer]->being_modified, exp, bool_false)) {
__SLD_ASSERT(0);
}
}
}
}
static inline bool _sl_valid_prev_next(sl_node *prev, sl_node *next) {
return _sl_valid_node(prev) && _sl_valid_node(next);
}
static inline int _sl_insert(sl_raw *slist, sl_node *node, bool no_dup) {
__SLD_(uint64_t tid; pthread_threadid_np(NULL, &tid);
size_t tid_hash = (size_t)tid % 256; (void)tid_hash;)
int top_layer = _sl_decide_top_layer(slist);
bool bool_true = true;
// init node before insertion
_sl_node_init(node, top_layer);
_sl_write_lock_an(node);
sl_node *prevs[SKIPLIST_MAX_LAYER];
sl_node *nexts[SKIPLIST_MAX_LAYER];
__SLD_P("%02x ins %p begin\n", (int)tid_hash, node);
insert_retry:
// in pure C, a label can only be part of a stmt.
(void)top_layer;
int cmp = 0, cur_layer = 0, layer;
sl_node *cur_node = &slist->head;
ATM_FETCH_ADD(cur_node->ref_count, 1);
__SLD_(size_t nh = 0);
__SLD_(static __thread sl_node * history[1024]; (void)history);
int sl_top_layer = slist->top_layer;
if (top_layer > sl_top_layer)
sl_top_layer = top_layer;
for (cur_layer = sl_top_layer; cur_layer >= 0; --cur_layer) {
do {
__SLD_(history[nh++] = cur_node);
sl_node *next_node = _sl_next(slist, cur_node, cur_layer, NULL, NULL);
if (!next_node) {
_sl_clr_flags(prevs, cur_layer + 1, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto insert_retry;
}
cmp = _sl_cmp(slist, node, next_node);
if (cmp > 0) {
// cur_node < next_node < node
// => move to next node
sl_node *temp = cur_node;
cur_node = next_node;
ATM_FETCH_SUB(temp->ref_count, 1);
continue;
} else {
// otherwise: cur_node < node <= next_node
ATM_FETCH_SUB(next_node->ref_count, 1);
}
if (no_dup && (cmp == 0)) {
// Duplicate key is not allowed.
_sl_clr_flags(prevs, cur_layer + 1, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
return -1;
}
if (cur_layer <= top_layer) {
prevs[cur_layer] = cur_node;
nexts[cur_layer] = next_node;
// both 'prev' and 'next' should be fully linked before
// insertion, and no other thread should not modify 'prev'
// at the same time.
int error_code = 0;
int locked_layer = cur_layer + 1;
// check if prev node is duplicated with upper layer
if (cur_layer < top_layer && prevs[cur_layer] == prevs[cur_layer + 1]) {
// duplicate
// => which means that 'being_modified' flag is already true
// => do nothing
} else {
bool expected = false;
if (ATM_CAS(prevs[cur_layer]->being_modified, expected, bool_true)) {
locked_layer = cur_layer;
} else {
error_code = -1;
}
}
if (error_code == 0 &&
!_sl_valid_prev_next(prevs[cur_layer], nexts[cur_layer])) {
error_code = -2;
}
if (error_code != 0) {
__SLD_RT_INS(error_code, node, top_layer, cur_layer);
_sl_clr_flags(prevs, locked_layer, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto insert_retry;
}
// set current node's pointers
ATM_STORE(node->next[cur_layer], nexts[cur_layer]);
// check if `cur_node->next` has been changed from `next_node`.
sl_node *next_node_again =
_sl_next(slist, cur_node, cur_layer, NULL, NULL);
ATM_FETCH_SUB(next_node_again->ref_count, 1);
if (next_node_again != next_node) {
__SLD_NC_INS(cur_node, next_node, top_layer, cur_layer);
// clear including the current layer
// as we already set modification flag above.
_sl_clr_flags(prevs, cur_layer, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto insert_retry;
}
}
if (cur_layer) {
// non-bottom layer => go down
break;
}
// bottom layer => insertion succeeded
// change prev/next nodes' prev/next pointers from 0 ~ top_layer
for (layer = 0; layer <= top_layer; ++layer) {
// `accessing_next` works as a spin-lock.
_sl_write_lock_an(prevs[layer]);
sl_node *exp = nexts[layer];
if (!ATM_CAS(prevs[layer]->next[layer], exp, node)) {
__SLD_P("%02x ASSERT ins %p[%d] -> %p (expected %p)\n", (int)tid_hash,
prevs[layer], cur_layer, ATM_GET(prevs[layer]->next[layer]),
nexts[layer]);
__SLD_ASSERT(0);
}
__SLD_P("%02x ins %p[%d] -> %p -> %p\n", (int)tid_hash, prevs[layer],
layer, node, ATM_GET(node->next[layer]));
_sl_write_unlock_an(prevs[layer]);
}
// now this node is fully linked
ATM_STORE(node->is_fully_linked, bool_true);
// allow removing next nodes
_sl_write_unlock_an(node);
__SLD_P("%02x ins %p done\n", (int)tid_hash, node);
ATM_FETCH_ADD(slist->num_entries, 1);
ATM_FETCH_ADD(slist->layer_entries[node->top_layer], 1);
for (int ii = slist->max_layer - 1; ii >= 0; --ii) {
if (slist->layer_entries[ii] > 0) {
slist->top_layer = ii;
break;
}
}
// modification is done for all layers
_sl_clr_flags(prevs, 0, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
return 0;
} while (cur_node != &slist->tail);
}
return 0;
}
int sl_insert(sl_raw *slist, sl_node *node) {
return _sl_insert(slist, node, false);
}
int sl_insert_nodup(sl_raw *slist, sl_node *node) {
return _sl_insert(slist, node, true);
}
typedef enum { SM = -2, SMEQ = -1, EQ = 0, GTEQ = 1, GT = 2 } _sl_find_mode;
// Note: it increases the `ref_count` of returned node.
// Caller is responsible to decrease it.
static inline sl_node *_sl_find(sl_raw *slist, sl_node *query,
_sl_find_mode mode) {
// mode:
// SM -2: smaller
// SMEQ -1: smaller or equal
// EQ 0: equal
// GTEQ 1: greater or equal
// GT 2: greater
find_retry:
(void)mode;
int cmp = 0;
int cur_layer = 0;
sl_node *cur_node = &slist->head;
ATM_FETCH_ADD(cur_node->ref_count, 1);
__SLD_(size_t nh = 0);
__SLD_(static __thread sl_node * history[1024]; (void)history);
uint8_t sl_top_layer = slist->top_layer;
for (cur_layer = sl_top_layer; cur_layer >= 0; --cur_layer) {
do {
__SLD_(history[nh++] = cur_node);
sl_node *next_node = _sl_next(slist, cur_node, cur_layer, NULL, NULL);
if (!next_node) {
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto find_retry;
}
cmp = _sl_cmp(slist, query, next_node);
if (cmp > 0) {
// cur_node < next_node < query
// => move to next node
sl_node *temp = cur_node;
cur_node = next_node;
ATM_FETCH_SUB(temp->ref_count, 1);
continue;
} else if (-1 <= mode && mode <= 1 && cmp == 0) {
// cur_node < query == next_node .. return
ATM_FETCH_SUB(cur_node->ref_count, 1);
return next_node;
}
// otherwise: cur_node < query < next_node
if (cur_layer) {
// non-bottom layer => go down
ATM_FETCH_SUB(next_node->ref_count, 1);
break;
}
// bottom layer
if (mode < 0 && cur_node != &slist->head) {
// smaller mode
ATM_FETCH_SUB(next_node->ref_count, 1);
return cur_node;
} else if (mode > 0 && next_node != &slist->tail) {
// greater mode
ATM_FETCH_SUB(cur_node->ref_count, 1);
return next_node;
}
// otherwise: exact match mode OR not found
ATM_FETCH_SUB(cur_node->ref_count, 1);
ATM_FETCH_SUB(next_node->ref_count, 1);
return NULL;
} while (cur_node != &slist->tail);
}
return NULL;
}
sl_node *sl_find(sl_raw *slist, sl_node *query) {
return _sl_find(slist, query, EQ);
}
sl_node *sl_find_smaller_or_equal(sl_raw *slist, sl_node *query) {
return _sl_find(slist, query, SMEQ);
}
sl_node *sl_find_greater_or_equal(sl_raw *slist, sl_node *query) {
return _sl_find(slist, query, GTEQ);
}
int sl_erase_node_passive(sl_raw *slist, sl_node *node) {
__SLD_(uint64_t tid; pthread_threadid_np(NULL, &tid);
size_t tid_hash = (size_t)tid % 256; (void)tid_hash;)
int top_layer = node->top_layer;
bool bool_true = true, bool_false = false;
bool removed = false;
bool is_fully_linked = false;
ATM_LOAD(node->removed, removed);
if (removed) {
// already removed
return -1;
}
sl_node *prevs[SKIPLIST_MAX_LAYER];
sl_node *nexts[SKIPLIST_MAX_LAYER];
bool expected = false;
if (!ATM_CAS(node->being_modified, expected, bool_true)) {
// already being modified .. cannot work on this node for now.
__SLD_BM(node);
return -2;
}
// set removed flag first, so that reader cannot read this node.
ATM_STORE(node->removed, bool_true);
__SLD_P("%02x rmv %p begin\n", (int)tid_hash, node);
erase_node_retry:
ATM_LOAD(node->is_fully_linked, is_fully_linked);
if (!is_fully_linked) {
// already unlinked .. remove is done by other thread
ATM_STORE(node->removed, bool_false);
ATM_STORE(node->being_modified, bool_false);
return -3;
}
int cmp = 0;
bool found_node_to_erase = false;
(void)found_node_to_erase;
sl_node *cur_node = &slist->head;
ATM_FETCH_ADD(cur_node->ref_count, 1);
__SLD_(size_t nh = 0);
__SLD_(static __thread sl_node * history[1024]; (void)history);
int cur_layer = slist->top_layer;
for (; cur_layer >= 0; --cur_layer) {
do {
__SLD_(history[nh++] = cur_node);
bool node_found = false;
sl_node *next_node =
_sl_next(slist, cur_node, cur_layer, node, &node_found);
if (!next_node) {
_sl_clr_flags(prevs, cur_layer + 1, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto erase_node_retry;
}
// Note: unlike insert(), we should find exact position of `node`.
cmp = _sl_cmp(slist, node, next_node);
if (cmp > 0 || (cur_layer <= top_layer && !node_found)) {
// cur_node <= next_node < node
// => move to next node
sl_node *temp = cur_node;
cur_node = next_node;
__SLD_(if (cmp > 0) {
int cmp2 = _sl_cmp(slist, cur_node, node);
if (cmp2 > 0) {
// node < cur_node <= next_node: not found.
_sl_clr_flags(prevs, cur_layer + 1, top_layer);
ATM_FETCH_SUB(temp->ref_count, 1);
ATM_FETCH_SUB(next_node->ref_count, 1);
__SLD_ASSERT(0);
}
})
ATM_FETCH_SUB(temp->ref_count, 1);
continue;
} else {
// otherwise: cur_node <= node <= next_node
ATM_FETCH_SUB(next_node->ref_count, 1);
}
if (cur_layer <= top_layer) {
prevs[cur_layer] = cur_node;
// note: 'next_node' and 'node' should not be the same,
// as 'removed' flag is already set.
__SLD_ASSERT(next_node != node);
nexts[cur_layer] = next_node;
// check if prev node duplicates with upper layer
int error_code = 0;
int locked_layer = cur_layer + 1;
if (cur_layer < top_layer && prevs[cur_layer] == prevs[cur_layer + 1]) {
// duplicate with upper layer
// => which means that 'being_modified' flag is already true
// => do nothing.
} else {
expected = false;
if (ATM_CAS(prevs[cur_layer]->being_modified, expected, bool_true)) {
locked_layer = cur_layer;
} else {
error_code = -1;
}
}
if (error_code == 0 &&
!_sl_valid_prev_next(prevs[cur_layer], nexts[cur_layer])) {
error_code = -2;
}
if (error_code != 0) {
__SLD_RT_RMV(error_code, node, top_layer, cur_layer);
_sl_clr_flags(prevs, locked_layer, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto erase_node_retry;
}
sl_node *next_node_again =
_sl_next(slist, cur_node, cur_layer, node, NULL);
ATM_FETCH_SUB(next_node_again->ref_count, 1);
if (next_node_again != nexts[cur_layer]) {
// `next` pointer has been changed, retry.
__SLD_NC_RMV(cur_node, nexts[cur_layer], top_layer, cur_layer);
_sl_clr_flags(prevs, cur_layer, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
YIELD();
goto erase_node_retry;
}
}
if (cur_layer == 0)
found_node_to_erase = true;
// go down
break;
} while (cur_node != &slist->tail);
}
// Not exist in the skiplist, should not happen.
__SLD_ASSERT(found_node_to_erase);
// bottom layer => removal succeeded.
// mark this node unlinked
_sl_write_lock_an(node);
{ ATM_STORE(node->is_fully_linked, bool_false); }
_sl_write_unlock_an(node);
// change prev nodes' next pointer from 0 ~ top_layer
for (cur_layer = 0; cur_layer <= top_layer; ++cur_layer) {
_sl_write_lock_an(prevs[cur_layer]);
sl_node *exp = node;
__SLD_ASSERT(exp != nexts[cur_layer]);
__SLD_ASSERT(nexts[cur_layer]->is_fully_linked);
if (!ATM_CAS(prevs[cur_layer]->next[cur_layer], exp, nexts[cur_layer])) {
__SLD_P("%02x ASSERT rmv %p[%d] -> %p (node %p)\n", (int)tid_hash,
prevs[cur_layer], cur_layer,
ATM_GET(prevs[cur_layer]->next[cur_layer]), node);
__SLD_ASSERT(0);
}
__SLD_ASSERT(nexts[cur_layer]->top_layer >= cur_layer);
__SLD_P("%02x rmv %p[%d] -> %p (node %p)\n", (int)tid_hash,
prevs[cur_layer], cur_layer, nexts[cur_layer], node);
_sl_write_unlock_an(prevs[cur_layer]);
}
__SLD_P("%02x rmv %p done\n", (int)tid_hash, node);
ATM_FETCH_SUB(slist->num_entries, 1);
ATM_FETCH_SUB(slist->layer_entries[node->top_layer], 1);
for (int ii = slist->max_layer - 1; ii >= 0; --ii) {
if (slist->layer_entries[ii] > 0) {
slist->top_layer = ii;
break;
}
}
// modification is done for all layers
_sl_clr_flags(prevs, 0, top_layer);
ATM_FETCH_SUB(cur_node->ref_count, 1);
ATM_STORE(node->being_modified, bool_false);
return 0;
}
int sl_erase_node(sl_raw *slist, sl_node *node) {
int ret = 0;
do {
ret = sl_erase_node_passive(slist, node);
// if ret == -2, other thread is accessing the same node
// at the same time. try again.
} while (ret == -2);
return ret;
}
int sl_erase(sl_raw *slist, sl_node *query) {
sl_node *found = sl_find(slist, query);
if (!found) {
// key not found
return -4;
}
int ret = 0;
do {
ret = sl_erase_node_passive(slist, found);
// if ret == -2, other thread is accessing the same node
// at the same time. try again.
} while (ret == -2);
ATM_FETCH_SUB(found->ref_count, 1);
return ret;
}
int sl_is_valid_node(sl_node *node) { return _sl_valid_node(node); }
int sl_is_safe_to_free(sl_node *node) {
if (node->accessing_next)
return 0;
if (node->being_modified)
return 0;
if (!node->removed)
return 0;
uint16_t ref_count = 0;
ATM_LOAD(node->ref_count, ref_count);
if (ref_count)
return 0;
return 1;
}
void sl_wait_for_free(sl_node *node) {
while (!sl_is_safe_to_free(node)) {
YIELD();
}
}
void sl_grab_node(sl_node *node) { ATM_FETCH_ADD(node->ref_count, 1); }
void sl_release_node(sl_node *node) {
__SLD_ASSERT(node->ref_count);
ATM_FETCH_SUB(node->ref_count, 1);
}
sl_node *sl_next(sl_raw *slist, sl_node *node) {
// << Issue >>
// If `node` is already removed and its next node is also removed
// and then released, the link update will not be applied to `node`
// as it is already unrechable from skiplist. `node` still points to
// the released node so that `_sl_next(node)` may return corrupted
// memory region.
//
// 0) initial:
// A -> B -> C -> D
//
// 1) B is `node`, which is removed but not yet released:
// B --+-> C -> D
// |
// A --+
//
// 2) remove C, and then release:
// B -> !C! +-> D
// |
// A --------+
//
// 3) sl_next(B):
// will fetch C, which is already released so that
// may contain garbage data.
//
// In this case, start over from the top layer,
// to find valid link (same as in prev()).
sl_node *next = _sl_next(slist, node, 0, NULL, NULL);
if (!next)
next = _sl_find(slist, node, GT);
if (next == &slist->tail)
return NULL;
return next;
}
sl_node *sl_prev(sl_raw *slist, sl_node *node) {
sl_node *prev = _sl_find(slist, node, SM);
if (prev == &slist->head)
return NULL;
return prev;
}
sl_node *sl_begin(sl_raw *slist) {
sl_node *next = NULL;
while (!next) {
next = _sl_next(slist, &slist->head, 0, NULL, NULL);
}
if (next == &slist->tail)
return NULL;
return next;
}
sl_node *sl_end(sl_raw *slist) { return sl_prev(slist, &slist->tail); }

131
src/skiplist.h Normal file
View file

@ -0,0 +1,131 @@
/**
* Copyright 2024-present Gregory Burd <greg@burd.me> All rights reserved.
*
* Portions of this code are derived from work copyrighted by Jung-Sang Ahn
* 2017-2024 Jung-Sang Ahn <jungsang.ahn@gmail.com> and made available under the
* MIT License. (see: https://github.com/greensky00 Skiplist version: 0.2.9)
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef SKIPLIST_H__
#define SKIPLIST_H__ (1)
#include <stddef.h>
#include <stdint.h>
#define SKIPLIST_MAX_LAYER (64)
struct _sl_node;
// #define _STL_ATOMIC (1)
#ifdef __APPLE__
#define _STL_ATOMIC (1)
#endif
typedef struct _sl_node *atm_node_ptr;
typedef uint8_t atm_bool;
typedef uint8_t atm_uint8_t;
typedef uint16_t atm_uint16_t;
typedef uint32_t atm_uint32_t;
#ifdef __cplusplus
extern "C" {
#endif
typedef struct _sl_node {
atm_node_ptr *next;
atm_bool is_fully_linked;
atm_bool being_modified;
atm_bool removed;
uint8_t top_layer; // 0: bottom
atm_uint16_t ref_count;
atm_uint32_t accessing_next;
} sl_node;
// *a < *b : return neg
// *a == *b : return 0
// *a > *b : return pos
typedef int sl_cmp_t(sl_node *a, sl_node *b, void *aux);
typedef struct {
size_t fanout;
size_t maxLayer;
void *aux;
} sl_raw_config;
typedef struct {
sl_node head;
sl_node tail;
sl_cmp_t *cmp_func;
void *aux;
atm_uint32_t num_entries;
atm_uint32_t *layer_entries;
atm_uint8_t top_layer;
uint8_t fanout;
uint8_t max_layer;
} sl_raw;
#ifndef _get_entry
#define _get_entry(ELEM, STRUCT, MEMBER) \
((STRUCT *)((uint8_t *)(ELEM)-offsetof(STRUCT, MEMBER)))
#endif
void sl_init(sl_raw *slist, sl_cmp_t *cmp_func);
void sl_free(sl_raw *slist);
void sl_init_node(sl_node *node);
void sl_free_node(sl_node *node);
size_t sl_get_size(sl_raw *slist);
sl_raw_config sl_get_default_config();
sl_raw_config sl_get_config(sl_raw *slist);
void sl_set_config(sl_raw *slist, sl_raw_config config);
int sl_insert(sl_raw *slist, sl_node *node);
int sl_insert_nodup(sl_raw *slist, sl_node *node);
sl_node *sl_find(sl_raw *slist, sl_node *query);
sl_node *sl_find_smaller_or_equal(sl_raw *slist, sl_node *query);
sl_node *sl_find_greater_or_equal(sl_raw *slist, sl_node *query);
int sl_erase_node_passive(sl_raw *slist, sl_node *node);
int sl_erase_node(sl_raw *slist, sl_node *node);
int sl_erase(sl_raw *slist, sl_node *query);
int sl_is_valid_node(sl_node *node);
int sl_is_safe_to_free(sl_node *node);
void sl_wait_for_free(sl_node *node);
void sl_grab_node(sl_node *node);
void sl_release_node(sl_node *node);
sl_node *sl_next(sl_raw *slist, sl_node *node);
sl_node *sl_prev(sl_raw *slist, sl_node *node);
sl_node *sl_begin(sl_raw *slist);
sl_node *sl_end(sl_raw *slist);
#ifdef __cplusplus
}
#endif
#endif // SKIPLIST_H__

109
src/skiplist_debug.h Normal file
View file

@ -0,0 +1,109 @@
/**
* Copyright (C) 2017-present Jung-Sang Ahn <jungsang.ahn@gmail.com>
* All rights reserved.
*
* https://github.com/greensky00
*
* Skiplist
* Version: 0.2.5
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <assert.h>
#include <stdio.h>
#include "skiplist.h"
struct dbg_node {
skiplist_node snode;
int value;
};
#if __SL_DEBUG >= 1
#undef __SLD_ASSERT
#undef __SLD_
#define __SLD_ASSERT(cond) assert(cond)
#define __SLD_(b) b
#endif
#if __SL_DEBUG >= 2
#undef __SLD_P
#define __SLD_P(args...) printf(args)
#endif
#if __SL_DEBUG >= 3
#undef __SLD_RT_INS
#undef __SLD_NC_INS
#undef __SLD_RT_RMV
#undef __SLD_NC_RMV
#undef __SLD_BM
#define __SLD_RT_INS(e, n, t, c) __sld_rt_ins(e, n, t, c)
#define __SLD_NC_INS(n, nn, t, c) __sld_nc_ins(n, nn, t, c)
#define __SLD_RT_RMV(e, n, t, c) __sld_rt_rmv(e, n, t, c)
#define __SLD_NC_RMV(n, nn, t, c) __sld_nc_rmv(n, nn, t, c)
#define __SLD_BM(n) __sld_bm(n)
#endif
#if __SL_DEBUG >= 4
#error "unknown debugging level"
#endif
inline void __sld_rt_ins(int error_code, skiplist_node *node, int top_layer,
int cur_layer) {
dbg_node *ddd = _get_entry(node, dbg_node, snode);
printf("[INS] retry (code %d) "
"%p (top %d, cur %d) %d\n",
error_code, node, top_layer, cur_layer, ddd->value);
}
inline void __sld_nc_ins(skiplist_node *node, skiplist_node *next_node,
int top_layer, int cur_layer) {
dbg_node *ddd = _get_entry(node, dbg_node, snode);
dbg_node *ddd_next = _get_entry(next_node, dbg_node, snode);
printf("[INS] next node changed, "
"%p %p (top %d, cur %d) %d %d\n",
node, next_node, top_layer, cur_layer, ddd->value, ddd_next->value);
}
inline void __sld_rt_rmv(int error_code, skiplist_node *node, int top_layer,
int cur_layer) {
dbg_node *ddd = _get_entry(node, dbg_node, snode);
printf("[RMV] retry (code %d) "
"%p (top %d, cur %d) %d\n",
error_code, node, top_layer, cur_layer, ddd->value);
}
inline void __sld_nc_rmv(skiplist_node *node, skiplist_node *next_node,
int top_layer, int cur_layer) {
dbg_node *ddd = _get_entry(node, dbg_node, snode);
dbg_node *ddd_next = _get_entry(next_node, dbg_node, snode);
printf("[RMV] next node changed, "
"%p %p (top %d, cur %d) %d %d\n",
node, next_node, top_layer, cur_layer, ddd->value, ddd_next->value);
}
inline void __sld_bm(skiplist_node *node) {
dbg_node *ddd = _get_entry(node, dbg_node, snode);
printf("[RMV] node is being modified %d\n", ddd->value);
}