Revert "fix for flash size in default config"
This reverts commit 38e7a35910.
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
idf_component_register(
|
||||
INCLUDE_DIRS "."
|
||||
)
|
||||
@@ -1,25 +0,0 @@
|
||||
BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2017-2023, Patrick Pelissier
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,3 +0,0 @@
|
||||
This folder is a partial git clone from:
|
||||
https://github.com/P-p-H-d/mlib/commit/d9401371a6bc1c0f240161514549976bcdd98999
|
||||
|
||||
-1240
File diff suppressed because it is too large
Load Diff
-1132
File diff suppressed because it is too large
Load Diff
@@ -1,342 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Thin stdatomic wrapper for C++ compatibility
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_ATOMIC_H
|
||||
#define MSTARLIB_ATOMIC_H
|
||||
|
||||
/* NOTE: Due to the C++ not having recognized stdatomic.h officialy,
|
||||
it is hard to use this header directly with a C++ compiler.
|
||||
See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60932
|
||||
clang++ has no issue with this header but if someone includes
|
||||
atomic from C++, there is incompatibility between atomic & stdatomic.
|
||||
Moreover some compilers lack a working stdatomic header.
|
||||
GCC 4.9 doesn't have a working implementation of 'atomic'.
|
||||
APPLE Clang defines __GNUC__ to be only 4 despite having full support
|
||||
for atomic.
|
||||
*/
|
||||
#if defined(__cplusplus) && __cplusplus >= 201103L \
|
||||
&& !(defined(__GNUC__) && __GNUC__ < 5 && !defined(__APPLE__))
|
||||
|
||||
/* NOTE: This is what the stdatomic.h header shall do in C++ mode. */
|
||||
#include <atomic>
|
||||
|
||||
using std::memory_order;
|
||||
|
||||
using std::atomic_bool;
|
||||
using std::atomic_char;
|
||||
using std::atomic_short;
|
||||
using std::atomic_int;
|
||||
using std::atomic_long;
|
||||
using std::atomic_llong;
|
||||
using std::atomic_uchar;
|
||||
using std::atomic_schar;
|
||||
using std::atomic_ushort;
|
||||
using std::atomic_uint;
|
||||
using std::atomic_ulong;
|
||||
using std::atomic_ullong;
|
||||
using std::atomic_intptr_t;
|
||||
using std::atomic_uintptr_t;
|
||||
using std::atomic_size_t;
|
||||
using std::atomic_ptrdiff_t;
|
||||
using std::atomic_intmax_t;
|
||||
using std::atomic_uintmax_t;
|
||||
using std::atomic_flag;
|
||||
|
||||
using std::kill_dependency;
|
||||
using std::atomic_thread_fence;
|
||||
using std::atomic_signal_fence;
|
||||
using std::atomic_is_lock_free;
|
||||
using std::atomic_store_explicit;
|
||||
using std::atomic_store;
|
||||
using std::atomic_load_explicit;
|
||||
using std::atomic_load;
|
||||
using std::atomic_exchange_explicit;
|
||||
using std::atomic_exchange;
|
||||
using std::atomic_compare_exchange_strong_explicit;
|
||||
using std::atomic_compare_exchange_strong;
|
||||
using std::atomic_compare_exchange_weak_explicit;
|
||||
using std::atomic_compare_exchange_weak;
|
||||
using std::atomic_fetch_add;
|
||||
using std::atomic_fetch_add_explicit;
|
||||
using std::atomic_fetch_sub;
|
||||
using std::atomic_fetch_sub_explicit;
|
||||
using std::atomic_fetch_or;
|
||||
using std::atomic_fetch_or_explicit;
|
||||
using std::atomic_fetch_xor;
|
||||
using std::atomic_fetch_xor_explicit;
|
||||
using std::atomic_fetch_and;
|
||||
using std::atomic_fetch_and_explicit;
|
||||
using std::atomic_flag_test_and_set;
|
||||
using std::atomic_flag_test_and_set_explicit;
|
||||
using std::atomic_flag_clear;
|
||||
using std::atomic_flag_clear_explicit;
|
||||
|
||||
using std::memory_order_relaxed;
|
||||
using std::memory_order_consume;
|
||||
using std::memory_order_acquire;
|
||||
using std::memory_order_release;
|
||||
using std::memory_order_acq_rel;
|
||||
using std::memory_order_seq_cst;
|
||||
|
||||
/* CLANG provides a warning on defining _Atomic as it sees it
|
||||
* as a reserved system macro. It is true. However, the goal of this
|
||||
* header is to provide stdatomic semantic, so it needs to define
|
||||
* _Atomic macro.
|
||||
*
|
||||
* So, this warning has to be ignored.
|
||||
*
|
||||
* It cannot use M_BEGIN_PROTECTED_CODE as this header is normally
|
||||
* independent of m-core.h
|
||||
*/
|
||||
#if defined(__clang__) && __clang_major__ >= 4
|
||||
_Pragma("clang diagnostic push")
|
||||
_Pragma("clang diagnostic ignored \"-Wreserved-id-macro\"")
|
||||
#endif
|
||||
|
||||
#define _Atomic(T) std::atomic< T >
|
||||
|
||||
#if defined(__clang__) && __clang_major__ >= 4
|
||||
_Pragma("clang diagnostic pop")
|
||||
#endif
|
||||
|
||||
/* C11 with working stdatomic
|
||||
STDATOMIC doesn't work with C++ except for clang but is incompatible with atomic.
|
||||
GCC < 4.9 doesn't provide a compliant stdatomic.h
|
||||
CLANG 3.5 has issues with GCC's stdatomic.h and doesn't provide its own
|
||||
ICC < 18 doesn't provide a compliant stdatomic.h
|
||||
*/
|
||||
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) ) \
|
||||
|| (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && !defined(__cplusplus) && (__GNUC__*100 + __GNUC_MINOR__) >= 409) \
|
||||
|| (defined(__clang__) && (__clang_major__ * 100 + __clang_minor__) >= 308) \
|
||||
|| (defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1800)
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
/* MSYS2 has a conflict between cdefs.h which defines a _Atomic macro (if not C11)
|
||||
not compatible with the used stdatomic.h (from GCC).
|
||||
Provide a configurable mechanism to undef it with auto-detection of msys2 / gcc */
|
||||
#ifndef M_USE_UNDEF_ATOMIC
|
||||
# if defined(__MSYS__) && defined(__GNUC__) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112L)
|
||||
# define M_USE_UNDEF_ATOMIC 1
|
||||
# endif
|
||||
#endif
|
||||
#if defined(M_USE_UNDEF_ATOMIC) && M_USE_UNDEF_ATOMIC == 1
|
||||
# undef _Atomic
|
||||
#endif
|
||||
|
||||
/* Non working C++ atomic header, nor working stdatomic.h found.
|
||||
Write a compatible layer using mutex as slin as possible.
|
||||
Supports only up to 64-bits atomic (sizeof long long to be more precise).
|
||||
The locks are never properly cleared and remain active until
|
||||
the end of the program.
|
||||
We also assume that the call to the atomic_* interface is "macro clean".
|
||||
*/
|
||||
#else
|
||||
|
||||
#include "m-thread.h"
|
||||
#include "m-core.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* _Atomic qualifier for a type (emulation).
|
||||
The structure is quite large:
|
||||
_val : value of the atomic type,
|
||||
_zero : zero value of the atomic type (constant),
|
||||
_previous: temporary value used within the mutex lock,
|
||||
_lock : the mutex lock.
|
||||
Support up to sizeof (long long) type.
|
||||
*/
|
||||
#define _Atomic(T) \
|
||||
struct { \
|
||||
T volatile _val; \
|
||||
T _zero; \
|
||||
T _previous; \
|
||||
m_mutex_t _lock; \
|
||||
}
|
||||
|
||||
/* Define the supported memory order.
|
||||
Even if memory order is defined, only the strongest constraint is used */
|
||||
typedef enum {
|
||||
memory_order_relaxed,
|
||||
memory_order_consume,
|
||||
memory_order_acquire,
|
||||
memory_order_release,
|
||||
memory_order_acq_rel,
|
||||
memory_order_seq_cst
|
||||
} memory_order;
|
||||
|
||||
typedef _Atomic(bool) atomic_bool;
|
||||
typedef _Atomic(char) atomic_char;
|
||||
typedef _Atomic(short) atomic_short;
|
||||
typedef _Atomic(int) atomic_int;
|
||||
typedef _Atomic(long) atomic_long;
|
||||
typedef _Atomic(long long) atomic_llong;
|
||||
typedef _Atomic(unsigned char) atomic_uchar;
|
||||
typedef _Atomic(signed char) atomic_schar;
|
||||
typedef _Atomic(unsigned short) atomic_ushort;
|
||||
typedef _Atomic(unsigned int) atomic_uint;
|
||||
typedef _Atomic(unsigned long) atomic_ulong;
|
||||
typedef _Atomic(unsigned long long) atomic_ullong;
|
||||
typedef _Atomic(intptr_t) atomic_intptr_t;
|
||||
typedef _Atomic(uintptr_t) atomic_uintptr_t;
|
||||
typedef _Atomic(size_t) atomic_size_t;
|
||||
typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;
|
||||
|
||||
/* Define the minimum size supported by the architecture
|
||||
for an atomic read or write.
|
||||
This can help a lot since it avoids locking for atomic_load and
|
||||
atomic_store.
|
||||
*/
|
||||
#if defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__)
|
||||
# define ATOMICI_MIN_RW_SIZE 8
|
||||
#elif defined(_M_86) || defined (__i386__)
|
||||
# define ATOMICI_MIN_RW_SIZE 4
|
||||
#else
|
||||
# define ATOMICI_MIN_RW_SIZE 0
|
||||
#endif
|
||||
|
||||
/* Detect if stdint.h was included */
|
||||
#if (defined (INTMAX_C) && defined (UINTMAX_C) && !defined(__cplusplus)) || \
|
||||
defined (_STDINT_H) || defined (_STDINT_H_) || defined (_STDINT) || \
|
||||
defined (_SYS_STDINT_H_)
|
||||
/* Define additional atomic types */
|
||||
typedef _Atomic(intmax_t) atomic_intmax_t;
|
||||
typedef _Atomic(uintmax_t) atomic_uintmax_t;
|
||||
#endif
|
||||
|
||||
/* (INTERNAL) Unlock the mutex and return the given value */
|
||||
M_INLINE long long atomic_fetch_unlock (m_mutex_t *lock, long long val)
|
||||
{
|
||||
m_mutex_unlock (*lock);
|
||||
return val;
|
||||
}
|
||||
|
||||
/* (INTERNAL) This is the heart of the wrapper:
|
||||
lock the atomic value, read it and returns the value.
|
||||
In order to avoid any compiler extension, we need to transform the
|
||||
atomic type into 'long long' then convert it back to its value.
|
||||
This is because _previous can't be read after the lock, and we can't
|
||||
generate temporary variable within a macro.
|
||||
The trick is computing _val - _zero within the lock, then
|
||||
returns retvalue + _zero after the release of the lock.
|
||||
*/
|
||||
#define atomic_fetch_op(ptr, val, op) \
|
||||
(m_mutex_lock((ptr)->_lock), \
|
||||
(ptr)->_previous = (ptr)->_val, \
|
||||
(ptr)->_val op (val), \
|
||||
atomic_fetch_unlock(&(ptr)->_lock, (long long)((ptr)->_previous-(ptr)->_zero))+(ptr)->_zero)
|
||||
|
||||
/* Perform an atomic add (EMULATION) */
|
||||
#define atomic_fetch_add(ptr, val) atomic_fetch_op(ptr, val, +=)
|
||||
/* Perform an atomic sub (EMULATION) */
|
||||
#define atomic_fetch_sub(ptr, val) atomic_fetch_op(ptr, val, -=)
|
||||
/* Perform an atomic or (EMULATION) */
|
||||
#define atomic_fetch_or(ptr, val) atomic_fetch_op(ptr, val, |=)
|
||||
/* Perform an atomic xor (EMULATION) */
|
||||
#define atomic_fetch_xor(ptr, val) atomic_fetch_op(ptr, val, ^=)
|
||||
/* Perform an atomic and (EMULATION) */
|
||||
#define atomic_fetch_and(ptr, val) atomic_fetch_op(ptr, val, &=)
|
||||
/* Perform an atomic exchange (EMULATION) */
|
||||
#define atomic_exchange(ptr, val) atomic_fetch_op(ptr, val, =)
|
||||
|
||||
/* Initialize an atomic GLOBAL variable */
|
||||
#define ATOMIC_VAR_INIT(val) { val, 0, 0, M_MUTEXI_INIT_VALUE }
|
||||
|
||||
/* Initialize an atomic variable */
|
||||
#define atomic_init(ptr, val) \
|
||||
(m_mutex_init((ptr)->_lock), (ptr)->_val = val, (ptr)->_zero = 0)
|
||||
|
||||
/* (INTERNAL) Load an atomic variable within a lock
|
||||
(needed for variable greater than CPU atomic size) */
|
||||
#define atomic_load_lock(ptr) \
|
||||
(m_mutex_lock((ptr)->_lock), \
|
||||
(ptr)->_previous = (ptr)->_val, \
|
||||
atomic_fetch_unlock(&(ptr)->_lock, (long long) ((ptr)->_previous-(ptr)->_zero))+(ptr)->_zero)
|
||||
|
||||
/* (INTERNAL) Store an atomic variable within a lock
|
||||
(needed for variable greater than CPU atomic size) */
|
||||
#define atomic_store_lock(ptr, val) \
|
||||
(m_mutex_lock((ptr)->_lock), \
|
||||
(ptr)->_val = (val), \
|
||||
m_mutex_unlock((ptr)->_lock))
|
||||
|
||||
/* Atomic load of a variable (EMULATION)
|
||||
If the atomic type size is not greater than the CPU atomic size,
|
||||
we can perform a direct read of the variable (much faster) */
|
||||
#define atomic_load(ptr) \
|
||||
( sizeof ((ptr)->_val) <= ATOMICI_MIN_RW_SIZE \
|
||||
? (ptr)->_val \
|
||||
: atomic_load_lock(ptr))
|
||||
|
||||
/* Atomic store of a variable (EMULATION)
|
||||
If the atomic type size is not greater than the CPU atomic size,
|
||||
we can perform a direct write of the variable (much faster) */
|
||||
#define atomic_store(ptr, val) do { \
|
||||
if ( sizeof ((ptr)->_val) <= ATOMICI_MIN_RW_SIZE) { \
|
||||
(ptr)->_val = (val); \
|
||||
} else { \
|
||||
long long _offset = (long long) ((val) - (ptr)->_zero); \
|
||||
atomic_store_lock(ptr, (ptr)->_zero + _offset); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Perform a CAS (Compare and swap) operation (EMULATION) */
|
||||
#define atomic_compare_exchange_strong(ptr, exp, val) \
|
||||
(m_mutex_lock((ptr)->_lock), \
|
||||
atomic_fetch_unlock(&(ptr)->_lock, \
|
||||
(ptr)->_val == *(exp) \
|
||||
? ((ptr)->_val = (val), true) \
|
||||
: (*(exp) = (ptr)->_val, false)))
|
||||
|
||||
|
||||
#define atomic_fetch_add_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, +=)
|
||||
#define atomic_fetch_sub_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, -=)
|
||||
#define atomic_fetch_or_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, |=)
|
||||
#define atomic_fetch_xor_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, ^=)
|
||||
#define atomic_fetch_and_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, &=)
|
||||
#define atomic_exchange_explicit(ptr, val, mem) atomic_fetch_op(ptr, val, =)
|
||||
#define atomic_load_explicit(ptr, mem) atomic_load(ptr)
|
||||
#define atomic_store_explicit(ptr, val, mem) atomic_store(ptr, val)
|
||||
#define kill_dependency(ptr) atomic_load(ptr)
|
||||
#define atomic_thread_fence(mem) (void) 0
|
||||
#define atomic_signal_fence(mem) (void) 0
|
||||
#define atomic_is_lock_free(ptr) false
|
||||
#define atomic_compare_exchange_strong_explicit(ptr, exp, val, mem1, mem2) atomic_compare_exchange_strong(ptr, exp, val)
|
||||
#define atomic_compare_exchange_weak_explicit(ptr, exp, val, mem1, mem2) atomic_compare_exchange_strong(ptr, exp, val)
|
||||
#define atomic_compare_exchange_weak(ptr, exp, val) atomic_compare_exchange_strong(ptr, exp, val)
|
||||
|
||||
/* TODO: Missing atomic_flag. Problem: it is supposed to be lock free! */
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#endif
|
||||
|
||||
// C17 deprecated ATOMIC_VAR_INIT
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201710L
|
||||
# define M_ATOMIC_VAR_INIT(x) (x)
|
||||
#else
|
||||
# define M_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,981 +0,0 @@
|
||||
/*
|
||||
* M*LIB - BITSET module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_BITSET_H
|
||||
#define MSTARLIB_BITSET_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "m-core.h"
|
||||
|
||||
/********************************** INTERNAL ************************************/
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
// Define the basic limb of a bitset
|
||||
typedef uint64_t m_b1tset_limb_ct;
|
||||
// And its size in bits
|
||||
#define M_B1TSET_LIMB_BIT (sizeof(m_b1tset_limb_ct) * CHAR_BIT)
|
||||
|
||||
// bitset grow policy. n is limb size
|
||||
#define M_B1TSET_INC_ALLOC_SIZE(n) ((n) < 4 ? 4 : (n) * 2)
|
||||
|
||||
// Compute the number of allocated limbs needed to handle 'n' bits.
|
||||
#define M_B1TSET_TO_ALLOC(n) (((n) + M_B1TSET_LIMB_BIT - 1) / M_B1TSET_LIMB_BIT)
|
||||
|
||||
// Compute the number of bits available from the allocated size in limbs
|
||||
#define M_B1TSET_FROM_ALLOC(n) ((n) * M_B1TSET_LIMB_BIT)
|
||||
|
||||
// Contract of a bitset
|
||||
#define M_B1TSET_CONTRACT(t) do { \
|
||||
M_ASSERT (t != NULL); \
|
||||
M_ASSERT (t->size <= M_B1TSET_FROM_ALLOC (t->alloc)); \
|
||||
M_ASSERT (t->alloc <= ((size_t)-1) / M_B1TSET_LIMB_BIT); \
|
||||
M_ASSERT (t->size < ((size_t)-1) - M_B1TSET_LIMB_BIT); \
|
||||
M_ASSERT (t->size == 0 || t->ptr != NULL); \
|
||||
M_ASSERT (t->alloc == 0 || t->ptr != NULL); \
|
||||
M_ASSERT ((t->size % M_B1TSET_LIMB_BIT) == 0 || (t->ptr[ (t->size-1) / M_B1TSET_LIMB_BIT] & ~(((((m_b1tset_limb_ct)1)<<(t->size % M_B1TSET_LIMB_BIT))<<1)-1)) == 0); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/********************************** EXTERNAL ************************************/
|
||||
|
||||
/* Define a type of variable 'bits' or array of packed booleans */
|
||||
typedef struct m_bitset_s {
|
||||
size_t size; // Size is the number of bits
|
||||
size_t alloc; // Alloc is the number of allocated limbs
|
||||
m_b1tset_limb_ct *ptr; // Pointer to the allocated limbs
|
||||
} m_bitset_t[1];
|
||||
|
||||
/* Pointer to a m_bitset_t */
|
||||
typedef struct m_bitset_s *m_bitset_ptr;
|
||||
|
||||
/* Constant Pointer to a m_bitset_t */
|
||||
typedef const struct m_bitset_s *m_bitset_srcptr;
|
||||
|
||||
/* Iterator on a bitset */
|
||||
typedef struct m_bitset_it_s {
|
||||
size_t index; // index to the array of bit
|
||||
bool value; // value used for _ref & _cref to store the value
|
||||
struct m_bitset_s *set; // the associated bitset
|
||||
} m_bitset_it_t[1];
|
||||
|
||||
/* Initialize a bitset (CONSTRUCTOR) */
|
||||
M_INLINE void
|
||||
m_bitset_init(m_bitset_t t)
|
||||
{
|
||||
M_ASSERT (t != NULL);
|
||||
M_STATIC_ASSERT (M_POWEROF2_P(M_B1TSET_LIMB_BIT), MLIB_INTERNAL, "M*LIB: BITSET LIMB shall be a power of 2.");
|
||||
t->size = 0;
|
||||
t->alloc = 0;
|
||||
t->ptr = NULL;
|
||||
M_B1TSET_CONTRACT(t);
|
||||
}
|
||||
|
||||
/* Clean a bitset */
|
||||
M_INLINE void
|
||||
m_bitset_reset(m_bitset_t t)
|
||||
{
|
||||
M_B1TSET_CONTRACT(t);
|
||||
t->size = 0;
|
||||
}
|
||||
|
||||
/* Clear a bitset (DESTRUCTOR) */
|
||||
M_INLINE void
|
||||
m_bitset_clear(m_bitset_t t)
|
||||
{
|
||||
m_bitset_reset(t);
|
||||
M_MEMORY_FREE(t->ptr);
|
||||
// This is not really needed, but is safer
|
||||
// This representation is invalid and will be detected by the contract.
|
||||
// A C compiler should be able to optimize out theses initializations.
|
||||
t->alloc = 1;
|
||||
t->ptr = NULL;
|
||||
}
|
||||
|
||||
/* Set a bitset to another one */
|
||||
M_INLINE void
|
||||
m_bitset_set(m_bitset_t d, const m_bitset_t s)
|
||||
{
|
||||
M_B1TSET_CONTRACT(d);
|
||||
M_B1TSET_CONTRACT(s);
|
||||
if (M_UNLIKELY (d == s)) return;
|
||||
const size_t needAlloc = M_B1TSET_TO_ALLOC (s->size);
|
||||
if (M_LIKELY (s->size > 0)) {
|
||||
// Test if enough space in target
|
||||
if (s->size > M_B1TSET_FROM_ALLOC (d->alloc)) {
|
||||
m_b1tset_limb_ct *ptr = M_MEMORY_REALLOC (m_b1tset_limb_ct, d->ptr, needAlloc);
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
|
||||
M_MEMORY_FULL(needAlloc);
|
||||
return ;
|
||||
}
|
||||
d->ptr = ptr;
|
||||
d->alloc = needAlloc;
|
||||
}
|
||||
M_ASSERT(d->ptr != NULL);
|
||||
M_ASSERT(s->ptr != NULL);
|
||||
memcpy (d->ptr, s->ptr, needAlloc * sizeof(m_b1tset_limb_ct) );
|
||||
}
|
||||
d->size = s->size;
|
||||
M_B1TSET_CONTRACT(d);
|
||||
}
|
||||
|
||||
/* Initialize & set a bitset to another one (CONSTRUCTOR) */
|
||||
M_INLINE void
|
||||
m_bitset_init_set(m_bitset_t d, const m_bitset_t s)
|
||||
{
|
||||
M_ASSERT (d != s);
|
||||
m_bitset_init(d);
|
||||
m_bitset_set(d, s);
|
||||
}
|
||||
|
||||
/* Initialize & move a bitset (CONSTRUCTOR) from another one (DESTRUCTOR) */
|
||||
M_INLINE void
|
||||
m_bitset_init_move(m_bitset_t d, m_bitset_t s)
|
||||
{
|
||||
M_B1TSET_CONTRACT(s);
|
||||
d->size = s->size;
|
||||
d->alloc = s->alloc;
|
||||
d->ptr = s->ptr;
|
||||
// Illegal representation of a bitset, to be detectable
|
||||
s->alloc = 1;
|
||||
s->ptr = NULL;
|
||||
M_B1TSET_CONTRACT(d);
|
||||
}
|
||||
|
||||
/* Move a bitset from another one (DESTRUCTOR) */
|
||||
M_INLINE void
|
||||
m_bitset_move(m_bitset_t d, m_bitset_t s)
|
||||
{
|
||||
m_bitset_clear(d);
|
||||
m_bitset_init_move (d, s);
|
||||
}
|
||||
|
||||
/* Set the bit 'i' in the bitset to the value 'x' */
|
||||
M_INLINE void
|
||||
m_bitset_set_at(m_bitset_t v, size_t i, bool x)
|
||||
{
|
||||
M_B1TSET_CONTRACT(v);
|
||||
M_ASSERT (v->ptr != NULL);
|
||||
M_ASSERT_INDEX(i, v->size);
|
||||
const size_t offset = i / M_B1TSET_LIMB_BIT;
|
||||
const size_t index = i % M_B1TSET_LIMB_BIT;
|
||||
// This is a branchless version as x can only be 0 or 1 with only one variable shift.
|
||||
const m_b1tset_limb_ct mask = ((m_b1tset_limb_ct)1)<<index;
|
||||
v->ptr[offset] = (v->ptr[offset] & ~mask) | (mask & (0-(m_b1tset_limb_ct)x));
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Flip the bit 'i' in the bitset */
|
||||
M_INLINE void
|
||||
m_bitset_flip_at(m_bitset_t v, size_t i)
|
||||
{
|
||||
M_B1TSET_CONTRACT(v);
|
||||
M_ASSERT (v->ptr != NULL);
|
||||
M_ASSERT_INDEX(i, v->size);
|
||||
size_t offset = i / M_B1TSET_LIMB_BIT;
|
||||
size_t index = i % M_B1TSET_LIMB_BIT;
|
||||
v->ptr[offset] ^= ((m_b1tset_limb_ct)1)<<index;
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Push back the boolean 'x' in the bitset (increasing the bitset) */
|
||||
M_INLINE void
|
||||
m_bitset_push_back (m_bitset_t v, bool x)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
if (M_UNLIKELY (v->size >= M_B1TSET_FROM_ALLOC (v->alloc))) {
|
||||
// Compute the needed allocation.
|
||||
const size_t needAlloc = M_B1TSET_INC_ALLOC_SIZE(v->alloc);
|
||||
// Check for integer overflow
|
||||
if (M_UNLIKELY_NOMEM (needAlloc <= v->alloc)) {
|
||||
M_MEMORY_FULL(needAlloc * sizeof(m_b1tset_limb_ct));
|
||||
return;
|
||||
}
|
||||
// Alloc memory
|
||||
m_b1tset_limb_ct *ptr = M_MEMORY_REALLOC (m_b1tset_limb_ct, v->ptr, needAlloc);
|
||||
// Check if success
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
|
||||
M_MEMORY_FULL(needAlloc * sizeof(m_b1tset_limb_ct));
|
||||
return;
|
||||
}
|
||||
v->ptr = ptr;
|
||||
v->alloc = needAlloc;
|
||||
}
|
||||
M_ASSERT(v->ptr != NULL);
|
||||
|
||||
const size_t i = v->size;
|
||||
const size_t offset = i / M_B1TSET_LIMB_BIT;
|
||||
const size_t index = i % M_B1TSET_LIMB_BIT;
|
||||
if (M_UNLIKELY(index == 0)) {
|
||||
// A new limb if used. Clear it before using it.
|
||||
v->ptr[offset] = 0;
|
||||
}
|
||||
// This is a branchless version as x can only be 0 or 1 with only one variable shift.
|
||||
const m_b1tset_limb_ct mask = ((m_b1tset_limb_ct)1)<<index;
|
||||
v->ptr[offset] = (v->ptr[offset] & ~mask) | (mask & (0-(m_b1tset_limb_ct)x));
|
||||
v->size ++;
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Resize the bitset to have exactly 'size' bits */
|
||||
M_INLINE void
|
||||
m_bitset_resize (m_bitset_t v, size_t size)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
// Check for overflow
|
||||
if (M_UNLIKELY_NOMEM (size >= ((size_t)-1) - M_B1TSET_LIMB_BIT)) {
|
||||
M_MEMORY_FULL((size_t) -1);
|
||||
return;
|
||||
}
|
||||
// Compute the needed allocation.
|
||||
size_t newAlloc = M_B1TSET_TO_ALLOC (size);
|
||||
if (newAlloc > v->alloc) {
|
||||
// Allocate more limbs to store the bitset.
|
||||
m_b1tset_limb_ct *ptr = M_MEMORY_REALLOC (m_b1tset_limb_ct, v->ptr, newAlloc);
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
|
||||
M_MEMORY_FULL(newAlloc * sizeof(m_b1tset_limb_ct));
|
||||
return;
|
||||
}
|
||||
v->ptr = ptr;
|
||||
v->alloc = newAlloc;
|
||||
}
|
||||
// Resize the bitsets
|
||||
const size_t old_size = v->size;
|
||||
const size_t offset = size / M_B1TSET_LIMB_BIT;
|
||||
const size_t index = size % M_B1TSET_LIMB_BIT;
|
||||
const m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1)<<index)-1;
|
||||
if (size < old_size) {
|
||||
// Resize down the bitset: clear unused bits
|
||||
if (M_LIKELY(index != 0)) {
|
||||
// Mask the last limb to clear the last bits
|
||||
v->ptr[offset] &= mask;
|
||||
}
|
||||
} else if (size > old_size) {
|
||||
// Resize up the bitset: set to 0 new bits.
|
||||
const size_t old_offset = (old_size + M_B1TSET_LIMB_BIT - 1)/ M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = old_offset ; i < offset; i++) {
|
||||
v->ptr[i] = 0;
|
||||
}
|
||||
if (M_LIKELY(index != 0)) {
|
||||
// Mask the last limb to clear the last bits
|
||||
v->ptr[offset] = 0;
|
||||
}
|
||||
}
|
||||
v->size = size;
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Reserve allocation in the bitset to accomodate at least 'size' bits without reallocation */
|
||||
M_INLINE void
|
||||
m_bitset_reserve (m_bitset_t v, size_t alloc)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
size_t oldAlloc = M_B1TSET_TO_ALLOC (v->size);
|
||||
size_t newAlloc = M_B1TSET_TO_ALLOC (alloc);
|
||||
// We refuse to reduce allocation below current size
|
||||
if (oldAlloc > newAlloc) {
|
||||
newAlloc = oldAlloc;
|
||||
}
|
||||
if (M_UNLIKELY (newAlloc == 0)) {
|
||||
// Free all memory used by the bitsets
|
||||
M_MEMORY_FREE (v->ptr);
|
||||
v->size = v->alloc = 0;
|
||||
v->ptr = NULL;
|
||||
} else {
|
||||
// Allocate more memory or reduce memory usage
|
||||
m_b1tset_limb_ct *ptr = M_MEMORY_REALLOC (m_b1tset_limb_ct, v->ptr, newAlloc);
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
|
||||
M_MEMORY_FULL(newAlloc * sizeof(m_b1tset_limb_ct));
|
||||
return;
|
||||
}
|
||||
v->ptr = ptr;
|
||||
v->alloc = newAlloc;
|
||||
}
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Return the value of the boolean at index 'i'.
|
||||
* NOTE: Interface is a little bit different:
|
||||
* It doesn't return a pointer to the data, but the data itself.
|
||||
*/
|
||||
M_INLINE bool
|
||||
m_bitset_get(const m_bitset_t v, size_t i)
|
||||
{
|
||||
M_B1TSET_CONTRACT(v);
|
||||
M_ASSERT (v->ptr != NULL);
|
||||
M_ASSERT_INDEX(i, v->size);
|
||||
size_t offset = i / M_B1TSET_LIMB_BIT;
|
||||
size_t index = i % M_B1TSET_LIMB_BIT;
|
||||
return ( v->ptr[offset] & (((m_b1tset_limb_ct)1) << index) ) != 0;
|
||||
}
|
||||
|
||||
/* m_bitset_cget is the exact same service than m_bitset_get */
|
||||
#define m_bitset_cget m_bitset_get
|
||||
|
||||
/* Pop back the last bit in the bitset */
|
||||
M_INLINE void
|
||||
m_bitset_pop_back(bool *dest, m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
M_ASSERT_INDEX (0, v->size);
|
||||
// Remove one item from the bitset
|
||||
v->size--;
|
||||
// Prepare clearing popped bit
|
||||
const size_t offset = v->size / M_B1TSET_LIMB_BIT;
|
||||
const size_t index = v->size % M_B1TSET_LIMB_BIT;
|
||||
const m_b1tset_limb_ct mask = ((m_b1tset_limb_ct)1)<<index;
|
||||
if (dest) {
|
||||
// Read popped bit
|
||||
*dest = (v->ptr[offset] & mask) != 0;
|
||||
}
|
||||
v->ptr[offset] &= mask-1;
|
||||
M_B1TSET_CONTRACT (v);
|
||||
}
|
||||
|
||||
/* Return the front bit value in the bitset */
|
||||
M_INLINE bool
|
||||
m_bitset_front(m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
M_ASSERT_INDEX (0, v->size);
|
||||
return m_bitset_get(v, 0);
|
||||
}
|
||||
|
||||
/* Return the back bit value in the bitset */
|
||||
M_INLINE bool
|
||||
m_bitset_back(m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
M_ASSERT_INDEX (0, v->size);
|
||||
return m_bitset_get(v, v->size-1);
|
||||
}
|
||||
|
||||
/* Test if the bitset is empty (no bits stored)*/
|
||||
M_INLINE bool
|
||||
m_bitset_empty_p(m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
return v->size == 0;
|
||||
}
|
||||
|
||||
/* Return the number of bits of the bitset */
|
||||
M_INLINE size_t
|
||||
m_bitset_size(m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
return v->size;
|
||||
}
|
||||
|
||||
/* Return the capacity in limbs of the bitset */
|
||||
M_INLINE size_t
|
||||
m_bitset_capacity(m_bitset_t v)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v);
|
||||
return M_B1TSET_FROM_ALLOC (v->alloc);
|
||||
}
|
||||
|
||||
/* Swap the bit at index i and j of the bitset */
|
||||
M_INLINE void
|
||||
m_bitset_swap_at (m_bitset_t v, size_t i, size_t j)
|
||||
{
|
||||
M_ASSERT_INDEX(i, v->size);
|
||||
M_ASSERT_INDEX(j, v->size);
|
||||
|
||||
bool i_val = m_bitset_get(v, i);
|
||||
bool j_val = m_bitset_get(v, j);
|
||||
m_bitset_set_at (v, i, j_val);
|
||||
m_bitset_set_at (v, j, i_val);
|
||||
}
|
||||
|
||||
/* Swap the bitsets */
|
||||
M_INLINE void
|
||||
m_bitset_swap (m_bitset_t v1, m_bitset_t v2)
|
||||
{
|
||||
M_B1TSET_CONTRACT (v1);
|
||||
M_B1TSET_CONTRACT (v2);
|
||||
M_SWAP (size_t, v1->size, v2->size);
|
||||
M_SWAP (size_t, v1->alloc, v2->alloc);
|
||||
M_SWAP (m_b1tset_limb_ct *, v1->ptr, v2->ptr);
|
||||
M_B1TSET_CONTRACT (v1);
|
||||
M_B1TSET_CONTRACT (v2);
|
||||
}
|
||||
|
||||
/* (INTERNAL) Left shift of the bitset (ptr+size) by 1 bit,
|
||||
* integrating the carry in the lowest position.
|
||||
* Return the new carry.
|
||||
*/
|
||||
M_INLINE m_b1tset_limb_ct
|
||||
m_b1tset_lshift(m_b1tset_limb_ct ptr[], size_t n, m_b1tset_limb_ct carry)
|
||||
{
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
m_b1tset_limb_ct v = ptr[i];
|
||||
ptr[i] = (v << 1) | carry;
|
||||
carry = (v >> (M_B1TSET_LIMB_BIT-1) );
|
||||
}
|
||||
return carry;
|
||||
}
|
||||
|
||||
/* (INTERNAL) Right shift of the bitset (ptr+size) by 1 bit,
|
||||
* integrating the carry in the lowest position.
|
||||
* Return the new carry.
|
||||
*/
|
||||
M_INLINE m_b1tset_limb_ct
|
||||
m_b1tset_rshift(m_b1tset_limb_ct ptr[], size_t n, m_b1tset_limb_ct carry)
|
||||
{
|
||||
for(size_t i = n - 1; i < n; i--) {
|
||||
m_b1tset_limb_ct v = ptr[i];
|
||||
ptr[i] = (v >> 1) | (carry << (M_B1TSET_LIMB_BIT-1) );
|
||||
carry = v & 1;
|
||||
}
|
||||
return carry;
|
||||
}
|
||||
|
||||
/* Insert a new bit at position 'key' of value 'value' in the bitset 'set'
|
||||
shifting the set accordingly */
|
||||
M_INLINE void
|
||||
m_bitset_push_at(m_bitset_t set, size_t key, bool value)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
// First push another value to extend the array to the right size
|
||||
m_bitset_push_back(set, false);
|
||||
M_ASSERT (set->ptr != NULL);
|
||||
M_ASSERT_INDEX(key, set->size);
|
||||
|
||||
// Then shift it
|
||||
size_t offset = key / M_B1TSET_LIMB_BIT;
|
||||
size_t index = key % M_B1TSET_LIMB_BIT;
|
||||
m_b1tset_limb_ct v = set->ptr[offset];
|
||||
m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1)<<index)-1;
|
||||
m_b1tset_limb_ct carry = (v >> (M_B1TSET_LIMB_BIT-1) );
|
||||
v = (v & mask) | ((unsigned int) value << index) | ((v & ~mask) << 1);
|
||||
set->ptr[offset] = v;
|
||||
size_t size = (set->size + M_B1TSET_LIMB_BIT - 1) / M_B1TSET_LIMB_BIT;
|
||||
M_ASSERT (size >= offset + 1);
|
||||
v = m_b1tset_lshift(&set->ptr[offset+1], size - offset - 1, carry);
|
||||
// v is unused as it should be zero.
|
||||
M_ASSERT(v == 0);
|
||||
(void) v;
|
||||
M_B1TSET_CONTRACT (set);
|
||||
}
|
||||
|
||||
/* Pop a new bit at position 'key' in the bitset
|
||||
* and return in *dest its value if *dest exists */
|
||||
M_INLINE void
|
||||
m_bitset_pop_at(bool *dest, m_bitset_t set, size_t key)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
M_ASSERT (set->ptr != NULL);
|
||||
M_ASSERT_INDEX(key, set->size);
|
||||
|
||||
if (dest) {
|
||||
*dest = m_bitset_get (set, key);
|
||||
}
|
||||
// Shift it
|
||||
size_t offset = key / M_B1TSET_LIMB_BIT;
|
||||
size_t index = key % M_B1TSET_LIMB_BIT;
|
||||
size_t size = (set->size + M_B1TSET_LIMB_BIT - 1) / M_B1TSET_LIMB_BIT;
|
||||
m_b1tset_limb_ct v, mask, carry;
|
||||
carry = m_b1tset_rshift(&set->ptr[offset+1], size - offset - 1, false);
|
||||
v = set->ptr[offset];
|
||||
mask = (((m_b1tset_limb_ct)1)<<index)-1;
|
||||
v = (v & mask) | ((v>>1) & ~mask) | (carry << (M_B1TSET_LIMB_BIT-1)) ;
|
||||
set->ptr[offset] = v;
|
||||
// Decrease size
|
||||
set->size --;
|
||||
M_B1TSET_CONTRACT (set);
|
||||
}
|
||||
|
||||
/* Test if two bitsets are equal */
|
||||
M_INLINE bool
|
||||
m_bitset_equal_p (const m_bitset_t set1, const m_bitset_t set2)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set1);
|
||||
M_B1TSET_CONTRACT (set2);
|
||||
if (set1->size != set2->size)
|
||||
return false;
|
||||
/* We won't compare each bit individualy,
|
||||
but instead compare them per limb */
|
||||
const size_t limbSize = (set1->size + M_B1TSET_LIMB_BIT -1) / M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < limbSize;i++)
|
||||
if (set1->ptr[i] != set2->ptr[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initialize an iterator to the first bit of the biset */
|
||||
M_INLINE void
|
||||
m_bitset_it(m_bitset_it_t it, m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
it->index = 0;
|
||||
it->set = set;
|
||||
}
|
||||
|
||||
/* Initialize an iterator to reference the same bit as the given one*/
|
||||
M_INLINE void
|
||||
m_bitset_it_set(m_bitset_it_t it, const m_bitset_it_t itorg)
|
||||
{
|
||||
M_ASSERT (it != NULL && itorg != NULL);
|
||||
it->index = itorg->index;
|
||||
it->set = itorg->set;
|
||||
}
|
||||
|
||||
/* Initialize an iterator to reference the last bit of the bitset*/
|
||||
M_INLINE void
|
||||
m_bitset_it_last(m_bitset_it_t it, m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
it->index = set->size-1;
|
||||
it->set = set;
|
||||
}
|
||||
|
||||
/* Initialize an iterator to reference no valid bit of the bitset*/
|
||||
M_INLINE void
|
||||
m_bitset_it_end(m_bitset_it_t it, m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
it->index = set->size;
|
||||
it->set = set;
|
||||
}
|
||||
|
||||
/* Test if an iterator references no valid bit of the bitset anymore */
|
||||
M_INLINE bool
|
||||
m_bitset_end_p(const m_bitset_it_t it)
|
||||
{
|
||||
M_ASSERT (it != NULL && it->set != NULL);
|
||||
return (it->index) >= (it->set->size);
|
||||
}
|
||||
|
||||
/* Test if an iterator references the last (or end) bit of the bitset anymore */
|
||||
M_INLINE bool
|
||||
m_bitset_last_p(const m_bitset_it_t it)
|
||||
{
|
||||
M_ASSERT (it != NULL && it->set != NULL);
|
||||
/* NOTE: Can not compute 'size-1' due to potential overflow
|
||||
if size is 0 */
|
||||
return (it->index+1) >= (it->set->size);
|
||||
}
|
||||
|
||||
/* Test if both iterators reference the same bit */
|
||||
M_INLINE bool
|
||||
m_bitset_it_equal_p(const m_bitset_it_t it1, const m_bitset_it_t it2)
|
||||
{
|
||||
M_ASSERT (it1 != NULL && it2 != NULL);
|
||||
return it1->index == it2->index && it1->set == it2->set;
|
||||
}
|
||||
|
||||
/* Move the iterator to the next bit */
|
||||
M_INLINE void
|
||||
m_bitset_next(m_bitset_it_t it)
|
||||
{
|
||||
M_ASSERT (it != NULL && it->set != NULL);
|
||||
it->index++;
|
||||
}
|
||||
|
||||
/* Move the iterator to the previous bit */
|
||||
M_INLINE void
|
||||
m_bitset_previous(m_bitset_it_t it)
|
||||
{
|
||||
M_ASSERT (it != NULL && it->set != NULL);
|
||||
it->index--;
|
||||
}
|
||||
|
||||
// There is no _ref as it is not possible to modify the value using the IT interface
|
||||
|
||||
/* Return a pointer to the bit referenced by the iterator
|
||||
* Only one reference is possible at a time per iterator */
|
||||
M_INLINE const bool *
|
||||
m_bitset_cref(m_bitset_it_t it)
|
||||
{
|
||||
M_ASSERT (it != NULL && it->set != NULL);
|
||||
it->value = m_bitset_get(it->set, it->index);
|
||||
return &it->value;
|
||||
}
|
||||
|
||||
/* Output the bitset as a formatted text in a FILE */
|
||||
M_INLINE void
|
||||
m_bitset_out_str(FILE *file, const m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
M_ASSERT(file != NULL);
|
||||
fputc ('[', file);
|
||||
for(size_t i = 0; i < set->size; i++) {
|
||||
const bool b = m_bitset_get (set, i);
|
||||
const char c = b ? '1' : '0';
|
||||
fputc (c, file);
|
||||
}
|
||||
fputc (']', file);
|
||||
}
|
||||
|
||||
/* Input the bitset from a formatted text in a FILE */
|
||||
M_INLINE bool
|
||||
m_bitset_in_str(m_bitset_t set, FILE *file)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
M_ASSERT(file != NULL);
|
||||
m_bitset_reset(set);
|
||||
int c = fgetc(file);
|
||||
if (M_UNLIKELY (c != '[')) return false;
|
||||
c = fgetc(file);
|
||||
while (c == '0' || c == '1') {
|
||||
const bool b = (c == '1');
|
||||
m_bitset_push_back (set, b);
|
||||
c = fgetc(file);
|
||||
}
|
||||
M_B1TSET_CONTRACT (set);
|
||||
return c == ']';
|
||||
}
|
||||
|
||||
/* Parse the bitset from a formatted text in a C string */
|
||||
M_INLINE bool
|
||||
m_bitset_parse_str(m_bitset_t set, const char str[], const char **endptr)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
M_ASSERT(str != NULL);
|
||||
bool success = false;
|
||||
m_bitset_reset(set);
|
||||
char c = *str++;
|
||||
if (M_UNLIKELY(c != '[')) goto exit;
|
||||
c = *str++;
|
||||
do {
|
||||
if (M_UNLIKELY(c != '0' && c != '1')) goto exit;
|
||||
const bool b = (c == '1');
|
||||
m_bitset_push_back (set, b);
|
||||
c = *str++;
|
||||
} while (c != ']' && c != 0);
|
||||
M_B1TSET_CONTRACT (set);
|
||||
success = (c == ']');
|
||||
exit:
|
||||
if (endptr) *endptr = str;
|
||||
return success;
|
||||
}
|
||||
|
||||
/* Set the bitset from a formatted text in a C string */
|
||||
M_INLINE bool
|
||||
m_bitset_set_str(m_bitset_t dest, const char str[])
|
||||
{
|
||||
return m_bitset_parse_str(dest, str, NULL);
|
||||
}
|
||||
|
||||
/* Perform an AND operation between the bitsets,
|
||||
* up to the minimum size of both bitsets */
|
||||
M_INLINE void
|
||||
m_bitset_and(m_bitset_t dest, const m_bitset_t src)
|
||||
{
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
M_B1TSET_CONTRACT(src);
|
||||
size_t s = M_MIN(dest->size, src->size);
|
||||
size_t n = (s + M_B1TSET_LIMB_BIT -1) / M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
dest->ptr[i] &= src->ptr[i];
|
||||
// Reduce the dest size to the minimum size between both
|
||||
dest->size = s;
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
}
|
||||
|
||||
/* Perform an OR operation between the bitsets,
|
||||
* up to the minimum size of both bitsets */
|
||||
M_INLINE void
|
||||
m_bitset_or(m_bitset_t dest, const m_bitset_t src)
|
||||
{
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
M_B1TSET_CONTRACT(src);
|
||||
size_t s = M_MIN(dest->size, src->size);
|
||||
size_t n = (s + M_B1TSET_LIMB_BIT - 1) / M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
dest->ptr[i] |= src->ptr[i];
|
||||
// Reduce the dest size to the minimum size between both
|
||||
dest->size = s;
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
}
|
||||
|
||||
/* Perform an XOR operation between the bitsets,
|
||||
* up to the minimum size of both bitsets */
|
||||
M_INLINE void
|
||||
m_bitset_xor(m_bitset_t dest, const m_bitset_t src)
|
||||
{
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
M_B1TSET_CONTRACT(src);
|
||||
size_t s = M_MIN(dest->size, src->size);
|
||||
size_t n = s / M_B1TSET_LIMB_BIT;
|
||||
size_t m = s % M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
dest->ptr[i] ^= src->ptr[i];
|
||||
if (M_LIKELY(m)) {
|
||||
// Last limb needs to be masked too
|
||||
m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1) << m) - 1;
|
||||
dest->ptr[n] = (dest->ptr[n] ^ src->ptr[n]) & mask;
|
||||
}
|
||||
// Reduce the dest size to the minimum size between both
|
||||
dest->size = s;
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
}
|
||||
|
||||
/* Perform a NOT operation of the bitset */
|
||||
M_INLINE void
|
||||
m_bitset_not(m_bitset_t dest)
|
||||
{
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
size_t s = dest->size;
|
||||
size_t n = s / M_B1TSET_LIMB_BIT;
|
||||
size_t m = s % M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
dest->ptr[i] = ~ (dest->ptr[i]);
|
||||
if (M_LIKELY(m)) {
|
||||
// Last limb needs to be masked too
|
||||
m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1) << m) - 1;
|
||||
dest->ptr[n] = (~ dest->ptr[n]) & mask;
|
||||
}
|
||||
M_B1TSET_CONTRACT(dest);
|
||||
}
|
||||
|
||||
/* Copute a hash of the bitset */
|
||||
M_INLINE size_t
|
||||
m_bitset_hash(const m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT(set);
|
||||
size_t s = set->size;
|
||||
size_t n = (s + M_B1TSET_LIMB_BIT-1) / M_B1TSET_LIMB_BIT;
|
||||
M_HASH_DECL(hash);
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
M_HASH_UP(hash, set->ptr[i]);
|
||||
return M_HASH_FINAL (hash);
|
||||
}
|
||||
|
||||
/* Count the number of leading zero */
|
||||
M_INLINE size_t
|
||||
m_bitset_clz(const m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT(set);
|
||||
size_t s = set->size;
|
||||
if (M_UNLIKELY (s == 0)) {
|
||||
return 0;
|
||||
}
|
||||
size_t n = (s -1) / M_B1TSET_LIMB_BIT;
|
||||
size_t m = s % M_B1TSET_LIMB_BIT;
|
||||
m_b1tset_limb_ct limb = set->ptr[n];
|
||||
if (m) {
|
||||
m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1) << m) - 1;
|
||||
limb &= mask;
|
||||
} else {
|
||||
m = M_B1TSET_LIMB_BIT;
|
||||
}
|
||||
s = 0;
|
||||
while (limb == 0 && n > 0) {
|
||||
s += m;
|
||||
limb = set->ptr[--n];
|
||||
m = M_B1TSET_LIMB_BIT;
|
||||
}
|
||||
s += m_core_clz64(limb) - (M_B1TSET_LIMB_BIT - m);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Count the number of trailing zero */
|
||||
M_INLINE size_t
|
||||
m_bitset_ctz(const m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT(set);
|
||||
size_t s = set->size;
|
||||
if (M_UNLIKELY (s == 0)) {
|
||||
return 0;
|
||||
}
|
||||
size_t i = 0, n = (s -1) / M_B1TSET_LIMB_BIT;
|
||||
size_t m = s % M_B1TSET_LIMB_BIT;
|
||||
m_b1tset_limb_ct limb = set->ptr[0];
|
||||
s = 0;
|
||||
while (limb == 0 && i < n) {
|
||||
s += M_B1TSET_LIMB_BIT;
|
||||
limb = set->ptr[++i];
|
||||
}
|
||||
if (i == n && m != 0) {
|
||||
m_b1tset_limb_ct mask = (((m_b1tset_limb_ct)1) << m) - 1;
|
||||
limb &= mask;
|
||||
}
|
||||
unsigned ctz = m_core_ctz64(limb);
|
||||
s += (ctz == 64) ? m : ctz;
|
||||
return s;
|
||||
}
|
||||
|
||||
// For GCC or CLANG or ICC
|
||||
#if defined(__GNUC__)
|
||||
M_INLINE size_t m_b1tset_popcount64(m_b1tset_limb_ct limb)
|
||||
{
|
||||
return (size_t) __builtin_popcountll(limb);
|
||||
}
|
||||
#else
|
||||
// MSVC __popcnt64 may not exist on the target architecture (no emulation layer)
|
||||
// Use emulation layer: https://en.wikipedia.org/wiki/Hamming_weight
|
||||
M_INLINE size_t m_b1tset_popcount64(m_b1tset_limb_ct limb)
|
||||
{
|
||||
limb = limb - ((limb >> 1) & 0x5555555555555555ULL);
|
||||
limb = (limb & 0x3333333333333333ULL) + ((limb >> 2) & 0x3333333333333333ULL);
|
||||
limb = (limb + (limb >> 4)) & 0x0f0f0f0f0f0f0f0fULL;
|
||||
return (limb * 0x0101010101010101ULL) >> 56;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Count the number of 1 */
|
||||
M_INLINE size_t
|
||||
m_bitset_popcount(const m_bitset_t set)
|
||||
{
|
||||
M_B1TSET_CONTRACT(set);
|
||||
size_t s = 0;
|
||||
size_t n = (set->size + M_B1TSET_LIMB_BIT - 1) / M_B1TSET_LIMB_BIT;
|
||||
for(size_t i = 0 ; i < n; i++)
|
||||
s += m_b1tset_popcount64(set->ptr[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Oplist for a bitset */
|
||||
#define M_BITSET_OPLIST \
|
||||
(INIT(m_bitset_init) \
|
||||
,INIT_SET(m_bitset_init_set) \
|
||||
,INIT_WITH(API_1(M_INIT_VAI)) \
|
||||
,SET(m_bitset_set) \
|
||||
,CLEAR(m_bitset_clear) \
|
||||
,INIT_MOVE(m_bitset_init_move) \
|
||||
,MOVE(m_bitset_move) \
|
||||
,SWAP(m_bitset_swap) \
|
||||
,TYPE(m_bitset_t) \
|
||||
,SUBTYPE(bool) \
|
||||
,EMPTY_P(m_bitset_empty_p), \
|
||||
,GET_SIZE(m_bitset_size) \
|
||||
,IT_TYPE(m_bitset_it_t) \
|
||||
,IT_FIRST(m_bitset_it) \
|
||||
,IT_SET(m_bitset_it_set) \
|
||||
,IT_LAST(m_bitset_it_last) \
|
||||
,IT_END(m_bitset_it_end) \
|
||||
,IT_END_P(m_bitset_end_p) \
|
||||
,IT_LAST_P(m_bitset_last_p) \
|
||||
,IT_EQUAL_P(m_bitset_it_equal_p) \
|
||||
,IT_NEXT(m_bitset_next) \
|
||||
,IT_PREVIOUS(m_bitset_previous) \
|
||||
,IT_CREF(m_bitset_cref) \
|
||||
,RESET(m_bitset_reset) \
|
||||
,PUSH(m_bitset_push_back) \
|
||||
,POP(m_bitset_pop_back) \
|
||||
,HASH(m_bitset_hash) \
|
||||
,GET_STR(m_bitset_get_str) \
|
||||
,OUT_STR(m_bitset_out_str) \
|
||||
,PARSE_STR(m_bitset_parse_str) \
|
||||
,IN_STR(m_bitset_in_str) \
|
||||
,EQUAL(m_bitset_equal_p) \
|
||||
)
|
||||
|
||||
/* Register the OPLIST as a global one */
|
||||
#define M_OPL_m_bitset_t() M_BITSET_OPLIST
|
||||
|
||||
// TODO: set_at2, insert_v, remove_v
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
|
||||
#define bitset_s m_bitset_s
|
||||
#define bitset_t m_bitset_t
|
||||
#define bitset_ptr m_bitset_ptr
|
||||
#define bitset_srcptr m_bitset_srcptr
|
||||
#define bitset_it_s m_bitset_it_s
|
||||
#define bitset_it_t m_bitset_it_t
|
||||
|
||||
#define bitset_init m_bitset_init
|
||||
#define bitset_reset m_bitset_reset
|
||||
#define bitset_clear m_bitset_clear
|
||||
#define bitset_set m_bitset_set
|
||||
#define bitset_init_set m_bitset_init_set
|
||||
#define bitset_init_move m_bitset_init_move
|
||||
#define bitset_move m_bitset_move
|
||||
#define bitset_set_at m_bitset_set_at
|
||||
#define bitset_flip_at m_bitset_flip_at
|
||||
#define bitset_push_back m_bitset_push_back
|
||||
#define bitset_resize m_bitset_resize
|
||||
#define bitset_reserve m_bitset_reserve
|
||||
#define bitset_get m_bitset_get
|
||||
#define bitset_pop_back m_bitset_pop_back
|
||||
#define bitset_front m_bitset_front
|
||||
#define bitset_back m_bitset_back
|
||||
#define bitset_empty_p m_bitset_empty_p
|
||||
#define bitset_size m_bitset_size
|
||||
#define bitset_capacity m_bitset_capacity
|
||||
#define bitset_swap_at m_bitset_swap_at
|
||||
#define bitset_swap m_bitset_swap
|
||||
#define bitset_push_at m_bitset_push_at
|
||||
#define bitset_pop_at m_bitset_pop_at
|
||||
#define bitset_equal_p m_bitset_equal_p
|
||||
#define bitset_it m_bitset_it
|
||||
#define bitset_it_set m_bitset_it_set
|
||||
#define bitset_it_last m_bitset_it_last
|
||||
#define bitset_it_end m_bitset_it_end
|
||||
#define bitset_end_p m_bitset_end_p
|
||||
#define bitset_last_p m_bitset_last_p
|
||||
#define bitset_it_equal_p m_bitset_it_equal_p
|
||||
#define bitset_next m_bitset_next
|
||||
#define bitset_previous m_bitset_previous
|
||||
#define bitset_cref m_bitset_cref
|
||||
#define bitset_out_str m_bitset_out_str
|
||||
#define bitset_in_str m_bitset_in_str
|
||||
#define bitset_parse_str m_bitset_parse_str
|
||||
#define bitset_set_str m_bitset_set_str
|
||||
#define bitset_and m_bitset_and
|
||||
#define bitset_or m_bitset_or
|
||||
#define bitset_xor m_bitset_xor
|
||||
#define bitset_not m_bitset_not
|
||||
#define bitset_hash m_bitset_hash
|
||||
#define bitset_clz m_bitset_clz
|
||||
#define bitset_ctz m_bitset_ctz
|
||||
#define bitset_popcount m_bitset_popcount
|
||||
#define bitset_get_str m_bitset_get_str
|
||||
|
||||
#define BITSET_OPLIST M_BITSET_OPLIST
|
||||
#define M_OPL_bitset_t M_OPL_m_bitset_t
|
||||
|
||||
#endif
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#endif
|
||||
|
||||
// NOTE: Define this function only if m-string has been included
|
||||
#if !defined(MSTARLIB_BITSET_STRING_H) && defined(MSTARLIB_STRING_H)
|
||||
#define MSTARLIB_BITSET_STRING_H
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Output to a m_string_t 'str' the formatted text representation of the bitset 'set'
|
||||
or append it to the strinf (append=true) */
|
||||
M_INLINE void
|
||||
m_bitset_get_str(m_string_t str, const m_bitset_t set, bool append)
|
||||
{
|
||||
M_B1TSET_CONTRACT (set);
|
||||
M_ASSERT(str != NULL);
|
||||
(append ? m_string_cat_cstr : m_string_set_cstr) (str, "[");
|
||||
for(size_t i = 0; i < set->size; i++) {
|
||||
const bool b = m_bitset_get (set, i);
|
||||
const char c = b ? '1' : '0';
|
||||
m_string_push_back (str, c);
|
||||
}
|
||||
m_string_push_back (str, ']');
|
||||
}
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,835 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Concurrent memory pool allocator
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_CONCURRENT_MEMPOOL_H
|
||||
#define MSTARLIB_CONCURRENT_MEMPOOL_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-atomic.h"
|
||||
#include "m-genint.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Minimum number of nodes per group of nodes */
|
||||
#define M_CMEMP00L_MIN_NODE_PER_GROUP 16
|
||||
|
||||
#define M_C_MEMPOOL_DEF(name, type_t) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_CMEMP00L_DEF_SINGLY_LIST(name, type_t) \
|
||||
M_CMEMP00L_DEF_LF_QUEUE(name, type_t) \
|
||||
M_CMEMP00L_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
||||
M_CMEMP00L_DEF_SYSTEM_ALLOC(name, type_t) \
|
||||
M_CMEMP00L_DEF_LF_MEMPOOL(name, type_t) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/* Classic internal Singly List without allocation */
|
||||
#define M_CMEMP00L_DEF_SINGLY_LIST(name, type_t) \
|
||||
\
|
||||
typedef struct M_F(name, _slist_node_s) { \
|
||||
struct M_F(name, _slist_node_s) *next; \
|
||||
type_t data; \
|
||||
} M_F(name, _slist_node_ct); \
|
||||
\
|
||||
typedef struct M_F(name, _slist_node_s) *M_F(name, _slist_ct)[1]; \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _slist_init)(M_F(name, _slist_ct) list) \
|
||||
{ \
|
||||
*list = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _slist_push)(M_F(name, _slist_ct) list, \
|
||||
M_F(name, _slist_node_ct) *node) \
|
||||
{ \
|
||||
node->next = *list; \
|
||||
*list = node; \
|
||||
} \
|
||||
\
|
||||
M_INLINE M_F(name, _slist_node_ct) * \
|
||||
M_F(name, _slist_pop)(M_F(name, _slist_ct) list) \
|
||||
{ \
|
||||
M_ASSERT (*list != NULL); \
|
||||
M_F(name, _slist_node_ct) *node = *list; \
|
||||
*list = node->next; \
|
||||
M_IF_DEBUG(node->next = NULL;) \
|
||||
return node; \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _slist_empty_p)(M_F(name, _slist_ct) list) \
|
||||
{ \
|
||||
return *list == NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _slist_move)(M_F(name, _slist_ct) list, \
|
||||
M_F(name, _slist_ct) src) \
|
||||
{ \
|
||||
*list = *src; \
|
||||
*src = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _slist_clear)(M_F(name, _slist_ct) list) \
|
||||
{ \
|
||||
M_F(name, _slist_node_ct) *it = *list, *next; \
|
||||
while (it) { \
|
||||
next = it->next; \
|
||||
M_MEMORY_DEL(it); \
|
||||
it = next; \
|
||||
} \
|
||||
*list = NULL; \
|
||||
} \
|
||||
|
||||
|
||||
/* Lock Free free queue list (not generic one) of lists without allocation
|
||||
Based on Michael & Scott Lock Free Queue List algorithm.
|
||||
Each list is considered empty if there is only one node within.
|
||||
This LF Queue List doesn't try to prevent the ABA problem. It is up to the
|
||||
caller to avoid recycling the nodes too fast.
|
||||
Each list has its own unique NIL ptr in order to avoid issues when
|
||||
migrating a node from a Q to another: in the following scenario,
|
||||
- Thread 1 performs a PUSH of N in Q1 with Q1 empty (only node is NA)
|
||||
NA.next is NIL.
|
||||
- Thread 1 is interrupted just before the CAS on NA.next
|
||||
- Thread 2 performs a sucessfull push of NB in Q1. NA.next is set to NB.
|
||||
- Thread 2 performs a sucessfull pop of NA in Q1
|
||||
- Thread 2 performs a sucessfull push of NA in Q2. NA.next is set to NIL.
|
||||
- Thread 1 is restored and will succeed as NA.next is once again NIL.
|
||||
In order to prevent the last CAS to succeed, each queue uses its own NIL pointer.
|
||||
It is a derived problem of the ABA problem.
|
||||
*/
|
||||
/* TODO: Optimize alignement to reduce memory consumption. NIL object can use []
|
||||
to reduce memory consumption too (non compatible with C++ ...) */
|
||||
#define M_CMEMP00L_DEF_LF_QUEUE(name, type_t) \
|
||||
\
|
||||
typedef struct M_F(name, _lf_node_s) { \
|
||||
M_ATTR_EXTENSION _Atomic(struct M_F(name, _lf_node_s) *) next; \
|
||||
m_gc_atomic_ticket_ct cpt; \
|
||||
M_F(name, _slist_ct) list; \
|
||||
} M_F(name, _lf_node_t); \
|
||||
\
|
||||
typedef struct M_F(name, _lflist_s) { \
|
||||
M_ATTR_EXTENSION _Atomic(M_F(name, _lf_node_t) *) head; \
|
||||
char align1[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_ATTR_EXTENSION _Atomic(M_F(name, _lf_node_t) *) tail; \
|
||||
char align2[M_ALIGN_FOR_CACHELINE_EXCLUSION]; \
|
||||
M_F(name, _lf_node_t) nil; \
|
||||
} M_F(name, _lflist_ct)[1]; \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _lflist_init)(M_F(name, _lflist_ct) list, \
|
||||
M_F(name, _lf_node_t) *node) \
|
||||
{ \
|
||||
atomic_init(&list->head, node); \
|
||||
atomic_init(&list->tail, node); \
|
||||
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _lflist_empty_p)(M_F(name, _lflist_ct) list) \
|
||||
{ \
|
||||
return atomic_load(&list->tail) == atomic_load(&list->head); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _lflist_push)(M_F(name, _lflist_ct) list, \
|
||||
M_F(name, _lf_node_t) *node, m_core_backoff_ct bkoff) \
|
||||
{ \
|
||||
M_F(name, _lf_node_t) *tail; \
|
||||
M_F(name, _lf_node_t) *next; \
|
||||
\
|
||||
atomic_store_explicit(&node->next, &list->nil, memory_order_relaxed); \
|
||||
m_core_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load_explicit(&tail->next, memory_order_acquire); \
|
||||
if (M_UNLIKELY(next != &list->nil)) { \
|
||||
/* Tail was not pointing to the last node \
|
||||
Try to swing Tail to the next node */ \
|
||||
atomic_compare_exchange_weak_explicit(&list->tail, \
|
||||
&tail, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Try to link node at the end of the linked list */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&tail->next, \
|
||||
&next, node, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) \
|
||||
break; \
|
||||
m_core_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
/* Enqueue is done. Try to swing Tail to the inserted node \
|
||||
If it fails, someone else will do it or has already did it. */ \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, node, \
|
||||
memory_order_acq_rel, \
|
||||
memory_order_relaxed); \
|
||||
} \
|
||||
\
|
||||
M_INLINE M_F(name, _lf_node_t) * \
|
||||
M_F(name, _lflist_pop)(M_F(name, _lflist_ct) list, m_core_backoff_ct bkoff) \
|
||||
{ \
|
||||
M_F(name, _lf_node_t) *head; \
|
||||
M_F(name, _lf_node_t) *tail; \
|
||||
M_F(name, _lf_node_t) *next; \
|
||||
\
|
||||
/* Reinitialize backoff */ \
|
||||
m_core_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
head = atomic_load(&list->head); \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load(&head->next); \
|
||||
/* Are head, tail, and next consistent?*/ \
|
||||
if (M_LIKELY(head == \
|
||||
atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
||||
{ \
|
||||
/* Is queue empty or Tail falling behind? */ \
|
||||
if (head == tail) { \
|
||||
/* Is queue empty? */ \
|
||||
if (next == &list->nil) \
|
||||
return NULL; \
|
||||
/* Tail is falling behind. Try to advance it */ \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, \
|
||||
next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Try to swing Head to the next node */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
||||
&head, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) { \
|
||||
break; \
|
||||
} \
|
||||
/* Failure: perform a random exponential backoff */ \
|
||||
m_core_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
/* dequeue returns an element that becomes the new dummy element (the new head), \
|
||||
and the former dummy element (the former head) is removed: \
|
||||
Since we want a link of free list, and we don't care about the content itsef, \
|
||||
provided that the node we return is older than the one we should return, \
|
||||
Therefore, we return the previous dummy head. \
|
||||
As such, it is not the original MSqueue algorithm. */ \
|
||||
M_IF_DEBUG(atomic_store(&head->next, (M_F(name, _lf_node_t) *) 0);) \
|
||||
return head; \
|
||||
} \
|
||||
\
|
||||
/* Dequeue a node if the node is old enough */ \
|
||||
M_INLINE M_F(name, _lf_node_t) * \
|
||||
M_F(name, _lflist_pop_if)(M_F(name, _lflist_ct) list, \
|
||||
m_gc_ticket_ct age, m_core_backoff_ct bkoff) \
|
||||
{ \
|
||||
M_F(name, _lf_node_t) *head; \
|
||||
M_F(name, _lf_node_t) *tail; \
|
||||
M_F(name, _lf_node_t) *next; \
|
||||
\
|
||||
m_core_backoff_reset(bkoff); \
|
||||
while (true) { \
|
||||
head = atomic_load(&list->head); \
|
||||
tail = atomic_load(&list->tail); \
|
||||
next = atomic_load(&head->next); \
|
||||
if (M_LIKELY(head == atomic_load_explicit(&list->head, memory_order_relaxed))) \
|
||||
{ \
|
||||
if (head == tail) { \
|
||||
if (next == &list->nil) \
|
||||
return NULL; \
|
||||
atomic_compare_exchange_strong_explicit(&list->tail, &tail, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed); \
|
||||
} else { \
|
||||
/* Test if the node is old enought to be popped */ \
|
||||
if (atomic_load_explicit(&next->cpt, memory_order_relaxed) >= age) \
|
||||
return NULL; \
|
||||
/* Try to swing Head to the next node */ \
|
||||
if (atomic_compare_exchange_strong_explicit(&list->head, \
|
||||
&head, next, \
|
||||
memory_order_release, \
|
||||
memory_order_relaxed)) { \
|
||||
break; \
|
||||
} \
|
||||
m_core_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
M_IF_DEBUG(atomic_store(&head->next, (M_F(name, _lf_node_t) *) 0);) \
|
||||
return head; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _lflist_clear)(M_F(name, _lflist_ct) list) \
|
||||
{ \
|
||||
m_core_backoff_ct bkoff; \
|
||||
m_core_backoff_init(bkoff); \
|
||||
while (true) { \
|
||||
M_F(name, _lf_node_t) *node = M_F(name, _lflist_pop)(list, bkoff); \
|
||||
if (node == NULL) break; \
|
||||
M_F(name, _lf_node_t) *next = atomic_load_explicit(&node->next, \
|
||||
memory_order_relaxed); \
|
||||
M_F(name, _slist_clear)(node->list); \
|
||||
M_MEMORY_DEL(node); \
|
||||
node = next; \
|
||||
} \
|
||||
/* Dummy node to free too */ \
|
||||
M_F(name, _lf_node_t) *dummy; \
|
||||
dummy = atomic_load_explicit(&list->head, memory_order_relaxed); \
|
||||
M_F(name, _slist_clear)(dummy->list); \
|
||||
M_MEMORY_DEL(dummy); \
|
||||
} \
|
||||
|
||||
/* System node allocator: request memory to the system.
|
||||
As such it is a non Lock-Free path. */
|
||||
#define M_CMEMP00L_DEF_SYSTEM_ALLOC(name, type_t) \
|
||||
\
|
||||
M_INLINE M_F(name, _lf_node_t) * \
|
||||
M_F(name, _alloc_node)(unsigned int initial) \
|
||||
{ \
|
||||
M_F(name, _lf_node_t) * node; \
|
||||
node = M_MEMORY_ALLOC(M_F(name, _lf_node_t)); \
|
||||
if (M_UNLIKELY_NOMEM (node == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_F(name, _lf_node_t))); \
|
||||
return NULL; \
|
||||
} \
|
||||
atomic_init(&node->next, (M_F(name, _lf_node_t) *) 0); \
|
||||
atomic_init(&node->cpt, 0UL); \
|
||||
M_F(name, _slist_init)(node->list); \
|
||||
for(unsigned i = 0; i < initial; i++) { \
|
||||
M_F(name, _slist_node_ct) *n; \
|
||||
n = M_MEMORY_ALLOC(M_F(name, _slist_node_ct)); \
|
||||
if (M_UNLIKELY_NOMEM (n == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_F(name, _lf_node_t))); \
|
||||
return NULL; \
|
||||
} \
|
||||
M_F(name, _slist_push)(node->list, n); \
|
||||
} \
|
||||
return node; \
|
||||
} \
|
||||
|
||||
/* Concurrent Memory pool
|
||||
The data structure is the following.
|
||||
Each thread has its own pool of nodes (local) that only it can
|
||||
access (it is a singly list). If there is no longer any node in this
|
||||
pool, it requests a new pool to the lock free queue of pool (group of
|
||||
nodes). If it fails, it requests a new pool to the system allocator
|
||||
(and from there it is no longer lock free).
|
||||
This memory pool can only be lock free if the initial state is
|
||||
sufficiently dimensionned to avoid calling the system allocator during
|
||||
the normal processing.
|
||||
Then each thread pushs its deleted node into another pool of nodes,
|
||||
where the node is logically deleted (no contain of the node is destroyed
|
||||
at this point and the node can be freely accessed by other threads).
|
||||
Once the thread mempool is put to sleep, the age of the pool of logical
|
||||
deleted nodes is computed and this pool is move to the Lock Free Queue
|
||||
List of pools to be reclaimed. Then A Garbage Collector is performed
|
||||
on this Lock Free Queue list to reclaim all pools thare are sufficiently
|
||||
aged (taking into account the grace period of the pool) to be moved back
|
||||
to the Lock Free Queue of the free pools.
|
||||
|
||||
Each pool of nodes can be in the following state:
|
||||
* FREE state if it is present in the Lock Free Queue of free pools.
|
||||
* EMPTY state if it is present in the Lock Free Queue of empty pools
|
||||
which means that the nodes present in it has been USED directly by a thread,
|
||||
* TO_BE_RECLAIMED state if it is present in the Lock Free Queue of TBR pools
|
||||
|
||||
A pool of nodes will go to the following state:
|
||||
FREE --> EMPTY --> TO_BE_RECLAIMED
|
||||
^ |
|
||||
+----------------------+
|
||||
|
||||
The ABA problem is taken into account as a node cannot be reused in the
|
||||
same queue without performing a full cycle of its state. Moreover
|
||||
it can only move from TO_BE_RECLAIMED to FREE if and only if a grace
|
||||
period is finished (and then we are sure that no thread references any
|
||||
older node).
|
||||
|
||||
Each thread has its own backoff structure (with local pseudo-random
|
||||
generator).
|
||||
|
||||
The grace period is detected through a global age counter (ticket)
|
||||
that is incremented each time a thread is awaken / sleep.
|
||||
Each thread has its own age that is set to the global ticket on sleep/awaken.
|
||||
The age of the pool to be reclaimed is also set to this global age counter.
|
||||
|
||||
To ensure that the grace period is finished, it tests if all threads
|
||||
are younger than the age of the pool to be reclaimed.
|
||||
|
||||
From a performance point of view, this puts a bottleneck on the global
|
||||
age counter that is shared and incremented by all threads. However,
|
||||
the sleep/awaken operations are much less frequent than other operations.
|
||||
Thus, it shall not have a huge impact on the performance if the user
|
||||
code is intelligent with the sleep/awaken operations.
|
||||
|
||||
As such it won't support more than ULONG_MAX sleep for all threads.
|
||||
*/
|
||||
#define M_CMEMP00L_DEF_LFMP_THREAD_MEMPOOL(name, type_t) \
|
||||
\
|
||||
typedef struct M_F(name, _lfmp_thread_s) { \
|
||||
M_F(name, _slist_ct) free; \
|
||||
M_F(name, _slist_ct) to_be_reclaimed; \
|
||||
M_CACHELINE_ALIGN(align1, M_F(name, _slist_ct), M_F(name, _slist_ct)); \
|
||||
} M_F(name, _lfmp_thread_ct); \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _lfmp_thread_init)(M_F(name, _lfmp_thread_ct) *t) \
|
||||
{ \
|
||||
M_F(name, _slist_init)(t->free); \
|
||||
M_F(name, _slist_init)(t->to_be_reclaimed); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _lfmp_thread_clear)(M_F(name, _lfmp_thread_ct) *t) \
|
||||
{ \
|
||||
M_ASSERT(M_F(name, _slist_empty_p)(t->to_be_reclaimed)); \
|
||||
M_F(name, _slist_clear)(t->free); \
|
||||
M_F(name, _slist_clear)(t->to_be_reclaimed); \
|
||||
} \
|
||||
|
||||
/* NOTE: once a node is deleted, its data are kept readable until the future GC */
|
||||
#define M_CMEMP00L_DEF_LF_MEMPOOL(name, type_t) \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
unsigned initial; \
|
||||
M_F(name, _lfmp_thread_ct) *thread_data; \
|
||||
M_F(name, _lflist_ct) free; \
|
||||
M_F(name, _lflist_ct) to_be_reclaimed; \
|
||||
M_F(name, _lflist_ct) empty; \
|
||||
m_cmemp00l_list_ct mempool_node; \
|
||||
struct m_gc_s *gc_mem; \
|
||||
} M_F(name, _t)[1]; \
|
||||
\
|
||||
/* Garbage collect of the nodes of the mempool on sleep */ \
|
||||
M_INLINE void \
|
||||
M_C3(m_cmemp00l_,name,_gc_on_sleep)(m_gc_t gc_mem, m_cmemp00l_list_ct *data, \
|
||||
m_gc_tid_t id, m_gc_ticket_ct ticket, m_gc_ticket_ct min_ticket) \
|
||||
{ \
|
||||
/* Get back the mempool from the node */ \
|
||||
struct M_F(name, _s) *mempool = \
|
||||
M_TYPE_FROM_FIELD(struct M_F(name, _s), data, m_cmemp00l_list_ct, mempool_node); \
|
||||
\
|
||||
/* Move the local nodes of the mempool to be reclaimed to the thread into the global pool */ \
|
||||
if (!M_F(name, _slist_empty_p)(mempool->thread_data[id].to_be_reclaimed)) { \
|
||||
M_F(name, _lf_node_t) *node; \
|
||||
/* Get a new empty group of nodes */ \
|
||||
node = M_F(name, _lflist_pop)(mempool->empty, gc_mem->thread_data[id].bkoff); \
|
||||
if (M_UNLIKELY (node == NULL)) { \
|
||||
/* Fail to get an empty group of node. \
|
||||
Alloc a new one from the system */ \
|
||||
node = M_F(name, _alloc_node)(0); \
|
||||
M_ASSERT(node != NULL); \
|
||||
} \
|
||||
M_ASSERT(M_F(name, _slist_empty_p)(node->list)); \
|
||||
M_F(name, _slist_move)(node->list, mempool->thread_data[id].to_be_reclaimed); \
|
||||
atomic_store_explicit(&node->cpt, ticket, memory_order_relaxed); \
|
||||
M_F(name, _lflist_push)(mempool->to_be_reclaimed, node, gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
\
|
||||
/* Perform a GC of the freelist of nodes */ \
|
||||
while (true) { \
|
||||
M_F(name, _lf_node_t) *node; \
|
||||
node = M_F(name, _lflist_pop_if)(mempool->to_be_reclaimed, \
|
||||
min_ticket, gc_mem->thread_data[id].bkoff); \
|
||||
if (node == NULL) break; \
|
||||
M_F(name, _lflist_push)(mempool->free, node, gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(M_F(name, _t) mem, m_gc_t gc_mem, \
|
||||
unsigned init_node_count, unsigned init_group_count) \
|
||||
{ \
|
||||
const size_t max_thread = gc_mem->max_thread; \
|
||||
/* Initialize the thread data of the mempool */ \
|
||||
mem->thread_data = M_MEMORY_REALLOC(M_F(name, _lfmp_thread_ct), NULL, max_thread); \
|
||||
if (M_UNLIKELY_NOMEM (mem->thread_data == NULL)) { \
|
||||
M_MEMORY_FULL(max_thread * sizeof(M_F(name, _lfmp_thread_ct))); \
|
||||
return; \
|
||||
} \
|
||||
for(unsigned i = 0; i < max_thread;i++) { \
|
||||
M_F(name, _lfmp_thread_init)(&mem->thread_data[i]); \
|
||||
} \
|
||||
/* Preallocate some group of nodes for the mempool */ \
|
||||
mem->initial = M_MAX(M_CMEMP00L_MIN_NODE_PER_GROUP, init_node_count); \
|
||||
M_F(name, _lflist_init)(mem->free, M_F(name, _alloc_node)(init_node_count)); \
|
||||
M_F(name, _lflist_init)(mem->to_be_reclaimed, M_F(name, _alloc_node)(init_node_count)); \
|
||||
M_F(name, _lflist_init)(mem->empty, M_F(name, _alloc_node)(0)); \
|
||||
for(unsigned i = 1; i < init_group_count; i++) { \
|
||||
M_F(name, _lflist_push)(mem->free, M_F(name, _alloc_node)(init_node_count), \
|
||||
gc_mem->thread_data[0].bkoff); \
|
||||
M_F(name, _lflist_push)(mem->empty, M_F(name, _alloc_node)(0), \
|
||||
gc_mem->thread_data[0].bkoff); \
|
||||
} \
|
||||
/* Register the mempool in the GC */ \
|
||||
mem->mempool_node.gc_on_sleep = M_C3(m_cmemp00l_,name,_gc_on_sleep); \
|
||||
mem->mempool_node.next = gc_mem->mempool_list; \
|
||||
gc_mem->mempool_list = &mem->mempool_node; \
|
||||
mem->gc_mem = gc_mem; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(M_F(name, _t) mem) \
|
||||
{ \
|
||||
const unsigned max_thread = mem->gc_mem->max_thread; \
|
||||
for(unsigned i = 0; i < max_thread;i++) { \
|
||||
M_F(name, _lfmp_thread_clear)(&mem->thread_data[i]); \
|
||||
} \
|
||||
M_MEMORY_FREE(mem->thread_data); \
|
||||
mem->thread_data = NULL; \
|
||||
M_F(name, _lflist_clear)(mem->empty); \
|
||||
M_F(name, _lflist_clear)(mem->free); \
|
||||
M_ASSERT(M_F(name, _lflist_empty_p)(mem->to_be_reclaimed)); \
|
||||
M_F(name, _lflist_clear)(mem->to_be_reclaimed); \
|
||||
/* TODO: Unregister from the GC? */ \
|
||||
} \
|
||||
\
|
||||
M_INLINE type_t * \
|
||||
M_F(name, _new)(M_F(name, _t) mem, m_gc_tid_t id) \
|
||||
{ \
|
||||
M_F(name, _slist_node_ct) *snode; \
|
||||
M_F(name, _lf_node_t) *node; \
|
||||
while (true) { \
|
||||
/* Fast & likely path where we access the thread pool of nodes */ \
|
||||
if (M_LIKELY(!M_F(name, _slist_empty_p)(mem->thread_data[id].free))) { \
|
||||
snode = M_F(name, _slist_pop)(mem->thread_data[id].free); \
|
||||
return &snode->data; \
|
||||
} \
|
||||
/* Request a group node to the freelist of groups */ \
|
||||
node = M_F(name, _lflist_pop)(mem->free, mem->gc_mem->thread_data[id].bkoff); \
|
||||
if (M_UNLIKELY (node == NULL)) { \
|
||||
/* Request a new group to the system. Non Lock Free path */ \
|
||||
M_ASSERT(mem->initial > 0); \
|
||||
node = M_F(name, _alloc_node)(mem->initial); \
|
||||
M_ASSERT(node != NULL); \
|
||||
M_ASSERT(!M_F(name, _slist_empty_p)(node->list)); \
|
||||
} \
|
||||
M_F(name, _slist_move)(mem->thread_data[id].free, node->list); \
|
||||
/* Push back the empty group */ \
|
||||
M_ASSERT (M_F(name, _slist_empty_p)(node->list)); \
|
||||
M_F(name, _lflist_push)(mem->empty, node, mem->gc_mem->thread_data[id].bkoff); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _del)(M_F(name, _t) mem, type_t *d, m_gc_tid_t id) \
|
||||
{ \
|
||||
M_F(name, _slist_node_ct) *snode; \
|
||||
M_ASSERT( d != NULL); \
|
||||
snode = M_TYPE_FROM_FIELD(M_F(name, _slist_node_ct), d, type_t, data); \
|
||||
M_F(name, _slist_push)(mem->thread_data[id].to_be_reclaimed, snode); \
|
||||
} \
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
|
||||
/* Define the ID of a thread */
|
||||
typedef unsigned int m_gc_tid_t;
|
||||
|
||||
/* Define the age of a node */
|
||||
/* TODO: Compute if sufficient (worst cast ULONG_MAX is 32 bits) */
|
||||
typedef unsigned long m_gc_ticket_ct;
|
||||
typedef atomic_ulong m_gc_atomic_ticket_ct;
|
||||
|
||||
/* Define the Linked List of mempools that are registered in the GC */
|
||||
struct m_gc_s;
|
||||
typedef struct m_cmemp00l_list_s {
|
||||
struct m_cmemp00l_list_s *next;
|
||||
void (*gc_on_sleep)(struct m_gc_s *gc_mem,
|
||||
struct m_cmemp00l_list_s *data, m_gc_tid_t id,
|
||||
m_gc_ticket_ct ticket, m_gc_ticket_ct min_ticket);
|
||||
void *data;
|
||||
} m_cmemp00l_list_ct;
|
||||
|
||||
/* Define the Garbage collector thread data */
|
||||
typedef struct m_gc_lfmp_thread_s {
|
||||
m_gc_atomic_ticket_ct ticket;
|
||||
m_core_backoff_ct bkoff;
|
||||
M_CACHELINE_ALIGN(align1, atomic_ulong, m_core_backoff_ct);
|
||||
} m_gc_lfmp_thread_ct;
|
||||
|
||||
/* Define the Garbage collector coordinator */
|
||||
typedef struct m_gc_s {
|
||||
m_gc_atomic_ticket_ct ticket;
|
||||
m_gc_tid_t max_thread;
|
||||
m_genint_t thread_alloc;
|
||||
m_gc_lfmp_thread_ct *thread_data;
|
||||
m_cmemp00l_list_ct *mempool_list;
|
||||
} m_gc_t[1];
|
||||
|
||||
M_INLINE void
|
||||
m_gc_init(m_gc_t gc_mem, size_t max_thread)
|
||||
{
|
||||
M_ASSERT(gc_mem != NULL);
|
||||
M_ASSERT(max_thread > 0 && max_thread < INT_MAX);
|
||||
|
||||
atomic_init(&gc_mem->ticket, 0UL);
|
||||
m_genint_init(gc_mem->thread_alloc, (unsigned int) max_thread);
|
||||
gc_mem->thread_data = M_MEMORY_REALLOC(m_gc_lfmp_thread_ct, NULL, max_thread);
|
||||
if (M_UNLIKELY_NOMEM (gc_mem->thread_data == NULL)) {
|
||||
M_MEMORY_FULL(max_thread * sizeof(m_gc_lfmp_thread_ct));
|
||||
return;
|
||||
}
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
atomic_init(&gc_mem->thread_data[i].ticket, ULONG_MAX);
|
||||
m_core_backoff_init(gc_mem->thread_data[i].bkoff);
|
||||
}
|
||||
gc_mem->max_thread = (unsigned int) max_thread;
|
||||
gc_mem->mempool_list = NULL;
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_gc_clear(m_gc_t gc_mem)
|
||||
{
|
||||
M_ASSERT(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
|
||||
for(m_gc_tid_t i = 0; i < gc_mem->max_thread;i++) {
|
||||
m_core_backoff_clear(gc_mem->thread_data[i].bkoff);
|
||||
}
|
||||
M_MEMORY_FREE(gc_mem->thread_data);
|
||||
gc_mem->thread_data = NULL;
|
||||
m_genint_clear(gc_mem->thread_alloc);
|
||||
}
|
||||
|
||||
M_INLINE m_gc_tid_t
|
||||
m_gc_attach_thread(m_gc_t gc_mem)
|
||||
{
|
||||
M_ASSERT(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
|
||||
unsigned id = m_genint_pop(gc_mem->thread_alloc);
|
||||
return M_ASSIGN_CAST(m_gc_tid_t, id);
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_gc_detach_thread(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
M_ASSERT(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
M_ASSERT(id < gc_mem->max_thread);
|
||||
M_ASSERT(atomic_load(&gc_mem->thread_data[id].ticket) == ULONG_MAX);
|
||||
|
||||
m_genint_push(gc_mem->thread_alloc, id);
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_gc_awake(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
M_ASSERT(gc_mem != NULL && gc_mem->max_thread > 0);
|
||||
M_ASSERT(id < gc_mem->max_thread);
|
||||
M_ASSERT(atomic_load(&gc_mem->thread_data[id].ticket) == ULONG_MAX);
|
||||
|
||||
m_gc_ticket_ct t = atomic_fetch_add(&gc_mem->ticket, 1UL) + 1;
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, t);
|
||||
}
|
||||
|
||||
M_INLINE m_gc_ticket_ct
|
||||
m_cmemp00l_gc_min_ticket(m_gc_t gc_mem)
|
||||
{
|
||||
m_gc_ticket_ct min = atomic_load(&gc_mem->thread_data[0].ticket);
|
||||
for(m_gc_tid_t i = 1; i < gc_mem->max_thread; i++) {
|
||||
m_gc_ticket_ct t = atomic_load(&gc_mem->thread_data[i].ticket);
|
||||
min = M_MIN(t, min);
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_gc_sleep(m_gc_t gc_mem, m_gc_tid_t id)
|
||||
{
|
||||
/* Increase life time of the thread */
|
||||
m_gc_ticket_ct t = atomic_fetch_add(&gc_mem->ticket, 1UL);
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, t+1);
|
||||
const m_gc_ticket_ct min_ticket = m_cmemp00l_gc_min_ticket(gc_mem);
|
||||
/* Iterate over all registered mempools */
|
||||
m_cmemp00l_list_ct *it = gc_mem->mempool_list;
|
||||
|
||||
while (it) {
|
||||
/* Perform a garbage collect of the mempool */
|
||||
it->gc_on_sleep(gc_mem, it, id, t, min_ticket);
|
||||
/* Next mempool to scan for GC */
|
||||
it = it->next;
|
||||
}
|
||||
/* Sleep the thread */
|
||||
atomic_store(&gc_mem->thread_data[id].ticket, ULONG_MAX);
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************/
|
||||
/* */
|
||||
/* Variable Length Array MEMPOOL */
|
||||
/* */
|
||||
/***********************************************************************/
|
||||
|
||||
M_CMEMP00L_DEF_SINGLY_LIST(m_vlapool, char)
|
||||
M_CMEMP00L_DEF_LF_QUEUE(m_vlapool, char)
|
||||
M_CMEMP00L_DEF_SYSTEM_ALLOC(m_vlapool, char)
|
||||
|
||||
typedef struct m_vlapool_lfmp_thread_s {
|
||||
m_vlapool_slist_ct to_be_reclaimed;
|
||||
M_CACHELINE_ALIGN(align1, m_vlapool_slist_ct);
|
||||
} m_vlapool_lfmp_thread_ct;
|
||||
|
||||
M_INLINE void
|
||||
m_vlapool_lfmp_thread_init(m_vlapool_lfmp_thread_ct *t)
|
||||
{
|
||||
m_vlapool_slist_init(t->to_be_reclaimed);
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_vlapool_lfmp_thread_clear(m_vlapool_lfmp_thread_ct *t)
|
||||
{
|
||||
M_ASSERT(m_vlapool_slist_empty_p(t->to_be_reclaimed));
|
||||
m_vlapool_slist_clear(t->to_be_reclaimed);
|
||||
}
|
||||
|
||||
typedef struct m_vlapool_s {
|
||||
m_vlapool_lflist_ct to_be_reclaimed;
|
||||
m_vlapool_lflist_ct empty;
|
||||
m_vlapool_lfmp_thread_ct *thread_data;
|
||||
m_cmemp00l_list_ct mvla_node;
|
||||
struct m_gc_s *gc_mem;
|
||||
} m_vlapool_t[1];
|
||||
|
||||
/* Garbage collect of the nodes of the vla mempool on sleep */
|
||||
M_INLINE void
|
||||
m_cmemp00l_vlapool_on_sleep(m_gc_t gc_mem, m_cmemp00l_list_ct *data,
|
||||
m_gc_tid_t id, m_gc_ticket_ct ticket, m_gc_ticket_ct min_ticket)
|
||||
{
|
||||
/* Get back the mempool from the node */
|
||||
struct m_vlapool_s *vlapool =
|
||||
M_TYPE_FROM_FIELD(struct m_vlapool_s, data, m_cmemp00l_list_ct, mvla_node);
|
||||
|
||||
/* Move the local nodes of the vlapool to be reclaimed to the thread into the global pool */
|
||||
if (!m_vlapool_slist_empty_p(vlapool->thread_data[id].to_be_reclaimed)) {
|
||||
m_vlapool_lf_node_t *node;
|
||||
/* Get a new empty group of nodes */
|
||||
node = m_vlapool_lflist_pop(vlapool->empty, gc_mem->thread_data[id].bkoff);
|
||||
if (M_UNLIKELY (node == NULL)) {
|
||||
/* Fail to get an empty group of node.
|
||||
Alloc a new one from the system */
|
||||
node = m_vlapool_alloc_node(0);
|
||||
M_ASSERT(node != NULL);
|
||||
}
|
||||
M_ASSERT(m_vlapool_slist_empty_p(node->list));
|
||||
m_vlapool_slist_move(node->list, vlapool->thread_data[id].to_be_reclaimed);
|
||||
atomic_store_explicit(&node->cpt, ticket, memory_order_relaxed);
|
||||
m_vlapool_lflist_push(vlapool->to_be_reclaimed, node, gc_mem->thread_data[id].bkoff);
|
||||
}
|
||||
|
||||
/* Perform a GC of the freelist of nodes */
|
||||
while (true) {
|
||||
m_vlapool_lf_node_t *node;
|
||||
node = m_vlapool_lflist_pop_if(vlapool->to_be_reclaimed,
|
||||
min_ticket, gc_mem->thread_data[id].bkoff);
|
||||
if (node == NULL) break;
|
||||
// No reuse of VLA nodes. Free physically the node back to the system
|
||||
m_vlapool_slist_clear(node->list);
|
||||
// Add back the empty group of nodes
|
||||
m_vlapool_slist_init(node->list);
|
||||
m_vlapool_lflist_push(vlapool->empty, node, gc_mem->thread_data[id].bkoff);
|
||||
}
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_vlapool_init(m_vlapool_t mem, m_gc_t gc_mem)
|
||||
{
|
||||
const size_t max_thread = gc_mem->max_thread;
|
||||
|
||||
/* Initialize the thread data of the vlapool */
|
||||
mem->thread_data = M_MEMORY_REALLOC(m_vlapool_lfmp_thread_ct, NULL, max_thread);
|
||||
if (M_UNLIKELY_NOMEM (mem->thread_data == NULL)) {
|
||||
M_MEMORY_FULL(max_thread * sizeof(m_vlapool_lfmp_thread_ct));
|
||||
return;
|
||||
}
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
m_vlapool_lfmp_thread_init(&mem->thread_data[i]);
|
||||
}
|
||||
|
||||
/* Initialize the lists */
|
||||
m_vlapool_lflist_init(mem->to_be_reclaimed, m_vlapool_alloc_node(0));
|
||||
m_vlapool_lflist_init(mem->empty, m_vlapool_alloc_node(0));
|
||||
|
||||
/* Register the mempool in the GC */
|
||||
mem->mvla_node.gc_on_sleep = m_cmemp00l_vlapool_on_sleep;
|
||||
mem->mvla_node.next = gc_mem->mempool_list;
|
||||
gc_mem->mempool_list = &mem->mvla_node;
|
||||
mem->gc_mem = gc_mem;
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_vlapool_clear(m_vlapool_t mem)
|
||||
{
|
||||
const unsigned max_thread = mem->gc_mem->max_thread;
|
||||
for(unsigned i = 0; i < max_thread;i++) {
|
||||
m_vlapool_lfmp_thread_clear(&mem->thread_data[i]);
|
||||
}
|
||||
M_MEMORY_FREE(mem->thread_data);
|
||||
mem->thread_data = NULL;
|
||||
m_vlapool_lflist_clear(mem->empty);
|
||||
M_ASSERT(m_vlapool_lflist_empty_p(mem->to_be_reclaimed));
|
||||
m_vlapool_lflist_clear(mem->to_be_reclaimed);
|
||||
/* TODO: Unregister from the GC? */
|
||||
}
|
||||
|
||||
M_INLINE void *
|
||||
m_vlapool_new(m_vlapool_t mem, m_gc_tid_t id, size_t size)
|
||||
{
|
||||
M_ASSERT(mem != NULL && mem->gc_mem != NULL);
|
||||
M_ASSERT(id < mem->gc_mem->max_thread);
|
||||
M_ASSERT( atomic_load(&mem->gc_mem->thread_data[id].ticket) != ULONG_MAX);
|
||||
|
||||
// Nothing to do with theses parameters yet
|
||||
(void) mem;
|
||||
(void) id;
|
||||
|
||||
// Ensure the size is big enough to also represent a node
|
||||
size += offsetof(struct m_vlapool_slist_node_s, data);
|
||||
|
||||
// Simply wrap around a system call to get the memory
|
||||
char *ptr = M_MEMORY_REALLOC(char, NULL, size);
|
||||
return (ptr == NULL) ? NULL : M_ASSIGN_CAST(void *, ptr + offsetof(struct m_vlapool_slist_node_s, data));
|
||||
}
|
||||
|
||||
M_INLINE void
|
||||
m_vlapool_del(m_vlapool_t mem, void *d, m_gc_tid_t id)
|
||||
{
|
||||
M_ASSERT(mem != NULL && mem->gc_mem != NULL);
|
||||
M_ASSERT(id < mem->gc_mem->max_thread);
|
||||
M_ASSERT(atomic_load(&mem->gc_mem->thread_data[id].ticket) != ULONG_MAX);
|
||||
M_ASSERT(d != NULL);
|
||||
|
||||
// Get back the pointer to a struct m_vlapool_slist_node_s.
|
||||
d = M_ASSIGN_CAST(void *, M_ASSIGN_CAST(char *, d) - offsetof(struct m_vlapool_slist_node_s, data));
|
||||
m_vlapool_slist_node_ct *snode = M_ASSIGN_CAST(m_vlapool_slist_node_ct *, d);
|
||||
// Push the logicaly free memory into the list of the nodes to be reclaimed.
|
||||
m_vlapool_slist_push(mem->thread_data[id].to_be_reclaimed, snode);
|
||||
}
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define C_MEMPOOL_DEF M_C_MEMPOOL_DEF
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,925 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Basic Protected Concurrent module over container.
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_CONCURRENT_H
|
||||
#define MSTARLIB_CONCURRENT_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-thread.h"
|
||||
#include "m-atomic.h"
|
||||
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
based on the given container.
|
||||
USAGE: CONCURRENT_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define M_CONCURRENT_DEF(name, ...) \
|
||||
M_CONCURRENT_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
based on the given container as the given name name_t
|
||||
USAGE: CONCURRENT_DEF_AS(name, name_t, type [, oplist_of_the_type]) */
|
||||
#define M_CONCURRENT_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_C0NCURRENT_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__, name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
based on its given container. Operations that perform only read of the container
|
||||
can be done in parallel.
|
||||
USAGE: CONCURRENT_RP_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define M_CONCURRENT_RP_DEF(name, ...) \
|
||||
M_CONCURRENT_RP_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a protected concurrent container and its associated functions
|
||||
as the given name name_t
|
||||
based on its given container. Operations that perform only read of the container
|
||||
can be done in parallel.
|
||||
USAGE: CONCURRENT_RP_DEF_AS(name, name_t, type [, oplist_of_the_type]) */
|
||||
#define M_CONCURRENT_RP_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_C0NCURRENT_RP_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__, name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a protected concurrent container given its name and its oplist.
|
||||
USAGE: CONCURRENT_OPLIST(name[, oplist of the type]) */
|
||||
#define M_CONCURRENT_OPLIST(...) \
|
||||
M_C0NCURRENT_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_BASIC_OPLIST), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************************** INTERNAL ***********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Deferred evaluation for the oplist definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_C0NCURRENT_OPLIST_P1(arg) M_C0NCURRENT_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given oplist */
|
||||
#define M_C0NCURRENT_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_C0NCURRENT_OPLIST_P3, M_C0NCURRENT_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_C0NCURRENT_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_CONCURRENT_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
/* OPLIST definition
|
||||
GET_KEY is not present as its interface is not compatible with a concurrent
|
||||
container (_get returns a pointer to an internal data, data that may be
|
||||
destroyed by another thread).
|
||||
*/
|
||||
#define M_C0NCURRENT_OPLIST_P3(name, oplist) \
|
||||
(M_IF_METHOD(INIT, oplist)(INIT(M_F(name, _init)),) \
|
||||
,M_IF_METHOD(INIT_SET, oplist)(INIT_SET(M_F(name, _init_set)),) \
|
||||
,M_IF_METHOD(SET, oplist)(SET(M_F(name, _set)),) \
|
||||
,M_IF_METHOD(CLEAR, oplist)(CLEAR(M_F(name, _clear)),) \
|
||||
,M_IF_METHOD(INIT_MOVE, oplist)(INIT_MOVE(M_F(name, _init_move)),) \
|
||||
,M_IF_METHOD(MOVE, oplist)(MOVE(M_F(name, _move)),) \
|
||||
,M_IF_METHOD(SWAP,oplist)(SWAP(M_F(name, _swap)),) \
|
||||
,NAME(name) \
|
||||
,TYPE(M_F(name,_ct)) \
|
||||
,SUBTYPE(M_F(name, _subtype_ct)) \
|
||||
,OPLIST(oplist) \
|
||||
,M_IF_METHOD(EMPTY_P, oplist)(EMPTY_P(M_F(name,_empty_p)),) \
|
||||
,M_IF_METHOD(GET_SIZE, oplist)(GET_SIZE(M_F(name,_size)),) \
|
||||
,M_IF_METHOD(RESET, oplist)(RESET(M_F(name,_reset)),) \
|
||||
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_TYPE(M_GET_KEY_TYPE oplist),) \
|
||||
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_TYPE(M_GET_VALUE_TYPE oplist),) \
|
||||
,M_IF_METHOD(KEY_TYPE, oplist)(KEY_OPLIST(M_GET_KEY_OPLIST oplist),) \
|
||||
,M_IF_METHOD(VALUE_TYPE, oplist)(VALUE_OPLIST(M_GET_VALUE_OPLIST oplist), ) \
|
||||
,M_IF_METHOD(SET_KEY, oplist)(SET_KEY(M_F(name, _set_at)),) \
|
||||
,M_IF_METHOD(ERASE_KEY, oplist)(ERASE_KEY(M_F(name, _erase)),) \
|
||||
,M_IF_METHOD(PUSH, oplist)(PUSH(M_F(name,_push)),) \
|
||||
,M_IF_METHOD(POP, oplist)(POP(M_F(name,_pop)),) \
|
||||
,M_IF_METHOD(PUSH_MOVE, oplist)(PUSH_MOVE(M_F(name,_push_move)),) \
|
||||
,M_IF_METHOD(POP_MOVE, oplist)(POP_MOVE(M_F(name,_pop_move)),) \
|
||||
,M_IF_METHOD(GET_STR, oplist)(GET_STR(M_F(name, _get_str)),) \
|
||||
,M_IF_METHOD(PARSE_STR, oplist)(PARSE_STR(M_F(name, _parse_str)),) \
|
||||
,M_IF_METHOD(OUT_STR, oplist)(OUT_STR(M_F(name, _out_str)),) \
|
||||
,M_IF_METHOD(IN_STR, oplist)(IN_STR(M_F(name, _in_str)),) \
|
||||
,M_IF_METHOD(OUT_SERIAL, oplist)(OUT_SERIAL(M_F(name, _out_serial)),) \
|
||||
,M_IF_METHOD(IN_SERIAL, oplist)(IN_SERIAL(M_F(name, _in_serial)),) \
|
||||
,M_IF_METHOD(EQUAL, oplist)(EQUAL(M_F(name, _equal_p)),) \
|
||||
,M_IF_METHOD(HASH, oplist)(HASH(M_F(name, _hash)),) \
|
||||
)
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
/* Internal contract
|
||||
NOTE: Can't check too much without locking the container itself
|
||||
*/
|
||||
#define M_C0NCURRENT_CONTRACT(c) do { \
|
||||
M_ASSERT ((c) != NULL); \
|
||||
M_ASSERT ((c)->self == (c)); \
|
||||
} while (0)
|
||||
|
||||
/* Deferred evaluation for the concurrent definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_C0NCURRENT_DEF_P1(arg) M_ID( M_C0NCURRENT_DEF_P2 arg )
|
||||
|
||||
/* Validate the value oplist before going further */
|
||||
#define M_C0NCURRENT_DEF_P2(name, type, oplist, concurrent_t) \
|
||||
M_IF_OPLIST(oplist)(M_C0NCURRENT_DEF_P3, M_C0NCURRENT_DEF_FAILURE)(name, type, oplist, concurrent_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_C0NCURRENT_DEF_FAILURE(name, type, oplist, concurrent_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(CONCURRENT_DEF): the given argument is not a valid oplist: " M_AS_STR(oplist))
|
||||
|
||||
/* Internal concurrent definition
|
||||
- name: prefix to be used
|
||||
- type: type of the sub container
|
||||
- oplist: oplist of the type of the sub container
|
||||
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
||||
*/
|
||||
#define M_C0NCURRENT_DEF_P3(name, type, oplist, concurrent_t) \
|
||||
M_C0NCURRENT_DEF_TYPE(name, type, oplist, concurrent_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_C0NCURRENT_DEF_CORE(name, type, oplist, concurrent_t) \
|
||||
M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t)
|
||||
|
||||
/* Define the type of a concurrent container */
|
||||
#define M_C0NCURRENT_DEF_TYPE(name, type, oplist, concurrent_t) \
|
||||
\
|
||||
/* Define a concurrent container using a lock */ \
|
||||
typedef struct M_F(name, _s) { \
|
||||
struct M_F(name, _s) *self; \
|
||||
m_mutex_t lock; \
|
||||
m_cond_t there_is_data; /* condition raised when there is data */ \
|
||||
type data; \
|
||||
} concurrent_t[1]; \
|
||||
\
|
||||
/* Define alias for pointer types */ \
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
/* Internal types for oplist */ \
|
||||
typedef concurrent_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
\
|
||||
/* Cannot define iterator as it cannot be reliable in a concurrent type */ \
|
||||
|
||||
/* Define the internal services used for the lock strategy */
|
||||
#define M_C0NCURRENT_DEF_CORE(name, type, oplist, concurrent_t) \
|
||||
\
|
||||
/* Initial the fields of the concurrent object not associated to the \
|
||||
sub-container. */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _internal_init)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_init(out->lock); \
|
||||
m_cond_init(out->there_is_data); \
|
||||
out->self = out; \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
\
|
||||
/* Clear the fields of the concurrent object not associated to the \
|
||||
sub-container. */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _internal_clear)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_clear(out->lock); \
|
||||
m_cond_clear(out->there_is_data); \
|
||||
out->self = NULL; \
|
||||
} \
|
||||
\
|
||||
/* Get the read lock. Multiple threads can get it, but only for reading. \
|
||||
write lock is exclusive. \
|
||||
NOTE: This instance doesn't implement the read/write strategy, \
|
||||
and only get the lock */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _read_lock)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
/* Free the read lock. See above. \
|
||||
NOTE: This instance doesn't implement the read/write strategy, \
|
||||
and only get the lock */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _read_unlock)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
/* Wait for a thread pushing some data in the container. \
|
||||
CONSTRAINT: the read lock shall be get before calling this service */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _read_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
||||
} \
|
||||
\
|
||||
/* Get the write lock. Only one threads can get it, and no other threads \
|
||||
can get the read lock too. \
|
||||
NOTE: This instance doesn't implement the read/write strategy, \
|
||||
and only get the lock */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _write_lock)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
} \
|
||||
\
|
||||
/* Free the write lock. \
|
||||
NOTE: This instance doesn't implement the read/write strategy, \
|
||||
and only get the lock */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _write_unlock)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
/* Wait for a thread pushing some data in the container. \
|
||||
CONSTRAINT: the write lock shall be get before calling this service */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _write_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_cond_wait(out->self->there_is_data, out->self->lock); \
|
||||
} \
|
||||
\
|
||||
/* Wait to all threads that some data are available in the container. \
|
||||
CONSTRAINT: the write lock shall be get before calling this service */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _write_signal)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
/* We need to signal this to ALL waiting threads as multiple threads \
|
||||
may wait on a some data of this container. */ \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
} \
|
||||
|
||||
/* Internal definition of the functions commons to concurrent and rp-concurrent
|
||||
- name: prefix to be used
|
||||
- type: type of the sub container
|
||||
- oplist: oplist of the type of the sub container
|
||||
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
||||
A function is defined only if the underlying container exports the needed
|
||||
services. It is usually one service declared per service exported.
|
||||
*/
|
||||
#define M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t) \
|
||||
\
|
||||
M_IF_METHOD(INIT, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(concurrent_t out) \
|
||||
{ \
|
||||
M_F(name, _internal_init)(out); \
|
||||
M_CALL_INIT(oplist, out->data); \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(INIT_SET, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_set)(concurrent_t out, concurrent_t const src) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(src); \
|
||||
M_ASSERT (out != src); \
|
||||
M_F(name, _internal_init)(out); \
|
||||
M_F(name, _read_lock)(src); \
|
||||
M_CALL_INIT_SET(oplist, out->data, src->data); \
|
||||
M_F(name, _read_unlock)(src); \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(SET, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _set)(concurrent_t out, concurrent_t const src) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
if (M_UNLIKELY (out == src)) return; \
|
||||
/* Need to order the locks in a total way to avoid lock deadlock. \
|
||||
Indeed, two call to _set can be done in two threads with : \
|
||||
T1: A := B \
|
||||
T2: B := A \
|
||||
If we lock first the mutex of out, then the src, it could be possible \
|
||||
in the previous scenario that both mutexs are locked: T1 has locked A \
|
||||
and T2 has locked B, and T1 is waiting for locking B, and T2 is waiting \
|
||||
for locking A, resulting in a deadlock. \
|
||||
To avoid this problem, we **always** lock the mutex which address is \
|
||||
the lowest. */ \
|
||||
if (out < src) { \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_F(name, _read_lock)(src); \
|
||||
} else { \
|
||||
M_F(name, _read_lock)(src); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
} \
|
||||
M_CALL_SET(oplist, out->data, src->data); \
|
||||
if (out < src) { \
|
||||
M_F(name, _read_lock)(src); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} else { \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
M_F(name, _read_unlock)(src); \
|
||||
} \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(CLEAR, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
/* No need to lock. A clear is supposed to be called when all operations \
|
||||
of the container in other threads are terminated */ \
|
||||
M_CALL_CLEAR(oplist, out->data); \
|
||||
M_F(name, _internal_clear)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(INIT_MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(concurrent_t out, concurrent_t src) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(src); \
|
||||
M_ASSERT (out != src); \
|
||||
/* No need to lock 'src' ? */ \
|
||||
M_F(name, _internal_init)(out); \
|
||||
M_CALL_INIT_MOVE(oplist, out->data, src->data); \
|
||||
M_F(name, _internal_clear)(src); \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(concurrent_t out, concurrent_t src) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_C0NCURRENT_CONTRACT(src); \
|
||||
/* No need to lock 'src' ? */ \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_MOVE(oplist, out->data, src->data); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
M_F(name, _internal_clear)(src); \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(SWAP, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _swap)(concurrent_t out, concurrent_t src) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_C0NCURRENT_CONTRACT(src); \
|
||||
if (M_UNLIKELY (out == src)) return; \
|
||||
/* See comment above */ \
|
||||
if (out < src) { \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_F(name, _write_lock)(src); \
|
||||
} else { \
|
||||
M_F(name, _write_lock)(src); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
} \
|
||||
M_CALL_SWAP(oplist, out->data, src->data); \
|
||||
if (out < src) { \
|
||||
M_F(name, _write_unlock)(src); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} else { \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
M_F(name, _write_unlock)(src); \
|
||||
} \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(RESET, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _reset)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_RESET(oplist, out->data); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(EMPTY_P, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _empty_p)(concurrent_t const out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
bool b = M_CALL_EMPTY_P(oplist, out->data); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_SIZE, oplist)( \
|
||||
M_INLINE size_t \
|
||||
M_F(name, _size)(concurrent_t const out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
size_t r = M_CALL_GET_SIZE(oplist, out->data); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
return r; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(SET_KEY, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _set_at)(concurrent_t out, M_GET_KEY_TYPE oplist const key, M_GET_VALUE_TYPE oplist const data) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_SET_KEY(oplist, out->data, key, data); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_KEY, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _get_copy)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_ASSERT (out_data != NULL); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
||||
if (p != NULL) { \
|
||||
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
||||
} \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
return p != NULL; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(SAFE_GET_KEY, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _safe_get_copy)(M_GET_VALUE_TYPE oplist *out_data, concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_ASSERT (out_data != NULL); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_SAFE_GET_KEY(oplist, out->data, key); \
|
||||
M_ASSERT (p != NULL); \
|
||||
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(ERASE_KEY, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _erase)(concurrent_t out, M_GET_KEY_TYPE oplist const key) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
bool b = M_CALL_ERASE_KEY(oplist, out->data, key); \
|
||||
/* We suppose that the container has 'infinite' capacity, so \
|
||||
we won't signal that a free space has been created */ \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(PUSH, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _push)(concurrent_t out, M_GET_SUBTYPE oplist const data) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_PUSH(oplist, out->data, data); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
\
|
||||
M_EMPLACE_QUEUE_DEF(name, concurrent_t, M_F(name, _emplace), M_GET_OPLIST oplist, M_EMPLACE_QUEUE_GENE) \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(POP, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _pop)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_POP(oplist, p, out->data); \
|
||||
/* See comment above */ \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(PUSH_MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _push_move)(concurrent_t out, M_GET_SUBTYPE oplist *data) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_PUSH_MOVE(oplist, out->data, data); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(POP_MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _pop_move)(M_GET_SUBTYPE oplist *p, concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
M_CALL_POP_MOVE(oplist, p, out->data); \
|
||||
/* See comment above */ \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_STR, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _get_str)(m_string_t str, concurrent_t const out, bool a) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
M_CALL_GET_STR(oplist, str, out->data, a); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(OUT_STR, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _out_str)(FILE *f, concurrent_t const out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
M_CALL_OUT_STR(oplist, f, out->data); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(PARSE_STR, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _parse_str)(concurrent_t out, const char str[], const char **e) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
bool b = M_CALL_PARSE_STR(oplist, out->data, str, e); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(IN_STR, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _in_str)(concurrent_t out, FILE *f) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
bool b = M_CALL_IN_STR(oplist, out->data, f); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(OUT_SERIAL, oplist)( \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _out_serial)(m_serial_write_t f, concurrent_t const out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
m_serial_return_code_t r = M_CALL_OUT_SERIAL(oplist, f, out->data); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
return r; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(IN_SERIAL, oplist)( \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _in_serial)(concurrent_t out, m_serial_read_t f) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _write_lock)(out); \
|
||||
m_serial_return_code_t r = M_CALL_IN_SERIAL(oplist, out->data, f); \
|
||||
M_F(name, _write_signal)(out); \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return r; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(EQUAL, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _equal_p)(concurrent_t const out1, concurrent_t const out2) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out1); \
|
||||
M_C0NCURRENT_CONTRACT(out2); \
|
||||
if (M_UNLIKELY (out1 == out2)) return true; \
|
||||
/* See comment above on mutal mutexs */ \
|
||||
if (out1 < out2) { \
|
||||
M_F(name, _read_lock)(out1); \
|
||||
M_F(name, _read_lock)(out2); \
|
||||
} else { \
|
||||
M_F(name, _read_lock)(out2); \
|
||||
M_F(name, _read_lock)(out1); \
|
||||
} \
|
||||
bool b = M_CALL_EQUAL(oplist, out1->data, out2->data); \
|
||||
if (out1 < out2) { \
|
||||
M_F(name, _read_unlock)(out2); \
|
||||
M_F(name, _read_unlock)(out1); \
|
||||
} else { \
|
||||
M_F(name, _read_unlock)(out1); \
|
||||
M_F(name, _read_unlock)(out2); \
|
||||
} \
|
||||
return b; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(GET_KEY, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _get_blocking)(M_GET_VALUE_TYPE oplist *out_data, const concurrent_t out, M_GET_KEY_TYPE oplist const key, bool blocking) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_ASSERT (out_data != NULL); \
|
||||
bool ret = false; \
|
||||
M_F(name, _read_lock)(out); \
|
||||
while (true) { \
|
||||
M_GET_VALUE_TYPE oplist *p = M_CALL_GET_KEY(oplist, out->data, key); \
|
||||
if (p != NULL) { \
|
||||
M_CALL_SET(M_GET_VALUE_OPLIST oplist, *out_data, *p); \
|
||||
ret = true; \
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
/* No data: wait for a write to signal some data */ \
|
||||
M_F(name, _read_wait)(out); \
|
||||
} \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD2(POP, EMPTY_P, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _pop_blocking)(M_GET_SUBTYPE oplist *p, concurrent_t out, bool blocking) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_ASSERT (p != NULL); \
|
||||
bool ret = false; \
|
||||
M_F(name, _write_lock)(out); \
|
||||
while (true) { \
|
||||
if (!M_CALL_EMPTY_P(oplist, out->data)) { \
|
||||
M_CALL_POP(oplist, p, out->data); \
|
||||
ret = true; \
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
/* No data: wait for a write to signal some data */ \
|
||||
M_F(name, _write_wait)(out); \
|
||||
} \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD2(POP_MOVE, EMPTY_P, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _pop_move_blocking)(M_GET_SUBTYPE oplist *p, concurrent_t out, bool blocking) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_ASSERT (p != NULL); \
|
||||
bool ret = false; \
|
||||
M_F(name, _write_lock)(out); \
|
||||
while (true) { \
|
||||
if (!M_CALL_EMPTY_P(oplist, out->data)) { \
|
||||
M_CALL_POP_MOVE(oplist, p, out->data); \
|
||||
ret = true; \
|
||||
break; \
|
||||
} \
|
||||
if (blocking == false) break; \
|
||||
/* No data: wait for a write to signal some data */ \
|
||||
M_F(name, _write_wait)(out); \
|
||||
} \
|
||||
M_F(name, _write_unlock)(out); \
|
||||
return ret; \
|
||||
} \
|
||||
,) \
|
||||
\
|
||||
M_IF_METHOD(HASH, oplist)( \
|
||||
M_INLINE size_t \
|
||||
M_F(name, _hash)(concurrent_t const out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
M_F(name, _read_lock)(out); \
|
||||
size_t h = M_CALL_HASH(oplist, out->data); \
|
||||
M_F(name, _read_unlock)(out); \
|
||||
/* The hash is unchanged by the concurrent container */ \
|
||||
return h; \
|
||||
} \
|
||||
,) \
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
/* Deferred evaluation for the RP concurrent definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_C0NCURRENT_RP_DEF_P1(arg) M_ID( M_C0NCURRENT_RP_DEF_P2 arg )
|
||||
|
||||
/* Validate the value oplist before going further */
|
||||
#define M_C0NCURRENT_RP_DEF_P2(name, type, oplist, concurrent_t) \
|
||||
M_IF_OPLIST(oplist)(M_C0NCURRENT_RP_DEF_P3, M_C0NCURRENT_RP_DEF_FAILURE)(name, type, oplist, concurrent_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_C0NCURRENT_RP_DEF_FAILURE(name, type, oplist, concurrent_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(CONCURRENT_RP_DEF): the given argument is not a valid oplist: " M_AS_STR(oplist))
|
||||
|
||||
/* Internal RP concurrent definition
|
||||
- name: prefix to be used
|
||||
- type: type of the sub container
|
||||
- oplist: oplist of the type of the sub container
|
||||
- concurrent_t: alias for M_F(name, _t) [ type of the container ]
|
||||
*/
|
||||
#define M_C0NCURRENT_RP_DEF_P3(name, type, oplist, concurrent_t) \
|
||||
M_C0NCURRENT_RP_DEF_TYPE(name, type, oplist, concurrent_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_C0NCURRENT_RP_DEF_CORE(name, type, oplist, concurrent_t) \
|
||||
M_C0NCURRENT_DEF_COMMON(name, type, oplist, concurrent_t)
|
||||
|
||||
/* Define the type of a RP concurrent container */
|
||||
#define M_C0NCURRENT_RP_DEF_TYPE(name, type, oplist, concurrent_t) \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
struct M_F(name, _s) *self; \
|
||||
m_mutex_t lock; \
|
||||
m_cond_t rw_done; \
|
||||
size_t read_count; \
|
||||
bool writer_waiting; \
|
||||
m_cond_t there_is_data; /* condition raised when there is data */ \
|
||||
type data; \
|
||||
} concurrent_t[1]; \
|
||||
\
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the internal services for the lock strategy of a RP container */
|
||||
#define M_C0NCURRENT_RP_DEF_CORE(name, type, oplist, concurrent_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _internal_init)(concurrent_t out) \
|
||||
{ \
|
||||
m_mutex_init(out->lock); \
|
||||
m_cond_init(out->rw_done); \
|
||||
m_cond_init(out->there_is_data); \
|
||||
out->self = out; \
|
||||
out->read_count = 0; \
|
||||
out->writer_waiting = false; \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _internal_clear)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_clear(out->lock); \
|
||||
m_cond_clear(out->rw_done); \
|
||||
m_cond_clear(out->there_is_data); \
|
||||
out->self = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _read_lock)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
struct M_F(name, _s) *self = out->self; \
|
||||
m_mutex_lock (self->lock); \
|
||||
while (self->writer_waiting == true) { \
|
||||
m_cond_wait(self->rw_done, self->lock); \
|
||||
} \
|
||||
self->read_count ++; \
|
||||
m_mutex_unlock (self->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _read_unlock)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
struct M_F(name, _s) *self = out->self; \
|
||||
m_mutex_lock (self->lock); \
|
||||
self->read_count --; \
|
||||
if (self->read_count == 0) { \
|
||||
m_cond_broadcast (self->rw_done); \
|
||||
} \
|
||||
m_mutex_unlock (self->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _write_lock)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
while (out->writer_waiting == true) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
out->writer_waiting = true; \
|
||||
while (out->read_count > 0) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _write_unlock)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
out->writer_waiting = false; \
|
||||
m_cond_broadcast (out->rw_done); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _read_wait)(const concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
struct M_F(name, _s) *self = out->self; \
|
||||
M_ASSERT (self == out); \
|
||||
m_mutex_lock (out->self->lock); \
|
||||
self->read_count --; \
|
||||
if (self->read_count == 0) { \
|
||||
m_cond_broadcast (self->rw_done); \
|
||||
} \
|
||||
m_cond_wait(self->there_is_data, self->lock); \
|
||||
while (self->writer_waiting == true) { \
|
||||
m_cond_wait(self->rw_done, self->lock); \
|
||||
} \
|
||||
self->read_count ++; \
|
||||
m_mutex_unlock (out->self->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _write_wait)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
out->writer_waiting = false; \
|
||||
m_cond_broadcast (out->rw_done); \
|
||||
m_cond_wait(out->there_is_data, out->lock); \
|
||||
while (out->writer_waiting == true) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
out->writer_waiting = true; \
|
||||
while (out->read_count > 0) { \
|
||||
m_cond_wait(out->rw_done, out->lock); \
|
||||
} \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _write_signal)(concurrent_t out) \
|
||||
{ \
|
||||
M_C0NCURRENT_CONTRACT(out); \
|
||||
m_mutex_lock (out->lock); \
|
||||
m_cond_broadcast(out->there_is_data); \
|
||||
m_mutex_unlock (out->lock); \
|
||||
} \
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define CONCURRENT_DEF M_CONCURRENT_DEF
|
||||
#define CONCURRENT_DEF_AS M_CONCURRENT_DEF_AS
|
||||
#define CONCURRENT_RP_DEF M_CONCURRENT_RP_DEF
|
||||
#define CONCURRENT_RP_DEF_AS M_CONCURRENT_RP_DEF_AS
|
||||
#define CONCURRENT_OPLIST M_CONCURRENT_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
-5297
File diff suppressed because it is too large
Load Diff
-1147
File diff suppressed because it is too large
Load Diff
-1988
File diff suppressed because it is too large
Load Diff
@@ -1,415 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Function Object module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_FUNCOBJ_H
|
||||
#define MSTARLIB_FUNCOBJ_H
|
||||
|
||||
#include "m-core.h"
|
||||
|
||||
/* Define a function object interface of name 'name'
|
||||
* with a function like retcode, type of param1, type of param 2, ...
|
||||
* USAGE:
|
||||
* FUNC_OBJ_ITF_DEF(name, retcode type, type of param1, type of param 2, ...)
|
||||
*/
|
||||
#define M_FUNC_OBJ_ITF_DEF(name, ...) \
|
||||
M_FUNC_OBJ_ITF_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a function object interface of name 'name'
|
||||
* as the given name name_t
|
||||
* USAGE:
|
||||
* FUNC_OBJ_ITF_DEF_AS(name, name_t, retcode type, type of param1, type of param 2, ...)
|
||||
*/
|
||||
#define M_FUNC_OBJ_ITF_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)(M_FUNC0BJ_ITF_NO_PARAM_DEF, M_FUNC0BJ_ITF_PARAM_DEF)(name, name_t, __VA_ARGS__) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define a function object instance of name 'name' based on the interface 'base_name'
|
||||
* The function is defined using:
|
||||
* - the prototype of the inherited interface
|
||||
* - the parameters of the function are named as per the list param_list
|
||||
* - the core of the function given in 'callback_core'
|
||||
* - optionals member attributes of the function object can be defined after the core
|
||||
* (just like for tuple & variant: (name, type [, oplist])
|
||||
*
|
||||
* In the core of the function, parameters are accessible just like a normal function.
|
||||
* A special variable named 'self' that refers to the function object itself
|
||||
* can be used to access member attributes using the syntax self->param1, ...
|
||||
*
|
||||
* There shall be **exactly** the same number of parameters in 'param_list' than
|
||||
* the number of parameters of the interface 'base_name'
|
||||
*
|
||||
* USAGE/EXAMPLE:
|
||||
* FUNC_OBJ_INS_DEF(name, base_name, (param1, ...), { return param1 * self->member1 }, (member1, int), ...)
|
||||
*/
|
||||
#define M_FUNC_OBJ_INS_DEF(name, base_name, param_list, ...) \
|
||||
M_FUNC_OBJ_INS_DEF_AS(name, M_F(name,_t), base_name, param_list, __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a function object instance of name 'name' based on the interface 'base_name'
|
||||
* as the given name name_t.
|
||||
* See FUNC_OBJ_INS_DEF for additional details.
|
||||
*
|
||||
* USAGE/EXAMPLE:
|
||||
* FUNC_OBJ_INS_DEF_AS(name, name_t, base_name, (param1, ...), { return param1 * self->member1 }, (member1, int), ...)
|
||||
*/
|
||||
#define M_FUNC_OBJ_INS_DEF_AS(name, name_t, base_name, param_list, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)(M_FUNC0BJ_INS_NO_ATTR_DEF, M_FUNC0BJ_INS_ATTR_DEF)(name, name_t, base_name, param_list, __VA_ARGS__) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* OPLIST of the instanced function object
|
||||
* USAGE:
|
||||
* FUNC_OBJ_INS_OPLIST(name, oplist of the attr1, ...)
|
||||
*/
|
||||
#define M_FUNC_OBJ_INS_OPLIST(...) \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)(M_FUNC0BJ_INS_NO_ATTR_OPLIST, M_FUNC0BJ_INS_ATTR_OPLIST_P1)( __VA_ARGS__)
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************************** INTERNAL ***********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* To be used by M_IF_FUNCOBJ macro defined in m-core.
|
||||
NOTE: It is reversed (0 instead of 1) so that it can be used in M_IF reliabely.
|
||||
*/
|
||||
#define M_FUNC0BJ_IS_NOT_DEFINED 0
|
||||
|
||||
/* Design Constraints:
|
||||
* callback SHALL be the first member of the structures in all the definitions.
|
||||
*
|
||||
* Structure definitions are specialized in function of the presence or not
|
||||
* of parameters and/or attributes
|
||||
* FIXME: How to factorize reasonnably well between the definitions?
|
||||
*/
|
||||
|
||||
/* Specialization of the OPLIST in function if there is at least one member or not */
|
||||
#define M_FUNC0BJ_INS_NO_ATTR_OPLIST(name) ( \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name, _ct)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
INIT(M_F(name,_init)) \
|
||||
)
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_FUNC0BJ_INS_ATTR_OPLIST_P1(name, ...) \
|
||||
M_IF(M_REDUCE(M_OPLIST_P, M_AND, __VA_ARGS__))(M_FUNC0BJ_INS_ATTR_OPLIST_P3, M_FUNC0BJ_INS_ATTR_OPLIST_FAILURE)(name, __VA_ARGS__)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_FUNC0BJ_INS_ATTR_OPLIST_FAILURE(name, ...) \
|
||||
((M_LIB_ERROR(ONE_ARGUMENT_OF_FUNC_OBJ_INS_OPLIST_IS_NOT_AN_OPLIST, name, __VA_ARGS__)))
|
||||
|
||||
/* Define the oplist of the instance */
|
||||
#define M_FUNC0BJ_INS_ATTR_OPLIST_P3(name, ...) ( \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name, _ct)), \
|
||||
INIT_WITH(M_F(name, _init_with)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
M_IF_METHOD_ALL(INIT, __VA_ARGS__)(INIT(M_F(name,_init)),), \
|
||||
PROPERTIES(( LET_AS_INIT_WITH(1) )) \
|
||||
)
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
/* Specialization of the definition a function object interface of name 'name'
|
||||
* with a function like "retcode (void)" that doesn't have any input parameters.
|
||||
* Define the following types to be used by instance:
|
||||
* - M_F(name, _retcode_ct): internal type of the return code
|
||||
* - M_F(name, _callback_ct): internal type of the callback.
|
||||
* - M_F(name, _ct): synonym of main type used by oplist.
|
||||
*/
|
||||
#define M_FUNC0BJ_ITF_NO_PARAM_DEF(name, interface_t, retcode) \
|
||||
\
|
||||
/* Forward declaration */ \
|
||||
struct M_F(name, _s); \
|
||||
\
|
||||
/* Internal type for instance */ \
|
||||
typedef retcode M_F(name, _retcode_ct); \
|
||||
/* No parameters to the callback */ \
|
||||
typedef retcode(*M_F(name, _callback_ct))(struct M_F(name, _s) *); \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _callback_ct) callback; \
|
||||
} *interface_t; \
|
||||
\
|
||||
/* Internal type for oplist & instance */ \
|
||||
typedef interface_t M_F(name, _ct); \
|
||||
\
|
||||
M_INLINE retcode \
|
||||
M_F(name, _call)(interface_t funcobj) \
|
||||
{ \
|
||||
M_IF(M_KEYWORD_P(void, retcode)) ( /* nothing */,return) \
|
||||
funcobj->callback(funcobj); \
|
||||
}
|
||||
|
||||
|
||||
/* Specialization of the definition a function object interface of name 'name'
|
||||
* with a function like retcode, type of param1, type of param 2, ...
|
||||
* with mandatory input parameters.
|
||||
* Define the following types to be used by instance:
|
||||
* - M_F(name, _retcode_ct): internal type of the return code
|
||||
* - M_F(name, _callback_ct): internal type of the callback.
|
||||
* - M_C4(name, _param_, num, _ct) for each parameter defined
|
||||
* - M_F(name, _ct): synonym of main type used by oplist.
|
||||
*/
|
||||
#define M_FUNC0BJ_ITF_PARAM_DEF(name, interface_t, retcode, ...) \
|
||||
\
|
||||
/* Forward declaration */ \
|
||||
struct M_F(name, _s); \
|
||||
\
|
||||
/* Internal types for instance */ \
|
||||
typedef retcode M_F(name, _retcode_ct); \
|
||||
/* Define types for all parameters */ \
|
||||
M_MAP3(M_FUNC0BJ_BASE_TYPE, name, __VA_ARGS__) \
|
||||
/* Define callback type with all parameters */ \
|
||||
typedef retcode(*M_F(name, _callback_ct))(struct M_F(name, _s) *, __VA_ARGS__); \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _callback_ct) callback; \
|
||||
} *interface_t; \
|
||||
\
|
||||
/* Internal type for oplist & instance */ \
|
||||
typedef interface_t M_F(name, _ct); \
|
||||
\
|
||||
M_INLINE retcode \
|
||||
M_F(name, _call)(interface_t funcobj \
|
||||
M_MAP3(M_FUNC0BJ_BASE_ARGLIST, name, __VA_ARGS__) ) \
|
||||
{ \
|
||||
/* If the retcode is 'void', don't return the value of the callback */ \
|
||||
M_IF(M_KEYWORD_P(void, retcode)) ( /* nothing */,return) \
|
||||
funcobj->callback(funcobj M_MAP3(M_FUNC0BJ_BASE_ARGCALL, name, __VA_ARGS__) ); \
|
||||
}
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
/* Specialization of the definition a function object instance of name 'name'
|
||||
* with no member attribute.
|
||||
*/
|
||||
#define M_FUNC0BJ_INS_NO_ATTR_DEF(name, instance_t, base_name, param_list, callback_core) \
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_C(base_name, _callback_ct) callback; \
|
||||
} instance_t[1]; \
|
||||
\
|
||||
/* Internal type for oplist */ \
|
||||
typedef instance_t M_F(name, _ct); \
|
||||
\
|
||||
M_INLINE M_C(base_name, _retcode_ct) \
|
||||
M_F(name, _callback)(M_C(base_name, _ct) _self \
|
||||
M_IF_EMPTY(M_OPFLAT param_list)( \
|
||||
/* No param */, \
|
||||
M_MAP3(M_FUNC0BJ_INS_ARGLIST, base_name, M_OPFLAT param_list) \
|
||||
) \
|
||||
) \
|
||||
{ \
|
||||
struct M_F(name, _s) *self = (struct M_F(name, _s) *)_self; \
|
||||
(void) self; /* maybe unused */ \
|
||||
callback_core; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_with)(instance_t obj) \
|
||||
{ \
|
||||
obj->callback = M_F(name, _callback); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(instance_t obj) \
|
||||
{ \
|
||||
(void) obj; /* nothing to do */ \
|
||||
} \
|
||||
\
|
||||
M_INLINE struct M_C(base_name, _s) * \
|
||||
M_F(name, _as_interface)(instance_t obj) \
|
||||
{ \
|
||||
return (struct M_C(base_name, _s) *) obj; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(instance_t obj) \
|
||||
{ \
|
||||
obj->callback = M_F(name, _callback); \
|
||||
} \
|
||||
|
||||
|
||||
/* Specialization of the definition a function object instance of name 'name'
|
||||
* with mandatory member attribute.
|
||||
* First inject oplist in member attributes.
|
||||
*/
|
||||
#define M_FUNC0BJ_INS_ATTR_DEF(name, instance_t, base_name, param_list, callback_core, ...) \
|
||||
M_FUNC0BJ_INS_ATTR_DEF_P2(name, instance_t, base_name, param_list, callback_core, M_FUNC0BJ_INJECT_GLOBAL(__VA_ARGS__) )
|
||||
|
||||
/* Inject the oplist within the list of arguments */
|
||||
#define M_FUNC0BJ_INJECT_GLOBAL(...) \
|
||||
M_MAP_C(M_FUNC0BJ_INJECT_OPLIST_A, __VA_ARGS__)
|
||||
|
||||
/* Transform (x, type) into (x, type, oplist) if there is global registered oplist
|
||||
or (x, type, M_BASIC_OPLIST) if there is no global one,
|
||||
or keep (x, type, oplist) if oplist was already present */
|
||||
#define M_FUNC0BJ_INJECT_OPLIST_A( duo_or_trio ) \
|
||||
M_FUNC0BJ_INJECT_OPLIST_B duo_or_trio
|
||||
#define M_FUNC0BJ_INJECT_OPLIST_B( f, ... ) \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)( (f, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)()), (f, __VA_ARGS__) )
|
||||
|
||||
// Test if all third argument of all arguments is an oplist
|
||||
#define M_FUNC0BJ_IF_ALL_OPLIST(...) \
|
||||
M_IF(M_REDUCE(M_FUNC0BJ_IS_OPLIST_P, M_AND, __VA_ARGS__))
|
||||
// Test if the third argument is an oplist. a is a trio (name, type, oplist)
|
||||
#define M_FUNC0BJ_IS_OPLIST_P(a) \
|
||||
M_OPLIST_P(M_RET_ARG3 a)
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_FUNC0BJ_INS_ATTR_DEF_P2(name, instance_t, base_name, param_list, callback_core, ...) \
|
||||
M_FUNC0BJ_IF_ALL_OPLIST(__VA_ARGS__)(M_FUNC0BJ_INS_ATTR_DEF_P3, M_FUNC0BJ_INS_ATTR_DEF_FAILURE)(name, instance_t, base_name, param_list, callback_core, __VA_ARGS__)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_FUNC0BJ_INS_ATTR_DEF_FAILURE(name, instance_t, base_name, param_list, callback_core, ...) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(FUNC_OBJ_INS_DEF): at least one of the given argument is not a valid oplist: " #__VA_ARGS__)
|
||||
|
||||
/* Expand the Function Object with members */
|
||||
#define M_FUNC0BJ_INS_ATTR_DEF_P3(name, instance_t, base_name, param_list, callback_core, ...) \
|
||||
typedef struct M_F(name, _s) { \
|
||||
/* Callback is the mandatory first argument */ \
|
||||
M_C(base_name, _callback_ct) callback; \
|
||||
/* All the member attribute of the Function Object */ \
|
||||
M_MAP(M_FUNC0BJ_INS_ATTR_STRUCT, __VA_ARGS__) \
|
||||
} instance_t[1]; \
|
||||
\
|
||||
/* Internal type for oplist */ \
|
||||
typedef instance_t M_F(name, _ct); \
|
||||
\
|
||||
M_FUNC0BJ_CONTROL_ALL_OPLIST(name, __VA_ARGS__) \
|
||||
\
|
||||
M_INLINE M_C(base_name, _retcode_ct) \
|
||||
M_F(name, _callback)(M_C(base_name, _ct) _self \
|
||||
M_IF_EMPTY(M_OPFLAT param_list)( \
|
||||
/* No param */, \
|
||||
M_MAP3(M_FUNC0BJ_INS_ARGLIST, base_name, M_OPFLAT param_list) \
|
||||
) \
|
||||
) \
|
||||
{ \
|
||||
/* Let's go through an uintptr_t to avoid [broken] aliasing detection by compiler */ \
|
||||
uintptr_t __self = (uintptr_t) _self; \
|
||||
struct M_F(name, _s) *self = (struct M_F(name, _s) *)(void*)__self; \
|
||||
(void) self; /* maybe unused */ \
|
||||
callback_core; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_with)(instance_t obj M_MAP(M_FUNC0BJ_INS_ATTR_LIST, __VA_ARGS__)) \
|
||||
{ \
|
||||
obj->callback = M_F(name, _callback); \
|
||||
M_MAP(M_FUNC0BJ_INS_ATTR_INIT_SET, __VA_ARGS__); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(instance_t obj) \
|
||||
{ \
|
||||
M_MAP(M_FUNC0BJ_INS_ATTR_CLEAR, __VA_ARGS__); \
|
||||
} \
|
||||
\
|
||||
M_INLINE struct M_C(base_name, _s) * \
|
||||
M_F(name, _as_interface)(instance_t obj) \
|
||||
{ \
|
||||
return (struct M_C(base_name, _s) *) obj; \
|
||||
} \
|
||||
\
|
||||
M_IF(M_FUNC0BJ_TEST_METHOD_P(INIT, __VA_ARGS)) \
|
||||
( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(instance_t obj) \
|
||||
{ \
|
||||
obj->callback = M_F(name, _callback); \
|
||||
M_MAP(M_FUNC0BJ_INS_ATTR_INIT, __VA_ARGS__); \
|
||||
} \
|
||||
, /* END OF INIT METHOD */ ) \
|
||||
|
||||
|
||||
|
||||
/* Define a numbered type of a parameter of the callback*/
|
||||
#define M_FUNC0BJ_BASE_TYPE(name, num, type) \
|
||||
typedef type M_C4(name, _param_, num, _ct);
|
||||
|
||||
/* Define a list of the type of arguments for a function definition */
|
||||
#define M_FUNC0BJ_BASE_ARGLIST(name, num, type) \
|
||||
M_DEFERRED_COMMA type M_C(param_, num)
|
||||
|
||||
/* Define a list of arguments for a function call */
|
||||
#define M_FUNC0BJ_BASE_ARGCALL(name, num, type) \
|
||||
M_DEFERRED_COMMA M_C(param_, num)
|
||||
|
||||
|
||||
/* Helper macros */
|
||||
/* arg = (name, type [, oplist]) */
|
||||
#define M_FUNC0BJ_INS_ATTR_STRUCT(arg) \
|
||||
M_RET_ARG2 arg M_RET_ARG1 arg;
|
||||
|
||||
#define M_FUNC0BJ_INS_ATTR_LIST(arg) \
|
||||
M_DEFERRED_COMMA M_RET_ARG2 arg const M_RET_ARG1 arg
|
||||
|
||||
#define M_FUNC0BJ_INS_ATTR_INIT(arg) \
|
||||
M_CALL_INIT(M_RET_ARG3 arg, obj -> M_RET_ARG1 arg);
|
||||
|
||||
#define M_FUNC0BJ_INS_ATTR_INIT_SET(arg) \
|
||||
M_CALL_INIT_SET(M_RET_ARG3 arg, obj -> M_RET_ARG1 arg, M_RET_ARG1 arg);
|
||||
|
||||
#define M_FUNC0BJ_INS_ATTR_CLEAR(arg) \
|
||||
M_CALL_CLEAR(M_RET_ARG3 arg, obj -> M_RET_ARG1 arg);
|
||||
|
||||
/* Define the list of arguments of the instance of the callback */
|
||||
#define M_FUNC0BJ_INS_ARGLIST(name, num, param) \
|
||||
M_DEFERRED_COMMA M_C4(name, _param_, num, _ct) param
|
||||
|
||||
|
||||
/* Macros for testing for a method presence in all the attributes */
|
||||
#define M_FUNC0BJ_TEST_METHOD2_P(method, op) \
|
||||
M_TEST_METHOD_P(method, op)
|
||||
#define M_FUNC0BJ_TEST_METHOD1_P(method, arg) \
|
||||
M_APPLY(M_FUNC0BJ_TEST_METHOD2_P, method, M_RET_ARG3 arg)
|
||||
#define M_FUNC0BJ_TEST_METHOD_P(method, ...) \
|
||||
M_IF(M_REDUCE2(M_FUNC0BJ_TEST_METHOD1_P, M_AND, method, __VA_ARGS__))
|
||||
|
||||
/* Macro for checking compatible type and oplist for all the attributes */
|
||||
#define M_FUNC0BJ_CONTROL_ALL_OPLIST(name, ...) \
|
||||
M_MAP2(M_FUNC0BJ_CONTROL_OPLIST, name, __VA_ARGS__)
|
||||
#define M_FUNC0BJ_CONTROL_OPLIST(name, a) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, M_RET_ARG1 a, M_RET_ARG2 a, M_RET_ARG3 a)
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define FUNC_OBJ_ITF_DEF M_FUNC_OBJ_ITF_DEF
|
||||
#define FUNC_OBJ_ITF_DEF_AS M_FUNC_OBJ_ITF_DEF_AS
|
||||
#define FUNC_OBJ_INS_DEF M_FUNC_OBJ_INS_DEF
|
||||
#define FUNC_OBJ_INS_DEF_AS M_FUNC_OBJ_INS_DEF_AS
|
||||
#define FUNC_OBJ_INS_OPLIST M_FUNC_OBJ_INS_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Integer Generator (GENINT) module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_GENINT_H
|
||||
#define MSTARLIB_GENINT_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-atomic.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* GENINT is an internal container providing unique integers.
|
||||
It has the following properties:
|
||||
- it stores integer from [0..N) (N is fixed).
|
||||
- an integer can have only one occurrence in the container.
|
||||
- you can atomically push in / pop out integer from this container
|
||||
provided that it is not already in the container.
|
||||
- there are no order (like FIFO or stack)
|
||||
|
||||
This can be used to map integers to index of resources in a table.
|
||||
At most we can support N = 32*64 = 2048 with the master limb usage.
|
||||
For the typical usage of this container
|
||||
(mapping hardware or software limited resources), this should be
|
||||
enough.
|
||||
*/
|
||||
|
||||
// Define the limb size used by genint
|
||||
typedef unsigned long long m_genint_limb_ct;
|
||||
|
||||
/* Define a generator of unique integer (Lock Free) */
|
||||
typedef struct m_genint_s {
|
||||
unsigned int n; // size of the container
|
||||
unsigned int max; // number of allocated limb - 1
|
||||
m_genint_limb_ct mask0; // mask of the last limb (constant)
|
||||
m_genint_limb_ct mask_master; // mask of the master limb that controls others (constant)
|
||||
atomic_ullong master; // master bitfield (which informs if a limb is full or not)
|
||||
atomic_ullong *data; // the bitfield which informs if an integer is used or not
|
||||
} m_genint_t[1];
|
||||
|
||||
// Define the max absolute supported value. It should be 2048 on most implementations.
|
||||
#define M_GENINT_MAX_ALLOC (M_GEN1NT_LIMBSIZE * (M_GEN1NT_LIMBSIZE - M_GEN1NT_ABA_CPT))
|
||||
|
||||
// Define the size of a limb in bits.
|
||||
#define M_GEN1NT_LIMBSIZE ((unsigned)(sizeof(m_genint_limb_ct) * CHAR_BIT))
|
||||
|
||||
// Define the contract of a genint
|
||||
#define M_GEN1NT_CONTRACT(s) do { \
|
||||
M_ASSERT (s != NULL); \
|
||||
M_ASSERT (s->n > 0 && s->n <= M_GENINT_MAX_ALLOC); \
|
||||
M_ASSERT ((s->max+1) * M_GEN1NT_LIMBSIZE >= s->n); \
|
||||
M_ASSERT (s->data != NULL); \
|
||||
} while (0)
|
||||
|
||||
// Define the limb one
|
||||
#define M_GEN1NT_ONE ((m_genint_limb_ct)1)
|
||||
|
||||
#define M_GEN1NT_FULL_MASK ULLONG_MAX
|
||||
|
||||
// Value returned in case of error (not integer available).
|
||||
#define M_GENINT_ERROR (UINT_MAX)
|
||||
|
||||
/* 32 bits of the master mask are kept for handling the ABA problem.
|
||||
* NOTE: May be too much. 16 bits should be more than enough. TBC
|
||||
*/
|
||||
#define M_GEN1NT_ABA_CPT 32
|
||||
#define M_GEN1NT_ABA_CPT_T uint32_t
|
||||
|
||||
// Set the bit 'i' of the master limb, and increase ABA counter.
|
||||
#define M_GEN1NT_MASTER_SET(master, i) \
|
||||
((((master)& (~((M_GEN1NT_ONE<< M_GEN1NT_ABA_CPT)-1))) | (M_GEN1NT_ONE << (M_GEN1NT_LIMBSIZE - 1 - i))) \
|
||||
|((M_GEN1NT_ABA_CPT_T)((master) + 1)))
|
||||
|
||||
// Reset the bit i of the master limb, and increase ABA counter.
|
||||
#define M_GEN1NT_MASTER_RESET(master, i) \
|
||||
(((master) & (~((M_GEN1NT_ONE<< M_GEN1NT_ABA_CPT)-1)) & ~(M_GEN1NT_ONE << (M_GEN1NT_LIMBSIZE - 1 - i))) \
|
||||
|((M_GEN1NT_ABA_CPT_T)((master) + 1)))
|
||||
|
||||
/* Initialize an integer generator (CONSTRUCTOR).
|
||||
* Initialy, the container is full of all the integers up to 'n-1'
|
||||
* The typical sequence is to initialize the container, and pop
|
||||
* the integer from it. Each pop integer is **unique** for all threads,
|
||||
* meaning it can be used to index global unique resources shared
|
||||
* for all threads.
|
||||
*/
|
||||
M_INLINE void
|
||||
m_genint_init(m_genint_t s, unsigned int n)
|
||||
{
|
||||
M_ASSERT (s != NULL && n > 0 && n <= M_GENINT_MAX_ALLOC);
|
||||
const size_t alloc = (n + M_GEN1NT_LIMBSIZE - 1) / M_GEN1NT_LIMBSIZE;
|
||||
const unsigned int index = n % M_GEN1NT_LIMBSIZE;
|
||||
atomic_ullong *ptr = M_MEMORY_REALLOC (atomic_ullong, NULL, alloc);
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
|
||||
M_MEMORY_FULL(alloc);
|
||||
return;
|
||||
}
|
||||
s->n = n;
|
||||
s->data = ptr;
|
||||
s->max = (unsigned int) (alloc-1);
|
||||
s->mask0 = (index == 0) ? M_GEN1NT_FULL_MASK : ~((M_GEN1NT_ONE<<(M_GEN1NT_LIMBSIZE-index))-1);
|
||||
s->mask_master = (((M_GEN1NT_ONE << alloc) - 1) << (M_GEN1NT_LIMBSIZE-alloc)) >> M_GEN1NT_ABA_CPT;
|
||||
atomic_init (&s->master, (m_genint_limb_ct)0);
|
||||
for(unsigned int i = 0; i < alloc; i++)
|
||||
atomic_init(&s->data[i], (m_genint_limb_ct)0);
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
}
|
||||
|
||||
/* Clear an integer generator (Destructor) */
|
||||
M_INLINE void
|
||||
m_genint_clear(m_genint_t s)
|
||||
{
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
M_MEMORY_FREE(s->data);
|
||||
s->data = NULL;
|
||||
}
|
||||
|
||||
/* Return the maximum integer that the generator will provide */
|
||||
M_INLINE size_t
|
||||
m_genint_size(m_genint_t s)
|
||||
{
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
return s->n;
|
||||
}
|
||||
|
||||
/* Get an unique integer from the integer generator.
|
||||
* NOTE: For a typical case, the amortized cost is one CAS per pop. */
|
||||
M_INLINE unsigned int
|
||||
m_genint_pop(m_genint_t s)
|
||||
{
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
// First read master to see which limb is not full.
|
||||
m_genint_limb_ct master = atomic_load(&s->master);
|
||||
// While master is not full
|
||||
while ((master >> M_GEN1NT_ABA_CPT) != s->mask_master) {
|
||||
// Let's get the index i of the first not full limb according to master.
|
||||
unsigned int i = m_core_clz64(~master);
|
||||
M_ASSERT (i < M_GEN1NT_LIMBSIZE);
|
||||
// Let's compute the mask of this limb representing the limb as being full
|
||||
m_genint_limb_ct mask = s->mask0;
|
||||
mask = (i == s->max) ? mask : M_GEN1NT_FULL_MASK;
|
||||
unsigned int bit;
|
||||
// Let's load this limb,
|
||||
m_genint_limb_ct next, org = atomic_load(&s->data[i]);
|
||||
do {
|
||||
// If it is now full, we have been preempted by another.
|
||||
if (M_UNLIKELY (org == mask))
|
||||
goto next_element;
|
||||
M_ASSERT (org != M_GEN1NT_FULL_MASK);
|
||||
// At least one bit is free in the limb. Find one.
|
||||
bit = M_GEN1NT_LIMBSIZE - 1 - m_core_clz64(~org);
|
||||
M_ASSERT (bit < M_GEN1NT_LIMBSIZE);
|
||||
M_ASSERT ((org & (M_GEN1NT_ONE<<bit)) == 0);
|
||||
M_ASSERT (i * M_GEN1NT_LIMBSIZE + M_GEN1NT_LIMBSIZE - 1 - bit < s->n);
|
||||
// Set the integer as being used.
|
||||
next = org | (M_GEN1NT_ONE << bit);
|
||||
// Try to reserve the integer
|
||||
} while (!atomic_compare_exchange_weak (&s->data[i], &org, next));
|
||||
// We have reserved the integer.
|
||||
// If the limb is now full, try to update master
|
||||
if (M_UNLIKELY(next == mask)) {
|
||||
while (true) {
|
||||
m_genint_limb_ct newMaster;
|
||||
if (next == mask) {
|
||||
newMaster = M_GEN1NT_MASTER_SET(master, i);
|
||||
} else {
|
||||
newMaster = M_GEN1NT_MASTER_RESET(master, i);
|
||||
}
|
||||
if (atomic_compare_exchange_weak (&s->master, &master, newMaster))
|
||||
break;
|
||||
// Fail to update. Reload limb to check if it is still full.
|
||||
next = atomic_load(&s->data[i]);
|
||||
}
|
||||
}
|
||||
// Return the new number
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
return i * M_GEN1NT_LIMBSIZE + M_GEN1NT_LIMBSIZE - 1 - bit;
|
||||
next_element:
|
||||
// Reload master
|
||||
master = atomic_load(&s->master);
|
||||
}
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
return M_GENINT_ERROR; // No more resource available
|
||||
}
|
||||
|
||||
/* Restore a used integer in the integer generator.
|
||||
* NOTE: For a typical case, the amortized cost is one CAS per pop */
|
||||
M_INLINE void
|
||||
m_genint_push(m_genint_t s, unsigned int n)
|
||||
{
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
M_ASSERT (n < s->n);
|
||||
const unsigned int i = n / M_GEN1NT_LIMBSIZE;
|
||||
const unsigned int bit = M_GEN1NT_LIMBSIZE - 1 - (n % M_GEN1NT_LIMBSIZE);
|
||||
m_genint_limb_ct master = atomic_load(&s->master);
|
||||
// Load the limb
|
||||
m_genint_limb_ct next, org = atomic_load(&s->data[i]);
|
||||
do {
|
||||
M_ASSERT ((org & (M_GEN1NT_ONE << bit)) != 0);
|
||||
// Reset it
|
||||
next = org & (~(M_GEN1NT_ONE << bit));
|
||||
// Try to unreserve it.
|
||||
} while (!atomic_compare_exchange_weak (&s->data[i], &org, next));
|
||||
// if the limb was marked as full by master
|
||||
m_genint_limb_ct mask = s->mask0;
|
||||
mask = (i == s->max) ? mask : M_GEN1NT_FULL_MASK;
|
||||
if (M_UNLIKELY (next != mask)) {
|
||||
// Let's compute the mask of this limb representing the limb as being full
|
||||
// Let's try to update master to say that this limb is not full
|
||||
while (true) {
|
||||
m_genint_limb_ct newMaster;
|
||||
if (next == mask) {
|
||||
newMaster = M_GEN1NT_MASTER_SET(master, i);
|
||||
} else {
|
||||
newMaster = M_GEN1NT_MASTER_RESET(master, i);
|
||||
}
|
||||
if (atomic_compare_exchange_weak (&s->master, &master, newMaster))
|
||||
break;
|
||||
// Fail to update. Reload limb to check if it is still full.
|
||||
next = atomic_load(&s->data[i]);
|
||||
}
|
||||
}
|
||||
M_GEN1NT_CONTRACT(s);
|
||||
}
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#endif
|
||||
@@ -1,679 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Intrusive List module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_I_LIST_H
|
||||
#define MSTARLIB_I_LIST_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-list.h" // For M_L1ST_ITBASE_DEF
|
||||
|
||||
/* Interface to add to a structure to enable intrusive doubly-linked support.
|
||||
name: name of the intrusive list.
|
||||
type: name of the type of the structure (aka. struct test_s) - not used currently
|
||||
USAGE:
|
||||
typedef struct tmp_str_s {
|
||||
...
|
||||
ILIST_INTERFACE(tmpstr, struct tmp_str_s);
|
||||
...
|
||||
} tmp_str_t;
|
||||
*/
|
||||
#define M_ILIST_INTERFACE(name, type) \
|
||||
struct m_il1st_head_s name
|
||||
|
||||
|
||||
/* Define a doubly-linked intrusive list of a given type.
|
||||
The type needs to have ILIST_INTERFACE().
|
||||
USAGE:
|
||||
ILIST_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define M_ILIST_DEF(name, ...) \
|
||||
M_ILIST_DEF_AS(name, M_F(name,_t), M_F(name,_it_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a doubly-linked intrusive list of a given type
|
||||
as the provided type name_t with the iterator named it_t.
|
||||
The type needs to have ILIST_INTERFACE().
|
||||
USAGE:
|
||||
ILIST_DEF_AS(name, name_t, it_t, type [, oplist_of_the_type]) */
|
||||
#define M_ILIST_DEF_AS(name, name_t, it_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_IL1ST_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t, it_t ), \
|
||||
(name, __VA_ARGS__, name_t, it_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a doubly-linked instrusive list of type.
|
||||
USAGE:
|
||||
ILIST_OPLIST(name [, oplist_of_the_type]) */
|
||||
#define M_ILIST_OPLIST(...) \
|
||||
M_IL1ST_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_BASIC_OPLIST), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************************** INTERNAL ***********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Define the basic structure to be added in all objects. */
|
||||
typedef struct m_il1st_head_s {
|
||||
struct m_il1st_head_s *next;
|
||||
struct m_il1st_head_s *prev;
|
||||
} m_il1st_head_ct;
|
||||
|
||||
/* Indirection call to allow expanding all arguments */
|
||||
#define M_IL1ST_OPLIST_P1(arg) M_IL1ST_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given oplist */
|
||||
#define M_IL1ST_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_IL1ST_OPLIST_P3, M_IL1ST_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_IL1ST_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_ILIST_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
/* Define the oplist of an ilist of type */
|
||||
#define M_IL1ST_OPLIST_P3(name, oplist) \
|
||||
(INIT(M_F(name, _init)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
INIT_MOVE(M_F(name, _init_move)), \
|
||||
MOVE(M_F(name, _move)), \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name,_ct)), \
|
||||
RESET(M_F(name,_reset)), \
|
||||
SUBTYPE(M_F(name,_subtype_ct)), \
|
||||
EMPTY_P(M_F(name,_empty_p)), \
|
||||
IT_TYPE(M_F(name,_it_ct)), \
|
||||
IT_FIRST(M_F(name,_it)), \
|
||||
IT_SET(M_F(name,_it_set)), \
|
||||
IT_LAST(M_F(name,_it_last)), \
|
||||
IT_END(M_F(name,_it_end)), \
|
||||
IT_END_P(M_F(name,_end_p)), \
|
||||
IT_LAST_P(M_F(name,_last_p)), \
|
||||
IT_EQUAL_P(M_F(name,_it_equal_p)), \
|
||||
IT_NEXT(M_F(name,_next)), \
|
||||
IT_PREVIOUS(M_F(name,_previous)), \
|
||||
IT_REF(M_F(name,_ref)), \
|
||||
IT_CREF(M_F(name,_cref)), \
|
||||
IT_REMOVE(M_F(name,_remove)), \
|
||||
M_IF_METHOD(NEW, oplist)(IT_INSERT(M_F(name,_insert)),), \
|
||||
OPLIST(oplist), \
|
||||
SPLICE_BACK(M_F(name,_splice_back)) \
|
||||
)
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
/* Contract respected by all intrusive lists */
|
||||
#define M_IL1ST_CONTRACT(name, list) do { \
|
||||
M_ASSERT(list != NULL); \
|
||||
M_ASSERT(list->name.prev != NULL); \
|
||||
M_ASSERT(list->name.next != NULL); \
|
||||
M_ASSERT(list->name.next->prev == &list->name); \
|
||||
M_ASSERT(list->name.prev->next == &list->name); \
|
||||
M_ASSERT(!(list->name.prev == &list->name) || list->name.prev == list->name.next); \
|
||||
} while (0)
|
||||
|
||||
#define M_IL1ST_NODE_CONTRACT(node) do { \
|
||||
M_ASSERT((node) != NULL); \
|
||||
M_ASSERT((node)->prev != NULL); \
|
||||
M_ASSERT((node)->next != NULL); \
|
||||
M_ASSERT((node)->next->prev == node); \
|
||||
M_ASSERT((node)->prev->next == node); \
|
||||
} while (0)
|
||||
|
||||
/* Indirection call to allow expanding all arguments */
|
||||
#define M_IL1ST_DEF_P1(arg) M_ID( M_IL1ST_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_IL1ST_DEF_P2(name, type, oplist, list_t, it_t) \
|
||||
M_IF_OPLIST(oplist)(M_IL1ST_DEF_P3, M_IL1ST_DEF_FAILURE)(name, type, oplist, list_t, it_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_IL1ST_DEF_FAILURE(name, type, oplist, list_t, it_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(ILIST_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Definition of the type and function for an intrusive doubly-linked list.
|
||||
USAGE:
|
||||
name: name of the intrusive list
|
||||
type: type of the object
|
||||
oplist: oplist of the type
|
||||
list_t: type of the intrusive list (name##_t)
|
||||
it_t: iterator of the intrusive list (name##_it_t)
|
||||
*/
|
||||
#define M_IL1ST_DEF_P3(name, type, oplist, list_t, it_t) \
|
||||
M_IL1ST_DEF_TYPE(name, type, oplist, list_t, it_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_IL1ST_DEF_CORE(name, type, oplist, list_t, it_t) \
|
||||
/* Used of internal macro from m-list */ \
|
||||
M_L1ST_ITBASE_DEF(name, type, oplist, list_t, it_t)
|
||||
|
||||
/* Define the type of an intrusive list */
|
||||
#define M_IL1ST_DEF_TYPE(name, type, oplist, list_t, it_t) \
|
||||
\
|
||||
/* Define the list as a structure containing pointers \
|
||||
* to the front & back nodes */ \
|
||||
typedef struct M_F(name, _s) { \
|
||||
struct m_il1st_head_s name; \
|
||||
} list_t[1]; \
|
||||
\
|
||||
/* Define internal types pointers to such a list */ \
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
/* Define iterator of such a list */ \
|
||||
typedef struct M_F(name, _it_s) { \
|
||||
struct m_il1st_head_s *head; \
|
||||
struct m_il1st_head_s *previous; \
|
||||
struct m_il1st_head_s *current; \
|
||||
struct m_il1st_head_s *next; \
|
||||
} it_t[1]; \
|
||||
\
|
||||
/* Define types used by oplist */ \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
typedef list_t M_F(name, _ct); \
|
||||
typedef it_t M_F(name, _it_ct); \
|
||||
|
||||
/* Define core functions for intrusive lists */
|
||||
#define M_IL1ST_DEF_CORE(name, type, oplist, list_t, it_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(list_t list) \
|
||||
{ \
|
||||
M_ASSERT (list != NULL); \
|
||||
list->name.next = &list->name; \
|
||||
list->name.prev = &list->name; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _reset)(list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
for(struct m_il1st_head_s *it = list->name.next, *next ; \
|
||||
it != &list->name; it = next) { \
|
||||
/* Cannot check node contract as previous node may be deleted */ \
|
||||
type *obj = M_TYPE_FROM_FIELD(type, it, \
|
||||
struct m_il1st_head_s, name); \
|
||||
/* Read next now before the object is destroyed */ \
|
||||
next = it->next; \
|
||||
M_ASSERT (next != NULL); \
|
||||
M_CALL_CLEAR(oplist, *obj); \
|
||||
/* Delete also the object if a DELETE operand is registered */ \
|
||||
M_IF_METHOD(DEL, oplist)(M_CALL_DEL(oplist, obj), (void) 0); \
|
||||
} \
|
||||
/* Nothing remains in the list anymore */ \
|
||||
list->name.next = &list->name; \
|
||||
list->name.prev = &list->name; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(list_t list) \
|
||||
{ \
|
||||
/* Nothing to do more than clean the list itself */ \
|
||||
M_F(name, _reset)(list); \
|
||||
/* For safety purpose (create invalid represenation of object) */ \
|
||||
list->name.next = NULL; \
|
||||
list->name.prev = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _empty_p)(const list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
return list->name.next == &list->name; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(list_t list, list_t ref) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, ref); \
|
||||
M_ASSERT (list != ref); \
|
||||
M_F(name,_init)(list); \
|
||||
if (!M_F(name,_empty_p)(ref)) { \
|
||||
list->name.next = ref->name.next; \
|
||||
list->name.prev = ref->name.prev; \
|
||||
list->name.next->prev = &list->name; \
|
||||
list->name.prev->next = &list->name; \
|
||||
} \
|
||||
ref->name.next = NULL; \
|
||||
ref->name.prev = NULL; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(list_t list, list_t ref) \
|
||||
{ \
|
||||
M_F(name, _clear)(list); \
|
||||
M_F(name, _init_move)(list, ref); \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _size)(const list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
size_t s = 0; \
|
||||
/* Scan the full list to count the number of elements */ \
|
||||
for(const struct m_il1st_head_s *it = list->name.next ; \
|
||||
it != &list->name; it = it->next) { \
|
||||
M_IL1ST_NODE_CONTRACT(it); \
|
||||
s++; \
|
||||
} \
|
||||
return s; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _push_back)(list_t list, type *obj) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (obj != NULL); \
|
||||
struct m_il1st_head_s *prev = list->name.prev; \
|
||||
list->name.prev = &obj->name; \
|
||||
obj->name.prev = prev; \
|
||||
obj->name.next = &list->name; \
|
||||
prev->next = &obj->name; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _push_front)(list_t list, type *obj) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (obj != NULL); \
|
||||
struct m_il1st_head_s *next = list->name.next; \
|
||||
list->name.next = &obj->name; \
|
||||
obj->name.next = next; \
|
||||
obj->name.prev = &list->name; \
|
||||
next->prev = &obj->name; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _push_after)(type *obj_pos, type *obj) \
|
||||
{ \
|
||||
M_ASSERT (obj_pos != NULL && obj != NULL); \
|
||||
/* We don't have the list, so we have no contract at list level */ \
|
||||
M_IL1ST_NODE_CONTRACT(&obj_pos->name); \
|
||||
struct m_il1st_head_s *next = obj_pos->name.next; \
|
||||
obj_pos->name.next = &obj->name; \
|
||||
obj->name.next = next; \
|
||||
obj->name.prev = &obj_pos->name; \
|
||||
next->prev = &obj->name; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_field)(type *obj) \
|
||||
{ \
|
||||
M_ASSERT (obj != NULL); \
|
||||
/* Init the fields of the node. To be used in object constructor */ \
|
||||
obj->name.next = NULL; \
|
||||
obj->name.prev = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _unlink)(type *obj) \
|
||||
{ \
|
||||
M_ASSERT (obj != NULL); \
|
||||
/* We don't have the list, so we have no contract at list level */ \
|
||||
M_IL1ST_NODE_CONTRACT(&obj->name); \
|
||||
struct m_il1st_head_s *next = obj->name.next; \
|
||||
struct m_il1st_head_s *prev = obj->name.prev; \
|
||||
next->prev = prev; \
|
||||
prev->next = next; \
|
||||
/* Note: not really needed, but safer */ \
|
||||
obj->name.next = NULL; \
|
||||
obj->name.prev = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _back)(const list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT(!M_F(name, _empty_p)(list)); \
|
||||
return M_TYPE_FROM_FIELD(type, list->name.prev, \
|
||||
struct m_il1st_head_s, name); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _front)(const list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT(!M_F(name, _empty_p)(list)); \
|
||||
return M_TYPE_FROM_FIELD(type, list->name.next, \
|
||||
struct m_il1st_head_s, name); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _next_obj)(const list_t list, type const *obj) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (obj != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(&obj->name); \
|
||||
return obj->name.next == &list->name ? NULL : \
|
||||
M_TYPE_FROM_FIELD(type, obj->name.next, \
|
||||
struct m_il1st_head_s, name); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _previous_obj)(const list_t list, type const *obj) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (obj != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(&obj->name); \
|
||||
return obj->name.prev == &list->name ? NULL : \
|
||||
M_TYPE_FROM_FIELD(type, obj->name.prev, \
|
||||
struct m_il1st_head_s, name); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it)(it_t it, const list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (it != NULL); \
|
||||
it->head = list->name.next->prev; \
|
||||
it->current = list->name.next; \
|
||||
it->next = list->name.next->next; \
|
||||
it->previous = it->head; \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_set)(it_t it, const it_t cit) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL && cit != NULL); \
|
||||
it->head = cit->head; \
|
||||
it->current = cit->current; \
|
||||
it->next = cit->next; \
|
||||
it->previous = cit->previous; \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_last)(it_t it, list_t const list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (it != NULL); \
|
||||
it->head = list->name.next->prev; \
|
||||
it->current = list->name.prev; \
|
||||
it->next = it->head; \
|
||||
it->previous = list->name.prev->prev; \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_end)(it_t it, list_t const list) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL && list != NULL); \
|
||||
it->head = list->name.next->prev; \
|
||||
it->current = it->head; \
|
||||
it->next = list->name.next; \
|
||||
it->previous = list->name.prev; \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _end_p)(const it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
return it->current == it->head; \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _last_p)(const it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
return it->next == it->head || it->current == it->head; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _next)(it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL); \
|
||||
/* Cannot check node for it->current: it may have been deleted! */ \
|
||||
/* Note: Can't set it->previous to it->current. \
|
||||
it->current may have been unlinked from the list */ \
|
||||
it->current = it->next; \
|
||||
M_ASSERT (it->current != NULL); \
|
||||
it->next = it->current->next; \
|
||||
it->previous = it->current->prev; \
|
||||
M_ASSERT (it->next != NULL && it->previous != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _previous)(it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL); \
|
||||
/* Cannot check node for it->current: it may have been deleted! */ \
|
||||
/* Note: Can't set it->next to it->current. \
|
||||
it->current may have been unlinked from the list */ \
|
||||
it->current = it->previous; \
|
||||
M_ASSERT (it->current != NULL); \
|
||||
it->next = it->current->next; \
|
||||
it->previous = it->current->prev; \
|
||||
M_ASSERT (it->next != NULL && it->previous != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _it_equal_p)(const it_t it1, const it_t it2 ) \
|
||||
{ \
|
||||
M_ASSERT (it1 != NULL && it2 != NULL); \
|
||||
/* No need to check for next & previous */ \
|
||||
return it1->head == it2->head && it1->current == it2->current; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _ref)(const it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL && it->current != NULL); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
/* check if 'it' was not deleted */ \
|
||||
M_ASSERT (it->current->next == it->next); \
|
||||
M_ASSERT (it->current->prev == it->previous); \
|
||||
M_ASSERT (!M_F(name, _end_p)(it)); \
|
||||
return M_TYPE_FROM_FIELD(type, it->current, \
|
||||
struct m_il1st_head_s, name); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _cref)(const it_t it) \
|
||||
{ \
|
||||
type *ptr = M_F(name, _ref)(it); \
|
||||
return M_CONST_CAST(type, ptr); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _remove)(list_t list, it_t it) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
(void)list; /* list param is not used */ \
|
||||
type *obj = M_TYPE_FROM_FIELD(type, it->current, \
|
||||
struct m_il1st_head_s, name); \
|
||||
M_F(name, _unlink)(obj); \
|
||||
M_CALL_CLEAR(oplist, obj); \
|
||||
M_IF_METHOD(DEL, oplist)(M_CALL_DEL(oplist, obj), (void) 0); \
|
||||
M_F(name, _next)(it); \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD2(NEW, INIT_SET, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _insert)(list_t list, it_t it, type x) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
type *p = M_CALL_NEW(oplist, type); \
|
||||
if (M_UNLIKELY_NOMEM (p == NULL)) { \
|
||||
M_MEMORY_FULL (sizeof (type)); \
|
||||
return ; \
|
||||
} \
|
||||
M_CALL_INIT_SET(oplist, *p, x); \
|
||||
type *obj = M_F(name, _ref)(it); \
|
||||
M_F(name, _push_after)(obj, p); \
|
||||
it->current = p; \
|
||||
(void) list; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
, /* NEW & INIT_SET not defined */) \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _pop_back)(list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (!M_F(name, _empty_p)(list)); \
|
||||
type *obj = M_F(name, _back)(list); \
|
||||
list->name.prev = list->name.prev->prev; \
|
||||
list->name.prev->next = &list->name; \
|
||||
return obj; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _pop_front)(list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
M_ASSERT (!M_F(name, _empty_p)(list)); \
|
||||
type *obj = M_F(name, _front)(list); \
|
||||
list->name.next = list->name.next->next; \
|
||||
list->name.next->prev = &list->name; \
|
||||
return obj; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _splice)(list_t list1, list_t list2) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list1); \
|
||||
M_IL1ST_CONTRACT(name, list2); \
|
||||
struct m_il1st_head_s *midle1 = list1->name.prev; \
|
||||
struct m_il1st_head_s *midle2 = list2->name.next; \
|
||||
midle1->next = midle2; \
|
||||
midle2->prev = midle1; \
|
||||
list1->name.prev = list2->name.prev; \
|
||||
list2->name.prev->next = &list1->name; \
|
||||
list2->name.next = &list2->name; \
|
||||
list2->name.prev = &list2->name; \
|
||||
M_IL1ST_CONTRACT(name, list1); \
|
||||
M_IL1ST_CONTRACT(name, list2); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _splice_back)(list_t nv, list_t ov, it_t it) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, nv); \
|
||||
M_IL1ST_CONTRACT(name, ov); \
|
||||
M_IL1ST_NODE_CONTRACT(it->current); \
|
||||
M_ASSERT (it != NULL); \
|
||||
(void) ov; \
|
||||
type *obj = M_F(name, _ref)(it); \
|
||||
M_F(name, _unlink)(obj); \
|
||||
M_F(name, _push_back)(nv, obj); \
|
||||
M_F(name, _next)(it); \
|
||||
M_IL1ST_CONTRACT(name, nv); \
|
||||
M_IL1ST_CONTRACT(name, ov); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _splice_at)(list_t nlist, it_t npos, \
|
||||
list_t olist, it_t opos) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, nlist); \
|
||||
M_IL1ST_CONTRACT(name, olist); \
|
||||
M_ASSERT (npos != NULL && opos != NULL); \
|
||||
M_ASSERT (!M_F(name, _end_p)(opos)); \
|
||||
/* npos may be end */ \
|
||||
(void) olist, (void) nlist; \
|
||||
type *obj = M_F(name, _ref)(opos); \
|
||||
struct m_il1st_head_s *ref = npos->current; \
|
||||
/* Remove object */ \
|
||||
M_F(name, _unlink)(obj); \
|
||||
/* Push 'obj' after 'ref' */ \
|
||||
struct m_il1st_head_s *next = ref->next; \
|
||||
ref->next = &obj->name; \
|
||||
obj->name.next = next; \
|
||||
obj->name.prev = ref; \
|
||||
next->prev = &obj->name; \
|
||||
/* Move iterator in old list */ \
|
||||
M_F(name, _next)(opos); \
|
||||
/* Set npos iterator to new position of object */ \
|
||||
npos->previous = ref; \
|
||||
npos->current = &obj->name; \
|
||||
npos->next = next; \
|
||||
M_IL1ST_CONTRACT(name, nlist); \
|
||||
M_IL1ST_CONTRACT(name, olist); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _swap)(list_t d, list_t e) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, d); \
|
||||
M_IL1ST_CONTRACT(name, e); \
|
||||
struct m_il1st_head_s *d_item = d->name.next; \
|
||||
struct m_il1st_head_s *e_item = e->name.next; \
|
||||
/* it is more complicated than other swap functions since \
|
||||
we need to detect "cyclic" loop */ \
|
||||
d->name.next = e_item == &e->name ? &d->name : e_item; \
|
||||
e->name.next = d_item == &d->name ? &e->name : d_item; \
|
||||
d_item = d->name.prev; \
|
||||
e_item = e->name.prev; \
|
||||
d->name.prev = e_item == &e->name ? &d->name : e_item; \
|
||||
e->name.prev = d_item == &d->name ? &e->name : d_item; \
|
||||
d->name.next->prev = &d->name; \
|
||||
d->name.prev->next = &d->name; \
|
||||
e->name.next->prev = &e->name; \
|
||||
e->name.prev->next = &e->name; \
|
||||
M_IL1ST_CONTRACT(name, d); \
|
||||
M_IL1ST_CONTRACT(name, e); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _reverse)(list_t list) \
|
||||
{ \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
struct m_il1st_head_s *next, *it; \
|
||||
for(it = list->name.next ; it != &list->name; it = next) { \
|
||||
next = it->next; \
|
||||
it->next = it->prev; \
|
||||
it->prev = next; \
|
||||
} \
|
||||
next = it->next; \
|
||||
it->next = it->prev; \
|
||||
it->prev = next; \
|
||||
M_IL1ST_CONTRACT(name, list); \
|
||||
} \
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define ILIST_INTERFACE M_ILIST_INTERFACE
|
||||
#define ILIST_DEF M_ILIST_DEF
|
||||
#define ILIST_DEF_AS M_ILIST_DEF_AS
|
||||
#define ILIST_OPLIST M_ILIST_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* M*LIB - INTRUSIVE SHARED PTR Module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_I_SHARED_PTR_H
|
||||
#define MSTARLIB_I_SHARED_PTR_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-atomic.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define the oplist of a intrusive shared pointer.
|
||||
USAGE: ISHARED_OPLIST(name [, oplist_of_the_type]) */
|
||||
#define M_ISHARED_PTR_OPLIST(...) \
|
||||
M_ISHAR3D_PTR_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_BASIC_OPLIST), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/* Interface to add to a structure to allow intrusive support.
|
||||
name: name of the intrusive shared pointer.
|
||||
type: name of the type of the structure (aka. struct test_s) - not used currently.
|
||||
NOTE: There can be only one interface of this kind in a type! */
|
||||
#define M_ISHARED_PTR_INTERFACE(name, type) \
|
||||
atomic_int M_F(name, _cpt)
|
||||
|
||||
|
||||
/* Value of the interface field for static intialization (Uses C99 designated element). */
|
||||
#define M_ISHARED_PTR_STATIC_DESIGNATED_INIT(name, type) \
|
||||
.M_F(name, _cpt) = M_ATOMIC_VAR_INIT(0)
|
||||
|
||||
/* Value of the interface field for static intialization (Uses C89 designated element). */
|
||||
#define M_ISHARED_PTR_STATIC_INIT(name, type) \
|
||||
M_ATOMIC_VAR_INIT(0)
|
||||
|
||||
|
||||
/* Define the intrusive shared pointer type and its M_INLINE functions.
|
||||
USAGE: ISHARED_PTR_DEF(name, type, [, oplist]) */
|
||||
#define M_ISHARED_PTR_DEF(name, ...) \
|
||||
M_ISHARED_PTR_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define the intrusive shared pointer type and its M_INLINE functions
|
||||
as the name name_t
|
||||
USAGE: ISHARED_PTR_DEF_AS(name, name_t, type, [, oplist]) */
|
||||
#define M_ISHARED_PTR_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_ISHAR3D_PTR_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__ , name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/******************************** INTERNAL ***********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
// Deferred evaluation
|
||||
#define M_ISHAR3D_PTR_OPLIST_P1(arg) M_ISHAR3D_PTR_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given oplist */
|
||||
#define M_ISHAR3D_PTR_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_ISHAR3D_PTR_OPLIST_P3, M_ISHAR3D_PTR_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_ISHAR3D_PTR_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_ISHARED_PTR_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
// Define the oplist
|
||||
#define M_ISHAR3D_PTR_OPLIST_P3(name, oplist) ( \
|
||||
INIT(M_INIT_DEFAULT), \
|
||||
INIT_SET(API_4(M_F(name, _init_set))), \
|
||||
SET(M_F(name, _set) M_IPTR), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
RESET(M_F(name, _reset) M_IPTR), \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name, _ct)), \
|
||||
OPLIST(oplist), \
|
||||
SUBTYPE(M_F(name, _subtype_ct)) \
|
||||
)
|
||||
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
// Deferred evaluatioin
|
||||
#define M_ISHAR3D_PTR_DEF_P1(arg) M_ID( M_ISHAR3D_PTR_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_ISHAR3D_PTR_DEF_P2(name, type, oplist, shared_t) \
|
||||
M_IF_OPLIST(oplist)(M_ISHAR3D_PTR_DEF_P3, M_ISHAR3D_PTR_DEF_FAILURE)(name, type, oplist, shared_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_ISHAR3D_PTR_DEF_FAILURE(name, type, oplist, shared_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(ISHARED_PTR_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
#define M_ISHAR3D_PTR_DEF_P3(name, type, oplist, shared_t) \
|
||||
M_ISHAR3D_PTR_DEF_TYPE(name, type, oplist, shared_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_ISHAR3D_PTR_DEF_CORE(name, type, oplist, shared_t) \
|
||||
|
||||
/* Define the types */
|
||||
#define M_ISHAR3D_PTR_DEF_TYPE(name, type, oplist, shared_t) \
|
||||
\
|
||||
/* The shared pointer is only a pointer to the type */ \
|
||||
typedef type *shared_t; \
|
||||
\
|
||||
/* Define internal types for oplist */ \
|
||||
typedef shared_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_ISHAR3D_PTR_DEF_CORE(name, type, oplist, shared_t) \
|
||||
\
|
||||
M_INLINE shared_t \
|
||||
M_F(name, _init)(type *ptr) \
|
||||
{ \
|
||||
/* Initialize the type referenced by the pointer */ \
|
||||
if (M_LIKELY (ptr != NULL)) { \
|
||||
atomic_init(&ptr->M_F(name, _cpt), 2); \
|
||||
} \
|
||||
return ptr; \
|
||||
} \
|
||||
\
|
||||
M_INLINE shared_t \
|
||||
M_F(name, _init_set)(shared_t shared) \
|
||||
{ \
|
||||
if (M_LIKELY (shared != NULL)) { \
|
||||
int n = atomic_fetch_add(&(shared->M_F(name, _cpt)), 2); \
|
||||
(void) n; \
|
||||
} \
|
||||
return shared; \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD(INIT, oplist)( \
|
||||
M_IF_DISABLED_METHOD(NEW, oplist) \
|
||||
( \
|
||||
/* This function is only for static object */ \
|
||||
M_INLINE shared_t \
|
||||
M_F(name, _init_once)(type *shared) \
|
||||
{ \
|
||||
if (M_LIKELY (shared != NULL)) { \
|
||||
/* Pretty much like atomic_add, except the first one increment by 1, others by 2 */ \
|
||||
int o = atomic_load(&(shared->M_F(name, _cpt))); \
|
||||
int n; \
|
||||
do { \
|
||||
n = o + 1 + (o != 0); \
|
||||
} while (!atomic_compare_exchange_strong(&(shared->M_F(name, _cpt)), &o, n)); \
|
||||
if (o == 0) { \
|
||||
/* Partial initialization: _cpt is odd */ \
|
||||
/* Call the INIT function once */ \
|
||||
M_CALL_INIT(oplist, *shared); \
|
||||
/* Finish initialization: _cpt is even */ \
|
||||
atomic_fetch_add(&(shared->M_F(name, _cpt)), 1); \
|
||||
} else if ( (o&1) != 0) { \
|
||||
/* Not fully initialized yet: wait for initialization */ \
|
||||
m_core_backoff_ct bkoff; \
|
||||
m_core_backoff_init(bkoff); \
|
||||
/* Wait for _cpt to be _even */ \
|
||||
while ((atomic_load(&(shared->M_F(name, _cpt)))&1) != 0 ) { \
|
||||
m_core_backoff_wait(bkoff); \
|
||||
} \
|
||||
} \
|
||||
M_ASSERT( (atomic_load(&(shared->M_F(name, _cpt)))&1) == 0); \
|
||||
} \
|
||||
return shared; \
|
||||
} \
|
||||
, \
|
||||
/* This function is only for dynamic object */ \
|
||||
M_INLINE shared_t \
|
||||
M_F(name, _init_new)(void) \
|
||||
{ \
|
||||
type *ptr = M_CALL_NEW(oplist, type); \
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(type)); \
|
||||
return NULL; \
|
||||
} \
|
||||
M_CALL_INIT(oplist, *ptr); \
|
||||
atomic_init (&ptr->M_F(name, _cpt), 2); \
|
||||
return ptr; \
|
||||
} \
|
||||
/* End of NEW */) \
|
||||
, /* End of INIT */) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(shared_t shared) \
|
||||
{ \
|
||||
if (shared != NULL) { \
|
||||
if (atomic_fetch_sub(&(shared->M_F(name, _cpt)), 2) == 2) { \
|
||||
M_CALL_CLEAR(oplist, *shared); \
|
||||
M_IF_DISABLED_METHOD(DEL, oplist)(, M_CALL_DEL(oplist, shared);) \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear_ptr)(shared_t *shared) \
|
||||
{ \
|
||||
M_ASSERT(shared != NULL); \
|
||||
M_F(name, _clear)(*shared); \
|
||||
*shared = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _reset)(shared_t *shared) \
|
||||
{ \
|
||||
M_F(name, _clear)(*shared); \
|
||||
*shared = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _set)(shared_t *ptr, shared_t shared) \
|
||||
{ \
|
||||
M_ASSERT (ptr != NULL); \
|
||||
if (M_LIKELY (*ptr != shared)) { \
|
||||
M_F(name, _clear)(*ptr); \
|
||||
*ptr = M_F(name, _init_set)(shared); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/******************************** INTERNAL ***********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define ISHARED_PTR_OPLIST M_ISHARED_PTR_OPLIST
|
||||
#define ISHARED_PTR_INTERFACE M_ISHARED_PTR_INTERFACE
|
||||
#define ISHARED_PTR_STATIC_DESIGNATED_INIT M_ISHARED_PTR_STATIC_DESIGNATED_INIT
|
||||
#define ISHARED_PTR_STATIC_INIT M_ISHARED_PTR_STATIC_INIT
|
||||
#define ISHARED_PTR_DEF M_ISHARED_PTR_DEF
|
||||
#define ISHARED_PTR_DEF_AS M_ISHARED_PTR_DEF_AS
|
||||
#endif
|
||||
|
||||
#endif
|
||||
-1527
File diff suppressed because it is too large
Load Diff
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* M*LIB - MEMPOOL module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_MEMPOOL_H
|
||||
#define MSTARLIB_MEMPOOL_H
|
||||
|
||||
#include "m-core.h"
|
||||
|
||||
/* Fast, fixed size, thread unsafe allocator based on memory regions.
|
||||
No oplist is needed.
|
||||
USAGE:
|
||||
MEMPOOL_DEF(name, type)
|
||||
Example:
|
||||
MEMPOOL_DEF(mempool_uint, unsigned int)
|
||||
...
|
||||
mempool_uint_t m;
|
||||
mempool_uint_init(m);
|
||||
unsigned int *ptr = mempool_uint_alloc(m);
|
||||
*ptr = 17;
|
||||
mempool_uint_free(m, ptr);
|
||||
mempool_uint_clear(m); // Give back memory to system
|
||||
*/
|
||||
#define M_MEMPOOL_DEF(name, type) \
|
||||
M_MEMPOOL_DEF_AS(name, M_F(name,_t), type)
|
||||
|
||||
|
||||
/* Fast, fixed Size, thread unsafe allocator based on memory region.
|
||||
USAGE:
|
||||
MEMPOOL_DEF_AS(name, name_t, type)
|
||||
*/
|
||||
#define M_MEMPOOL_DEF_AS(name, name_t, type) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_M3MPOOL_DEF_P2(name, type, name_t ) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* User shall be able to cutomize the size of the region segment and/or
|
||||
the minimun number of elements.
|
||||
The default is the number of elements that fits in 16KB, or 256
|
||||
is the size of the type is too big.
|
||||
*/
|
||||
#ifndef M_USE_MEMPOOL_MAX_PER_SEGMENT
|
||||
#define M_USE_MEMPOOL_MAX_PER_SEGMENT(type) \
|
||||
M_MAX((16*1024-sizeof(unsigned int) - 2*sizeof(void*)) / sizeof (type), 256U)
|
||||
#endif
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/*
|
||||
Technically, it uses a list of memory regions, where multiple
|
||||
allocations are performed in each region. However, it
|
||||
can not use m-list since it may be expanded from LIST_DEF
|
||||
(recursive dependency problem). */
|
||||
#define M_M3MPOOL_DEF_P2(name, type, name_t) \
|
||||
M_M3MPOOL_DEF_TYPE(name, type, name_t) \
|
||||
M_M3MPOOL_DEF_CORE(name, type, name_t)
|
||||
|
||||
/* Define the types of the mempool */
|
||||
#define M_M3MPOOL_DEF_TYPE(name, type, name_t) \
|
||||
\
|
||||
/* Define the type of element in a segment of the mempool. \
|
||||
Either it is the basic type or a pointer to another one. */ \
|
||||
typedef union M_F(name,_union_s) { \
|
||||
type t; \
|
||||
union M_F(name,_union_s) *next; \
|
||||
} M_F(name,_union_ct); \
|
||||
\
|
||||
/* Define a segment of a mempool. \
|
||||
It is an array of basic type, each segment is in a linked list */ \
|
||||
typedef struct M_F(name,_segment_s) { \
|
||||
unsigned int count; \
|
||||
struct M_F(name,_segment_s) *next; \
|
||||
M_F(name,_union_ct) tab[M_USE_MEMPOOL_MAX_PER_SEGMENT(type)]; \
|
||||
} M_F(name,_segment_ct); \
|
||||
\
|
||||
/* Define a mempool. \
|
||||
It is a pointer to the first free object within the segments \
|
||||
and the segments themselves */ \
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name,_union_ct) *free_list; \
|
||||
M_F(name,_segment_ct) *current_segment; \
|
||||
} name_t[1]; \
|
||||
|
||||
|
||||
/* Define the core functions of the mempool */
|
||||
#define M_M3MPOOL_DEF_CORE(name, type, name_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name,_init)(name_t mem) \
|
||||
{ \
|
||||
mem->free_list = NULL; \
|
||||
mem->current_segment = M_MEMORY_ALLOC(M_F(name,_segment_ct)); \
|
||||
if (M_UNLIKELY_NOMEM(mem->current_segment == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof (M_F(name,_segment_ct))); \
|
||||
return; \
|
||||
} \
|
||||
mem->current_segment->next = NULL; \
|
||||
mem->current_segment->count = 0; \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name,_clear)(name_t mem) \
|
||||
{ \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
M_F(name,_segment_ct) *segment = mem->current_segment; \
|
||||
while (segment != NULL) { \
|
||||
M_F(name,_segment_ct) *next = segment->next; \
|
||||
M_MEMORY_DEL (segment); \
|
||||
segment = next; \
|
||||
} \
|
||||
/* Clean pointers to be safer */ \
|
||||
mem->free_list = NULL; \
|
||||
mem->current_segment = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name,_alloc)(name_t mem) \
|
||||
{ \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
/* Test if one object is in the free list */ \
|
||||
M_F(name,_union_ct) *ret = mem->free_list; \
|
||||
if (ret != NULL) { \
|
||||
/* Yes, so return it, and pop it from the free list */ \
|
||||
mem->free_list = ret->next; \
|
||||
return &ret->t; \
|
||||
} \
|
||||
/* No cheap free object exist. Test within a segment */ \
|
||||
M_F(name,_segment_ct) *segment = mem->current_segment; \
|
||||
M_ASSERT(segment != NULL); \
|
||||
unsigned int count = segment->count; \
|
||||
/* If segment is full, allocate a new one from the system */ \
|
||||
if (M_UNLIKELY (count >= M_USE_MEMPOOL_MAX_PER_SEGMENT(type))) { \
|
||||
M_F(name,_segment_ct) *new_segment = M_MEMORY_ALLOC (M_F(name,_segment_ct)); \
|
||||
if (M_UNLIKELY_NOMEM (new_segment == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof (M_F(name,_segment_ct))); \
|
||||
return NULL; \
|
||||
} \
|
||||
new_segment->next = segment; \
|
||||
new_segment->count = 0; \
|
||||
mem->current_segment = new_segment; \
|
||||
segment = new_segment; \
|
||||
count = 0; \
|
||||
} \
|
||||
/* Return the object as the last element of the current segment */ \
|
||||
ret = &segment->tab[count]; \
|
||||
segment->count = count + 1; \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
return &ret->t; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name,_free)(name_t mem, type *ptr) \
|
||||
{ \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
/* NOTE: Unsafe cast: suppose that the given pointer \
|
||||
was allocated by the previous alloc function. */ \
|
||||
M_F(name,_union_ct) *ret = (M_F(name,_union_ct) *)(uintptr_t)ptr; \
|
||||
/* Add the object back in the free list */ \
|
||||
ret->next = mem->free_list; \
|
||||
mem->free_list = ret; \
|
||||
/* NOTE: the objects are NOT given back to the system until the mempool \
|
||||
is fully cleared */ \
|
||||
M_M3MPOOL_CONTRACT(mem, type); \
|
||||
} \
|
||||
|
||||
/* MEMPOOL contract. We only control the current segment. */
|
||||
#define M_M3MPOOL_CONTRACT(mempool, type) do { \
|
||||
M_ASSERT((mempool) != NULL); \
|
||||
M_ASSERT((mempool)->current_segment != NULL); \
|
||||
M_ASSERT((mempool)->current_segment->count <= M_USE_MEMPOOL_MAX_PER_SEGMENT(type)); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define MEMPOOL_DEF M_MEMPOOL_DEF
|
||||
#define MEMPOOL_DEF_AS M_MEMPOOL_DEF_AS
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Thin Mutex & Thread wrapper (compatibility layer)
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
#warning "m-mutex.h is an obsolete header. Use m-thread.h instead."
|
||||
#endif
|
||||
#include "m-thread.h"
|
||||
@@ -1,520 +0,0 @@
|
||||
/*
|
||||
* M*LIB - dynamic priority queue module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_PRIOQUEUE_H
|
||||
#define MSTARLIB_PRIOQUEUE_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-array.h" /* Priority queue are built upon array */
|
||||
|
||||
/* Priority queue based on binary heap implementation */
|
||||
|
||||
/* Define a prioqueue of a given type and its associated functions.
|
||||
USAGE: PRIOQUEUE_DEF(name, type [, oplist_of_the_type]) */
|
||||
#define M_PRIOQUEUE_DEF(name, ...) \
|
||||
M_PRIOQUEUE_DEF_AS(name, M_F(name,_t), M_F(name,_it_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a prioqueue of a given type and its associated functions.
|
||||
as the name name_t with an iterator named it_t
|
||||
USAGE: PRIOQUEUE_DEF_AS(name, name_t, it_t, type [, oplist_of_the_type]) */
|
||||
#define M_PRIOQUEUE_DEF_AS(name, name_t, it_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_PR1OQUEUE_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t, it_t ), \
|
||||
(name, __VA_ARGS__, name_t, it_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a prioqueue of type.
|
||||
USAGE: PRIOQUEUE_OPLIST(name[, oplist of the type]) */
|
||||
#define M_PRIOQUEUE_OPLIST(...) \
|
||||
M_PR1OQUEUE_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_BASIC_OPLIST), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Deferred evaluation for the definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_PR1OQUEUE_OPLIST_P1(arg) M_PR1OQUEUE_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given oplist */
|
||||
#define M_PR1OQUEUE_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_PR1OQUEUE_OPLIST_P3, M_PR1OQUEUE_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_PR1OQUEUE_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_PRIOQUEUE_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
/* Define oplist of a priority queue */
|
||||
#define M_PR1OQUEUE_OPLIST_P3(name, oplist) \
|
||||
(INIT(M_F(name, _init)) \
|
||||
,INIT_SET(M_F(name, _init_set)) \
|
||||
,INIT_WITH(API_1(M_INIT_VAI)) \
|
||||
,SET(M_F(name, _set)) \
|
||||
,CLEAR(M_F(name, _clear)) \
|
||||
,INIT_MOVE(M_F(name, _init_move)) \
|
||||
,MOVE(M_F(name, _move)) \
|
||||
,SWAP(M_F(name, _swap)) \
|
||||
,NAME(name) \
|
||||
,TYPE(M_F(name,_ct)) \
|
||||
,SUBTYPE(M_F(name, _subtype_ct)) \
|
||||
,RESET(M_F(name,_reset)) \
|
||||
,PUSH(M_F(name,_push)) \
|
||||
,POP(M_F(name,_pop)) \
|
||||
,OPLIST(oplist) \
|
||||
,EMPTY_P(M_F(name, _empty_p)) \
|
||||
,GET_SIZE(M_F(name, _size)) \
|
||||
,IT_TYPE(M_F(name, _it_ct)) \
|
||||
,IT_FIRST(M_F(name,_it)) \
|
||||
,IT_END(M_F(name,_it_end)) \
|
||||
,IT_SET(M_F(name,_it_set)) \
|
||||
,IT_END_P(M_F(name,_end_p)) \
|
||||
,IT_EQUAL_P(M_F(name,_it_equal_p)) \
|
||||
,IT_LAST_P(M_F(name,_last_p)) \
|
||||
,IT_NEXT(M_F(name,_next)) \
|
||||
,IT_CREF(M_F(name,_cref)) \
|
||||
,M_IF_METHOD(GET_STR, oplist)(GET_STR(M_F(name, _get_str)),) \
|
||||
,M_IF_METHOD(PARSE_STR, oplist)(PARSE_STR(M_F(name, _parse_str)),) \
|
||||
,M_IF_METHOD(OUT_STR, oplist)(OUT_STR(M_F(name, _out_str)),) \
|
||||
,M_IF_METHOD(IN_STR, oplist)(IN_STR(M_F(name, _in_str)),) \
|
||||
,M_IF_METHOD(OUT_SERIAL, oplist)(OUT_SERIAL(M_F(name, _out_serial)),) \
|
||||
,M_IF_METHOD(IN_SERIAL, oplist)(IN_SERIAL(M_F(name, _in_serial)),) \
|
||||
)
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Deferred evaluation for the definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_PR1OQUEUE_DEF_P1(arg) M_ID( M_PR1OQUEUE_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_PR1OQUEUE_DEF_P2(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_IF_OPLIST(oplist)(M_PR1OQUEUE_DEF_P3, M_PR1OQUEUE_DEF_FAILURE)(name, type, oplist, prioqueue_t, it_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_PR1OQUEUE_DEF_FAILURE(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(PRIOQUEUE_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Define the priority queue:
|
||||
- name: prefix to use,
|
||||
- type: type of the contained objects,
|
||||
- oplist: oplist of the contained objects,
|
||||
- prioqueue_t: type of the container,
|
||||
- it_t: iterator of the container
|
||||
*/
|
||||
#define M_PR1OQUEUE_DEF_P3(name, type, oplist, prioqueue_t, it_t) \
|
||||
/* Definition of the internal array used to construct the priority queue */ \
|
||||
ARRAY_DEF(M_F(name, _array), type, oplist) \
|
||||
M_PR1OQUEUE_DEF_TYPE(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_PR1OQUEUE_DEF_CORE(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_PR1OQUEUE_DEF_IT(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_PR1OQUEUE_DEF_IO(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_EMPLACE_QUEUE_DEF(name, prioqueue_t, M_F(name, _emplace), oplist, M_EMPLACE_QUEUE_GENE)
|
||||
|
||||
/* Define the types */
|
||||
#define M_PR1OQUEUE_DEF_TYPE(name, type, oplist, prioqueue_t, it_t) \
|
||||
\
|
||||
/* Define the priority queue over the defined array */ \
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _array_t) array; \
|
||||
} prioqueue_t[1]; \
|
||||
/* Define the pointer references to the priority queue */ \
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
/* The iterator is the same one as the one of the internal array */ \
|
||||
typedef M_F(name, _array_it_t) it_t; \
|
||||
\
|
||||
/* Definition of the internal types used by the oplist */ \
|
||||
typedef prioqueue_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
typedef it_t M_F(name, _it_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_PR1OQUEUE_DEF_CORE(name, type, oplist, prioqueue_t, it_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(prioqueue_t p) \
|
||||
{ \
|
||||
M_F(name, _array_init)(p->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_set)(prioqueue_t p, prioqueue_t const o) \
|
||||
{ \
|
||||
M_F(name, _array_init_set)(p->array, o->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _set)(prioqueue_t p, prioqueue_t const o) \
|
||||
{ \
|
||||
M_F(name, _array_set)(p->array, o->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(prioqueue_t p) \
|
||||
{ \
|
||||
M_F(name, _array_clear)(p->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(prioqueue_t p, prioqueue_t o) \
|
||||
{ \
|
||||
M_F(name, _array_init_move)(p->array, o->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(prioqueue_t p, prioqueue_t o) \
|
||||
{ \
|
||||
M_F(name, _array_move)(p->array, o->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _swap)(prioqueue_t p, prioqueue_t o) \
|
||||
{ \
|
||||
M_F(name, _array_swap)(p->array, o->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _reset)(prioqueue_t p) \
|
||||
{ \
|
||||
M_F(name, _array_reset)(p->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _i_parent)(size_t i) \
|
||||
{ \
|
||||
M_ASSERT (i > 0); \
|
||||
return (i - 1) / 2; \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _i_lchild)(size_t i) \
|
||||
{ \
|
||||
M_ASSERT(i <= ((SIZE_MAX)-2)/2); \
|
||||
return 2*i + 1; \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _i_rchild)(size_t i) \
|
||||
{ \
|
||||
M_ASSERT(i <= ((SIZE_MAX)-2)/2); \
|
||||
return 2*i + 2; \
|
||||
} \
|
||||
\
|
||||
M_INLINE int \
|
||||
M_F(name, _i_cmp)(const prioqueue_t p, size_t i, size_t j) \
|
||||
{ \
|
||||
return M_CALL_CMP(oplist, *M_F(name, _array_cget)(p->array, i), \
|
||||
*M_F(name, _array_cget)(p->array, j)); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _empty_p)(prioqueue_t const p) \
|
||||
{ \
|
||||
return M_F(name, _array_empty_p)(p->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _size)(prioqueue_t const p) \
|
||||
{ \
|
||||
return M_F(name, _array_size)(p->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _push)(prioqueue_t p, type const x) \
|
||||
{ \
|
||||
/* Push back the new element at the end of the array */ \
|
||||
M_F(name, _array_push_back)(p->array, x); \
|
||||
\
|
||||
/* Reorder the array by swapping with its parent \
|
||||
* until it reaches the right position */ \
|
||||
size_t i = M_F(name, _array_size)(p->array)-1; \
|
||||
while (i > 0) { \
|
||||
size_t j = M_F(name, _i_parent)(i); \
|
||||
if (M_F(name, _i_cmp)(p, j, i) <= 0) \
|
||||
break; \
|
||||
M_F(name, _array_swap_at) (p->array, i, j); \
|
||||
i = j; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _front)(prioqueue_t const p) \
|
||||
{ \
|
||||
return M_F(name, _array_cget)(p->array, 0); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _pop)(type *x, prioqueue_t p) \
|
||||
{ \
|
||||
/* Swap the front element with the last element */ \
|
||||
size_t size = M_F(name, _array_size)(p->array)-1; \
|
||||
M_F(name, _array_swap_at) (p->array, 0, size); \
|
||||
/* Swap the new last element */ \
|
||||
M_F(name, _array_pop_back)(x, p->array); \
|
||||
\
|
||||
/* Reorder the heap */ \
|
||||
size_t i = 0; \
|
||||
while (true) { \
|
||||
size_t child = M_F(name, _i_lchild)(i); \
|
||||
if (child >= size) \
|
||||
break; \
|
||||
size_t otherChild = M_F(name, _i_rchild)(i); \
|
||||
if (otherChild < size \
|
||||
&& M_F(name, _i_cmp)(p, otherChild, child) < 0 ) { \
|
||||
child = otherChild; \
|
||||
} \
|
||||
if (M_F(name, _i_cmp)(p, i, child) <= 0) \
|
||||
break; \
|
||||
M_F(name, _array_swap_at) (p->array, i, child); \
|
||||
i = child; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD(EQUAL, oplist) \
|
||||
( \
|
||||
/* EQUAL & CMP may be uncorrelated */ \
|
||||
M_INLINE bool \
|
||||
M_F(name, _equal_p)(prioqueue_t const p, prioqueue_t const q) \
|
||||
{ \
|
||||
return M_F(name, _array_equal_p)(p->array, q->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE size_t \
|
||||
M_F(name, _i_find)(prioqueue_t p, type const x) \
|
||||
{ \
|
||||
size_t size = M_F(name, _array_size)(p->array); \
|
||||
size_t i = 0; \
|
||||
for(i = 0; i < size; i++) { \
|
||||
/* We cannot use CMP and the partial order to go faster \
|
||||
EQUAL & CMP may be uncorrelated */ \
|
||||
if (M_CALL_EQUAL(oplist, *M_F(name, _array_cget)(p->array, i), x)) \
|
||||
break; \
|
||||
} \
|
||||
return i; \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _erase)(prioqueue_t p, type const x) \
|
||||
{ \
|
||||
/* First pass: search for an item EQUAL to x */ \
|
||||
size_t size = M_F(name, _array_size)(p->array); \
|
||||
size_t i = M_F(name, _i_find)(p, x); \
|
||||
/* If x is not found, then stop */ \
|
||||
if (i >= size) \
|
||||
return false; \
|
||||
/* Swap the found item and the last element */ \
|
||||
size--; \
|
||||
M_F(name, _array_swap_at) (p->array, i, size); \
|
||||
M_F(name, _array_pop_back)(NULL, p->array); \
|
||||
/* Move back the last swapped element to its right position in the heap */ \
|
||||
while (true) { \
|
||||
size_t child = M_F(name, _i_lchild)(i); \
|
||||
if (child >= size) break; \
|
||||
size_t otherChild = M_F(name, _i_rchild)(i); \
|
||||
if (otherChild < size \
|
||||
&& M_F(name, _i_cmp)(p, otherChild, child) < 0 ) { \
|
||||
child = otherChild; \
|
||||
} \
|
||||
if (M_F(name, _i_cmp)(p, i, child) <= 0) break; \
|
||||
M_F(name, _array_swap_at) (p->array, i, child); \
|
||||
i = child; \
|
||||
} \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _update)(prioqueue_t p, type const xold, type const xnew) \
|
||||
{ \
|
||||
/* NOTE: xold can be the same pointer than xnew */ \
|
||||
/* First pass: search for an item EQUAL to x */ \
|
||||
size_t size = M_F(name, _array_size)(p->array); \
|
||||
size_t i = M_F(name, _i_find)(p, xold); \
|
||||
/* We shall have found the item */ \
|
||||
M_ASSERT (i < size); \
|
||||
/* Test if the position of the old data is further or nearer than the new */ \
|
||||
int cmp = M_CALL_CMP(oplist, *M_F(name, _array_cget)(p->array, i), xnew); \
|
||||
/* Set the found item to the new element */ \
|
||||
M_F(name, _array_set_at) (p->array, i, xnew); \
|
||||
if (cmp < 0) { \
|
||||
/* Move back the updated element to its new position, further in the heap */ \
|
||||
while (true) { \
|
||||
size_t child = M_F(name, _i_lchild)(i); \
|
||||
if (child >= size) break; \
|
||||
size_t otherChild = M_F(name, _i_rchild)(i); \
|
||||
if (otherChild < size \
|
||||
&& M_F(name, _i_cmp)(p, otherChild, child) < 0 ) { \
|
||||
child = otherChild; \
|
||||
} \
|
||||
if (M_F(name, _i_cmp)(p, i, child) <= 0) break; \
|
||||
M_F(name, _array_swap_at) (p->array, i, child); \
|
||||
i = child; \
|
||||
} \
|
||||
} else { \
|
||||
/* Move back the updated element to its new position, nearest in the heap */ \
|
||||
while (i > 0) { \
|
||||
size_t parent = M_F(name, _i_parent)(i); \
|
||||
if (M_F(name, _i_cmp)(p, parent, i) <= 0) break; \
|
||||
M_F(name, _array_swap_at) (p->array, i, parent); \
|
||||
i = parent; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
, /* No EQUAL */ ) \
|
||||
|
||||
/* Define the IT based functions */
|
||||
#define M_PR1OQUEUE_DEF_IT(name, type, oplist, prioqueue_t, it_t) \
|
||||
\
|
||||
/* Define iterators over the array iterator */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _it)(it_t it, prioqueue_t const v) \
|
||||
{ \
|
||||
M_F(name, _array_it)(it, v->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_last)(it_t it, prioqueue_t const v) \
|
||||
{ \
|
||||
M_F(name, _array_it_last)(it, v->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_end)(it_t it, prioqueue_t const v) \
|
||||
{ \
|
||||
M_F(name, _array_it_end)(it, v->array); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_set)(it_t it, const it_t org) \
|
||||
{ \
|
||||
M_F(name, _array_it_set)(it, org); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _end_p)(const it_t it) \
|
||||
{ \
|
||||
return M_F(name, _array_end_p)(it); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _last_p)(const it_t it) \
|
||||
{ \
|
||||
return M_F(name, _array_last_p)(it); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _it_equal_p)(const it_t it1, \
|
||||
const it_t it2) \
|
||||
{ \
|
||||
return M_F(name, _array_it_equal_p)(it1, it2); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _next)(it_t it) \
|
||||
{ \
|
||||
M_F(name, _array_next)(it); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _previous)(it_t it) \
|
||||
{ \
|
||||
M_F(name, _array_previous)(it); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _cref)(const it_t it) \
|
||||
{ \
|
||||
return M_F(name, _array_cref)(it); \
|
||||
} \
|
||||
|
||||
/* Define the IO functions */
|
||||
#define M_PR1OQUEUE_DEF_IO(name, type, oplist, prioqueue_t, it_t) \
|
||||
M_IF_METHOD(OUT_STR, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _out_str)(FILE *file, const prioqueue_t p) \
|
||||
{ \
|
||||
M_F(name, _array_out_str)(file, p->array); \
|
||||
} \
|
||||
,/* No OUT_STR */) \
|
||||
\
|
||||
M_IF_METHOD(IN_STR, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _in_str)(prioqueue_t p, FILE *file) \
|
||||
{ \
|
||||
return M_F(name, _array_in_str)(p->array, file); \
|
||||
} \
|
||||
,/* No IN_STR */) \
|
||||
\
|
||||
M_IF_METHOD(GET_STR, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _get_str)(string_t str, const prioqueue_t p, bool append) \
|
||||
{ \
|
||||
M_F(name, _array_get_str)(str, p->array, append); \
|
||||
} \
|
||||
,/* No GET_STR */) \
|
||||
\
|
||||
M_IF_METHOD(PARSE_STR, oplist)( \
|
||||
M_INLINE bool \
|
||||
M_F(name, _parse_str)(prioqueue_t p, const char str[], const char **endp) \
|
||||
{ \
|
||||
return M_F(name, _array_parse_str)(p->array, str, endp); \
|
||||
} \
|
||||
,/* No PARSE_STR */) \
|
||||
\
|
||||
M_IF_METHOD(OUT_SERIAL, oplist)( \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _out_serial)(m_serial_write_t f, const prioqueue_t p) \
|
||||
{ \
|
||||
return M_F(name, _array_out_serial)(f, p->array); \
|
||||
} \
|
||||
,/* No OUT_SERIAL */) \
|
||||
\
|
||||
M_IF_METHOD2(IN_SERIAL, INIT, oplist)( \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _in_serial)(prioqueue_t p, m_serial_read_t f) \
|
||||
{ \
|
||||
return M_F(name, _array_in_serial)(p->array, f); \
|
||||
} \
|
||||
,/* No in_SERIAL */) \
|
||||
|
||||
|
||||
// TODO: set all & remove all function
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define PRIOQUEUE_DEF M_PRIOQUEUE_DEF
|
||||
#define PRIOQUEUE_DEF_AS M_PRIOQUEUE_DEF_AS
|
||||
#define PRIOQUEUE_OPLIST M_PRIOQUEUE_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,552 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Serial BIN
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_SERIAL_BIN_H
|
||||
#define MSTARLIB_SERIAL_BIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-string.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
/************************** FILE / WRITE / BIN *******************************/
|
||||
/********************************************************************************/
|
||||
|
||||
/* Internal service:
|
||||
* Write size_t in the stream in a compact form to reduce consumption
|
||||
* (and I/O bandwidth)
|
||||
*/
|
||||
M_INLINE bool
|
||||
m_ser1al_bin_write_size(FILE *f, const size_t size)
|
||||
{
|
||||
bool b;
|
||||
if (M_LIKELY(size < 253))
|
||||
{
|
||||
b = EOF != fputc((unsigned char) size, f);
|
||||
} else if (size < 1ULL << 16) {
|
||||
b = EOF != fputc(253, f); // Save 16 bits encoding
|
||||
b &= EOF != fputc((unsigned char) (size >> 8), f);
|
||||
b &= EOF != fputc((unsigned char) size, f);
|
||||
}
|
||||
// For 32 bits systems, don't encode a 64 bits size_t
|
||||
#if SIZE_MAX < 1ULL<< 32
|
||||
else {
|
||||
b = EOF != fputc(254, f); // Save 32 bits encoding
|
||||
b &= EOF != fputc((unsigned char) (size >> 24), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 16), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 8), f);
|
||||
b &= EOF != fputc((unsigned char) size, f);
|
||||
}
|
||||
#else
|
||||
else if (size < 1ULL<< 32) {
|
||||
b = EOF != fputc(254, f); // Save 32 bits encoding
|
||||
b &= EOF != fputc((unsigned char) (size >> 24), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 16), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 8), f);
|
||||
b &= EOF != fputc((unsigned char) size, f);
|
||||
} else {
|
||||
b = EOF != fputc(255, f); // Save 64 bits encoding
|
||||
b &= EOF != fputc((unsigned char) (size >> 56), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 48), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 40), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 32), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 24), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 16), f);
|
||||
b &= EOF != fputc((unsigned char) (size >> 8), f);
|
||||
b &= EOF != fputc((unsigned char) size, f);
|
||||
}
|
||||
#endif
|
||||
return b;
|
||||
}
|
||||
|
||||
/* Internal service:
|
||||
* Read size_t in the stream from a compact form to reduce consumption
|
||||
* (and I/O bandwidth)
|
||||
*/
|
||||
M_INLINE bool
|
||||
m_ser1al_bin_read_size(FILE *f, size_t *size)
|
||||
{
|
||||
int c;
|
||||
c = fgetc(f);
|
||||
if (M_UNLIKELY(c == EOF)) return false;
|
||||
if (M_LIKELY(c < 253)) {
|
||||
*size = (size_t) c;
|
||||
return true;
|
||||
}
|
||||
size_t s = 0;
|
||||
int l = (c == 255) ? 8 : (c == 254) ? 4 : 2;
|
||||
for(int i = 0; i < l; i++) {
|
||||
c = fgetc(f);
|
||||
if (M_UNLIKELY(c == EOF)) return false;
|
||||
s = (s << 8) | (size_t) c;
|
||||
}
|
||||
*size = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Write the boolean 'data' into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_boolean(m_serial_write_t serial, const bool data)
|
||||
{
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
size_t n = fwrite (M_ASSIGN_CAST(const void*, &data), sizeof (bool), 1, f);
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Write the integer 'data' of 'size_of_type' bytes into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_integer(m_serial_write_t serial,const long long data, const size_t size_of_type)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
if (size_of_type == 1) {
|
||||
int8_t i8 = (int8_t) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &i8), sizeof i8, 1, f);
|
||||
} else if (size_of_type == 2) {
|
||||
int16_t i16 = (int16_t) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &i16), sizeof i16, 1, f);
|
||||
} else if (size_of_type == 4) {
|
||||
int32_t i32 = (int32_t) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &i32), sizeof i32, 1, f);
|
||||
} else {
|
||||
M_ASSERT(size_of_type == 8);
|
||||
int64_t i64 = (int64_t) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &i64), sizeof i64, 1, f);
|
||||
}
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Write the float 'data' of 'size_of_type' bytes into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_float(m_serial_write_t serial, const long double data, const size_t size_of_type)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
if (size_of_type == sizeof (float) ) {
|
||||
float f1 = (float) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &f1), sizeof f1, 1, f);
|
||||
} else if (size_of_type == sizeof (double) ) {
|
||||
double f2 = (double) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &f2), sizeof f2, 1, f);
|
||||
} else {
|
||||
M_ASSERT(size_of_type == sizeof (long double) );
|
||||
long double f3 = (long double) data;
|
||||
n = fwrite (M_ASSIGN_CAST(const void*, &f3), sizeof f3, 1, f);
|
||||
}
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Write the null-terminated string 'data'into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_string(m_serial_write_t serial, const char data[], size_t length)
|
||||
{
|
||||
M_ASSERT_SLOW(length == strlen(data) );
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
M_ASSERT(f != NULL && data != NULL);
|
||||
// Write first the number of (non null) characters
|
||||
if (m_ser1al_bin_write_size(f, length) != true) return m_core_serial_fail();
|
||||
// Write the characters (excluding the final null char)
|
||||
// NOTE: fwrite supports length == 0.
|
||||
size_t n = fwrite (M_ASSIGN_CAST(const void*, data), 1, length, f);
|
||||
return (n == length) ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Start writing an array of 'number_of_elements' objects into the serial stream 'serial'.
|
||||
If 'number_of_elements' is 0, then either the array has no data,
|
||||
or the number of elements of the array is unkown.
|
||||
Initialize 'local' so that it can be used to serialize the array
|
||||
(local is an unique serialization object of the array).
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_array_start(m_serial_local_t local, m_serial_write_t serial, const size_t number_of_elements)
|
||||
{
|
||||
(void) local; //Unused
|
||||
if (number_of_elements == (size_t)-1) return M_SERIAL_FAIL_RETRY;
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
size_t n = fwrite (M_ASSIGN_CAST(const void*, &number_of_elements), sizeof number_of_elements, 1, f);
|
||||
return n == 1 ? M_SERIAL_OK_CONTINUE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Write an array separator between elements of an array into the serial stream 'serial' if needed.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_array_next(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // Unused
|
||||
(void) serial; // Unused
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* End the writing of an array into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_array_end(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // Unused
|
||||
(void) serial; // Unused
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Write a value separator between element of the same pair of a map into the serial stream 'serial' if needed.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_map_value(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Start writing a tuple into the serial stream 'serial'.
|
||||
Initialize 'local' so that it can serial the tuple
|
||||
(local is an unique serialization object of the tuple).
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_tuple_start(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Start writing the field named field_name[index] of a tuple into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_tuple_id(m_serial_local_t local, m_serial_write_t serial, const char *const field_name[], const int max, const int index)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
(void) field_name;
|
||||
(void) max;
|
||||
(void) index; // Assume index are write in order from 0 to max.
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* End the write of a tuple into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_tuple_end(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_DONE;
|
||||
}
|
||||
|
||||
/* Start writing a variant into the serial stream 'serial'.
|
||||
If index <= 0, the variant is empty.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise
|
||||
Otherwise, the field 'field_name[index]' will be filled.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_variant_start(m_serial_local_t local, m_serial_write_t serial, const char *const field_name[], const int max, const int index)
|
||||
{
|
||||
(void) field_name;
|
||||
(void) max;
|
||||
(void) local;
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
size_t n = fwrite (M_ASSIGN_CAST(const void*, &index), sizeof index, 1, f);
|
||||
return n == 1 ? ((index < 0) ? M_SERIAL_OK_DONE : M_SERIAL_OK_CONTINUE) : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* End Writing a variant into the serial stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_write_variant_end(m_serial_local_t local, m_serial_write_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_DONE;
|
||||
}
|
||||
|
||||
/* The exported interface. */
|
||||
static const m_serial_write_interface_t m_ser1al_bin_write_interface = {
|
||||
m_ser1al_bin_write_boolean,
|
||||
m_ser1al_bin_write_integer,
|
||||
m_ser1al_bin_write_float,
|
||||
m_ser1al_bin_write_string,
|
||||
m_ser1al_bin_write_array_start,
|
||||
m_ser1al_bin_write_array_next,
|
||||
m_ser1al_bin_write_array_end,
|
||||
m_ser1al_bin_write_array_start,
|
||||
m_ser1al_bin_write_map_value,
|
||||
m_ser1al_bin_write_array_next,
|
||||
m_ser1al_bin_write_array_end,
|
||||
m_ser1al_bin_write_tuple_start,
|
||||
m_ser1al_bin_write_tuple_id,
|
||||
m_ser1al_bin_write_tuple_end,
|
||||
m_ser1al_bin_write_variant_start,
|
||||
m_ser1al_bin_write_variant_end
|
||||
};
|
||||
|
||||
M_INLINE void m_serial_bin_write_init(m_serial_write_t serial, FILE *f)
|
||||
{
|
||||
serial->m_interface = &m_ser1al_bin_write_interface;
|
||||
serial->data[0].p = M_ASSIGN_CAST(void*, f);
|
||||
}
|
||||
|
||||
M_INLINE void m_serial_bin_write_clear(m_serial_write_t serial)
|
||||
{
|
||||
(void) serial; // Nothing to do
|
||||
}
|
||||
|
||||
|
||||
/* Define a synonym of m_serial_read_t to the BIN serializer with its proper OPLIST */
|
||||
typedef m_serial_write_t m_serial_bin_write_t;
|
||||
|
||||
#define M_OPL_m_serial_bin_write_t() \
|
||||
(INIT_WITH(m_serial_bin_write_init), CLEAR(m_serial_bin_write_clear), \
|
||||
TYPE(m_serial_bin_write_t), PROPERTIES(( LET_AS_INIT_WITH(1) )) )
|
||||
|
||||
|
||||
|
||||
/********************************************************************************/
|
||||
/************************** FILE / READ / BIN *******************************/
|
||||
/********************************************************************************/
|
||||
|
||||
/* Read from the stream 'serial' a boolean.
|
||||
Set '*b' with the boolean value if succeeds
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_boolean(m_serial_read_t serial, bool *b){
|
||||
FILE *f = (FILE*) serial->data[0].p;
|
||||
size_t n = fread (M_ASSIGN_CAST(void*, b), sizeof (bool), 1, f);
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Read from the stream 'serial' an integer that can be represented with 'size_of_type' bytes.
|
||||
Set '*i' with the integer value if succeeds
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_integer(m_serial_read_t serial, long long *i, const size_t size_of_type){
|
||||
int8_t i8;
|
||||
int16_t i16;
|
||||
int32_t i32;
|
||||
int64_t i64;
|
||||
size_t n;
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
if (size_of_type == 1) {
|
||||
n = fread (M_ASSIGN_CAST(void*, &i8), sizeof i8, 1, f);
|
||||
*i = i8;
|
||||
} else if (size_of_type == 2) {
|
||||
n = fread (M_ASSIGN_CAST(void*, &i16), sizeof i16, 1, f);
|
||||
*i = i16;
|
||||
} else if (size_of_type == 4) {
|
||||
n = fread (M_ASSIGN_CAST(void*, &i32), sizeof i32, 1, f);
|
||||
*i = i32;
|
||||
} else {
|
||||
M_ASSERT(size_of_type == 8);
|
||||
n = fread (M_ASSIGN_CAST(void*, &i64), sizeof i64, 1, f);
|
||||
*i = i64;
|
||||
}
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Read from the stream 'serial' a float that can be represented with 'size_of_type' bytes.
|
||||
Set '*r' with the boolean value if succeeds
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_float(m_serial_read_t serial, long double *r, const size_t size_of_type){
|
||||
float f1;
|
||||
double f2;
|
||||
long double f3;
|
||||
size_t n;
|
||||
FILE *f = (FILE *)serial->data[0].p;
|
||||
if (size_of_type == sizeof f1) {
|
||||
n = fread (M_ASSIGN_CAST(void*, &f1), sizeof f1, 1, f);
|
||||
*r = f1;
|
||||
} else if (size_of_type == sizeof f2) {
|
||||
n = fread (M_ASSIGN_CAST(void*, &f2), sizeof f2, 1, f);
|
||||
*r = f2;
|
||||
} else {
|
||||
M_ASSERT(size_of_type == sizeof f3);
|
||||
n = fread (M_ASSIGN_CAST(void*, &f3), sizeof f3, 1, f);
|
||||
*r = f3;
|
||||
}
|
||||
return n == 1 ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Read from the stream 'serial' a string.
|
||||
Set 's' with the string if succeeds
|
||||
Return M_SERIAL_OK_DONE if it succeeds, M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_string(m_serial_read_t serial, struct string_s *s){
|
||||
FILE *f = (FILE*) serial->data[0].p;
|
||||
M_ASSERT(f != NULL && s != NULL);
|
||||
// First read the number of non null characters
|
||||
size_t length;
|
||||
if (m_ser1al_bin_read_size(f, &length) != true) return m_core_serial_fail();
|
||||
// Use of internal string interface to dimension the string
|
||||
char *p = m_str1ng_fit2size(s, length + 1);
|
||||
m_str1ng_set_size(s, length);
|
||||
// Read the characters excluding the final null one.
|
||||
// NOTE: fread supports length == 0.
|
||||
size_t n = fread(M_ASSIGN_CAST(void*, p), 1, length, f);
|
||||
// Force the final null character
|
||||
p[length] = 0;
|
||||
return (n == length) ? M_SERIAL_OK_DONE : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* Start reading from the stream 'serial' an array.
|
||||
Set '*num' with the number of elements, or 0 if it is not known.
|
||||
Initialize 'local' so that it can be used to serialize the array
|
||||
(local is an unique serialization object of the array).
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the array continue,
|
||||
M_SERIAL_OK_DONE if it succeeds and the array ends (the array is empty),
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_array_start(m_serial_local_t local, m_serial_read_t serial, size_t *num)
|
||||
{
|
||||
FILE *f = (FILE*) serial->data[0].p;
|
||||
size_t n = fread (M_ASSIGN_CAST(void*, num), sizeof *num, 1, f);
|
||||
local->data[1].s = *num;
|
||||
return (n != 1) ? m_core_serial_fail() : (local->data[1].s == 0) ? M_SERIAL_OK_DONE : M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Continue reading from the stream 'serial' an array.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the array continue,
|
||||
M_SERIAL_OK_DONE if it succeeds and the array ends,
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_array_next(m_serial_local_t local, m_serial_read_t serial)
|
||||
{
|
||||
(void) serial; // Unused
|
||||
M_ASSERT(local->data[1].s > 0);
|
||||
local->data[1].s --;
|
||||
return local->data[1].s == 0 ? M_SERIAL_OK_DONE : M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
/* Continue reading from the stream 'serial' the value separator
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the map continue,
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_map_value(m_serial_local_t local, m_serial_read_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Start reading a tuple from the stream 'serial'.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the tuple continues,
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_tuple_start(m_serial_local_t local, m_serial_read_t serial)
|
||||
{
|
||||
(void) serial;
|
||||
local->data[1].i = 0;
|
||||
return M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Continue reading a tuple from the stream 'serial'.
|
||||
Set '*id' with the corresponding index of the table 'field_name[max]'
|
||||
associated to the parsed field in the stream.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the tuple continues,
|
||||
Return M_SERIAL_OK_DONE if it succeeds and the tuple ends,
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_tuple_id(m_serial_local_t local, m_serial_read_t serial, const char *const field_name [], const int max, int *id)
|
||||
{
|
||||
(void) serial;
|
||||
(void) field_name;
|
||||
(void) max;
|
||||
*id = local->data[1].i;
|
||||
local->data[1].i ++;
|
||||
return (*id == max) ? M_SERIAL_OK_DONE : M_SERIAL_OK_CONTINUE;
|
||||
}
|
||||
|
||||
/* Start reading a variant from the stream 'serial'.
|
||||
Set '*id' with the corresponding index of the table 'field_name[max]'
|
||||
associated to the parsed field in the stream.
|
||||
Return M_SERIAL_OK_CONTINUE if it succeeds and the variant continues,
|
||||
Return M_SERIAL_OK_DONE if it succeeds and the variant ends(variant is empty),
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_variant_start(m_serial_local_t local, m_serial_read_t serial, const char *const field_name[], const int max, int*id)
|
||||
{
|
||||
(void) field_name;
|
||||
(void) max;
|
||||
(void) local; // argument not used
|
||||
FILE *f = (FILE*) serial->data[0].p;
|
||||
size_t n = fread (M_ASSIGN_CAST(void*, id), sizeof *id, 1, f);
|
||||
return n == 1 ? ((*id < 0) ? M_SERIAL_OK_DONE : M_SERIAL_OK_CONTINUE) : m_core_serial_fail();
|
||||
}
|
||||
|
||||
/* End reading a variant from the stream 'serial'.
|
||||
Return M_SERIAL_OK_DONE if it succeeds and the variant ends,
|
||||
M_SERIAL_FAIL otherwise */
|
||||
M_INLINE m_serial_return_code_t
|
||||
m_ser1al_bin_read_variant_end(m_serial_local_t local, m_serial_read_t serial)
|
||||
{
|
||||
(void) local; // argument not used
|
||||
(void) serial;
|
||||
return M_SERIAL_OK_DONE;
|
||||
}
|
||||
|
||||
static const m_serial_read_interface_t m_ser1al_bin_read_interface = {
|
||||
m_ser1al_bin_read_boolean,
|
||||
m_ser1al_bin_read_integer,
|
||||
m_ser1al_bin_read_float,
|
||||
m_ser1al_bin_read_string,
|
||||
m_ser1al_bin_read_array_start,
|
||||
m_ser1al_bin_read_array_next,
|
||||
m_ser1al_bin_read_array_start,
|
||||
m_ser1al_bin_read_map_value,
|
||||
m_ser1al_bin_read_array_next,
|
||||
m_ser1al_bin_read_tuple_start,
|
||||
m_ser1al_bin_read_tuple_id,
|
||||
m_ser1al_bin_read_variant_start,
|
||||
m_ser1al_bin_read_variant_end
|
||||
};
|
||||
|
||||
M_INLINE void m_serial_bin_read_init(m_serial_read_t serial, FILE *f)
|
||||
{
|
||||
serial->m_interface = &m_ser1al_bin_read_interface;
|
||||
serial->data[0].p = M_ASSIGN_CAST(void*, f);
|
||||
}
|
||||
|
||||
M_INLINE void m_serial_bin_read_clear(m_serial_read_t serial)
|
||||
{
|
||||
(void) serial; // Nothing to do
|
||||
}
|
||||
|
||||
/* Define a synonym of m_serial_read_t to the BIN serializer with its proper OPLIST */
|
||||
typedef m_serial_read_t m_serial_bin_read_t;
|
||||
#define M_OPL_m_serial_bin_read_t() \
|
||||
(INIT_WITH(m_serial_bin_read_init), CLEAR(m_serial_bin_read_clear), \
|
||||
TYPE(m_serial_bin_read_t), PROPERTIES(( LET_AS_INIT_WITH(1) )) )
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,553 +0,0 @@
|
||||
/*
|
||||
* M*LIB - SHARED Pointer Module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_SHARED_PTR_H
|
||||
#define MSTARLIB_SHARED_PTR_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-atomic.h"
|
||||
#include "m-genint.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define shared pointer and its function.
|
||||
USAGE: SHARED_PTR_DEF(name, type, [, oplist]) */
|
||||
#define M_SHARED_PTR_DEF(name, ...) \
|
||||
M_SHARED_PTR_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define shared pointer and its function
|
||||
as the given name name_t
|
||||
USAGE: SHARED_PTR_DEF_AS(name, name_t, type, [, oplist]) */
|
||||
#define M_SHARED_PTR_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SHAR3D_PTR_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_SHAR3D_ATOMIC_OPLIST, name_t ), \
|
||||
(name, __VA_ARGS__ , M_SHAR3D_ATOMIC_OPLIST, name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a shared pointer.
|
||||
USAGE: SHARED_OPLIST(name [, oplist_of_the_type]) */
|
||||
#define M_SHARED_PTR_OPLIST(...) \
|
||||
M_SHAR3D_PTR_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_BASIC_OPLIST ), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/* Define relaxed shared pointer and its function (thread unsafe).
|
||||
USAGE: SHARED_PTR_RELAXED_DEF(name, type, [, oplist]) */
|
||||
#define M_SHARED_PTR_RELAXED_DEF(name, ...) \
|
||||
M_SHARED_PTR_RELAXED_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define relaxed shared pointer and its function (thread unsafe)
|
||||
as the given name name_t
|
||||
USAGE: SHARED_PTR_RELAXED_DEF(name, type, [, oplist]) */
|
||||
#define M_SHARED_PTR_RELAXED_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SHAR3D_PTR_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_SHAR3D_INTEGER_OPLIST, name_t ), \
|
||||
(name, __VA_ARGS__, M_SHAR3D_INTEGER_OPLIST, name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define shared resource and its function.
|
||||
This is a bounded pool of resource shared by multiple owners.
|
||||
USAGE: SHARED_RESOURCE_DEF(name, type, [, oplist]) */
|
||||
#define M_SHARED_RESOURCE_DEF(name, ...) \
|
||||
M_SHARED_RESOURCE_DEF_AS(name, M_F(name,_t), M_F(name,_it_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define shared resource and its function
|
||||
as the given name named_t and the iterator it_t
|
||||
This is a bounded pool of resource shared by multiple owners.
|
||||
USAGE: SHARED_RESOURCE_DEF_AS(name, name_t, it_t, type, [, oplist]) */
|
||||
#define M_SHARED_RESOURCE_DEF_AS(name, name_t, it_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SHAR3D_RESOURCE_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t, it_t ), \
|
||||
(name, __VA_ARGS__, name_t, it_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
// deferred evaluation
|
||||
#define M_SHAR3D_PTR_OPLIST_P1(arg) M_SHAR3D_PTR_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given, shared_t oplist */
|
||||
#define M_SHAR3D_PTR_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_SHAR3D_PTR_OPLIST_P3, M_SHAR3D_PTR_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_SHAR3D_PTR_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_SHARED_PTR_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
#define M_SHAR3D_PTR_OPLIST_P3(name, oplist) ( \
|
||||
INIT(M_F(name, _init)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
INIT_SET(M_F(name, _init_set)), \
|
||||
SET(M_F(name, _set)) \
|
||||
INIT_MOVE(M_F(name, _init_move)), \
|
||||
RESET(M_F(name, _reset)), \
|
||||
MOVE(M_F(name, _move)), \
|
||||
SWAP(M_F(name, _swap)) \
|
||||
,NAME(name) \
|
||||
,TYPE(M_F(name, _ct)) \
|
||||
)
|
||||
|
||||
// OPLIST to handle a counter of atomic type
|
||||
#define M_SHAR3D_ATOMIC_OPLIST (TYPE(atomic_int), \
|
||||
INIT_SET(atomic_init), \
|
||||
ADD(atomic_fetch_add), \
|
||||
SUB(atomic_fetch_sub), \
|
||||
IT_CREF(atomic_load))
|
||||
|
||||
// OPLIST to handle a counter of non-atomic type
|
||||
#define M_SHAR3D_INTEGER_OPLIST (TYPE(int), \
|
||||
INIT_SET(m_shar3d_integer_init_set), \
|
||||
ADD(m_shar3d_integer_add), \
|
||||
SUB(m_shar3d_integer_sub), \
|
||||
IT_CREF(m_shar3d_integer_cref))
|
||||
|
||||
/* Atomic like interface for basic integers */
|
||||
M_INLINE void m_shar3d_integer_init_set(int *p, int val) { *p = val; }
|
||||
M_INLINE int m_shar3d_integer_add(int *p, int val) { int r = *p; *p += val; return r; }
|
||||
M_INLINE int m_shar3d_integer_sub(int *p, int val) { int r = *p; *p -= val; return r; }
|
||||
M_INLINE int m_shar3d_integer_cref(int *p) { return *p; }
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Contract of a shared pointer */
|
||||
#define M_SHAR3D_CONTRACT(shared, cpt_oplist) do { \
|
||||
M_ASSERT(shared != NULL); \
|
||||
M_ASSERT(*shared == NULL || M_CALL_IT_CREF(cpt_oplist, &(*shared)->cpt) >= 1); \
|
||||
} while (0)
|
||||
|
||||
// deferred evaluation
|
||||
#define M_SHAR3D_PTR_DEF_P1(arg) M_ID( M_SHAR3D_PTR_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_SHAR3D_PTR_DEF_P2(name, type, oplist, cpt_oplist, shared_t) \
|
||||
M_IF_OPLIST(oplist)(M_SHAR3D_PTR_DEF_P3, M_SHAR3D_PTR_DEF_FAILURE)(name, type, oplist, cpt_oplist, shared_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_SHAR3D_PTR_DEF_FAILURE(name, type, oplist, cpt_oplist, shared_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(SHARED_PTR_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Code generation */
|
||||
#define M_SHAR3D_PTR_DEF_P3(name, type, oplist, cpt_oplist, shared_t) \
|
||||
M_SHAR3D_PTR_DEF_TYPE(name, type, oplist, cpt_oplist, shared_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_SHAR3D_PTR_DEF_CORE(name, type, oplist, cpt_oplist, shared_t) \
|
||||
M_EMPLACE_QUEUE_DEF(name, cpt_oplist, M_F(name, _init_with), oplist, M_SHAR3D_PTR_DEF_EMPLACE)
|
||||
|
||||
/* Define the types */
|
||||
#define M_SHAR3D_PTR_DEF_TYPE(name, type, oplist, cpt_oplist, shared_t) \
|
||||
\
|
||||
typedef struct M_F(name, _s){ \
|
||||
type *data; /* Pointer to the data */ \
|
||||
M_GET_TYPE cpt_oplist cpt; /* Counter of how many refs the data */ \
|
||||
bool combineAlloc; /* Does the data and the ptr share the slot? */ \
|
||||
} *shared_t[1]; \
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
/* Internal type for oplist */ \
|
||||
typedef shared_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
\
|
||||
typedef struct M_F(name, _combine_s) { \
|
||||
struct M_F(name, _s) ptr; \
|
||||
type data; \
|
||||
} M_F(name, combine_ct)[1]; \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_SHAR3D_PTR_DEF_CORE(name, type, oplist, cpt_oplist, shared_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(shared_t shared) \
|
||||
{ \
|
||||
*shared = NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init2)(shared_t shared, type *data) \
|
||||
{ \
|
||||
M_ASSERT (shared != NULL); \
|
||||
/* The shared ptr get exclusive access to data */ \
|
||||
struct M_F(name, _s) *ptr; \
|
||||
if (M_UNLIKELY (data == NULL)) { \
|
||||
*shared = NULL; \
|
||||
return; \
|
||||
} \
|
||||
ptr = M_CALL_NEW(oplist, struct M_F(name, _s)); \
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(struct M_F(name, _s))); \
|
||||
return; \
|
||||
} \
|
||||
ptr->data = data; \
|
||||
M_CALL_INIT_SET(cpt_oplist, &ptr->cpt, 1); \
|
||||
ptr->combineAlloc = false; \
|
||||
*shared = ptr; \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD(INIT, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_new)(shared_t shared) \
|
||||
{ \
|
||||
/* NOTE: Alloc 1 struct with both structures. */ \
|
||||
struct M_F(name, _combine_s) *p = \
|
||||
M_CALL_NEW(oplist, struct M_F(name, _combine_s)); \
|
||||
if (M_UNLIKELY_NOMEM (p == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(struct M_F(name, _combine_s))); \
|
||||
return; \
|
||||
} \
|
||||
struct M_F(name, _s) *ptr = &p->ptr; \
|
||||
ptr->combineAlloc = true; \
|
||||
type *data = &p->data; \
|
||||
M_CALL_INIT( oplist, *data); \
|
||||
ptr->data = data; \
|
||||
M_CALL_INIT_SET(cpt_oplist, &ptr->cpt, 1); \
|
||||
*shared = ptr; \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
} \
|
||||
, /* No INIT */ ) \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _NULL_p)(const shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
return *shared == NULL; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_set)(shared_t dest, \
|
||||
const shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_ASSERT (dest != shared); \
|
||||
*dest = *shared; \
|
||||
if (*dest != NULL) { \
|
||||
int n = M_CALL_ADD(cpt_oplist, &((*dest)->cpt), 1); \
|
||||
(void) n; /* unused return value */ \
|
||||
} \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(shared_t dest) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
if (*dest != NULL) { \
|
||||
if (M_CALL_SUB(cpt_oplist, &((*dest)->cpt), 1) == 1) { \
|
||||
bool combineAlloc = (*dest)->combineAlloc; \
|
||||
/* Note: if combineAlloc is true, the address of the slot \
|
||||
combining both data & ptr is the same as the address of the \
|
||||
first element, aka data itself. Static analyzer tools don't \
|
||||
seem to detect this and report error. */ \
|
||||
M_CALL_CLEAR(oplist, *(*dest)->data); \
|
||||
if (combineAlloc == false) { \
|
||||
M_CALL_DEL(oplist, (*dest)->data); \
|
||||
} \
|
||||
M_CALL_DEL(oplist, *dest); \
|
||||
} \
|
||||
*dest = NULL; \
|
||||
} \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _reset)(shared_t dest) \
|
||||
{ \
|
||||
/* NOTE: Clear will also set dest to NULL */ \
|
||||
M_F(name, _clear)(dest); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _set)(shared_t dest, \
|
||||
const shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_F(name, _clear)(dest); \
|
||||
M_F(name, _init_set)(dest, shared); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(shared_t dest, \
|
||||
shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_ASSERT (dest != NULL && dest != shared); \
|
||||
*dest = *shared; \
|
||||
*shared = NULL; \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(shared_t dest, \
|
||||
shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(dest, cpt_oplist); \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_ASSERT (dest != shared); \
|
||||
M_F(name, _clear)(dest); \
|
||||
M_F(name, _init_move)(dest, shared); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _swap)(shared_t p1, \
|
||||
shared_t p2) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(p1, cpt_oplist); \
|
||||
M_SHAR3D_CONTRACT(p2, cpt_oplist); \
|
||||
/* NOTE: SWAP is not atomic */ \
|
||||
M_SWAP (struct M_F(name, _s)*, *p1, *p2); \
|
||||
M_SHAR3D_CONTRACT(p1, cpt_oplist); \
|
||||
M_SHAR3D_CONTRACT(p2, cpt_oplist); \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _equal_p)(const shared_t p1, \
|
||||
const shared_t p2) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(p1, cpt_oplist); \
|
||||
M_SHAR3D_CONTRACT(p2, cpt_oplist); \
|
||||
return *p1 == *p2; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _cref)(const shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_ASSERT(*shared != NULL); \
|
||||
type *data = (*shared)->data; \
|
||||
M_ASSERT (data != NULL); \
|
||||
return M_CONST_CAST (type, data); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _ref)(shared_t shared) \
|
||||
{ \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
M_ASSERT(*shared != NULL); \
|
||||
type *data = (*shared)->data; \
|
||||
M_ASSERT (data != NULL); \
|
||||
return data; \
|
||||
} \
|
||||
|
||||
/* Definition of the emplace_back function for arrays */
|
||||
#define M_SHAR3D_PTR_DEF_EMPLACE(name, cpt_oplist, function_name, oplist, init_func, exp_emplace_type) \
|
||||
M_INLINE void \
|
||||
function_name(M_F(name, _ct) shared \
|
||||
M_EMPLACE_LIST_TYPE_VAR(a, exp_emplace_type) ) \
|
||||
{ \
|
||||
/* NOTE: Alloc 1 struct with both structures. */ \
|
||||
struct M_F(name, _combine_s) *p = \
|
||||
M_CALL_NEW(oplist, struct M_F(name, _combine_s)); \
|
||||
if (M_UNLIKELY_NOMEM (p == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(struct M_F(name, _combine_s))); \
|
||||
return; \
|
||||
} \
|
||||
struct M_F(name, _s) *ptr = &p->ptr; \
|
||||
ptr->combineAlloc = true; \
|
||||
M_F(name, _subtype_ct) *data = &p->data; \
|
||||
M_EMPLACE_CALL_FUNC(a, init_func, oplist, *data, exp_emplace_type); \
|
||||
ptr->data = data; \
|
||||
M_CALL_INIT_SET(cpt_oplist, &ptr->cpt, 1); \
|
||||
*shared = ptr; \
|
||||
M_SHAR3D_CONTRACT(shared, cpt_oplist); \
|
||||
} \
|
||||
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#define M_SHAR3D_RESOURCE_CONTRACT(s) do { \
|
||||
M_ASSERT (s != NULL); \
|
||||
M_ASSERT (s->buffer != NULL); \
|
||||
} while (0)
|
||||
|
||||
// deferred
|
||||
#define M_SHAR3D_RESOURCE_DEF_P1(arg) M_ID( M_SHAR3D_RESOURCE_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_SHAR3D_RESOURCE_DEF_P2(name, type, oplist, shared_t, it_t) \
|
||||
M_IF_OPLIST(oplist)(M_SHAR3D_RESOURCE_DEF_P3, M_SHAR3D_RESOURCE_DEF_FAILURE)(name, type, oplist, shared_t, it_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_SHAR3D_RESOURCE_DEF_FAILURE(name, type, oplist, shared_t, it_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(SHARED_RESOURCE_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
#define M_SHAR3D_RESOURCE_DEF_P3(name, type, oplist, shared_t, it_t) \
|
||||
M_SHAR3D_RESOURCE_DEF_TYPE(name, type, oplist, shared_t, it_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_SHAR3D_RESOURCE_DEF_CORE(name, type, oplist, shared_t, it_t) \
|
||||
|
||||
/* Define the types */
|
||||
#define M_SHAR3D_RESOURCE_DEF_TYPE(name, type, oplist, shared_t, it_t) \
|
||||
\
|
||||
/* Create an aligned type to avoid false sharing between threads */ \
|
||||
typedef struct M_F(name, _atype_s) { \
|
||||
atomic_uint cpt; \
|
||||
type x; \
|
||||
M_CACHELINE_ALIGN(align, type, atomic_uint); \
|
||||
} M_F(name, _atype_ct); \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
m_genint_t core; \
|
||||
M_F(name, _atype_ct) *buffer; \
|
||||
} shared_t[1]; \
|
||||
\
|
||||
typedef struct M_F(name, _it_s) { \
|
||||
unsigned int idx; \
|
||||
struct M_F(name, _s) *ref; \
|
||||
} it_t[1]; \
|
||||
\
|
||||
/* Internal Types for oplist */ \
|
||||
typedef shared_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_SHAR3D_RESOURCE_DEF_CORE(name, type, oplist, shared_t, it_t) \
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(shared_t s, size_t n) \
|
||||
{ \
|
||||
M_ASSERT(s != NULL); \
|
||||
M_ASSERT (n > 0 && n < UINT_MAX); \
|
||||
s->buffer = M_CALL_REALLOC(oplist, M_F(name, _atype_ct), NULL, n); \
|
||||
if (M_UNLIKELY_NOMEM (s->buffer == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_F(name, _atype_ct)) * n); \
|
||||
return; \
|
||||
} \
|
||||
for(size_t i = 0; i < n; i++) { \
|
||||
M_CALL_INIT(oplist, s->buffer[i].x); \
|
||||
atomic_init (&s->buffer[i].cpt, 0U); \
|
||||
} \
|
||||
m_genint_init(s->core, (unsigned int) n); \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(s); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(shared_t s) \
|
||||
{ \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(s); \
|
||||
size_t n = m_genint_size(s->core); \
|
||||
for(size_t i = 0; i < n; i++) { \
|
||||
M_CALL_CLEAR(oplist, s->buffer[i].x); \
|
||||
} \
|
||||
M_CALL_FREE(oplist, s->buffer); \
|
||||
s->buffer = NULL; \
|
||||
m_genint_clear(s->core); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it)(it_t it, shared_t s) \
|
||||
{ \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(s); \
|
||||
M_ASSERT (it != NULL); \
|
||||
unsigned int idx = m_genint_pop(s->core); \
|
||||
it->idx = idx; \
|
||||
it->ref = s; \
|
||||
if (M_LIKELY (idx != M_GENINT_ERROR)) { \
|
||||
M_ASSERT(atomic_load(&s->buffer[idx].cpt) == 0); \
|
||||
atomic_store(&s->buffer[idx].cpt, 1U); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE bool \
|
||||
M_F(name, _end_p)(it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL); \
|
||||
return it->idx == M_GENINT_ERROR; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _ref)(it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL && it->ref != NULL && it->idx != M_GENINT_ERROR); \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(it->ref); \
|
||||
return &it->ref->buffer[it->idx].x; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _cref)(it_t it) \
|
||||
{ \
|
||||
M_ASSERT (it != NULL && it->ref != NULL && it->idx != M_GENINT_ERROR); \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(it->ref); \
|
||||
return M_CONST_CAST (type, &it->ref->buffer[it->idx].x); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _end)(it_t it, shared_t s) \
|
||||
{ \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(s); \
|
||||
M_ASSERT (it != NULL); \
|
||||
M_ASSERT (it->ref == s); \
|
||||
unsigned int idx = it->idx; \
|
||||
if (M_LIKELY (idx != M_GENINT_ERROR)) { \
|
||||
unsigned int c = atomic_fetch_sub (&it->ref->buffer[idx].cpt, 1U); \
|
||||
if (c == 1) { \
|
||||
m_genint_push(it->ref->core, idx); \
|
||||
} \
|
||||
it->idx = M_GENINT_ERROR; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _it_set)(it_t itd, it_t its) \
|
||||
{ \
|
||||
M_ASSERT (itd != NULL && its != NULL); \
|
||||
M_SHAR3D_RESOURCE_CONTRACT(its->ref); \
|
||||
itd->ref = its->ref; \
|
||||
unsigned int idx = its->idx; \
|
||||
itd->idx = idx; \
|
||||
if (M_LIKELY (idx != M_GENINT_ERROR)) { \
|
||||
unsigned int c = atomic_fetch_add(&itd->ref->buffer[idx].cpt, 1U); \
|
||||
M_ASSERT (c >= 1); \
|
||||
} \
|
||||
} \
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define SHARED_PTR_OPLIST M_SHARED_PTR_OPLIST
|
||||
#define SHARED_PTR_DEF M_SHARED_PTR_DEF
|
||||
#define SHARED_PTR_DEF_AS M_SHARED_PTR_DEF_AS
|
||||
#define SHARED_PTR_RELAXED_DEF M_SHARED_PTR_RELAXED_DEF
|
||||
#define SHARED_PTR_RELAXED_DEF_AS M_SHARED_PTR_RELAXED_DEF_AS
|
||||
#define SHARED_RESOURCE_DEF M_SHARED_RESOURCE_DEF
|
||||
#define SHARED_RESOURCE_DEF_AS M_SHARED_RESOURCE_DEF_AS
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,814 +0,0 @@
|
||||
/*
|
||||
* M*LIB - SNAPSHOT Module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_SNAPSHOT_H
|
||||
#define MSTARLIB_SNAPSHOT_H
|
||||
|
||||
#include "m-atomic.h"
|
||||
#include "m-core.h"
|
||||
#include "m-genint.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define a Single Producer Single Consummer snapshot and its functions
|
||||
USAGE: SNAPSHOT_SPSC_DEF(name, type[, oplist]) */
|
||||
#define M_SNAPSHOT_SPSC_DEF(name, ...) \
|
||||
M_SNAPSHOT_SPSC_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a Single Producer Single Consummer snapshot and its functions
|
||||
as the given name name_t
|
||||
USAGE: SNAPSHOT_SPSC_DEF_AS(name, name_t, type[, oplist]) */
|
||||
#define M_SNAPSHOT_SPSC_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SNAPSH0T_SPSC_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__ , name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define a Single Producer Multiple Consummer snapshot and its functions
|
||||
USAGE: SNAPSHOT_SPMC_DEF(name, type[, oplist]) */
|
||||
#define M_SNAPSHOT_SPMC_DEF(name, ...) \
|
||||
M_SNAPSHOT_SPMC_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a Single Producer Multiple Consummer snapshot and its functions
|
||||
as the given name name_t
|
||||
USAGE: SNAPSHOT_SPMC_DEF_AS(name, type[, oplist]) */
|
||||
#define M_SNAPSHOT_SPMC_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SNAPSH0T_SPMC_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__ , name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define a Multiple Producer Multiple Consummer snapshot and its functions
|
||||
USAGE: SNAPSHOT_MPMC_DEF(name, type[, oplist]) */
|
||||
#define M_SNAPSHOT_MPMC_DEF(name, ...) \
|
||||
M_SNAPSHOT_MPMC_DEF_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define a Multiple Producer Multiple Consummer snapshot and its functions
|
||||
as the given name name_t
|
||||
USAGE: SNAPSHOT_MPMC_DEF_AS(name, name_t, type[, oplist]) */
|
||||
#define M_SNAPSHOT_MPMC_DEF_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_SNAPSH0T_MPMC_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t ), \
|
||||
(name, __VA_ARGS__ , name_t ))) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a snapshot (SPSC, SPMC or MPMC).
|
||||
USAGE: SNAPSHOT_OPLIST(name[, oplist]) */
|
||||
#define M_SNAPSHOT_OPLIST(...) \
|
||||
M_SNAPSH0T_OPLIST_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
((__VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)() ), \
|
||||
(__VA_ARGS__ )))
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
// deferred evaluation of the input
|
||||
#define M_SNAPSH0T_OPLIST_P1(arg) M_SNAPSH0T_OPLIST_P2 arg
|
||||
|
||||
/* Validation of the given oplist */
|
||||
#define M_SNAPSH0T_OPLIST_P2(name, oplist) \
|
||||
M_IF_OPLIST(oplist)(M_SNAPSH0T_OPLIST_P3, M_SNAPSH0T_OPLIST_FAILURE)(name, oplist)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_SNAPSH0T_OPLIST_FAILURE(name, oplist) \
|
||||
((M_LIB_ERROR(ARGUMENT_OF_SNAPSHOT_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
|
||||
|
||||
/* Define the oplist of a snapshot */
|
||||
#define M_SNAPSH0T_OPLIST_P3(name, oplist) \
|
||||
(INIT(M_F(name, _init)) \
|
||||
,INIT_SET(M_F(name, _init_set)) \
|
||||
,SET(M_F(name, _set)) \
|
||||
,CLEAR(M_F(name, _clear)) \
|
||||
,NAME(name) \
|
||||
,TYPE(M_F(name, _ct)) \
|
||||
,SUBTYPE(M_F(name, _subtype_ct)) \
|
||||
,OPLIST(oplist) \
|
||||
,M_IF_METHOD(INIT_MOVE, oplist)(INIT_MOVE(M_F(name, _init_move)),) \
|
||||
,M_IF_METHOD(MOVE, oplist)(MOVE(M_F(name, _move)),) \
|
||||
)
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Flag defining the atomic state of a snapshot:
|
||||
* - r: Index of the read buffer Range [0..2]
|
||||
* - w: Index of the write buffer Range [0..2]
|
||||
* - f: Next index of the write buffer when a shot is taken Range [0..2]
|
||||
* - b: Boolean indicating that the read buffer shall be updated
|
||||
* all fields packed in an unsigned char type.
|
||||
*/
|
||||
#define M_SNAPSH0T_SPSC_FLAG(r, w, f, b) \
|
||||
((unsigned char)( ( (r) << 4) | ((w) << 2) | ((f)) | ((b) << 6)))
|
||||
#define M_SNAPSH0T_SPSC_R(flags) \
|
||||
(((unsigned int) (flags) >> 4) & 0x03u)
|
||||
#define M_SNAPSH0T_SPSC_W(flags) \
|
||||
(((unsigned int) (flags) >> 2) & 0x03u)
|
||||
#define M_SNAPSH0T_SPSC_F(flags) \
|
||||
(((unsigned int) (flags) >> 0) & 0x03u)
|
||||
#define M_SNAPSH0T_SPSC_B(flags) \
|
||||
(((unsigned int) (flags) >> 6) & 0x01u)
|
||||
|
||||
/* NOTE: Due to atomic_load only accepting non-const pointer,
|
||||
we can't have any const in the interface. */
|
||||
#define M_SNAPSH0T_SPSC_FLAGS_CONTRACT(flags) \
|
||||
M_ASSERT(M_SNAPSH0T_SPSC_R(flags) != M_SNAPSH0T_SPSC_W(flags) \
|
||||
&& M_SNAPSH0T_SPSC_R(flags) != M_SNAPSH0T_SPSC_F(flags) \
|
||||
&& M_SNAPSH0T_SPSC_W(flags) != M_SNAPSH0T_SPSC_F(flags))
|
||||
|
||||
#define M_SNAPSH0T_SPSC_CONTRACT(snap) do { \
|
||||
M_ASSERT((snap) != NULL); \
|
||||
unsigned char f = atomic_load (&(snap)->flags); \
|
||||
M_SNAPSH0T_SPSC_FLAGS_CONTRACT(f); \
|
||||
} while (0)
|
||||
|
||||
// A snapshot is basically an atomic triple buffer (Lock Free)
|
||||
// between a single producer thread and a single consummer thread.
|
||||
#define M_SNAPSH0T_SPSC_MAX_BUFFER 3
|
||||
|
||||
// Defered evaluation of the arguments.
|
||||
#define M_SNAPSH0T_SPSC_DEF_P1(arg) M_ID( M_SNAPSH0T_SPSC_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_SNAPSH0T_SPSC_DEF_P2(name, type, oplist, snapshot_t) \
|
||||
M_IF_OPLIST(oplist)(M_SNAPSH0T_SPSC_DEF_P3, M_SNAPSH0T_SPSC_DEF_FAILURE)(name, type, oplist, snapshot_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_SNAPSH0T_SPSC_DEF_FAILURE(name, type, oplist, snapshot_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(SNAPSHOT_SPSC_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Expand the type and the functions of a SPSC snapshot */
|
||||
#define M_SNAPSH0T_SPSC_DEF_P3(name, type, oplist, snapshot_t) \
|
||||
M_SNAPSH0T_SPSC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_SNAPSH0T_SPSC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
|
||||
/* Define the type */
|
||||
#define M_SNAPSH0T_SPSC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
/* Create an aligned type to avoid false sharing between threads */ \
|
||||
typedef struct M_F(name, _aligned_type_s) { \
|
||||
type x; \
|
||||
M_CACHELINE_ALIGN(align, type); \
|
||||
} M_F(name, _aligned_type_ct); \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _aligned_type_ct) data[M_SNAPSH0T_SPSC_MAX_BUFFER]; \
|
||||
atomic_uchar flags; \
|
||||
} snapshot_t[1]; \
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
\
|
||||
/* Define internal types for oplist */ \
|
||||
typedef snapshot_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_SNAPSH0T_SPSC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(snapshot_t snap) \
|
||||
{ \
|
||||
M_ASSERT(snap != NULL); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_INIT(oplist, snap->data[i].x); \
|
||||
} \
|
||||
atomic_init (&snap->flags, M_SNAPSH0T_SPSC_FLAG(0, 1, 2, 0)); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_CLEAR(oplist, snap->data[i].x); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
/* const is missing for org due to use of atomic_load of org */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_set)(snapshot_t snap, snapshot_t org) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(org); \
|
||||
M_ASSERT(snap != NULL && snap != org); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_INIT_SET(oplist, snap->data[i].x, org->data[i].x); \
|
||||
} \
|
||||
atomic_init (&snap->flags, atomic_load(&org->flags)); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
} \
|
||||
\
|
||||
/* const is missing for org due to use of atomic_load of org */ \
|
||||
M_INLINE void \
|
||||
M_F(name, _set)(snapshot_t snap, snapshot_t org) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(org); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_SET(oplist, snap->data[i].x, org->data[i].x); \
|
||||
} \
|
||||
atomic_init (&snap->flags, atomic_load(&org->flags)); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
} \
|
||||
\
|
||||
M_IF_METHOD(INIT_MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(snapshot_t snap, snapshot_t org) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(org); \
|
||||
M_ASSERT(snap != NULL && snap != org); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_INIT_MOVE(oplist, snap->data[i].x, org->data[i].x); \
|
||||
} \
|
||||
atomic_store (&snap->flags, atomic_load(&org->flags)); \
|
||||
atomic_store (&org->flags, M_SNAPSH0T_SPSC_FLAG(0,0,0,0) ); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
} \
|
||||
,) /* IF_METHOD (INIT_MOVE) */ \
|
||||
\
|
||||
M_IF_METHOD(MOVE, oplist)( \
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(snapshot_t snap, \
|
||||
snapshot_t org) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(org); \
|
||||
M_ASSERT(snap != org); \
|
||||
for(int i = 0; i < M_SNAPSH0T_SPSC_MAX_BUFFER; i++) { \
|
||||
M_CALL_MOVE(oplist, snap->data[i].x, org->data[i].x); \
|
||||
} \
|
||||
atomic_store (&snap->flags, atomic_load(&org->flags)); \
|
||||
atomic_store (&org->flags, M_SNAPSH0T_SPSC_FLAG(0,0,0,0) ); \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
} \
|
||||
,) /* IF_METHOD (MOVE) */ \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _write)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
unsigned char nextFlags, origFlags = atomic_load (&snap->flags); \
|
||||
/* Atomic CAS operation */ \
|
||||
do { \
|
||||
/* Swap F and W buffer, setting exchange flag */ \
|
||||
nextFlags = M_SNAPSH0T_SPSC_FLAG(M_SNAPSH0T_SPSC_R(origFlags), \
|
||||
M_SNAPSH0T_SPSC_F(origFlags), \
|
||||
M_SNAPSH0T_SPSC_W(origFlags), 1); \
|
||||
/* exponential backoff is not needed as there can't be more \
|
||||
than 2 threads which try to update the data. */ \
|
||||
} while (!atomic_compare_exchange_weak (&snap->flags, &origFlags, \
|
||||
nextFlags)); \
|
||||
/* Return new write buffer for new updating */ \
|
||||
return &snap->data[M_SNAPSH0T_SPSC_W(nextFlags)].x; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _read)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
unsigned char nextFlags, origFlags = atomic_load (&snap->flags); \
|
||||
/* Atomic CAS operation */ \
|
||||
do { \
|
||||
/* If no exchange registered, do nothing and keep the same */ \
|
||||
if (!M_SNAPSH0T_SPSC_B(origFlags)) { \
|
||||
nextFlags = origFlags; \
|
||||
break; \
|
||||
} \
|
||||
/* Swap R and F buffer, clearing exchange flag */ \
|
||||
nextFlags = M_SNAPSH0T_SPSC_FLAG(M_SNAPSH0T_SPSC_F(origFlags), \
|
||||
M_SNAPSH0T_SPSC_W(origFlags), \
|
||||
M_SNAPSH0T_SPSC_R(origFlags), 0); \
|
||||
/* exponential backoff is not needed as there can't be more \
|
||||
than 2 threads which try to update the data. */ \
|
||||
} while (!atomic_compare_exchange_weak (&snap->flags, &origFlags, \
|
||||
nextFlags)); \
|
||||
/* Return current read buffer */ \
|
||||
return M_CONST_CAST(type, &snap->data[M_SNAPSH0T_SPSC_R(nextFlags)].x); \
|
||||
} \
|
||||
\
|
||||
/* Non const due to use of atomic_load */ \
|
||||
M_INLINE bool \
|
||||
M_F(name, _updated_p)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
unsigned char flags = atomic_load (&snap->flags); \
|
||||
return M_SNAPSH0T_SPSC_B(flags); \
|
||||
} \
|
||||
\
|
||||
/* Non const due to use of atomic_load */ \
|
||||
M_INLINE type * \
|
||||
M_F(name, _get_write_buffer)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
unsigned char flags = atomic_load(&snap->flags); \
|
||||
return &snap->data[M_SNAPSH0T_SPSC_W(flags)].x; \
|
||||
} \
|
||||
\
|
||||
/* Non const due to use of atomic_load */ \
|
||||
M_INLINE type const * \
|
||||
M_F(name, _get_read_buffer)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPSC_CONTRACT(snap); \
|
||||
unsigned char flags = atomic_load(&snap->flags); \
|
||||
return M_CONST_CAST(type, &snap->data[M_SNAPSH0T_SPSC_R(flags)].x); \
|
||||
} \
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#define M_SNAPSH0T_SPMC_INT_FLAG(w, n) ( ((w) << 1) | (n) )
|
||||
#define M_SNAPSH0T_SPMC_INT_FLAG_W(f) ((f) >> 1)
|
||||
#define M_SNAPSH0T_SPMC_INT_FLAG_N(f) ((f) & 1)
|
||||
|
||||
// 2 more buffer than the number of readers are needed
|
||||
#define M_SNAPSH0T_SPMC_EXTRA_BUFFER 2
|
||||
|
||||
#define M_SNAPSH0T_SPMC_MAX_READER (M_GENINT_MAX_ALLOC-M_SNAPSH0T_SPMC_EXTRA_BUFFER)
|
||||
|
||||
/* Internal structure to handle SPMC snapshot but return an unique index in the buffer array.
|
||||
- lastNext: last published written index + next flag (format M_SNAPSH0T_SPMC_INT_FLAG)
|
||||
- currentWrite: the index being currently written.
|
||||
- n_reader : number of readers
|
||||
- cptTab: ref counter array to keep track of how many readers use the corresponding buffer.
|
||||
- freeList: a pool of free integers.
|
||||
*/
|
||||
typedef struct m_snapsh0t_mrsw_s {
|
||||
atomic_uint lastNext;
|
||||
unsigned int currentWrite;
|
||||
size_t n_reader;
|
||||
atomic_uint *cptTab;
|
||||
m_genint_t freeList;
|
||||
} m_snapsh0t_mrsw_ct[1];
|
||||
|
||||
// can't check currentWrite due to potential data race on it
|
||||
#define M_SNAPSH0T_SPMC_INT_CONTRACT(s) do { \
|
||||
M_ASSERT (s != NULL); \
|
||||
M_ASSERT (s->n_reader > 0 && s->n_reader <= M_SNAPSH0T_SPMC_MAX_READER); \
|
||||
M_ASSERT ((size_t)M_SNAPSH0T_SPMC_INT_FLAG_W(atomic_load(&s->lastNext)) \
|
||||
<= s->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER); \
|
||||
M_ASSERT (s->cptTab != NULL); \
|
||||
} while (0)
|
||||
|
||||
/* Initialize m_snapsh0t_mrsw_ct for n readers (constructor) */
|
||||
M_INLINE void
|
||||
m_snapsh0t_mrsw_init(m_snapsh0t_mrsw_ct s, size_t n)
|
||||
{
|
||||
M_ASSERT (s != NULL);
|
||||
M_ASSERT (n >= 1 && n <= M_SNAPSH0T_SPMC_MAX_READER);
|
||||
s->n_reader = n;
|
||||
n += M_SNAPSH0T_SPMC_EXTRA_BUFFER;
|
||||
|
||||
// Initialize the counters to zero (no reader use it)
|
||||
atomic_uint *ptr = M_MEMORY_REALLOC (atomic_uint, NULL, n);
|
||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
|
||||
M_MEMORY_FULL(sizeof (atomic_uint) * n);
|
||||
return;
|
||||
}
|
||||
s->cptTab = ptr;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
atomic_init(&s->cptTab[i], 0U);
|
||||
m_genint_init (s->freeList, (unsigned int) n);
|
||||
|
||||
// Get a free buffer and set it as available for readers
|
||||
unsigned int w = m_genint_pop(s->freeList);
|
||||
M_ASSERT (w != M_GENINT_ERROR);
|
||||
atomic_store(&s->cptTab[w], 1U);
|
||||
atomic_init(&s->lastNext, M_SNAPSH0T_SPMC_INT_FLAG(w, true));
|
||||
|
||||
// Get working buffer
|
||||
s->currentWrite = m_genint_pop(s->freeList);
|
||||
M_ASSERT (s->currentWrite != M_GENINT_ERROR);
|
||||
atomic_store(&s->cptTab[s->currentWrite], 1U);
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
}
|
||||
|
||||
/* Clear m_snapsh0t_mrsw_ct (destructor) */
|
||||
M_INLINE void
|
||||
m_snapsh0t_mrsw_clear(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
M_MEMORY_FREE (s->cptTab);
|
||||
m_genint_clear(s->freeList);
|
||||
s->cptTab = NULL;
|
||||
s->n_reader = 0;
|
||||
}
|
||||
|
||||
/* Return the current index that is written in the buffer */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_get_write_idx(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return s->currentWrite;
|
||||
}
|
||||
|
||||
/* Return the number of readers */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_size(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return (unsigned int) s->n_reader;
|
||||
}
|
||||
|
||||
/* Give the current index that is written to the readers,
|
||||
and return new available index for the writer thread */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_write_idx(m_snapsh0t_mrsw_ct s, unsigned int idx)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
|
||||
// Provide the finalized written buffer to the readers.
|
||||
unsigned int newNext, previous = atomic_load(&s->lastNext);
|
||||
do {
|
||||
newNext = M_SNAPSH0T_SPMC_INT_FLAG(idx, true);
|
||||
} while (!atomic_compare_exchange_weak(&s->lastNext, &previous, newNext));
|
||||
|
||||
if (M_SNAPSH0T_SPMC_INT_FLAG_N(previous)) {
|
||||
// Reuse previous buffer as it was not used by any reader
|
||||
idx = M_SNAPSH0T_SPMC_INT_FLAG_W(previous);
|
||||
// Some other read threads may already have try to reserve this index
|
||||
// So atomic_load(&s->cptTab[idx]) can be greater than 1.
|
||||
// However they will fail to ack it in lastNext,
|
||||
// so they will remove their reservation later
|
||||
} else {
|
||||
// Remove the writer thread counter from the count of the previous buffer
|
||||
idx = M_SNAPSH0T_SPMC_INT_FLAG_W(previous);
|
||||
unsigned int c = atomic_fetch_sub(&s->cptTab[idx], 1U);
|
||||
M_ASSERT (c != 0 && c <= s->n_reader + 1);
|
||||
// Get a new buffer.
|
||||
if (c != 1) {
|
||||
// If someone else keeps a ref on the buffer, we can't reuse it
|
||||
// get another free one.
|
||||
idx = m_genint_pop(s->freeList);
|
||||
M_ASSERT(idx != M_GENINT_ERROR);
|
||||
} else {
|
||||
// No other thread keep track of this buffer.
|
||||
// Reuse it.
|
||||
}
|
||||
M_ASSERT (idx < s->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER);
|
||||
M_ASSERT (atomic_load(&s->cptTab[idx]) == 0);
|
||||
atomic_store(&s->cptTab[idx], 1U);
|
||||
}
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* Perform a swap of the current write buffer and return a new one */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_write(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
s->currentWrite = m_snapsh0t_mrsw_write_idx(s, s->currentWrite);
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return s->currentWrite;
|
||||
}
|
||||
|
||||
/* Start writing to the write buffer and return its index */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_write_start(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
// Get a new buffer.
|
||||
unsigned int idx = m_genint_pop(s->freeList);
|
||||
M_ASSERT (idx != M_GENINT_ERROR);
|
||||
M_ASSERT (idx < s->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER);
|
||||
M_ASSERT (atomic_load(&s->cptTab[idx]) == 0);
|
||||
atomic_store(&s->cptTab[idx], 1U);
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* End writing to the given write buffer */
|
||||
M_INLINE void
|
||||
m_snapsh0t_mrsw_write_end(m_snapsh0t_mrsw_ct s, unsigned int idx)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
|
||||
// Provide this write bufer to the readers
|
||||
unsigned int newNext, previous = atomic_load(&s->lastNext);
|
||||
do {
|
||||
newNext = M_SNAPSH0T_SPMC_INT_FLAG(idx, true);
|
||||
} while (!atomic_compare_exchange_weak(&s->lastNext, &previous, newNext));
|
||||
|
||||
// Free the previous write buffer
|
||||
idx = M_SNAPSH0T_SPMC_INT_FLAG_W(previous);
|
||||
unsigned int c = atomic_fetch_sub(&s->cptTab[idx], 1U);
|
||||
M_ASSERT (c != 0 && c <= s->n_reader + 1);
|
||||
if (c == 1) {
|
||||
m_genint_push(s->freeList, idx);
|
||||
}
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
}
|
||||
|
||||
/* Start reading the latest written buffer and return the index to it */
|
||||
M_INLINE unsigned int
|
||||
m_snapsh0t_mrsw_read_start(m_snapsh0t_mrsw_ct s)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
unsigned int idx, previous;
|
||||
reload:
|
||||
// Load the last published index + Next flag
|
||||
previous = atomic_load(&s->lastNext);
|
||||
while (true) {
|
||||
// Get the last published index
|
||||
idx = M_SNAPSH0T_SPMC_INT_FLAG_W(previous);
|
||||
// Load the number of threads using this index
|
||||
unsigned int c = atomic_load(&s->cptTab[idx]);
|
||||
M_ASSERT (c <= s->n_reader + 1);
|
||||
// Reserve the index if it still being reserved by someone else
|
||||
if (M_UNLIKELY (c == 0
|
||||
|| !atomic_compare_exchange_strong(&s->cptTab[idx], &c, c+1)))
|
||||
goto reload;
|
||||
// Try to ack it
|
||||
unsigned int newNext = M_SNAPSH0T_SPMC_INT_FLAG(idx, false);
|
||||
reforce:
|
||||
if (M_LIKELY (atomic_compare_exchange_strong(&s->lastNext, &previous, newNext)))
|
||||
break;
|
||||
// We have been preempted by another thread
|
||||
if (idx == M_SNAPSH0T_SPMC_INT_FLAG_W(previous)) {
|
||||
// This is still ok if the index has not changed
|
||||
// We can get previous to true again if the writer has recycled the index,
|
||||
// while we reserved it, and the reader get prempted until its CAS.
|
||||
if (M_UNLIKELY (M_SNAPSH0T_SPMC_INT_FLAG_N(previous) == true)) goto reforce;
|
||||
break;
|
||||
}
|
||||
// Free the reserved index as we failed it to ack it
|
||||
c = atomic_fetch_sub(&s->cptTab[idx], 1U);
|
||||
M_ASSERT (c != 0 && c <= s->n_reader + 1);
|
||||
if (c == 1) {
|
||||
m_genint_push(s->freeList, idx);
|
||||
}
|
||||
}
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/* End the reading the given buffer */
|
||||
M_INLINE void
|
||||
m_snapsh0t_mrsw_read_end(m_snapsh0t_mrsw_ct s, unsigned int idx)
|
||||
{
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
M_ASSERT (idx < s->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER);
|
||||
// Decrement reference counter of the buffer
|
||||
unsigned int c = atomic_fetch_sub(&s->cptTab[idx], 1U);
|
||||
M_ASSERT (c != 0 && c <= s->n_reader + 1);
|
||||
if (c == 1) {
|
||||
// Buffer no longer used by any reader thread.
|
||||
// Push back index in free list
|
||||
m_genint_push(s->freeList, idx);
|
||||
}
|
||||
M_SNAPSH0T_SPMC_INT_CONTRACT(s);
|
||||
}
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Contract of a SPMC snapshot.
|
||||
Nothing notable as it can be accessed concurrently */
|
||||
#define M_SNAPSH0T_SPMC_CONTRACT(snap) do { \
|
||||
M_ASSERT (snap != NULL); \
|
||||
M_ASSERT (snap->data != NULL); \
|
||||
} while (0)
|
||||
|
||||
|
||||
// Defered evaluation
|
||||
#define M_SNAPSH0T_SPMC_DEF_P1(arg) M_ID( M_SNAPSH0T_SPMC_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_SNAPSH0T_SPMC_DEF_P2(name, type, oplist, snapshot_t) \
|
||||
M_IF_OPLIST(oplist)(M_SNAPSH0T_SPMC_DEF_P3, M_SNAPSH0T_SPMC_DEF_FAILURE)(name, type, oplist, snapshot_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_SNAPSH0T_SPMC_DEF_FAILURE(name, type, oplist, snapshot_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(SNAPSHOT_SPMC_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Expand the type and the functions of a SPMC snapshot */
|
||||
#define M_SNAPSH0T_SPMC_DEF_P3(name, type, oplist, snapshot_t) \
|
||||
M_SNAPSH0T_SPMC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_SNAPSH0T_SPMC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
|
||||
/* Define the type */
|
||||
#define M_SNAPSH0T_SPMC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
/* Create an aligned type to avoid false sharing between threads */ \
|
||||
typedef struct M_F(name, _aligned_type_s) { \
|
||||
type x; \
|
||||
M_CACHELINE_ALIGN(align, type); \
|
||||
} M_F(name, _aligned_type_ct); \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _aligned_type_ct) *data; \
|
||||
m_snapsh0t_mrsw_ct core; \
|
||||
} snapshot_t[1]; \
|
||||
\
|
||||
/* Define internal types for oplist */ \
|
||||
typedef snapshot_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_SNAPSH0T_SPMC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(snapshot_t snap, size_t nReader) \
|
||||
{ \
|
||||
M_ASSERT (snap != NULL); \
|
||||
M_ASSERT (nReader > 0 && nReader <= M_SNAPSH0T_SPMC_MAX_READER); \
|
||||
snap->data = M_CALL_REALLOC(oplist, M_F(name, _aligned_type_ct), \
|
||||
NULL, nReader+M_SNAPSH0T_SPMC_EXTRA_BUFFER); \
|
||||
if (M_UNLIKELY_NOMEM (snap->data == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof(M_F(name, _aligned_type_ct)) * \
|
||||
(nReader+M_SNAPSH0T_SPMC_EXTRA_BUFFER)); \
|
||||
return; \
|
||||
} \
|
||||
for(size_t i = 0; i < nReader + M_SNAPSH0T_SPMC_EXTRA_BUFFER; i++) { \
|
||||
M_CALL_INIT(oplist, snap->data[i].x); \
|
||||
} \
|
||||
m_snapsh0t_mrsw_init(snap->core, nReader); \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
size_t nReader = m_snapsh0t_mrsw_size(snap->core); \
|
||||
for(size_t i = 0; i < nReader + M_SNAPSH0T_SPMC_EXTRA_BUFFER; i++) { \
|
||||
M_CALL_CLEAR(oplist, snap->data[i].x); \
|
||||
} \
|
||||
M_CALL_FREE(oplist, snap->data); \
|
||||
m_snapsh0t_mrsw_clear(snap->core); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _write)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
const unsigned int idx = m_snapsh0t_mrsw_write(snap->core); \
|
||||
return &snap->data[idx].x; \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _read_start)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
const unsigned int idx = m_snapsh0t_mrsw_read_start(snap->core); \
|
||||
return M_CONST_CAST(type, &snap->data[idx].x); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _read_end)(snapshot_t snap, type const *old) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
M_ASSERT (old != NULL); \
|
||||
const M_F(name, _aligned_type_ct) *oldx; \
|
||||
oldx = M_CTYPE_FROM_FIELD(M_F(name, _aligned_type_ct), old, type, x); \
|
||||
M_ASSERT (oldx >= snap->data); \
|
||||
M_ASSERT (oldx < snap->data + snap->core->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER); \
|
||||
M_ASSERT(snap->core->n_reader +M_SNAPSH0T_SPMC_EXTRA_BUFFER < UINT_MAX); \
|
||||
const unsigned int idx = (unsigned int) (oldx - snap->data); \
|
||||
m_snapsh0t_mrsw_read_end(snap->core, idx); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _get_write_buffer)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap); \
|
||||
const unsigned int idx = m_snapsh0t_mrsw_get_write_idx(snap->core); \
|
||||
return &snap->data[idx].x; \
|
||||
} \
|
||||
\
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
// MPMC is built upon SPMC
|
||||
|
||||
// Defered evaluation
|
||||
#define M_SNAPSH0T_MPMC_DEF_P1(arg) M_ID( M_SNAPSH0T_MPMC_DEF_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_SNAPSH0T_MPMC_DEF_P2(name, type, oplist, snapshot_t) \
|
||||
M_IF_OPLIST(oplist)(M_SNAPSH0T_MPMC_DEF_P3, M_SNAPSH0T_MPMC_DEF_FAILURE)(name, type, oplist, snapshot_t)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_SNAPSH0T_MPMC_DEF_FAILURE(name, type, oplist, snapshot_t) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(SNAPSHOT_MPMC_DEF): the given argument is not a valid oplist: " #oplist)
|
||||
|
||||
/* Expand the type and the functions of a MPMC snapshot */
|
||||
#define M_SNAPSH0T_MPMC_DEF_P3(name, type, oplist, snapshot_t) \
|
||||
M_SNAPSH0T_SPMC_DEF_P1((M_F(name, _mrsw), type, oplist, M_F(name, _mrsw_pct))) \
|
||||
M_SNAPSH0T_MPMC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, 1, type, oplist) \
|
||||
M_SNAPSH0T_MPMC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
|
||||
/* Define the types */
|
||||
#define M_SNAPSH0T_MPMC_DEF_TYPE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_F(name, _mrsw_pct) core; \
|
||||
} snapshot_t[1]; \
|
||||
\
|
||||
/* Define internal types for oplist */ \
|
||||
typedef snapshot_t M_F(name, _ct); \
|
||||
typedef type M_F(name, _subtype_ct); \
|
||||
|
||||
/* Define the core functions */
|
||||
#define M_SNAPSH0T_MPMC_DEF_CORE(name, type, oplist, snapshot_t) \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _init)(snapshot_t snap, size_t nReader, size_t nWriter) \
|
||||
{ \
|
||||
M_F(name, _mrsw_init)(snap->core, nReader + nWriter -1 ); \
|
||||
unsigned int idx = snap->core->core->currentWrite; \
|
||||
snap->core->core->currentWrite = M_GENINT_ERROR; \
|
||||
m_snapsh0t_mrsw_write_end(snap->core->core, idx); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _clear)(snapshot_t snap) \
|
||||
{ \
|
||||
M_F(name, _mrsw_clear)(snap->core); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type * \
|
||||
M_F(name, _write_start)(snapshot_t snap) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap->core); \
|
||||
const unsigned int idx = m_snapsh0t_mrsw_write_start(snap->core->core); \
|
||||
return &snap->core->data[idx].x; \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _write_end)(snapshot_t snap, type *old) \
|
||||
{ \
|
||||
M_SNAPSH0T_SPMC_CONTRACT(snap->core); \
|
||||
const M_F(name, _mrsw_aligned_type_ct) *oldx; \
|
||||
oldx = M_CTYPE_FROM_FIELD(M_F(name, _mrsw_aligned_type_ct), old, type, x); \
|
||||
M_ASSERT (oldx >= snap->core->data); \
|
||||
M_ASSERT (oldx < snap->core->data + snap->core->core->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER); \
|
||||
M_ASSERT(snap->core->core->n_reader + M_SNAPSH0T_SPMC_EXTRA_BUFFER < UINT_MAX); \
|
||||
const unsigned int idx = (unsigned int) (oldx - snap->core->data); \
|
||||
m_snapsh0t_mrsw_write_end(snap->core->core, idx); \
|
||||
} \
|
||||
\
|
||||
M_INLINE type const * \
|
||||
M_F(name, _read_start)(snapshot_t snap) \
|
||||
{ \
|
||||
return M_F(name, _mrsw_read_start)(snap->core); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_F(name, _read_end)(snapshot_t snap, type const *old) \
|
||||
{ \
|
||||
M_F(name, _mrsw_read_end)(snap->core, old); \
|
||||
} \
|
||||
\
|
||||
|
||||
//FIXME: Evaluate the needs for the methods _set_, _init_set.
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define SNAPSHOT_SPSC_DEF M_SNAPSHOT_SPSC_DEF
|
||||
#define SNAPSHOT_SPSC_DEF_AS M_SNAPSHOT_SPSC_DEF_AS
|
||||
#define SNAPSHOT_SPMC_DEF M_SNAPSHOT_SPMC_DEF
|
||||
#define SNAPSHOT_SPMC_DEF_AS M_SNAPSHOT_SPMC_DEF_AS
|
||||
#define SNAPSHOT_MPMC_DEF M_SNAPSHOT_MPMC_DEF
|
||||
#define SNAPSHOT_MPMC_DEF_AS M_SNAPSHOT_MPMC_DEF_AS
|
||||
#define SNAPSHOT_OPLIST M_SNAPSHOT_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,748 +0,0 @@
|
||||
/*
|
||||
* M*LIB - Thin Mutex & Thread wrapper
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_MUTEX_H
|
||||
#define MSTARLIB_MUTEX_H
|
||||
|
||||
/* Auto-detect the thread backend to use if the user has not override it */
|
||||
#ifndef M_USE_THREAD_BACKEND
|
||||
# if defined(INC_FREERTOS_H)
|
||||
# define M_USE_THREAD_BACKEND 4
|
||||
# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \
|
||||
&& !defined(__STDC_NO_THREADS__)
|
||||
# define M_USE_THREAD_BACKEND 1
|
||||
# elif defined(WIN32) || defined(_WIN32) || defined(__CYGWIN__)
|
||||
# define M_USE_THREAD_BACKEND 2
|
||||
# else
|
||||
# define M_USE_THREAD_BACKEND 3
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/****************************** C11 version ********************************/
|
||||
#if M_USE_THREAD_BACKEND == 1
|
||||
|
||||
#include <threads.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include "m-core.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define a mutex type based on C11 definition */
|
||||
typedef mtx_t m_mutex_t[1];
|
||||
|
||||
/* Define a condition variable type based on C11 definition */
|
||||
typedef cnd_t m_cond_t[1];
|
||||
|
||||
/* Define a thread type based on C11 definition */
|
||||
typedef thrd_t m_thread_t[1];
|
||||
|
||||
/* Initialize the mutex (constructor) */
|
||||
M_INLINE void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
int rc = mtx_init(m, mtx_plain);
|
||||
// Abort program in case of initialization failure
|
||||
// There is really nothing else to do if a mutex cannot be constructed
|
||||
M_ASSERT_INIT (rc == thrd_success, "mutex");
|
||||
}
|
||||
|
||||
/* Clear the mutex (destructor) */
|
||||
M_INLINE void m_mutex_clear(m_mutex_t m)
|
||||
{
|
||||
mtx_destroy(m);
|
||||
}
|
||||
|
||||
/* Lock the mutex */
|
||||
M_INLINE void m_mutex_lock(m_mutex_t m)
|
||||
{
|
||||
mtx_lock(m);
|
||||
}
|
||||
|
||||
/* Unlock the mutex */
|
||||
M_INLINE void m_mutex_unlock(m_mutex_t m)
|
||||
{
|
||||
mtx_unlock(m);
|
||||
}
|
||||
|
||||
/* Initialize the condition variable (constructor) */
|
||||
M_INLINE void m_cond_init(m_cond_t c)
|
||||
{
|
||||
int rc = cnd_init(c);
|
||||
// Abort program in case of initialization failure
|
||||
// There is really nothing else to do if the object cannot be constructed
|
||||
M_ASSERT_INIT (rc == thrd_success, "conditional variable");
|
||||
}
|
||||
|
||||
/* Clear the condition variable (destructor) */
|
||||
M_INLINE void m_cond_clear(m_cond_t c)
|
||||
{
|
||||
cnd_destroy(c);
|
||||
}
|
||||
|
||||
/* Signal the condition variable to at least one waiting thread */
|
||||
M_INLINE void m_cond_signal(m_cond_t c)
|
||||
{
|
||||
cnd_signal(c);
|
||||
}
|
||||
|
||||
/* Signal the condition variable to all waiting threads */
|
||||
M_INLINE void m_cond_broadcast(m_cond_t c)
|
||||
{
|
||||
cnd_broadcast(c);
|
||||
}
|
||||
|
||||
/* Wait for signaling the condition variable by another thread */
|
||||
M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
{
|
||||
cnd_wait(c, m);
|
||||
}
|
||||
|
||||
/* Create the thread (constructor) and start it */
|
||||
M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void* arg)
|
||||
{
|
||||
int rc = thrd_create(t, (int(*)(void*))(void(*)(void))func, arg);
|
||||
// Abort program in case of initialization failure
|
||||
M_ASSERT_INIT (rc == thrd_success, "thread");
|
||||
}
|
||||
|
||||
/* Wait for the thread to terminate and destroy it (destructor) */
|
||||
M_INLINE void m_thread_join(m_thread_t t)
|
||||
{
|
||||
int rc = thrd_join(*t, NULL);
|
||||
M_ASSERT (rc == thrd_success);
|
||||
// Avoid warning about variable unused.
|
||||
(void) rc;
|
||||
}
|
||||
|
||||
/* The thread has nothing meaningfull to do.
|
||||
Inform the OS to let other threads be scheduled */
|
||||
M_INLINE void m_thread_yield(void)
|
||||
{
|
||||
thrd_yield();
|
||||
}
|
||||
|
||||
/* Sleep the thread for at least usec microseconds.
|
||||
Return true if the sleep was successful (or we cannot know) */
|
||||
M_INLINE bool m_thread_sleep(unsigned long long usec)
|
||||
{
|
||||
struct timespec tv;
|
||||
tv.tv_sec = (long) (usec / 1000000ULL);
|
||||
tv.tv_nsec = (long) ((usec % 1000000ULL) * 1000UL);
|
||||
int retval = thrd_sleep(&tv, NULL);
|
||||
return retval == 0;
|
||||
}
|
||||
|
||||
// a helper structure for m_once_call
|
||||
typedef once_flag m_once_t[1];
|
||||
|
||||
// Initial value for m_once_t
|
||||
#define M_ONCE_INIT_VALUE { ONCE_FLAG_INIT }
|
||||
|
||||
// Call the function exactly once
|
||||
M_INLINE void m_once_call(m_once_t o, void (*func)(void))
|
||||
{
|
||||
call_once(o,func);
|
||||
}
|
||||
|
||||
// Attribute to use to allocate a global variable to a thread.
|
||||
#define M_THREAD_ATTR _Thread_local
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/****************************** WIN32 version ******************************/
|
||||
#elif M_USE_THREAD_BACKEND == 2
|
||||
|
||||
/* CLANG provides some useless and wrong warnings:
|
||||
* - _WIN32_WINNT starts with '_' which is reserved by the standard
|
||||
* as per the MSVC compiler, it is needed to be defined by the user
|
||||
* to define which version of windows it want to be compatible with.
|
||||
* - windows.h may be different than the case used by the file sytem
|
||||
* there is however no normalized case.
|
||||
*
|
||||
* So, theses warnings have to be ignored and are disabled.
|
||||
*
|
||||
* We cannot add theses warnings in M_BEGIN_PROTECTED_CODE
|
||||
* as they need to be disabled **BEFORE** including any system header
|
||||
* and m-core includes some system headers.
|
||||
* So we need to disable them explictly here.
|
||||
*/
|
||||
#if defined(__clang__) && __clang_major__ >= 4
|
||||
_Pragma("clang diagnostic push")
|
||||
_Pragma("clang diagnostic ignored \"-Wreserved-id-macro\"")
|
||||
_Pragma("clang diagnostic ignored \"-Wnonportable-system-include-path\"")
|
||||
#endif
|
||||
|
||||
/* CriticalSection & ConditionVariable are available from Windows Vista */
|
||||
#ifndef WINVER
|
||||
#define WINVER _WIN32_WINNT_VISTA
|
||||
#endif
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT _WIN32_WINNT_VISTA
|
||||
#endif
|
||||
|
||||
/* Include system headers */
|
||||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include "m-core.h"
|
||||
|
||||
#if defined(__clang__) && __clang_major__ >= 4
|
||||
_Pragma("clang diagnostic pop")
|
||||
#endif
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define a thread type based on WINDOWS definition */
|
||||
typedef HANDLE m_thread_t[1];
|
||||
|
||||
/* Define a mutex type based on WINDOWS definition */
|
||||
typedef CRITICAL_SECTION m_mutex_t[1];
|
||||
|
||||
/* Define a condition variable type based on WINDOWS definition */
|
||||
typedef CONDITION_VARIABLE m_cond_t[1];
|
||||
|
||||
/* Initialize a mutex (Constructor)*/
|
||||
M_INLINE void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
InitializeCriticalSection(m);
|
||||
}
|
||||
|
||||
/* Clear a mutex (destructor) */
|
||||
M_INLINE void m_mutex_clear(m_mutex_t m)
|
||||
{
|
||||
DeleteCriticalSection(m);
|
||||
}
|
||||
|
||||
/* Lock a mutex */
|
||||
M_INLINE void m_mutex_lock(m_mutex_t m)
|
||||
{
|
||||
EnterCriticalSection(m);
|
||||
}
|
||||
|
||||
/* Unlock a mutex */
|
||||
M_INLINE void m_mutex_unlock(m_mutex_t m)
|
||||
{
|
||||
LeaveCriticalSection(m);
|
||||
}
|
||||
|
||||
/* Initialize a condition variable (constructor) */
|
||||
M_INLINE void m_cond_init(m_cond_t c)
|
||||
{
|
||||
InitializeConditionVariable(c);
|
||||
}
|
||||
|
||||
/* Clear a condition variable (destructor) */
|
||||
M_INLINE void m_cond_clear(m_cond_t c)
|
||||
{
|
||||
(void) c; // There is no destructor for this object.
|
||||
}
|
||||
|
||||
/* Signal a condition variable to at least one waiting thread */
|
||||
M_INLINE void m_cond_signal(m_cond_t c)
|
||||
{
|
||||
WakeConditionVariable(c);
|
||||
}
|
||||
|
||||
/* Signal a condition variable to all waiting threads */
|
||||
M_INLINE void m_cond_broadcast(m_cond_t c)
|
||||
{
|
||||
WakeAllConditionVariable(c);
|
||||
}
|
||||
|
||||
/* Wait for a condition variable */
|
||||
M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
{
|
||||
SleepConditionVariableCS(c, m, INFINITE);
|
||||
}
|
||||
|
||||
/* Create a thread (constructor) and start it */
|
||||
M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void *arg)
|
||||
{
|
||||
*t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) (uintptr_t) func, arg, 0, NULL);
|
||||
M_ASSERT_INIT (*t != NULL, "thread");
|
||||
}
|
||||
|
||||
/* Wait for the thread to terminate and destroy it (destructor) */
|
||||
M_INLINE void m_thread_join(m_thread_t t)
|
||||
{
|
||||
DWORD dwWaitResult = WaitForSingleObject(*t, INFINITE);
|
||||
(void) dwWaitResult;
|
||||
M_ASSERT (dwWaitResult == WAIT_OBJECT_0);
|
||||
CloseHandle(*t);
|
||||
}
|
||||
|
||||
/* The thread has nothing meaningfull to do.
|
||||
Inform the OS to let other threads be scheduled */
|
||||
M_INLINE void m_thread_yield(void)
|
||||
{
|
||||
Sleep(0);
|
||||
}
|
||||
|
||||
/* Sleep the thread for at least usec microseconds
|
||||
Return true if the sleep was successful */
|
||||
M_INLINE bool m_thread_sleep(unsigned long long usec)
|
||||
{
|
||||
LARGE_INTEGER ft;
|
||||
M_ASSERT (usec <= LLONG_MAX);
|
||||
ft.QuadPart = -(10LL*(long long) usec);
|
||||
HANDLE hd = CreateWaitableTimer(NULL, TRUE, NULL);
|
||||
M_ASSERT_INIT (hd != NULL, "timer");
|
||||
SetWaitableTimer(hd, &ft, 0, NULL, NULL, 0);
|
||||
DWORD dwWaitResult = WaitForSingleObject(hd, INFINITE);
|
||||
CloseHandle(hd);
|
||||
return dwWaitResult == WAIT_OBJECT_0;
|
||||
}
|
||||
|
||||
|
||||
typedef INIT_ONCE m_once_t[1];
|
||||
#define M_ONCE_INIT_VALUE { INIT_ONCE_STATIC_INIT }
|
||||
M_INLINE BOOL CALLBACK m_once_callback( PINIT_ONCE InitOnce, PVOID Parameter, PVOID *lpContext)
|
||||
{
|
||||
void (*func)(void);
|
||||
(void) InitOnce;
|
||||
(void) lpContext;
|
||||
func = (void (*)(void))(uintptr_t) Parameter;
|
||||
(*func)();
|
||||
return TRUE;
|
||||
}
|
||||
M_INLINE void m_once_call(m_once_t o, void (*func)(void))
|
||||
{
|
||||
InitOnceExecuteOnce(o, m_once_callback, (void*)(intptr_t)func, NULL);
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// Attribute to use to allocate a global variable to a thread (MSVC def).
|
||||
# define M_THREAD_ATTR __declspec( thread )
|
||||
#else
|
||||
// Attribute to use to allocate a global variable to a thread (GCC def).
|
||||
# define M_THREAD_ATTR __thread
|
||||
#endif
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/**************************** PTHREAD version ******************************/
|
||||
#elif M_USE_THREAD_BACKEND == 3
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef _POSIX_PRIORITY_SCHEDULING
|
||||
#include <sched.h>
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include "m-core.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Define a mutex type based on PTHREAD definition */
|
||||
typedef pthread_mutex_t m_mutex_t[1];
|
||||
|
||||
/* Define a condition variable type based on PTHREAD definition */
|
||||
typedef pthread_cond_t m_cond_t[1];
|
||||
|
||||
/* Define a thread type based on PTHREAD definition */
|
||||
typedef pthread_t m_thread_t[1];
|
||||
|
||||
/* Initialize the mutex (constructor) */
|
||||
M_INLINE void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
int _rc = pthread_mutex_init(m, NULL);
|
||||
// Abort program in case of initialization failure
|
||||
// There is really nothing else to do if a mutex cannot be constructed
|
||||
M_ASSERT_INIT (_rc == 0, "mutex");
|
||||
}
|
||||
|
||||
/* Clear the mutex (destructor) */
|
||||
M_INLINE void m_mutex_clear(m_mutex_t m)
|
||||
{
|
||||
pthread_mutex_destroy(m);
|
||||
}
|
||||
|
||||
/* Lock the mutex */
|
||||
M_INLINE void m_mutex_lock(m_mutex_t m)
|
||||
{
|
||||
pthread_mutex_lock(m);
|
||||
}
|
||||
|
||||
/* Unlock the mutex */
|
||||
M_INLINE void m_mutex_unlock(m_mutex_t m)
|
||||
{
|
||||
pthread_mutex_unlock(m);
|
||||
}
|
||||
|
||||
/* Lazy lock initialization */
|
||||
#define M_MUTEXI_INIT_VALUE { PTHREAD_MUTEX_INITIALIZER }
|
||||
|
||||
/* Internal function compatible with lazy lock */
|
||||
M_INLINE void m_mutexi_lazy_lock(m_mutex_t m)
|
||||
{
|
||||
pthread_mutex_lock(m);
|
||||
}
|
||||
|
||||
/* Initialize the condition variable (constructor) */
|
||||
M_INLINE void m_cond_init(m_cond_t c)
|
||||
{
|
||||
int _rc = pthread_cond_init(c, NULL);
|
||||
// Abort program in case of initialization failure
|
||||
// There is really nothing else to do if a mutex cannot be constructed
|
||||
M_ASSERT_INIT (_rc == 0, "conditional variable");
|
||||
}
|
||||
|
||||
/* Clear the condition variable (destructor) */
|
||||
M_INLINE void m_cond_clear(m_cond_t c)
|
||||
{
|
||||
pthread_cond_destroy(c);
|
||||
}
|
||||
|
||||
/* Signal a condition variable to at least a waiting thread */
|
||||
M_INLINE void m_cond_signal(m_cond_t c)
|
||||
{
|
||||
pthread_cond_signal(c);
|
||||
}
|
||||
|
||||
/* Signal a condition variable to all waiting threads */
|
||||
M_INLINE void m_cond_broadcast(m_cond_t c)
|
||||
{
|
||||
pthread_cond_broadcast(c);
|
||||
}
|
||||
|
||||
/* Waiting for a condition variable */
|
||||
M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
{
|
||||
pthread_cond_wait(c, m);
|
||||
}
|
||||
|
||||
/* Create a thread (constructor) and start it */
|
||||
M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void *arg)
|
||||
{
|
||||
int _rc = pthread_create(t, NULL, (void*(*)(void*))(void(*)(void))func, arg);
|
||||
M_ASSERT_INIT (_rc == 0, "thread");
|
||||
}
|
||||
|
||||
/* Wait for the thread to terminate and destroy it (destructor) */
|
||||
M_INLINE void m_thread_join(m_thread_t t)
|
||||
{
|
||||
int _rc = pthread_join(*t, NULL);
|
||||
(void)_rc; // Avoid warning about variable unused.
|
||||
M_ASSERT (_rc == 0);
|
||||
}
|
||||
|
||||
/* The thread has nothing meaningfull to do.
|
||||
Inform the OS to let other threads be scheduled */
|
||||
M_INLINE void m_thread_yield(void)
|
||||
{
|
||||
#ifdef _POSIX_PRIORITY_SCHEDULING
|
||||
sched_yield();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Sleep for at least usec microseconds
|
||||
Return true if the sleep was successful */
|
||||
M_INLINE bool m_thread_sleep(unsigned long long usec)
|
||||
{
|
||||
struct timeval tv;
|
||||
/* We don't want to use usleep or nanosleep so that
|
||||
we remain compatible with strict C99 build */
|
||||
tv.tv_sec = (time_t) (usec / 1000000ULL);
|
||||
tv.tv_usec = (suseconds_t) (usec % 1000000ULL);
|
||||
int retval = select(1, NULL, NULL, NULL, &tv);
|
||||
return retval == 0;
|
||||
}
|
||||
|
||||
typedef pthread_once_t m_once_t[1];
|
||||
#define M_ONCE_INIT_VALUE { PTHREAD_ONCE_INIT }
|
||||
M_INLINE void m_once_call(m_once_t o, void (*func)(void))
|
||||
{
|
||||
pthread_once(o,func);
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# define M_THREAD_ATTR __thread
|
||||
#else
|
||||
# define M_THREAD_ATTR /* Not supported */
|
||||
#endif
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/****************************** FreeRTOS version ********************************/
|
||||
#elif M_USE_THREAD_BACKEND == 4
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <semphr.h>
|
||||
#include <task.h>
|
||||
#include "m-core.h"
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Default value for the stack */
|
||||
#ifndef M_USE_TASK_STACK_SIZE
|
||||
#define M_USE_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
|
||||
#endif
|
||||
|
||||
/* Default value for the priority tasks */
|
||||
#ifndef M_USE_TASK_PRIORITY
|
||||
#define M_USE_TASK_PRIORITY ( tskIDLE_PRIORITY )
|
||||
#endif
|
||||
|
||||
/* Define a mutex type based on FreeRTOS definition */
|
||||
typedef struct m_mutex_s {
|
||||
SemaphoreHandle_t handle;
|
||||
StaticSemaphore_t MutexBuffer;
|
||||
} m_mutex_t[1];
|
||||
|
||||
/* Define a thread type based on FreeRTOS definition */
|
||||
typedef struct m_cond_s {
|
||||
SemaphoreHandle_t handle;
|
||||
StaticSemaphore_t SemBuffer;
|
||||
unsigned int NumThreadWaiting;
|
||||
} m_cond_t[1];
|
||||
|
||||
/* Define a thread type based on FreeRTOS definition */
|
||||
typedef struct m_thread_s {
|
||||
SemaphoreHandle_t SemHandle;
|
||||
StaticSemaphore_t SemBuffer;
|
||||
TaskHandle_t TaskHandle;
|
||||
StaticTask_t TaskBuffer;
|
||||
void (*EntryPoint)(void *);
|
||||
void* ArgsEntryPoint;
|
||||
StackType_t* StackBuffer;
|
||||
} m_thread_t[1];
|
||||
|
||||
/* Initialize the mutex (constructor) */
|
||||
M_INLINE void m_mutex_init(m_mutex_t m)
|
||||
{
|
||||
/* Create a mutex semaphore without using any dynamic allocation */
|
||||
m->handle = xSemaphoreCreateMutexStatic(&m->MutexBuffer);
|
||||
// It cannot fail, so we won't use M_ASSERT_INIT
|
||||
M_ASSERT(m->handle);
|
||||
}
|
||||
|
||||
/* Clear the mutex (destructor) */
|
||||
M_INLINE void m_mutex_clear(m_mutex_t m)
|
||||
{
|
||||
vSemaphoreDelete(m->handle);
|
||||
}
|
||||
|
||||
/* Lock the mutex */
|
||||
M_INLINE void m_mutex_lock(m_mutex_t m)
|
||||
{
|
||||
xSemaphoreTake(m->handle, portMAX_DELAY);
|
||||
}
|
||||
|
||||
/* Unlock the mutex */
|
||||
M_INLINE void m_mutex_unlock(m_mutex_t m)
|
||||
{
|
||||
xSemaphoreGive(m->handle);
|
||||
}
|
||||
|
||||
|
||||
/* Initialize the condition variable (constructor) */
|
||||
M_INLINE void m_cond_init(m_cond_t c)
|
||||
{
|
||||
c->NumThreadWaiting = 0;
|
||||
// Create a semaphore to implement the conditional variable
|
||||
// Initial value is 0 and valid range is <= 0
|
||||
c->handle = xSemaphoreCreateCountingStatic( INT_MAX, 0, &c->SemBuffer );
|
||||
// It cannot fail, so we won't use M_ASSERT_INIT
|
||||
M_ASSERT(c->handle);
|
||||
}
|
||||
|
||||
/* Clear the condition variable (destructor) */
|
||||
M_INLINE void m_cond_clear(m_cond_t c)
|
||||
{
|
||||
vSemaphoreDelete(c->handle);
|
||||
}
|
||||
|
||||
/* Signal the condition variable to at least one waiting thread */
|
||||
M_INLINE void m_cond_signal(m_cond_t c)
|
||||
{
|
||||
// This function is called within the mutex lock
|
||||
// NumThreadWaiting doesn't need to be atomic
|
||||
if (c->NumThreadWaiting > 0) {
|
||||
// Wakeup one thread by posting on the semaphore
|
||||
xSemaphoreGive(c->handle);
|
||||
} // Otherwise there is no waiting thread, so nothing to signal
|
||||
}
|
||||
|
||||
/* Signal the condition variable to all waiting threads */
|
||||
M_INLINE void m_cond_broadcast(m_cond_t c)
|
||||
{
|
||||
// This function is called within the mutex lock
|
||||
// NumThreadWaiting doesn't need to be atomic
|
||||
if (c->NumThreadWaiting > 0) {
|
||||
// Wakeup all thread by posting on the semaphore
|
||||
// as many times as there are waiting threads
|
||||
for(unsigned i = 0; i < c->NumThreadWaiting; i++) {
|
||||
xSemaphoreGive(c->handle);
|
||||
}
|
||||
} // Otherwise there is no waiting thread, so nothing to signal
|
||||
}
|
||||
|
||||
/* Wait for signaling the condition variable by another thread */
|
||||
M_INLINE void m_cond_wait(m_cond_t c, m_mutex_t m)
|
||||
{
|
||||
// This function is called within the mutex lock
|
||||
// Increment the number of waiting thread
|
||||
c->NumThreadWaiting ++;
|
||||
m_mutex_unlock(m);
|
||||
// Wait for post in the semaphore
|
||||
xSemaphoreTake(c->handle, portMAX_DELAY);
|
||||
m_mutex_lock(m);
|
||||
c->NumThreadWaiting --;
|
||||
}
|
||||
|
||||
M_INLINE void m_thr3ad_wrapper( void *args)
|
||||
{
|
||||
struct m_thread_s *thread_ptr = args;
|
||||
thread_ptr->EntryPoint(thread_ptr->ArgsEntryPoint);
|
||||
// Give back the semaphore.
|
||||
xSemaphoreGive(thread_ptr->SemHandle);
|
||||
// Wait for destruction
|
||||
while (true) { vTaskSuspend(NULL); }
|
||||
}
|
||||
|
||||
/* Create the thread (constructor) and start it */
|
||||
M_INLINE void m_thread_create(m_thread_t t, void (*func)(void*), void* arg)
|
||||
{
|
||||
// Create a semaphore to implement the final wait
|
||||
t->SemHandle = xSemaphoreCreateCountingStatic( 1, 0, &t->SemBuffer );
|
||||
M_ASSERT(t->SemHandle);
|
||||
// Save the argument to the thread
|
||||
t->EntryPoint = func;
|
||||
t->ArgsEntryPoint = arg;
|
||||
|
||||
// Allocate the stack
|
||||
t->StackBuffer = pvPortMalloc( sizeof (StackType_t) * M_USE_TASK_STACK_SIZE);
|
||||
M_ASSERT_INIT(t->StackBuffer, "STACK");
|
||||
|
||||
// Create the task without using any dynamic allocation
|
||||
t->TaskHandle = xTaskCreateStatic(m_thr3ad_wrapper, "M*LIB", M_USE_TASK_STACK_SIZE, (void*) t, M_USE_TASK_PRIORITY, t->StackBuffer, &t->TaskBuffer);
|
||||
// It cannot fail, so we won't use M_ASSERT_INIT
|
||||
M_ASSERT(t->TaskHandle);
|
||||
}
|
||||
|
||||
/* Wait for the thread to terminate and destroy it (destructor) */
|
||||
M_INLINE void m_thread_join(m_thread_t t)
|
||||
{
|
||||
xSemaphoreTake(t->SemHandle, portMAX_DELAY);
|
||||
vTaskDelete(t->TaskHandle);
|
||||
vPortFree(t->StackBuffer);
|
||||
vSemaphoreDelete(t->SemHandle);
|
||||
t->TaskHandle = 0;
|
||||
t->StackBuffer = 0;
|
||||
t->SemHandle = 0;
|
||||
}
|
||||
|
||||
/* The thread has nothing meaningfull to do.
|
||||
Inform the OS to let other threads be scheduled */
|
||||
M_INLINE void m_thread_yield(void)
|
||||
{
|
||||
taskYIELD();
|
||||
}
|
||||
|
||||
/* Sleep the thread for at least usec microseconds.
|
||||
Return true if the sleep was successful */
|
||||
M_INLINE bool m_thread_sleep(unsigned long long usec)
|
||||
{
|
||||
TickType_t delay = (TickType_t) (usec / portTICK_PERIOD_MS / 1000ULL);
|
||||
vTaskDelay(delay);
|
||||
return true;
|
||||
}
|
||||
|
||||
// a helper structure for m_once_call
|
||||
typedef struct {
|
||||
atomic_int count;
|
||||
} m_once_t[1];
|
||||
|
||||
// Initial value for m_once_t
|
||||
#define M_ONCE_INIT_VALUE { { M_ATOMIC_VAR_INIT(0) } }
|
||||
|
||||
// Call the function exactly once
|
||||
M_INLINE void m_once_call(m_once_t o, void (*func)(void))
|
||||
{
|
||||
if (atomic_load(&o->count) != 2) {
|
||||
int n = 0;
|
||||
if (atomic_compare_exchange_strong( &o->count, &n, 1)) {
|
||||
// First thread success
|
||||
func();
|
||||
atomic_store(&o->count, 2);
|
||||
}
|
||||
// Wait for function call (FIXME: priority inversion possible?)
|
||||
while (atomic_load(&o->count) != 2) { m_thread_yield(); }
|
||||
} // Already called. Nothing to do
|
||||
}
|
||||
|
||||
// Attribute to use to allocate a global variable to a thread.
|
||||
#define M_THREAD_ATTR __thread
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/******************************** INVALID VALUE **********************************/
|
||||
|
||||
#else
|
||||
# error Value of M_USE_THREAD_BACKEND is incorrect. Please see the documentation for valid usage.
|
||||
#endif
|
||||
|
||||
// TODO: Obsolete M_LOCK macro.
|
||||
|
||||
/* M_LOCK macro. Allow simple locking encapsulation.
|
||||
USAGE:
|
||||
static M_LOCK_DECL(name);
|
||||
int f(int n) {
|
||||
M_LOCK(name) {
|
||||
// Exclusive access
|
||||
}
|
||||
}
|
||||
*/
|
||||
/* NOTE: Either using direct support by the OS (WIN32/PTHREAD)
|
||||
or using C11's ONCE mechanism */
|
||||
#ifdef M_MUTEXI_INIT_VALUE
|
||||
# define M_LOCK_DECL(name) m_mutex_t name = M_MUTEXI_INIT_VALUE
|
||||
# define M_LOCK(name) \
|
||||
M_LOCKI_DO(name, M_C(local_cont_, __LINE__), m_mutexi_lazy_lock, m_mutex_unlock)
|
||||
#else
|
||||
# define M_LOCK_DECL(name) \
|
||||
m_mutex_t name; \
|
||||
static void M_C(m_mutex_init_, name)(void) { \
|
||||
m_mutex_init(name); \
|
||||
} \
|
||||
m_once_t M_C(m_once_, name) = M_ONCE_INIT_VALUE
|
||||
# define M_LOCKI_BY_ONCE(name) \
|
||||
(m_once_call(M_C(m_once_, name), M_C(m_mutex_init_, name)), \
|
||||
m_mutex_lock(name), (void) 0 )
|
||||
# define M_LOCK(name) \
|
||||
M_LOCKI_DO(name, M_C(local_cont_, __LINE__), M_LOCKI_BY_ONCE, m_mutex_unlock)
|
||||
#endif
|
||||
|
||||
#define M_LOCKI_DO(name, cont, lock_func, unlock_func) \
|
||||
for(bool cont = true \
|
||||
; cont && (lock_func (name), true); \
|
||||
(unlock_func (name), cont = false))
|
||||
|
||||
#endif
|
||||
-1603
File diff suppressed because it is too large
Load Diff
@@ -1,578 +0,0 @@
|
||||
/*
|
||||
* M*LIB - try / catch mechanism for M*LIB
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_TRY_H
|
||||
#define MSTARLIB_TRY_H
|
||||
|
||||
#include "m-core.h"
|
||||
#include "m-thread.h"
|
||||
|
||||
/*
|
||||
* Select mechanism to use for support of RAII and exception,
|
||||
* so that for each variable defined using M_LET,
|
||||
* its destructor is still called when exceptions are thrown.
|
||||
* It is either the C++ try,
|
||||
* or it uses a GCC or CLANG extension,
|
||||
* or the standard C compliant way (much slower).
|
||||
* The user can override the desired mechanism.
|
||||
*/
|
||||
#ifndef M_USE_TRY_MECHANISM
|
||||
# if defined(__has_extension)
|
||||
# if __has_extension(blocks)
|
||||
# define M_TRY_CLANG_BLOCKS
|
||||
# endif
|
||||
# endif
|
||||
# if defined(__cplusplus)
|
||||
# define M_USE_TRY_MECHANISM 1
|
||||
# elif defined(M_TRY_CLANG_BLOCKS)
|
||||
# define M_USE_TRY_MECHANISM 2
|
||||
# elif defined(__GNUC__) && !defined(__clang__)
|
||||
# define M_USE_TRY_MECHANISM 3
|
||||
# else
|
||||
# define M_USE_TRY_MECHANISM 4
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Start a protected section of code 'name' where all exceptions are catched
|
||||
* by the associated CATCH section.
|
||||
*/
|
||||
#define M_TRY(name) \
|
||||
M_TRY_B( M_C(m_try_bool_, name), M_C(m_try_buf_, name), name)
|
||||
|
||||
|
||||
/*
|
||||
* Catch an exception associated to the TRY block 'name' that matches the given error_code
|
||||
* If error_code is 0, it catches all error codes.
|
||||
* error code shall be a constant positive integer.
|
||||
*/
|
||||
#define M_CATCH(name, error_code) M_CATCH_B(name, error_code)
|
||||
|
||||
|
||||
/*
|
||||
* Throw an exception to the upper try block
|
||||
* error_code shall be the first argument.
|
||||
* Other arguments are integers or pointers stored in the exception.
|
||||
* error code shall be a constant positive integer.
|
||||
* There is no genericity of the exception data structure itself.
|
||||
*/
|
||||
#define M_THROW(...) do { \
|
||||
M_STATIC_ASSERT(M_RET_ARG1 (__VA_ARGS__) != 0, \
|
||||
M_LIB_NOT_A_CONSTANT_NON_NULL_INTEGER, \
|
||||
"The error code shall be a non null positive constant"); \
|
||||
M_STATIC_ASSERT(M_NARGS (__VA_ARGS__) <= 1+M_USE_MAX_CONTEXT, \
|
||||
M_LIB_TOO_MANY_ARGUMENTS, \
|
||||
"There are too many arguments for an exception."); \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)(M_THROW_1, M_THROW_N)(__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
|
||||
/*
|
||||
* Size of the context data that are stored in an exception data structure.
|
||||
*/
|
||||
#ifndef M_USE_MAX_CONTEXT
|
||||
#define M_USE_MAX_CONTEXT 10
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The exception itself.
|
||||
*
|
||||
* It is POD data where every fields can be used by the user.
|
||||
* It has been decided to have only one exception data structure
|
||||
* to simplify error code and because :
|
||||
* - using generic types is much harder in C to do (still possible)
|
||||
* - it will make exceptions more usable for errors which should not
|
||||
* be handled by exceptions.
|
||||
*
|
||||
* For C++, we need to encapsulate it in a template,
|
||||
* so that it can be a unique type for each error code,
|
||||
* which is needed for the catch mechanism.
|
||||
* We all need to override the operator -> since the C++
|
||||
* throw the type and catch the type, whereas the C back-end
|
||||
* throw the type and catch a pointer to the type:
|
||||
* within the catch block you are supposed to use the arrow
|
||||
* operator to test the content of the exception.
|
||||
*/
|
||||
#if M_USE_TRY_MECHANISM == 1
|
||||
namespace m_lib {
|
||||
template <unsigned int N>
|
||||
#endif
|
||||
struct m_exception_s {
|
||||
unsigned error_code; // Error code
|
||||
unsigned short line; // Line number where the error was detected
|
||||
unsigned short num; // Number of entries in 'context' table
|
||||
const char *filename; // filename where the error was detected
|
||||
intptr_t context[M_USE_MAX_CONTEXT]; // Specific context of the exception
|
||||
#ifdef __cplusplus
|
||||
m_exception_s<N> *operator->() { return this; }
|
||||
#endif
|
||||
};
|
||||
#if M_USE_TRY_MECHANISM == 1
|
||||
}
|
||||
#endif
|
||||
|
||||
// Typical Error codes (TODO: add more classic?)
|
||||
#define M_ERROR_MEMORY 1
|
||||
#define M_ERROR_ACCESS 2
|
||||
#define M_ERROR_BUSY 3
|
||||
|
||||
/*
|
||||
* Define all global needed by the try mechanism with a
|
||||
* thread attribute. It needs to be defined once in all the program
|
||||
*/
|
||||
#define M_TRY_DEF_ONCE() M_TRY_DEF_ONCE_B()
|
||||
|
||||
|
||||
/*
|
||||
* Re-throw the last exception
|
||||
* It shall be done in a CATCH block.
|
||||
*/
|
||||
#define M_RETHROW() m_rethrow()
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/*
|
||||
* Define the C++ back-end.
|
||||
* It is fully different from C back-end as it reuses the classic try of the C++.
|
||||
* Surprisingly it has more constraints than the C one.
|
||||
* error_code shall be a positive, constant integer.
|
||||
* the catch all block shall always be the last block.
|
||||
* at least catch block is mandatory for each try block.
|
||||
* Note that theses constraints are meaningless in real code,
|
||||
* and simply good behavior.
|
||||
* Notice also that you won't have any access to the exception for a catch all error.
|
||||
*/
|
||||
#if M_USE_TRY_MECHANISM == 1
|
||||
|
||||
// Define the CATCH block. If error_code is 0, it shall catch all errors.
|
||||
// NOTE: It will even catch non M*LIB errors.
|
||||
#define M_CATCH_B(name, error_code) \
|
||||
M_IF(M_BOOL(error_code)) \
|
||||
(catch (m_lib::m_exception_s<error_code> &name), catch (...))
|
||||
|
||||
// No global to define in C++
|
||||
#define M_TRY_DEF_ONCE_B() /* Nothing to do */
|
||||
|
||||
// Reuse the try keyword of the C++
|
||||
#define M_TRY_B(cont, buf, exception) \
|
||||
try
|
||||
|
||||
// Reuse the throw keyword of the C++
|
||||
// by throwing the type m_lib::m_exception_s<error_code>
|
||||
#define M_THROW_1(error_code) \
|
||||
throw m_lib::m_exception_s<error_code>{ error_code, __LINE__, 0, __FILE__, { 0 } }
|
||||
|
||||
// Reuse the throw keyword of the C++
|
||||
// by throwing the type m_lib::m_exception_s<error_code>
|
||||
#define M_THROW_N(error_code, ...) \
|
||||
throw m_lib::m_exception_s<error_code>{ error_code, __LINE__, \
|
||||
M_NARGS(__VA_ARGS__), __FILE__, { __VA_ARGS__ } }
|
||||
|
||||
// Nothing to inject for a pre initialization of a M*LIB object
|
||||
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) /* Nothing to do */
|
||||
|
||||
// Code to inject for a post initialization of a M*LIB object
|
||||
// We create a C++ object with a destructor that will call the CLEAR operator of the M*LIB object
|
||||
// by using a lambda function.
|
||||
// If the CLEAR operator is called naturally, we disable the destructor of the C++ object.
|
||||
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
||||
for(m_lib::m_regclear M_C(m_try_regclear_, name){[&](void) { M_CALL_CLEAR(oplist, name); } } \
|
||||
; cont ; M_C(m_try_regclear_, name).disable() )
|
||||
|
||||
// M_DEFER Injection / pre initialization
|
||||
#define M_DEFER_TRY_INJECT_PRE_B(cont, ...) /* Nothing to do */
|
||||
|
||||
// M_DEFER Injection / post initialization
|
||||
// Register the stack frame and tests for the longjmp.
|
||||
// In which case call the 'clear' operations (...), unstack the error list and rethrow the error.
|
||||
#define M_DEFER_TRY_INJECT_POST_B(cont, ...) \
|
||||
for(m_lib::m_regclear M_C(m_try_regclear_, cont){[&](void) { __VA_ARGS__; } } \
|
||||
; cont ; M_C(m_try_regclear_, cont).disable() )
|
||||
|
||||
// Definition of the C++ object wrapper
|
||||
// The registered function is called by the destructor,
|
||||
// except if the disable function has been called.
|
||||
#include <functional>
|
||||
namespace m_lib {
|
||||
class m_regclear {
|
||||
std::function<void(void)> function;
|
||||
bool done;
|
||||
public:
|
||||
inline m_regclear(const std::function<void(void)> &f) : function{f}, done{false} { }
|
||||
inline void disable(void) { done = true; }
|
||||
inline ~m_regclear() { if (done == false) { function(); done = true; } }
|
||||
};
|
||||
}
|
||||
|
||||
// Rethrow is simply throw without any argument
|
||||
#define m_rethrow() throw
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* The C back-end.
|
||||
* It is fully different from the C++ back-end and is based on setjmp/lonjmp
|
||||
* (classic implementation).
|
||||
* The main difficulty is the mechanism to register the CLEAR operators
|
||||
* to call when throwing an exception.
|
||||
* Contrary to the C++ back-end, it is not cost-free as it adds some
|
||||
* instructions to the normal behavior of the program.
|
||||
*/
|
||||
#else
|
||||
|
||||
#if (M_USE_TRY_MECHANISM == 3)
|
||||
// Use of builtin setjmp / longjmp for GCC
|
||||
// There are at least twice faster at worst, and reduce stack consumption
|
||||
// See https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.html
|
||||
// CLANG doesn't support these builtins officialy (https://groups.google.com/g/llvm-dev/c/9QgfdW23K8M)
|
||||
#define m_try_setjmp(x) __builtin_setjmp(x)
|
||||
#define m_try_longjmp(x,v) __builtin_longjmp(x, v)
|
||||
typedef intptr_t m_try_jmp_buf[5];
|
||||
#define m_try_jmp_buf m_try_jmp_buf
|
||||
#else
|
||||
// C compliant setjmp
|
||||
#include <setjmp.h>
|
||||
#define m_try_setjmp(x) setjmp(x)
|
||||
#define m_try_longjmp(x,v) longjmp(x, v)
|
||||
#define m_try_jmp_buf jmp_buf
|
||||
#endif
|
||||
|
||||
// Define the CATCH block associated to the 'name' TRY to catch the exception
|
||||
// associated to 'error_code' and provide 'name' as a pointer to the exception
|
||||
// if the exception matches the error code.
|
||||
// If error code is 0, it matches all errors.
|
||||
#define M_CATCH_B(name, error_code) \
|
||||
else if (m_catch( M_C(m_try_buf_, name), (error_code), &name))
|
||||
|
||||
// Define the operator to define nested functions (GCC) or blocks (CLANG)
|
||||
#if M_USE_TRY_MECHANISM == 2
|
||||
# define M_TRY_FUNC_OPERATOR ^
|
||||
#else
|
||||
# define M_TRY_FUNC_OPERATOR *
|
||||
#endif
|
||||
|
||||
// Define the linked structure used to identify what is present in the C stack.
|
||||
// We create for each M_TRY and each M_LET a new node in the stack that represents
|
||||
// this point in the stack frame. Each nodes are linked together, so that we can
|
||||
// analyze the stack frame on exception.
|
||||
typedef struct m_try_s {
|
||||
enum { M_STATE_TRY, M_STATE_EXCEPTION_IN_PROGRESS, M_STATE_EXCEPTION_CATCHED,
|
||||
M_STATE_CLEAR_JMPBUF, M_STATE_CLEAR_CB } kind;
|
||||
struct m_try_s *next;
|
||||
union {
|
||||
m_try_jmp_buf buf;
|
||||
struct { void (M_TRY_FUNC_OPERATOR func)(void*); void *data; } clear;
|
||||
} data;
|
||||
} m_try_t[1];
|
||||
|
||||
// Define the TRY block.
|
||||
// Classic usage of the for trick to push destructor on the exit path.
|
||||
#define M_TRY_B(cont, buf, exception) \
|
||||
for(bool cont = true ; cont ; cont = false) \
|
||||
for(m_try_t buf ; cont ; m_try_clear(buf), cont = false ) \
|
||||
for(const struct m_exception_s *exception = NULL; cont; cont = false, exception = exception) \
|
||||
if (m_try_init(buf))
|
||||
|
||||
// Throw the error code
|
||||
#define M_THROW_1(error_code) \
|
||||
m_throw( &(const struct m_exception_s) { error_code, __LINE__, 0, __FILE__, { 0 } } )
|
||||
|
||||
// Throw the error code
|
||||
#define M_THROW_N(error_code, ...) \
|
||||
m_throw( &(const struct m_exception_s) { error_code, __LINE__, M_NARGS(__VA_ARGS__), __FILE__, \
|
||||
{ __VA_ARGS__ } } )
|
||||
|
||||
// Copy an exception to another.
|
||||
M_INLINE void
|
||||
m_exception_set(struct m_exception_s *out, const struct m_exception_s *in)
|
||||
{
|
||||
if (in != out) {
|
||||
memcpy(out, in, sizeof *out);
|
||||
}
|
||||
}
|
||||
|
||||
// The global thread attribute variables and functions.
|
||||
extern M_THREAD_ATTR struct m_try_s *m_global_error_list;
|
||||
extern M_THREAD_ATTR struct m_exception_s m_global_exception;
|
||||
extern M_ATTR_NO_RETURN M_ATTR_COLD_FUNCTION void m_throw(const struct m_exception_s *exception);
|
||||
|
||||
// Macro to add once in one source file to define theses global:
|
||||
#define M_TRY_DEF_ONCE_B() \
|
||||
M_THREAD_ATTR struct m_try_s *m_global_error_list; \
|
||||
M_THREAD_ATTR struct m_exception_s m_global_exception; \
|
||||
\
|
||||
/* Throw the given exception \
|
||||
This function should be rarely called. */ \
|
||||
M_ATTR_NO_RETURN M_ATTR_COLD_FUNCTION void \
|
||||
m_throw(const struct m_exception_s *exception) \
|
||||
{ \
|
||||
/* Analyze the error list to see what has been registered */ \
|
||||
struct m_try_s *e = m_global_error_list; \
|
||||
while (e != NULL) { \
|
||||
/* A CLEAR operator has been registered: call it */ \
|
||||
if (e->kind == M_STATE_CLEAR_CB) { \
|
||||
e->data.clear.func(e->data.clear.data); \
|
||||
} \
|
||||
else { \
|
||||
/* A JUMP command has been registered. \
|
||||
* Either due to the M_TRY block or \
|
||||
* because of the jump to the CLEAR operator of the object to clear. */ \
|
||||
M_ASSERT(e->kind == M_STATE_TRY || e->kind == M_STATE_CLEAR_JMPBUF); \
|
||||
/* If the exception is already m_global_exception, it won't be copied */ \
|
||||
m_exception_set(&m_global_exception, exception); \
|
||||
e->kind = M_STATE_EXCEPTION_IN_PROGRESS; \
|
||||
m_global_error_list = e; \
|
||||
m_try_longjmp(e->data.buf, 1); \
|
||||
} \
|
||||
/* Next stack frame */ \
|
||||
e = e->next; \
|
||||
} \
|
||||
/* No exception found. \
|
||||
Display the information and halt program . */ \
|
||||
M_RAISE_FATAL("Exception '%u' raised by (%s:%d) is not catched. Program aborted.\n", \
|
||||
exception->error_code, exception->filename, exception->line); \
|
||||
}
|
||||
|
||||
// Rethrow the error
|
||||
M_INLINE void
|
||||
m_rethrow(void)
|
||||
{
|
||||
M_ASSERT(m_global_error_list != NULL);
|
||||
m_throw(&m_global_exception);
|
||||
}
|
||||
|
||||
// Catch the error code associated to the TRY block state
|
||||
// and provide a pointer to the exception (which is a global).
|
||||
M_INLINE bool
|
||||
m_catch(m_try_t state, unsigned error_code, const struct m_exception_s **exception)
|
||||
{
|
||||
M_ASSERT(m_global_error_list == state);
|
||||
M_ASSERT(state->kind == M_STATE_EXCEPTION_IN_PROGRESS);
|
||||
*exception = &m_global_exception;
|
||||
if (error_code != 0 && m_global_exception.error_code != error_code)
|
||||
return false;
|
||||
// The exception has been catched.
|
||||
state->kind = M_STATE_EXCEPTION_CATCHED;
|
||||
// Unstack the try block, so that next throw command in the CATCH block
|
||||
// will reach the upper TRY block.
|
||||
m_global_error_list = state->next;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialize the state to a TRY state.
|
||||
M_INLINE void
|
||||
m_try_init(m_try_t state)
|
||||
{
|
||||
state->kind = M_STATE_TRY;
|
||||
state->next = m_global_error_list;
|
||||
m_global_error_list = state;
|
||||
// setjmp needs to be done in the MACRO.
|
||||
}
|
||||
#define m_try_init(s) \
|
||||
M_LIKELY ((m_try_init(s), m_try_setjmp(((s)->data.buf)) != 1))
|
||||
|
||||
// Disable the current TRY block.
|
||||
M_INLINE void
|
||||
m_try_clear(m_try_t state)
|
||||
{
|
||||
// Even if there is a CATCH block and an unstack of the exception
|
||||
// m_global_error_list won't be changed.
|
||||
m_global_error_list = state->next;
|
||||
if (M_UNLIKELY (state->kind == M_STATE_EXCEPTION_IN_PROGRESS)) {
|
||||
// There was no catch for this error.
|
||||
// Forward it to the upper level.
|
||||
m_rethrow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Implement the M_LET injection macros, so that the CLEAR operator is called on exception
|
||||
// Helper functions
|
||||
// Each mechanisme provide 3 helper functions:
|
||||
// * pre: which is called before the constructor
|
||||
// * post: which is called after the constructor
|
||||
// * final: which is called before the destructor.
|
||||
|
||||
// We register a call to the CLEAR callback.
|
||||
// We don't modify m_global_error_list until we have successfully called the INIT operator
|
||||
// to avoid registering the CLEAR operator on exception whereas the object is not initialized yet.
|
||||
// However we register the position in the stack frame now so that in case of partial initialization
|
||||
// of the object (if the INIT operator of the object calls other INIT operators of composed fields),
|
||||
// since partial initialization will be unstacked naturally by the composing object.
|
||||
M_INLINE bool
|
||||
m_try_cb_pre(m_try_t state)
|
||||
{
|
||||
state->kind = M_STATE_CLEAR_CB;
|
||||
state->next = m_global_error_list;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We register the function to call of the initialized object.
|
||||
M_INLINE bool
|
||||
m_try_cb_post(m_try_t state, void (M_TRY_FUNC_OPERATOR func)(void*), void *data)
|
||||
{
|
||||
state->data.clear.func = func;
|
||||
state->data.clear.data = data;
|
||||
m_global_error_list = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The object will be cleared.
|
||||
// We can pop the stack frame of the errors.
|
||||
M_INLINE void
|
||||
m_try_cb_final(m_try_t state)
|
||||
{
|
||||
m_global_error_list = state->next;
|
||||
}
|
||||
|
||||
// Pre initialization function. Save the stack frame for a longjmp
|
||||
M_INLINE bool
|
||||
m_try_jump_pre(m_try_t state)
|
||||
{
|
||||
state->kind = M_STATE_CLEAR_JMPBUF;
|
||||
state->next = m_global_error_list;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Post initialization function. Register the stack frame for a longjmp
|
||||
M_INLINE void
|
||||
m_try_jump_post(m_try_t state)
|
||||
{
|
||||
m_global_error_list = state;
|
||||
}
|
||||
// And call setjmp to register the position in the code.
|
||||
#define m_try_jump_post(s) \
|
||||
M_LIKELY ((m_try_jump_post(s), m_try_setjmp(((s)->data.buf)) != 1))
|
||||
|
||||
// The object will be cleared.
|
||||
// We can pop the stack frame of the errors.
|
||||
M_INLINE void
|
||||
m_try_jump_final(m_try_t state)
|
||||
{
|
||||
m_global_error_list = state->next;
|
||||
}
|
||||
|
||||
|
||||
// Implement the M_LET injection macros, so that the CLEAR operator is called on exception
|
||||
//
|
||||
#if M_USE_TRY_MECHANISM == 1
|
||||
# error M*LIB: Internal error. C++ back-end requested within C implementation.
|
||||
|
||||
#elif M_USE_TRY_MECHANISM == 2
|
||||
// Use of CLANG blocks
|
||||
|
||||
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \
|
||||
for(m_try_t M_C(m_try_state_, name); cont && \
|
||||
m_try_cb_pre(M_C(m_try_state_, name) ); )
|
||||
|
||||
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
||||
for(m_try_cb_post(M_C(m_try_state_, name), \
|
||||
^ void (void *_data) { M_GET_TYPE oplist *_t = _data; M_CALL_CLEAR(oplist, *_t); }, \
|
||||
(void*) &name); cont; m_try_cb_final(M_C(m_try_state_, name)) )
|
||||
|
||||
#elif M_USE_TRY_MECHANISM == 3
|
||||
// Use of GCC nested functions.
|
||||
|
||||
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \
|
||||
for(m_try_t M_C(m_try_state_, name); cont && \
|
||||
m_try_cb_pre(M_C(m_try_state_, name) ); )
|
||||
|
||||
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
||||
for(m_try_cb_post(M_C(m_try_state_, name), \
|
||||
__extension__ ({ __extension__ void _callback (void *_data) { M_GET_TYPE oplist *_t = _data; M_CALL_CLEAR(oplist, *_t); } _callback; }), \
|
||||
(void*) &name); cont; m_try_cb_final(M_C(m_try_state_, name)) )
|
||||
|
||||
#elif M_USE_TRY_MECHANISM == 4
|
||||
// STD C compliant (without compiler extension): use of setjmp
|
||||
// This is the basic implementation in case of compiler unknown.
|
||||
// It uses setjmp/longjmp, and as such, is much slower than
|
||||
// other implementations.
|
||||
|
||||
// M_LET Injection / pre initialization
|
||||
// Initialize the stack frame.
|
||||
#define M_LET_TRY_INJECT_PRE_B(cont, oplist, name) \
|
||||
for(m_try_t M_C(m_try_state_, name); cont && \
|
||||
m_try_jump_pre(M_C(m_try_state_, name)); )
|
||||
|
||||
// M_LET Injection / post initialization
|
||||
// Register the stack frame and tests for the longjmp.
|
||||
// In which case call the CLEAR operator, unstack the error list and rethrow the error.
|
||||
#define M_LET_TRY_INJECT_POST_B(cont, oplist, name) \
|
||||
for( ; cont ; m_try_jump_final(M_C(m_try_state_, name))) \
|
||||
if (m_try_jump_post(M_C(m_try_state_, name)) \
|
||||
|| (M_CALL_CLEAR(oplist, name), m_try_jump_final(M_C(m_try_state_, name)), m_rethrow(), false))
|
||||
|
||||
#else
|
||||
# error M*LIB: Invalid value for M_USE_TRY_MECHANISM [1..4]
|
||||
#endif
|
||||
|
||||
|
||||
// M_DEFER Injection / pre initialization
|
||||
// Initialize the stack frame.
|
||||
#define M_DEFER_TRY_INJECT_PRE_B(cont, ...) \
|
||||
for(m_try_t M_C(m_try_state_, cont); cont && \
|
||||
m_try_jump_pre(M_C(m_try_state_, cont)); )
|
||||
|
||||
// M_DEFER Injection / post initialization
|
||||
// Register the stack frame and tests for the longjmp.
|
||||
// In which case call the CLEAR operator, unstack the error list and rethrow the error.
|
||||
#define M_DEFER_TRY_INJECT_POST_B(cont, ...) \
|
||||
for( ; cont ; m_try_jump_final(M_C(m_try_state_, cont))) \
|
||||
if (m_try_jump_post(M_C(m_try_state_, cont)) \
|
||||
|| (__VA_ARGS__ , m_try_jump_final(M_C(m_try_state_, cont)), m_rethrow(), false))
|
||||
|
||||
#endif /* cplusplus */
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
// Macro injection for M_LET.
|
||||
// If the oplist defined NOCLEAR property, we won't register this variable for clear on exception
|
||||
#undef M_LET_TRY_INJECT_PRE
|
||||
#define M_LET_TRY_INJECT_PRE(cont, oplist, name) \
|
||||
M_IF(M_GET_PROPERTY(oplist, NOCLEAR))(M_EAT, M_LET_TRY_INJECT_PRE_B) \
|
||||
(cont, oplist, name)
|
||||
|
||||
#undef M_LET_TRY_INJECT_POST
|
||||
#define M_LET_TRY_INJECT_POST(cont, oplist, name) \
|
||||
M_IF(M_GET_PROPERTY(oplist, NOCLEAR))(M_EAT, M_LET_TRY_INJECT_POST_B) \
|
||||
(cont, oplist, name)
|
||||
|
||||
|
||||
// Macro injection for M_DEFER.
|
||||
#undef M_DEFER_TRY_INJECT_PRE
|
||||
#define M_DEFER_TRY_INJECT_PRE(cont, ...) M_DEFER_TRY_INJECT_PRE_B(cont, __VA_ARGS__)
|
||||
#undef M_DEFER_TRY_INJECT_POST
|
||||
#define M_DEFER_TRY_INJECT_POST(cont, ...) M_DEFER_TRY_INJECT_POST_B(cont, __VA_ARGS__)
|
||||
|
||||
|
||||
// In case of MEMORY FULL errors, throw an error instead of aborting.
|
||||
#undef M_MEMORY_FULL
|
||||
#define M_MEMORY_FULL(size) M_THROW(M_ERROR_MEMORY, (intptr_t)(size))
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,784 +0,0 @@
|
||||
/*
|
||||
* M*LIB - TUPLE module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_TUPLE_H
|
||||
#define MSTARLIB_TUPLE_H
|
||||
|
||||
#include "m-core.h"
|
||||
|
||||
/* Define the tuple type and functions.
|
||||
USAGE:
|
||||
TUPLE_DEF2(name, [(field1, type1[, oplist1]), (field2, type2[, oplist2]), ...] ) */
|
||||
#define M_TUPLE_DEF2(name, ...) \
|
||||
M_TUPLE_DEF2_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define the tuple type and functions
|
||||
as the given name.
|
||||
USAGE:
|
||||
TUPLE_DEF2_AS(name, name_t, [(field1, type1[, oplist1]), (field2, type2[, oplist2]), ...] ) */
|
||||
#define M_TUPLE_DEF2_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_TUPL3_DEF2_P1( (name, name_t M_TUPL3_INJECT_GLOBAL(__VA_ARGS__)) ) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a tuple.
|
||||
USAGE: TUPLE_OPLIST(name[, oplist of the first type, ...]) */
|
||||
#define M_TUPLE_OPLIST(...) \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
(M_TUPL3_OPLIST_P1((__VA_ARGS__, M_BASIC_OPLIST )), \
|
||||
M_TUPL3_OPLIST_P1((__VA_ARGS__ )))
|
||||
|
||||
|
||||
/* Return an array suitable for the WIP _cmp_order function.
|
||||
As compound literals are not supported in C++,
|
||||
provide a separate definition for C++ using initializer_list
|
||||
(shall be constexpr, but only supported in C++14).
|
||||
*/
|
||||
#ifndef __cplusplus
|
||||
#define M_TUPLE_ORDER(name, ...) \
|
||||
( (const int[]) {M_MAP2_C(M_TUPL3_ORDER_CONVERT, name, __VA_ARGS__), 0})
|
||||
#else
|
||||
#include <initializer_list>
|
||||
namespace m_lib {
|
||||
template <unsigned int N>
|
||||
struct m_tupl3_integer_va {
|
||||
int data[N];
|
||||
/*constexpr*/ inline m_tupl3_integer_va(std::initializer_list<int> init){
|
||||
int j = 0;
|
||||
for(auto i:init) {
|
||||
data[j++] = i;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#define M_TUPLE_ORDER(name, ...) \
|
||||
(m_lib::m_tupl3_integer_va<M_NARGS(__VA_ARGS__,0)>({M_MAP2_C(M_TUPL3_ORDER_CONVERT, name, __VA_ARGS__), 0}).data)
|
||||
#endif
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Contract of a tuple. Nothing notable */
|
||||
#define M_TUPL3_CONTRACT(tup) do { \
|
||||
M_ASSERT(tup != NULL); \
|
||||
} while (0)
|
||||
|
||||
/* Inject the oplist within the list of arguments */
|
||||
#define M_TUPL3_INJECT_GLOBAL(...) \
|
||||
M_MAP(M_TUPL3_INJECT_OPLIST_A, __VA_ARGS__)
|
||||
|
||||
/* Transform (x, type) into (x, type, oplist) if there is global registered oplist
|
||||
or (x, type, M_BASIC_OPLIST) if there is no global one,
|
||||
or keep (x, type, oplist) if oplist was already present */
|
||||
#define M_TUPL3_INJECT_OPLIST_A( duo_or_trio ) \
|
||||
M_TUPL3_INJECT_OPLIST_B duo_or_trio
|
||||
|
||||
#define M_TUPL3_INJECT_OPLIST_B( f, ... ) \
|
||||
M_DEFERRED_COMMA \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)( (f, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)()), (f, __VA_ARGS__) )
|
||||
|
||||
// Deferred evaluation
|
||||
#define M_TUPL3_DEF2_P1(...) M_ID( M_TUPL3_DEF2_P2 __VA_ARGS__ )
|
||||
|
||||
// Test if all third argument of all arguments is an oplist
|
||||
#define M_TUPL3_IF_ALL_OPLIST(...) \
|
||||
M_IF(M_REDUCE(M_TUPL3_IS_OPLIST_P, M_AND, __VA_ARGS__))
|
||||
|
||||
// Test if the third argument of (name, type, oplist) is an oplist
|
||||
#define M_TUPL3_IS_OPLIST_P(a) \
|
||||
M_OPLIST_P(M_RET_ARG3 a)
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_TUPL3_DEF2_P2(name, name_t, ...) \
|
||||
M_TUPL3_IF_ALL_OPLIST(__VA_ARGS__)(M_TUPL3_DEF2_P3, M_TUPL3_DEF2_FAILURE)(name, name_t, __VA_ARGS__)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_TUPL3_DEF2_FAILURE(name, name_t, ...) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(TUPLE_DEF2): at least one of the given argument is not a valid oplist: " #__VA_ARGS__)
|
||||
|
||||
/* Define the tuple */
|
||||
#define M_TUPL3_DEF2_P3(name, name_t, ...) \
|
||||
M_TUPL3_DEFINE_TYPE(name, name_t, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_ENUM(name, __VA_ARGS__) \
|
||||
M_TUPL3_CONTROL_ALL_OPLIST(name, __VA_ARGS__) \
|
||||
M_TUPL3_IF_ALL(INIT, __VA_ARGS__)(M_TUPL3_DEFINE_INIT(name, __VA_ARGS__),) \
|
||||
M_TUPL3_DEFINE_INIT_SET(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_INIT_SET2(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_SET(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_SET2(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_CLEAR(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_GETTER_FIELD(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_SETTER_FIELD(name, __VA_ARGS__) \
|
||||
M_TUPL3_DEFINE_EMPLACE_FIELD(name, __VA_ARGS__) \
|
||||
M_TUPL3_IF_ONE(CMP, __VA_ARGS__)(M_TUPL3_DEFINE_CMP(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(CMP, __VA_ARGS__)(M_TUPL3_DEFINE_CMP_ORDER(name, __VA_ARGS__),) \
|
||||
M_TUPL3_DEFINE_CMP_FIELD(name, __VA_ARGS__) \
|
||||
M_TUPL3_IF_ONE(HASH, __VA_ARGS__)(M_TUPL3_DEFINE_HASH(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ONE(EQUAL, __VA_ARGS__)(M_TUPL3_DEFINE_EQUAL(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(GET_STR, __VA_ARGS__)(M_TUPL3_DEFINE_GET_STR(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(OUT_STR, __VA_ARGS__)(M_TUPL3_DEFINE_OUT_STR(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(IN_STR, __VA_ARGS__)(M_TUPL3_DEFINE_IN_STR(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(PARSE_STR, __VA_ARGS__)(M_TUPL3_DEFINE_PARSE_STR(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(OUT_SERIAL, __VA_ARGS__)(M_TUPL3_DEFINE_OUT_SERIAL(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(IN_SERIAL, __VA_ARGS__)(M_TUPL3_DEFINE_IN_SERIAL(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(INIT_MOVE, __VA_ARGS__)(M_TUPL3_DEFINE_INIT_MOVE(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(MOVE, __VA_ARGS__)(M_TUPL3_DEFINE_MOVE(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(SWAP, __VA_ARGS__)(M_TUPL3_DEFINE_SWAP(name, __VA_ARGS__),) \
|
||||
M_TUPL3_IF_ALL(RESET, __VA_ARGS__)(M_TUPL3_DEFINE_RESET(name, __VA_ARGS__),)
|
||||
|
||||
/* Provide order for _cmp_order */
|
||||
#define M_TUPL3_ORDER_CONVERT(name, x) M_F(name, M_C(M_TUPL3_ORDER_CONVERT_, x))
|
||||
#define M_TUPL3_ORDER_CONVERT_ASC(x) M_C3(_,x,_value)
|
||||
#define M_TUPL3_ORDER_CONVERT_DSC(x) M_C3(_,x,_value)*-1
|
||||
|
||||
/* Get the field name, the type, the oplist or the methods
|
||||
based on the tuple (field, type, oplist) */
|
||||
#define M_TUPL3_GET_FIELD(f,t,o) f
|
||||
#define M_TUPL3_GET_TYPE(f,t,o) t
|
||||
#define M_TUPL3_GET_OPLIST(f,t,o) o
|
||||
#define M_TUPL3_GET_INIT(f,t,o) M_GET_INIT o
|
||||
#define M_TUPL3_GET_INIT_SET(f,t,o) M_GET_INIT_SET o
|
||||
#define M_TUPL3_GET_INIT_MOVE(f,t,o) M_GET_INIT_MOVE o
|
||||
#define M_TUPL3_GET_MOVE(f,t,o) M_GET_MOVE o
|
||||
#define M_TUPL3_GET_SET(f,t,o) M_GET_SET o
|
||||
#define M_TUPL3_GET_CLEAR(f,t,o) M_GET_CLEAR o
|
||||
#define M_TUPL3_GET_CMP(f,t,o) M_GET_CMP o
|
||||
#define M_TUPL3_GET_HASH(f,t,o) M_GET_HASH o
|
||||
#define M_TUPL3_GET_EQUAL(f,t,o) M_GET_EQUAL o
|
||||
#define M_TUPL3_GET_STR(f,t,o) M_GET_GET_STR o
|
||||
#define M_TUPL3_GET_OUT_STR(f,t,o) M_GET_OUT_STR o
|
||||
#define M_TUPL3_GET_IN_STR(f,t,o) M_GET_IN_STR o
|
||||
#define M_TUPL3_GET_OUT_SERIAL(f,t,o) M_GET_OUT_SERIAL o
|
||||
#define M_TUPL3_GET_IN_SERIAL(f,t,o) M_GET_IN_SERIAL o
|
||||
#define M_TUPL3_GET_PARSE_STR(f,t,o) M_GET_PARSE_STR o
|
||||
#define M_TUPL3_GET_SWAP(f,t,o) M_GET_SWAP o
|
||||
#define M_TUPL3_GET_RESET(f,t,o) M_GET_RESET o
|
||||
|
||||
/* Call the method associated to the given operator for the given parameter
|
||||
of the tuple t=(name, type, oplist) */
|
||||
#define M_TUPL3_CALL_INIT(t, ...) M_APPLY_API(M_TUPL3_GET_INIT t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_INIT_SET(t, ...) M_APPLY_API(M_TUPL3_GET_INIT_SET t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_INIT_MOVE(t, ...) M_APPLY_API(M_TUPL3_GET_INIT_MOVE t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_MOVE(t, ...) M_APPLY_API(M_TUPL3_GET_MOVE t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_SET(t, ...) M_APPLY_API(M_TUPL3_GET_SET t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_CLEAR(t, ...) M_APPLY_API(M_TUPL3_GET_CLEAR t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_CMP(t, ...) M_APPLY_API(M_TUPL3_GET_CMP t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_HASH(t, ...) M_APPLY_API(M_TUPL3_GET_HASH t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_EQUAL(t, ...) M_APPLY_API(M_TUPL3_GET_EQUAL t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_GET_STR(t, ...) M_APPLY_API(M_TUPL3_GET_STR t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_OUT_STR(t, ...) M_APPLY_API(M_TUPL3_GET_OUT_STR t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_IN_STR(t, ...) M_APPLY_API(M_TUPL3_GET_IN_STR t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_PARSE_STR(t, ...) M_APPLY_API(M_TUPL3_GET_PARSE_STR t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_OUT_SERIAL(t, ...) M_APPLY_API(M_TUPL3_GET_OUT_SERIAL t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_IN_SERIAL(t, ...) M_APPLY_API(M_TUPL3_GET_IN_SERIAL t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_SWAP(t, ...) M_APPLY_API(M_TUPL3_GET_SWAP t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_TUPL3_CALL_RESET(t, ...) M_APPLY_API(M_TUPL3_GET_RESET t, M_TUPL3_GET_OPLIST t, __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define the type of a tuple */
|
||||
#define M_TUPL3_DEFINE_TYPE(name, name_t, ...) \
|
||||
typedef struct M_F(name, _s) { \
|
||||
M_MAP(M_TUPL3_DEFINE_RECUR_TYPE_ELE , __VA_ARGS__) \
|
||||
} name_t[1]; \
|
||||
\
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
/* Define internal type for oplist */ \
|
||||
typedef name_t M_F(name, _ct); \
|
||||
/* Save constant as the number of arguments (internal) */ \
|
||||
typedef enum { \
|
||||
M_C3(m_tupl3_, name, _num_args) = M_NARGS(__VA_ARGS__) \
|
||||
} M_C3(m_tupl3_, name, _num_args_ct); \
|
||||
/* Save alias for the types of arguments */ \
|
||||
M_MAP3(M_TUPL3_DEFINE_TYPE_ELE, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_DEFINE_TYPE_ELE(name, num, a) \
|
||||
typedef M_TUPL3_GET_TYPE a M_C4(name, _type_, num, _ct);
|
||||
|
||||
#define M_TUPL3_DEFINE_RECUR_TYPE_ELE(a) \
|
||||
M_TUPL3_GET_TYPE a M_TUPL3_GET_FIELD a ;
|
||||
|
||||
/* Define the basic enumerate, identifying a parameter */
|
||||
#define M_TUPL3_DEFINE_ENUM(name, ...) \
|
||||
typedef enum { \
|
||||
M_F(name, _first_one_val), \
|
||||
M_MAP2_C(M_TUPL3_DEFINE_ENUM_ELE , name, __VA_ARGS__) \
|
||||
} M_F(name,_field_e);
|
||||
|
||||
#define M_TUPL3_DEFINE_ENUM_ELE(name, a) \
|
||||
M_C4(name, _, M_TUPL3_GET_FIELD a, _value)
|
||||
|
||||
/* Control that all given oplists of all parameters are really oplists */
|
||||
#define M_TUPL3_CONTROL_ALL_OPLIST(name, ...) \
|
||||
M_MAP2(M_TUPL3_CONTROL_OPLIST, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_CONTROL_OPLIST(name, a) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, M_TUPL3_GET_FIELD a, \
|
||||
M_TUPL3_GET_TYPE a, M_TUPL3_GET_OPLIST a)
|
||||
|
||||
/* Define the INIT method calling the INIT method for all params */
|
||||
#define M_TUPL3_DEFINE_INIT(name, ...) \
|
||||
M_INLINE void M_F(name, _init)(M_F(name,_ct) my) { \
|
||||
M_MAP(M_TUPL3_DEFINE_INIT_FUNC , __VA_ARGS__) {} \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_INIT_FUNC(a) \
|
||||
M_CHAIN_OBJ(M_TUPL3_GET_FIELD a, M_TUPL3_GET_OPLIST a, my -> M_TUPL3_GET_FIELD a)
|
||||
|
||||
/* Define the INIT_SET method calling the INIT_SET method for all params */
|
||||
#define M_TUPL3_DEFINE_INIT_SET(name, ...) \
|
||||
M_INLINE void M_F(name, _init_set)(M_F(name,_ct) my , M_F(name,_ct) const org) { \
|
||||
M_TUPL3_CONTRACT(org); \
|
||||
M_MAP(M_TUPL3_DEFINE_INIT_SET_FUNC , __VA_ARGS__) {} \
|
||||
}
|
||||
#define M_TUPL3_DEFINE_INIT_SET_FUNC(a) \
|
||||
M_CHAIN_OBJ(M_TUPL3_GET_FIELD a, M_TUPL3_GET_OPLIST a, \
|
||||
my -> M_TUPL3_GET_FIELD a , org -> M_TUPL3_GET_FIELD a )
|
||||
|
||||
/* Define the INIT_WITH method calling the INIT_SET method for all params. */
|
||||
#define M_TUPL3_DEFINE_INIT_SET2(name, ...) \
|
||||
M_INLINE void M_F(name, _init_emplace)(M_F(name,_ct) my \
|
||||
M_MAP(M_TUPL3_DEFINE_INIT_SET2_PROTO, __VA_ARGS__) \
|
||||
) { \
|
||||
M_MAP(M_TUPL3_DEFINE_INIT_SET2_FUNC , __VA_ARGS__) {} \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_INIT_SET2_PROTO(a) \
|
||||
, M_TUPL3_GET_TYPE a const M_TUPL3_GET_FIELD a
|
||||
|
||||
#define M_TUPL3_DEFINE_INIT_SET2_FUNC(a) \
|
||||
M_CHAIN_OBJ(M_TUPL3_GET_FIELD a, M_TUPL3_GET_OPLIST a, \
|
||||
my -> M_TUPL3_GET_FIELD a , M_TUPL3_GET_FIELD a )
|
||||
|
||||
|
||||
/* Define the SET method calling the SET method for all params. */
|
||||
#define M_TUPL3_DEFINE_SET(name, ...) \
|
||||
M_INLINE void M_F(name, _set)(M_F(name,_ct) my , \
|
||||
M_F(name,_ct) const org) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
M_TUPL3_CONTRACT(org); \
|
||||
M_MAP(M_TUPL3_DEFINE_SET_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_SET_FUNC(a) \
|
||||
M_TUPL3_CALL_SET(a, my -> M_TUPL3_GET_FIELD a , org -> M_TUPL3_GET_FIELD a );
|
||||
|
||||
|
||||
/* Define the SET_WITH method calling the SET method for all params. */
|
||||
#define M_TUPL3_DEFINE_SET2(name, ...) \
|
||||
M_INLINE void M_F(name, _emplace)(M_F(name,_ct) my \
|
||||
M_MAP(M_TUPL3_DEFINE_SET2_PROTO, __VA_ARGS__) \
|
||||
) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
M_MAP(M_TUPL3_DEFINE_SET2_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
#define M_TUPL3_DEFINE_SET2_PROTO(a) \
|
||||
, M_TUPL3_GET_TYPE a const M_TUPL3_GET_FIELD a
|
||||
|
||||
#define M_TUPL3_DEFINE_SET2_FUNC(a) \
|
||||
M_TUPL3_CALL_SET(a, my -> M_TUPL3_GET_FIELD a , M_TUPL3_GET_FIELD a );
|
||||
|
||||
|
||||
/* Define the CLEAR method calling the CLEAR method for all params. */
|
||||
#define M_TUPL3_DEFINE_CLEAR(name, ...) \
|
||||
M_INLINE void M_F(name, _clear)(M_F(name,_ct) my) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
M_MAP(M_TUPL3_DEFINE_CLEAR_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_CLEAR_FUNC(a) \
|
||||
M_TUPL3_CALL_CLEAR(a, my -> M_TUPL3_GET_FIELD a );
|
||||
|
||||
|
||||
/* Define the GET_AT_field & CGET_AT methods for all params. */
|
||||
#define M_TUPL3_DEFINE_GETTER_FIELD(name, ...) \
|
||||
M_MAP3(M_TUPL3_DEFINE_GETTER_FIELD_PROTO, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_DEFINE_GETTER_FIELD_PROTO(name, num, a) \
|
||||
M_INLINE M_TUPL3_GET_TYPE a * M_C3(name, _get_at_, M_TUPL3_GET_FIELD a) \
|
||||
(M_F(name,_ct) my) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
return &(my->M_TUPL3_GET_FIELD a); \
|
||||
} \
|
||||
M_INLINE M_TUPL3_GET_TYPE a const * M_C3(name, _cget_at_, M_TUPL3_GET_FIELD a) \
|
||||
(M_F(name,_ct) const my) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
return &(my->M_TUPL3_GET_FIELD a); \
|
||||
} \
|
||||
/* Same but uses numerical index for accessing the field (internal) */ \
|
||||
M_INLINE M_TUPL3_GET_TYPE a * M_C4(m_tupl3_, name, _get_at_, num) \
|
||||
(M_F(name,_ct) my) { \
|
||||
return &(my->M_TUPL3_GET_FIELD a); \
|
||||
} \
|
||||
|
||||
|
||||
/* Define the SET_field methods for all params. */
|
||||
#define M_TUPL3_DEFINE_SETTER_FIELD(name, ...) \
|
||||
M_MAP2(M_TUPL3_DEFINE_SETTER_FIELD_PROTO, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_DEFINE_SETTER_FIELD_PROTO(name, a) \
|
||||
M_INLINE void M_C3(name, _set_, M_TUPL3_GET_FIELD a) \
|
||||
(M_F(name,_ct) my, M_TUPL3_GET_TYPE a const M_TUPL3_GET_FIELD a) { \
|
||||
M_TUPL3_CONTRACT(my); \
|
||||
M_TUPL3_CALL_SET(a, my ->M_TUPL3_GET_FIELD a, M_TUPL3_GET_FIELD a); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the EMPLACE_field methods for all params. */
|
||||
#define M_TUPL3_DEFINE_EMPLACE_FIELD(name, ...) \
|
||||
M_REDUCE3(M_TUPL3_DEFINE_EMPLACE_FIELD_PROTO, M_TUPL3_DEFINE_EMPLACE_G, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_DEFINE_EMPLACE_G(a, b) a b
|
||||
|
||||
#define M_TUPL3_DEFINE_EMPLACE_FIELD_PROTO(name, id, a) \
|
||||
M_EMPLACE_QUEUE_DEF(M_TUPL3_GET_FIELD a, M_F(name, _ct), M_C3(name, _emplace_, M_TUPL3_GET_FIELD a), M_TUPL3_GET_OPLIST a, M_TUPL3_EMPLACE_DEF)
|
||||
|
||||
#define M_TUPL3_EMPLACE_DEF(name, name_t, function_name, oplist, init_func, exp_emplace_type) \
|
||||
M_INLINE void \
|
||||
function_name(name_t v \
|
||||
M_EMPLACE_LIST_TYPE_VAR(a, exp_emplace_type) ) \
|
||||
{ \
|
||||
M_CALL_CLEAR(oplist, v->id); \
|
||||
M_EMPLACE_CALL_FUNC(a, init_func, oplist, v->id, exp_emplace_type); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the CMP method by calling CMP methods for all params. */
|
||||
#define M_TUPL3_DEFINE_CMP(name, ...) \
|
||||
M_INLINE int M_F(name, _cmp)(M_F(name,_ct) const e1 , \
|
||||
M_F(name,_ct) const e2) { \
|
||||
int i; \
|
||||
M_TUPL3_CONTRACT(e1); \
|
||||
M_TUPL3_CONTRACT(e2); \
|
||||
M_MAP(M_TUPL3_DEFINE_CMP_FUNC_P0, __VA_ARGS__) \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_CMP_FUNC_P0(a) \
|
||||
M_IF(M_TUPL3_TEST_METHOD_P(CMP, a))(M_TUPL3_DEFINE_CMP_FUNC_P1, M_EAT)(a)
|
||||
#define M_TUPL3_DEFINE_CMP_FUNC_P1(a) \
|
||||
i = M_TUPL3_CALL_CMP(a, e1 -> M_TUPL3_GET_FIELD a , e2 -> M_TUPL3_GET_FIELD a ); \
|
||||
if (i != 0) return i;
|
||||
|
||||
/* Define the CMP_ORDER method by calling CMP methods for all params
|
||||
In the right order
|
||||
FIXME: _cmp_order is not supported by algorithm yet.
|
||||
FIXME: All oplists shall define the CMP operator or at least one?
|
||||
*/
|
||||
#define M_TUPL3_DEFINE_CMP_ORDER(name, ...) \
|
||||
M_INLINE int M_F(name, _cmp_order)(M_F(name,_ct) const e1 , \
|
||||
M_F(name,_ct) const e2, \
|
||||
const int order[]) { \
|
||||
int i, r; \
|
||||
M_TUPL3_CONTRACT(e1); \
|
||||
M_TUPL3_CONTRACT(e2); \
|
||||
while (true) { \
|
||||
i=*order++; \
|
||||
switch (i) { \
|
||||
case 0: return 0; \
|
||||
M_MAP2(M_TUPL3_DEFINE_CMP_ORDER_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(0); \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_CMP_ORDER_FUNC(name, a) \
|
||||
case M_C4(name, _, M_TUPL3_GET_FIELD a, _value): \
|
||||
case -M_C4(name, _, M_TUPL3_GET_FIELD a, _value): \
|
||||
r = M_TUPL3_CALL_CMP(a, e1 -> M_TUPL3_GET_FIELD a , e2 -> M_TUPL3_GET_FIELD a ); \
|
||||
if (r != 0) return i < 0 ? -r : r; \
|
||||
break;
|
||||
|
||||
|
||||
/* Define a CMP_field method for all given params that export a CMP method */
|
||||
#define M_TUPL3_DEFINE_CMP_FIELD(name, ...) \
|
||||
M_MAP2(M_TUPL3_MAP_CMP_FIELD, name, __VA_ARGS__)
|
||||
|
||||
#define M_TUPL3_MAP_CMP_FIELD(name, a) \
|
||||
M_IF_METHOD(CMP, M_TUPL3_GET_OPLIST a)( \
|
||||
M_TUPL3_DEFINE_CMP_FIELD_FUNC(name, M_TUPL3_GET_FIELD a, M_TUPL3_GET_CMP a, M_TUPL3_GET_OPLIST a), \
|
||||
)
|
||||
|
||||
#define M_TUPL3_DEFINE_CMP_FIELD_FUNC(name, field, func_cmp, oplist) \
|
||||
M_INLINE int M_C3(name, _cmp_, field)(M_F(name,_ct) const e1 , \
|
||||
M_F(name,_ct) const e2) { \
|
||||
M_TUPL3_CONTRACT(e1); \
|
||||
M_TUPL3_CONTRACT(e2); \
|
||||
return M_APPLY_API(func_cmp, oplist, e1 -> field , e2 -> field ); \
|
||||
}
|
||||
|
||||
|
||||
/* Define a EQUAL method by calling the EQUAL methods for all params */
|
||||
#define M_TUPL3_DEFINE_EQUAL(name, ...) \
|
||||
M_INLINE bool M_F(name, _equal_p)(M_F(name,_ct) const e1 , \
|
||||
M_F(name,_ct) const e2) { \
|
||||
bool b; \
|
||||
M_TUPL3_CONTRACT(e1); \
|
||||
M_TUPL3_CONTRACT(e2); \
|
||||
M_MAP(M_TUPL3_DEFINE_EQUAL_FUNC_P0, __VA_ARGS__) \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_EQUAL_FUNC_P0(a) \
|
||||
M_IF(M_TUPL3_TEST_METHOD_P(EQUAL, a))(M_TUPL3_DEFINE_EQUAL_FUNC_P1, M_EAT)(a)
|
||||
#define M_TUPL3_DEFINE_EQUAL_FUNC_P1(a) \
|
||||
b = M_TUPL3_CALL_EQUAL(a, e1 -> M_TUPL3_GET_FIELD a , e2 -> M_TUPL3_GET_FIELD a ); \
|
||||
if (!b) return false;
|
||||
|
||||
|
||||
/* Define a HASH method by calling the HASH methods for all params */
|
||||
#define M_TUPL3_DEFINE_HASH(name, ...) \
|
||||
M_INLINE size_t M_F(name, _hash)(M_F(name,_ct) const e1) { \
|
||||
M_TUPL3_CONTRACT(e1); \
|
||||
M_HASH_DECL(hash); \
|
||||
M_MAP(M_TUPL3_DEFINE_HASH_FUNC_P0, __VA_ARGS__) \
|
||||
return M_HASH_FINAL (hash); \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_HASH_FUNC_P0(a) \
|
||||
M_IF(M_TUPL3_TEST_METHOD_P(HASH, a))(M_TUPL3_DEFINE_HASH_FUNC_P1, M_EAT)(a)
|
||||
#define M_TUPL3_DEFINE_HASH_FUNC_P1(a) \
|
||||
M_HASH_UP(hash, M_TUPL3_CALL_HASH(a, e1 -> M_TUPL3_GET_FIELD a) );
|
||||
|
||||
|
||||
/* Define a GET_STR method by calling the GET_STR methods for all params */
|
||||
#define M_TUPL3_DEFINE_GET_STR(name, ...) \
|
||||
M_INLINE void M_F(name, _get_str)(m_string_t str, \
|
||||
M_F(name,_ct) const el, \
|
||||
bool append) { \
|
||||
bool comma = false; \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (str != NULL); \
|
||||
(append ? m_string_cat_cstr : m_string_set_cstr) (str, "("); \
|
||||
M_MAP(M_TUPL3_DEFINE_GET_STR_FUNC , __VA_ARGS__) \
|
||||
m_string_push_back (str, ')'); \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_GET_STR_FUNC(a) \
|
||||
if (comma) m_string_push_back (str, ','); \
|
||||
comma = true; \
|
||||
M_TUPL3_CALL_GET_STR(a, str, el -> M_TUPL3_GET_FIELD a, true); \
|
||||
|
||||
|
||||
/* Define a OUT_STR method by calling the OUT_STR methods for all params */
|
||||
#define M_TUPL3_DEFINE_OUT_STR(name, ...) \
|
||||
M_INLINE void M_F(name, _out_str)(FILE *f, \
|
||||
M_F(name,_ct) const el) { \
|
||||
bool comma = false; \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (f != NULL); \
|
||||
fputc('(', f); \
|
||||
M_MAP(M_TUPL3_DEFINE_OUT_STR_FUNC , __VA_ARGS__) \
|
||||
fputc (')', f); \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_OUT_STR_FUNC(a) \
|
||||
if (comma) fputc (',', f); \
|
||||
comma = true; \
|
||||
M_TUPL3_CALL_OUT_STR(a, f, el -> M_TUPL3_GET_FIELD a); \
|
||||
|
||||
|
||||
/* Define a IN_STR method by calling the IN_STR methods for all params */
|
||||
#define M_TUPL3_DEFINE_IN_STR(name, ...) \
|
||||
M_INLINE bool M_F(name, _in_str)(M_F(name,_ct) el, FILE *f) { \
|
||||
bool comma = false; \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (f != NULL); \
|
||||
int c = fgetc(f); \
|
||||
if (c != '(') return false; \
|
||||
M_MAP(M_TUPL3_DEFINE_IN_STR_FUNC , __VA_ARGS__) \
|
||||
c = fgetc(f); \
|
||||
return (c == ')'); \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_IN_STR_FUNC(a) \
|
||||
if (comma) { \
|
||||
c = fgetc (f); \
|
||||
if (c != ',' || c == EOF) return false; \
|
||||
} \
|
||||
comma = true; \
|
||||
if (M_TUPL3_CALL_IN_STR(a, el -> M_TUPL3_GET_FIELD a, f) == false) \
|
||||
return false ; \
|
||||
|
||||
|
||||
/* Define a PARSE_STR method by calling the PARSE_STR methods for all params */
|
||||
#define M_TUPL3_DEFINE_PARSE_STR(name, ...) \
|
||||
M_INLINE bool M_F(name, _parse_str)(M_F(name,_ct) el, \
|
||||
const char str[], \
|
||||
const char **endptr) { \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (str != NULL); \
|
||||
bool success = false; \
|
||||
bool comma = false; \
|
||||
int c = *str++; \
|
||||
if (c != '(') goto exit; \
|
||||
M_MAP(M_TUPL3_DEFINE_PARSE_STR_FUNC , __VA_ARGS__) \
|
||||
c = *str++; \
|
||||
success = (c == ')'); \
|
||||
exit: \
|
||||
if (endptr) *endptr = str; \
|
||||
return success; \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_PARSE_STR_FUNC(a) \
|
||||
if (comma) { \
|
||||
c = *str++; \
|
||||
if (c != ',' || c == 0) goto exit; \
|
||||
} \
|
||||
comma = true; \
|
||||
if (M_TUPL3_CALL_PARSE_STR(a, el -> M_TUPL3_GET_FIELD a, str, &str) == false) \
|
||||
goto exit ; \
|
||||
|
||||
|
||||
/* Return the parameter name as a C string */
|
||||
#define M_TUPL3_STRINGIFY_NAME(a) \
|
||||
M_AS_STR(M_TUPL3_GET_FIELD a)
|
||||
|
||||
|
||||
/* Define a OUT_SERIAL method by calling the OUT_SERIAL methods for all params */
|
||||
#define M_TUPL3_DEFINE_OUT_SERIAL(name, ...) \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _out_serial)(m_serial_write_t f, \
|
||||
M_F(name,_ct) const el) { \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (f != NULL && f->m_interface != NULL); \
|
||||
const int field_max = M_NARGS(__VA_ARGS__); \
|
||||
/* Define a constant static table of all fields names */ \
|
||||
static const char *const field_name[] = \
|
||||
{ M_REDUCE(M_TUPL3_STRINGIFY_NAME, M_ID, __VA_ARGS__) }; \
|
||||
int index = 0; \
|
||||
m_serial_local_t local; \
|
||||
m_serial_return_code_t ret; \
|
||||
ret = f->m_interface->write_tuple_start(local, f); \
|
||||
M_MAP(M_TUPL3_DEFINE_OUT_SERIAL_FUNC , __VA_ARGS__) \
|
||||
M_ASSERT( index == field_max); \
|
||||
ret |= f->m_interface->write_tuple_end(local, f); \
|
||||
return ret & M_SERIAL_FAIL; \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_OUT_SERIAL_FUNC(a) \
|
||||
f->m_interface->write_tuple_id(local, f, field_name, field_max, index); \
|
||||
M_TUPL3_CALL_OUT_SERIAL(a, f, el -> M_TUPL3_GET_FIELD a); \
|
||||
index++; \
|
||||
|
||||
|
||||
/* Define a IN_SERIAL method by calling the IN_SERIAL methods for all params */
|
||||
#define M_TUPL3_DEFINE_IN_SERIAL(name, ...) \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _in_serial)(M_F(name,_ct) el, m_serial_read_t f) { \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_ASSERT (f != NULL && f->m_interface != NULL); \
|
||||
int index = -1; \
|
||||
const int field_max = M_NARGS(__VA_ARGS__); \
|
||||
static const char *const field_name[] = \
|
||||
{ M_REDUCE(M_TUPL3_STRINGIFY_NAME, M_ID, __VA_ARGS__) }; \
|
||||
m_serial_local_t local; \
|
||||
m_serial_return_code_t ret; \
|
||||
ret = f->m_interface->read_tuple_start(local, f); \
|
||||
while (ret == M_SERIAL_OK_CONTINUE) { \
|
||||
ret = f->m_interface->read_tuple_id(local, f, field_name, field_max, &index); \
|
||||
if (ret == M_SERIAL_OK_CONTINUE) { \
|
||||
M_ASSERT (index >= 0 && index < field_max); \
|
||||
switch (1+index) { \
|
||||
M_MAP2(M_TUPL3_DEFINE_IN_SERIAL_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(0); \
|
||||
} \
|
||||
ret = (ret == M_SERIAL_OK_DONE) ? M_SERIAL_OK_CONTINUE : M_SERIAL_FAIL; \
|
||||
} \
|
||||
} \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_IN_SERIAL_FUNC(name, a) \
|
||||
case M_C4(name, _, M_TUPL3_GET_FIELD a, _value): \
|
||||
ret = M_TUPL3_CALL_IN_SERIAL(a, el -> M_TUPL3_GET_FIELD a, f); \
|
||||
break; \
|
||||
|
||||
|
||||
/* Define a INIT_MOVE method by calling the INIT_MOVE methods for all params
|
||||
INIT_MOVE cannot fail and cannot throw any exception */
|
||||
#define M_TUPL3_DEFINE_INIT_MOVE(name, ...) \
|
||||
M_INLINE void M_F(name, _init_move)(M_F(name,_ct) el, M_F(name,_ct) org) { \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_MAP(M_TUPL3_DEFINE_INIT_MOVE_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_INIT_MOVE_FUNC(a) \
|
||||
M_TUPL3_CALL_INIT_MOVE(a, el -> M_TUPL3_GET_FIELD a, org -> M_TUPL3_GET_FIELD a);
|
||||
|
||||
|
||||
/* Define a MOVE method by calling the MOVE methods for all params */
|
||||
#define M_TUPL3_DEFINE_MOVE(name, ...) \
|
||||
M_INLINE void M_F(name, _move)(M_F(name,_ct) el, M_F(name,_ct) org) { \
|
||||
M_TUPL3_CONTRACT(el); \
|
||||
M_MAP(M_TUPL3_DEFINE_MOVE_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_MOVE_FUNC(a) \
|
||||
M_TUPL3_CALL_MOVE(a, el -> M_TUPL3_GET_FIELD a, org -> M_TUPL3_GET_FIELD a);
|
||||
|
||||
|
||||
/* Define a SWAP method by calling the SWAP methods for all params */
|
||||
#define M_TUPL3_DEFINE_SWAP(name, ...) \
|
||||
M_INLINE void M_F(name, _swap)(M_F(name,_ct) el1, M_F(name,_ct) el2) { \
|
||||
M_TUPL3_CONTRACT(el1); \
|
||||
M_TUPL3_CONTRACT(el2); \
|
||||
M_MAP(M_TUPL3_DEFINE_SWAP_FUNC , __VA_ARGS__) \
|
||||
}
|
||||
|
||||
#define M_TUPL3_DEFINE_SWAP_FUNC(a) \
|
||||
M_TUPL3_CALL_SWAP(a, el1 -> M_TUPL3_GET_FIELD a, el2 -> M_TUPL3_GET_FIELD a);
|
||||
|
||||
|
||||
/* Define a RESET method by calling the RESET methods for all params */
|
||||
#define M_TUPL3_DEFINE_RESET(name, ...) \
|
||||
M_INLINE void M_F(name, _reset)(M_F(name,_ct) el1) { \
|
||||
M_TUPL3_CONTRACT(el1); \
|
||||
M_MAP(M_TUPL3_DEFINE_RESET_FUNC , __VA_ARGS__) \
|
||||
} \
|
||||
|
||||
#define M_TUPL3_DEFINE_RESET_FUNC(a) \
|
||||
M_TUPL3_CALL_RESET(a, el1 -> M_TUPL3_GET_FIELD a);
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* INIT_WITH macro enabling recursive INIT_WITH initialization
|
||||
tuple = { int, m_string_t, array<m_string_t> }
|
||||
USAGE:
|
||||
M_LET( (x, 2, ("John"), ( ("Bear"), ("Rabbit") )), tuple_t)
|
||||
|
||||
"If you think it's simple, you're deluding yourself."
|
||||
|
||||
Several pass are done:
|
||||
1) If the number of arguments doesn't match the number of oplists of the
|
||||
tuple oplist, it is assumed something is wrong. It uses the _init_emplace
|
||||
function to provide proper warning in such case.
|
||||
2) Otherwise, it checks that the number of arguments matches the number
|
||||
of arguments of the tuple definition.
|
||||
3) Mix all arguments with their associated oplists to have pair (arg, oplist),
|
||||
4) Map the following macro for each computed pair :
|
||||
4.a) If INIT_WITH macro is not defined for this pair, it uses INIT_SET
|
||||
4.b) If the argument is encapsulated with parenthesis, it uses INIT_WITH
|
||||
4.c) If the oplist property LET_AS_INIT_WITH is defined, it uses INIT_WITH
|
||||
4.d) Otherwise it uses INIT_SET.
|
||||
*/
|
||||
#define M_TUPL3_INIT_WITH(oplist, dest, ...) \
|
||||
M_TUPL3_INIT_WITH_P1(M_GET_NAME oplist, M_GET_OPLIST oplist, dest, __VA_ARGS__)
|
||||
#define M_TUPL3_INIT_WITH_P1(name, oplist_arglist, dest, ...) \
|
||||
M_IF(M_NOTEQUAL( M_NARGS oplist_arglist, M_NARGS (__VA_ARGS__))) \
|
||||
(M_TUPL3_INIT_WITH_P1_FUNC, M_TUPL3_INIT_WITH_P1_MACRO)(name, oplist_arglist, dest, __VA_ARGS__)
|
||||
#define M_TUPL3_INIT_WITH_P1_FUNC(name, oplist_arglist, dest, ...) \
|
||||
M_F(name, _init_emplace)(dest, __VA_ARGS__)
|
||||
#define M_TUPL3_INIT_WITH_P1_MACRO(name, oplist_arglist, dest, ...) \
|
||||
( M_STATIC_ASSERT( M_NARGS oplist_arglist == M_C3(m_tupl3_, name, _num_args), M_LIB_DIMENSION_ERROR, "The number of oplists given to TUPLE_OPLIST don't match the number of oplists used to create the tuple." ), \
|
||||
M_STATIC_ASSERT( M_NARGS(__VA_ARGS__) == M_C3(m_tupl3_, name, _num_args), M_LIB_DIMENSION_ERROR, "Missing / Too many arguments for tuple"), \
|
||||
M_MAP3(M_TUPL3_INIT_WITH_P2, (name, dest), M_OPFLAT M_MERGE_ARGLIST( oplist_arglist, (__VA_ARGS__) ) ) \
|
||||
(void) 0)
|
||||
#define M_TUPL3_INIT_WITH_P2(name_dest, num, pair) \
|
||||
M_TUPL3_INIT_WITH_P3( M_PAIR_1 name_dest, M_PAIR_2 name_dest, num, M_PAIR_1 pair, M_PAIR_2 pair )
|
||||
#define M_TUPL3_INIT_WITH_P3(name, dest, num, oplist, param) \
|
||||
M_IF(M_TEST_METHOD_P(INIT_WITH, oplist))(M_TUPL3_INIT_WITH_P4, M_TUPL3_INIT_WITH_SET)(name, dest, num, oplist, param)
|
||||
#define M_TUPL3_INIT_WITH_SET(name, dest, num, oplist, param) \
|
||||
M_CALL_INIT_SET (oplist, *M_C4(m_tupl3_, name, _get_at_, num)(dest), param) ,
|
||||
#define M_TUPL3_INIT_WITH_P4(name, dest, num, oplist, param) \
|
||||
M_IF(M_PARENTHESIS_P( param))(M_TUPL3_INIT_WITH_P5, M_TUPL3_INIT_WITH_P6)(name, dest, num, oplist, param)
|
||||
#define M_TUPL3_INIT_WITH_P5(name, dest, num, oplist, param) \
|
||||
M_CALL_INIT_WITH(oplist, *M_C4(m_tupl3_, name, _get_at_, num)(dest), M_REMOVE_PARENTHESIS (param) ) ,
|
||||
#define M_TUPL3_INIT_WITH_P6(name, dest, num, oplist, param) \
|
||||
M_IF(M_GET_PROPERTY(oplist, LET_AS_INIT_WITH))(M_TUPL3_INIT_WITH_P5, M_TUPL3_INIT_WITH_SET)(name, dest, num, oplist, param)
|
||||
|
||||
/* Macros for testing for the presence of a method in the parameter (name, type, oplist) */
|
||||
#define M_TUPL3_TEST_METHOD_P(method, trio) \
|
||||
M_APPLY(M_TUPL3_TEST_METHOD2_P, method, M_OPFLAT trio)
|
||||
|
||||
#define M_TUPL3_TEST_METHOD2_P(method, f, t, op) \
|
||||
M_TEST_METHOD_P(method, op)
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Macros for testing for the presence of a method in all the params */
|
||||
#define M_TUPL3_IF_ALL(method, ...) \
|
||||
M_IF(M_REDUCE2(M_TUPL3_TEST_METHOD_P, M_AND, method, __VA_ARGS__))
|
||||
|
||||
/* Macros for testing for the presence of a method in at least one params */
|
||||
#define M_TUPL3_IF_ONE(method, ...) \
|
||||
M_IF(M_REDUCE2(M_TUPL3_TEST_METHOD_P, M_OR, method, __VA_ARGS__))
|
||||
|
||||
// deferred evaluation
|
||||
#define M_TUPL3_OPLIST_P1(arg) M_TUPL3_OPLIST_P2 arg
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_TUPL3_OPLIST_P2(name, ...) \
|
||||
M_IF(M_REDUCE(M_OPLIST_P, M_AND, __VA_ARGS__))(M_TUPL3_OPLIST_P3, M_TUPL3_OPLIST_FAILURE)(name, __VA_ARGS__)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_TUPL3_OPLIST_FAILURE(name, ...) \
|
||||
((M_LIB_ERROR(ONE_ARGUMENT_OF_M_TUPL3_OPLIST_IS_NOT_AN_OPLIST, name, __VA_ARGS__)))
|
||||
|
||||
/* Define the TUPLE oplist */
|
||||
#define M_TUPL3_OPLIST_P3(name, ...) \
|
||||
(M_IF_METHOD_ALL(INIT, __VA_ARGS__)(INIT(M_F(name,_init)),), \
|
||||
INIT_SET(M_F(name, _init_set)), \
|
||||
INIT_WITH(API_1(M_TUPL3_INIT_WITH)), \
|
||||
SET(M_F(name,_set)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name,_ct)), \
|
||||
OPLIST( (__VA_ARGS__) ), \
|
||||
M_IF_METHOD_ALL(CMP, __VA_ARGS__)(CMP(M_F(name, _cmp)),), \
|
||||
M_IF_METHOD_ALL(HASH, __VA_ARGS__)(HASH(M_F(name, _hash)),), \
|
||||
M_IF_METHOD_ALL(EQUAL, __VA_ARGS__)(EQUAL(M_F(name, _equal_p)),), \
|
||||
M_IF_METHOD_ALL(GET_STR, __VA_ARGS__)(GET_STR(M_F(name, _get_str)),), \
|
||||
M_IF_METHOD_ALL(PARSE_STR, __VA_ARGS__)(PARSE_STR(M_F(name, _parse_str)),), \
|
||||
M_IF_METHOD_ALL(IN_STR, __VA_ARGS__)(IN_STR(M_F(name, _in_str)),), \
|
||||
M_IF_METHOD_ALL(OUT_STR, __VA_ARGS__)(OUT_STR(M_F(name, _out_str)),), \
|
||||
M_IF_METHOD_ALL(IN_SERIAL, __VA_ARGS__)(IN_SERIAL(M_F(name, _in_serial)),), \
|
||||
M_IF_METHOD_ALL(OUT_SERIAL, __VA_ARGS__)(OUT_SERIAL(M_F(name, _out_serial)),), \
|
||||
M_IF_METHOD_ALL(INIT_MOVE, __VA_ARGS__)(INIT_MOVE(M_F(name, _init_move)),), \
|
||||
M_IF_METHOD_ALL(MOVE, __VA_ARGS__)(MOVE(M_F(name, _move)),), \
|
||||
M_IF_METHOD_ALL(SWAP, __VA_ARGS__)(SWAP(M_F(name, _swap)),), \
|
||||
M_IF_METHOD_ALL(RESET, __VA_ARGS__)(RESET(M_F(name, _reset)),), \
|
||||
EMPLACE_TYPE( ( M_REDUCE2(M_TUPL3_OPLIST_SUBTYPE, M_ID, name, M_SEQ(1, M_NARGS(__VA_ARGS__))) ) ) \
|
||||
)
|
||||
|
||||
/* Support for EMPLACE_TYPE in OPLIST. It refers the created internal type alias */
|
||||
#define M_TUPL3_OPLIST_SUBTYPE(name, num) \
|
||||
M_C4(name, _type_, num, _ct)
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define TUPLE_DEF2 M_TUPLE_DEF2
|
||||
#define TUPLE_DEF2_AS M_TUPLE_DEF2_AS
|
||||
#define TUPLE_OPLIST M_TUPLE_OPLIST
|
||||
#define TUPLE_ORDER M_TUPLE_ORDER
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,819 +0,0 @@
|
||||
/*
|
||||
* M*LIB - VARIANT module
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_VARIANT_H
|
||||
#define MSTARLIB_VARIANT_H
|
||||
|
||||
#include "m-core.h"
|
||||
|
||||
/* Define the variant type and functions.
|
||||
USAGE:
|
||||
VARIANT_DEF2(name, [(field1, type1, oplist1), (field2, type2, oplist2), ...] ) */
|
||||
#define M_VARIANT_DEF2(name, ...) \
|
||||
M_VARIANT_DEF2_AS(name, M_F(name,_t), __VA_ARGS__)
|
||||
|
||||
|
||||
/* Define the variant type and functions
|
||||
as the given name_t
|
||||
USAGE:
|
||||
VARIANT_DEF2_AS(name, name_t, [(field1, type1, oplist1), (field2, type2, oplist2), ...] ) */
|
||||
#define M_VARIANT_DEF2_AS(name, name_t, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_VAR1ANT_DEF2_P1( (name, name_t M_VAR1ANT_INJECT_GLOBAL(__VA_ARGS__)) ) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
|
||||
/* Define the oplist of a variant.
|
||||
USAGE: VARIANT_OPLIST(name[, oplist of the first type, ...]) */
|
||||
#define M_VARIANT_OPLIST(...) \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__) \
|
||||
(M_VAR1ANT_OPLIST_P1((__VA_ARGS__, M_BASIC_OPLIST)), \
|
||||
M_VAR1ANT_OPLIST_P1((__VA_ARGS__ )))
|
||||
|
||||
|
||||
/*****************************************************************************/
|
||||
/********************************** INTERNAL *********************************/
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Contract of a variant. */
|
||||
#define M_VAR1ANT_CONTRACT(name, my) do { \
|
||||
M_ASSERT(my != NULL); \
|
||||
M_ASSERT(my->type >= M_F(name, _EMPTY)); \
|
||||
M_ASSERT(my->type <= (enum M_F(name, _enum)) M_F(name, _MAX_TYPE)); \
|
||||
} while (0)
|
||||
|
||||
/* Inject the oplist within the list of arguments */
|
||||
#define M_VAR1ANT_INJECT_GLOBAL(...) \
|
||||
M_MAP(M_VAR1ANT_INJECT_OPLIST_A, __VA_ARGS__)
|
||||
|
||||
/* Transform (x, type) into (x, type, oplist) if there is global registered oplist
|
||||
or (x, type, M_BASIC_OPLIST) if there is no global one,
|
||||
or keep (x, type, oplist) if oplist was already present */
|
||||
#define M_VAR1ANT_INJECT_OPLIST_A( duo_or_trio ) \
|
||||
M_VAR1ANT_INJECT_OPLIST_B duo_or_trio
|
||||
|
||||
#define M_VAR1ANT_INJECT_OPLIST_B( f, ... ) \
|
||||
M_DEFERRED_COMMA \
|
||||
M_IF_NARGS_EQ1(__VA_ARGS__)( (f, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)()), (f, __VA_ARGS__) )
|
||||
|
||||
// Deferred evaluation
|
||||
#define M_VAR1ANT_DEF2_P1(...) M_ID( M_VAR1ANT_DEF2_P2 __VA_ARGS__ )
|
||||
|
||||
// Test if all third argument of all arguments is an oplist
|
||||
#define M_VAR1ANT_IF_ALL_OPLIST(...) \
|
||||
M_IF(M_REDUCE(M_VAR1ANT_IS_OPLIST_P, M_AND, __VA_ARGS__))
|
||||
|
||||
// Test if the third argument is an oplist
|
||||
#define M_VAR1ANT_IS_OPLIST_P(a) \
|
||||
M_OPLIST_P(M_RET_ARG3 a)
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_VAR1ANT_DEF2_P2(name, name_t, ...) \
|
||||
M_VAR1ANT_IF_ALL_OPLIST(__VA_ARGS__)(M_VAR1ANT_DEF2_P3, M_VAR1ANT_DEF2_FAILURE)(name, name_t, __VA_ARGS__)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_VAR1ANT_DEF2_FAILURE(name, name_t, ...) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(VARIANT_DEF2): at least one of the given argument is not a valid oplist: " #__VA_ARGS__)
|
||||
|
||||
/* Define the variant */
|
||||
#define M_VAR1ANT_DEF2_P3(name, name_t, ...) \
|
||||
M_VAR1ANT_DEFINE_TYPE(name, name_t, __VA_ARGS__) \
|
||||
M_VAR1ANT_CONTROL_ALL_OPLIST(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_INIT(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_CLEAR(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_INIT_SET(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_SET(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_EMPLACE(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_TEST_P(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_IF_ALL(INIT, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_INIT_FIELD(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_DEFINE_INIT_SETTER_FIELD(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_SETTER_FIELD(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_GETTER_FIELD(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_DEFINE_RESET_FUNC(name, __VA_ARGS__) \
|
||||
M_VAR1ANT_IF_ALL(HASH, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_HASH(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(EQUAL, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_EQUAL(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(GET_STR, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_GET_STR(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL2(PARSE_STR, INIT, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_PARSE_STR(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(OUT_STR, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_OUT_STR(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL2(IN_STR, INIT, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_IN_STR(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(OUT_SERIAL, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_OUT_SERIAL(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL2(IN_SERIAL, INIT, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_IN_SERIAL(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(INIT_MOVE, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_INIT_MOVE(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(INIT_MOVE, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_MOVE(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(INIT_MOVE, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_MOVER(name, __VA_ARGS__),) \
|
||||
M_VAR1ANT_IF_ALL(SWAP, __VA_ARGS__) \
|
||||
(M_VAR1ANT_DEFINE_SWAP(name, __VA_ARGS__),)
|
||||
|
||||
|
||||
/* Get the field name, the type, the oplist or the methods
|
||||
based on the variant (field, type, oplist) */
|
||||
#define M_VAR1ANT_GET_FIELD(f,t,o) f
|
||||
#define M_VAR1ANT_GET_TYPE(f,t,o) t
|
||||
#define M_VAR1ANT_GET_OPLIST(f,t,o) o
|
||||
#define M_VAR1ANT_GET_INIT(f,t,o) M_GET_INIT o
|
||||
#define M_VAR1ANT_GET_INIT_SET(f,t,o) M_GET_INIT_SET o
|
||||
#define M_VAR1ANT_GET_INIT_MOVE(f,t,o) M_GET_INIT_MOVE o
|
||||
#define M_VAR1ANT_GET_MOVE(f,t,o) M_GET_MOVE o
|
||||
#define M_VAR1ANT_GET_SET(f,t,o) M_GET_SET o
|
||||
#define M_VAR1ANT_GET_CLEAR(f,t,o) M_GET_CLEAR o
|
||||
#define M_VAR1ANT_GET_CMP(f,t,o) M_GET_CMP o
|
||||
#define M_VAR1ANT_GET_HASH(f,t,o) M_GET_HASH o
|
||||
#define M_VAR1ANT_GET_EQUAL(f,t,o) M_GET_EQUAL o
|
||||
#define M_VAR1ANT_GET_STR(f,t,o) M_GET_GET_STR o
|
||||
#define M_VAR1ANT_GET_PARSE_STR(f,t,o) M_GET_PARSE_STR o
|
||||
#define M_VAR1ANT_GET_OUT_STR(f,t,o) M_GET_OUT_STR o
|
||||
#define M_VAR1ANT_GET_IN_STR(f,t,o) M_GET_IN_STR o
|
||||
#define M_VAR1ANT_GET_OUT_SERIAL(f,t,o) M_GET_OUT_SERIAL o
|
||||
#define M_VAR1ANT_GET_IN_SERIAL(f,t,o) M_GET_IN_SERIAL o
|
||||
#define M_VAR1ANT_GET_SWAP(f,t,o) M_GET_SWAP o
|
||||
|
||||
/* Call the methods through API */
|
||||
#define M_VAR1ANT_CALL_INIT(t, ...) M_APPLY_API(M_VAR1ANT_GET_INIT t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_INIT_SET(t, ...) M_APPLY_API(M_VAR1ANT_GET_INIT_SET t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_INIT_MOVE(t, ...) M_APPLY_API(M_VAR1ANT_GET_INIT_MOVE t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_MOVE(t, ...) M_APPLY_API(M_VAR1ANT_GET_MOVE t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_SET(t, ...) M_APPLY_API(M_VAR1ANT_GET_SET t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_CLEAR(t, ...) M_APPLY_API(M_VAR1ANT_GET_CLEAR t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_CMP(t, ...) M_APPLY_API(M_VAR1ANT_GET_CMP t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_HASH(t, ...) M_APPLY_API(M_VAR1ANT_GET_HASH t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_EQUAL(t, ...) M_APPLY_API(M_VAR1ANT_GET_EQUAL t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_GET_STR(t, ...) M_APPLY_API(M_VAR1ANT_GET_STR t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_PARSE_STR(t, ...) M_APPLY_API(M_VAR1ANT_GET_PARSE_STR t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_OUT_STR(t, ...) M_APPLY_API(M_VAR1ANT_GET_OUT_STR t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_IN_STR(t, ...) M_APPLY_API(M_VAR1ANT_GET_IN_STR t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_OUT_SERIAL(t, ...) M_APPLY_API(M_VAR1ANT_GET_OUT_SERIAL t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_IN_SERIAL(t, ...) M_APPLY_API(M_VAR1ANT_GET_IN_SERIAL t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
#define M_VAR1ANT_CALL_SWAP(t, ...) M_APPLY_API(M_VAR1ANT_GET_SWAP t, M_VAR1ANT_GET_OPLIST t, __VA_ARGS__)
|
||||
|
||||
/* Define the type */
|
||||
#define M_VAR1ANT_DEFINE_TYPE(name, name_t, ...) \
|
||||
/* Define enum of all types of the variant */ \
|
||||
enum M_F(name, _enum) { M_F(name, _EMPTY) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_UNION_ELE, name, __VA_ARGS__) \
|
||||
}; \
|
||||
/* Define enum equal to the number of types of the variant */ \
|
||||
enum M_F(name, _enum_max) { \
|
||||
M_F(name, _MAX_TYPE) = M_NARGS(__VA_ARGS__) \
|
||||
}; \
|
||||
/* Define the variant */ \
|
||||
typedef struct M_F(name, _s) { \
|
||||
enum M_F(name, _enum) type; \
|
||||
union { \
|
||||
M_MAP(M_VAR1ANT_DEFINE_TYPE_ELE , __VA_ARGS__) \
|
||||
} value; \
|
||||
} name_t[1]; \
|
||||
\
|
||||
typedef struct M_F(name, _s) *M_F(name, _ptr); \
|
||||
typedef const struct M_F(name, _s) *M_F(name, _srcptr); \
|
||||
/* Define internal type for oplist */ \
|
||||
typedef name_t M_F(name, _ct);
|
||||
|
||||
#define M_VAR1ANT_DEFINE_UNION_ELE(name, a) \
|
||||
, M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_TYPE_ELE(a) \
|
||||
M_VAR1ANT_GET_TYPE a M_VAR1ANT_GET_FIELD a ;
|
||||
|
||||
|
||||
/* Control that all given oplists of all parameters are really oplists */
|
||||
#define M_VAR1ANT_CONTROL_ALL_OPLIST(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_CONTROL_OPLIST, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_CONTROL_OPLIST(name, a) \
|
||||
M_CHECK_COMPATIBLE_OPLIST(name, M_VAR1ANT_GET_FIELD a, \
|
||||
M_VAR1ANT_GET_TYPE a, M_VAR1ANT_GET_OPLIST a)
|
||||
|
||||
|
||||
/* Define the INIT function. Init the variant to empty */
|
||||
#define M_VAR1ANT_DEFINE_INIT(name, ...) \
|
||||
M_INLINE void M_F(name, _init)(M_F(name,_ct) my) { \
|
||||
my->type = M_F(name, _EMPTY); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the INIT_SET function. */
|
||||
#define M_VAR1ANT_DEFINE_INIT_SET(name, ...) \
|
||||
M_INLINE void M_F(name, _init_set)(M_F(name,_ct) my , \
|
||||
M_F(name,_ct) const org) { \
|
||||
M_VAR1ANT_CONTRACT(name, org); \
|
||||
my->type = org->type; \
|
||||
switch (org->type) { \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_INIT_SET_FUNC, name, __VA_ARGS__) \
|
||||
case M_F(name, _EMPTY): /* fallthrough */ \
|
||||
default: M_ASSUME(org->type == M_F(name, _EMPTY)); break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_SET_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_INIT_SET(a, my -> value. M_VAR1ANT_GET_FIELD a , \
|
||||
org -> value.M_VAR1ANT_GET_FIELD a ); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the SET function. */
|
||||
#define M_VAR1ANT_DEFINE_SET(name, ...) \
|
||||
M_INLINE void M_F(name, _set)(M_F(name,_ct) my , \
|
||||
M_F(name,_ct) const org) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
M_VAR1ANT_CONTRACT(name, org); \
|
||||
if (my->type != org->type) { \
|
||||
/* Different types: clear previous one and create new */ \
|
||||
M_F(name, _clear)(my); \
|
||||
M_F(name, _init_set)(my, org); \
|
||||
} else { \
|
||||
/* Same type: optimize the set */ \
|
||||
switch (org->type) { \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_SET_FUNC, name, __VA_ARGS__) \
|
||||
case M_F(name, _EMPTY): /* fallthrough */ \
|
||||
default: M_ASSUME(org->type == M_F(name, _EMPTY)); break; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_SET_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_SET(a, my -> value. M_VAR1ANT_GET_FIELD a , \
|
||||
org -> value.M_VAR1ANT_GET_FIELD a ); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the CLEAR function. */
|
||||
#define M_VAR1ANT_DEFINE_CLEAR(name, ...) \
|
||||
M_INLINE void M_F(name, _clear)(M_F(name,_ct) my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
switch (my->type) { \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_CLEAR_FUNC, name, __VA_ARGS__) \
|
||||
case M_F(name, _EMPTY): /* fallthrough */ \
|
||||
default: M_ASSUME(my->type == M_F(name, _EMPTY)); break; \
|
||||
} \
|
||||
my->type = M_F(name, _EMPTY); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_CLEAR_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_CLEAR(a, my -> value. M_VAR1ANT_GET_FIELD a); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the TEST_P function. */
|
||||
#define M_VAR1ANT_DEFINE_TEST_P(name, ...) \
|
||||
M_INLINE bool M_F(name, _empty_p)(M_F(name,_ct) const my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
return my->type == M_F(name, _EMPTY); \
|
||||
} \
|
||||
M_INLINE enum M_F(name, _enum) \
|
||||
M_F(name, _type)(M_F(name,_ct) my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
return my->type; \
|
||||
} \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_TEST_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_TEST_FUNC(name, a) \
|
||||
M_INLINE bool \
|
||||
M_C4(name, _, M_VAR1ANT_GET_FIELD a, _p)(M_F(name,_ct) const my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
return my->type == M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the INIT function. */
|
||||
#define M_VAR1ANT_DEFINE_INIT_FIELD(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_INIT_FIELD_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_FIELD_FUNC(name, a) \
|
||||
M_INLINE void \
|
||||
M_C3(name, _init_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) my) { \
|
||||
/* Reinit variable with the given value */ \
|
||||
my->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT(a, my -> value. M_VAR1ANT_GET_FIELD a); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the INIT_SET of a given type function. */
|
||||
#define M_VAR1ANT_DEFINE_INIT_SETTER_FIELD(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_INIT_SETTER_FIELD_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_SETTER_FIELD_FUNC(name, a) \
|
||||
M_INLINE void \
|
||||
M_C3(name, _init_set_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) my, \
|
||||
M_VAR1ANT_GET_TYPE a const M_VAR1ANT_GET_FIELD a ) { \
|
||||
my->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT_SET(a, my -> value. M_VAR1ANT_GET_FIELD a, \
|
||||
M_VAR1ANT_GET_FIELD a); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the SET of a given type function. */
|
||||
#define M_VAR1ANT_DEFINE_SETTER_FIELD(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_SETTER_FIELD_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_SETTER_FIELD_FUNC(name, a) \
|
||||
M_INLINE void \
|
||||
M_C3(name, _set_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) my, \
|
||||
M_VAR1ANT_GET_TYPE a const M_VAR1ANT_GET_FIELD a ) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
if (my->type == M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value) ) { \
|
||||
M_VAR1ANT_CALL_SET(a, my -> value. M_VAR1ANT_GET_FIELD a, \
|
||||
M_VAR1ANT_GET_FIELD a); \
|
||||
} else { \
|
||||
M_F(name, _clear)(my); \
|
||||
/* Reinit variable with the given value */ \
|
||||
my->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT_SET(a, my -> value. M_VAR1ANT_GET_FIELD a, \
|
||||
M_VAR1ANT_GET_FIELD a); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
/* Define the GET_field of a given type function. */
|
||||
#define M_VAR1ANT_DEFINE_GETTER_FIELD(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_GETTER_FIELD_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_GETTER_FIELD_FUNC(name, a) \
|
||||
M_INLINE M_VAR1ANT_GET_TYPE a * \
|
||||
M_C3(name, _get_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
if (my->type != M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value) ) { \
|
||||
return NULL; \
|
||||
} \
|
||||
return &my -> value . M_VAR1ANT_GET_FIELD a; \
|
||||
} \
|
||||
\
|
||||
M_INLINE M_VAR1ANT_GET_TYPE a const * \
|
||||
M_C3(name, _cget_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) const my) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
if (my->type != M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value) ) { \
|
||||
return NULL; \
|
||||
} \
|
||||
return &my -> value . M_VAR1ANT_GET_FIELD a; \
|
||||
}
|
||||
|
||||
|
||||
/* Define the EMPLACE of a given type function.
|
||||
NOTE: Use of a variant of MAP3 because of recursive use of MAP2/MAP3/REDUCE2 !
|
||||
*/
|
||||
#define M_VAR1ANT_DEFINE_EMPLACE(name, ...) \
|
||||
M_VAR1ANT_MAP3_ALT(M_VAR1ANT_DEFINE_EMPLACE_FUNC, name, __VA_ARGS__)
|
||||
// Variant of M_MAP3 using M_REDUCE3
|
||||
#define M_VAR1ANT_MAP3_ALT(f, d, ...) M_REDUCE3(f, M_VAR1ANT_MAP3_ALT_ID, d, __VA_ARGS__)
|
||||
#define M_VAR1ANT_MAP3_ALT_ID(a, b) a b
|
||||
|
||||
#define M_VAR1ANT_DEFINE_EMPLACE_FUNC(name, num, a) \
|
||||
M_EMPLACE_QUEUE_DEF( (name, M_VAR1ANT_GET_FIELD a), M_F(name,_ct), M_C3(name, _init_emplace_, M_VAR1ANT_GET_FIELD a), M_VAR1ANT_GET_OPLIST a, M_VAR1ANT_DEFINE_INIT_EMPLACE_DEF) \
|
||||
M_EMPLACE_QUEUE_DEF( (name, M_VAR1ANT_GET_FIELD a), M_F(name,_ct), M_C3(name, _emplace_, M_VAR1ANT_GET_FIELD a), M_VAR1ANT_GET_OPLIST a, M_VAR1ANT_DEFINE_EMPLACE_DEF)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_EMPLACE_DEF(name, name_t, function_name, oplist, init_func, exp_emplace_type) \
|
||||
M_INLINE void \
|
||||
function_name(name_t my \
|
||||
M_EMPLACE_LIST_TYPE_VAR(ab, exp_emplace_type) ) \
|
||||
{ \
|
||||
my->type = M_C4(M_PAIR_1 name, _, M_PAIR_2 name, _value); \
|
||||
M_EMPLACE_CALL_FUNC(ab, init_func, oplist, my -> value. M_PAIR_2 name, exp_emplace_type); \
|
||||
} \
|
||||
|
||||
#define M_VAR1ANT_DEFINE_EMPLACE_DEF(name, name_t, function_name, oplist, init_func, exp_emplace_type) \
|
||||
M_INLINE void \
|
||||
function_name(name_t my \
|
||||
M_EMPLACE_LIST_TYPE_VAR(ab, exp_emplace_type) ) \
|
||||
{ \
|
||||
/* No optimization done */ \
|
||||
M_C(M_PAIR_1 name, _clear)(my); \
|
||||
my->type = M_C4(M_PAIR_1 name, _, M_PAIR_2 name, _value); \
|
||||
M_EMPLACE_CALL_FUNC(ab, init_func, oplist, my -> value. M_PAIR_2 name, exp_emplace_type); \
|
||||
} \
|
||||
|
||||
/* Define the EQUAL_P function. */
|
||||
#define M_VAR1ANT_DEFINE_EQUAL(name, ...) \
|
||||
M_INLINE bool M_F(name, _equal_p)(M_F(name,_ct) const e1 , \
|
||||
M_F(name,_ct) const e2) { \
|
||||
bool b; \
|
||||
M_VAR1ANT_CONTRACT(name, e1); \
|
||||
M_VAR1ANT_CONTRACT(name, e2); \
|
||||
if (e1->type != e2->type) return false; \
|
||||
switch (e1->type) { \
|
||||
case M_F(name, _EMPTY): break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_EQUAL_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_EQUAL_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
b = M_VAR1ANT_CALL_EQUAL(a, e1 -> value . M_VAR1ANT_GET_FIELD a , \
|
||||
e2 -> value . M_VAR1ANT_GET_FIELD a ); \
|
||||
return b; \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the HASH function. */
|
||||
#define M_VAR1ANT_DEFINE_HASH(name, ...) \
|
||||
M_INLINE size_t M_F(name, _hash)(M_F(name,_ct) const e1) { \
|
||||
M_VAR1ANT_CONTRACT(name, e1); \
|
||||
M_HASH_DECL(hash); \
|
||||
M_HASH_UP (hash, (unsigned int) (e1 -> type)); \
|
||||
switch (e1->type) { \
|
||||
case M_F(name, _EMPTY): break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_HASH_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
return M_HASH_FINAL (hash); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_HASH_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_HASH_UP(hash, M_VAR1ANT_CALL_HASH(a, e1 -> value . M_VAR1ANT_GET_FIELD a) ); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the INIT_MOVE function. */
|
||||
#define M_VAR1ANT_DEFINE_INIT_MOVE(name, ...) \
|
||||
M_INLINE void \
|
||||
M_F(name, _init_move)(M_F(name,_ct) el, M_F(name,_ct) org) { \
|
||||
M_VAR1ANT_CONTRACT(name, org); \
|
||||
el -> type = org -> type; \
|
||||
switch (el->type) { \
|
||||
case M_F(name, _EMPTY): break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_INIT_MOVE_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
org -> type = M_F(name, _EMPTY); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_MOVE_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_INIT_MOVE(a, el -> value . M_VAR1ANT_GET_FIELD a, \
|
||||
org -> value . M_VAR1ANT_GET_FIELD a); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the MOVE function.
|
||||
This is not optimized version.
|
||||
It can be optimized if both types are the same.
|
||||
*/
|
||||
#define M_VAR1ANT_DEFINE_MOVE(name, ...) \
|
||||
M_INLINE void \
|
||||
M_F(name, _move)(M_F(name,_ct) el, M_F(name,_ct) org) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
M_VAR1ANT_CONTRACT(name, org); \
|
||||
M_F(name, _clear)(el); \
|
||||
M_F(name, _init_move)(el , org); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the MOVE function of a given type */
|
||||
#define M_VAR1ANT_DEFINE_MOVER(name, ...) \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_MOVER_FUNC, name, __VA_ARGS__)
|
||||
|
||||
#define M_VAR1ANT_DEFINE_MOVER_FUNC(name, a) \
|
||||
M_INLINE void \
|
||||
M_C3(name, _move_, M_VAR1ANT_GET_FIELD a)(M_F(name,_ct) my, \
|
||||
M_VAR1ANT_GET_TYPE a M_VAR1ANT_GET_FIELD a ) { \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
M_F(name, _clear)(my); \
|
||||
/* Reinit variable with the given value */ \
|
||||
my->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT_MOVE(a, my -> value. M_VAR1ANT_GET_FIELD a, \
|
||||
M_VAR1ANT_GET_FIELD a); \
|
||||
}
|
||||
|
||||
|
||||
/* Define the SWAP function */
|
||||
#define M_VAR1ANT_DEFINE_SWAP(name, ...) \
|
||||
M_INLINE void \
|
||||
M_F(name, _swap)(M_F(name,_ct) el1, M_F(name,_ct) el2) { \
|
||||
M_VAR1ANT_CONTRACT(name, el1); \
|
||||
M_VAR1ANT_CONTRACT(name, el2); \
|
||||
if (el1->type == el2->type) { \
|
||||
switch (el1->type) { \
|
||||
case M_F(name, _EMPTY): break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_INIT_SWAP_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
} else { \
|
||||
M_F(name,_ct) tmp; \
|
||||
M_VAR1ANT_IF_ALL(INIT_MOVE, __VA_ARGS__) \
|
||||
( /* NOTE: Slow implementation */ \
|
||||
M_F(name, _init_move)(tmp, el1); \
|
||||
M_F(name, _init_move)(el1, el2); \
|
||||
M_F(name, _init_move)(el2, tmp); \
|
||||
, \
|
||||
/* NOTE: Very slow implementation */ \
|
||||
M_F(name, _init_set)(tmp, el1); \
|
||||
M_F(name, _set)(el1, el2); \
|
||||
M_F(name, _set)(el2, tmp); \
|
||||
M_F(name, _clear)(tmp); \
|
||||
) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_INIT_SWAP_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_SWAP(a, el1 -> value . M_VAR1ANT_GET_FIELD a, \
|
||||
el2 -> value . M_VAR1ANT_GET_FIELD a); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the GET_STR function */
|
||||
#define M_VAR1ANT_DEFINE_GET_STR(name, ...) \
|
||||
M_INLINE void M_F(name, _get_str)(m_string_t str, \
|
||||
M_F(name,_ct) const el, \
|
||||
bool append) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
M_ASSERT (str != NULL); \
|
||||
void (*func)(m_string_t, const char *); \
|
||||
func = append ? m_string_cat_cstr : m_string_set_cstr; \
|
||||
switch (el->type) { \
|
||||
case M_F(name, _EMPTY): func(str, "@EMPTY@"); break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_GET_STR_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
m_string_push_back (str, '@'); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_GET_STR_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
func(str, "@" M_AS_STR(M_VAR1ANT_GET_FIELD a) "@"); \
|
||||
M_VAR1ANT_CALL_GET_STR(a, str, el -> value . M_VAR1ANT_GET_FIELD a, true); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the PARSE_STR function */
|
||||
#define M_VAR1ANT_DEFINE_PARSE_STR(name, ...) \
|
||||
M_INLINE bool M_F(name, _parse_str)(M_F(name,_ct) el, \
|
||||
const char str[], \
|
||||
const char **endp) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
M_ASSERT (str != NULL); \
|
||||
bool success = false; \
|
||||
char variantTypeBuf[M_USE_IDENTIFIER_ALLOC+1]; \
|
||||
int c = *str++; \
|
||||
unsigned int i = 0; \
|
||||
M_F(name, _reset)(el); \
|
||||
if (c != '@') goto exit; \
|
||||
/* First read the name of the type */ \
|
||||
c = *str++; \
|
||||
while (c != '@' && c != 0 && i < sizeof(variantTypeBuf) - 1) { \
|
||||
variantTypeBuf[i++] = (char) c; \
|
||||
c = *str++; \
|
||||
} \
|
||||
if (c != '@') goto exit; \
|
||||
variantTypeBuf[i++] = 0; \
|
||||
M_ASSERT(i < sizeof(variantTypeBuf)); \
|
||||
/* In function of the type */ \
|
||||
if (strcmp(variantTypeBuf, "EMPTY") == 0) { \
|
||||
el->type = M_F(name, _EMPTY); \
|
||||
} \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_PARSE_STR_FUNC , name, __VA_ARGS__) \
|
||||
else goto exit; \
|
||||
success = (*str++ == '@'); \
|
||||
exit: \
|
||||
if (endp) *endp = str; \
|
||||
return success; \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_PARSE_STR_FUNC(name, a) \
|
||||
else if (strcmp (variantTypeBuf, M_AS_STR(M_VAR1ANT_GET_FIELD a)) == 0) { \
|
||||
el->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT(a, el ->value . M_VAR1ANT_GET_FIELD a ); \
|
||||
bool b = M_VAR1ANT_CALL_PARSE_STR(a, el -> value . M_VAR1ANT_GET_FIELD a, str, &str); \
|
||||
if (!b) goto exit; \
|
||||
}
|
||||
|
||||
|
||||
/* Define the OUT_STR function */
|
||||
#define M_VAR1ANT_DEFINE_OUT_STR(name, ...) \
|
||||
M_INLINE void M_F(name, _out_str)(FILE *f, \
|
||||
M_F(name,_ct) const el) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
M_ASSERT (f != NULL); \
|
||||
switch (el->type) { \
|
||||
case M_F(name, _EMPTY): fprintf(f, "@EMPTY@"); break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_OUT_STR_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
fputc ('@', f); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_OUT_STR_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
fprintf(f, "@" M_AS_STR(M_VAR1ANT_GET_FIELD a) "@"); \
|
||||
M_VAR1ANT_CALL_OUT_STR(a, f, el -> value . M_VAR1ANT_GET_FIELD a); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the IN_STR function */
|
||||
#define M_VAR1ANT_DEFINE_IN_STR(name, ...) \
|
||||
M_INLINE bool M_F(name, _in_str)(M_F(name,_ct) el, \
|
||||
FILE *f) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
M_ASSERT (f != NULL); \
|
||||
char variantTypeBuf[M_USE_IDENTIFIER_ALLOC+1]; \
|
||||
M_F(name, _reset)(el); \
|
||||
if (fgetc(f) != '@') return false; \
|
||||
/* First read the name of the type */ \
|
||||
bool b = true; \
|
||||
int c = fgetc(f); \
|
||||
unsigned int i = 0; \
|
||||
while (c != '@' && c != EOF && i < sizeof(variantTypeBuf) - 1) { \
|
||||
variantTypeBuf[i++] = (char) c; \
|
||||
c = fgetc(f); \
|
||||
} \
|
||||
if (c != '@') return false; \
|
||||
variantTypeBuf[i++] = 0; \
|
||||
M_ASSERT(i < sizeof(variantTypeBuf)); \
|
||||
/* In function of the type */ \
|
||||
if (strcmp(variantTypeBuf, "EMPTY") == 0) { \
|
||||
el->type = M_F(name, _EMPTY); \
|
||||
} \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_IN_STR_FUNC , name, __VA_ARGS__) \
|
||||
else { b = false; } \
|
||||
return b && (fgetc(f) == '@'); \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_IN_STR_FUNC(name, a) \
|
||||
else if (strcmp (variantTypeBuf, M_AS_STR(M_VAR1ANT_GET_FIELD a)) == 0) { \
|
||||
el->type = M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value); \
|
||||
M_VAR1ANT_CALL_INIT(a, el ->value . M_VAR1ANT_GET_FIELD a ); \
|
||||
b = M_VAR1ANT_CALL_IN_STR(a, el -> value . M_VAR1ANT_GET_FIELD a, f); \
|
||||
}
|
||||
|
||||
|
||||
/* Return the STRING version of a parameter name */
|
||||
#define M_VAR1ANT_STRINGIFY_NAME(a) \
|
||||
M_AS_STR(M_VAR1ANT_GET_FIELD a)
|
||||
|
||||
|
||||
/* Define the OUT_SERIAL function */
|
||||
#define M_VAR1ANT_DEFINE_OUT_SERIAL(name, ...) \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _out_serial)(m_serial_write_t f, \
|
||||
M_F(name,_ct) const el) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
const int field_max = M_NARGS(__VA_ARGS__); \
|
||||
static const char *const field_name[] = \
|
||||
{ M_REDUCE(M_VAR1ANT_STRINGIFY_NAME, M_ID, __VA_ARGS__) }; \
|
||||
M_ASSERT (f != NULL && f->m_interface != NULL); \
|
||||
m_serial_local_t local; \
|
||||
m_serial_return_code_t ret; \
|
||||
switch (el->type) { \
|
||||
case M_F(name, _EMPTY): \
|
||||
return f->m_interface->write_variant_start(local, f, field_name, field_max, -1); \
|
||||
break; \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_OUT_SERIAL_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
ret |= f->m_interface->write_variant_end(local, f); \
|
||||
return ret & M_SERIAL_FAIL; \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_OUT_SERIAL_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
ret = f->m_interface->write_variant_start(local, f, field_name, field_max, \
|
||||
M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value) -1); \
|
||||
M_VAR1ANT_CALL_OUT_SERIAL(a, f, el -> value . M_VAR1ANT_GET_FIELD a); \
|
||||
break;
|
||||
|
||||
|
||||
/* Define the IN_SERIAL function */
|
||||
#define M_VAR1ANT_DEFINE_IN_SERIAL(name, ...) \
|
||||
M_INLINE m_serial_return_code_t \
|
||||
M_F(name, _in_serial)(M_F(name,_ct) el, \
|
||||
m_serial_read_t f) { \
|
||||
M_VAR1ANT_CONTRACT(name, el); \
|
||||
const int field_max = M_NARGS(__VA_ARGS__); \
|
||||
static const char *const field_name[] = \
|
||||
{ M_REDUCE(M_VAR1ANT_STRINGIFY_NAME, M_ID, __VA_ARGS__) }; \
|
||||
M_ASSERT (f != NULL && f->m_interface != NULL); \
|
||||
m_serial_local_t local; \
|
||||
m_serial_return_code_t ret; \
|
||||
int id = -1; \
|
||||
M_F(name, _reset)(el); \
|
||||
ret = f->m_interface->read_variant_start(local, f, field_name, field_max, &id); \
|
||||
if (ret != M_SERIAL_OK_CONTINUE) return ret; \
|
||||
M_ASSERT (id >= 0 && id < field_max); \
|
||||
el->type = (enum M_F(name, _enum))(id+1); \
|
||||
switch (id+1) { \
|
||||
M_MAP2(M_VAR1ANT_DEFINE_IN_SERIAL_FUNC , name, __VA_ARGS__) \
|
||||
default: M_ASSUME(false); break; \
|
||||
} \
|
||||
if (ret == M_SERIAL_OK_DONE) \
|
||||
ret = f->m_interface->read_variant_end(local, f); \
|
||||
return ret; \
|
||||
}
|
||||
|
||||
#define M_VAR1ANT_DEFINE_IN_SERIAL_FUNC(name, a) \
|
||||
case M_C4(name, _, M_VAR1ANT_GET_FIELD a, _value): \
|
||||
M_VAR1ANT_CALL_INIT(a, el ->value . M_VAR1ANT_GET_FIELD a ); \
|
||||
ret = M_VAR1ANT_CALL_IN_SERIAL(a, el -> value . M_VAR1ANT_GET_FIELD a, f); \
|
||||
break; \
|
||||
|
||||
|
||||
/* Define the RESET function */
|
||||
#define M_VAR1ANT_DEFINE_RESET_FUNC(name, ...) \
|
||||
M_INLINE void M_F(name, _reset)(M_F(name,_ct) my) \
|
||||
{ \
|
||||
M_VAR1ANT_CONTRACT(name, my); \
|
||||
M_F(name, _clear)(my); \
|
||||
M_F(name, _init)(my); \
|
||||
} \
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* deferred evaluation of the oplist */
|
||||
#define M_VAR1ANT_OPLIST_P1(arg) M_VAR1ANT_OPLIST_P2 arg
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_VAR1ANT_OPLIST_P2(name, ...) \
|
||||
M_IF(M_REDUCE(M_OPLIST_P, M_AND, __VA_ARGS__))(M_VAR1ANT_OPLIST_P3, M_VAR1ANT_OPLIST_FAILURE)(name, __VA_ARGS__)
|
||||
|
||||
/* Prepare a clean compilation failure */
|
||||
#define M_VAR1ANT_OPLIST_FAILURE(name, ...) \
|
||||
((M_LIB_ERROR(ONE_ARGUMENT_OF_VARIANT_OPLIST_IS_NOT_AN_OPLIST, name, __VA_ARGS__)))
|
||||
|
||||
/* Define the oplist */
|
||||
#define M_VAR1ANT_OPLIST_P3(name, ...) \
|
||||
(INIT(M_F(name,_init)), \
|
||||
INIT_SET(M_F(name, _init_set)), \
|
||||
SET(M_F(name,_set)), \
|
||||
CLEAR(M_F(name, _clear)), \
|
||||
RESET(M_F(name, _reset)), \
|
||||
NAME(name), \
|
||||
TYPE(M_F(name,_ct)), \
|
||||
EMPTY_P(M_F(name,_empty_p)), \
|
||||
M_IF_METHOD_ALL(HASH, __VA_ARGS__)(HASH(M_F(name, _hash)),), \
|
||||
M_IF_METHOD_ALL(EQUAL, __VA_ARGS__)(EQUAL(M_F(name, _equal_p)),), \
|
||||
M_IF_METHOD_ALL(GET_STR, __VA_ARGS__)(GET_STR(M_F(name, _get_str)),), \
|
||||
M_IF_METHOD2_ALL(PARSE_STR, INIT, __VA_ARGS__)(PARSE_STR(M_F(name, _parse_str)),), \
|
||||
M_IF_METHOD2_ALL(IN_STR, INIT, __VA_ARGS__)(IN_STR(M_F(name, _in_str)),), \
|
||||
M_IF_METHOD_ALL(OUT_STR, __VA_ARGS__)(OUT_STR(M_F(name, _out_str)),), \
|
||||
M_IF_METHOD2_ALL(IN_SERIAL, INIT, __VA_ARGS__)(IN_SERIAL(M_F(name, _in_serial)),), \
|
||||
M_IF_METHOD_ALL(OUT_SERIAL, __VA_ARGS__)(OUT_SERIAL(M_F(name, _out_serial)),), \
|
||||
M_IF_METHOD_ALL(INIT_MOVE, __VA_ARGS__)(INIT_MOVE(M_F(name, _init_move)),), \
|
||||
M_IF_METHOD_ALL(INIT_MOVE, __VA_ARGS__)(MOVE(M_F(name, _move)),), \
|
||||
M_IF_METHOD_ALL(SWAP, __VA_ARGS__)(SWAP(M_F(name, _swap)),), \
|
||||
)
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
/* Macros for testing for method presence */
|
||||
#define M_VAR1ANT_TEST_METHOD_P2(method, f, t, op) \
|
||||
M_TEST_METHOD_P(method, op)
|
||||
#define M_VAR1ANT_TEST_METHOD_P(method, trio) \
|
||||
M_APPLY(M_VAR1ANT_TEST_METHOD_P2, method, M_OPFLAT trio)
|
||||
#define M_VAR1ANT_IF_ALL(method, ...) \
|
||||
M_IF(M_REDUCE2(M_VAR1ANT_TEST_METHOD_P, M_AND, method, __VA_ARGS__))
|
||||
|
||||
#define M_VAR1ANT_TEST_METHOD2_P2(method1, method2, f, t, op) \
|
||||
M_AND(M_TEST_METHOD_P(method1, op), M_TEST_METHOD_P(method2, op))
|
||||
#define M_VAR1ANT_TEST_METHOD2_P(method, trio) \
|
||||
M_APPLY(M_VAR1ANT_TEST_METHOD2_P2, M_PAIR_1 method, M_PAIR_2 method, M_OPFLAT trio)
|
||||
#define M_VAR1ANT_IF_ALL2(method1, method2, ...) \
|
||||
M_IF(M_REDUCE2(M_VAR1ANT_TEST_METHOD2_P, M_AND, (method1, method2), __VA_ARGS__))
|
||||
|
||||
|
||||
/********************************** INTERNAL *********************************/
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define VARIANT_DEF2 M_VARIANT_DEF2
|
||||
#define VARIANT_DEF2_AS M_VARIANT_DEF2_AS
|
||||
#define VARIANT_OPLIST M_VARIANT_OPLIST
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,698 +0,0 @@
|
||||
/*
|
||||
* M*LIB / WORKER - Extra worker interface
|
||||
*
|
||||
* Copyright (c) 2017-2023, Patrick Pelissier
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* + Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* + Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef MSTARLIB_WORKER_H
|
||||
#define MSTARLIB_WORKER_H
|
||||
|
||||
/* The User Code can define M_USE_WORKER to 0 to disable the use of workers.
|
||||
The macros / functions are then defined to only use one core.
|
||||
By default, the behavior is to use workers.
|
||||
*/
|
||||
#ifndef M_USE_WORKER
|
||||
# define M_USE_WORKER 1
|
||||
#endif
|
||||
|
||||
|
||||
#if M_USE_WORKER
|
||||
|
||||
#include "m-atomic.h"
|
||||
#include "m-buffer.h"
|
||||
#include "m-thread.h"
|
||||
|
||||
/* Include needed system header for detection of how many cores are available in the system */
|
||||
#if defined(_WIN32)
|
||||
# include <sysinfoapi.h>
|
||||
#elif (defined(__APPLE__) && defined(__MACH__)) \
|
||||
|| defined(__DragonFly__) || defined(__FreeBSD__) \
|
||||
|| defined(__NetBSD__) || defined(__OpenBSD__)
|
||||
# include <sys/param.h>
|
||||
# include <sys/sysctl.h>
|
||||
# define M_USE_WORKER_SYSCTL 1
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* Support for CLANG block since CLANG doesn't support nested function.
|
||||
M-WORKER uses its 'blocks' extension instead, but it is not compatible
|
||||
with function.
|
||||
So you need to compile with "-fblocks" and link with "-lBlocksRuntime"
|
||||
if you use clang & want to use the MACRO version.
|
||||
|
||||
if C++, it will use Lambda function (and std::function) instead
|
||||
(It doesn't support pre-C++11 compiler).
|
||||
|
||||
Otherwise go with nested function (GCC) for the MACRO version.
|
||||
|
||||
This behavior can be overriden by User Code by defining to 1 or 0 the
|
||||
following macros:
|
||||
* M_USE_WORKER_CPP_FUNCTION
|
||||
* M_USE_WORKER_CLANG_BLOCK
|
||||
*/
|
||||
|
||||
#if defined(__cplusplus) && !defined(M_USE_WORKER_CPP_FUNCTION)
|
||||
# define M_USE_WORKER_CPP_FUNCTION 1
|
||||
# include <functional>
|
||||
#elif defined(__has_extension) && !defined(M_USE_WORKER_CLANG_BLOCK)
|
||||
# if __has_extension(blocks)
|
||||
# define M_USE_WORKER_CLANG_BLOCK 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef M_USE_WORKER_CLANG_BLOCK
|
||||
# define M_USE_WORKER_CLANG_BLOCK 0
|
||||
#endif
|
||||
#ifndef M_USE_WORKER_CPP_FUNCTION
|
||||
# define M_USE_WORKER_CPP_FUNCTION 0
|
||||
#endif
|
||||
|
||||
/* Control that not both options are selected at the same time.
|
||||
Note: there are not really incompatible, but if we use C++ we shall go to
|
||||
lambda directly (there is no need to support blocks). */
|
||||
#if M_USE_WORKER_CLANG_BLOCK && M_USE_WORKER_CPP_FUNCTION
|
||||
# error M_USE_WORKER_CPP_FUNCTION and M_USE_WORKER_CLANG_BLOCK are both defined. This is not supported.
|
||||
#endif
|
||||
|
||||
M_BEGIN_PROTECTED_CODE
|
||||
|
||||
/* Definition of a work order */
|
||||
typedef struct m_work3r_order_s {
|
||||
struct m_worker_sync_s *block; // Reference to the shared Synchronization block
|
||||
void * data; // The work order data
|
||||
void (*func) (void *data); // The work order function (for GCC)
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
void (^blockFunc)(void *data); // The work order function (block for clang)
|
||||
#endif
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
std::function<void(void*)> function; // The work order function (for C++)
|
||||
#endif
|
||||
} m_work3r_order_ct;
|
||||
|
||||
/* Define the macros needed to initialize an order.
|
||||
* * MACRO to be used to send an empty order to stop the thread
|
||||
* * MACRO to complete the not-used fields
|
||||
*/
|
||||
#if M_USE_WORKER_CLANG_BLOCK || M_USE_WORKER_CPP_FUNCTION
|
||||
# define M_WORK3R_EMPTY_ORDER { NULL, NULL, NULL, NULL }
|
||||
# define M_WORK3R_EXTRA_ORDER , NULL
|
||||
#else
|
||||
# define M_WORK3R_EMPTY_ORDER { NULL, NULL, NULL }
|
||||
# define M_WORK3R_EXTRA_ORDER
|
||||
#endif
|
||||
|
||||
/* As it is C++, it uses std::function, M_POD_OPLIST
|
||||
is not sufficient for initialization of the structure.
|
||||
So let's use C++ constructor, destructor and copy constructor */
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
# define M_WORK3R_CPP_INIT(x) (new (&(x)) m_work3r_order_ct())
|
||||
# define M_WORK3R_CPP_INIT_SET(x, y) (new (&(x)) m_work3r_order_ct(y))
|
||||
# define M_WORK3R_CPP_SET(x, y) ((x) = (y))
|
||||
# define M_WORK3R_CPP_CLEAR(x) ((&(x))->~m_work3r_order_ct())
|
||||
# define M_WORK3R_CPP_INIT_MOVE(x,y) (new (&(x)) m_work3r_order_ct(y), ((&(y))->~m_work3r_order_ct()))
|
||||
# define M_WORK3R_OPLIST \
|
||||
(INIT(M_WORK3R_CPP_INIT), INIT_SET(M_WORK3R_CPP_INIT_SET), \
|
||||
SET(M_WORK3R_CPP_SET), CLEAR(M_WORK3R_CPP_CLEAR), INIT_MOVE(M_WORK3R_CPP_INIT_MOVE) )
|
||||
#else
|
||||
# define M_WORK3R_OPLIST M_POD_OPLIST
|
||||
#endif
|
||||
|
||||
/* Definition of the identity of a worker thread */
|
||||
typedef struct m_work3r_thread_s {
|
||||
m_thread_t id;
|
||||
} m_work3r_thread_ct;
|
||||
|
||||
/* Definition of the queue that will record the work orders */
|
||||
BUFFER_DEF(m_work3r_queue, m_work3r_order_ct, 0,
|
||||
BUFFER_QUEUE|BUFFER_UNBLOCKING_PUSH|BUFFER_BLOCKING_POP|BUFFER_THREAD_SAFE|BUFFER_DEFERRED_POP, M_WORK3R_OPLIST)
|
||||
|
||||
/* Definition the global pool of workers */
|
||||
typedef struct m_worker_s {
|
||||
/* The work order queue */
|
||||
m_work3r_queue_t queue_g;
|
||||
|
||||
/* The table of available workers */
|
||||
m_work3r_thread_ct *worker;
|
||||
|
||||
/* Number of workers in the table */
|
||||
unsigned int numWorker_g;
|
||||
|
||||
/* The global reset function */
|
||||
void (*resetFunc_g)(void);
|
||||
|
||||
/* The global clear function */
|
||||
void (*clearFunc_g)(void);
|
||||
|
||||
m_mutex_t lock;
|
||||
m_cond_t a_thread_ends; // EVENT: A worker has ended
|
||||
|
||||
} m_worker_t[1];
|
||||
|
||||
/* Definition of the synchronization point for workers */
|
||||
typedef struct m_worker_sync_s {
|
||||
atomic_int num_spawn; // Number of spawned workers accord this synchronization point
|
||||
atomic_int num_terminated_spawn; // Number of terminated spawned workers
|
||||
struct m_worker_s *worker; // Reference to the pool of workers
|
||||
} m_worker_sync_t[1];
|
||||
|
||||
|
||||
/* Extend m_worker_spawn by defining a specialization function
|
||||
with the given arguments.
|
||||
Generate the needed encapsulation for the user.
|
||||
USAGE: name, oplists of arguments */
|
||||
#define M_WORKER_SPAWN_DEF2(name, ...) \
|
||||
M_BEGIN_PROTECTED_CODE \
|
||||
M_WORK3R_SPAWN_EXTEND_P1( (name, M_MAP_C(M_WORK3R_SPAWN_EXTEND_P0, __VA_ARGS__) ) ) \
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
/* Output a valid oplist with the given type.
|
||||
input is (fieldname, type) or (fieldname, type, oplist)
|
||||
Output shall be : M_OPEXTEND(M_GLOBAL_OPLIST_OR_DEF(type_or_oplist)(), TYPE(type)) / M_OPEXTEND(oplist, TYPE(type))
|
||||
*/
|
||||
#define M_WORK3R_SPAWN_EXTEND_P0(...) M_BY_NARGS(M_WORK3R_SPAWN_EXTEND_P0, M_ID __VA_ARGS__) __VA_ARGS__
|
||||
#define M_WORK3R_SPAWN_EXTEND_P0__2(field, type) M_OPEXTEND(M_GLOBAL_OPLIST_OR_DEF(type)(), TYPE(type))
|
||||
#define M_WORK3R_SPAWN_EXTEND_P0__3(field, type, oplist) M_IF_OPLIST(oplist)(M_WORK3R_SPAWN_EXTEND_P0__3_OK, M_WORK3R_SPAWN_EXTEND_P0__3_KO)(field, type, oplist)
|
||||
#define M_WORK3R_SPAWN_EXTEND_P0__3_OK(field, type, oplist) M_OPEXTEND(oplist, TYPE(type))
|
||||
#define M_WORK3R_SPAWN_EXTEND_P0__3_KO(field, type, oplist) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(M_WORKER_SPAWN_EXTEND): the argument is not a valid oplist: " M_MAP(M_AS_STR, oplist))
|
||||
|
||||
/* Deferred evaluation for the definition,
|
||||
so that all arguments are evaluated before further expansion */
|
||||
#define M_WORK3R_SPAWN_EXTEND_P1(arg) M_ID( M_WORK3R_SPAWN_EXTEND_P2 arg )
|
||||
|
||||
/* Validate the oplist before going further */
|
||||
#define M_WORK3R_SPAWN_EXTEND_P2(name, ...) \
|
||||
M_IF(M_REDUCE(M_OPLIST_P, M_AND, __VA_ARGS__)) \
|
||||
(M_WORK3R_SPAWN_EXTEND_P3, M_WORK3R_SPAWN_EXTEND_FAILURE)(name, __VA_ARGS__)
|
||||
|
||||
/* Stop processing with a compilation failure */
|
||||
#define M_WORK3R_SPAWN_EXTEND_FAILURE(name, ...) \
|
||||
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, \
|
||||
"(M_WORKER_SPAWN_EXTEND): at least one of the given argument is not a valid oplist: " \
|
||||
M_MAP(M_AS_STR, __VA_ARGS__))
|
||||
|
||||
/* Define the extension of spawn */
|
||||
#define M_WORK3R_SPAWN_EXTEND_P3(name, ...) \
|
||||
M_WORK3R_SPAWN_EXTEND_DEF_TYPE(name, __VA_ARGS__) \
|
||||
M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK(name, __VA_ARGS__) \
|
||||
M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE(name, __VA_ARGS__) \
|
||||
|
||||
/* Define the type */
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_TYPE(name, ...) \
|
||||
typedef void (*M_C3(m_worker_,name, _callback_ct))(M_MAP_C(M_WORK3R_SPAWN_EXTEND_DEF_TYPE_TYPE, __VA_ARGS__)); \
|
||||
struct M_C3(m_worker_, name, _s){ \
|
||||
M_C3(m_worker_, name, _callback_ct) callback; \
|
||||
M_MAP3(M_WORK3R_SPAWN_EXTEND_DEF_TYPE_FIELD, data, __VA_ARGS__) \
|
||||
};
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_TYPE_FIELD(data, num, oplist) \
|
||||
M_GET_TYPE oplist M_C(field, num);
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_TYPE_TYPE(oplist) \
|
||||
M_GET_TYPE oplist
|
||||
|
||||
/* Define the callback */
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK(name, ...) \
|
||||
M_INLINE void \
|
||||
M_C3(m_work3r_, name, _clear)(struct M_C3(m_worker_, name, _s) *p) \
|
||||
{ \
|
||||
M_MAP3(M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK_CLEAR, data, __VA_ARGS__) \
|
||||
/* TODO: Overload */ \
|
||||
M_MEMORY_DEL(p); \
|
||||
} \
|
||||
\
|
||||
M_INLINE void \
|
||||
M_C3(m_work3r_, name, _callback)(void *data) \
|
||||
{ \
|
||||
struct M_C3(m_worker_, name, _s) *p = (struct M_C3(m_worker_, name, _s) *) data; \
|
||||
(*p->callback)( \
|
||||
M_MAP3_C(M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK_FIELD, data, __VA_ARGS__) \
|
||||
); \
|
||||
M_C3(m_work3r_, name, _clear)(p); \
|
||||
}
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK_FIELD(data, num, oplist) \
|
||||
p->M_C(field, num)
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_CALLBACK_CLEAR(data, num, oplist) \
|
||||
M_CALL_CLEAR(oplist, p->M_C(field, num)) ;
|
||||
|
||||
/* Define the emplace like spawn method */
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE(name, ...) \
|
||||
M_INLINE void \
|
||||
M_C(m_worker_spawn_, name)(m_worker_sync_t block, M_C3(m_worker_, name, _callback_ct) callback, \
|
||||
M_MAP3_C(M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD, data, __VA_ARGS__) \
|
||||
) \
|
||||
{ \
|
||||
if (!m_work3r_queue_full_p(block->worker->queue_g)) { \
|
||||
struct M_C3(m_worker_, name, _s) *p = M_MEMORY_ALLOC ( struct M_C3(m_worker_, name, _s)); \
|
||||
if (M_UNLIKELY_NOMEM(p == NULL)) { \
|
||||
M_MEMORY_FULL(sizeof (struct M_C3(m_worker_, name, _s))); \
|
||||
} \
|
||||
p->callback = callback; \
|
||||
M_MAP3(M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD_COPY, data, __VA_ARGS__) \
|
||||
const m_work3r_order_ct w = { block, p, M_C3(m_work3r_, name, _callback) M_WORK3R_EXTRA_ORDER }; \
|
||||
if (m_work3r_queue_push (block->worker->queue_g, w) == true) { \
|
||||
atomic_fetch_add (&block->num_spawn, 1); \
|
||||
return; \
|
||||
} \
|
||||
/* No worker available now. Call the function ourself */ \
|
||||
/* But before clear the allocated data */ \
|
||||
M_C3(m_work3r_, name, _clear)(p); \
|
||||
} \
|
||||
/* No worker available. Call the function ourself */ \
|
||||
(*callback) ( \
|
||||
M_MAP3_C(M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD_ALONE, data, __VA_ARGS__) \
|
||||
); \
|
||||
}
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD(data, num, oplist) \
|
||||
M_GET_TYPE oplist M_C(param, num)
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD_COPY(data, num, oplist) \
|
||||
M_CALL_INIT_SET(oplist, p-> M_C(field, num), M_C(param, num) );
|
||||
|
||||
#define M_WORK3R_SPAWN_EXTEND_DEF_EMPLACE_FIELD_ALONE(data, num, oplist) \
|
||||
M_C(param, num)
|
||||
|
||||
|
||||
|
||||
/* Return the number of CPU cores available in the system.
|
||||
Works for WINDOWS, MACOS, *BSD, LINUX.
|
||||
*/
|
||||
M_INLINE int
|
||||
m_work3r_get_cpu_count(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
M_ASSERT(sysinfo.dwNumberOfProcessors <= INT_MAX);
|
||||
return (int) sysinfo.dwNumberOfProcessors;
|
||||
#elif defined(M_USE_WORKER_SYSCTL)
|
||||
int nm[2];
|
||||
int count = 0;
|
||||
size_t len = sizeof (count);
|
||||
nm[0] = CTL_HW;
|
||||
nm[1] = HW_NCPU;
|
||||
sysctl(nm, 2, &count, &len, NULL, 0);
|
||||
return M_MAX(1, count);
|
||||
#elif defined (_SC_NPROCESSORS_ONLN)
|
||||
return (int) sysconf(_SC_NPROCESSORS_ONLN);
|
||||
#elif defined (_SC_NPROCESSORS_CONF)
|
||||
return (int) sysconf(_SC_NPROCESSORS_CONF);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// (INTERNAL) Debug support for workers
|
||||
#if 1
|
||||
#define M_WORK3R_DEBUG(...) (void) 0
|
||||
#else
|
||||
#define M_WORK3R_DEBUG(...) printf(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
/* Execute the registered work order **synchronously** */
|
||||
M_INLINE void
|
||||
m_work3r_exec(m_work3r_order_ct *w)
|
||||
{
|
||||
M_ASSERT (w!= NULL && w->block != NULL);
|
||||
M_WORK3R_DEBUG ("Starting thread with data %p\n", w->data);
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
M_WORK3R_DEBUG ("Running %s f=%p b=%p\n", (w->func == NULL) ? "Blocks" : "Function", w->func, w->blockFunc);
|
||||
if (w->func == NULL)
|
||||
w->blockFunc(w->data);
|
||||
else
|
||||
#endif
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
M_WORK3R_DEBUG ("Running %s f=%p b=%p\n", (w->function == NULL) ? "Lambda" : "Function", w->func, w->blockFunc);
|
||||
if (w->function)
|
||||
w->function(w->data);
|
||||
else
|
||||
#endif
|
||||
w->func(w->data);
|
||||
/* Increment the number of terminated work order for the synchronous point */
|
||||
atomic_fetch_add (&w->block->num_terminated_spawn, 1);
|
||||
}
|
||||
|
||||
|
||||
/* The worker thread main loop*/
|
||||
M_INLINE void
|
||||
m_work3r_thread(void *arg)
|
||||
{
|
||||
// Get back the given argument
|
||||
struct m_worker_s *g = M_ASSIGN_CAST(struct m_worker_s *, arg);
|
||||
while (true) {
|
||||
m_work3r_order_ct w;
|
||||
// If needed, reset the global state of the worker
|
||||
if (g->resetFunc_g != NULL) {
|
||||
g->resetFunc_g();
|
||||
}
|
||||
// Waiting for data
|
||||
M_WORK3R_DEBUG ("Waiting for data (queue: %lu / %lu)\n", m_work3r_queue_size(g->queue_g), m_work3r_queue_capacity(g->queue_g));
|
||||
m_work3r_queue_pop(&w, g->queue_g);
|
||||
// We received a work order
|
||||
// Note: that the work order is still present in the queue
|
||||
// preventing further work order to be pushed in the queue until it finishes doing the work
|
||||
// If a stop request is received, terminate the thread
|
||||
if (w.block == NULL) break;
|
||||
// Execute the work order
|
||||
m_work3r_exec(&w);
|
||||
// Consumme fully the work order in the queue
|
||||
m_work3r_queue_pop_release(g->queue_g);
|
||||
// Signal that a worker has finished.
|
||||
m_mutex_lock(g->lock);
|
||||
m_cond_broadcast(g->a_thread_ends);
|
||||
m_mutex_unlock(g->lock);
|
||||
}
|
||||
// If needed, clear global state of the thread
|
||||
if (g->clearFunc_g != NULL) {
|
||||
g->clearFunc_g();
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialization of the worker module (constructor)
|
||||
Input:
|
||||
@numWorker: number of worker to create (0=autodetect, -1=2*autodetect)
|
||||
@extraQueue: number of extra work order we can get if all workers are full
|
||||
@resetFunc: function to reset the state of a worker between work orders (or NULL if none)
|
||||
@clearFunc: function to clear the state of a worker before terminaning (or NULL if none)
|
||||
*/
|
||||
M_INLINE void
|
||||
m_worker_init(m_worker_t g, int numWorker, unsigned int extraQueue, void (*resetFunc)(void), void (*clearFunc)(void))
|
||||
{
|
||||
M_ASSERT (numWorker >= -1);
|
||||
// Auto compute number of workers if the argument is 0
|
||||
if (numWorker <= 0)
|
||||
numWorker = (1 + (numWorker == -1))*m_work3r_get_cpu_count()-1;
|
||||
M_WORK3R_DEBUG ("Starting queue with: %d\n", numWorker + extraQueue);
|
||||
// Initialization
|
||||
// numWorker can still be 0 if it is a single core cpu (no worker available)
|
||||
M_ASSERT(numWorker >= 0);
|
||||
size_t numWorker_st = (size_t) numWorker;
|
||||
g->worker = M_MEMORY_REALLOC(m_work3r_thread_ct, NULL, numWorker_st);
|
||||
if (M_UNLIKELY_NOMEM (g->worker == NULL)) {
|
||||
M_MEMORY_FULL(sizeof (m_work3r_thread_ct) * numWorker_st);
|
||||
return;
|
||||
}
|
||||
m_work3r_queue_init(g->queue_g, numWorker_st + extraQueue);
|
||||
g->numWorker_g = (unsigned int) numWorker_st;
|
||||
g->resetFunc_g = resetFunc;
|
||||
g->clearFunc_g = clearFunc;
|
||||
m_mutex_init(g->lock);
|
||||
m_cond_init(g->a_thread_ends);
|
||||
|
||||
// Create & start the workers
|
||||
for(size_t i = 0; i < numWorker_st; i++) {
|
||||
m_thread_create(g->worker[i].id, m_work3r_thread, M_ASSIGN_CAST(void*, g));
|
||||
}
|
||||
}
|
||||
/* Initialization of the worker module (constructor)
|
||||
Provide default values for the arguments.
|
||||
Input:
|
||||
@numWorker: number of worker to create (0=autodetect, -1=2*autodetect)
|
||||
@extraQueue: number of extra work order we can get if all workers are full
|
||||
@resetFunc: function to reset the state of a worker between work orders (optional)
|
||||
@clearFunc: function to clear the state of a worker before terminaning (optional)
|
||||
*/
|
||||
#define m_worker_init(...) m_worker_init(M_DEFAULT_ARGS(5, (0, 0, NULL, NULL), __VA_ARGS__))
|
||||
|
||||
/* Clear of the worker module (destructor) */
|
||||
M_INLINE void
|
||||
m_worker_clear(m_worker_t g)
|
||||
{
|
||||
M_ASSERT (m_work3r_queue_empty_p (g->queue_g));
|
||||
// Push the terminate order on the queue
|
||||
for(unsigned int i = 0; i < g->numWorker_g; i++) {
|
||||
m_work3r_order_ct w = M_WORK3R_EMPTY_ORDER;
|
||||
// Normaly all worker threads shall be waiting at this
|
||||
// stage, so all push won't block as the queue is empty.
|
||||
// But for robustness, let's wait.
|
||||
m_work3r_queue_push_blocking (g->queue_g, w, true);
|
||||
}
|
||||
// Wait for thread terminanison
|
||||
for(unsigned int i = 0; i < g->numWorker_g; i++) {
|
||||
m_thread_join(g->worker[i].id);
|
||||
}
|
||||
// Clear memory
|
||||
M_MEMORY_FREE(g->worker);
|
||||
m_mutex_clear(g->lock);
|
||||
m_cond_clear(g->a_thread_ends);
|
||||
m_work3r_queue_clear(g->queue_g);
|
||||
}
|
||||
|
||||
/* Start a new collaboration between workers of pool 'g'
|
||||
by defining the synchronization point 'block' */
|
||||
M_INLINE void
|
||||
m_worker_start(m_worker_sync_t block, m_worker_t g)
|
||||
{
|
||||
atomic_init (&block->num_spawn, 0);
|
||||
atomic_init (&block->num_terminated_spawn, 0);
|
||||
block->worker = g;
|
||||
}
|
||||
|
||||
/* Spawn the given work order to workers if possible,
|
||||
or do it ourself if no worker is available.
|
||||
The synchronization point is defined a 'block'
|
||||
The work order if composed of the function 'func' and its 'data'
|
||||
*/
|
||||
M_INLINE void
|
||||
m_worker_spawn(m_worker_sync_t block, void (*func)(void *data), void *data)
|
||||
{
|
||||
const m_work3r_order_ct w = { block, data, func M_WORK3R_EXTRA_ORDER };
|
||||
if (M_UNLIKELY (!m_work3r_queue_full_p(block->worker->queue_g))
|
||||
&& m_work3r_queue_push (block->worker->queue_g, w) == true) {
|
||||
M_WORK3R_DEBUG ("Sending data to thread: %p (block: %d / %d)\n", data, block->num_spawn, block->num_terminated_spawn);
|
||||
atomic_fetch_add (&block->num_spawn, 1);
|
||||
return;
|
||||
}
|
||||
M_WORK3R_DEBUG ("Running data ourself: %p\n", data);
|
||||
/* No worker available. Call the function ourself */
|
||||
(*func) (data);
|
||||
}
|
||||
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
/* Spawn or not the given work order to workers,
|
||||
or do it ourself if no worker is available */
|
||||
M_INLINE void
|
||||
m_work3r_spawn_block(m_worker_sync_t block, void (^func)(void *data), void *data)
|
||||
{
|
||||
const m_work3r_order_ct w = { block, data, NULL, func };
|
||||
if (M_UNLIKELY (!m_work3r_queue_full_p(block->worker->queue_g))
|
||||
&& m_work3r_queue_push (block->worker->queue_g, w) == true) {
|
||||
M_WORK3R_DEBUG ("Sending data to thread as block: %p (block: %d / %d)\n", data, block->num_spawn, block->num_terminated_spawn);
|
||||
atomic_fetch_add (&block->num_spawn, 1);
|
||||
return;
|
||||
}
|
||||
M_WORK3R_DEBUG ("Running data ourself as block: %p\n", data);
|
||||
/* No worker available. Call the function ourself */
|
||||
func (data);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if M_USE_WORKER_CPP_FUNCTION
|
||||
/* Spawn or not the given work order to workers,
|
||||
or do it ourself if no worker is available */
|
||||
M_INLINE void
|
||||
m_work3r_spawn_function(m_worker_sync_t block, std::function<void(void *data)> func, void *data)
|
||||
{
|
||||
const m_work3r_order_ct w = { block, data, NULL, func };
|
||||
if (M_UNLIKELY (!m_work3r_queue_full_p(block->worker->queue_g))
|
||||
&& m_work3r_queue_push (block->worker->queue_g, w) == true) {
|
||||
M_WORK3R_DEBUG ("Sending data to thread as block: %p (block: %d / %d)\n", data, block->num_spawn, block->num_terminated_spawn);
|
||||
atomic_fetch_add (&block->num_spawn, 1);
|
||||
return;
|
||||
}
|
||||
M_WORK3R_DEBUG ("Running data ourself as block: %p\n", data);
|
||||
/* No worker available. Call the function ourself */
|
||||
func (data);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Test if all work orders of the given synchronization point are finished */
|
||||
M_INLINE bool
|
||||
m_worker_sync_p(m_worker_sync_t block)
|
||||
{
|
||||
/* If the number of spawns is greated than the number
|
||||
of terminated spawns, some spawns are still working.
|
||||
So wait for terminaison */
|
||||
return (atomic_load(&block->num_spawn) == atomic_load (&block->num_terminated_spawn));
|
||||
}
|
||||
|
||||
/* Wait for all work orders of the given synchronization point to be finished */
|
||||
M_INLINE void
|
||||
m_worker_sync(m_worker_sync_t block)
|
||||
{
|
||||
M_WORK3R_DEBUG ("Waiting for thread terminasion.\n");
|
||||
// Fast case: all workers have finished
|
||||
if (m_worker_sync_p(block)) return;
|
||||
// Slow case: perform a locked wait to put this thread to waiting state
|
||||
m_mutex_lock(block->worker->lock);
|
||||
while (!m_worker_sync_p(block)) {
|
||||
m_cond_wait(block->worker->a_thread_ends, block->worker->lock);
|
||||
}
|
||||
m_mutex_unlock(block->worker->lock);
|
||||
}
|
||||
|
||||
/* Flush any work order in the queue ourself if some remains.*/
|
||||
M_INLINE void
|
||||
m_worker_flush(m_worker_t g)
|
||||
{
|
||||
m_work3r_order_ct w;
|
||||
while (m_work3r_queue_pop_blocking (&w, g->queue_g, false) == true) {
|
||||
m_work3r_exec(&w);
|
||||
m_work3r_queue_pop_release(g->queue_g);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Return the number of workers */
|
||||
M_INLINE size_t
|
||||
m_worker_count(m_worker_t g)
|
||||
{
|
||||
return g->numWorker_g + 1;
|
||||
}
|
||||
|
||||
/* Spawn the 'core' block computation into another thread if
|
||||
a worker thread is available. Compute it in the current thread otherwise.
|
||||
'block' shall be the initialised synchronised block for all threads.
|
||||
'input' is the list of input variables of the 'core' block within "( )"
|
||||
'output' is the list of output variables of the 'core' block within "( )"
|
||||
Output variables are only available after a synchronisation block.
|
||||
TODO: Support oplist for input & outputs parameters
|
||||
*/
|
||||
#if M_USE_WORKER_CLANG_BLOCK
|
||||
#define M_WORKER_SPAWN(_block, _input, _core, _output) \
|
||||
M_WORK3R_DEF_DATA(_input, _output) \
|
||||
M_WORK3R_DEF_SUBBLOCK(_input, _output, _core) \
|
||||
m_work3r_spawn_block ((_block), M_WORK3R_SPAWN_SUBFUNC_NAME, &M_WORK3R_SPAWN_DATA_NAME)
|
||||
#elif M_USE_WORKER_CPP_FUNCTION
|
||||
// TODO: Explicit pass all arguments by reference.
|
||||
#define M_WORKER_SPAWN(_block, _input, _core, _output) \
|
||||
m_work3r_spawn_function ((_block), [&](void *param) {(void)param ; _core } , NULL)
|
||||
#else
|
||||
#define M_WORKER_SPAWN(_block, _input, _core, _output) \
|
||||
M_WORK3R_DEF_DATA(_input, _output) \
|
||||
M_WORK3R_DEF_SUBFUNC(_input, _output, _core) \
|
||||
m_worker_spawn ((_block), M_WORK3R_SPAWN_SUBFUNC_NAME, &M_WORK3R_SPAWN_DATA_NAME)
|
||||
#endif
|
||||
|
||||
#define M_WORK3R_SPAWN_STRUCT_NAME M_C(m_work3r_data_s_, __LINE__)
|
||||
#define M_WORK3R_SPAWN_DATA_NAME M_C(m_work3r_data_, __LINE__)
|
||||
#define M_WORK3R_SPAWN_SUBFUNC_NAME M_C(m_work3r_subfunc_, __LINE__)
|
||||
#define M_WORK3R_DEF_DATA(_input, _output) \
|
||||
struct M_WORK3R_SPAWN_STRUCT_NAME { \
|
||||
M_WORK3R_DEF_DATA_INPUT _input \
|
||||
M_IF_EMPTY _output ( , M_WORK3R_DEF_DATA_OUTPUT _output) \
|
||||
} M_WORK3R_SPAWN_DATA_NAME = { \
|
||||
M_WORK3R_INIT_DATA_INPUT _input \
|
||||
M_IF_EMPTY _output (, M_WORK3R_INIT_DATA_OUTPUT _output) \
|
||||
};
|
||||
#define M_WORK3R_DEF_SINGLE_INPUT(var) __typeof__(var) var;
|
||||
#define M_WORK3R_DEF_DATA_INPUT(...) \
|
||||
M_MAP(M_WORK3R_DEF_SINGLE_INPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_DEF_SINGLE_OUTPUT(var) \
|
||||
__typeof__(var) *M_C(var, _ptr);
|
||||
#define M_WORK3R_DEF_DATA_OUTPUT(...) \
|
||||
M_MAP(M_WORK3R_DEF_SINGLE_OUTPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_INIT_SINGLE_INPUT(var) \
|
||||
.var = var,
|
||||
#define M_WORK3R_INIT_DATA_INPUT(...) \
|
||||
M_MAP(M_WORK3R_INIT_SINGLE_INPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_INIT_SINGLE_OUTPUT(var) \
|
||||
.M_C(var, _ptr) = &var,
|
||||
#define M_WORK3R_INIT_DATA_OUTPUT(...) \
|
||||
M_MAP(M_WORK3R_INIT_SINGLE_OUTPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_DEF_SUBFUNC(_input, _output, _core) \
|
||||
__extension__ auto void M_WORK3R_SPAWN_SUBFUNC_NAME(void *) ; \
|
||||
__extension__ void M_WORK3R_SPAWN_SUBFUNC_NAME(void *_data) \
|
||||
{ \
|
||||
struct M_WORK3R_SPAWN_STRUCT_NAME *_s_data = _data ; \
|
||||
M_WORK3R_INIT_LOCAL_INPUT _input \
|
||||
M_IF_EMPTY _output ( , M_WORK3R_INIT_LOCAL_OUTPUT _output) \
|
||||
do { _core } while (0); \
|
||||
M_IF_EMPTY _output ( , M_WORK3R_PROPAGATE_LOCAL_OUTPUT _output) \
|
||||
};
|
||||
#define M_WORK3R_DEF_SUBBLOCK(_input, _output, _core) \
|
||||
void (^M_WORK3R_SPAWN_SUBFUNC_NAME) (void *) = ^ void (void * _data) \
|
||||
{ \
|
||||
struct M_WORK3R_SPAWN_STRUCT_NAME *_s_data = _data ; \
|
||||
M_WORK3R_INIT_LOCAL_INPUT _input \
|
||||
M_IF_EMPTY _output ( , M_WORK3R_INIT_LOCAL_OUTPUT _output) \
|
||||
do { _core } while (0); \
|
||||
M_IF_EMPTY _output ( , M_WORK3R_PROPAGATE_LOCAL_OUTPUT _output) \
|
||||
};
|
||||
#define M_WORK3R_INIT_SINGLE_LOCAL_INPUT(var) \
|
||||
__typeof__(var) var = _s_data->var;
|
||||
#define M_WORK3R_INIT_LOCAL_INPUT(...) \
|
||||
M_MAP(M_WORK3R_INIT_SINGLE_LOCAL_INPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_INIT_SINGLE_LOCAL_OUTPUT(var) \
|
||||
__typeof__(var) var;
|
||||
#define M_WORK3R_INIT_LOCAL_OUTPUT(...) \
|
||||
M_MAP(M_WORK3R_INIT_SINGLE_LOCAL_OUTPUT, __VA_ARGS__)
|
||||
#define M_WORK3R_PROPAGATE_SINGLE_OUTPUT(var) \
|
||||
*(_s_data->M_C(var, _ptr)) = var;
|
||||
#define M_WORK3R_PROPAGATE_LOCAL_OUTPUT(...) \
|
||||
M_MAP(M_WORK3R_PROPAGATE_SINGLE_OUTPUT, __VA_ARGS__)
|
||||
|
||||
M_END_PROTECTED_CODE
|
||||
|
||||
#else /* M_USE_WORKER */
|
||||
|
||||
/* Define empty types and empty functions to not use any worker */
|
||||
|
||||
typedef struct m_worker_block_s {
|
||||
int x;
|
||||
} m_worker_sync_t[1];
|
||||
|
||||
typedef struct m_worker_s {
|
||||
int x;
|
||||
} m_worker_t[1];
|
||||
|
||||
#define m_worker_init(g, numWorker, extraQueue, resetFunc) do { (void) g; } while (0)
|
||||
#define m_worker_clear(g) do { (void) g; } while (0)
|
||||
#define m_worker_start(b, w) do { (void) b; } while (0)
|
||||
#define m_worker_spawn(b, f, d) do { f(d); } while (0)
|
||||
#define m_worker_sync_p(b) true
|
||||
#define m_worker_sync(b) do { (void) b; } while (0)
|
||||
#define m_worker_count(w) 1
|
||||
#define m_worker_flush(w) do { (void) w; } while (0)
|
||||
#define M_WORKER_SPAWN(b, i, c, o) do { c } while (0)
|
||||
|
||||
#endif /* M_USE_WORKER */
|
||||
|
||||
|
||||
#if M_USE_SMALL_NAME
|
||||
#define worker_t m_worker_t
|
||||
#define worker_sync_t m_worker_sync_t
|
||||
#define worker_init m_worker_init
|
||||
#define worker_clear m_worker_clear
|
||||
#define worker_start m_worker_start
|
||||
#define worker_spawn m_worker_spawn
|
||||
#define worker_sync_p m_worker_sync_p
|
||||
#define worker_sync m_worker_sync
|
||||
#define worker_count m_worker_count
|
||||
#define worker_flush m_worker_flush
|
||||
#define WORKER_SPAWN M_WORKER_SPAWN
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user