LCOV - code coverage report
Current view: top level - util/alloc - fd_alloc.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 447 652 68.6 %
Date: 2026-08-10 04:52:31 Functions: 27 30 90.0 %

          Line data    Source code
       1             : #include "fd_alloc.h"
       2             : #include "fd_alloc_cfg.h"
       3             : #include "../sanitize/fd_sanitize.h"
       4             : 
       5             : /* Note: this will still compile on platforms without FD_HAS_ATOMIC.  It
       6             :    should only be used single threaded in those use cases.  (The code
       7             :    does imitate at a very low level the operations required by
       8             :    FD_HAS_ATOMIC but this is to minimize amount of code differences to
       9             :    test.) */
      10             : 
      11             : /* If FD_ALLOC_STYLE is non-zero, this will clear any padding needed to
      12             :    align a user allocation.  This is not strictly necessary and can slow
      13             :    down fd_alloc_malloc.  But it does make diagnostics like
      14             :    fd_alloc_fprintf more accurate. */
      15             : 
      16             : #ifndef FD_ALLOC_STYLE
      17             : #define FD_ALLOC_STYLE 0
      18             : #endif
      19             : 
      20             : /* sizeclass APIs *****************************************************/
      21             : 
      22             : /* fd_alloc_preferred_sizeclass returns the tightest fitting sizeclass
      23             :    for the given footprint.  The caller promises there is at least one
      24             :    possible size class (i.e. that footprint is in
      25             :    [0,FD_ALLOC_FOOTPRINT_SMALL_THRESH]).  The return will be in
      26             :    [0,FD_ALLOC_SIZECLASS_CNT). */
      27             : 
      28             : static inline ulong
      29      655887 : fd_alloc_preferred_sizeclass( ulong footprint ) {
      30      655887 :   ulong l = 0UL;
      31      655887 :   ulong h = FD_ALLOC_SIZECLASS_CNT-1UL;
      32             : 
      33             :   /* Fixed count loop without early exit to make it easy for compiler to
      34             :      unroll and nominally eliminate all branches for fast, highly
      35             :      deterministic performance with no consumption of BTB resources.
      36             :      FIXME: check the compiler is doing the right thing here with
      37             :      unrolling and branch elimination. */
      38             : 
      39     4591209 :   for( ulong r=0UL; r<FD_ALLOC_SIZECLASS_ITER_MAX; r++ ) {
      40             : 
      41             :     /* At this point sizeclasses in [0,l) are known to be inadequate and
      42             :        sizeclasses in [h,SIZECLASS_CNT) are known to be suitable.
      43             :        Sizeclasses in [l,h) have not been tested. */
      44             : 
      45     3935322 :     ulong m = (l+h)>>1; /* Note: no overflow for reasonable sizeclass_cnt and l<=m<=h */
      46     3935322 :     int   c = (((ulong)fd_alloc_sizeclass_cfg[ m ].block_footprint)>=footprint);
      47     3935322 :     l = fd_ulong_if( c, l, m+1UL ); /* cmov */
      48     3935322 :     h = fd_ulong_if( c, m, h     ); /* cmov */
      49             : 
      50     3935322 :   }
      51             : 
      52      655887 :   return h;
      53      655887 : }
      54             : 
      55             : /* fd_alloc_block_set *************************************************/
      56             : 
      57             : /* A fd_alloc_block_set specifies a set of blocks in a superblock. */
      58             : 
      59             : #define SET_NAME fd_alloc_block_set
      60             : #define SET_TYPE ulong
      61             : #define SET_MAX  64
      62             : #include "../tmpl/fd_smallset.c"
      63             : 
      64             : /* fd_alloc_block_set_all returns the set { 0,1,2, ... block_cnt-1 }.
      65             :    Assumes block_cnt is in [0,64]. */
      66             : 
      67             : FD_FN_CONST static inline fd_alloc_block_set_t
      68      623874 : fd_alloc_block_set_all( ulong block_cnt ) {
      69      623874 :   return (((ulong)(block_cnt<=63UL)) << (block_cnt & 63UL)) - 1UL; /* Handle wide shifts */
      70      623874 : }
      71             : 
      72             : /* fd_alloc_block_set_{add,sub}_then_fetch inserts / removes blocks to /
      73             :    from block set pointed to by set.  The caller promises that blocks
      74             :    are not / are already in the block set.  This operation is a compiler
      75             :    fence.  Further, if FD_HAS_ATOMIC, this operation is done atomically.
      76             :    Returns the value of the block_set just after the operation.  Note:
      77             :    atomic add/sub has slightly better asm on x86 than atomic or/and/nand
      78             :    (compiler quality issue, not an architecture issue) and generates the
      79             :    same results provided the caller promises are met. */
      80             : 
      81             : #if FD_HAS_ATOMIC
      82             : 
      83             : static inline fd_alloc_block_set_t
      84             : fd_alloc_block_set_add_then_fetch( fd_alloc_block_set_t * _set,
      85      652641 :                                    fd_alloc_block_set_t   blocks ) {
      86      652641 :   FD_COMPILER_MFENCE();
      87      652641 :   fd_alloc_block_set_t ret = FD_ATOMIC_ADD_AND_FETCH( _set, blocks );
      88      652641 :   FD_COMPILER_MFENCE();
      89      652641 :   return ret;
      90      652641 : }
      91             : 
      92             : static inline fd_alloc_block_set_t
      93             : fd_alloc_block_set_sub_then_fetch( fd_alloc_block_set_t * _set,
      94      658185 :                                    fd_alloc_block_set_t   blocks ) {
      95      658185 :   FD_COMPILER_MFENCE();
      96      658185 :   fd_alloc_block_set_t ret = FD_ATOMIC_SUB_AND_FETCH( _set, blocks );
      97      658185 :   FD_COMPILER_MFENCE();
      98      658185 :   return ret;
      99      658185 : }
     100             : 
     101             : #else
     102             : 
     103             : static inline fd_alloc_block_set_t
     104             : fd_alloc_block_set_add_then_fetch( fd_alloc_block_set_t * _set,
     105             :                                    fd_alloc_block_set_t   blocks ) {
     106             :   FD_COMPILER_MFENCE();
     107             :   fd_alloc_block_set_t ret = (*_set) + blocks;
     108             :   *_set = ret;
     109             :   FD_COMPILER_MFENCE();
     110             :   return ret;
     111             : }
     112             : 
     113             : static inline fd_alloc_block_set_t
     114             : fd_alloc_block_set_sub_then_fetch( fd_alloc_block_set_t * _set,
     115             :                                    fd_alloc_block_set_t   blocks ) {
     116             :   FD_COMPILER_MFENCE();
     117             :   fd_alloc_block_set_t ret = (*_set) - blocks;
     118             :   *_set = ret;
     119             :   FD_COMPILER_MFENCE();
     120             :   return ret;
     121             : }
     122             : 
     123             : #endif
     124             : 
     125             : /* fd_alloc_hdr_t *****************************************************/
     126             : 
     127             : /* A fd_alloc_hdr_t is a small header prepended to an allocation that
     128             :    describes how to free the allocation. */
     129             : 
     130             : typedef uint fd_alloc_hdr_t;
     131             : 
     132             : /* FD_ALLOC_HDR_TYPE_* enumerate fd_alloc_hdr_t types */
     133             : 
     134      650949 : #define FD_ALLOC_HDR_TYPE_USER_SMALL      (0) /* This is a (small) user alloc contained in a superblock */
     135        5403 : #define FD_ALLOC_HDR_TYPE_USER_LARGE      (1) /* This is a (large) user alloc contained in a workspace partition */
     136        7236 : #define FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK (2) /* This is a superblock contained in a (larger) superblock */
     137        1755 : #define FD_ALLOC_HDR_TYPE_ROOT_SUPERBLOCK (3) /* This is a superblock contained in a workspace partition */
     138             : 
     139             : /* fd_alloc_hdr packs a (type,idx,off) tuple into a fd_alloc_hdr_t.
     140             :    fd_alloc_hdr_{type,idx,off} unpack the corresponding field from a
     141             :    fd_alloc_hdr_t. */
     142             : 
     143             : FD_FN_CONST static inline fd_alloc_hdr_t
     144             : fd_alloc_hdr( int   type,   /* FD_ALLOC_HDR_TYPE_USER_SMALL or FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK */
     145             :               ulong idx,    /* in [0,64), containing superblock block block_idx */
     146      658185 :               ulong off ) { /* in [0,2^26), aligned 4, byte offset of alloc/superblock in containing superblock */
     147      658185 :   return (fd_alloc_hdr_t)((off<<6) | (idx<<2) | (ulong)type);
     148      658185 : }
     149             : 
     150           0 : FD_FN_CONST static inline int   fd_alloc_hdr_type( fd_alloc_hdr_t hdr ) { return (int)  ( hdr     &  3U); } /* FD_ALLOC_HDR_TYPE */
     151      652641 : FD_FN_CONST static inline ulong fd_alloc_hdr_idx ( fd_alloc_hdr_t hdr ) { return (ulong)((hdr>>2) & 63U); } /* in [0,64) */
     152      652641 : FD_FN_CONST static inline ulong fd_alloc_hdr_off ( fd_alloc_hdr_t hdr ) { return (ulong)((hdr>>8) << 2);  } /* in [0,2^26), aligned 4 */
     153             : 
     154             : /* FD_ALLOC_HDR_{USER_LARGE,ROOT_SUPERBLOCK} allocations do not need the
     155             :    fd_alloc_hdr_t idx and off fields.  These fields are replaced by
     156             :    magic numbers to help with various allocation diagnostics. */
     157             : 
     158        5403 : #define FD_ALLOC_HDR_USER_LARGE      (0xfdac5e70U | (uint)FD_ALLOC_HDR_TYPE_USER_LARGE     )
     159        1755 : #define FD_ALLOC_HDR_ROOT_SUPERBLOCK (0xfda70010U | (uint)FD_ALLOC_HDR_TYPE_ROOT_SUPERBLOCK)
     160             : 
     161             : /* fd_alloc_hdr_load loads the header for the allocation whose first
     162             :    byte is at laddr in the caller's address space.  The header will be
     163             :    observed at some point of time between when this call was made and
     164             :    returned.  This implies that the allocation at laddr must be valid at
     165             :    least until the caller stops using the hdr.
     166             : 
     167             :    fd_alloc_hdr_store stores a fd_alloc_hdr_t to the
     168             :    sizeof(fd_alloc_hdr_t) bytes immediately preceding the byte pointed
     169             :    to by laddr in the caller's address space.  The caller promises that
     170             :    these bytes are somewhere within the containing block or wksp
     171             :    allocation.
     172             : 
     173             :    Note that superblocks are aligned FD_ALLOC_SUPERBLOCK_ALIGN.
     174             :    Further, to support superblock nesting, the blocks in a superblock
     175             :    are similarly aligned.  As such, fd_alloc_hdr_t (which need an
     176             :    alignment less than this) are guaranteed to be loaded/stored at
     177             :    alignof(fd_alloc_hdr_t) aligned locations. */
     178             : 
     179             : FD_FN_PURE static inline fd_alloc_hdr_t
     180      650904 : fd_alloc_hdr_load( void const * laddr ) { /* Aligned at least alignof(fd_alloc_hdr_t) */
     181      650904 :   return *(fd_alloc_hdr_t const *)((ulong)laddr - sizeof(fd_alloc_hdr_t));
     182      650904 : }
     183             : 
     184             : static inline void *
     185             : fd_alloc_hdr_store( void *         laddr,  /* Aligned at least alignof(fd_alloc_hdr_t) */
     186      656352 :                     fd_alloc_hdr_t hdr ) {
     187      656352 :   *(fd_alloc_hdr_t *)((ulong)laddr - sizeof(fd_alloc_hdr_t)) = hdr;
     188      656352 :   return laddr;
     189      656352 : }
     190             : 
     191             : /* fd_alloc_superblock ************************************************/
     192             : 
     193             : struct __attribute__((aligned(FD_ALLOC_SUPERBLOCK_ALIGN))) fd_alloc_superblock {
     194             :   fd_alloc_hdr_t       hdr;         /* ==FD_ALLOC_HDR_ROOT_SUPERBLOCK or (FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK,idx,off) */
     195             :   ushort               sizeclass;   /* superblock sizeclass, in [0,FD_ALLOC_SIZECLASS_CNT) */
     196             :   uchar                block_cnt;   /* ==fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt */
     197             :   uchar                cgroup_mask; /* ==fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask */
     198             :   fd_alloc_block_set_t free_blocks; /* which blocks in this superblock are free */
     199             :   ulong                next_gaddr;  /* if on the inactive superblock stack, next inactive superblock or NULL, ignored o.w. */
     200             : 
     201             :   /* TODO: consider making a ulong bit-packed tuple for
     202             :      (sizeclass,block_cnt,cgroup_mask) and adding a cgroup_hint to it? */
     203             : 
     204             :   /* Storage for blocks follows */
     205             : };
     206             : 
     207             : typedef struct fd_alloc_superblock fd_alloc_superblock_t;
     208             : 
     209             : /* fd_alloc ***********************************************************/
     210             : 
     211             : /* fd_alloc_vgaddr_t provides APIs for versioned gaddrs used in an
     212             :    fd_alloc_t's sizeclass inactive stack. */
     213             : 
     214             : #define VOFF_NAME      fd_alloc_vgaddr
     215             : #define VOFF_TYPE      ulong
     216      701034 : #define VOFF_VER_WIDTH 17
     217             : #include "../tmpl/fd_voff.c"
     218             : 
     219             : /* fd_alloc_private_active_slot returns a pointer to the location in the
     220             :    caller's address space where the gaddr of the active superblock for
     221             :    (sizeclass,cgroup) is stored.  Assumes alloc is a non-NULL pointer in
     222             :    the caller's address space to the fd_alloc (not a join handle). */
     223             : 
     224             : FD_FN_CONST static inline ulong *
     225             : fd_alloc_private_active_slot( fd_alloc_t * alloc,
     226             :                               ulong        sizeclass,
     227      835488 :                               ulong        cgroup ) {
     228      835488 :   return alloc->active_slot + sizeclass + FD_ALLOC_SIZECLASS_MAX*cgroup;
     229      835488 : }
     230             : 
     231             : /* fd_alloc_private_active_slot_replace replaces the value currently in
     232             :    the slot pointed to by active_slot with new_superblock_gaddr and
     233             :    returns the superblock_gaddr previously in there.  This is a compiler
     234             :    fence.  If FD_HAS_ATOMIC, this will be done atomically. */
     235             : 
     236             : static inline ulong
     237             : fd_alloc_private_active_slot_replace( ulong * _active_slot,
     238     1380033 :                                       ulong   new_superblock_gaddr ) {
     239     1380033 :   FD_COMPILER_MFENCE();
     240     1380033 : # if FD_HAS_ATOMIC
     241     1380033 :   ulong old_superblock_gaddr = FD_ATOMIC_XCHG( _active_slot, new_superblock_gaddr );
     242             : # else
     243             :   ulong old_superblock_gaddr = *_active_slot;
     244             :   *_active_slot = new_superblock_gaddr;
     245             : # endif
     246     1380033 :   FD_COMPILER_MFENCE();
     247     1380033 :   return old_superblock_gaddr;
     248     1380033 : }
     249             : 
     250             : /* fd_alloc_private_inactive_stack_push pushes the superblock at the
     251             :    workspace global address superblock_gaddr in workspace wksp onto the
     252             :    stack _inactive_stack.  This is a compiler fence.  If FD_HAS_ATOMIC,
     253             :    this will be done atomically. */
     254             : 
     255             : static inline void
     256             : fd_alloc_private_inactive_stack_push( fd_alloc_vgaddr_t * _inactive_stack,
     257             :                                       fd_wksp_t *         wksp,
     258       40134 :                                       ulong               superblock_gaddr ) {
     259       40134 :   fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     260             : 
     261       40134 :   for(;;) {
     262             : 
     263             :     /* Read the top of the inactive stack. */
     264             : 
     265       40134 :     FD_COMPILER_MFENCE();
     266       40134 :     fd_alloc_vgaddr_t old = *_inactive_stack;
     267       40134 :     FD_COMPILER_MFENCE();
     268             : 
     269       40134 :     ulong top_ver   = fd_alloc_vgaddr_ver( old );
     270       40134 :     ulong top_gaddr = fd_alloc_vgaddr_off( old ) << FD_ALLOC_SUPERBLOCK_LG_ALIGN;
     271             : 
     272             :     /* Try to push the top of the inactive stack */
     273             : 
     274       40134 :     fd_alloc_vgaddr_t new = fd_alloc_vgaddr( top_ver+1UL, superblock_gaddr >> FD_ALLOC_SUPERBLOCK_LG_ALIGN );
     275             : 
     276       40134 :     FD_COMPILER_MFENCE();
     277       40134 :     superblock->next_gaddr = top_gaddr;
     278       40134 :     FD_COMPILER_MFENCE();
     279             : 
     280       40134 : #   if FD_HAS_ATOMIC
     281       40134 :     if( FD_LIKELY( FD_ATOMIC_CAS( _inactive_stack, old, new )==old ) ) break;
     282             : #   else
     283             :     if( FD_LIKELY( (*_inactive_stack)==old ) ) { *_inactive_stack = new; break; }
     284             : #   endif
     285             : 
     286             :     /* Hmmm ... that failed ... try again */
     287             : 
     288           0 :     FD_SPIN_PAUSE();
     289           0 :   }
     290             : 
     291       40134 :   FD_COMPILER_MFENCE();
     292       40134 : }
     293             : 
     294             : /* fd_alloc_private_inactive_stack_pop pops the superblock off the top
     295             :    of the stack _inactive_stack.  Returns the non-zero wksp superblock
     296             :    gaddr of the popped stack top on success or 0 on failure (i.e.
     297             :    _inactive_stack at some point in time between which this was called
     298             :    and this returned).  This is a compiler fence.  If FD_HAS_ATOMIC,
     299             :    this will be done atomically. */
     300             : 
     301             : #if FD_HAS_DEEPASAN
     302             : FD_FN_NO_ASAN static
     303             : #else
     304             : static inline
     305             : #endif
     306             : ulong
     307             : fd_alloc_private_inactive_stack_pop( fd_alloc_vgaddr_t * _inactive_stack,
     308      205593 :                                      fd_wksp_t *         wksp ) {
     309      205593 :   ulong top_gaddr;
     310             : 
     311      205593 :   for(;;) {
     312             : 
     313             :     /* Read the top of the inactive stack.  Return if the inactive stack
     314             :        is empty. */
     315             : 
     316      205593 :     FD_COMPILER_MFENCE();
     317      205593 :     fd_alloc_vgaddr_t old = *_inactive_stack;
     318      205593 :     FD_COMPILER_MFENCE();
     319             : 
     320      205593 :     /**/  top_gaddr = fd_alloc_vgaddr_off( old ) << FD_ALLOC_SUPERBLOCK_LG_ALIGN;
     321      205593 :     ulong top_ver   = fd_alloc_vgaddr_ver( old );
     322      205593 :     if( FD_UNLIKELY( !top_gaddr ) ) break;
     323             : 
     324             :     /* Try to pop the top of the inactive stack. */
     325             : 
     326       40134 :     fd_alloc_superblock_t * top = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, top_gaddr );
     327             : 
     328             :     /* Note: under concurrent FD_HAS_DEEPASAN operation, another thread
     329             :        could pop and free the inactive superblock at the top and free it
     330             :        (thus poisoning top) after our top read above and before our
     331             :        next_gaddr read below.  This could trigger a spurious asan
     332             :        failure (that, in normal operation, would have been failed and
     333             :        retried via the CAS below).  Hence the noasan above. */
     334             : 
     335       40134 :     FD_COMPILER_MFENCE();
     336       40134 :     ulong next_gaddr = top->next_gaddr;
     337       40134 :     FD_COMPILER_MFENCE();
     338             : 
     339       40134 :     fd_alloc_vgaddr_t new = fd_alloc_vgaddr( top_ver+1UL, next_gaddr >> FD_ALLOC_SUPERBLOCK_LG_ALIGN );
     340             : 
     341       40134 : #   if FD_HAS_ATOMIC
     342       40134 :     if( FD_LIKELY( FD_ATOMIC_CAS( _inactive_stack, old, new )==old ) ) break;
     343             : #   else
     344             :     if( FD_LIKELY( (*_inactive_stack)==old ) ) { *_inactive_stack = new; break; }
     345             : #   endif
     346             : 
     347             :     /* Hmmm ... that failed ... try again */
     348             : 
     349           0 :     FD_SPIN_PAUSE();
     350           0 :   }
     351             : 
     352      205593 :   FD_COMPILER_MFENCE();
     353             : 
     354      205593 :   return top_gaddr;
     355      205593 : }
     356             : 
     357             : /* fd_alloc_private_alloc will allocate a sizeclass sized block from
     358             :    the given alloc with the concurrency hint cgroup_hint.  On success,
     359             :    returns the superblock that contains the allocated block in the
     360             :    caller's address space and *_block_idx will hold the index of the
     361             :    allocated block.  On failure, returns NULL and *_block_idx is
     362             :    unchanged. */
     363             : 
     364             : static fd_alloc_superblock_t *
     365             : fd_alloc_private_alloc( fd_alloc_t * alloc,
     366             :                         fd_wksp_t *  wksp,
     367             :                         ulong        sizeclass,
     368             :                         ulong        cgroup_hint,
     369             :                         ulong *      _block_idx );
     370             : 
     371             : /* fd_alloc_private_free frees (superblock,block_idx).  Assumes join is a
     372             :    current local join to an allocator, superblock points in the caller's
     373             :    address space to a valid superblock from this allocator, and
     374             :    block_idx a valid block index for this superblock that is currently
     375             :    allocated. */
     376             : 
     377             : static void
     378             : fd_alloc_private_free( fd_alloc_t *            join,
     379             :                        fd_alloc_superblock_t * superblock,
     380             :                        ulong                   block_idx );
     381             : 
     382             : /* Constructors *******************************************************/
     383             : 
     384             : ulong
     385        2586 : fd_alloc_align( void ) {
     386        2586 :   return alignof(fd_alloc_t);
     387        2586 : }
     388             : 
     389             : ulong
     390         549 : fd_alloc_footprint( void ) {
     391         549 :   return sizeof(fd_alloc_t);
     392         549 : }
     393             : 
     394             : void *
     395             : fd_alloc_new( void * shmem,
     396         168 :               ulong  tag ) {
     397             : 
     398         168 :   if( FD_UNLIKELY( !shmem ) ) {
     399           3 :     FD_LOG_WARNING(( "NULL shmem" ));
     400           3 :     return NULL;
     401           3 :   }
     402             : 
     403         165 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, alignof(fd_alloc_t) ) ) ) {
     404           3 :     FD_LOG_WARNING(( "misaligned shmem" ));
     405           3 :     return NULL;
     406           3 :   }
     407             : 
     408         162 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
     409         162 :   if( FD_UNLIKELY( !wksp ) ) {
     410           3 :     FD_LOG_WARNING(( "shmem must be in a workspace" ));
     411           3 :     return NULL;
     412           3 :   }
     413             : 
     414         159 :   if( FD_UNLIKELY( fd_wksp_gaddr_hi( wksp ) > (1UL<<(fd_alloc_vgaddr_OFF_WIDTH + FD_ALLOC_SUPERBLOCK_LG_ALIGN)) ) ) {
     415           0 :     FD_LOG_WARNING(( "wksp too large for current fd_alloc implementation" ));
     416           0 :     return NULL;
     417           0 :   }
     418             : 
     419         159 :   if( FD_UNLIKELY( !tag ) ) {
     420           3 :     FD_LOG_WARNING(( "bad tag" ));
     421           3 :     return NULL;
     422           3 :   }
     423             : 
     424         156 :   fd_alloc_t * alloc = (fd_alloc_t *)shmem;
     425         156 :   memset( alloc, 0, sizeof(fd_alloc_t) );
     426             : 
     427         156 :   alloc->wksp_off = (ulong)alloc - (ulong)wksp;
     428         156 :   alloc->tag      = tag;
     429             : 
     430         156 :   FD_COMPILER_MFENCE();
     431         156 :   alloc->magic = FD_ALLOC_MAGIC;
     432         156 :   FD_COMPILER_MFENCE();
     433             : 
     434         156 :   return shmem;
     435         159 : }
     436             : 
     437             : fd_alloc_t *
     438             : fd_alloc_join( void * shalloc,
     439         495 :                ulong  cgroup_hint ) {
     440         495 :   fd_alloc_t * alloc = shalloc;
     441             : 
     442         495 :   if( FD_UNLIKELY( !alloc ) ) {
     443           3 :     FD_LOG_WARNING(( "NULL shalloc" ));
     444           3 :     return NULL;
     445           3 :   }
     446             : 
     447         492 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)alloc, alignof(fd_alloc_t) ) ) ) {
     448           3 :     FD_LOG_WARNING(( "misaligned shalloc" ));
     449           3 :     return NULL;
     450           3 :   }
     451             : 
     452         489 :   if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) {
     453           9 :     FD_LOG_WARNING(( "bad magic" ));
     454           9 :     return NULL;
     455           9 :   }
     456             : 
     457         480 :   return fd_alloc_join_cgroup_hint_set( alloc, cgroup_hint );
     458         489 : }
     459             : 
     460             : void *
     461         192 : fd_alloc_leave( fd_alloc_t * join ) {
     462             : 
     463         192 :   if( FD_UNLIKELY( !join ) ) {
     464           3 :     FD_LOG_WARNING(( "NULL join" ));
     465           3 :     return NULL;
     466           3 :   }
     467             : 
     468         189 :   return fd_alloc_private_join_alloc( join );
     469         192 : }
     470             : 
     471             : void *
     472             : fd_alloc_private_delete( void * shalloc,
     473         159 :                          int    level ) {
     474             : 
     475         159 :   if( FD_UNLIKELY( !shalloc ) ) {
     476           3 :     FD_LOG_WARNING(( "NULL shalloc" ));
     477           3 :     return NULL;
     478           3 :   }
     479             : 
     480         156 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shalloc, alignof(fd_alloc_t) ) ) ) {
     481           3 :     FD_LOG_WARNING(( "misaligned shalloc" ));
     482           3 :     return NULL;
     483           3 :   }
     484             : 
     485         153 :   fd_alloc_t * alloc = (fd_alloc_t *)shalloc;
     486             : 
     487         153 :   if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) {
     488           3 :     FD_LOG_WARNING(( "bad magic" ));
     489           3 :     return NULL;
     490           3 :   }
     491             : 
     492         150 :   FD_COMPILER_MFENCE();
     493         150 :   alloc->magic = 0UL;
     494         150 :   FD_COMPILER_MFENCE();
     495             : 
     496         150 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     497             : 
     498         150 :   if( level<=0 ) { /* no wksp cleanup */
     499             : 
     500             :     /* nothing to do */
     501             : 
     502         150 :   } else if( level<=1 ) { /* quick wksp cleanup */
     503             : 
     504             :     /* For each sizeclass, make all active superblocks inactive and then
     505             :        delete all inactive superblocks.  This will not cleanup any
     506             :        superblocks that are fully allocated (and thus out of circulation
     507             :        for malloc) or any large allocations as a quick wksp cleanup
     508             :        assumes the application freed all outstanding allocations before
     509             :        calling this. */
     510             : 
     511        9300 :     for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
     512        9150 :       fd_alloc_vgaddr_t * _inactive_stack = alloc->inactive_stack + sizeclass;
     513             : 
     514        9150 :       ulong cgroup_cnt = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
     515       36450 :       for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
     516       27300 :         ulong superblock_gaddr =
     517       27300 :           fd_alloc_private_active_slot_replace( fd_alloc_private_active_slot( alloc, sizeclass, cgroup_idx ), 0UL );
     518       27300 :         if( FD_UNLIKELY( superblock_gaddr ) ) fd_alloc_private_inactive_stack_push( _inactive_stack, wksp, superblock_gaddr );
     519       27300 :       }
     520             : 
     521       10302 :       for(;;) {
     522       10302 :         ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( _inactive_stack, wksp );
     523       10302 :         if( FD_LIKELY( !superblock_gaddr ) ) break;
     524        1152 :         fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     525             : 
     526        1152 :         fd_alloc_hdr_t hdr = superblock->hdr;
     527        1152 :         if( FD_UNLIKELY( hdr==FD_ALLOC_HDR_ROOT_SUPERBLOCK ) ) fd_wksp_free( wksp, superblock_gaddr );
     528         954 :         else {
     529         954 :           fd_alloc_superblock_t * parent_superblock = (fd_alloc_superblock_t *)((ulong)superblock - fd_alloc_hdr_off( hdr ));
     530         954 :           ulong                   parent_block_idx  = fd_alloc_hdr_idx( hdr );
     531         954 :           fd_alloc_private_free( alloc, parent_superblock, parent_block_idx );
     532         954 :         }
     533        1152 :       }
     534        9150 :     }
     535             : 
     536         150 :   } else { /* deep wksp cleanup (level>1) */
     537             : 
     538             :     /* A deep wksp cleanup will free all wksp allocations that match the
     539             :        alloc's tag. */
     540             : 
     541           0 :     fd_wksp_tag_free( wksp, &alloc->tag, 1UL );
     542             : 
     543           0 :   }
     544             : 
     545         150 :   return shalloc;
     546         153 : }
     547             : 
     548         159 : void * fd_alloc_delete( void * shalloc ) { return fd_alloc_private_delete( shalloc, 1 ); }
     549             : 
     550             : static fd_alloc_superblock_t *
     551             : fd_alloc_private_alloc( fd_alloc_t * alloc,
     552             :                         fd_wksp_t *  wksp,
     553             :                         ulong        sizeclass,
     554             :                         ulong        cgroup_hint,
     555      672927 :                         ulong *      _block_idx ) {
     556             : 
     557      672927 :   ulong cgroup_mask = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask;
     558             : 
     559      672927 :   ulong cgroup = cgroup_hint & cgroup_mask;
     560             : 
     561             :   /* Try to get exclusive access to the preferred active superblock
     562             :      for (sizeclass,cgroup).  Note that all active superblocks have at
     563             :      least one free block.  We do a test-and-test-and-set style to avoid
     564             :      an atomic operation if there currently isn't an active superblock
     565             :      for (sizeclass,cgroup). */
     566             : 
     567      672927 :   ulong * active_slot = fd_alloc_private_active_slot( alloc, sizeclass, cgroup );
     568             : 
     569      672927 :   ulong superblock_gaddr = *active_slot;
     570             : 
     571      672927 :   if( FD_LIKELY( superblock_gaddr ) ) superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, 0UL );
     572             : 
     573             :   /* At this point, if superblock_gaddr is non-zero, we have exclusive
     574             :      access to the superblock and only we can allocate blocks from it.
     575             :      (Other threads could free blocks to it concurrently though.)
     576             : 
     577             :      If superblock_gaddr is zero, there was no preferred active
     578             :      superblock for (sizeclass,cgroup) when we looked.  So, we try to
     579             :      pop the inactive superblock stack for this sizeclass.  Note that
     580             :      all inactive superblocks also have at least one free block.
     581             : 
     582             :      If that fails, we try to allocate a new superblock to hold this
     583             :      allocation.  If we are able to do so, obviously the new superblock
     584             :      will have at least one free block for this allocation.  (Yes,
     585             :      malloc calls itself recursively.  The base case is root superblock
     586             :      allocation from the underlying workspace.)
     587             : 
     588             :      If that fails, we are in trouble and fail (we are either out of
     589             :      memory or have too much wksp fragmentation). */
     590             : 
     591      672927 :   if( FD_UNLIKELY( !superblock_gaddr ) ) {
     592             : 
     593       52356 :     superblock_gaddr = fd_alloc_private_inactive_stack_pop( alloc->inactive_stack + sizeclass, wksp );
     594             : 
     595       52356 :     if( FD_UNLIKELY( !superblock_gaddr ) ) {
     596             : 
     597       23733 :       fd_alloc_superblock_t * superblock;
     598       23733 :       fd_alloc_hdr_t          hdr;
     599             : 
     600       23733 :       ulong parent_sizeclass = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].parent_sizeclass;
     601             : 
     602       23733 :       if( FD_LIKELY( parent_sizeclass<FD_ALLOC_SIZECLASS_CNT ) ) {
     603             : 
     604       17040 :         ulong                   parent_idx;
     605       17040 :         fd_alloc_superblock_t * parent_superblock =
     606       17040 :           fd_alloc_private_alloc( alloc, wksp, parent_sizeclass, cgroup_hint, &parent_idx );
     607       17040 :         if( FD_UNLIKELY( !parent_superblock ) ) return NULL;
     608             : 
     609        7236 :         ulong parent_off = sizeof(fd_alloc_superblock_t)
     610        7236 :                          + parent_idx*(ulong)fd_alloc_sizeclass_cfg[ parent_sizeclass ].block_footprint;
     611             : 
     612        7236 :         superblock = (fd_alloc_superblock_t *)((ulong)parent_superblock + parent_off);
     613        7236 :         hdr        = fd_alloc_hdr( FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK, parent_idx, parent_off );
     614             : 
     615             : #       if FD_HAS_DEEPASAN
     616             :         /* At this point, the block containing the nested superblock is
     617             :            poisoned.  Unpoison the header region.  Note that blocks are
     618             :            aligned FD_ALLOC_SUPERBLOCK_ALIGN, FD_ALLOC_SUPERBLOCK_ALIGN
     619             :            is at least FD_ASAN_ALIGN and sizeof(fd_alloc_superblock_t)
     620             :            is a FD_ALLOC_SUPERBLOCK_ALIGN multiple. */
     621             :         fd_asan_unpoison( superblock, sizeof(fd_alloc_superblock_t) );
     622             : #       endif
     623             : 
     624        7236 :         superblock_gaddr = fd_wksp_gaddr_fast( wksp, superblock );
     625             : 
     626        7236 :       } else {
     627             : 
     628        6693 :         superblock_gaddr = fd_wksp_alloc( wksp, FD_ALLOC_SUPERBLOCK_ALIGN, FD_ALLOC_ROOT_SUPERBLOCK_FOOTPRINT, alloc->tag );
     629        6693 :         if( FD_UNLIKELY( !superblock_gaddr ) ) return NULL;
     630             : 
     631        1755 :         superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     632        1755 :         hdr        = FD_ALLOC_HDR_ROOT_SUPERBLOCK;
     633             : 
     634             : #       if FD_HAS_DEEPASAN
     635             :         /* At this point, the entire root superblock is unpoisoned.
     636             :            Poison the superblock block (keeping the header unpoisoned).
     637             :            See note above regarding alignments. */
     638             :         fd_asan_poison( superblock+1, FD_ALLOC_ROOT_SUPERBLOCK_FOOTPRINT - sizeof(fd_alloc_superblock_t) );
     639             : #       endif
     640             : 
     641        1755 :       }
     642             : 
     643        8991 :       ulong block_cnt = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt;
     644             : 
     645        8991 :       superblock->hdr         = hdr;
     646        8991 :       superblock->sizeclass   = (ushort)sizeclass;
     647        8991 :       superblock->block_cnt   = (uchar)block_cnt;
     648        8991 :       superblock->cgroup_mask = (uchar)cgroup_mask;
     649        8991 :       superblock->free_blocks = fd_alloc_block_set_all( block_cnt );
     650        8991 :       superblock->next_gaddr  = 0UL;
     651             : 
     652        8991 :     }
     653       52356 :   }
     654             : 
     655             :   /* At this point, we have a superblock with space for at least one
     656             :      allocation and only we can allocate blocks from it.  Other threads
     657             :      could free blocks in this superblock concurrently though.  As such,
     658             :      we can non-atomically find a set bit in free_blocks (there will be
     659             :      at least one and no other thread will clear it behind our back) but
     660             :      we must atomically clear the bit we found so we don't mess up the
     661             :      other bits that might be concurrently set by free. */
     662             : 
     663      658185 :   fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     664             : 
     665      658185 :   fd_alloc_block_set_t * _free_blocks = &superblock->free_blocks;
     666             : 
     667      658185 :   FD_COMPILER_MFENCE();
     668      658185 :   fd_alloc_block_set_t free_blocks_before = *_free_blocks;
     669      658185 :   FD_COMPILER_MFENCE();
     670             : 
     671      658185 :   ulong block_idx = (ulong)fd_alloc_block_set_first( free_blocks_before );
     672             : 
     673      658185 :   fd_alloc_block_set_t free_blocks_after = fd_alloc_block_set_sub_then_fetch( _free_blocks, fd_alloc_block_set_ele( block_idx ) );
     674             : 
     675             :   /* At this point, we've allocated block block_idx from the superblock
     676             :      and free_blocks_after gives the set of free blocks in the
     677             :      superblock immediately after the allocation occurred. */
     678             : 
     679      658185 :   if( FD_LIKELY( free_blocks_after ) ) {
     680             : 
     681             :     /* At this point, we know the superblock has at least one
     682             :        allocated block in it (the one we just allocated) and one free
     683             :        block in it.  And this will hold true until we put this
     684             :        superblock back into circulation.  Specifically, nobody can free
     685             :        the block we just allocated until we return to tell them about it
     686             :        and nobody can allocate any remaining free blocks until we get
     687             :        this superblock back into circulation.  To get this superblock
     688             :        back into circulation, we make it the active superblock for
     689             :        (sizeclass,cgroup). */
     690             : 
     691      595935 :     ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, superblock_gaddr );
     692             : 
     693             :     /* And if this displaced a previously active superblock (e.g.
     694             :        another thread made a different superblock the active one while
     695             :        we were doing the above), we add the displaced superblock to the
     696             :        sizeclass's inactive superblocks.  Note that any such displaced
     697             :        superblock also has at least one free block in it (the active
     698             :        superblock always has at least one free block) that nobody can
     699             :        allocate from as, at this point, it is not in circulation.  Thus,
     700             :        pushing it onto the superblock's inactive stack will preserve the
     701             :        invariant that all inactive superblocks have at least one free
     702             :        block. */
     703             : 
     704      595935 :     if( FD_UNLIKELY( displaced_superblock_gaddr ) )
     705           0 :       fd_alloc_private_inactive_stack_push( alloc->inactive_stack + sizeclass, wksp, displaced_superblock_gaddr );
     706             : 
     707      595935 :   } //else {
     708             : 
     709             :     /* The superblock had no more free blocks immediately after the
     710             :        allocation occurred.  We should not make put this superblock into
     711             :        circulation as it would break the invariants that all superblocks
     712             :        in circulation have at least one free block.
     713             : 
     714             :        And, as this superblock had no free blocks, we don't need to
     715             :        track the superblock anyway as malloc can't use the superblock
     716             :        until some of the blocks in it have been freed.  As such, this
     717             :        superblock will not be used in a malloc until after the next call
     718             :        to free on a block in this superblock returns this superblock to
     719             :        circulation.  Note that this superblock will have at least one
     720             :        allocated block until after this function returns (the block we
     721             :        just allocated) and thus cannot ever be considered as a deletion
     722             :        candidate until after this function returns and this allocation
     723             :        is freed.
     724             : 
     725             :        As discussed in free below, we could update a superblock cgroup
     726             :        hint here (such that the when the superblock goes back into
     727             :        circulation, it will be put into circulation as the active
     728             :        superblock for this cgroup to encourage for additional mallocs
     729             :        from this thread for good spatial locality).  This doesn't need
     730             :        to be atomic.  Even though a concurrent free on another thread
     731             :        might get this into superblock into circulation before this
     732             :        executes (and thus also have other mallocs occurred that changed
     733             :        the active_hint), it doesn't matter.  So long as the hint is a
     734             :        sane value at all points in time, free will work fine. */
     735             : 
     736             : //}
     737             : 
     738      658185 :   *_block_idx = block_idx;
     739      658185 :   return superblock;
     740      672927 : }
     741             : 
     742             : static void
     743             : fd_alloc_private_free( fd_alloc_t *            join,
     744             :                        fd_alloc_superblock_t * superblock,
     745      652641 :                        ulong                   block_idx ) {
     746             : 
     747             :   /* These reads and the ASAN poisoning must be before the free because
     748             :      block could potentially be reused by other threads the moment it is
     749             :      marked as free. */
     750             : 
     751      652641 :   ulong sizeclass   = (ulong)superblock->sizeclass;
     752      652641 :   ulong block_cnt   = (ulong)superblock->block_cnt;
     753      652641 :   ulong cgroup_mask = (ulong)superblock->cgroup_mask;
     754             : 
     755             : # if FD_HAS_DEEPASAN /* Poison the block we are about to free */
     756             :   ulong block_footprint = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
     757             :   ulong block_laddr     = (ulong)superblock + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
     758             :   fd_asan_poison( (void *)block_laddr, block_footprint );
     759             : # endif
     760             : 
     761      652641 :   fd_alloc_block_set_t * _free_blocks = &superblock->free_blocks;
     762             : 
     763      652641 :   fd_alloc_block_set_t block = fd_alloc_block_set_ele( block_idx );
     764             : 
     765      652641 :   fd_alloc_block_set_t free_blocks_after = fd_alloc_block_set_add_then_fetch( _free_blocks, block );
     766             : 
     767             :   /* At this point, superblock is no longer safe to read and
     768             :      free_blocks_after is the set of free blocks just after the free. */
     769             : 
     770      652641 :   if( FD_UNLIKELY( free_blocks_after==block ) ) {
     771             : 
     772             :     /* The superblock containing this block had no free blocks
     773             :        immediately before we freed the allocation.  Thus, at this point,
     774             :        nobody can allocate any blocks from this superblock (the
     775             :        superblock is neither an active superblock nor on the inactive
     776             :        stack as per the note in malloc above) and we need to get the
     777             :        superblock back into circulation for reuse.  It is okay if other
     778             :        threads concurrently free other blocks in this superblock while
     779             :        we are doing this (they know the superblock is either in
     780             :        circulation or is being put into circulation).
     781             : 
     782             :        Since there is at least one free block in the superblock and
     783             :        nobody can allocate from it until it is circulation, putting it
     784             :        into circulation preserves the invariant that all superblocks in
     785             :        circulation have at least one free block.
     786             : 
     787             :        We have a bunch of options for putting this superblock back into
     788             :        circulation:
     789             : 
     790             :        - By pushing it onto the inactive stack
     791             :        - By making it the active superblock of the caller's cgroup
     792             :        - By making it the active superblock of the cgroup that most did
     793             :          the most recent malloc from it.
     794             :        - By making it the active superblock based on explicitly provided
     795             :          hint.
     796             :        - ...
     797             : 
     798             :        The first option is simplest to implement and balanced between
     799             :        common use cases single threaded, malloc/free pairs have thread
     800             :        affinity, and pipelined use cases.  (E.g. single threaded will
     801             :        take an extra time to hop from inactive and active and potential
     802             :        has slightly worse overallocation, similar story for paired.
     803             :        Cache affinity in these two cases might be slightly degraded from
     804             :        empty superblocks hopping between concurrency groups via the
     805             :        inactive stack, pipelined naturally gets the page back to the
     806             :        malloc-ing thread albeit with a brief hop through the inactive
     807             :        stack though).
     808             : 
     809             :        The second option is about as simple and optimizes the single
     810             :        threaded and paired use cases as this thread is also likely the
     811             :        same thread that malloc'd this.  Pipelined is marginally worse as
     812             :        the superblock will have to take two hops before it gets reused
     813             :        again (from the free-ing thread active superblock to the inactive
     814             :        stack to the malloc-ing active superblock).
     815             : 
     816             :        The third and fourth options can simultaneously get all options
     817             :        optimized but they require extra plumbing (either under the hood
     818             :        as per the note in malloc above or from the caller to get the
     819             :        extra context).
     820             : 
     821             :        Currently we do the second option for simplicity and optimal
     822             :        behaviors in the single threaded and paired use cases.  (The
     823             :        fourth option is possible via the user changing the join's
     824             :        cgroup_hint to match the thread of the original allocator.) */
     825             : 
     826       62097 :     fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     827       62097 :     fd_wksp_t *  wksp  = fd_alloc_private_wksp( alloc );
     828             : 
     829       62097 :     ulong cgroup = fd_alloc_join_cgroup_hint( join ) & cgroup_mask;
     830             : 
     831       62097 :     ulong * _active_slot = fd_alloc_private_active_slot( alloc, sizeclass, cgroup );
     832             : 
     833       62097 :     ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( _active_slot, fd_wksp_gaddr_fast( wksp, superblock ) );
     834             : 
     835             :     /* If this displaced an already active superblock, we need to push
     836             :        the displaced superblock onto the inactive stack (note that the
     837             :        superblock cannot be the same as the currently active superblock
     838             :        because the superblock was not in circulation before). */
     839             : 
     840       62097 :     if( FD_UNLIKELY( displaced_superblock_gaddr ) )
     841       37023 :       fd_alloc_private_inactive_stack_push( alloc->inactive_stack + sizeclass, wksp, displaced_superblock_gaddr );
     842             : 
     843       62097 :     return;
     844             : 
     845       62097 :   }
     846             : 
     847      590544 :   ulong all_blocks = fd_alloc_block_set_all( block_cnt );
     848             : 
     849      590544 :   if( FD_LIKELY( free_blocks_after!=all_blocks ) ) return;
     850             : 
     851             :   /* None of the blocks were in use after the above free.  We might
     852             :      consider freeing it to reclaim space for other sizeclasses or
     853             :      large allocations.  But we don't mind having a few totally empty
     854             :      superblocks in circulation for a sizeclass as this prevents
     855             :      things like:
     856             : 
     857             :        addr = malloc(sz);
     858             :        free(addr);
     859             :        addr = malloc(sz);
     860             :        free(addr)
     861             :        ...
     862             :        addr = malloc(sz);
     863             :        free(addr)
     864             : 
     865             :      from repeatedly needing to invoke malloc recursively to recreate
     866             :      superblock hierarchies that were prematurely freed.
     867             : 
     868             :      Regardless, since this superblock is in circulation, we can't be
     869             :      sure it is safe to delete because something might be malloc-ing
     870             :      from it concurrently.  Thus, we are going to keep this superblock
     871             :      in circulation as is.
     872             : 
     873             :      But, since we know we have at least 1 completely empty superblock
     874             :      in circulation now, to prevent the unbounded accumulation of
     875             :      completely empty superblocks, we will try to get an inactive
     876             :      superblock and, if that is empty, delete that.
     877             : 
     878             :      This is pretty tricky as it is possible other threads are
     879             :      concurrently trying to pop the inactive stack to do a malloc.  If
     880             :      we actually unmapped the memory here, such a thread could seg
     881             :      fault if it stalls after it reads the top of the stack but before
     882             :      it queries the top for the next_gaddr (and we'd have to use
     883             :      another strategy).  But that is not an issue here as the
     884             :      underlying wksp memory is still mapped post-deletion regardless.
     885             : 
     886             :      Likewise, though the post deletion top->next_gaddr read will get a
     887             :      stale value in this scenario, it will highly likely not be injected
     888             :      into the inactive_stack because the CAS will detect that
     889             :      inactive_stack top has changed and fail.
     890             : 
     891             :      And, lastly, we version the inactive_stack top such that, even if
     892             :      somehow we had a thread stall in pop after reading top->next_gaddr
     893             :      / other threads do other operations that ultimately keep top the
     894             :      same change the value of top->next_gaddr / stalled thread resumes,
     895             :      the version number on the stalled thread will be wrong cause the
     896             :      CAS to fail.  (There is a theoretical risk of version number reuse
     897             :      but the version number is wide enough to make that risk zero on any
     898             :      practical timescale.) */
     899             : 
     900       93462 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     901             : 
     902       93462 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     903             : 
     904       93462 :   fd_alloc_vgaddr_t * _inactive_stack = alloc->inactive_stack + sizeclass;
     905             : 
     906       93462 :   ulong deletion_candidate_gaddr = fd_alloc_private_inactive_stack_pop( _inactive_stack, wksp );
     907       93462 :   if( FD_LIKELY( !deletion_candidate_gaddr ) ) return; /* no deletion candidate, unclear branch prob */
     908             : 
     909        9564 :   fd_alloc_superblock_t * deletion_candidate = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, deletion_candidate_gaddr );
     910             : 
     911        9564 :   if( FD_LIKELY( deletion_candidate->free_blocks!=all_blocks ) ) { /* deletion candidate not empty -> return to circulation */
     912        1920 :     fd_alloc_private_inactive_stack_push( _inactive_stack, wksp, deletion_candidate_gaddr );
     913        1920 :     return;
     914        1920 :   }
     915             : 
     916        7644 :   fd_alloc_hdr_t hdr = deletion_candidate->hdr;
     917        7644 :   if( FD_LIKELY( hdr!=FD_ALLOC_HDR_ROOT_SUPERBLOCK ) ) { /* empty deletion candidate in a parent superblock, free from parent */
     918        6111 :     fd_alloc_private_free( join, (fd_alloc_superblock_t *)((ulong)deletion_candidate - fd_alloc_hdr_off( hdr )),
     919        6111 :                            fd_alloc_hdr_idx( hdr ) );
     920        6111 :     return;
     921        6111 :   }
     922             : 
     923             : # if FD_HAS_DEEPASAN
     924             :   /* At this point, just the header of the root superblock to delete
     925             :      is unpoisoned.  Since fd_wksp_free will poison the entire root
     926             :      superblock anyway, we don't need to do anything here. */
     927             : # endif
     928             : 
     929        1533 :   fd_wksp_free( wksp, deletion_candidate_gaddr ); /* empty deletion candidate wksp allocated, free from wksp */
     930        1533 : }
     931             : 
     932             : void *
     933             : fd_alloc_malloc_at_least( fd_alloc_t * join,
     934             :                           ulong        align,
     935             :                           ulong        sz,
     936      661326 :                           ulong *      _max ) {
     937             : 
     938             :   /* Handle default align, NULL alloc, 0 size, non-power-of-two align,
     939             :      unreasonably large sz and NULL _max.  footprint has room for a
     940             :      fd_alloc_hdr_t, sz bytes and enough padding to allow for the
     941             :      alignment of superblock blocks / wksp allocations is at least
     942             :      alignof(fd_alloc_hdr_t). */
     943             : 
     944      661326 :   if( FD_UNLIKELY( !_max ) ) return NULL;
     945             : 
     946      661326 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     947             : 
     948      661326 :   align = fd_ulong_if( !align, FD_ALLOC_MALLOC_ALIGN_DEFAULT, align );
     949             : 
     950      661326 :   ulong footprint = sz + fd_ulong_max( align, sizeof(fd_alloc_hdr_t) );
     951             : 
     952      661326 :   if( FD_UNLIKELY( (!alloc) | (!fd_ulong_is_pow2( align )) | (!sz) | (footprint<=sz) ) ) {
     953          36 :     *_max = 0UL;
     954          36 :     return NULL;
     955          36 :   }
     956             : 
     957      661290 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     958             : 
     959             :   /* At this point, alloc is non-NULL and backed by wksp, align is a
     960             :      power-of-2, footprint is a reasonable non-zero value.  If this is a
     961             :      large user allocation, allocate it directly from the underlying
     962             :      workspace. */
     963             : 
     964      661290 :   if( FD_UNLIKELY( footprint > FD_ALLOC_FOOTPRINT_SMALL_THRESH ) ) {
     965        5403 :     ulong part_gaddr_lo;
     966        5403 :     ulong part_gaddr_hi;
     967        5403 :     if( FD_UNLIKELY( !fd_wksp_alloc_at_least( wksp, alignof(fd_alloc_hdr_t), footprint, alloc->tag,
     968        5403 :                                               &part_gaddr_lo, &part_gaddr_hi ) ) ) {
     969           0 :       *_max = 0UL;
     970           0 :       return NULL;
     971           0 :     }
     972             : 
     973             :     /* Carve the requested allocation out of the newly allocated
     974             :        partition, prepending the allocation header for use by free.  If
     975             :        we are running under the address sanitizer, note that
     976             :        fd_wksp_alloc_at_least already unpoisoned this partition.  If we
     977             :        are running under the memory sanitizer, we mark the returned
     978             :        region as uninitialized. */
     979             : 
     980        5403 :     ulong part_laddr     = (ulong)fd_wksp_laddr_fast( wksp, part_gaddr_lo );
     981        5403 :     ulong part_footprint = part_gaddr_hi - part_gaddr_lo;
     982             : 
     983        5403 :     ulong alloc_laddr = fd_ulong_align_up( part_laddr + sizeof(fd_alloc_hdr_t), align );
     984        5403 :     ulong asz         = alloc_laddr - part_laddr;
     985             : 
     986             : #   if FD_ALLOC_STYLE==1 /* clear all align padding */
     987             :     if( asz > sizeof(fd_alloc_hdr_t) ) memset( (void *)part_laddr, 0, asz - sizeof(fd_alloc_hdr_t) );
     988             : #   else /* partially clear align padding to improve diagnostics */
     989        5403 :     *(uint *)part_laddr = 0U;
     990        5403 : #   endif
     991             : 
     992        5403 :     *_max = part_footprint - asz;
     993        5403 :     return fd_msan_poison( fd_alloc_hdr_store( (void *)alloc_laddr, FD_ALLOC_HDR_USER_LARGE ), *_max );
     994        5403 :   }
     995             : 
     996             :   /* At this point, this is a small user allocation.  Determine the
     997             :      preferred sizeclass and cgroup_hint and then allocate a suitable
     998             :      block. */
     999             : 
    1000      655887 :   ulong sizeclass   = fd_alloc_preferred_sizeclass( footprint );
    1001      655887 :   ulong cgroup_hint = fd_alloc_join_cgroup_hint( join );
    1002             : 
    1003      655887 :   ulong                   block_idx;
    1004      655887 :   fd_alloc_superblock_t * superblock = fd_alloc_private_alloc( alloc, wksp, sizeclass, cgroup_hint, &block_idx );
    1005      655887 :   if( FD_UNLIKELY( !superblock ) ) {
    1006        4938 :     *_max = 0UL;
    1007        4938 :     return NULL;
    1008        4938 :   }
    1009             : 
    1010             :   /* Carve the requested allocation out of the newly allocated block,
    1011             :      prepending the allocation header for use by free.  If we are
    1012             :      running under the address sanitizer, we unpoison the block (see
    1013             :      note above about asan alignment).  If we are running under the
    1014             :      memory sanitizer, we mark the returned region as uninitialized. */
    1015             : 
    1016      650949 :   ulong block_footprint = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
    1017      650949 :   ulong block_laddr     = (ulong)superblock + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
    1018             : 
    1019             : # if FD_HAS_DEEPASAN
    1020             :   fd_asan_unpoison( (void *)block_laddr, block_footprint );
    1021             : # endif
    1022             : 
    1023      650949 :   ulong alloc_laddr = fd_ulong_align_up( block_laddr + sizeof(fd_alloc_hdr_t), align );
    1024      650949 :   ulong asz         = alloc_laddr - block_laddr;
    1025             : 
    1026             : # if FD_ALLOC_STYLE==1 /* clear all align padding */
    1027             :   if( asz > sizeof(fd_alloc_hdr_t) ) memset( (void *)block_laddr, 0, asz - sizeof(fd_alloc_hdr_t) );
    1028             : # else /* partially clear align padding to improve diagnostics */
    1029      650949 :   *(fd_alloc_hdr_t *)block_laddr = 0U;
    1030      650949 : # endif
    1031             : 
    1032      650949 :   fd_alloc_hdr_t hdr = fd_alloc_hdr( FD_ALLOC_HDR_TYPE_USER_SMALL, block_idx, alloc_laddr - (ulong)superblock );
    1033             : 
    1034      650949 :   *_max = block_footprint - asz;
    1035      650949 :   return fd_msan_poison( fd_alloc_hdr_store( (void *)alloc_laddr, hdr ), *_max );
    1036      655887 : }
    1037             : 
    1038             : void
    1039             : fd_alloc_free( fd_alloc_t * join,
    1040      650919 :                void *       laddr ) {
    1041             : 
    1042             :   /* Handle NULL alloc and/or NULL laddr */
    1043             : 
    1044      650919 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
    1045      650919 :   if( FD_UNLIKELY( (!alloc) | (!laddr) ) ) return;
    1046             : 
    1047             :   /* At this point, we have a valid join and a pointer to the first byte
    1048             :      of an allocation done by it.  Load the allocation header.  If the
    1049             :      header indicates this is a large allocation, free it from the
    1050             :      underlying wksp (note that fd_wksp_free_laddr works for any byte
    1051             :      within the wksp allocation ... thus we don't have to apply a header
    1052             :      offset and thus can reuse the header idx and off for data integrity
    1053             :      checks).  Otherwise (i.e. the header indicates this allocation is a
    1054             :      block in a superblock), free it from the containing superblock. */
    1055             : 
    1056      650904 :   fd_alloc_hdr_t hdr = fd_alloc_hdr_load( laddr );
    1057             : 
    1058      650904 :   if( FD_UNLIKELY( hdr==FD_ALLOC_HDR_USER_LARGE ) ) {
    1059        5361 :     fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1060             : 
    1061             : #   if FD_HAS_DEEPASAN
    1062             :     /* Note that fd_wksp_free will poison the partition on our behalf so
    1063             :        we don't have anything to do here. */
    1064             : #   endif
    1065             : 
    1066        5361 :     fd_wksp_free( wksp, fd_wksp_gaddr_fast( wksp, laddr ) );
    1067        5361 :     return;
    1068        5361 :   }
    1069             : 
    1070      645543 :   fd_alloc_private_free( join, (fd_alloc_superblock_t *)((ulong)laddr - fd_alloc_hdr_off( hdr )), fd_alloc_hdr_idx( hdr ) );
    1071      645543 : }
    1072             : 
    1073             : void
    1074         399 : fd_alloc_compact( fd_alloc_t * join ) {
    1075             : 
    1076         399 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
    1077         399 :   if( FD_UNLIKELY( !alloc ) ) {
    1078           0 :     FD_LOG_WARNING(( "bad join" ));
    1079           0 :     return;
    1080           0 :   }
    1081             : 
    1082         399 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1083             : 
    1084             :   /* We scan each sizeclass (in monotonically increasing order) for
    1085             :      completely empty superblocks that thus can be freed.  This has the
    1086             :      pleasant side effect that, as smaller empty superblocks get freed,
    1087             :      larger superblocks in which they are nested could become completely
    1088             :      empty.  At the end of compaction, if no other operations are
    1089             :      running concurrently, any remaining superblocks should contain at
    1090             :      least one user small allocation somewhere in them. */
    1091             : 
    1092       24738 :   for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
    1093       24339 :     fd_alloc_block_set_t all_blocks      = fd_alloc_block_set_all( (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt );
    1094       24339 :     ulong                cgroup_cnt      = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
    1095       24339 :     fd_alloc_vgaddr_t *  _inactive_stack = alloc->inactive_stack + sizeclass;
    1096             : 
    1097             :     /* For each active superblock in this sizeclass */
    1098             : 
    1099       96957 :     for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1100       72618 :       ulong * _active_slot = fd_alloc_private_active_slot( alloc, sizeclass, cgroup_idx );
    1101             : 
    1102       72618 :       ulong superblock_gaddr = fd_alloc_private_active_slot_replace( _active_slot, 0UL );
    1103       72618 :       if( !superblock_gaddr ) continue; /* application dependent branch prob */
    1104        1551 :       fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1105             : 
    1106             :       /* At this point, we have atomically acquired the cgroup_idx's
    1107             :          active superblock and it has at least one free block.  If this
    1108             :          superblock is empty, we push it onto the inactive stack (for
    1109             :          freeing below).  Otherwise, we put the superblock back into
    1110             :          circulation (we know from the above it still has at least one
    1111             :          free block, preserving the invariant).  This might displace a
    1112             :          superblock that another thread made active behind our back.  We
    1113             :          push any such superblock block onto the inactive stack (it also
    1114             :          will have at least one free block for the same reasons). */
    1115             : 
    1116        1551 :       if( superblock->free_blocks==all_blocks ) { /* application dependent branch prob */
    1117          39 :         fd_alloc_private_inactive_stack_push( _inactive_stack, wksp, superblock_gaddr );
    1118        1512 :       } else {
    1119        1512 :         ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( _active_slot, superblock_gaddr );
    1120        1512 :         if( FD_UNLIKELY( displaced_superblock_gaddr ) )
    1121           0 :           fd_alloc_private_inactive_stack_push( _inactive_stack, wksp, displaced_superblock_gaddr );
    1122        1512 :       }
    1123        1551 :     }
    1124             : 
    1125             :     /* Drain the inactive stack for this sizeclass.  All empty
    1126             :        superblocks found are freed.  All other superblocks will be
    1127             :        pushed onto a local stack (every one will have at least one free
    1128             :        block).  After the inactive stack drain, we drain the local stack
    1129             :        back into the inactive stack to get all these remaining
    1130             :        superblocks back into circulation (also safe for the same
    1131             :        reasons) and with same relative ordering (nice but not required).
    1132             :        We technically don't need to use a lockfree push / pop for the
    1133             :        local stack but no sense in implementing a second version for
    1134             :        this mostly diagnostic / teardown oriented use case. */
    1135             : 
    1136       24339 :     fd_alloc_vgaddr_t _local_stack[1];
    1137             : 
    1138       24339 :     *_local_stack = fd_alloc_vgaddr( 0UL, 0UL );
    1139             : 
    1140       24756 :     for(;;) {
    1141       24756 :       ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( _inactive_stack, wksp );
    1142       24756 :       if( !superblock_gaddr ) break; /* application dependent branch prob */
    1143         417 :       fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1144             : 
    1145         417 :       if( superblock->free_blocks==all_blocks ) { /* if superblock is empty, free it, application dependent branch prob */
    1146             : 
    1147          39 :         fd_alloc_hdr_t hdr = superblock->hdr;
    1148          39 :         if( FD_LIKELY( hdr!=FD_ALLOC_HDR_ROOT_SUPERBLOCK ) ) { /* empty superblock in a parent superblock, free from parent */
    1149          33 :           fd_alloc_private_free( join, (fd_alloc_superblock_t *)((ulong)superblock - fd_alloc_hdr_off( hdr )),
    1150          33 :                                  fd_alloc_hdr_idx( hdr ) );
    1151          33 :         } else {
    1152           6 :           fd_wksp_free( wksp, superblock_gaddr ); /* empty superblock wksp allocated, free from wksp */
    1153           6 :         }
    1154             : 
    1155         378 :       } else {
    1156             : 
    1157         378 :         fd_alloc_private_inactive_stack_push( _local_stack, wksp, superblock_gaddr );
    1158             : 
    1159         378 :       }
    1160         417 :     }
    1161             : 
    1162       24717 :     for(;;) {
    1163       24717 :       ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( _local_stack, wksp );
    1164       24717 :       if( !superblock_gaddr ) break; /* application dependent branch prob */
    1165         378 :       fd_alloc_private_inactive_stack_push( _inactive_stack, wksp, superblock_gaddr );
    1166         378 :     }
    1167       24339 :   }
    1168         399 : }
    1169             : 
    1170             : int
    1171         204 : fd_alloc_is_empty( fd_alloc_t * join ) {
    1172         204 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
    1173         204 :   if( FD_UNLIKELY( !alloc ) ) return 0;
    1174             : 
    1175             :   /* Compact out any preallocated memory from the wksp */
    1176             : 
    1177         204 :   fd_alloc_compact( join );
    1178             : 
    1179             :   /* At this point, if there are no user allocations, there should be no
    1180             :      wksp partitions tagged with the allocator's tag (except, maybe, the
    1181             :      partition that holds the fd_alloc state, if the creator used the
    1182             :      same tag there).  So we compute the number of partitions used by
    1183             :      the alloc's tag and deduct if necessary the partition used to hold
    1184             :      the fd_alloc's metadata. */
    1185             : 
    1186         204 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1187             : 
    1188         204 :   fd_wksp_usage_t usage[1];
    1189         204 :   fd_wksp_usage( wksp, &alloc->tag, 1UL, usage );
    1190             : 
    1191         204 :   usage->used_cnt -= (ulong)(fd_wksp_tag( wksp, fd_wksp_gaddr_fast( wksp, alloc ) )==alloc->tag);
    1192             : 
    1193         204 :   return !usage->used_cnt;
    1194         204 : }
    1195             : 
    1196             : /* fd_alloc_fprintf pretty prints comprehensive details about the state
    1197             :    of the allocator to stream.  Returns the number of characters printed
    1198             :    to stream (saturated to INT_MAX).
    1199             : 
    1200             :    IMPORTANT SAFETY TIP!  If FD_ALLOC_STYLE==0 or if called while
    1201             :    concurrent operations are in progress, this can spuriously report
    1202             :    errors (due to alignment padding containing stale fd_alloc_hdr_t or
    1203             :    from concurrent operations changing allocations while they are being
    1204             :    analyzed by the below).
    1205             : 
    1206             :    IMPORTANT SAFETY TIP!  fd_alloc_fprintf can generate ASAN errors if
    1207             :    run concurrently under ASAN because concurrent operations might
    1208             :    poison regions of the wksp as they are being analyzed by the below.
    1209             :    Hence the below functions are marked as FD_FN_NO_ASAN.  Similar
    1210             :    considerations for FD_FN_NO_MSAN. */
    1211             : 
    1212             : #include <stdio.h>
    1213             : #include "../wksp/fd_wksp_private.h"
    1214             : 
    1215             : FD_FN_NO_ASAN FD_FN_NO_MSAN static fd_alloc_hdr_t
    1216           0 : fd_alloc_hdr_load_no_san( void const * laddr ) {
    1217           0 :   return *(fd_alloc_hdr_t const *)((ulong)laddr - sizeof(fd_alloc_hdr_t));
    1218           0 : }
    1219             : 
    1220             : FD_FN_NO_ASAN FD_FN_NO_MSAN static ulong
    1221             : fd_alloc_private_superblock_fprintf( FILE *            stream,           /* Stream to fprintf */
    1222             :                                      fd_wksp_t const * wksp,
    1223             :                                      ulong             superblock_gaddr, /* Superblock to fprintf */
    1224             :                                      ulong             parent_gaddr_lo,  /* wksp region that contains this superblock */
    1225             :                                      ulong             parent_gaddr_hi,
    1226             :                                      int               recurse,          /* Should this recurse into nested superblocks */
    1227             :                                      ulong             indent_cnt,       /* How much to indent */
    1228           0 :                                      ulong *           ctr ) {
    1229             : 
    1230           0 :   ulong cnt = 0UL;
    1231             : 
    1232           0 : # define EMIT( f ) do {                                              \
    1233           0 :     for( ulong _rem=indent_cnt; _rem; _rem-- ) fputc( ' ', stream ); \
    1234           0 :     cnt += (ulong)fd_int_max( (f), 0 ) + indent_cnt;                 \
    1235           0 :   } while(0)
    1236             : 
    1237           0 : # define SB_TEST( c ) \
    1238           0 :   do { if( FD_UNLIKELY( !(c) ) ) { EMIT( fprintf( stream, "unexpected: %s failed\n", #c ) ); ctr[0]++; return cnt; } } while(0)
    1239             : 
    1240             :   /* validate the superblock header */
    1241             : 
    1242           0 :   fd_alloc_superblock_t const * parent;
    1243           0 :   if( !parent_gaddr_lo ) {
    1244           0 :     parent          = NULL;
    1245           0 :     parent_gaddr_lo = superblock_gaddr;
    1246           0 :   } else {
    1247           0 :     parent = (fd_alloc_superblock_t const *)fd_wksp_laddr_fast( wksp, parent_gaddr_lo );
    1248           0 :   }
    1249             : 
    1250           0 :   SB_TEST( parent_gaddr_lo<=superblock_gaddr                                  ); /* safe to read superblock header */
    1251           0 :   SB_TEST( (superblock_gaddr+sizeof(fd_alloc_superblock_t))<=parent_gaddr_hi  ); /* " */
    1252           0 :   SB_TEST( fd_ulong_is_aligned( superblock_gaddr, FD_ALLOC_SUPERBLOCK_ALIGN ) ); /* " */
    1253             : 
    1254           0 :   fd_alloc_superblock_t const * superblock = (fd_alloc_superblock_t const *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1255             : 
    1256           0 :   ulong                sizeclass   = (ulong)superblock->sizeclass;
    1257           0 :   ulong                block_cnt   = (ulong)superblock->block_cnt;
    1258           0 :   ulong                cgroup_mask = (ulong)superblock->cgroup_mask;
    1259           0 :   fd_alloc_block_set_t free_blocks =        superblock->free_blocks;
    1260           0 :   ulong                next_gaddr  =        superblock->next_gaddr;
    1261             : 
    1262           0 :   SB_TEST( sizeclass  < FD_ALLOC_SIZECLASS_CNT                                 ); /* valid sizeclass */
    1263           0 :   SB_TEST( block_cnt  ==(ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt   ); /* block_cnt   matches sizeclass */
    1264           0 :   SB_TEST( cgroup_mask==(ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask ); /* cgroup_mask matches sizeclass */
    1265           0 :   SB_TEST( free_blocks==(free_blocks & fd_alloc_block_set_all( block_cnt ))    ); /* no spurious bits set */
    1266           0 :   SB_TEST( fd_ulong_is_aligned( next_gaddr, FD_ALLOC_SUPERBLOCK_ALIGN )        ); /* aligned gaddr or NULL gaddr */
    1267             : 
    1268           0 :   ulong block_footprint      = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
    1269           0 :   ulong superblock_footprint = sizeof(fd_alloc_superblock_t) + block_cnt*block_footprint;
    1270             : 
    1271           0 :   SB_TEST( (superblock_gaddr + superblock_footprint)<=parent_gaddr_hi ); /* safe to read superblock body */
    1272             : 
    1273             :   /* validate the superblock nesting */
    1274             : 
    1275           0 :   ulong parent_sizeclass;
    1276             : 
    1277           0 :   if( !parent ) {
    1278             : 
    1279           0 :     parent_sizeclass = FD_ALLOC_SIZECLASS_CNT;
    1280             : 
    1281           0 :     SB_TEST( superblock->hdr==FD_ALLOC_HDR_ROOT_SUPERBLOCK ); /* root superblock */
    1282             : 
    1283           0 :   } else {
    1284             : 
    1285           0 :     parent_sizeclass = (ulong)parent->sizeclass;
    1286             : 
    1287           0 :     ulong parent_block_footprint = (ulong)fd_alloc_sizeclass_cfg[ parent_sizeclass ].block_footprint;
    1288             : 
    1289           0 :     fd_alloc_hdr_t hdr = superblock->hdr;
    1290           0 :     int   type = fd_alloc_hdr_type( hdr );
    1291           0 :     ulong idx  = fd_alloc_hdr_idx ( hdr );
    1292           0 :     ulong off  = fd_alloc_hdr_off ( hdr );
    1293             : 
    1294           0 :     SB_TEST( type==FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK                           ); /* nested superblock */
    1295           0 :     SB_TEST( idx < (ulong)fd_alloc_sizeclass_cfg[ parent_sizeclass ].block_cnt ); /* valid index */
    1296           0 :     SB_TEST( off == sizeof(fd_alloc_superblock_t) + idx*parent_block_footprint ); /* valid offset */
    1297           0 :     SB_TEST( off == ((ulong)superblock - (ulong)parent)                        ); /* " */
    1298             : 
    1299           0 :   }
    1300             : 
    1301           0 :   SB_TEST( parent_sizeclass==(ulong)fd_alloc_sizeclass_cfg[ sizeclass ].parent_sizeclass ); /* valid parent sizeclass */
    1302             : 
    1303           0 :   EMIT( fprintf( stream, "%s superblock at [%013lx,%013lx)\n",
    1304           0 :                  parent ? "nested" : "root", superblock_gaddr, superblock_gaddr + superblock_footprint ) );
    1305             : 
    1306           0 :   indent_cnt += 2UL;
    1307             : 
    1308           0 :   EMIT( fprintf( stream, "sizeclass   %3lu (cgroup_cnt %2lu block_cnt %2lu block_footprint %lu)\n",
    1309           0 :                  sizeclass, cgroup_mask+1UL, block_cnt, block_footprint ) );
    1310             : 
    1311           0 :   ulong free_cnt = (ulong)fd_ulong_popcnt( free_blocks );
    1312             : 
    1313           0 :   EMIT( fprintf( stream, "free_blocks %013lx next_gaddr %013lx (free_cnt %2lu used_cnt %2lu)\n",
    1314           0 :                  free_blocks, next_gaddr, free_cnt, block_cnt - free_cnt ) );
    1315             : 
    1316             :   /* iterate over all used blocks in the superblock */
    1317             : 
    1318           0 :   for( ulong block_idx=0UL; block_idx<block_cnt; block_idx++ ) {
    1319           0 :     if( fd_alloc_block_set_test( free_blocks, block_idx ) ) continue;
    1320             : 
    1321             :     /* At this point, the block block_idx is in use and should either
    1322             :        contain a child superblock or a USER_SMALL allocation.  Test if
    1323             :        the block is a child superblock (note that will recursively test
    1324             :        the contents of the child if so).  If not, search the block for a
    1325             :        valid fd_alloc_hdr_t (there should be at least one ...  if more
    1326             :        than one, the later ones are probably user data that accidentally
    1327             :        matched a header). */
    1328             : 
    1329           0 :     ulong block_gaddr_lo = superblock_gaddr + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
    1330           0 :     ulong block_gaddr_hi = block_gaddr_lo + block_footprint;
    1331             : 
    1332           0 :     fd_alloc_hdr_t hdr = fd_alloc_hdr_load_no_san( fd_wksp_laddr_fast( wksp, block_gaddr_lo + sizeof(fd_alloc_hdr_t) ) );
    1333             : 
    1334           0 :     if( hdr==fd_alloc_hdr( FD_ALLOC_HDR_TYPE_NEST_SUPERBLOCK, block_idx, block_gaddr_lo - superblock_gaddr ) ) {
    1335             : 
    1336           0 :       EMIT( fprintf( stream, "block %2lu [%013lx,%013lx): nested superblock\n", block_idx, block_gaddr_lo, block_gaddr_hi ) );
    1337             : 
    1338           0 :       if( recurse ) cnt += fd_alloc_private_superblock_fprintf( stream, wksp, block_gaddr_lo,
    1339           0 :                                                                 superblock_gaddr, superblock_gaddr + superblock_footprint,
    1340           0 :                                                                 recurse, indent_cnt+2UL, ctr );
    1341             : 
    1342           0 :     } else {
    1343             : 
    1344           0 :       ulong align      = 1UL;
    1345           0 :       ulong align_stop = block_footprint - sizeof(fd_alloc_hdr_t); /* Note: block_footprint > sizeof(fd_alloc_hdr_t) */
    1346             : 
    1347           0 :       for(;;) {
    1348             : 
    1349           0 :         if( FD_UNLIKELY( align>=align_stop ) ) {
    1350           0 :           EMIT( fprintf( stream, "block %2lu [%013lx,%013lx): alloc header not found\n",
    1351           0 :                          block_idx, block_gaddr_lo, block_gaddr_hi ) );
    1352           0 :           ctr[0]++;
    1353           0 :           break;
    1354           0 :         }
    1355             : 
    1356           0 :         ulong alloc_gaddr = fd_ulong_align_up( block_gaddr_lo + sizeof(fd_alloc_hdr_t), align );
    1357             : 
    1358           0 :         fd_alloc_hdr_t hdr = fd_alloc_hdr_load_no_san( fd_wksp_laddr_fast( wksp, alloc_gaddr ) );
    1359             : 
    1360           0 :         if( FD_LIKELY( hdr==fd_alloc_hdr( FD_ALLOC_HDR_TYPE_USER_SMALL, block_idx, alloc_gaddr - superblock_gaddr ) ) ) {
    1361           0 :           ulong alloc_max = block_gaddr_hi - alloc_gaddr;
    1362           0 :           EMIT( fprintf( stream, "block %2lu [%013lx,%013lx): small user allocation (gaddr %013lx max %lu)\n",
    1363           0 :                          block_idx, block_gaddr_lo, block_gaddr_hi, alloc_gaddr, alloc_max ));
    1364           0 :           ctr[1]++;
    1365           0 :           break;
    1366           0 :         }
    1367             : 
    1368           0 :         align <<= 1;
    1369           0 :       }
    1370             : 
    1371           0 :     }
    1372           0 :   }
    1373             : 
    1374           0 : # undef SB_TEST
    1375           0 : # undef EMIT
    1376             : 
    1377           0 :   return cnt;
    1378           0 : }
    1379             : 
    1380             : FD_FN_NO_ASAN FD_FN_NO_MSAN int
    1381             : fd_alloc_fprintf( fd_alloc_t * join,
    1382           3 :                   FILE *       stream ) {
    1383           3 :   if( FD_UNLIKELY( !stream ) ) return 0; /* NULL stream, can't print anything */
    1384             : 
    1385           3 :   ulong cnt = 0UL;
    1386             : 
    1387          12 : # define EMIT(x) do { cnt += (ulong)fd_int_max( (x), 0 ); } while(0)
    1388             : 
    1389           3 :   ulong ctr[6];
    1390           3 :   ctr[0] = 0UL; /* errors detected */
    1391           3 :   ctr[1] = 0UL; /* small alloc found */
    1392           3 :   ctr[2] = 0UL; /* wksp partitions used */
    1393           3 :   ctr[3] = 0UL; /* wksp bytes used */
    1394           3 :   ctr[4] = 0UL; /* wksp partitions used for large alloc */
    1395           3 :   ctr[5] = 0UL; /* wksp bytes used for large alloc */
    1396             : 
    1397           3 :   fd_alloc_t * alloc       = fd_alloc_private_join_alloc( join );
    1398           3 :   ulong        cgroup_hint = fd_alloc_join_cgroup_hint  ( join );
    1399             : 
    1400           3 :   if( FD_UNLIKELY( !alloc ) ) { /* NULL join passed */
    1401             : 
    1402           0 :     EMIT( fprintf( stream, "alloc: gaddr -, join_cgroup_hint %lu, magic 0x0 (bad)\n", cgroup_hint ) );
    1403           0 :     ctr[0]++;
    1404             : 
    1405           3 :   } else { /* Normal join */
    1406             : 
    1407           3 :     fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1408             : 
    1409           3 :     ulong wksp_gaddr_lo  = wksp->gaddr_lo;
    1410           3 :     ulong wksp_gaddr_hi  = wksp->gaddr_hi;
    1411             : 
    1412             :     /* Print the summary header */
    1413             : 
    1414           3 :     EMIT( fprintf( stream, "alloc: wksp %s gaddr %013lx, join_cgroup_hint %lu, magic 0x%lx (%s)\n",
    1415           3 :                    wksp->name, fd_wksp_gaddr_fast( wksp, alloc ), cgroup_hint,
    1416           3 :                    alloc->magic, alloc->magic==FD_ALLOC_MAGIC ? "good" : "bad" ) );
    1417           3 :     if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) ctr[0]++;
    1418             : 
    1419             :     /* Print known details about each sizeclass */
    1420             : 
    1421           3 :     ulong block_footprint = 0UL;
    1422         186 :     for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
    1423         183 :       ulong block_footprint_prev = block_footprint;
    1424         183 :       /**/  block_footprint  = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
    1425             : 
    1426         183 :       ulong parent_sizeclass = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].parent_sizeclass;
    1427         183 :       ulong block_cnt        = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt;
    1428         183 :       ulong cgroup_cnt       = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
    1429             : 
    1430         183 :       fd_alloc_vgaddr_t inactive_stack = alloc->inactive_stack[ sizeclass ];
    1431             : 
    1432         183 :       ulong inactive_stack_ver   = fd_alloc_vgaddr_ver( inactive_stack );
    1433         183 :       ulong inactive_stack_gaddr = fd_alloc_vgaddr_off( inactive_stack ) << FD_ALLOC_SUPERBLOCK_LG_ALIGN;
    1434             : 
    1435             :       /* Omit sizeclasses that have no superblocks in circulation */
    1436             : 
    1437         183 :       int do_print = !!inactive_stack_gaddr;
    1438         183 :       if( !do_print ) {
    1439         729 :         for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1440         546 :           if( *fd_alloc_private_active_slot( alloc, sizeclass, cgroup_idx ) ) {
    1441           0 :             do_print = 1;
    1442           0 :             break;
    1443           0 :           }
    1444         546 :         }
    1445         183 :         if( !do_print ) continue;
    1446         183 :       }
    1447             : 
    1448             :       /* Print size class header */
    1449             : 
    1450           0 :       EMIT( fprintf( stream,
    1451           0 :                      "  sizeclass %3lu: parent_sizeclass %3lu, cgroup_cnt %2lu, block_cnt %2lu, footprint (%lu,%lu]\n",
    1452           0 :                      sizeclass, parent_sizeclass, cgroup_cnt, block_cnt, block_footprint_prev, block_footprint ) );
    1453             : 
    1454             :       /* Print inactive stack top */
    1455             : 
    1456           0 :       EMIT( fprintf( stream, "    inactive_stack: gaddr %013lx, version %lu\n", inactive_stack_gaddr, inactive_stack_ver ) );
    1457             : 
    1458             :       /* Print active superblocks */
    1459             : 
    1460           0 :       ulong superblock_gaddr;
    1461             : 
    1462           0 :       for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1463           0 :         superblock_gaddr = *fd_alloc_private_active_slot( alloc, sizeclass, cgroup_idx );
    1464           0 :         if( !superblock_gaddr ) continue;
    1465           0 :         ulong next_gaddr = ((fd_alloc_superblock_t const *)fd_wksp_laddr_fast( wksp, superblock_gaddr))->next_gaddr;
    1466           0 :         EMIT( fprintf( stream, "    superblock %013lx: next %013lx (ignored), active (cgroup_idx %2lu)\n",
    1467           0 :                        superblock_gaddr, next_gaddr, cgroup_idx ) );
    1468           0 :       }
    1469             : 
    1470             :       /* Print leading inactive superblocks (best effort) */
    1471             : 
    1472           0 :       superblock_gaddr = inactive_stack_gaddr;
    1473           0 :       for( ulong rem=1024UL; rem; rem-- ) {
    1474           0 :         if( !( (wksp_gaddr_lo<=superblock_gaddr) & (superblock_gaddr<wksp_gaddr_hi) &
    1475           0 :                fd_ulong_is_aligned( superblock_gaddr, FD_ALLOC_SUPERBLOCK_ALIGN ) ) ) {
    1476           0 :           if( FD_UNLIKELY( superblock_gaddr ) ) {
    1477           0 :             EMIT( fprintf( stream, "    inactive stack likely modified by a concurrent operation\n" ) );
    1478           0 :             ctr[0]++;
    1479           0 :           }
    1480           0 :           break;
    1481           0 :         }
    1482           0 :         ulong next_gaddr = ((fd_alloc_superblock_t const *)fd_wksp_laddr_fast( wksp, superblock_gaddr))->next_gaddr;
    1483           0 :         EMIT( fprintf( stream, "    superblock %013lx: next %013lx, inactive\n", superblock_gaddr, next_gaddr ) );
    1484           0 :         superblock_gaddr = next_gaddr;
    1485           0 :       }
    1486           0 :     }
    1487             : 
    1488             :     /* Scan the wksp partition table for partitions that match this
    1489             :        allocation tag.  We do this in a brute force way that is not algo
    1490             :        efficient to avoid taking a lock. */
    1491             : 
    1492           3 :     ulong alloc_gaddr_lo = fd_wksp_gaddr_fast( wksp, alloc );
    1493           3 :     ulong alloc_gaddr_hi = alloc_gaddr_lo + FD_ALLOC_FOOTPRINT;
    1494           3 :     ulong alloc_tag      = alloc->tag;
    1495             : 
    1496           3 :     ulong                     part_max = wksp->part_max;
    1497           3 :     fd_wksp_private_pinfo_t * pinfo    = fd_wksp_private_pinfo( wksp );
    1498             : 
    1499       49107 :     for( ulong part_idx=0UL; part_idx<part_max; part_idx++ ) {
    1500       49104 :       if( pinfo[ part_idx ].tag!=alloc_tag ) continue; /* skip ones that don't match */
    1501             : 
    1502           0 :       ulong part_gaddr_lo = pinfo[ part_idx ].gaddr_lo;
    1503           0 :       ulong part_gaddr_hi = pinfo[ part_idx ].gaddr_hi;
    1504           0 :       if( FD_UNLIKELY( !((wksp_gaddr_lo<=part_gaddr_lo) & (part_gaddr_lo<part_gaddr_hi) & (part_gaddr_hi<=wksp_gaddr_hi)) ) ) {
    1505           0 :         EMIT( fprintf( stream, "  partition [%013lx,%013lx): invalid\n", part_gaddr_lo, part_gaddr_hi ) );
    1506           0 :         ctr[0]++;
    1507           0 :         continue;
    1508           0 :       }
    1509             : 
    1510           0 :       ulong part_footprint = part_gaddr_hi - part_gaddr_lo;
    1511             : 
    1512           0 :       if( FD_UNLIKELY( (part_gaddr_lo<=alloc_gaddr_lo) & (alloc_gaddr_hi<=part_gaddr_hi) ) ) { /* Partition metadata */
    1513           0 :         EMIT( fprintf( stream, "  partition [%013lx,%013lx): metadata\n", part_gaddr_lo, part_gaddr_hi ) );
    1514           0 :         ctr[2] += 1UL;
    1515           0 :         ctr[3] += part_footprint;
    1516           0 :         continue;
    1517           0 :       }
    1518             : 
    1519           0 :       if( FD_UNLIKELY( !fd_ulong_is_aligned( part_gaddr_lo, alignof(fd_alloc_hdr_t) ) ) ) {
    1520           0 :         EMIT( fprintf( stream, "  partition [%013lx,%013lx): misaligned\n", part_gaddr_lo, part_gaddr_hi ) );
    1521           0 :         ctr[0]++;
    1522           0 :         continue;
    1523           0 :       }
    1524             : 
    1525           0 :       if( FD_UNLIKELY( part_footprint <= FD_ALLOC_FOOTPRINT_SMALL_THRESH ) ) {
    1526           0 :         EMIT( fprintf( stream, "  partition [%013lx,%013lx): runt fd_alloc partition\n", part_gaddr_lo, part_gaddr_hi ) );
    1527           0 :         ctr[0]++;
    1528           0 :         continue;
    1529           0 :       }
    1530             : 
    1531           0 :       fd_alloc_hdr_t hdr = fd_alloc_hdr_load_no_san( fd_wksp_laddr_fast( wksp, part_gaddr_lo + sizeof(fd_alloc_hdr_t) ) );
    1532             : 
    1533           0 :       if( FD_LIKELY( (hdr           ==FD_ALLOC_HDR_ROOT_SUPERBLOCK                   ) &
    1534           0 :                      (part_footprint==FD_ALLOC_ROOT_SUPERBLOCK_FOOTPRINT             ) &
    1535           0 :                      (fd_ulong_is_aligned( part_gaddr_lo, FD_ALLOC_SUPERBLOCK_ALIGN )) ) ) {
    1536             : 
    1537           0 :         EMIT( fprintf( stream, "  partition [%013lx,%013lx): root superblock\n", part_gaddr_lo, part_gaddr_hi ) );
    1538           0 :         ctr[2]++;
    1539           0 :         ctr[3] += part_footprint;
    1540             : 
    1541           0 :         cnt += fd_alloc_private_superblock_fprintf( stream, wksp, part_gaddr_lo, 0UL, part_gaddr_hi,
    1542           0 :                                                     1 /*recurse*/, 4UL /*indent*/, ctr );
    1543             : 
    1544           0 :       } else {
    1545             : 
    1546             :         /* Search the partition for a USER_LARGE fd_alloc_hdr_t.  There
    1547             :            will be at least one if the partition contains a USER_LARGE
    1548             :            allocation (if more than one ... the later ones are probably
    1549             :            user data that looked like a valid hdr).  It is theoretically
    1550             :            possible for junk in an overaligned alloc to accidentally
    1551             :            look like a header (in which case, this logic will compute
    1552             :            the wrong location / max for the user large allocation.  This
    1553             :            can be avoided by clearing the alignment padding in
    1554             :            fd_alloc_malloc_at_least.  Once we have a plausible location
    1555             :            for the user's large alloc, we can compute bounds to how
    1556             :            large a size was used.  We use the upper bound the size
    1557             :            estimate for simplicity (it would take a lot more space and
    1558             :            time overhead in normal operation to track the exact size and
    1559             :            alignment requested explicitly). */
    1560             : 
    1561           0 :         ulong align      = 1UL;
    1562           0 :         ulong align_stop = part_footprint - sizeof(fd_alloc_hdr_t);
    1563             : 
    1564           0 :         for(;;) {
    1565             : 
    1566           0 :           if( FD_UNLIKELY( align>=align_stop ) ) {
    1567           0 :             EMIT( fprintf( stream, "  partition [%013lx,%013lx): alloc header not found\n", part_gaddr_lo, part_gaddr_hi ) );
    1568           0 :             ctr[0]++;
    1569           0 :             break;
    1570           0 :           }
    1571             : 
    1572           0 :           ulong alloc_gaddr = fd_ulong_align_up( part_gaddr_lo + sizeof(fd_alloc_hdr_t), align );
    1573             : 
    1574           0 :           fd_alloc_hdr_t hdr = fd_alloc_hdr_load_no_san( fd_wksp_laddr_fast( wksp, alloc_gaddr ) );
    1575             : 
    1576           0 :           if( FD_LIKELY( hdr==FD_ALLOC_HDR_USER_LARGE ) ) {
    1577           0 :             ulong alloc_max = part_gaddr_hi - alloc_gaddr;
    1578           0 :             EMIT( fprintf( stream, "  partition [%013lx,%013lx): large user allocation (gaddr %013lx max %lu)\n",
    1579           0 :                            part_gaddr_lo, part_gaddr_hi, alloc_gaddr, alloc_max ));
    1580           0 :             ctr[2]++;
    1581           0 :             ctr[3] += part_footprint;
    1582           0 :             ctr[4] += 1UL;
    1583           0 :             ctr[5] += part_footprint;
    1584           0 :             break;
    1585           0 :           }
    1586             : 
    1587           0 :           align <<= 1;
    1588           0 :         }
    1589             : 
    1590           0 :       }
    1591           0 :     }
    1592           3 :   }
    1593             : 
    1594             :   /* Print summary statistics */
    1595             : 
    1596           3 :   EMIT( fprintf( stream,
    1597           3 :                  "  summary\n"
    1598           3 :                  "    errors detected       %21lu%s\n"
    1599           3 :                  "    small alloc cnt       %21lu\n"
    1600           3 :                  "    wksp part used        %21lu\n"
    1601           3 :                  "    wksp byte used        %21lu\n"
    1602           3 :                  "    large alloc part used %21lu\n"
    1603           3 :                  "    large alloc byte used %21lu\n",
    1604           3 :                  ctr[0], ctr[0] ? " (highly likely spurious if running concurrent and/or with dirty align padding)" : "",
    1605           3 :                  ctr[1], ctr[2], ctr[3], ctr[4], ctr[5] ) );
    1606             : 
    1607           3 : # undef EMIT
    1608             : 
    1609             :   return (int)fd_ulong_min( cnt, (ulong)INT_MAX );
    1610           3 : }

Generated by: LCOV version 1.14