Compare commits

...

2 Commits

Author SHA1 Message Date
Tony Finch
2cf07c8481 Detect compiler support for transparent unions
When we are using an inheritance pattern, where several structures
share a common prefix, we would like it to be easy to pass any of
these subtype structures to functions that only operate on the common
prefix, but we would also like it to be type safe.

A simple way is to use void pointers, because they can be freely
assigned without explicit casts, but this isn't type safe. We can use
_Generic macros to dispatch based on subtype, but that can be quite
ugly and awkward.

This change adds an ISC_ATTR_TRANSPARENT macro to indicate when we can
use GNU C transparent unions. Transparent unions have quiet
conversions like void pointers, but only for a specific set of types.
On compilers without transparent unions we can use void pointers
instead; we still benefit from type checking during development.
2023-02-15 16:50:34 +00:00
Tony Finch
70a0bf9090 Simple lock-free singly-linked lists
A singly-linked list that supports lock-free prepend and drain (to
empty the list and clean up its elements). Intended for use with QSBR
to collect objects that need safe memory reclamation.

In <isc/atomic.h>, add an `atomic_ptr()` macro to make type
declarations a little less abominable, and clean up a duplicate
definition of `atomic_compare_exchange_strong_acq_rel()`
2023-02-15 16:49:51 +00:00
5 changed files with 170 additions and 4 deletions

View File

@@ -1,3 +1,10 @@
6104. [port] Detect compiler support for transparent unions, to
improve the type safety of polymorphic functions.
[GL !7472]
6103. [func] Support for simple lock-free singly-linked lists.
[GL !7470]
6102. [cleanup] Several nugatory headers have been removed from libisc.
[GL !7464]

View File

@@ -649,7 +649,7 @@ More macros are provided for iterating the list:
isc_foo_t *foo;
for (foo = ISC_LIST_HEAD(foolist);
foo != NULL;
foo != NULL;
foo = ISC_LIST_NEXT(foo, link))
{
/* do things */
@@ -662,6 +662,44 @@ Items can be removed from the list using `ISC_LIST_UNLINK`:
ISC_LIST_UNLINK(foolist, foo, link);
##### Atomic lists
In `<isc/list.h>`, there are also similar macros for atomic
singly-linked lists.
In general, atomic linked lists have a number of pitfalls that these
macros avoid by having a restricted API. Firstly, it is difficult to
implement atomic doubly-linked lists without at least a double-width
compare-exchange, which is not a widely supported primitive, so this
list is singly-linked. Secondly, When individual elements can be
removed from a list (whether doubly-linked or singly-linked), we run
into the ABA problem, where removes and (re-)inserts race and as a
result the list gets in a tangle. Safe support for removing individual
elements requires some kind of garbage collection, or extending list
pointers with generation counters. Instead the ALIST macros only
provide a function for emptying the entire list in one go,
`ISC_ALIST_DRAIN()`. This only requires setting the `head` pointer to
`NULL`, which cannot cause `ISC_ALIST_PREPEND()` to go wrong.
The `ISC_ALIST` and `ISC_ALINK` macros are used the same way as the
non-atomic doubly-linked lists described above, except that not all of
the `ISC_LIST` macros have `ISC_ALIST` equivalents.
As well as read-only iteration as described above, there is an idiom
for emptying a list and processing its contents:
isc_foo_t *foo = ISC_ALIST_DRAIN(foolist);
while (foo != NULL) {
isc_foo_t *next = ISC_ALIST_TAKE(foo, link);
/* do things */
/* maybe ISC_ALIST_PREPEND(foolist, foo, link) */
foo = next;
}
There is also an `ISC_ALIST_ADD()` macro, which is a convenience
wrapper for prepending an element to a list if it has not already been
added.
#### <a name="names"></a>Names
The `dns_name` API has facilities for processing DNS names and labels,

View File

@@ -42,9 +42,6 @@
#define atomic_compare_exchange_strong_relaxed(o, e, d) \
atomic_compare_exchange_strong_explicit( \
(o), (e), (d), memory_order_relaxed, memory_order_relaxed)
#define atomic_compare_exchange_strong_acq_rel(o, e, d) \
atomic_compare_exchange_strong_explicit( \
(o), (e), (d), memory_order_acq_rel, memory_order_acquire)
/* Acquire-Release Memory Ordering */
@@ -73,3 +70,6 @@
/* compare/exchange that MUST succeed */
#define atomic_compare_exchange_enforced(o, e, d) \
RUNTIME_CHECK(atomic_compare_exchange_strong((o), (e), (d)))
/* more comfortable atomic pointer declarations */
#define atomic_ptr(type) _Atomic(type *)

View File

@@ -80,3 +80,23 @@
#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator)
#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx)
#endif /* HAVE_FUNC_ATTRIBUTE_MALLOC */
/*
* When we are using an inheritance pattern (where several structures
* share a common prefix) we would like it to be easy to pass any of
* these subtype structures to functions that only operate on the
* common prefix, and we would also like it to be type safe.
*
* GNU C transparent unions have quiet conversions like void pointers,
* but only for a specific set of types. So they make it easy to pass
* just the known subtypes to a function that operates on their common
* prefix.
*
* On compilers without transparent unions we can use void pointers
* instead; we still benefit from type checking during development.
*/
#ifdef __has_attribute
#if __has_attribute(__transparent_union__)
#define ISC_ATTR_TRANSPARENT __attribute__((__transparent_union__))
#endif
#endif

View File

@@ -14,6 +14,8 @@
#pragma once
#include <isc/assertions.h>
#include <isc/atomic.h>
#include <isc/pause.h>
#define ISC_LINK_TOMBSTONE(type) ((type *)-1)
@@ -227,3 +229,102 @@
INSIST(ISC_LIST_EMPTY(dest)); \
ISC_LIST_MOVEUNSAFE(dest, src); \
}
/*
* An atomic list has an atomic pointer at its head so that multiple
* threads can add elements without locking (though they may need to
* wait briefly when there is contention). We sidestep the ABA problem
* which occurs when elements can be removed individually: instead the
* whole list must be drained. This is also why operations on `next`
* pointers are not atomic.
*
* The pointers are wrapped in structs so that their types are
* distinct, and to match the ISC_LIST macros.
*
* See doc/dev/dev.md for examples.
*/
#define ISC_ALIST(type) \
struct { \
atomic_ptr(type) head; \
}
#define ISC_ALIST_INITIALIZER \
{ \
.head = NULL, \
}
#define ISC_ALIST_INIT(list) \
do { \
(list).head = NULL; \
} while (0)
#define ISC_ALINK(type) \
struct { \
type *next; \
}
#define ISC_ALINK_INITIALIZER_TYPE(type) \
{ \
.next = ISC_LINK_TOMBSTONE(type), \
}
#define ISC_ALINK_INIT_TYPE(elt, link, type) \
do { \
(elt)->link.next = ISC_LINK_TOMBSTONE(type); \
} while (0)
#define ISC_ALINK_INITIALIZER ISC_ALINK_INITIALIZER_TYPE(void)
#define ISC_ALINK_INIT(elt, link) ISC_ALINK_INIT_TYPE(elt, link, void)
#define ISC_ALIST_NEXT(elt, link) ((elt)->link.next)
#define ISC_ALINK_LINKED_TYPE(elt, link, type) \
(ISC_ALIST_NEXT(elt, link) != ISC_LINK_TOMBSTONE(type))
#define ISC_ALINK_LINKED(elt, link) ISC_ALINK_LINKED_TYPE(elt, link, void)
#define ISC_ALIST_TAKE_TYPE(elt, link, type) \
({ \
type *__next = ISC_ALIST_NEXT(elt, link); \
ISC_ALINK_INIT_TYPE(elt, link, type); \
__next; \
})
#define ISC_ALIST_TAKE(elt, link) ISC_ALIST_TAKE_TYPE(elt, link, void)
/*
* ATOMIC: for performance, this kind of retry loop should use a weak
* CAS with relaxed ordering in the failure case; on success, release
* ordering ensures that writing the element contents happens before
* reading them, following the acquire in ISC_ALIST_HEAD() and _DRAIN().
*/
#define __ISC_ALIST_PREPENDUNSAFE(list, elt, link) \
do { \
(elt)->link.next = atomic_load_relaxed(&(list).head); \
while (!atomic_compare_exchange_weak_explicit( \
&(list).head, &(elt)->link.next, elt, \
memory_order_release, memory_order_relaxed)) \
{ \
isc_pause(); \
} \
} while (0)
#define ISC_ALIST_PREPEND(list, elt, link) \
do { \
ISC_LINK_INSIST(!ISC_ALINK_LINKED(elt, link)); \
__ISC_ALIST_PREPENDUNSAFE(list, elt, link); \
} while (0)
#define ISC_ALIST_ADD(list, elt, link) \
do { \
if (!ISC_ALINK_LINKED(elt, link)) { \
__ISC_ALIST_PREPENDUNSAFE(list, elt, link); \
} \
} while (0)
/*
* ATOMIC: acquire ordering pairs with __ISC_ALIST_PREPENDUNSAFE()
*/
#define ISC_ALIST_HEAD(list) atomic_load_acquire(&(list).head)
/*
* ATOMIC: acquire ordering pairs with __ISC_ALIST_PREPENDUNSAFE()
*/
#define ISC_ALIST_DRAIN(list) \
atomic_exchange_explicit(&(list).head, NULL, memory_order_acquire)