diff options
author | David Robillard <d@drobilla.net> | 2022-08-10 05:32:34 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2022-08-17 13:51:03 -0400 |
commit | 06b49a3419f35ee8c802f1f15b52cc45b546efb3 (patch) | |
tree | 6a331324a73a83d6ff4d10087cdc740c3f1af8aa /src/zix | |
parent | c17c67cdba3045be3c4a0f65dac0039987e0d8cf (diff) | |
download | jalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.tar.gz jalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.tar.bz2 jalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.zip |
Update zix
Diffstat (limited to 'src/zix')
-rw-r--r-- | src/zix/allocator.c | 96 | ||||
-rw-r--r-- | src/zix/allocator.h | 186 | ||||
-rw-r--r-- | src/zix/attributes.h | 81 | ||||
-rw-r--r-- | src/zix/ring.c | 239 | ||||
-rw-r--r-- | src/zix/ring.h | 141 | ||||
-rw-r--r-- | src/zix/sem.h | 58 | ||||
-rw-r--r-- | src/zix/thread.h | 4 |
7 files changed, 660 insertions, 145 deletions
diff --git a/src/zix/allocator.c b/src/zix/allocator.c new file mode 100644 index 0000000..310ef33 --- /dev/null +++ b/src/zix/allocator.c @@ -0,0 +1,96 @@ +// Copyright 2011-2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#include "zix/allocator.h" + +#include "zix/attributes.h" + +#include "jalv_config.h" + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN 1 +# include <malloc.h> +# include <windows.h> +#endif + +#include <stdlib.h> + +ZIX_MALLOC_FUNC +static void* +zix_default_malloc(ZixAllocator* const allocator, const size_t size) +{ + (void)allocator; + return malloc(size); +} + +ZIX_MALLOC_FUNC +static void* +zix_default_calloc(ZixAllocator* const allocator, + const size_t nmemb, + const size_t size) +{ + (void)allocator; + return calloc(nmemb, size); +} + +static void* +zix_default_realloc(ZixAllocator* const allocator, + void* const ptr, + const size_t size) +{ + (void)allocator; + return realloc(ptr, size); +} + +static void +zix_default_free(ZixAllocator* const allocator, void* const ptr) +{ + (void)allocator; + free(ptr); +} + +ZIX_MALLOC_FUNC +static void* +zix_default_aligned_alloc(ZixAllocator* const allocator, + const size_t alignment, + const size_t size) +{ + (void)allocator; + +#if defined(_WIN32) + return _aligned_malloc(size, alignment); +#elif USE_POSIX_MEMALIGN + void* ptr = NULL; + const int ret = posix_memalign(&ptr, alignment, size); + return ret ? NULL : ptr; +#else + return NULL; +#endif +} + +static void +zix_default_aligned_free(ZixAllocator* const allocator, void* const ptr) +{ + (void)allocator; + +#if defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +ZixAllocator* +zix_default_allocator(void) +{ + static ZixAllocator default_allocator = { + zix_default_malloc, + zix_default_calloc, + zix_default_realloc, + zix_default_free, + zix_default_aligned_alloc, + zix_default_aligned_free, + }; + + return &default_allocator; +} diff --git a/src/zix/allocator.h b/src/zix/allocator.h new file mode 100644 index 0000000..bcffda5 --- /dev/null +++ b/src/zix/allocator.h @@ -0,0 +1,186 @@ +// Copyright 2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#ifndef ZIX_ALLOCATOR_H +#define ZIX_ALLOCATOR_H + +#include "zix/attributes.h" + +#include <stddef.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + @addtogroup zix + @{ + @name Allocator + @{ +*/ + +struct ZixAllocatorImpl; + +/** + A memory allocator. + + This object-like structure provides an interface like the standard C + functions malloc(), calloc(), realloc(), free(), and aligned_alloc(). It + contains function pointers that differ from their standard counterparts by + taking a context parameter (a pointer to this struct), which allows the user + to implement custom stateful allocators. +*/ +typedef struct ZixAllocatorImpl ZixAllocator; + +/** + General malloc-like memory allocation function. + + This works like the standard C malloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*ZixMallocFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + size_t size); + +/** + General calloc-like memory allocation function. + + This works like the standard C calloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*ZixCallocFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + size_t nmemb, + size_t size); + +/** + General realloc-like memory reallocation function. + + This works like the standard C remalloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*ZixReallocFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr, + size_t size); + +/** + General free-like memory deallocation function. + + This works like the standard C remalloc(), except has an additional handle + parameter for implementing stateful allocators without static data. +*/ +typedef void (*ZixFreeFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr); + +/** + General aligned_alloc-like memory deallocation function. + + This works like the standard C aligned_alloc(), except has an additional + handle parameter for implementing stateful allocators without static data. +*/ +typedef void* ZIX_ALLOCATED (*ZixAlignedAllocFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + size_t alignment, + size_t size); + +/** + General aligned memory deallocation function. + + This works like the standard C free(), but must be used to free memory + allocated with the aligned_alloc() method of the allocator. This allows + portability to systems (like Windows) that can not use the same free function + in these cases. +*/ +typedef void (*ZixAlignedFreeFunc)( // + ZixAllocator* ZIX_NULLABLE allocator, + void* ZIX_NULLABLE ptr); + +/// Definition of ZixAllocator +struct ZixAllocatorImpl { + ZixMallocFunc ZIX_NONNULL malloc; + ZixCallocFunc ZIX_NONNULL calloc; + ZixReallocFunc ZIX_NONNULL realloc; + ZixFreeFunc ZIX_NONNULL free; + ZixAlignedAllocFunc ZIX_NONNULL aligned_alloc; + ZixAlignedFreeFunc ZIX_NONNULL aligned_free; +}; + +/// Return the default allocator which simply uses the system allocator +ZIX_CONST_API +ZixAllocator* ZIX_NONNULL +zix_default_allocator(void); + +/// Convenience wrapper that defers to malloc() if allocator is null +static inline void* ZIX_ALLOCATED +zix_malloc(ZixAllocator* const ZIX_NULLABLE allocator, const size_t size) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + return actual->malloc(actual, size); +} + +/// Convenience wrapper that defers to calloc() if allocator is null +static inline void* ZIX_ALLOCATED +zix_calloc(ZixAllocator* const ZIX_NULLABLE allocator, + const size_t nmemb, + const size_t size) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + return actual->calloc(actual, nmemb, size); +} + +/// Convenience wrapper that defers to realloc() if allocator is null +static inline void* ZIX_ALLOCATED +zix_realloc(ZixAllocator* const ZIX_NULLABLE allocator, + void* const ZIX_NULLABLE ptr, + const size_t size) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + return actual->realloc(actual, ptr, size); +} + +/// Convenience wrapper that defers to free() if allocator is null +static inline void +zix_free(ZixAllocator* const ZIX_NULLABLE allocator, + void* const ZIX_NULLABLE ptr) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + actual->free(actual, ptr); +} + +/// Convenience wrapper that defers to the system allocator if allocator is null +static inline void* ZIX_ALLOCATED +zix_aligned_alloc(ZixAllocator* const ZIX_NULLABLE allocator, + const size_t alignment, + const size_t size) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + return actual->aligned_alloc(actual, alignment, size); +} + +/// Convenience wrapper that defers to the system allocator if allocator is null +static inline void +zix_aligned_free(ZixAllocator* const ZIX_NULLABLE allocator, + void* const ZIX_NULLABLE ptr) +{ + ZixAllocator* const actual = allocator ? allocator : zix_default_allocator(); + + actual->aligned_free(actual, ptr); +} + +/** + @} + @} +*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ZIX_ALLOCATOR_H */ diff --git a/src/zix/attributes.h b/src/zix/attributes.h new file mode 100644 index 0000000..602fac8 --- /dev/null +++ b/src/zix/attributes.h @@ -0,0 +1,81 @@ +// Copyright 2021 David Robillard <d@drobilla.net> +// SPDX-License-Identifier: ISC + +#ifndef ZIX_ATTRIBUTES_H +#define ZIX_ATTRIBUTES_H + +/** + @addtogroup zix + @{ +*/ + +// ZIX_API must be used to decorate things in the public API +#ifndef ZIX_API +# if defined(_WIN32) && !defined(ZIX_STATIC) && defined(ZIX_INTERNAL) +# define ZIX_API __declspec(dllexport) +# elif defined(_WIN32) && !defined(ZIX_STATIC) +# define ZIX_API __declspec(dllimport) +# elif defined(__GNUC__) +# define ZIX_API __attribute__((visibility("default"))) +# else +# define ZIX_API +# endif +#endif + +// GCC pure/const/malloc attributes +#ifdef __GNUC__ +# define ZIX_PURE_FUNC __attribute__((pure)) +# define ZIX_CONST_FUNC __attribute__((const)) +# define ZIX_MALLOC_FUNC __attribute__((malloc)) +#else +# define ZIX_PURE_FUNC +# define ZIX_CONST_FUNC +# define ZIX_MALLOC_FUNC +#endif + +#define ZIX_PURE_API \ + ZIX_API \ + ZIX_PURE_FUNC + +#define ZIX_CONST_API \ + ZIX_API \ + ZIX_CONST_FUNC + +#define ZIX_MALLOC_API \ + ZIX_API \ + ZIX_MALLOC_FUNC + +// Printf-like format functions +#ifdef __GNUC__ +# define ZIX_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) +#else +# define ZIX_LOG_FUNC(fmt, arg1) +#endif + +// Unused parameter macro to suppresses warnings and make it impossible to use +#if defined(__cplusplus) +# define ZIX_UNUSED(name) +#elif defined(__GNUC__) +# define ZIX_UNUSED(name) name##_unused __attribute__((__unused__)) +#elif defined(_MSC_VER) +# define ZIX_UNUSED(name) __pragma(warning(suppress : 4100)) name +#else +# define ZIX_UNUSED(name) name +#endif + +// Clang nullability annotations +#if defined(__clang__) && __clang_major__ >= 7 +# define ZIX_NONNULL _Nonnull +# define ZIX_NULLABLE _Nullable +# define ZIX_ALLOCATED _Null_unspecified +#else +# define ZIX_NONNULL +# define ZIX_NULLABLE +# define ZIX_ALLOCATED +#endif + +/** + @} +*/ + +#endif /* ZIX_ATTRIBUTES_H */ diff --git a/src/zix/ring.c b/src/zix/ring.c index a48153a..d2d3195 100644 --- a/src/zix/ring.c +++ b/src/zix/ring.c @@ -1,12 +1,15 @@ -// Copyright 2011-2020 David Robillard <d@drobilla.net> +// Copyright 2011-2022 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC #include "zix/ring.h" +#include "zix/common.h" + +#include "jalv_config.h" #include <stdlib.h> #include <string.h> -#if HAVE_MLOCK +#if USE_MLOCK # include <sys/mman.h> # define ZIX_MLOCK(ptr, size) mlock((ptr), (size)) #elif defined(_WIN32) @@ -17,124 +20,156 @@ # define ZIX_MLOCK(ptr, size) #endif +/* + Note that for simplicity, only x86 and x64 are supported with MSVC. Hopefully + stdatomic.h support arrives before anyone cares about running this code on + Windows on ARM. +*/ #if defined(_MSC_VER) -# include <windows.h> -# define ZIX_READ_BARRIER() MemoryBarrier() -# define ZIX_WRITE_BARRIER() MemoryBarrier() -#elif defined(__GNUC__) -# define ZIX_READ_BARRIER() __atomic_thread_fence(__ATOMIC_ACQUIRE) -# define ZIX_WRITE_BARRIER() __atomic_thread_fence(__ATOMIC_RELEASE) -#else -# pragma message("warning: No memory barriers, possible SMP bugs") -# define ZIX_READ_BARRIER() -# define ZIX_WRITE_BARRIER() +# include <intrin.h> #endif struct ZixRingImpl { - uint32_t write_head; ///< Read index into buf - uint32_t read_head; ///< Write index into buf - uint32_t size; ///< Size (capacity) in bytes - uint32_t size_mask; ///< Mask for fast modulo - char* buf; ///< Contents + ZixAllocator* allocator; ///< User allocator + uint32_t write_head; ///< Read index into buf + uint32_t read_head; ///< Write index into buf + uint32_t size; ///< Size (capacity) in bytes + uint32_t size_mask; ///< Mask for fast modulo + char* buf; ///< Contents }; static inline uint32_t +zix_atomic_load(const uint32_t* const ptr) +{ +#if defined(_MSC_VER) + const uint32_t val = *ptr; + _ReadBarrier(); + return val; +#else + return __atomic_load_n(ptr, __ATOMIC_ACQUIRE); +#endif +} + +static inline void +zix_atomic_store(uint32_t* const ptr, // NOLINT(readability-non-const-parameter) + const uint32_t val) +{ +#if defined(_MSC_VER) + _WriteBarrier(); + *ptr = val; +#else + __atomic_store_n(ptr, val, __ATOMIC_RELEASE); +#endif +} + +static inline uint32_t next_power_of_two(uint32_t size) { // http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 size--; - size |= size >> 1u; - size |= size >> 2u; - size |= size >> 4u; - size |= size >> 8u; - size |= size >> 16u; + size |= size >> 1U; + size |= size >> 2U; + size |= size >> 4U; + size |= size >> 8U; + size |= size >> 16U; size++; return size; } ZixRing* -zix_ring_new(uint32_t size) +zix_ring_new(ZixAllocator* const allocator, const uint32_t size) { - ZixRing* ring = (ZixRing*)malloc(sizeof(ZixRing)); - ring->write_head = 0; - ring->read_head = 0; - ring->size = next_power_of_two(size); - ring->size_mask = ring->size - 1; - ring->buf = (char*)malloc(ring->size); + ZixRing* ring = (ZixRing*)zix_malloc(allocator, sizeof(ZixRing)); + + if (ring) { + ring->allocator = allocator; + ring->write_head = 0; + ring->read_head = 0; + ring->size = next_power_of_two(size); + ring->size_mask = ring->size - 1; + + if (!(ring->buf = (char*)zix_malloc(allocator, ring->size))) { + zix_free(allocator, ring); + return NULL; + } + } + return ring; } void -zix_ring_free(ZixRing* ring) +zix_ring_free(ZixRing* const ring) { if (ring) { - free(ring->buf); - free(ring); + zix_free(ring->allocator, ring->buf); + zix_free(ring->allocator, ring); } } void -zix_ring_mlock(ZixRing* ring) +zix_ring_mlock(ZixRing* const ring) { ZIX_MLOCK(ring, sizeof(ZixRing)); ZIX_MLOCK(ring->buf, ring->size); } void -zix_ring_reset(ZixRing* ring) +zix_ring_reset(ZixRing* const ring) { ring->write_head = 0; ring->read_head = 0; } +/* + General pattern for public thread-safe functions below: start with a single + atomic load of the "other's" index, then do whatever work, and finally end + with a single atomic store to "your" index (if it is changed). +*/ + static inline uint32_t -read_space_internal(const ZixRing* ring, uint32_t r, uint32_t w) +read_space_internal(const ZixRing* const ring, + const uint32_t r, + const uint32_t w) { - if (r < w) { - return w - r; - } - - return (w - r + ring->size) & ring->size_mask; + return (w - r) & ring->size_mask; } uint32_t -zix_ring_read_space(const ZixRing* ring) +zix_ring_read_space(const ZixRing* const ring) { - return read_space_internal(ring, ring->read_head, ring->write_head); + const uint32_t w = zix_atomic_load(&ring->write_head); + + return read_space_internal(ring, ring->read_head, w); } static inline uint32_t -write_space_internal(const ZixRing* ring, uint32_t r, uint32_t w) +write_space_internal(const ZixRing* const ring, + const uint32_t r, + const uint32_t w) { - if (r == w) { - return ring->size - 1; - } - - if (r < w) { - return ((r - w + ring->size) & ring->size_mask) - 1; - } - - return (r - w) - 1; + return (r - w - 1U) & ring->size_mask; } uint32_t -zix_ring_write_space(const ZixRing* ring) +zix_ring_write_space(const ZixRing* const ring) { - return write_space_internal(ring, ring->read_head, ring->write_head); + const uint32_t r = zix_atomic_load(&ring->read_head); + + return write_space_internal(ring, r, ring->write_head); } uint32_t -zix_ring_capacity(const ZixRing* ring) +zix_ring_capacity(const ZixRing* const ring) { return ring->size - 1; } static inline uint32_t -peek_internal(const ZixRing* ring, - uint32_t r, - uint32_t w, - uint32_t size, - void* dst) +peek_internal(const ZixRing* const ring, + const uint32_t r, + const uint32_t w, + const uint32_t size, + void* const dst) { if (read_space_internal(ring, r, w) < size) { return 0; @@ -152,59 +187,91 @@ peek_internal(const ZixRing* ring, } uint32_t -zix_ring_peek(ZixRing* ring, void* dst, uint32_t size) +zix_ring_peek(ZixRing* const ring, void* const dst, const uint32_t size) { - return peek_internal(ring, ring->read_head, ring->write_head, size, dst); + const uint32_t w = zix_atomic_load(&ring->write_head); + + return peek_internal(ring, ring->read_head, w, size, dst); } uint32_t -zix_ring_read(ZixRing* ring, void* dst, uint32_t size) +zix_ring_read(ZixRing* const ring, void* const dst, const uint32_t size) { + const uint32_t w = zix_atomic_load(&ring->write_head); const uint32_t r = ring->read_head; - const uint32_t w = ring->write_head; - - if (peek_internal(ring, r, w, size, dst)) { - ZIX_READ_BARRIER(); - ring->read_head = (r + size) & ring->size_mask; - return size; + if (!peek_internal(ring, r, w, size, dst)) { + return 0; } - return 0; + zix_atomic_store(&ring->read_head, (r + size) & ring->size_mask); + return size; } uint32_t -zix_ring_skip(ZixRing* ring, uint32_t size) +zix_ring_skip(ZixRing* const ring, const uint32_t size) { + const uint32_t w = zix_atomic_load(&ring->write_head); const uint32_t r = ring->read_head; - const uint32_t w = ring->write_head; if (read_space_internal(ring, r, w) < size) { return 0; } - ZIX_READ_BARRIER(); - ring->read_head = (r + size) & ring->size_mask; + zix_atomic_store(&ring->read_head, (r + size) & ring->size_mask); return size; } -uint32_t -zix_ring_write(ZixRing* ring, const void* src, uint32_t size) +ZixRingTransaction +zix_ring_begin_write(ZixRing* const ring) { - const uint32_t r = ring->read_head; + const uint32_t r = zix_atomic_load(&ring->read_head); const uint32_t w = ring->write_head; + + const ZixRingTransaction tx = {r, w}; + return tx; +} + +ZixStatus +zix_ring_amend_write(ZixRing* const ring, + ZixRingTransaction* const tx, + const void* const src, + const uint32_t size) +{ + const uint32_t r = tx->read_head; + const uint32_t w = tx->write_head; if (write_space_internal(ring, r, w) < size) { - return 0; + return ZIX_STATUS_NO_MEM; } - if (w + size <= ring->size) { + const uint32_t end = w + size; + if (end <= ring->size) { memcpy(&ring->buf[w], src, size); - ZIX_WRITE_BARRIER(); - ring->write_head = (w + size) & ring->size_mask; + tx->write_head = end & ring->size_mask; } else { - const uint32_t this_size = ring->size - w; - memcpy(&ring->buf[w], src, this_size); - memcpy(&ring->buf[0], (const char*)src + this_size, size - this_size); - ZIX_WRITE_BARRIER(); - ring->write_head = size - this_size; + const uint32_t size1 = ring->size - w; + const uint32_t size2 = size - size1; + memcpy(&ring->buf[w], src, size1); + memcpy(&ring->buf[0], (const char*)src + size1, size2); + tx->write_head = size2; + } + + return ZIX_STATUS_SUCCESS; +} + +ZixStatus +zix_ring_commit_write(ZixRing* const ring, const ZixRingTransaction* const tx) +{ + zix_atomic_store(&ring->write_head, tx->write_head); + return ZIX_STATUS_SUCCESS; +} + +uint32_t +zix_ring_write(ZixRing* const ring, const void* src, const uint32_t size) +{ + ZixRingTransaction tx = zix_ring_begin_write(ring); + + if (zix_ring_amend_write(ring, &tx, src, size) || + zix_ring_commit_write(ring, &tx)) { + return 0; } return size; diff --git a/src/zix/ring.h b/src/zix/ring.h index 8aa9a08..872f963 100644 --- a/src/zix/ring.h +++ b/src/zix/ring.h @@ -1,9 +1,11 @@ -// Copyright 2011-2020 David Robillard <d@drobilla.net> +// Copyright 2011-2022 David Robillard <d@drobilla.net> // SPDX-License-Identifier: ISC #ifndef ZIX_RING_H #define ZIX_RING_H +#include "zix/allocator.h" +#include "zix/attributes.h" #include "zix/common.h" #include <stdint.h> @@ -22,99 +24,184 @@ extern "C" { /** A lock-free ring buffer. - Thread-safe with a single reader and single writer, and realtime safe - on both ends. + Thread-safe (with a few noted exceptions) for a single reader and single + writer, and realtime-safe on both ends. */ typedef struct ZixRingImpl ZixRing; /** + A transaction for writing data in multiple parts. + + The simple zix_ring_write() can be used to write an atomic message that will + immediately be visible to the reader, but transactions allow data to be + written in several chunks before being "committed" and becoming readable. + This can be useful for things like prefixing messages with a header without + needing an allocated buffer to construct the "packet". +*/ +typedef struct { + uint32_t read_head; ///< Read head at the start of the transaction + uint32_t write_head; ///< Write head if the transaction were committed +} ZixRingTransaction; + +/** Create a new ring. @param size Size in bytes (note this may be rounded up). At most `size` - 1 bytes may be stored in the ring at once. */ ZIX_MALLOC_API -ZixRing* -zix_ring_new(uint32_t size); +ZixRing* ZIX_ALLOCATED +zix_ring_new(ZixAllocator* ZIX_NULLABLE allocator, uint32_t size); -/** - Destroy a ring. -*/ +/// Destroy a ring ZIX_API void -zix_ring_free(ZixRing* ring); +zix_ring_free(ZixRing* ZIX_NULLABLE ring); /** Lock the ring data into physical memory. This function is NOT thread safe or real-time safe, but it should be called after zix_ring_new() to lock all ring memory to avoid page faults while - using the ring (i.e. this function MUST be called first in order for the - ring to be truly real-time safe). - + using the ring. */ ZIX_API void -zix_ring_mlock(ZixRing* ring); +zix_ring_mlock(ZixRing* ZIX_NONNULL ring); /** Reset (empty) a ring. - This function is NOT thread-safe, it may only be called when there are no - readers or writers. + This function is NOT thread-safe, it may only be called when there is no + reader or writer. */ ZIX_API void -zix_ring_reset(ZixRing* ring); +zix_ring_reset(ZixRing* ZIX_NONNULL ring); /** Return the number of bytes of space available for reading. + + Reader only. */ -ZIX_CONST_API +ZIX_PURE_API uint32_t -zix_ring_read_space(const ZixRing* ring); +zix_ring_read_space(const ZixRing* ZIX_NONNULL ring); /** Return the number of bytes of space available for writing. + + Writer only. */ -ZIX_CONST_API +ZIX_PURE_API uint32_t -zix_ring_write_space(const ZixRing* ring); +zix_ring_write_space(const ZixRing* ZIX_NONNULL ring); /** - Return the capacity (i.e. total write space when empty). + Return the capacity (the total write space when empty). + + This function returns a constant for any given ring, and may (but usually + shouldn't) be called anywhere. */ -ZIX_CONST_API +ZIX_PURE_API uint32_t -zix_ring_capacity(const ZixRing* ring); +zix_ring_capacity(const ZixRing* ZIX_NONNULL ring); /** Read from the ring without advancing the read head. + + Reader only. */ ZIX_API uint32_t -zix_ring_peek(ZixRing* ring, void* dst, uint32_t size); +zix_ring_peek(ZixRing* ZIX_NONNULL ring, void* ZIX_NONNULL dst, uint32_t size); /** Read from the ring and advance the read head. + + Reader only. */ ZIX_API uint32_t -zix_ring_read(ZixRing* ring, void* dst, uint32_t size); +zix_ring_read(ZixRing* ZIX_NONNULL ring, void* ZIX_NONNULL dst, uint32_t size); /** Skip data in the ring (advance read head without reading). + + Reader only. */ ZIX_API uint32_t -zix_ring_skip(ZixRing* ring, uint32_t size); +zix_ring_skip(ZixRing* ZIX_NONNULL ring, uint32_t size); /** Write data to the ring. + + Writer only. */ ZIX_API uint32_t -zix_ring_write(ZixRing* ring, const void* src, uint32_t size); +zix_ring_write(ZixRing* ZIX_NONNULL ring, + const void* ZIX_NONNULL src, + uint32_t size); + +/** + Begin a write. + + The returned transaction is initially empty. Data can be written to it by + calling zix_ring_amend_write() one or more times, then finishing with + zix_ring_commit_write(). + + Note that the returned "transaction" is not meant to be long-lived: a call + to this function should be (more or less) immediately followed by calls to + zix_ring_amend_write() then a call to zix_ring_commit_write(). + + @param ring The ring to write data to. + @return A new empty transaction. +*/ +ZixRingTransaction +zix_ring_begin_write(ZixRing* ZIX_NONNULL ring); + +/** + Amend the current write with some data. + + The data is written immediately after the previously amended data, as if + they were written contiguously with a single write call. This data is not + visible to the reader until zix_ring_commit_write() is called. + + If any call to this function returns an error, then the transaction is + invalid and must not be committed. No cleanup is necessary for an invalid + transaction. Any bytes written while attempting the transaction will remain + in the free portion of the buffer and be overwritten by subsequent writes. + + @param ring The ring this transaction is writing to. + @param tx The active transaction, from zix_ring_begin_write(). + @param src Pointer to the data to write. + @param size Length of data to write in bytes. + @return #ZIX_STATUS_NO_MEM or #ZIX_STATUS_SUCCESS. +*/ +ZixStatus +zix_ring_amend_write(ZixRing* ZIX_NONNULL ring, + ZixRingTransaction* ZIX_NONNULL tx, + const void* ZIX_NONNULL src, + uint32_t size); + +/** + Commit the current write. + + This atomically updates the state of the ring, so that the reader will + observe the data written during the transaction. + + This function usually shouldn't be called for any transaction which + zix_ring_amend_write() returned an error for. + + @param ring The ring this transaction is writing to. + @param tx The active transaction, from zix_ring_begin_write(). + @return #ZIX_STATUS_SUCCESS. +*/ +ZixStatus +zix_ring_commit_write(ZixRing* ZIX_NONNULL ring, + const ZixRingTransaction* ZIX_NONNULL tx); /** @} diff --git a/src/zix/sem.h b/src/zix/sem.h index fdd57ab..0337f03 100644 --- a/src/zix/sem.h +++ b/src/zix/sem.h @@ -4,6 +4,7 @@ #ifndef ZIX_SEM_H #define ZIX_SEM_H +#include "zix/attributes.h" #include "zix/common.h" #ifdef __APPLE__ @@ -50,31 +51,29 @@ struct ZixSemImpl; */ typedef struct ZixSemImpl ZixSem; -/** - Create and initialize `sem` to `initial`. -*/ +/// Create and initialize `sem` to `initial` static inline ZixStatus -zix_sem_init(ZixSem* sem, unsigned initial); +zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned initial); -/** - Destroy `sem`. -*/ +/// Destroy `sem` static inline void -zix_sem_destroy(ZixSem* sem); +zix_sem_destroy(ZixSem* ZIX_NONNULL sem); /** - Increment (and signal any waiters). + Increment and signal any waiters. + Realtime safe. */ static inline void -zix_sem_post(ZixSem* sem); +zix_sem_post(ZixSem* ZIX_NONNULL sem); /** Wait until count is > 0, then decrement. + Obviously not realtime safe. */ static inline ZixStatus -zix_sem_wait(ZixSem* sem); +zix_sem_wait(ZixSem* ZIX_NONNULL sem); /** Non-blocking version of wait(). @@ -82,7 +81,7 @@ zix_sem_wait(ZixSem* sem); @return true if decrement was successful (lock was acquired). */ static inline bool -zix_sem_try_wait(ZixSem* sem); +zix_sem_try_wait(ZixSem* ZIX_NONNULL sem); /** @cond @@ -95,27 +94,28 @@ struct ZixSemImpl { }; static inline ZixStatus -zix_sem_init(ZixSem* sem, unsigned val) +zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned val) { - return semaphore_create(mach_task_self(), &sem->sem, SYNC_POLICY_FIFO, val) + return semaphore_create( + mach_task_self(), &sem->sem, SYNC_POLICY_FIFO, (int)val) ? ZIX_STATUS_ERROR : ZIX_STATUS_SUCCESS; } static inline void -zix_sem_destroy(ZixSem* sem) +zix_sem_destroy(ZixSem* ZIX_NONNULL sem) { semaphore_destroy(mach_task_self(), sem->sem); } static inline void -zix_sem_post(ZixSem* sem) +zix_sem_post(ZixSem* ZIX_NONNULL sem) { semaphore_signal(sem->sem); } static inline ZixStatus -zix_sem_wait(ZixSem* sem) +zix_sem_wait(ZixSem* ZIX_NONNULL sem) { if (semaphore_wait(sem->sem) != KERN_SUCCESS) { return ZIX_STATUS_ERROR; @@ -124,7 +124,7 @@ zix_sem_wait(ZixSem* sem) } static inline bool -zix_sem_try_wait(ZixSem* sem) +zix_sem_try_wait(ZixSem* ZIX_NONNULL sem) { const mach_timespec_t zero = {0, 0}; return semaphore_timedwait(sem->sem, zero) == KERN_SUCCESS; @@ -137,26 +137,26 @@ struct ZixSemImpl { }; static inline ZixStatus -zix_sem_init(ZixSem* sem, unsigned initial) +zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned initial) { - sem->sem = CreateSemaphore(NULL, initial, LONG_MAX, NULL); + sem->sem = CreateSemaphore(NULL, (LONG)initial, LONG_MAX, NULL); return (sem->sem) ? ZIX_STATUS_SUCCESS : ZIX_STATUS_ERROR; } static inline void -zix_sem_destroy(ZixSem* sem) +zix_sem_destroy(ZixSem* ZIX_NONNULL sem) { CloseHandle(sem->sem); } static inline void -zix_sem_post(ZixSem* sem) +zix_sem_post(ZixSem* ZIX_NONNULL sem) { ReleaseSemaphore(sem->sem, 1, NULL); } static inline ZixStatus -zix_sem_wait(ZixSem* sem) +zix_sem_wait(ZixSem* ZIX_NONNULL sem) { if (WaitForSingleObject(sem->sem, INFINITE) != WAIT_OBJECT_0) { return ZIX_STATUS_ERROR; @@ -165,7 +165,7 @@ zix_sem_wait(ZixSem* sem) } static inline bool -zix_sem_try_wait(ZixSem* sem) +zix_sem_try_wait(ZixSem* ZIX_NONNULL sem) { return WaitForSingleObject(sem->sem, 0) == WAIT_OBJECT_0; } @@ -177,26 +177,26 @@ struct ZixSemImpl { }; static inline ZixStatus -zix_sem_init(ZixSem* sem, unsigned initial) +zix_sem_init(ZixSem* ZIX_NONNULL sem, unsigned initial) { return sem_init(&sem->sem, 0, initial) ? ZIX_STATUS_ERROR : ZIX_STATUS_SUCCESS; } static inline void -zix_sem_destroy(ZixSem* sem) +zix_sem_destroy(ZixSem* ZIX_NONNULL sem) { sem_destroy(&sem->sem); } static inline void -zix_sem_post(ZixSem* sem) +zix_sem_post(ZixSem* ZIX_NONNULL sem) { sem_post(&sem->sem); } static inline ZixStatus -zix_sem_wait(ZixSem* sem) +zix_sem_wait(ZixSem* ZIX_NONNULL sem) { while (sem_wait(&sem->sem)) { if (errno != EINTR) { @@ -209,7 +209,7 @@ zix_sem_wait(ZixSem* sem) } static inline bool -zix_sem_try_wait(ZixSem* sem) +zix_sem_try_wait(ZixSem* ZIX_NONNULL sem) { return (sem_trywait(&sem->sem) == 0); } diff --git a/src/zix/thread.h b/src/zix/thread.h index bea3383..74bb19b 100644 --- a/src/zix/thread.h +++ b/src/zix/thread.h @@ -44,9 +44,7 @@ zix_thread_create(ZixThread* thread, void* (*function)(void*), void* arg); -/** - Join `thread` (block until `thread` exits). -*/ +/// Join `thread` (block until `thread` exits) static inline ZixStatus zix_thread_join(ZixThread thread, void** retval); |