aboutsummaryrefslogtreecommitdiffstats
path: root/src/zix
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-08-10 05:32:34 -0400
committerDavid Robillard <d@drobilla.net>2022-08-17 13:51:03 -0400
commit06b49a3419f35ee8c802f1f15b52cc45b546efb3 (patch)
tree6a331324a73a83d6ff4d10087cdc740c3f1af8aa /src/zix
parentc17c67cdba3045be3c4a0f65dac0039987e0d8cf (diff)
downloadjalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.tar.gz
jalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.tar.bz2
jalv-06b49a3419f35ee8c802f1f15b52cc45b546efb3.zip
Update zix
Diffstat (limited to 'src/zix')
-rw-r--r--src/zix/allocator.c96
-rw-r--r--src/zix/allocator.h186
-rw-r--r--src/zix/attributes.h81
-rw-r--r--src/zix/ring.c239
-rw-r--r--src/zix/ring.h141
-rw-r--r--src/zix/sem.h58
-rw-r--r--src/zix/thread.h4
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);