lib: add _last and _prev on typesafe RB/DLIST

RB-tree and double-linked-list easily support backwards iteration, and
an use case seems to have popped up.  Let's make it accessible.

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
This commit is contained in:
David Lamparter 2022-03-09 14:26:05 +01:00
parent 9d888674b2
commit 643ea83be2
7 changed files with 230 additions and 32 deletions

View file

@ -28,6 +28,9 @@ ForEachMacros:
- frr_each - frr_each
- frr_each_safe - frr_each_safe
- frr_each_from - frr_each_from
- frr_rev_each
- frr_rev_each_safe
- frr_rev_each_from
- frr_with_mutex - frr_with_mutex
- frr_with_privs - frr_with_privs
- LIST_FOREACH - LIST_FOREACH

View file

@ -100,35 +100,39 @@ Available types:
Functions provided: Functions provided:
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| Function | LIST | HEAP | HASH | \*_UNIQ | \*_NONUNIQ | | Function | LIST | HEAP | HASH | \*_UNIQ | \*_NONUNIQ |
+====================================+======+======+======+=========+============+ +====================================+=======+======+======+=========+============+
| _init, _fini | yes | yes | yes | yes | yes | | _init, _fini | yes | yes | yes | yes | yes |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _first, _next, _next_safe, | yes | yes | yes | yes | yes | | _first, _next, _next_safe, | yes | yes | yes | yes | yes |
| | | | | | | | | | | | | |
| _const_first, _const_next | | | | | | | _const_first, _const_next | | | | | |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _swap_all | yes | yes | yes | yes | yes | | _last, _prev, _prev_safe, | DLIST | -- | -- | RB only | RB only |
+------------------------------------+------+------+------+---------+------------+ | | only | | | | |
| _anywhere | yes | -- | -- | -- | -- | | _const_last, _const_prev | | | | | |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _add_head, _add_tail, _add_after | yes | -- | -- | -- | -- | | _swap_all | yes | yes | yes | yes | yes |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _add | -- | yes | yes | yes | yes | | _anywhere | yes | -- | -- | -- | -- |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _member | yes | yes | yes | yes | yes | | _add_head, _add_tail, _add_after | yes | -- | -- | -- | -- |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _del, _pop | yes | yes | yes | yes | yes | | _add | -- | yes | yes | yes | yes |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _find, _const_find | -- | -- | yes | yes | -- | | _member | yes | yes | yes | yes | yes |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| _find_lt, _find_gteq, | -- | -- | -- | yes | yes | | _del, _pop | yes | yes | yes | yes | yes |
| | | | | | | +------------------------------------+-------+------+------+---------+------------+
| _const_find_lt, _const_find_gteq | | | | | | | _find, _const_find | -- | -- | yes | yes | -- |
+------------------------------------+------+------+------+---------+------------+ +------------------------------------+-------+------+------+---------+------------+
| use with frr_each() macros | yes | yes | yes | yes | yes | | _find_lt, _find_gteq, | -- | -- | -- | yes | yes |
+------------------------------------+------+------+------+---------+------------+ | | | | | | |
| _const_find_lt, _const_find_gteq | | | | | |
+------------------------------------+-------+------+------+---------+------------+
| use with frr_each() macros | yes | yes | yes | yes | yes |
+------------------------------------+-------+------+------+---------+------------+
@ -236,6 +240,13 @@ The following iteration macros work across all data structures:
resume iteration after breaking out of the loop by keeping the ``from`` resume iteration after breaking out of the loop by keeping the ``from``
value persistent and reusing it for the next loop. value persistent and reusing it for the next loop.
.. c:macro:: frr_rev_each(Z, head, item)
.. c:macro:: frr_rev_each_safe(Z, head, item)
.. c:macro:: frr_rev_each_from(Z, head, item, from)
Reverse direction variants of the above. Only supported on containers that
implement ``_last`` and ``_prev`` (i.e. ``RBTREE`` and ``DLIST``).
To iterate over ``const`` pointers, add ``_const`` to the name of the To iterate over ``const`` pointers, add ``_const`` to the name of the
datastructure (``Z`` above), e.g. ``frr_each (mylist, head, item)`` becomes datastructure (``Z`` above), e.g. ``frr_each (mylist, head, item)`` becomes
``frr_each (mylist_const, head, item)``. ``frr_each (mylist_const, head, item)``.
@ -291,6 +302,12 @@ The following documentation assumes that a list has been defined using
empty. This is O(1) for all data structures except red-black trees empty. This is O(1) for all data structures except red-black trees
where it is O(log n). where it is O(log n).
.. c:function:: const itemtype *Z_const_last(const struct Z_head *)
.. c:function:: itemtype *Z_last(struct Z_head *)
Last item in the structure, or ``NULL``. Only available on containers
that support reverse iteration (i.e. ``RBTREE`` and ``DLIST``).
.. c:function:: itemtype *Z_pop(struct Z_head *) .. c:function:: itemtype *Z_pop(struct Z_head *)
Remove and return the first item in the structure, or ``NULL`` if the Remove and return the first item in the structure, or ``NULL`` if the
@ -329,6 +346,13 @@ The following documentation assumes that a list has been defined using
Same as :c:func:`Z_next()`, except that ``NULL`` is returned if Same as :c:func:`Z_next()`, except that ``NULL`` is returned if
``prev`` is ``NULL``. ``prev`` is ``NULL``.
.. c:function:: const itemtype *Z_const_prev(const struct Z_head *, const itemtype *next)
.. c:function:: itemtype *Z_prev(struct Z_head *, itemtype *next)
.. c:function:: itemtype *Z_prev_safe(struct Z_head *, itemtype *next)
As above, but preceding item. Only available on structures that support
reverse iteration (i.e. ``RBTREE`` and ``DLIST``).
.. c:function:: itemtype *Z_del(struct Z_head *, itemtype *item) .. c:function:: itemtype *Z_del(struct Z_head *, itemtype *item)
Remove ``item`` from the list and return it. Remove ``item`` from the list and return it.

View file

@ -468,6 +468,28 @@ struct rb_entry *typed_rb_next(const struct rb_entry *rbe_const)
return rbe; return rbe;
} }
struct rb_entry *typed_rb_prev(const struct rb_entry *rbe_const)
{
struct rb_entry *rbe = (struct rb_entry *)rbe_const;
if (RBE_LEFT(rbe)) {
rbe = RBE_LEFT(rbe);
while (RBE_RIGHT(rbe))
rbe = RBE_RIGHT(rbe);
} else {
if (RBE_PARENT(rbe) && (rbe == RBE_RIGHT(RBE_PARENT(rbe))))
rbe = RBE_PARENT(rbe);
else {
while (RBE_PARENT(rbe)
&& (rbe == RBE_LEFT(RBE_PARENT(rbe))))
rbe = RBE_PARENT(rbe);
rbe = RBE_PARENT(rbe);
}
}
return rbe;
}
struct rb_entry *typed_rb_min(const struct rbt_tree *rbt) struct rb_entry *typed_rb_min(const struct rbt_tree *rbt)
{ {
struct rb_entry *rbe = RBH_ROOT(rbt); struct rb_entry *rbe = RBH_ROOT(rbt);
@ -481,6 +503,19 @@ struct rb_entry *typed_rb_min(const struct rbt_tree *rbt)
return parent; return parent;
} }
struct rb_entry *typed_rb_max(const struct rbt_tree *rbt)
{
struct rb_entry *rbe = RBH_ROOT(rbt);
struct rb_entry *parent = NULL;
while (rbe != NULL) {
parent = rbe;
rbe = RBE_RIGHT(rbe);
}
return parent;
}
bool typed_rb_member(const struct typed_rb_root *rbt, bool typed_rb_member(const struct typed_rb_root *rbt,
const struct typed_rb_entry *rbe) const struct typed_rb_entry *rbe)
{ {

View file

@ -62,6 +62,8 @@ const struct typed_rb_entry *typed_rb_find_lt(const struct typed_rb_root *rbt,
const struct typed_rb_entry *a, const struct typed_rb_entry *a,
const struct typed_rb_entry *b)); const struct typed_rb_entry *b));
struct typed_rb_entry *typed_rb_min(const struct typed_rb_root *rbt); struct typed_rb_entry *typed_rb_min(const struct typed_rb_root *rbt);
struct typed_rb_entry *typed_rb_max(const struct typed_rb_root *rbt);
struct typed_rb_entry *typed_rb_prev(const struct typed_rb_entry *rbe);
struct typed_rb_entry *typed_rb_next(const struct typed_rb_entry *rbe); struct typed_rb_entry *typed_rb_next(const struct typed_rb_entry *rbe);
bool typed_rb_member(const struct typed_rb_root *rbt, bool typed_rb_member(const struct typed_rb_root *rbt,
const struct typed_rb_entry *rbe); const struct typed_rb_entry *rbe);
@ -135,12 +137,32 @@ macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
return container_of_null(re, type, field.re); \ return container_of_null(re, type, field.re); \
} \ } \
TYPESAFE_FIRST_NEXT(prefix, type) \ TYPESAFE_FIRST_NEXT(prefix, type) \
macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \
{ \
const struct typed_rb_entry *re; \
re = typed_rb_max(&h->rr); \
return container_of_null(re, type, field.re); \
} \
macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \
const type *item) \
{ \
const struct typed_rb_entry *re; \
re = typed_rb_prev(&item->field.re); \
return container_of_null(re, type, field.re); \
} \
TYPESAFE_LAST_PREV(prefix, type) \
macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
{ \ { \
struct typed_rb_entry *re; \ struct typed_rb_entry *re; \
re = item ? typed_rb_next(&item->field.re) : NULL; \ re = item ? typed_rb_next(&item->field.re) : NULL; \
return container_of_null(re, type, field.re); \ return container_of_null(re, type, field.re); \
} \ } \
macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \
{ \
struct typed_rb_entry *re; \
re = item ? typed_rb_prev(&item->field.re) : NULL; \
return container_of_null(re, type, field.re); \
} \
macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
{ \ { \
return h->rr.count; \ return h->rr.count; \

View file

@ -43,6 +43,22 @@ extern "C" {
item; \ item; \
item = from, from = prefix##_next_safe(head, from)) item = from, from = prefix##_next_safe(head, from))
/* reverse direction, only supported by a few containers */
#define frr_rev_each(prefix, head, item) \
for (item = prefix##_last(head); item; \
item = prefix##_prev(head, item))
#define frr_rev_each_safe(prefix, head, item) \
for (typeof(prefix##_prev_safe(head, NULL)) prefix##_safe = \
prefix##_prev_safe(head, \
(item = prefix##_last(head))); \
item; \
item = prefix##_safe, \
prefix##_safe = prefix##_prev_safe(head, prefix##_safe))
#define frr_rev_each_from(prefix, head, item, from) \
for (item = from, from = prefix##_prev_safe(head, item); \
item; \
item = from, from = prefix##_prev_safe(head, from))
/* non-const variants. these wrappers are the same for all the types, so /* non-const variants. these wrappers are the same for all the types, so
* bundle them together here. * bundle them together here.
@ -57,6 +73,16 @@ macro_pure type *prefix ## _next(struct prefix##_head *h, type *item) \
return (type *)prefix ## _const_next(h, item); \ return (type *)prefix ## _const_next(h, item); \
} \ } \
/* ... */ /* ... */
#define TYPESAFE_LAST_PREV(prefix, type) \
macro_pure type *prefix ## _last(struct prefix##_head *h) \
{ \
return (type *)prefix ## _const_last(h); \
} \
macro_pure type *prefix ## _prev(struct prefix##_head *h, type *item) \
{ \
return (type *)prefix ## _const_prev(h, item); \
} \
/* ... */
#define TYPESAFE_FIND(prefix, type) \ #define TYPESAFE_FIND(prefix, type) \
macro_inline type *prefix ## _find(struct prefix##_head *h, \ macro_inline type *prefix ## _find(struct prefix##_head *h, \
const type *item) \ const type *item) \
@ -398,12 +424,34 @@ macro_pure const type *prefix ## _const_next(const struct prefix##_head *h, \
return container_of(ditem->next, type, field.di); \ return container_of(ditem->next, type, field.di); \
} \ } \
TYPESAFE_FIRST_NEXT(prefix, type) \ TYPESAFE_FIRST_NEXT(prefix, type) \
macro_pure const type *prefix ## _const_last(const struct prefix##_head *h) \
{ \
const struct dlist_item *ditem = h->dh.hitem.prev; \
if (ditem == &h->dh.hitem) \
return NULL; \
return container_of(ditem, type, field.di); \
} \
macro_pure const type *prefix ## _const_prev(const struct prefix##_head *h, \
const type *item) \
{ \
const struct dlist_item *ditem = &item->field.di; \
if (ditem->prev == &h->dh.hitem) \
return NULL; \
return container_of(ditem->prev, type, field.di); \
} \
TYPESAFE_LAST_PREV(prefix, type) \
macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \ macro_pure type *prefix ## _next_safe(struct prefix##_head *h, type *item) \
{ \ { \
if (!item) \ if (!item) \
return NULL; \ return NULL; \
return prefix ## _next(h, item); \ return prefix ## _next(h, item); \
} \ } \
macro_pure type *prefix ## _prev_safe(struct prefix##_head *h, type *item) \
{ \
if (!item) \
return NULL; \
return prefix ## _prev(h, item); \
} \
macro_pure size_t prefix ## _count(const struct prefix##_head *h) \ macro_pure size_t prefix ## _count(const struct prefix##_head *h) \
{ \ { \
return h->dh.count; \ return h->dh.count; \

View file

@ -58,9 +58,10 @@
#define T_HASH (1 << 2) #define T_HASH (1 << 2)
#define T_HEAP (1 << 3) #define T_HEAP (1 << 3)
#define T_ATOMIC (1 << 4) #define T_ATOMIC (1 << 4)
#define T_REVERSE (1 << 5)
#define _T_LIST (0) #define _T_LIST (0)
#define _T_DLIST (0) #define _T_DLIST (0 | T_REVERSE)
#define _T_ATOMLIST (0 | T_ATOMIC) #define _T_ATOMLIST (0 | T_ATOMIC)
#define _T_HEAP (T_SORTED | T_HEAP) #define _T_HEAP (T_SORTED | T_HEAP)
#define _T_SORTLIST_UNIQ (T_SORTED | T_UNIQ) #define _T_SORTLIST_UNIQ (T_SORTED | T_UNIQ)
@ -68,8 +69,8 @@
#define _T_HASH (T_SORTED | T_UNIQ | T_HASH) #define _T_HASH (T_SORTED | T_UNIQ | T_HASH)
#define _T_SKIPLIST_UNIQ (T_SORTED | T_UNIQ) #define _T_SKIPLIST_UNIQ (T_SORTED | T_UNIQ)
#define _T_SKIPLIST_NONUNIQ (T_SORTED) #define _T_SKIPLIST_NONUNIQ (T_SORTED)
#define _T_RBTREE_UNIQ (T_SORTED | T_UNIQ) #define _T_RBTREE_UNIQ (T_SORTED | T_UNIQ | T_REVERSE)
#define _T_RBTREE_NONUNIQ (T_SORTED) #define _T_RBTREE_NONUNIQ (T_SORTED | T_REVERSE)
#define _T_ATOMSORT_UNIQ (T_SORTED | T_UNIQ | T_ATOMIC) #define _T_ATOMSORT_UNIQ (T_SORTED | T_UNIQ | T_ATOMIC)
#define _T_ATOMSORT_NONUNIQ (T_SORTED | T_ATOMIC) #define _T_ATOMSORT_NONUNIQ (T_SORTED | T_ATOMIC)
@ -79,6 +80,7 @@
#define IS_HASH(type) (_T_TYPE(type) & T_HASH) #define IS_HASH(type) (_T_TYPE(type) & T_HASH)
#define IS_HEAP(type) (_T_TYPE(type) & T_HEAP) #define IS_HEAP(type) (_T_TYPE(type) & T_HEAP)
#define IS_ATOMIC(type) (_T_TYPE(type) & T_ATOMIC) #define IS_ATOMIC(type) (_T_TYPE(type) & T_ATOMIC)
#define IS_REVERSE(type) (_T_TYPE(type) & T_REVERSE)
static struct timeval ref, ref0; static struct timeval ref, ref0;

View file

@ -31,6 +31,11 @@
#define list_const_next concat(TYPE, _const_next) #define list_const_next concat(TYPE, _const_next)
#define list_next concat(TYPE, _next) #define list_next concat(TYPE, _next)
#define list_next_safe concat(TYPE, _next_safe) #define list_next_safe concat(TYPE, _next_safe)
#define list_const_last concat(TYPE, _const_last)
#define list_last concat(TYPE, _last)
#define list_const_prev concat(TYPE, _const_prev)
#define list_prev concat(TYPE, _prev)
#define list_prev_safe concat(TYPE, _prev_safe)
#define list_count concat(TYPE, _count) #define list_count concat(TYPE, _count)
#define list_add concat(TYPE, _add) #define list_add concat(TYPE, _add)
#define list_add_head concat(TYPE, _add_head) #define list_add_head concat(TYPE, _add_head)
@ -171,6 +176,9 @@ static void concat(test_, TYPE)(void)
list_init(&head); list_init(&head);
assert(list_first(&head) == NULL); assert(list_first(&head) == NULL);
#if IS_REVERSE(REALTYPE)
assert(list_last(&head) == NULL);
#endif
ts_hash("init", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119"); ts_hash("init", "df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119");
@ -203,6 +211,10 @@ static void concat(test_, TYPE)(void)
assert(!list_first(&head)); assert(!list_first(&head));
assert(list_count(&other) == k); assert(list_count(&other) == k);
assert(list_first(&other) != NULL); assert(list_first(&other) != NULL);
#if IS_REVERSE(REALTYPE)
assert(!list_last(&head));
assert(list_last(&other) != NULL);
#endif
ts_hash_headx( ts_hash_headx(
&other, "swap1", &other, "swap1",
"a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838"); "a538546a6e6ab0484e925940aa8dd02fd934408bbaed8cb66a0721841584d838");
@ -269,13 +281,36 @@ static void concat(test_, TYPE)(void)
(void)cprev; (void)cprev;
#else #else
assert(!cprev || cprev->val < citem->val); assert(!cprev || cprev->val < citem->val);
#if IS_REVERSE(REALTYPE)
assert(list_const_prev(chead, citem) == cprev);
#endif
#endif #endif
cprev = citem; cprev = citem;
k++; k++;
} }
assert(list_count(chead) == k); assert(list_count(chead) == k);
#if IS_REVERSE(REALTYPE)
assert(cprev == list_const_last(chead));
#endif
ts_ref("walk"); ts_ref("walk");
#if IS_REVERSE(REALTYPE) && !IS_HASH(REALTYPE) && !IS_HEAP(REALTYPE)
cprev = NULL;
k = 0;
frr_rev_each(list_const, chead, citem) {
assert(!cprev || cprev->val > citem->val);
assert(list_const_next(chead, citem) == cprev);
cprev = citem;
k++;
}
assert(list_count(chead) == k);
assert(cprev == list_const_first(chead));
ts_ref("reverse-walk");
#endif
#if IS_UNIQ(REALTYPE) #if IS_UNIQ(REALTYPE)
prng_free(prng); prng_free(prng);
prng = prng_new(0); prng = prng_new(0);
@ -439,6 +474,9 @@ static void concat(test_, TYPE)(void)
} }
assert(list_count(&head) == k); assert(list_count(&head) == k);
assert(list_first(&head) != NULL); assert(list_first(&head) != NULL);
#if IS_REVERSE(REALTYPE)
assert(list_last(&head) != NULL);
#endif
ts_hash("fill / add_tail", "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); ts_hash("fill / add_tail", "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19");
#if !IS_ATOMIC(REALTYPE) #if !IS_ATOMIC(REALTYPE)
@ -451,6 +489,10 @@ static void concat(test_, TYPE)(void)
assert(!list_first(&head)); assert(!list_first(&head));
assert(list_count(&other) == k); assert(list_count(&other) == k);
assert(list_first(&other) != NULL); assert(list_first(&other) != NULL);
#if IS_REVERSE(REALTYPE)
assert(!list_last(&head));
assert(list_last(&other) != NULL);
#endif
ts_hash_head( ts_hash_head(
&other, "swap1", &other, "swap1",
"eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19"); "eabfcf1413936daaf20965abced95762f45110a6619b84aac7d38481bce4ea19");
@ -534,6 +576,21 @@ static void concat(test_, TYPE)(void)
} }
ts_hash("member", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d"); ts_hash("member", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d");
#endif #endif
#if IS_REVERSE(REALTYPE)
i = 0;
prev = NULL;
frr_rev_each (list, &head, item) {
assert(item->scratchpad != 0);
assert(list_next(&head, item) == prev);
i++;
prev = item;
}
assert(list_first(&head) == prev);
assert(list_count(&head) == i);
ts_hash("reverse-walk", "42b8950c880535b2d2e0c980f9845f7841ecf675c0fb9801aec4170d2036349d");
#endif
while ((item = list_pop(&head))) { while ((item = list_pop(&head))) {
assert(item->scratchpad != 0); assert(item->scratchpad != 0);
@ -746,6 +803,13 @@ static void concat(test_, TYPE)(void)
#undef list_first #undef list_first
#undef list_next #undef list_next
#undef list_next_safe #undef list_next_safe
#undef list_const_first
#undef list_const_next
#undef list_last
#undef list_prev
#undef list_prev_safe
#undef list_const_last
#undef list_const_prev
#undef list_count #undef list_count
#undef list_add #undef list_add
#undef list_add_head #undef list_add_head