mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-18 10:53:17 +00:00
248 lines
9.6 KiB
C
248 lines
9.6 KiB
C
/*
|
|
* 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
|