#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
+/*
+ * The removed flag needs to be updated atomically with the pointer.
+ * The dummy flag does not require to be updated atomically with the
+ * pointer, but it is added as a pointer low bit flag to save space.
+ */
#define REMOVED_FLAG (1UL << 0)
-#define FLAGS_MASK ((1UL << 1) - 1)
+#define DUMMY_FLAG (1UL << 1)
+#define FLAGS_MASK ((1UL << 2) - 1)
struct rcu_table {
unsigned long size; /* always a power of 2 */
return (struct rcu_ht_node *) (((unsigned long) node) | REMOVED_FLAG);
}
+static
+int is_dummy(struct rcu_ht_node *node)
+{
+ return ((unsigned long) node) & DUMMY_FLAG;
+}
+
+static
+struct rcu_ht_node *flag_dummy(struct rcu_ht_node *node)
+{
+ return (struct rcu_ht_node *) (((unsigned long) node) | DUMMY_FLAG);
+}
+
static
unsigned long _uatomic_max(unsigned long *ptr, unsigned long v)
{
static
void _ht_gc_bucket(struct rcu_ht_node *dummy, struct rcu_ht_node *node)
{
- struct rcu_ht_node *iter_prev, *iter, *next;
+ struct rcu_ht_node *iter_prev, *iter, *next, *new_next;
for (;;) {
iter_prev = dummy;
iter = rcu_dereference(iter_prev->p.next);
assert(iter_prev->p.reverse_hash <= node->p.reverse_hash);
for (;;) {
- if (unlikely(!iter))
+ if (unlikely(!clear_flag(iter)))
return;
if (clear_flag(iter)->p.reverse_hash > node->p.reverse_hash)
return;
next = rcu_dereference(clear_flag(iter)->p.next);
if (is_removed(next))
break;
- iter_prev = iter;
+ iter_prev = clear_flag(iter);
iter = next;
}
assert(!is_removed(iter));
- (void) uatomic_cmpxchg(&iter_prev->p.next, iter, clear_flag(next));
+ if (is_dummy(iter))
+ new_next = flag_dummy(clear_flag(next));
+ else
+ new_next = clear_flag(next);
+ (void) uatomic_cmpxchg(&iter_prev->p.next, iter, new_next);
}
}
static
struct rcu_ht_node *_ht_add(struct rcu_ht *ht, struct rcu_table *t,
- struct rcu_ht_node *node, int unique)
+ struct rcu_ht_node *node, int unique, int dummy)
{
- struct rcu_ht_node *iter_prev, *dummy, *iter, *next;
+ struct rcu_ht_node *iter_prev, *iter, *next, *new_node, *new_next,
+ *dummy_node;
unsigned long hash;
if (!t->size) {
- assert(node->p.dummy);
+ assert(dummy);
+ node->p.next = flag_dummy(NULL);
return node; /* Initial first add (head) */
}
hash = bit_reverse_ulong(node->p.reverse_hash);
iter = rcu_dereference(iter_prev->p.next);
assert(iter_prev->p.reverse_hash <= node->p.reverse_hash);
for (;;) {
- if (unlikely(!iter))
+ if (unlikely(!clear_flag(iter)))
goto insert;
if (clear_flag(iter)->p.reverse_hash > node->p.reverse_hash)
goto insert;
if (is_removed(next))
goto gc_node;
if (unique
- && !clear_flag(iter)->p.dummy
+ && !is_dummy(next)
&& !ht->compare_fct(node->key, node->key_len,
clear_flag(iter)->key,
clear_flag(iter)->key_len))
assert(node != clear_flag(iter));
assert(!is_removed(iter_prev));
assert(iter_prev != node);
- node->p.next = iter;
+ if (!dummy)
+ node->p.next = clear_flag(iter);
+ else
+ node->p.next = flag_dummy(clear_flag(iter));
+ if (is_dummy(iter))
+ new_node = flag_dummy(node);
+ else
+ new_node = node;
if (uatomic_cmpxchg(&iter_prev->p.next, iter,
- node) != iter)
+ new_node) != iter)
continue; /* retry */
else
goto gc_end;
gc_node:
assert(!is_removed(iter));
- (void) uatomic_cmpxchg(&iter_prev->p.next, iter, clear_flag(next));
+ if (is_dummy(iter))
+ new_next = flag_dummy(clear_flag(next));
+ else
+ new_next = clear_flag(next);
+ (void) uatomic_cmpxchg(&iter_prev->p.next, iter, new_next);
/* retry */
}
gc_end:
/* Garbage collect logically removed nodes in the bucket */
- dummy = rcu_dereference(t->tbl[hash & (t->size - 1)]);
- _ht_gc_bucket(dummy, node);
+ dummy_node = rcu_dereference(t->tbl[hash & (t->size - 1)]);
+ _ht_gc_bucket(dummy_node, node);
return node;
}
next = old;
if (is_removed(next))
goto end;
- assert(!node->p.dummy);
+ assert(!is_dummy(next));
old = uatomic_cmpxchg(&node->p.next, next,
flag_removed(next));
} while (old != next);
if (i != 0 && !(i & (i - 1)))
t->size = i;
t->tbl[i] = calloc(1, sizeof(struct _rcu_ht_node));
- t->tbl[i]->p.dummy = 1;
t->tbl[i]->p.reverse_hash = bit_reverse_ulong(i);
- (void) _ht_add(ht, t, t->tbl[i], 0);
+ (void) _ht_add(ht, t, t->tbl[i], 0, 1);
}
t->resize_target = t->size = end;
t->resize_initiated = 0;
struct rcu_ht_node *ht_lookup(struct rcu_ht *ht, void *key, size_t key_len)
{
struct rcu_table *t;
- struct rcu_ht_node *node;
+ struct rcu_ht_node *node, *next;
unsigned long hash, reverse_hash;
hash = ht->hash_fct(key, key_len, ht->hash_seed);
node = NULL;
break;
}
- if (likely(!is_removed(rcu_dereference(node->p.next)))
- && !node->p.dummy
+ next = rcu_dereference(node->p.next);
+ if (likely(!is_removed(next))
+ && !is_dummy(next)
&& likely(!ht->compare_fct(node->key, node->key_len, key, key_len))) {
break;
}
- node = clear_flag(rcu_dereference(node->p.next));
+ node = clear_flag(next);
}
- assert(!node || !node->p.dummy);
+ assert(!node || !is_dummy(rcu_dereference(node->p.next)));
return node;
}
node->p.reverse_hash = bit_reverse_ulong((unsigned long) hash);
t = rcu_dereference(ht->t);
- (void) _ht_add(ht, t, node, 0);
+ (void) _ht_add(ht, t, node, 0, 0);
}
struct rcu_ht_node *ht_add_unique(struct rcu_ht *ht, struct rcu_ht_node *node)
node->p.reverse_hash = bit_reverse_ulong((unsigned long) hash);
t = rcu_dereference(ht->t);
- return _ht_add(ht, t, node, 1);
+ return _ht_add(ht, t, node, 1, 0);
}
int ht_remove(struct rcu_ht *ht, struct rcu_ht_node *node)
/* Check that the table is empty */
node = t->tbl[0];
do {
- if (!node->p.dummy)
+ node = clear_flag(node)->p.next;
+ if (!is_dummy(node))
return -EPERM;
- node = node->p.next;
assert(!is_removed(node));
- } while (node);
+ } while (clear_flag(node));
/* Internal sanity check: all nodes left should be dummy */
for (i = 0; i < t->size; i++) {
- assert(t->tbl[i]->p.dummy);
+ assert(is_dummy(t->tbl[i]->p.next));
free(t->tbl[i]);
}
return 0;
do {
next = rcu_dereference(node->p.next);
if (is_removed(next)) {
- assert(!node->p.dummy);
+ assert(!is_dummy(next));
(*removed)++;
- } else if (!node->p.dummy)
+ } else if (!is_dummy(next))
(*count)++;
node = clear_flag(next);
} while (node);