LCOV - code coverage report
Current view: top level - util/tmpl - fd_pool_para.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 167 488 34.2 %
Date: 2026-10-22 18:10:43 Functions: 75 1989 3.8 %

          Line data    Source code
       1             : /* Generate prototypes, inlines and/or implementations for concurrent
       2             :    persistent shared element pools.  A pool can hold a practically
       3             :    unbounded number of elements.  Acquiring an element from and
       4             :    releasing an element to a pool are typically fast O(1) time.
       5             :    Requires small O(1) space per element.
       6             : 
       7             :    The current implementation is based on a lockfree stack.  Acquire and
       8             :    release are done via atomic compare-and-swap of the stack top.  As
       9             :    such, concurrent usage requires FD_HAS_ATOMIC support (this can still
      10             :    be used on platforms without FD_HAS_ATOMIC but it will not be safe
      11             :    for concurrent usage).  Stack top versioning is used to handle ABA.
      12             :    Versioning has been tweaked to support locked pool operations like
      13             :    initialization (and thus this can also be used without changes as a
      14             :    more conventional spin lock based concurrent stack).  Unsurprisingly,
      15             :    the current implementation is equally usable as a concurrent element
      16             :    stack (though the implementation may be changed in the future to
      17             :    better support ultra high contention ultra high concurrency ala
      18             :    fd_alloc).
      19             : 
      20             :    The current implementation is optimized for pools with a moderate
      21             :    number of reasonably localized users (e.g. a handful of cores and
      22             :    memory on the same NUMA node).  Various operations are slightly more
      23             :    optimal when the size of a pool element is an integer power of 2.
      24             :    Operations do much internal integrity checking / bounds checking for
      25             :    use in high reliability / high security environments.
      26             : 
      27             :    This API is designed for tight and flexible composition with treaps,
      28             :    heaps, lists, maps, etc.  Further, a pool can be persisted beyond the
      29             :    lifetime of the creating process, be used inter-process, be relocated
      30             :    in memory, be naively serialized/deserialized, be moved between
      31             :    hosts, use index compression for cache and memory bandwidth
      32             :    efficiency, etc.
      33             : 
      34             :    Typical usage:
      35             : 
      36             :      struct myele {
      37             :        ulong next; // Technically "POOL_IDX_T POOL_NEXT" (default is ulong next), managed by the mypool when in the mypool
      38             : 
      39             :        ... next can be located arbitrarily in the element and can be
      40             :        ... reused for other purposes when the element is not in a
      41             :        ... mypool.  elements are all located in a linear array element
      42             :        ... store whose lifetime is at least that of the mypool.
      43             : 
      44             :      };
      45             : 
      46             :      typedef struct myele myele_t;
      47             : 
      48             :      #define POOL_NAME  mypool
      49             :      #define POOL_ELE_T myele_t
      50             :      #include "tmpl/fd_pool_para.c"
      51             : 
      52             :    will declare the following APIs as a header only style library in the
      53             :    compilation unit:
      54             : 
      55             :      // A mypool_t is a stack declaration friendly quasi-opaque handle
      56             :      // used to describe a join to a mypool.  E.g. it is fine to do
      57             :      // "mypool_t join[1];" to allocate a mypool_t but the contents
      58             :      // should be used directly.
      59             : 
      60             :      typedef struct mypool_private mypool_t;
      61             : 
      62             :      // Constructors
      63             : 
      64             :      // mypool_ele_max_max returns the maximum element store capacity
      65             :      // compatible with a mypool.
      66             : 
      67             :      ulong mypool_ele_max_max( void );
      68             : 
      69             :      // mypool_{align,footprint} returns the alignment and footprint
      70             :      // needed for a memory region to be used as a mypool.  align will
      71             :      // be an integer power-of-two and footprint will be a multiple of
      72             :      // align.
      73             :      //
      74             :      // mypool_new formats a memory region with the appropriate
      75             :      // alignment and footprint into a mypool.  shmem points in the
      76             :      // caller's address space of the memory region to format.  Returns
      77             :      // shmem on success (mypool has ownership of the memory region) and
      78             :      // NULL on failure (no changes, logs details).  Caller is not
      79             :      // joined on return.  The mypool will be empty and unlocked.
      80             :      //
      81             :      // mypool_join joins a mypool.  ljoin points to a mypool_t
      82             :      // compatible memory region in the caller's address space used to
      83             :      // hold info about the local join, shpool points in the caller's
      84             :      // address space to the memory region containing the mypool, shele
      85             :      // points in the caller's address space to mypool's element store,
      86             :      // and ele_max is the element store's capacity.  Returns a handle
      87             :      // to the caller's local join on success (join has ownership of the
      88             :      // ljoin region) and NULL on failure (no changes, logs details).
      89             :      //
      90             :      // mypool_leave leaves a mypool.  join points to a current local
      91             :      // join.  Returns the memory used for the local join (caller has
      92             :      // ownership on return and caller is no longer joined) on success
      93             :      // and NULL on failure (no changes, logs details).  Use the join
      94             :      // accessors before leaving to get shpool, shele and ele_max used
      95             :      // by the join if needed.
      96             :      //
      97             :      // mypool_delete unformats a memory region used as a mypool.
      98             :      // Assumes shpool points in the caller's address space to the
      99             :      // memory region containing the mypool and that there are no
     100             :      // current joins globally.  Returns shpool on success (caller has
     101             :      // ownership of the memory region and any elements still in the
     102             :      // mypool are acquired by the caller) and NULL on failure (no
     103             :      // changes, logs details).
     104             : 
     105             :      ulong      mypool_align    ( void );
     106             :      ulong      mypool_footprint( void );
     107             :      void *     mypool_new      ( void *     shmem );
     108             :      mypool_t * mypool_join     ( void *     ljoin, void * shpool, void * shele, ulong ele_max );
     109             :      void *     mypool_leave    ( mypool_t * join );
     110             :      void *     mypool_delete   ( void *     shpool );
     111             : 
     112             :      // mypool_{shpool,shele,ele_max} return join details.  Assumes join
     113             :      // is a current local join.  mypool_{shpool_const,shele_const} are
     114             :      // const correct versions.  The lifetime of the returned pointers
     115             :      // is the lifetime of the join.
     116             : 
     117             :      void const * mypool_shpool_const( mypool_t const * join );
     118             :      void const * mypool_shele_const ( mypool_t const * join );
     119             :      ulong        mypool_ele_max     ( mypool_t const * join );
     120             : 
     121             :      void * mypool_shpool( mypool_t * join );
     122             :      void * mypool_shele ( mypool_t * join );
     123             : 
     124             :      // mypool_idx_null returns the element store index used to
     125             :      // represent null for a mypool.
     126             :      //
     127             :      // mypool_idx_is_null returns 1 if an element store index is the
     128             :      // null index value and 0 otherwise.
     129             :      //
     130             :      // mypool_idx returns the element store index for the element
     131             :      // pointed to by ele in the caller's address space.  Assumes join
     132             :      // is a current local join.  If ele is NULL or not into the element
     133             :      // store, returns the element store null index.
     134             :      //
     135             :      // mypool_ele returns a pointer in the caller's address space to
     136             :      // the element whose element store index is ele_idx.  If ele_idx is
     137             :      // the null value or invalid, returns NULL.  mypool_ele_const is a
     138             :      // const correct version.
     139             :      //
     140             :      // These are usually not needed but allow translating pointers to
     141             :      // element store elements from one address space to another.
     142             : 
     143             :      ulong mypool_idx_null   ( void );
     144             :      int   mypool_idx_is_null( ulong idx );
     145             :      ulong mypool_idx        ( mypool_t const * join, myele_t const * ele );
     146             : 
     147             :      myele_t const * mypool_ele_const( mypool_t const * join, ulong ele_idx );
     148             :      myele_t *       mypool_ele      ( mypool_t *       join, ulong ele_idx );
     149             : 
     150             :      // mypool_peek returns a pointer in the local address space to the
     151             :      // next element to acquire from the mypool or NULL if the mypool
     152             :      // was empty at some point during the call.  mypool_peek_const is a
     153             :      // const correct version.  Because of concurrent operations, unless
     154             :      // the caller is holding a lock on the mypool, this may not be the
     155             :      // actual element the caller will acquire next from the mypool.
     156             : 
     157             :      myele_t const * mypool_peek_const( mypool_t const * join );
     158             :      myele_t *       mypool_peek      ( mypool_t       * join );
     159             : 
     160             :      // mypool_acquire acquires an element from a mypool.  Assumes join
     161             :      // is a current local join.  If the mypool is empty returns NULL
     162             :      // On success, the returned value will be a pointer in the caller's
     163             :      // address space to the element store element acquired from the
     164             :      // mypool.  On failure, the value returned will be NULL and the
     165             :      // mypool will be unchanged.  Failure can occur if the mypool
     166             :      // contained no elements at some point during the call.
     167             : 
     168             :      myele_t * mypool_acquire( mypool_t * join );
     169             : 
     170             :      // mypool_acquire_nolock does an atomic-unsafe acquire.
     171             :      // Assumes that the current thread is the only pool user.
     172             :      // U.B. if other threads access the pool concurrently.
     173             : 
     174             :      myele_t * mypool_acquire_nolock( mypool_t * join );
     175             : 
     176             :      // mypool_acquire_batch acquires exactly cnt elements from a mypool
     177             :      // and stores pointers to them in out[0,cnt).  Assumes join is a
     178             :      // current local join and out has room for cnt pointers.  cnt==0 is
     179             :      // a no-op that returns NULL.  Elements come from the free stack
     180             :      // first (LIFO), then, if POOL_LAZY, from the lazy region
     181             :      // (ascending).  On success, returns out.  If the pool runs
     182             :      // short, returns acquired elements to the pool and returns NULL.
     183             :      //
     184             :      // Acquired elements are independent: any subset can be handed back
     185             :      // with mypool_release.
     186             : 
     187             :      myele_t ** mypool_acquire_batch( mypool_t * join, ulong cnt, myele_t * out[] );
     188             : 
     189             :      // mypool_release releases an element to a mypoool.  Assumes join
     190             :      // is a current local join, ele is a pointer in the caller's
     191             :      // address space to the element, and the element is currently not
     192             :      // in the mypool.
     193             : 
     194             :      void mypool_release( mypool_t * join, myele_t * ele );
     195             : 
     196             :      // mypool_release_chain splices a singly-linked chain of elements
     197             :      // back into the mypool free stack in a single CAS.  head is a
     198             :      // pointer to the first element of the chain and tail is a pointer
     199             :      // to the last.  The chain must be linked via POOL_NEXT (i.e.
     200             :      // head->next -> ... -> tail).  tail's next pointer will be
     201             :      // overwritten to point at the current stack top.  Assumes join is
     202             :      // a current local join, head and tail are valid elements in the
     203             :      // element store, the chain is non-empty, and none of the elements
     204             :      // are currently in the mypool.
     205             : 
     206             :      void mypool_release_chain( mypool_t * join, myele_t * head, myele_t * tail );
     207             : 
     208             :      // mypool_is_empty returns 1 if the mypool has no elements available
     209             :      // for acquire and 0 otherwise.  Observation is a point-in-time
     210             :      // sample; concurrent acquire / release operations may change the
     211             :      // result immediately after.  Assumes join is a current local join.
     212             : 
     213             :      int mypool_is_empty( mypool_t * join );
     214             : 
     215             :      // mypool_lock will lock a mypool (e.g. pausing concurrent acquire
     216             :      // / release operations).  A non-zero / zero value for blocking
     217             :      // indicates the call should / should not wait to lock the mypool
     218             :      // if it is currently locked.  Returns FD_POOL_SUCCESS on success
     219             :      // (caller has the lock on return) and FD_POOL_ERR_AGAIN on failure
     220             :      // (pool was already locked at some point during the call).  AGAIN
     221             :      // is never returned if blocking is requested.  Assumes join is a
     222             :      // current local join.
     223             : 
     224             :      int mypool_lock( mypool_t * join, int blocking );
     225             : 
     226             :      // mypool_unlock will unlock a mypool (e.g. resuming concurrent
     227             :      // acquire / release operations).  Assumes join is a current local
     228             :      // join and the caller has a lock on mypool.  Guaranteed to
     229             :      // succeed.
     230             : 
     231             :      void mypool_unlock( mypool_t * join );
     232             : 
     233             :      // mypool_reset resets the mypool.  On return, it will hold all
     234             :      // elements in the element store (e.g. initialization after
     235             :      // creation) in ascending order.  Assumes join is a current local
     236             :      // join and the mypool is locked or otherwise idle.
     237             : 
     238             :      void mypool_reset( mypool_t * join );
     239             : 
     240             :      // mypool_verify returns FD_POOL_SUCCESS if join appears to be
     241             :      // current local join to a valid mypool and FD_POOL_ERR_CORRUPT
     242             :      // otherwise (logs details).  Assumes join is a current local join
     243             :      // and the mypool is locked or otherwise idle.
     244             : 
     245             :      int mypool_verify( mypool_t const * join );
     246             : 
     247             :      // mypool_strerror converts an FD_POOL_SUCCESS / FD_POOL_ERR code
     248             :      // into a human readable cstr.  The lifetime of the returned
     249             :      // pointer is infinite.  The returned pointer is always to a
     250             :      // non-NULL cstr.
     251             : 
     252             :      char const * mypool_strerror( int err );
     253             : 
     254             :    Do this as often as desired in a compilation unit to get different
     255             :    types of concurrent pools.  Options exist for generating library
     256             :    header prototypes and/or library implementations for concurrent pools
     257             :    usable across multiple compilation units.  Additional options exist
     258             :    to use index compression, configuring versioning, etc. */
     259             : 
     260             : /* POOL_NAME gives the API prefix to use for pool */
     261             : 
     262             : #ifndef POOL_NAME
     263             : #error "Define POOL_NAME"
     264             : #endif
     265             : 
     266             : /* POOL_ELE_T is the pool element type. */
     267             : 
     268             : #ifndef POOL_ELE_T
     269             : #error "Define POOL_ELE_T"
     270             : #endif
     271             : 
     272             : /* POOL_IDX_T is the type used for the next field in the POOL_ELE_T.
     273             :    Should be a primitive unsigned integer type.  Defaults to ulong.  A
     274             :    pool can't use element stores with a capacity that can't be
     275             :    represented by a POOL_IDX_T.  (E.g. if ushort, the maximum capacity
     276             :    pool compatible element store is 65535 elements.) */
     277             : 
     278             : #ifndef POOL_IDX_T
     279             : #define POOL_IDX_T ulong
     280             : #endif
     281             : 
     282             : /* POOL_NEXT is the POOL_ELE_T next field */
     283             : 
     284             : #ifndef POOL_NEXT
     285           0 : #define POOL_NEXT next
     286             : #endif
     287             : 
     288             : /* POOL_ALIGN gives the alignment required for the pool shared memory.
     289             :    Default is 128 for double cache line alignment.  Should be at least
     290             :    ulong alignment. */
     291             : 
     292             : #ifndef POOL_ALIGN
     293             : #define POOL_ALIGN (128UL)
     294             : #endif
     295             : 
     296             : /* POOL_IDX_WIDTH gives the number of bits in a ulong to reserve for
     297             :    encoding the element store index in a versioned index.  Element store
     298             :    capacity should be representable in this width.  Default is 43 bits
     299             :    (e.g. enough to support a ~1 PiB element store of 128 byte elements).
     300             :    The versioning width will be 64-POOL_IDX_WIDTH.  Since the least
     301             :    significant bit of the version is used to indicate global locking,
     302             :    versioning width should be at least 2 and ideally as large as
     303             :    possible.  With the 43 default, version numbers will not be reused
     304             :    until 2^20 individual operations have been done. */
     305             : 
     306             : #ifndef POOL_IDX_WIDTH
     307      703787 : #define POOL_IDX_WIDTH (43)
     308             : #endif
     309             : 
     310             : /* POOL_MAGIC is the magic number to use for the structure to aid in
     311             :    persistent and or IPC usage. */
     312             : 
     313             : #ifndef POOL_MAGIC
     314          36 : #define POOL_MAGIC (0xf17eda2c37c90010UL) /* firedancer cpool version 0 */
     315             : #endif
     316             : 
     317             : /* POOL_IMPL_STYLE controls what to generate:
     318             :      0 - local use only
     319             :      1 - library header declaration
     320             :      2 - library implementation */
     321             : 
     322             : #ifndef POOL_IMPL_STYLE
     323             : #define POOL_IMPL_STYLE 0
     324             : #endif
     325             : 
     326             : /* POOL_LAZY enables lazy initialization for faster startup if defined
     327             :    to non-zero.  Decreases pool_reset cost from O(ele_max) to O(1), at
     328             :    the cost of more complex allocation logic. */
     329             : 
     330             : #ifndef POOL_LAZY
     331             : #define POOL_LAZY 0
     332             : #endif
     333             : 
     334             : /* Common pool error codes (FIXME: probably should get around to making
     335             :    unified error codes and string handling across all util at least so
     336             :    we don't have to do this in the generator itself) */
     337             : 
     338           0 : #define FD_POOL_SUCCESS     ( 0)
     339           0 : #define FD_POOL_ERR_AGAIN   (-1)
     340           0 : #define FD_POOL_ERR_CORRUPT (-2)
     341             : 
     342             : /* Implementation *****************************************************/
     343             : 
     344     3425231 : #define POOL_VER_WIDTH (64-POOL_IDX_WIDTH)
     345             : 
     346             : #if POOL_IMPL_STYLE==0 /* local use only */
     347             : #define POOL_STATIC FD_FN_UNUSED static
     348             : #else /* library header and/or implementation */
     349             : #define POOL_STATIC
     350             : #endif
     351             : 
     352     5182630 : #define POOL_(n) FD_EXPAND_THEN_CONCAT3(POOL_NAME,_,n)
     353             : 
     354             : #if POOL_IMPL_STYLE!=2 /* need header */
     355             : 
     356             : #include "../bits/fd_bits.h"
     357             : 
     358             : struct __attribute__((aligned(POOL_ALIGN))) POOL_(shmem_private) {
     359             : 
     360             :   /* Note: there is no free count because that that isn't precisely
     361             :      knowable in a portable concurrent data structure.  (Not enough bits
     362             :      to squeeze into ver_top for large pools, requiring 128-bit wide
     363             :      ver_top would limit supported targets, etc). */
     364             : 
     365             :   ulong magic;   /* == POOL_MAGIC */
     366             :   ulong ver_top; /* Versioned index of the free stack top, top is in [0,ele_max) (not-empty) or is idx_null (empty) */
     367             : 
     368             : # if POOL_LAZY
     369             :   ulong ver_lazy; /* Versioned index of the lazy init, lazy is in [0,ele_max] (not-empty) or is idx_null (empty) */
     370             : # endif
     371             : 
     372             : };
     373             : 
     374             : typedef struct POOL_(shmem_private) POOL_(shmem_t);
     375             : 
     376             : struct POOL_(private) {
     377             :   POOL_(shmem_t) * pool;    /* Pool location in the local address space */
     378             :   POOL_ELE_T *     ele;     /* Element store location in the local address space, NULL okay if ele_max==0 */
     379             :   ulong            ele_max; /* Element store capacity, in [0,ele_max_max] */
     380             : };
     381             : 
     382             : typedef struct POOL_(private) POOL_(t);
     383             : 
     384             : FD_PROTOTYPES_BEGIN
     385             : 
     386             : /* pool_private_vidx pack ver and idx into a versioned idx.  ver is
     387             :    masked to fit into POOL_VER_WIDTH bits.  idx is assumed in
     388             :    [0,ele_max_max].
     389             : 
     390             :    pool_private_vidx_{ver,idx} extract the {version,index} from a
     391             :    versioned index and will fit into {POOL_VER_WIDTH,POOL_IDX_WIDTH}
     392             :    bits. */
     393             : 
     394      871345 : FD_FN_CONST static inline ulong POOL_(private_vidx)( ulong ver, ulong idx ) { return (ver<<POOL_IDX_WIDTH) | idx; }
     395             : 
     396      884479 : FD_FN_CONST static inline ulong POOL_(private_vidx_ver)( ulong ver_idx ) { return ver_idx >> POOL_IDX_WIDTH;  }
     397      884849 : FD_FN_CONST static inline ulong POOL_(private_vidx_idx)( ulong ver_idx ) { return (ver_idx << POOL_VER_WIDTH) >> POOL_VER_WIDTH; }
     398             : 
     399             : /* pool_private_{cidx,idx} compress/decompress a 64-bit in-register
     400             :    index to/from its in-memory representation. */
     401             : 
     402      440732 : FD_FN_CONST static inline POOL_IDX_T POOL_(private_cidx)( ulong  idx ) { return (POOL_IDX_T) idx; }
     403      782406 : FD_FN_CONST static inline ulong      POOL_(private_idx) ( ulong cidx ) { return (ulong)     cidx; }
     404             : 
     405             : /* pool_private_cas does a ulong FD_ATOMIC_CAS when FD_HAS_ATOMIC
     406             :    support is available and emulates it when not.  Similarly for
     407             :    pool_private_fetch_and_or.  When emulated, the pool will not be safe
     408             :    to use concurrently (but will still work). */
     409             : 
     410             : static inline ulong
     411             : POOL_(private_cas)( ulong volatile * p,
     412             :                     ulong            c,
     413      871187 :                     ulong            s ) {
     414      871187 :   ulong o;
     415      871187 :   FD_COMPILER_MFENCE();
     416      871187 : # if FD_HAS_ATOMIC
     417      871187 :   o = FD_ATOMIC_CAS( p, c, s );
     418             : # else
     419             :   o = *p;
     420             :   *p = fd_ulong_if( o==c, s, o );
     421             : # endif
     422      871187 :   FD_COMPILER_MFENCE();
     423      871187 :   return o;
     424      871187 : }
     425             : 
     426             : static inline ulong
     427             : POOL_(private_fetch_and_or)( ulong volatile * p,
     428           0 :                              ulong            b ) {
     429           0 :   ulong x;
     430           0 :   FD_COMPILER_MFENCE();
     431           0 : # if FD_HAS_ATOMIC
     432           0 :   x = FD_ATOMIC_FETCH_AND_OR( p, b );
     433             : # else
     434             :   x = *p;
     435             :   *p = x | b;
     436             : # endif
     437           0 :   FD_COMPILER_MFENCE();
     438           0 :   return x;
     439           0 : }
     440             : 
     441          72 : FD_FN_CONST static inline ulong POOL_(ele_max_max)( void ) { return (ulong)(POOL_IDX_T)(ULONG_MAX >> POOL_VER_WIDTH); }
     442             : 
     443         108 : FD_FN_CONST static inline ulong POOL_(align)    ( void ) { return alignof(POOL_(shmem_t)); }
     444           0 : FD_FN_CONST static inline ulong POOL_(footprint)( void ) { return sizeof (POOL_(shmem_t)); }
     445             : 
     446           0 : FD_FN_PURE  static inline void const * POOL_(shpool_const)( POOL_(t) const * join ) { return join->pool;    }
     447           0 : FD_FN_PURE  static inline void const * POOL_(shele_const) ( POOL_(t) const * join ) { return join->ele;     }
     448        8342 : FD_FN_PURE  static inline ulong        POOL_(ele_max)     ( POOL_(t) const * join ) { return join->ele_max; }
     449             : 
     450           0 : FD_FN_PURE  static inline void * POOL_(shpool)( POOL_(t) * join ) { return join->pool; }
     451           0 : FD_FN_PURE  static inline void * POOL_(shele) ( POOL_(t) * join ) { return join->ele;  }
     452             : 
     453     1655461 : FD_FN_CONST static inline ulong POOL_(idx_null)( void ) { return (ulong)(POOL_IDX_T)(ULONG_MAX >> POOL_VER_WIDTH); }
     454     1655380 : FD_FN_CONST static inline int   POOL_(idx_is_null)( ulong idx ) { return idx==POOL_(idx_null)(); }
     455             : 
     456             : FD_FN_PURE static inline ulong
     457             : POOL_(idx)( POOL_(t)   const * join,
     458      797559 :             POOL_ELE_T const * ele ) {
     459      797559 :   ulong  ele_idx = (ulong)(ele - join->ele);
     460      797559 :   return ele_idx<join->ele_max ? ele_idx : POOL_(idx_null)();
     461      797559 : }
     462             : 
     463             : FD_FN_PURE static inline POOL_ELE_T const *
     464             : POOL_(ele_const)( POOL_(t) const * join,
     465           0 :                   ulong            ele_idx ) {
     466           0 :   POOL_ELE_T const * ele = join->ele;
     467           0 :   return (ele_idx < join->ele_max) ? (ele + ele_idx) : NULL;
     468           0 : }
     469             : 
     470             : FD_FN_PURE static inline POOL_ELE_T *
     471             : POOL_(ele)( POOL_(t) * join,
     472      440630 :             ulong      ele_idx ) {
     473      440630 :   POOL_ELE_T * ele = join->ele;
     474      440630 :   return (ele_idx < join->ele_max) ? (ele + ele_idx) : NULL;
     475      440630 : }
     476             : 
     477             : static inline POOL_ELE_T const *
     478           0 : POOL_(peek_const)( POOL_(t) const * join ) {
     479           0 :   POOL_(shmem_t) const * pool    = join->pool;
     480           0 :   POOL_ELE_T     const * ele     = join->ele;
     481           0 :   ulong                  ele_max = join->ele_max;
     482           0 :   FD_COMPILER_MFENCE();
     483           0 :   ulong ver_top  = pool->ver_top;
     484           0 : # if POOL_LAZY
     485           0 :   ulong ver_lazy = pool->ver_lazy;
     486           0 : # endif
     487           0 :   FD_COMPILER_MFENCE();
     488           0 :   ulong ele_idx = POOL_(private_vidx_idx)( ver_top );
     489           0 : # if POOL_LAZY
     490           0 :   if( ele_idx<ele_max ) {
     491           0 :     return ele + ele_idx;
     492           0 :   } else {
     493           0 :     ulong lazy_idx = POOL_(private_vidx_idx)( ver_lazy );
     494           0 :     return (lazy_idx<ele_max) ? ele + lazy_idx : NULL;
     495           0 :   }
     496             : # else
     497             :   return (ele_idx<ele_max) ? ele + ele_idx : NULL;
     498             : # endif
     499           0 : }
     500             : 
     501           0 : static inline POOL_ELE_T * POOL_(peek)( POOL_(t) * join ) { return (POOL_ELE_T *)POOL_(peek_const)( join ); }
     502             : 
     503             : static inline void
     504           0 : POOL_(unlock)( POOL_(t) * join ) {
     505           0 :   POOL_(shmem_t) * pool = join->pool;
     506           0 :   FD_COMPILER_MFENCE();
     507           0 :   pool->ver_top  += 1UL<<POOL_IDX_WIDTH;
     508           0 : # if POOL_LAZY
     509           0 :   pool->ver_lazy += 1UL<<POOL_IDX_WIDTH;
     510           0 : # endif
     511           0 :   FD_COMPILER_MFENCE();
     512           0 : }
     513             : 
     514             : POOL_STATIC void *     POOL_(new)   ( void *     shmem );
     515             : POOL_STATIC POOL_(t) * POOL_(join)  ( void *     ljoin, void * shpool, void * shele, ulong  ele_max );
     516             : POOL_STATIC void *     POOL_(leave) ( POOL_(t) * join );
     517             : POOL_STATIC void *     POOL_(delete)( void *     shpool );
     518             : 
     519             : POOL_STATIC POOL_ELE_T * POOL_(acquire)( POOL_(t) * join );
     520             : 
     521             : POOL_STATIC POOL_ELE_T * POOL_(acquire_nolock)( POOL_(t) * join );
     522             : 
     523             : POOL_STATIC POOL_ELE_T ** POOL_(acquire_batch)( POOL_(t) * join, ulong cnt, POOL_ELE_T * out[] );
     524             : 
     525             : POOL_STATIC void POOL_(release)( POOL_(t) * join, POOL_ELE_T * ele );
     526             : 
     527             : POOL_STATIC void POOL_(release_chain)( POOL_(t) * join, POOL_ELE_T * head, POOL_ELE_T * tail );
     528             : 
     529             : POOL_STATIC int POOL_(is_empty)( POOL_(t) * join );
     530             : 
     531             : POOL_STATIC int POOL_(lock)( POOL_(t) * join, int blocking );
     532             : 
     533             : POOL_STATIC void POOL_(reset)( POOL_(t) * join );
     534             : 
     535             : POOL_STATIC int POOL_(verify)( POOL_(t) const * join );
     536             : 
     537             : POOL_STATIC FD_FN_CONST char const * POOL_(strerror)( int err );
     538             : 
     539             : FD_PROTOTYPES_END
     540             : 
     541             : #endif
     542             : 
     543             : #if POOL_IMPL_STYLE!=1 /* need implementations (assumes header already included) */
     544             : 
     545             : #include "../log/fd_log.h" /* used by constructors and verify (FIXME: Consider making a compile time option) */
     546             : 
     547             : POOL_STATIC void *
     548          36 : POOL_(new)( void * shmem ) {
     549          36 :   POOL_(shmem_t) * pool = (POOL_(shmem_t) *)shmem;
     550             : 
     551          36 :   if( FD_UNLIKELY( !pool ) ) {
     552           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     553           0 :     return NULL;
     554           0 :   }
     555             : 
     556          36 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)pool, POOL_(align)() ) ) ) {
     557           0 :     FD_LOG_WARNING(( "misaligned shmem" ));
     558           0 :     return NULL;
     559           0 :   }
     560             : 
     561          36 :   pool->ver_top  = POOL_(private_vidx)( 0UL, POOL_(idx_null)() );
     562             : # if POOL_LAZY
     563          24 :   pool->ver_lazy = POOL_(private_vidx)( 0UL, POOL_(idx_null)() );
     564             : # endif
     565             : 
     566          36 :   FD_COMPILER_MFENCE();
     567          36 :   pool->magic = POOL_MAGIC;
     568          36 :   FD_COMPILER_MFENCE();
     569             : 
     570          36 :   return (void *)pool;
     571          36 : }
     572             : 
     573             : POOL_STATIC POOL_(t) *
     574             : POOL_(join)( void * ljoin,
     575             :              void * shpool,
     576             :              void * shele,
     577          72 :              ulong  ele_max ) {
     578          72 :   POOL_(t)       * join = (POOL_(t)       *)ljoin;
     579          72 :   POOL_(shmem_t) * pool = (POOL_(shmem_t) *)shpool;
     580          72 :   POOL_ELE_T     * ele  = (POOL_ELE_T     *)shele;
     581             : 
     582          72 :   if( FD_UNLIKELY( !join ) ) {
     583           0 :     FD_LOG_WARNING(( "NULL ljoin" ));
     584           0 :     return NULL;
     585           0 :   }
     586             : 
     587          72 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)join, alignof(POOL_(t)) ) ) ) {
     588           0 :     FD_LOG_WARNING(( "misaligned ljoin" ));
     589           0 :     return NULL;
     590           0 :   }
     591             : 
     592          72 :   if( FD_UNLIKELY( !pool ) ) {
     593           0 :     FD_LOG_WARNING(( "NULL shpool" ));
     594           0 :     return NULL;
     595           0 :   }
     596             : 
     597          72 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)pool, POOL_(align)() ) ) ) {
     598           0 :     FD_LOG_WARNING(( "misaligned shpool" ));
     599           0 :     return NULL;
     600           0 :   }
     601             : 
     602          72 :   if( FD_UNLIKELY( pool->magic!=POOL_MAGIC ) ) {
     603           0 :     FD_LOG_WARNING(( "bad magic" ));
     604           0 :     return NULL;
     605           0 :   }
     606             : 
     607          72 :   if( FD_UNLIKELY( (!ele) & (!!ele_max) ) ) {
     608           0 :     FD_LOG_WARNING(( "NULL shele" ));
     609           0 :     return NULL;
     610           0 :   }
     611             : 
     612          72 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ele, alignof(POOL_ELE_T) ) ) ) {
     613           0 :     FD_LOG_WARNING(( "misaligned shele" ));
     614           0 :     return NULL;
     615           0 :   }
     616             : 
     617          72 :   if( FD_UNLIKELY( ele_max>POOL_(ele_max_max)() ) ) {
     618           0 :     FD_LOG_WARNING(( "bad ele_max" ));
     619           0 :     return NULL;
     620           0 :   }
     621             : 
     622          72 :   join->pool    = pool;
     623          72 :   join->ele     = ele;
     624          72 :   join->ele_max = ele_max;
     625             : 
     626          72 :   return join;
     627          72 : }
     628             : 
     629             : POOL_STATIC void *
     630          36 : POOL_(leave)( POOL_(t) * join ) {
     631             : 
     632          36 :   if( FD_UNLIKELY( !join ) ) {
     633           0 :     FD_LOG_WARNING(( "NULL join" ));
     634           0 :     return NULL;
     635           0 :   }
     636             : 
     637          36 :   return (void *)join;
     638          36 : }
     639             : 
     640             : POOL_STATIC void *
     641           0 : POOL_(delete)( void * shpool ) {
     642           0 :   POOL_(shmem_t) * pool = (POOL_(shmem_t) *)shpool;
     643             : 
     644           0 :   if( FD_UNLIKELY( !pool) ) {
     645           0 :     FD_LOG_WARNING(( "NULL shpool" ));
     646           0 :     return NULL;
     647           0 :   }
     648             : 
     649           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)pool, POOL_(align)() ) ) ) {
     650           0 :     FD_LOG_WARNING(( "misaligned shpool" ));
     651           0 :     return NULL;
     652           0 :   }
     653             : 
     654           0 :   if( FD_UNLIKELY( pool->magic!=POOL_MAGIC ) ) {
     655           0 :     FD_LOG_WARNING(( "bad magic" ));
     656           0 :     return NULL;
     657           0 :   }
     658             : 
     659           0 :   FD_COMPILER_MFENCE();
     660           0 :   pool->magic = 0UL;
     661           0 :   FD_COMPILER_MFENCE();
     662             : 
     663           0 :   return (void *)pool;
     664           0 : }
     665             : 
     666             : #if POOL_LAZY
     667             : 
     668             : /* pool_acquire_lazy acquires up to cnt elements from the lazy region in
     669             :    a single CAS, storing them in out[0,n) in ascending index order.
     670             :    Returns n in [0,cnt]; n<cnt iff the lazy region ran out.  Assumes
     671             :    cnt>0. */
     672             : 
     673             : static inline ulong
     674             : POOL_(acquire_lazy)( POOL_(t) *   join,
     675             :                      ulong        cnt,
     676       10058 :                      POOL_ELE_T * out[] ) {
     677       10058 :   POOL_ELE_T *     ele0    = join->ele;
     678       10058 :   ulong            ele_max = join->ele_max;
     679       10058 :   ulong volatile * _l      = (ulong volatile *)&join->pool->ver_lazy;
     680             : 
     681       10058 :   ulong n = 0UL;
     682             : 
     683       10058 :   FD_COMPILER_MFENCE();
     684             : 
     685       10066 :   for(;;) {
     686       10066 :     ulong ver_lazy = *_l;
     687             : 
     688       10066 :     ulong ver     = POOL_(private_vidx_ver)( ver_lazy );
     689       10066 :     ulong ele_idx = POOL_(private_vidx_idx)( ver_lazy );
     690             : 
     691       10072 :     if( FD_LIKELY( !(ver & 1UL) ) ) { /* opt for unlocked */
     692             : 
     693       10072 :       if( FD_UNLIKELY( POOL_(idx_is_null)( ele_idx ) ) ) { /* opt for not empty */
     694           0 :         break;
     695           0 :       }
     696             : 
     697       10072 :       if( FD_UNLIKELY( ele_idx>=ele_max ) ) {
     698           0 :         FD_LOG_CRIT(( "corruption detected (ele_idx=%lu ele_max=%lu)", ele_idx, ele_max ));
     699           0 :       }
     700             : 
     701       10072 :       ulong ele_cnt = fd_ulong_min( cnt, ele_max-ele_idx );
     702       10072 :       ulong ele_nxt = ele_idx+ele_cnt;
     703       10072 :       if( FD_UNLIKELY( ele_nxt>=ele_max ) ) ele_nxt = POOL_(idx_null)();
     704             : 
     705       10072 :       ulong new_ver_lazy = POOL_(private_vidx)( ver+2UL, ele_nxt );
     706       10072 :       if( FD_LIKELY( POOL_(private_cas)( _l, ver_lazy, new_ver_lazy )==ver_lazy ) ) { /* opt for low contention */
     707       20020 :         for( ; n<ele_cnt; n++ ) out[ n ] = ele0 + ele_idx + n;
     708        9996 :         break;
     709        9996 :       }
     710       10072 :     }
     711             : 
     712          70 :     FD_SPIN_PAUSE();
     713          70 :   }
     714             : 
     715       10058 :   FD_COMPILER_MFENCE();
     716             : 
     717       10058 :   return n;
     718       10058 : }
     719             : 
     720             : #endif /* POOL_LAZY */
     721             : 
     722             : POOL_STATIC POOL_ELE_T *
     723      798610 : POOL_(acquire)( POOL_(t) * join ) {
     724      798610 :   POOL_ELE_T *     ele0    = join->ele;
     725      798610 :   ulong            ele_max = join->ele_max;
     726      798610 :   ulong volatile * _v      = (ulong volatile *)&join->pool->ver_top;
     727             : 
     728      798610 :   POOL_ELE_T * ele = NULL;
     729             : 
     730      798610 :   FD_COMPILER_MFENCE();
     731             : 
     732      798610 :   for(;;) {
     733      796827 :     ulong ver_top = *_v;
     734             : 
     735      796827 :     ulong ver     = POOL_(private_vidx_ver)( ver_top );
     736      796827 :     ulong ele_idx = POOL_(private_vidx_idx)( ver_top );
     737             : 
     738      796827 :     if( FD_LIKELY( !(ver & 1UL) ) ) { /* opt for unlocked */
     739             : 
     740      794710 :       if( FD_UNLIKELY( POOL_(idx_is_null)( ele_idx ) ) ) { /* opt for not empty */
     741             : #       if POOL_LAZY
     742       10054 :         POOL_(acquire_lazy)( join, 1UL, &ele );
     743             : #       endif
     744       10054 :         break;
     745       10054 :       }
     746             : 
     747      784656 :       if( FD_UNLIKELY( ele_idx>=ele_max ) ) {
     748           0 :         FD_LOG_CRIT(( "corruption detected (ele_idx=%lu ele_max=%lu)", ele_idx, ele_max ));
     749           0 :       }
     750             : 
     751      784656 :       ulong ele_nxt = POOL_(private_idx)( ele0[ ele_idx ].POOL_NEXT );
     752             : 
     753      784656 :       if( FD_UNLIKELY( (ele_nxt>=ele_max) & (!POOL_(idx_is_null)( ele_nxt )) ) ) { /* ele_nxt is invalid, opt for valid */
     754             :         /* It is possible that another thread acquired ele_idx and
     755             :            repurposed ele_idx's POOL_NEXT (storing something in it that
     756             :            isn't a valid pool value) between when we read ver_top and
     757             :            when we read ele_idx's POOL_NEXT above.  If so, the pool
     758             :            version would be changed from what we read above.  We thus
     759             :            only signal ERR_CORRUPT if the version number hasn't changed
     760             :            since we read it. */
     761             : 
     762           0 :         if( FD_UNLIKELY( POOL_(private_vidx_ver)( *_v )==ver ) ) {
     763           0 :           FD_LOG_CRIT(( "corruption detected (ele_nxt=%lu ele_max=%lu)", ele_nxt, ele_max ));
     764           0 :         }
     765      784656 :       } else { /* ele_nxt is valid */
     766      784656 :         ulong new_ver_top = POOL_(private_vidx)( ver+2UL, ele_nxt );
     767             : 
     768      789163 :         if( FD_LIKELY( POOL_(private_cas)( _v, ver_top, new_ver_top )==ver_top ) ) { /* opt for low contention */
     769      789163 :           ele = ele0 + ele_idx;
     770      789163 :           break;
     771      789163 :         }
     772      784656 :       }
     773      784656 :     }
     774             : 
     775 >1844*10^16 :     FD_SPIN_PAUSE();
     776 >1844*10^16 :   }
     777             : 
     778      798610 :   FD_COMPILER_MFENCE();
     779             : 
     780      798610 :   return ele;
     781      798610 : }
     782             : 
     783             : #if POOL_LAZY
     784             : 
     785             : static inline POOL_ELE_T *
     786           0 : POOL_(acquire_lazy_nolock)( POOL_(t) * join ) {
     787           0 :   POOL_ELE_T * ele0    = join->ele;
     788           0 :   ulong        ele_max = join->ele_max;
     789           0 :   ulong *      _l      = &join->pool->ver_lazy;
     790             : 
     791           0 :   POOL_ELE_T * ele = NULL;
     792             : 
     793           0 :   ulong ver_lazy = *_l;
     794             : 
     795           0 :   ulong ver     = POOL_(private_vidx_ver)( ver_lazy );
     796           0 :   ulong ele_idx = POOL_(private_vidx_idx)( ver_lazy );
     797             : 
     798           0 :   if( FD_LIKELY( !POOL_(idx_is_null)( ele_idx ) ) ) {
     799           0 :     if( FD_UNLIKELY( ele_idx>=ele_max ) ) {
     800           0 :       FD_LOG_CRIT(( "corruption detected (ele_idx=%lu ele_max=%lu)",
     801           0 :                     ele_idx, ele_max ));
     802           0 :     }
     803             : 
     804           0 :     ulong ele_nxt = ele_idx+1UL;
     805           0 :     if( FD_UNLIKELY( ele_nxt>=ele_max ) ) {
     806           0 :       ele_nxt = POOL_(idx_null)();
     807           0 :     }
     808             : 
     809           0 :     *_l = POOL_(private_vidx)( ver+2UL, ele_nxt );
     810           0 :     ele = ele0 + ele_idx;
     811           0 :   }
     812             : 
     813           0 :   return ele;
     814           0 : }
     815             : 
     816             : #endif /* POOL_LAZY */
     817             : 
     818             : POOL_STATIC POOL_ELE_T *
     819           0 : POOL_(acquire_nolock)( POOL_(t) * join ) {
     820           0 :   POOL_ELE_T * ele0    = join->ele;
     821           0 :   ulong        ele_max = join->ele_max;
     822           0 :   ulong *      _v      = &join->pool->ver_top;
     823             : 
     824           0 :   POOL_ELE_T * ele = NULL;
     825             : 
     826           0 :   ulong ver_top = *_v;
     827             : 
     828           0 :   ulong ver     = POOL_(private_vidx_ver)( ver_top );
     829           0 :   ulong ele_idx = POOL_(private_vidx_idx)( ver_top );
     830             : 
     831           0 :   if( FD_UNLIKELY( POOL_(idx_is_null)( ele_idx ) ) ) {
     832             : #   if POOL_LAZY
     833           0 :     return POOL_(acquire_lazy_nolock)( join );
     834             : #   endif
     835           0 :   } else {
     836           0 :     if( FD_UNLIKELY( ele_idx>=ele_max ) ) {
     837           0 :       FD_LOG_CRIT(( "corruption detected (ele_idx=%lu ele_max=%lu)",
     838           0 :                     ele_idx, ele_max ));
     839           0 :     }
     840             : 
     841           0 :     ulong ele_nxt = POOL_(private_idx)( ele0[ ele_idx ].POOL_NEXT );
     842             : 
     843           0 :     if( FD_UNLIKELY( (ele_nxt>=ele_max) & (!POOL_(idx_is_null)( ele_nxt )) ) ) {
     844           0 :       FD_LOG_CRIT(( "corruption detected (ele_nxt=%lu ele_max=%lu)",
     845           0 :                     ele_nxt, ele_max ));
     846           0 :     }
     847             : 
     848           0 :     *_v = POOL_(private_vidx)( ver+2UL, ele_nxt );
     849           0 :     ele = ele0 + ele_idx;
     850           0 :   }
     851             : 
     852           0 :   return ele;
     853           0 : }
     854             : 
     855             : POOL_STATIC POOL_ELE_T **
     856             : POOL_(acquire_batch)( POOL_(t) *   join,
     857             :                       ulong        cnt,
     858           0 :                       POOL_ELE_T * out[] ) {
     859           0 :   if( FD_UNLIKELY( !cnt ) ) return NULL;
     860             : 
     861           0 :   POOL_ELE_T *     ele0    = join->ele;
     862           0 :   ulong            ele_max = join->ele_max;
     863           0 :   ulong volatile * _v      = (ulong volatile *)&join->pool->ver_top;
     864             : 
     865           0 :   ulong acq_cnt = 0UL; /* elements acquired so far, in out[0,acq_cnt) */
     866             : 
     867             : # if POOL_LAZY
     868             :   int lazy_empty = 0; /* 1 once the lazy region is known empty */
     869             : # else
     870             :   int lazy_empty = 1; /* no lazy region: an empty stack is an empty pool */
     871             : # endif
     872             : 
     873           0 :   FD_COMPILER_MFENCE();
     874             : 
     875           0 :   for(;;) {
     876             : 
     877             :     /* Pop up to rem elements off the free stack with one CAS.  Exits
     878             :        with all acquired or with the stack observed empty. */
     879             : 
     880           0 :     ulong         rem = cnt - acq_cnt;
     881           0 :     POOL_ELE_T ** dst = out + acq_cnt;
     882             : 
     883           0 :     for(;;) {
     884           0 :       ulong ver_top = *_v;
     885             : 
     886           0 :       ulong ver     = POOL_(private_vidx_ver)( ver_top );
     887           0 :       ulong ele_idx = POOL_(private_vidx_idx)( ver_top );
     888             : 
     889           0 :       if( FD_LIKELY( !(ver & 1UL) ) ) { /* opt for unlocked */
     890             : 
     891           0 :         if( FD_UNLIKELY( POOL_(idx_is_null)( ele_idx ) ) ) break; /* stack empty, opt for not empty */
     892             : 
     893           0 :         if( FD_UNLIKELY( ele_idx>=ele_max ) ) {
     894           0 :           FD_LOG_CRIT(( "corruption detected (ele_idx=%lu ele_max=%lu)", ele_idx, ele_max ));
     895           0 :         }
     896             : 
     897             :         /* Walk up to rem links from the top.  ele_nxt becomes the new
     898             :            top (null if we drained the stack).  Another thread may have
     899             :            acquired an element below us and repurposed its POOL_NEXT,
     900             :            so links are bounds checked before use and the CAS below
     901             :            fails if ver_top changed under us. */
     902             : 
     903           0 :         ulong n       = 0UL;
     904           0 :         ulong ele_nxt = ele_idx;
     905           0 :         do {
     906           0 :           dst[ n++ ] = ele0 + ele_nxt;
     907           0 :           ele_nxt = POOL_(private_idx)( ele0[ ele_nxt ].POOL_NEXT );
     908           0 :         } while( FD_LIKELY( (n<rem) & (ele_nxt<ele_max) ) );
     909             : 
     910           0 :         if( FD_UNLIKELY( (ele_nxt>=ele_max) & (!POOL_(idx_is_null)( ele_nxt )) ) ) { /* ele_nxt is invalid, opt for valid */
     911             : 
     912             :           /* As in acquire: only corruption if the stack did not change */
     913             : 
     914           0 :           if( FD_UNLIKELY( POOL_(private_vidx_ver)( *_v )==ver ) ) {
     915           0 :             FD_LOG_CRIT(( "corruption detected (ele_nxt=%lu ele_max=%lu)", ele_nxt, ele_max ));
     916           0 :           }
     917           0 :         } else { /* ele_nxt is valid */
     918           0 :           ulong new_ver_top = POOL_(private_vidx)( ver+2UL, ele_nxt );
     919             : 
     920           0 :           if( FD_LIKELY( POOL_(private_cas)( _v, ver_top, new_ver_top )==ver_top ) ) { /* opt for low contention */
     921           0 :             acq_cnt += n;
     922           0 :             break;
     923           0 :           }
     924           0 :         }
     925           0 :       }
     926             : 
     927           0 :       FD_SPIN_PAUSE();
     928           0 :     }
     929             : 
     930           0 :     if( FD_LIKELY( acq_cnt==cnt ) ) break;
     931             : 
     932             :     /* Stack was just seen empty.  If the lazy region was already seen
     933             :        empty, the pool was empty while we still needed elements.  Else
     934             :        take from the lazy region and, if still short, recheck the stack
     935             :        once (it may have been refilled meanwhile). */
     936             : 
     937           0 :     if( FD_UNLIKELY( lazy_empty ) ) {
     938           0 :       while( acq_cnt ) POOL_(release)( join, out[ --acq_cnt ] );
     939           0 :       return NULL;
     940           0 :     }
     941             : 
     942             : #   if POOL_LAZY
     943           0 :     acq_cnt += POOL_(acquire_lazy)( join, cnt-acq_cnt, out+acq_cnt );
     944           0 :     if( FD_LIKELY( acq_cnt==cnt ) ) break;
     945           0 :     lazy_empty = 1;
     946           0 : #   endif
     947           0 :   }
     948             : 
     949           0 :   FD_COMPILER_MFENCE();
     950           0 :   return out;
     951           0 : }
     952             : 
     953             : POOL_STATIC void
     954             : POOL_(release)( POOL_(t) *   join,
     955           0 :                 POOL_ELE_T * ele ) {
     956           0 :   ulong            ele_max = join->ele_max;
     957           0 :   ulong volatile * _v      = (ulong volatile *)&join->pool->ver_top;
     958             : 
     959           0 :   ulong ele_idx = (ulong)(ele - join->ele);
     960           0 :   if( FD_UNLIKELY( ele_idx>=ele_max ) ) FD_LOG_CRIT(( "corruption detected: ele=%p is not in bounds", (void *)ele ));
     961             : 
     962           0 :   FD_COMPILER_MFENCE();
     963             : 
     964           0 :   for(;;) {
     965           0 :     ulong ver_top = *_v;
     966             : 
     967           0 :     ulong ver     = POOL_(private_vidx_ver)( ver_top );
     968           0 :     ulong ele_nxt = POOL_(private_vidx_idx)( ver_top );
     969             : 
     970           0 :     if( FD_LIKELY( !(ver & 1UL) ) ) { /* opt for unlocked */
     971             : 
     972           0 :       if( FD_UNLIKELY( (ele_nxt>=ele_max) & (!POOL_(idx_is_null)( ele_nxt )) ) ) { /* opt for not corrupt */
     973           0 :         FD_LOG_CRIT(( "corruption detected (ele_nxt=%lu ele_max=%lu)", ele_nxt, ele_max ));
     974           0 :       }
     975             : 
     976           0 :       ele->POOL_NEXT = POOL_(private_cidx)( ele_nxt );
     977             : 
     978           0 :       ulong new_ver_top = POOL_(private_vidx)( ver+2UL, ele_idx );
     979             : 
     980           0 :       if( FD_LIKELY( POOL_(private_cas)( _v, ver_top, new_ver_top )==ver_top ) ) break; /* opt for low contention */
     981             : 
     982           0 :     }
     983             : 
     984           0 :     FD_SPIN_PAUSE();
     985           0 :   }
     986             : 
     987           0 :   FD_COMPILER_MFENCE();
     988           0 : }
     989             : 
     990             : POOL_STATIC void
     991             : POOL_(release_chain)( POOL_(t) *   join,
     992             :                       POOL_ELE_T * head,
     993       80719 :                       POOL_ELE_T * tail ) {
     994       80719 :   ulong            ele_max = join->ele_max;
     995       80719 :   ulong volatile * _v      = (ulong volatile *)&join->pool->ver_top;
     996             : 
     997       80719 :   ulong head_idx = (ulong)(head - join->ele);
     998       80719 :   if( FD_UNLIKELY( head_idx>=ele_max ) ) FD_LOG_CRIT(( "corruption detected: ele=%p is not in bounds", (void *)head ));
     999             : 
    1000       80719 :   ulong tail_idx = (ulong)(tail - join->ele);
    1001       80719 :   if( FD_UNLIKELY( tail_idx>=ele_max ) ) FD_LOG_CRIT(( "corruption detected: ele=%p is not in bounds", (void *)tail ));
    1002             : 
    1003       80719 :   FD_COMPILER_MFENCE();
    1004             : 
    1005       80722 :   for(;;) {
    1006       80722 :     ulong ver_top = *_v;
    1007             : 
    1008       80722 :     ulong ver     = POOL_(private_vidx_ver)( ver_top );
    1009       80722 :     ulong ele_nxt = POOL_(private_vidx_idx)( ver_top );
    1010             : 
    1011       80731 :     if( FD_LIKELY( !(ver & 1UL) ) ) { /* opt for unlocked */
    1012             : 
    1013       80731 :       if( FD_UNLIKELY( (ele_nxt>=ele_max) & (!POOL_(idx_is_null)( ele_nxt )) ) ) { /* opt for not corrupt */
    1014           0 :         FD_LOG_CRIT(( "corruption detected (ele_nxt=%lu ele_max=%lu)", ele_nxt, ele_max ));
    1015           0 :       }
    1016             : 
    1017       80731 :       tail->POOL_NEXT = POOL_(private_cidx)( ele_nxt );
    1018             : 
    1019       80731 :       ulong new_ver_top = POOL_(private_vidx)( ver+2UL, head_idx );
    1020             : 
    1021       80808 :       if( FD_LIKELY( POOL_(private_cas)( _v, ver_top, new_ver_top )==ver_top ) ) break; /* opt for low contention */
    1022             : 
    1023       80731 :     }
    1024             : 
    1025 >1844*10^16 :     FD_SPIN_PAUSE();
    1026 >1844*10^16 :   }
    1027             : 
    1028       80719 :   FD_COMPILER_MFENCE();
    1029       80719 : }
    1030             : 
    1031             : POOL_STATIC int
    1032           0 : POOL_(is_empty)( POOL_(t) * join ) {
    1033           0 :   POOL_(shmem_t) const * pool = join->pool;
    1034           0 :   FD_COMPILER_MFENCE();
    1035           0 :   ulong ver_top  = pool->ver_top;
    1036             : # if POOL_LAZY
    1037             :   ulong ver_lazy = pool->ver_lazy;
    1038             : # endif
    1039           0 :   FD_COMPILER_MFENCE();
    1040             : 
    1041           0 :   ulong top_idx = POOL_(private_vidx_idx)( ver_top );
    1042             : # if POOL_LAZY
    1043             :   ulong  ele_max = join->ele_max;
    1044           0 :   if( FD_LIKELY( top_idx<ele_max ) ) return 0;  /* explicit stack non-empty */
    1045           0 :   ulong lazy_idx = POOL_(private_vidx_idx)( ver_lazy );
    1046           0 :   return !(lazy_idx<ele_max);                   /* empty only if lazy also empty */
    1047             : # else
    1048           0 :   return POOL_(idx_is_null)( top_idx );
    1049             : # endif
    1050           0 : }
    1051             : 
    1052             : POOL_STATIC int
    1053             : POOL_(lock)( POOL_(t) * join,
    1054           0 :              int        blocking ) {
    1055           0 :   ulong volatile * _v = (ulong volatile *)&join->pool->ver_top;
    1056             : # if POOL_LAZY
    1057             :   ulong volatile * _l = (ulong volatile *)&join->pool->ver_lazy;
    1058             : # endif
    1059             : 
    1060           0 :   int err = FD_POOL_SUCCESS;
    1061             : 
    1062           0 :   FD_COMPILER_MFENCE();
    1063             : 
    1064           0 :   ulong ver_top;
    1065           0 :   for(;;) {
    1066             : 
    1067             :     /* use a test-and-test-and-set style for reduced contention */
    1068             : 
    1069           0 :     ver_top = *_v;
    1070           0 :     if( FD_LIKELY( !(ver_top & (1UL<<POOL_IDX_WIDTH)) ) ) { /* opt for low contention */
    1071           0 :       ver_top = POOL_(private_fetch_and_or)( _v, 1UL<<POOL_IDX_WIDTH );
    1072           0 :       if( FD_LIKELY( !(ver_top & (1UL<<POOL_IDX_WIDTH)) ) ) break; /* opt for low contention */
    1073           0 :     }
    1074             : 
    1075           0 :     if( FD_UNLIKELY( !blocking ) ) { /* opt for blocking */
    1076           0 :       err = FD_POOL_ERR_AGAIN;
    1077           0 :       goto fail;
    1078           0 :     }
    1079             : 
    1080           0 :     FD_SPIN_PAUSE();
    1081           0 :   }
    1082             : 
    1083           0 :   FD_COMPILER_MFENCE();
    1084             : 
    1085             : # if POOL_LAZY
    1086             : 
    1087           0 :   for(;;) {
    1088             : 
    1089             :     /* use a test-and-test-and-set style for reduced contention */
    1090             : 
    1091           0 :     ulong ver_lazy = *_l;
    1092           0 :     if( FD_LIKELY( !(ver_lazy & (1UL<<POOL_IDX_WIDTH)) ) ) { /* opt for low contention */
    1093           0 :       ver_lazy = POOL_(private_fetch_and_or)( _l, 1UL<<POOL_IDX_WIDTH );
    1094           0 :       if( FD_LIKELY( !(ver_lazy & (1UL<<POOL_IDX_WIDTH)) ) ) break; /* opt for low contention */
    1095           0 :     }
    1096             : 
    1097           0 :     if( FD_UNLIKELY( !blocking ) ) { /* opt for blocking */
    1098           0 :       *_v = POOL_(private_vidx)( POOL_(private_vidx_ver)( ver_top )+2UL, POOL_(private_vidx_idx)( ver_top ) ); /* unlock */
    1099           0 :       err = FD_POOL_ERR_AGAIN;
    1100           0 :       goto fail;
    1101           0 :     }
    1102             : 
    1103           0 :     FD_SPIN_PAUSE();
    1104           0 :   }
    1105             : 
    1106           0 :   FD_COMPILER_MFENCE();
    1107             : 
    1108           0 : # endif
    1109             : 
    1110           0 : fail:
    1111           0 :   return err;
    1112           0 : }
    1113             : 
    1114             : #if !POOL_LAZY
    1115             : 
    1116             : POOL_STATIC void
    1117          12 : POOL_(reset)( POOL_(t) * join ) {
    1118          12 :   POOL_(shmem_t) * pool    = join->pool;
    1119          12 :   POOL_ELE_T *     ele     = join->ele;
    1120          12 :   ulong            ele_max = join->ele_max;
    1121             : 
    1122             :   /* Insert all elements in increasing order */
    1123             : 
    1124          12 :   ulong ele_top = 0UL;
    1125          12 :   if( FD_UNLIKELY( !ele_max ) ) ele_top = POOL_(idx_null)();
    1126          12 :   else {
    1127         192 :     for( ulong ele_idx=ele_top; ele_idx<(ele_max-1UL); ele_idx++ ) {
    1128         180 :       ele[ ele_idx ].POOL_NEXT = POOL_(private_cidx)( ele_idx+1UL );
    1129         180 :     }
    1130          12 :     ele[ ele_max-1UL ].POOL_NEXT = POOL_(private_cidx)( POOL_(idx_null)() );
    1131          12 :   }
    1132             : 
    1133          12 :   ulong ver_top = pool->ver_top;
    1134          12 :   ulong ver     = POOL_(private_vidx_ver)( ver_top );
    1135          12 :   pool->ver_top = POOL_(private_vidx)( ver, ele_top );
    1136          12 : }
    1137             : 
    1138             : #else
    1139             : 
    1140             : POOL_STATIC void
    1141          24 : POOL_(reset)( POOL_(t) * join ) {
    1142          24 :   POOL_(shmem_t) * pool    = join->pool;
    1143             : 
    1144             :   /* Assign all elements to the bump allocator */
    1145             : 
    1146          24 :   ulong ele_top  = POOL_(idx_null)();
    1147          24 :   ulong ele_lazy = 0UL;
    1148             : 
    1149          24 :   ulong ver_top  = pool->ver_top;
    1150          24 :   ulong ver_lazy = pool->ver_lazy;
    1151          24 :   pool->ver_top  = POOL_(private_vidx)( POOL_(private_vidx_ver)( ver_top  ), ele_top  );
    1152          24 :   pool->ver_lazy = POOL_(private_vidx)( POOL_(private_vidx_ver)( ver_lazy ), ele_lazy );
    1153          24 : }
    1154             : 
    1155             : #endif
    1156             : 
    1157             : POOL_STATIC int
    1158           0 : POOL_(verify)( POOL_(t) const * join ) {
    1159             : 
    1160           0 : # define POOL_TEST(c) do {                                                                        \
    1161           0 :     if( FD_UNLIKELY( !(c) ) ) { FD_LOG_WARNING(( "FAIL: %s", #c )); return FD_POOL_ERR_CORRUPT; } \
    1162           0 :   } while(0)
    1163             : 
    1164             :   /* Validate join */
    1165             : 
    1166           0 :   POOL_TEST( join );
    1167           0 :   POOL_TEST( fd_ulong_is_aligned( (ulong)join, alignof(POOL_(t)) ) );
    1168             : 
    1169           0 :   POOL_(shmem_t) const * pool    = join->pool;
    1170           0 :   POOL_ELE_T const *     ele     = join->ele;
    1171           0 :   ulong                  ele_max = join->ele_max;
    1172             : 
    1173           0 :   POOL_TEST( pool );
    1174           0 :   POOL_TEST( fd_ulong_is_aligned( (ulong)pool, POOL_(align)() ) );
    1175             : 
    1176           0 :   POOL_TEST( (!!ele)| (!ele_max) );
    1177           0 :   POOL_TEST( fd_ulong_is_aligned( (ulong)ele, alignof(POOL_ELE_T) ) );
    1178             : 
    1179           0 :   POOL_TEST( ele_max<=POOL_(ele_max_max)() );
    1180             : 
    1181             :   /* Validate pool metadata */
    1182             : 
    1183           0 :   ulong magic   = pool->magic;
    1184           0 :   ulong ver_top = pool->ver_top;
    1185             : 
    1186             :   /* version arbitrary as far as verify is concerned */
    1187           0 :   ulong ele_idx = POOL_(private_vidx_idx)( ver_top );
    1188             : 
    1189           0 :   POOL_TEST( magic==POOL_MAGIC );
    1190             : 
    1191             :   /* Validate pool elements */
    1192             : 
    1193           0 :   ulong ele_rem = ele_max;
    1194           0 :   while( ele_idx<ele_max ) {
    1195           0 :     POOL_TEST( ele_rem ); ele_rem--; /* no cycles */
    1196           0 :     ele_idx = POOL_(private_idx)( ele[ ele_idx ].POOL_NEXT );
    1197           0 :   }
    1198             : 
    1199           0 :   POOL_TEST( POOL_(idx_is_null)( ele_idx ) );
    1200             : 
    1201             : # if POOL_LAZY
    1202           0 :   ulong lazy_idx  = POOL_(private_vidx_idx)( pool->ver_lazy );
    1203           0 :   ulong lazy_free = POOL_(idx_is_null)( lazy_idx ) ? 0UL : (ele_max-lazy_idx);
    1204           0 :   POOL_TEST( lazy_free<=ele_rem );
    1205           0 : # endif
    1206             : 
    1207           0 : # undef POOL_TEST
    1208             : 
    1209           0 :   return FD_POOL_SUCCESS;
    1210           0 : }
    1211             : 
    1212             : POOL_STATIC char const *
    1213           0 : POOL_(strerror)( int err ) {
    1214           0 :   switch( err ) {
    1215           0 :   case FD_POOL_SUCCESS:     return "success";
    1216           0 :   case FD_POOL_ERR_AGAIN:   return "try again";
    1217           0 :   case FD_POOL_ERR_CORRUPT: return "corruption detected";
    1218           0 :   default: break;
    1219           0 :   }
    1220           0 :   return "unknown";
    1221           0 : }
    1222             : 
    1223             : #endif
    1224             : 
    1225             : #undef POOL_
    1226             : #undef POOL_STATIC
    1227             : #undef POOL_VER_WIDTH
    1228             : 
    1229             : #undef POOL_LAZY
    1230             : #undef POOL_IMPL_STYLE
    1231             : #undef POOL_MAGIC
    1232             : #undef POOL_IDX_WIDTH
    1233             : #undef POOL_ALIGN
    1234             : #undef POOL_NEXT
    1235             : #undef POOL_IDX_T
    1236             : #undef POOL_ELE_T
    1237             : #undef POOL_NAME

Generated by: LCOV version 1.14