LCOV - code coverage report
Current view: top level - util/alloc - fd_alloc.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 492 561 87.7 %
Date: 2025-09-16 04:29:32 Functions: 30 35 85.7 %

          Line data    Source code
       1             : #include "fd_alloc.h"
       2             : #include "fd_alloc_cfg.h"
       3             : #include "../sanitize/fd_asan.h"
       4             : #include "../sanitize/fd_msan.h"
       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             : /* sizeclass APIs *****************************************************/
      12             : 
      13             : /* fd_alloc_preferred_sizeclass returns the tightest fitting sizeclass
      14             :    for the given footprint.  The caller promises there is at least one
      15             :    possible size class (i.e. that footprint is in
      16             :    [0,FD_ALLOC_FOOTPRINT_SMALL_THRESH].  The return will be in
      17             :    [0,FD_ALLOC_SIZECLASS_CNT). */
      18             : 
      19             : FD_STATIC_ASSERT( 64UL<FD_ALLOC_SIZECLASS_CNT && FD_ALLOC_SIZECLASS_CNT<=128UL, limits );
      20             : 
      21             : static inline ulong
      22     3898758 : fd_alloc_preferred_sizeclass( ulong footprint ) {
      23     3898758 :   ulong l = 0UL;
      24     3898758 :   ulong h = FD_ALLOC_SIZECLASS_CNT-1UL;
      25             : 
      26             :   /* Fixed count loop without early exit to make it easy for compiler to
      27             :      unroll and nominally eliminate all branches for fast, highly
      28             :      deterministic performance with no consumption of BTB resources.
      29             :      Note that 7 assumes 64<=FD_ALLOC_SIZECLASS_CNT-1<128.  FIXME: check
      30             :      the compiler is doing the right thing here with unrolling and
      31             :      branch elimination. */
      32             : 
      33    31190064 :   for( ulong r=0UL; r<7UL; r++ ) {
      34             : 
      35             :     /* At this point sizeclasses<l are known to be inadequate and
      36             :        sizeclasses>=h are known to be suitable.  Sizeclasses in [l,h)
      37             :        have not been tested. */
      38             : 
      39    27291306 :     ulong m = (l+h)>>1; /* Note: no overflow for reasonable sizeclass_cnt and l<=m<=h */
      40    27291306 :     int   c = (((ulong)fd_alloc_sizeclass_cfg[ m ].block_footprint)>=footprint);
      41    27291306 :     l = fd_ulong_if( c, l, m+1UL ); /* cmov */
      42    27291306 :     h = fd_ulong_if( c, m, h     ); /* cmov */
      43    27291306 :   }
      44             : 
      45     3898758 :   return h;
      46     3898758 : }
      47             : 
      48             : /* fd_alloc_preferred_sizeclass_cgroup returns preferred sizeclass
      49             :    concurrency group for a join with the given cgroup_idx.  The caller
      50             :    promises sizeclass is in [0,FD_ALLOC_SIZECLASS_CNT). */
      51             : 
      52             : static inline ulong
      53             : fd_alloc_preferred_sizeclass_cgroup( ulong sizeclass,
      54     4128591 :                                      ulong cgroup_idx ) {
      55     4128591 :   return cgroup_idx & (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask;
      56     4128591 : }
      57             : 
      58             : /* fd_alloc_block_set *************************************************/
      59             : 
      60             : /* A fd_alloc_block_set specifies a set of blocks in a superblock. */
      61             : 
      62             : #define SET_NAME fd_alloc_block_set
      63             : #define SET_TYPE ulong
      64             : #define SET_MAX  64
      65             : #include "../tmpl/fd_smallset.c"
      66             : 
      67             : /* fd_alloc_block_set_{add,sub} inserts / removes blocks to / from
      68             :    block set pointed to by set.  The caller promises that blocks are not
      69             :    / are already in the block set.  This operation is a compiler fence.
      70             :    Further, if FD_HAS_ATOMIC, this operation is done atomically.
      71             :    Returns the value of the block_set just before the operation.  Note:
      72             :    atomic add/sub has slightly better asm on x86 than atomic or/and/nand
      73             :    (compiler quality issue, not an architecture issue) and generates the
      74             :    same results provided the caller promises are met. */
      75             : 
      76             : #if FD_HAS_ATOMIC
      77             : 
      78             : static inline fd_alloc_block_set_t
      79             : fd_alloc_block_set_add( fd_alloc_block_set_t * set,
      80     3898182 :                         fd_alloc_block_set_t   blocks ) {
      81     3898182 :   fd_alloc_block_set_t ret;
      82     3898182 :   FD_COMPILER_MFENCE();
      83     3898182 :   ret = FD_ATOMIC_FETCH_AND_ADD( set, blocks );
      84     3898182 :   FD_COMPILER_MFENCE();
      85     3898182 :   return ret;
      86     3898182 : }
      87             : 
      88             : static inline fd_alloc_block_set_t
      89             : fd_alloc_block_set_sub( fd_alloc_block_set_t * set,
      90     3898758 :                         fd_alloc_block_set_t   blocks ) {
      91     3898758 :   fd_alloc_block_set_t ret;
      92     3898758 :   FD_COMPILER_MFENCE();
      93     3898758 :   ret = FD_ATOMIC_FETCH_AND_SUB( set, blocks );
      94     3898758 :   FD_COMPILER_MFENCE();
      95     3898758 :   return ret;
      96     3898758 : }
      97             : 
      98             : #else
      99             : 
     100             : static inline fd_alloc_block_set_t
     101             : fd_alloc_block_set_add( fd_alloc_block_set_t * set,
     102             :                         fd_alloc_block_set_t   blocks ) {
     103             :   fd_alloc_block_set_t ret;
     104             :   FD_COMPILER_MFENCE();
     105             :   ret = FD_VOLATILE_CONST( *set );
     106             :   FD_VOLATILE( *set ) = ret + blocks;
     107             :   FD_COMPILER_MFENCE();
     108             :   return ret;
     109             : }
     110             : 
     111             : static inline fd_alloc_block_set_t
     112             : fd_alloc_block_set_sub( fd_alloc_block_set_t * set,
     113             :                         fd_alloc_block_set_t   blocks ) {
     114             :   fd_alloc_block_set_t ret;
     115             :   FD_COMPILER_MFENCE();
     116             :   ret = FD_VOLATILE_CONST( *set );
     117             :   FD_VOLATILE( *set ) = ret - blocks;
     118             :   FD_COMPILER_MFENCE();
     119             :   return ret;
     120             : }
     121             : 
     122             : #endif
     123             : 
     124             : /* fd_alloc_superblock ************************************************/
     125             : 
     126       24573 : #define FD_ALLOC_SUPERBLOCK_ALIGN (16UL) /* Guarantees free_blocks and next_gaddr aligned are on the same cache line */
     127             : 
     128             : struct __attribute__((aligned(FD_ALLOC_SUPERBLOCK_ALIGN))) fd_alloc_superblock {
     129             :   fd_alloc_block_set_t free_blocks; /* which blocks in this superblock are allocated */
     130             :   ulong                next_gaddr;  /* if on the inactive superblock stack, next inactive superblock or NULL, ignored o.w. */
     131             :   /* FIXME: could put a cgroup hint in the lsb of next gaddr given
     132             :      it is already aligned 16. */
     133             :   /* Storage for blocks follows */
     134             : };
     135             : 
     136             : typedef struct fd_alloc_superblock fd_alloc_superblock_t;
     137             : 
     138             : /* fd_alloc ***********************************************************/
     139             : 
     140             : /* FD_ALLOC_MAGIC is an ideally unique number that specifies the precise
     141             :    memory layout of a fd_alloc */
     142             : 
     143             : #if FD_HAS_X86 /* Technically platforms with 16B compare-exchange */
     144             : 
     145          42 : #define FD_ALLOC_MAGIC (0xF17EDA2C37A110C1UL) /* FIRE DANCER ALLOC version 1 */
     146             : 
     147             : #define VOFF_NAME      fd_alloc_vgaddr
     148             : #define VOFF_TYPE      uint128
     149     3351624 : #define VOFF_VER_WIDTH 64
     150             : #include "../tmpl/fd_voff.c"
     151             : 
     152             : #else /* Platforms without 16B compare-exchange */
     153             : 
     154             : #define FD_ALLOC_MAGIC (0xF17EDA2C37A110C0UL) /* FIRE DANCER ALLOC version 0 */
     155             : 
     156             : /* TODO: Overaligning superblocks on these targets to get a wider
     157             :    version width */
     158             : 
     159             : #define VOFF_NAME      fd_alloc_vgaddr
     160             : #define VOFF_TYPE      ulong
     161             : #define VOFF_VER_WIDTH 4
     162             : #include "../tmpl/fd_voff.c"
     163             : 
     164             : #endif
     165             : 
     166             : struct __attribute__((aligned(FD_ALLOC_ALIGN))) fd_alloc {
     167             :   ulong magic;    /* ==FD_ALLOC_MAGIC */
     168             :   ulong wksp_off; /* Offset of the first byte of this structure from the start of the wksp */
     169             :   ulong tag;      /* tag that will be used by this allocator.  Positive. */
     170             : 
     171             :   /* Padding to 128 byte alignment here */
     172             : 
     173             :   /* active_slot[ sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup ] is the
     174             :      gaddr of the active superblock for sizeclass allocations done by a
     175             :      member of concurrency group cgroup or 0 if there is no active
     176             :      superblock currently for that sizeclass,cgroup pair.
     177             : 
     178             :      inactive_stack[ sizeclass ] is top of inactive stack for sizeclass
     179             :      or 0 if the stack is empty.  This is versioned offset with a 64-bit
     180             :      version number in the least significant bits and a 64-bit gaddr in
     181             :      the upper bits.  At 64-bits wide, note that version number reuse
     182             :      will not occur on any human timescales.  (FIXME: consider using a
     183             :      ulong to be more portable to platforms without 128-bit CAS support.
     184             :      E.g. 20-bit version and a 44-bit gaddr and restricting maximum
     185             :      supported workspace to ~16 TiB.)
     186             : 
     187             :      Note that this is compact but organized such that concurrent
     188             :      operations from different cgroups are unlikely to create false
     189             :      sharing. */
     190             : 
     191             :   ulong active_slot[ FD_ALLOC_SIZECLASS_CNT*(FD_ALLOC_JOIN_CGROUP_HINT_MAX+1UL) ] __attribute__((aligned(128UL)));
     192             : 
     193             :   /* Padding to 128 byte alignment here */
     194             : 
     195             :   fd_alloc_vgaddr_t inactive_stack[ FD_ALLOC_SIZECLASS_CNT ] __attribute__((aligned(128UL)));
     196             : };
     197             : 
     198             : /* fd_alloc_private_wksp returns the wksp backing alloc.  Assumes alloc
     199             :    is a non-NULL pointer in the caller's address space to the fd_alloc
     200             :    (not a join handle). */
     201             : 
     202             : FD_FN_PURE static inline fd_wksp_t *
     203     5753961 : fd_alloc_private_wksp( fd_alloc_t * alloc ) {
     204     5753961 :   return (fd_wksp_t *)(((ulong)alloc) - alloc->wksp_off);
     205     5753961 : }
     206             : 
     207             : /* fd_alloc_private_active_slot_replace replaces the value currently in
     208             :    the slot pointed to by active_slot with new_superblock_gaddr and
     209             :    returns the superblock_gaddr previously in there.  This is a compiler
     210             :    fence.  If FD_HAS_ATOMIC, this will be done atomically. */
     211             : 
     212             : static inline ulong
     213             : fd_alloc_private_active_slot_replace( ulong * active_slot,
     214     8658144 :                                       ulong   new_superblock_gaddr ) {
     215     8658144 :   ulong old_superblock_gaddr;
     216     8658144 :   FD_COMPILER_MFENCE();
     217     8658144 : # if FD_HAS_ATOMIC
     218     8658144 :   old_superblock_gaddr = FD_ATOMIC_XCHG( active_slot, new_superblock_gaddr );
     219             : # else
     220             :   old_superblock_gaddr = FD_VOLATILE_CONST( *active_slot );
     221             :   FD_VOLATILE( *active_slot ) = new_superblock_gaddr;
     222             : # endif
     223     8658144 :   FD_COMPILER_MFENCE();
     224     8658144 :   return old_superblock_gaddr;
     225     8658144 : }
     226             : 
     227             : /* fd_alloc_private_inactive_stack_push pushes superblock at the
     228             :    workspace global address superblock_gaddr in workspace wksp onto the
     229             :    stack inactive stack.  This is a compiler fence.  If FD_HAS_ATOMIC,
     230             :    this will be done atomically. */
     231             : 
     232             : static inline void
     233             : fd_alloc_private_inactive_stack_push( fd_alloc_vgaddr_t * inactive_stack,
     234             :                                       fd_wksp_t *         wksp,
     235      154494 :                                       ulong               superblock_gaddr ) {
     236      154494 :   fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     237             : 
     238      154494 :   for(;;) {
     239             : 
     240             :     /* Read the top of the inactive stack. */
     241             : 
     242      154494 :     fd_alloc_vgaddr_t old;
     243      154494 :     FD_COMPILER_MFENCE();
     244      154494 :     old = FD_VOLATILE_CONST( *inactive_stack );
     245      154494 :     FD_COMPILER_MFENCE();
     246             : 
     247      154494 :     ulong top_ver   = (ulong)fd_alloc_vgaddr_ver( old );
     248      154494 :     ulong top_gaddr = (ulong)fd_alloc_vgaddr_off( old );
     249             : 
     250             :     /* Try to push the top of the inactive stack */
     251             : 
     252      154494 :     fd_alloc_vgaddr_t new = fd_alloc_vgaddr( top_ver+1UL, superblock_gaddr );
     253             : 
     254      154494 :     FD_COMPILER_MFENCE();
     255      154494 :     FD_VOLATILE( superblock->next_gaddr ) = top_gaddr;
     256      154494 :     FD_COMPILER_MFENCE();
     257             : 
     258      154494 : #   if FD_HAS_ATOMIC
     259      154494 :     if( FD_LIKELY( FD_ATOMIC_CAS( inactive_stack, old, new )==old ) ) break;
     260             : #   else
     261             :     if( FD_LIKELY( FD_VOLATILE_CONST( *inactive_stack )==old ) ) { FD_VOLATILE( *inactive_stack ) = new; break; }
     262             : #   endif
     263             : 
     264             :     /* Hmmm ... that failed ... try again */
     265             : 
     266           0 :     FD_SPIN_PAUSE();
     267           0 :   }
     268             : 
     269      154494 :   FD_COMPILER_MFENCE();
     270      154494 : }
     271             : 
     272             : /* fd_alloc_private_inactive_stack_pop pops superblock off the top of
     273             :    the inactive stack.  Returns the non-zero wksp superblock gaddr of
     274             :    the popped stack top on success or 0 on failure (i.e. inactive stack
     275             :    is empty).  This is a compiler fence.  If FD_HAS_ATOMIC, this will be
     276             :    done atomically. */
     277             : 
     278             : static inline ulong
     279             : fd_alloc_private_inactive_stack_pop( fd_alloc_vgaddr_t * inactive_stack,
     280     1160568 :                                      fd_wksp_t *         wksp ) {
     281     1160568 :   ulong top_gaddr;
     282             : 
     283     1160568 :   for(;;) {
     284             : 
     285             :     /* Read the top of the inactive stack.  Return if the inactive stack
     286             :        is empty. */
     287             : 
     288     1160568 :     fd_alloc_vgaddr_t old;
     289     1160568 :     FD_COMPILER_MFENCE();
     290     1160568 :     old = FD_VOLATILE_CONST( *inactive_stack );
     291     1160568 :     FD_COMPILER_MFENCE();
     292             : 
     293             :     /**/  top_gaddr = (ulong)fd_alloc_vgaddr_off( old );
     294     1160568 :     ulong top_ver   = (ulong)fd_alloc_vgaddr_ver( old );
     295     1160568 :     fd_msan_unpoison( &top_gaddr, sizeof( top_gaddr ) );
     296     1160568 :     if( FD_UNLIKELY( !top_gaddr ) ) break;
     297             : 
     298             :     /* Try to pop the top of the inactive stack. */
     299             : 
     300      154470 :     fd_alloc_superblock_t * top = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, top_gaddr );
     301             : 
     302      154470 :     ulong next_gaddr;
     303      154470 :     FD_COMPILER_MFENCE();
     304      154470 :     next_gaddr = FD_VOLATILE_CONST( top->next_gaddr );
     305      154470 :     FD_COMPILER_MFENCE();
     306             : 
     307      154470 :     fd_alloc_vgaddr_t new = fd_alloc_vgaddr( top_ver+1UL, next_gaddr );
     308             : 
     309      154470 : #   if FD_HAS_ATOMIC
     310      154470 :     if( FD_LIKELY( FD_ATOMIC_CAS( inactive_stack, old, new )==old ) ) break;
     311             : #   else
     312             :     if( FD_LIKELY( FD_VOLATILE_CONST( *inactive_stack )==old ) ) { FD_VOLATILE( *inactive_stack ) = new; break; }
     313             : #   endif
     314             : 
     315             :     /* Hmmm ... that failed ... try again */
     316             : 
     317           0 :     FD_SPIN_PAUSE();
     318           0 :   }
     319             : 
     320     1160568 :   FD_COMPILER_MFENCE();
     321             : 
     322     1160568 :   return top_gaddr;
     323     1160568 : }
     324             : 
     325             : /* fd_alloc_hdr_t *****************************************************/
     326             : 
     327             : /* An fd_alloc_hdr_t is a small header prepended to an allocation that
     328             :    describes the allocation.  Because fd_alloc supports arbitrary
     329             :    allocation alignments, these headers might be stored at unaligned
     330             :    positions. */
     331             : 
     332             : typedef uint fd_alloc_hdr_t;
     333             : 
     334             : /* FD_ALLOC_HDR_LARGE_* give the fd_alloc_hdr_t used for mallocs done by
     335             :    the allocator of last resort (i.e. fd_wksp_alloc).  The least
     336             :    significant 7 bits must be FD_ALLOC_SIZECLASS_LARGE so that free can
     337             :    detect an allocation is done by that allocator.  The most significant
     338             :    24 bits are a magic number to help with analytics.  Bit 7 indicates
     339             :    whether this allocation is direct user allocation or holds a
     340             :    superblock that aggregates many small allocations together. */
     341             : 
     342           0 : #define FD_ALLOC_HDR_LARGE_DIRECT     ((fd_alloc_hdr_t)(0xFDA11C00U | (fd_alloc_hdr_t)FD_ALLOC_SIZECLASS_LARGE))
     343             : #define FD_ALLOC_HDR_LARGE_SUPERBLOCK ((fd_alloc_hdr_t)(0xFDA11C80U | (fd_alloc_hdr_t)FD_ALLOC_SIZECLASS_LARGE))
     344             : 
     345             : /* fd_alloc_hdr_load loads the header for the allocation whose first
     346             :    byte is at laddr in the caller's address space.  The header will be
     347             :    observed at some point of time between when this call was made and
     348             :    returned and this implies that the allocation at laddr must be at
     349             :    least until the caller stops using the header. */
     350             : 
     351             : FD_FN_PURE static inline fd_alloc_hdr_t
     352     4260438 : fd_alloc_hdr_load( void const * laddr ) {
     353     4260438 :   return FD_LOAD( fd_alloc_hdr_t, ((ulong)laddr) - sizeof(fd_alloc_hdr_t) );
     354     4260438 : }
     355             : 
     356             : /* fd_alloc_hdr_sizeclass returns the sizeclass of an allocation
     357             :    described by hdr.  This will be in [0,FD_ALLOC_SIZECLASS_CNT) or
     358             :    FD_ALLOC_SIZECLASS_LARGE.  sizeclass==FD_ALLOC_SIZECLASS_LARGE
     359             :    indicates that the allocation was done via fd_wksp_alloc directly.
     360             :    Small allocations are clustered into superblocks for performance and
     361             :    packing efficiency.  All allocations in a superblock are from the
     362             :    same sizeclass. */
     363             : 
     364     4266720 : FD_FN_CONST static inline ulong fd_alloc_hdr_sizeclass( fd_alloc_hdr_t hdr ) { return (ulong)(hdr & 127U); }
     365             : 
     366             : /* fd_alloc_hdr_{superblock,block} return details about a small
     367             :    allocation whose first byte is at laddr in the caller's address space
     368             :    and is described by hdr.  In particular:
     369             : 
     370             :    fd_alloc_hdr_superblock( hdr, laddr ) returns the location in the
     371             :    caller's address space of the superblock containing the allocation.
     372             :    The lifetime of a superblock is at least as long as the allocation at
     373             :    laddr.
     374             : 
     375             :    fd_alloc_hdr_block_idx( hdr ) returns the superblock block index of the
     376             :    block containing the allocation.  Note that this will be in:
     377             :      [0,fd_alloc_sizeclass_block_cnt(sizeclass))
     378             :    and that:
     379             :      fd_alloc_sizeclass_block_cnt(sizeclass)<=fd_alloc_block_set_max()<=64. */
     380             : 
     381             : FD_FN_CONST static inline fd_alloc_superblock_t *
     382             : fd_alloc_hdr_superblock( fd_alloc_hdr_t hdr,
     383     3898920 :                          void *         laddr ) {
     384     3898920 :   return (fd_alloc_superblock_t *)(((ulong)laddr) - ((ulong)(hdr >> 13)));
     385     3898920 : }
     386             : 
     387     3898929 : FD_FN_CONST static inline ulong fd_alloc_hdr_block_idx( fd_alloc_hdr_t hdr ) { return (ulong)((hdr >> 7) & 63U); }
     388             : 
     389             : /* fd_alloc_hdr_is_large returns 1 if hdr is for a large allocation and
     390             :    0 if not.  fd_alloc_hdr_sizeclass( hdr )==FD_ALLOC_SIZECLASS_LARGE
     391             :    also works but does not test the magic number in the most significant
     392             :    bits. */
     393             : 
     394             : FD_FN_CONST static inline int
     395           0 : fd_alloc_hdr_is_large( fd_alloc_hdr_t hdr ) {
     396           0 :   return fd_uint_clear_bit( hdr, 7 )==FD_ALLOC_HDR_LARGE_DIRECT;
     397           0 : }
     398             : 
     399             : /* fd_alloc_hdr_large_is_superblock returns 1 if hdr (which is assumed
     400             :    to be for a large allocation) is for a large allocation that holds a
     401             :    superblock. */
     402             : 
     403             : FD_FN_CONST static inline int
     404           0 : fd_alloc_hdr_large_is_superblock( fd_alloc_hdr_t hdr ) {
     405           0 :   return fd_uint_extract_bit( hdr, 7 );
     406           0 : }
     407             : 
     408             : /* fd_alloc_hdr_store stores a fd_alloc_hdr_t describing a small
     409             :    sizeclass allocation contained within block of superblock in the
     410             :    sizeof(fd_alloc_hdr_t) bytes immediately preceding the byte pointed
     411             :    to by laddr in the caller's address space.  The caller promises that
     412             :    these bytes are somewhere within the block.
     413             : 
     414             :    fd_alloc_hdr_store_large similarly stores a fd_alloc_hdr_t describing
     415             :    a large allocation. */
     416             : 
     417             : static inline void *
     418             : fd_alloc_hdr_store( void *                  laddr,
     419             :                     fd_alloc_superblock_t * superblock,
     420             :                     ulong                   block_idx,
     421     3898758 :                     ulong                   sizeclass ) {
     422     3898758 :   FD_STORE( fd_alloc_hdr_t, ((ulong)laddr) - sizeof(fd_alloc_hdr_t),
     423     3898758 :             (fd_alloc_hdr_t)( ((((ulong)laddr) - ((ulong)superblock)) << 13) |    /* Bits 31:13 - 19 bit: superblock offset */
     424     3898758 :                               (block_idx                              <<  7) |    /* Bits 12: 7 -  6 bit: block */
     425     3898758 :                               sizeclass                                      ) ); /* Bits  6: 0 -  7 bit: sizeclass */
     426     3898758 :   return laddr;
     427     3898758 : }
     428             : 
     429             : static inline void *
     430             : fd_alloc_hdr_store_large( void * laddr,
     431      362277 :                           int    is_superblock ) { /* in [0,1] */
     432      362277 :   FD_STORE( fd_alloc_hdr_t, ((ulong)laddr) - sizeof(fd_alloc_hdr_t),
     433      362277 :             FD_ALLOC_HDR_LARGE_DIRECT | (((fd_alloc_hdr_t)is_superblock) << 7) );
     434      362277 :   return laddr;
     435      362277 : }
     436             : 
     437             : /* Misc ***************************************************************/
     438             : 
     439             : /* fd_alloc_private_join_alloc returns the local address of the alloc
     440             :    for a join. */
     441             : 
     442             : FD_FN_CONST fd_alloc_t *
     443     8512656 : fd_alloc_private_join_alloc( fd_alloc_t * join ) {
     444     8512656 :   return (fd_alloc_t *)(((ulong)join) & ~FD_ALLOC_JOIN_CGROUP_HINT_MAX);
     445     8512656 : }
     446             : 
     447             : /* Constructors *******************************************************/
     448             : 
     449             : ulong
     450         207 : fd_alloc_align( void ) {
     451         207 :   return alignof(fd_alloc_t);
     452         207 : }
     453             : 
     454             : ulong
     455         111 : fd_alloc_footprint( void ) {
     456         111 :   return sizeof(fd_alloc_t);
     457         111 : }
     458             : 
     459             : ulong
     460           0 : fd_alloc_superblock_footprint(void){
     461           0 :   return sizeof(fd_alloc_superblock_t);
     462           0 : }
     463             : 
     464             : void *
     465             : fd_alloc_new( void * shmem,
     466          54 :               ulong  tag ) {
     467             : 
     468             :   /* Check input arguments */
     469             : 
     470          54 :   if( FD_UNLIKELY( !shmem ) ) {
     471           3 :     FD_LOG_WARNING(( "NULL shmem" ));
     472           3 :     return NULL;
     473           3 :   }
     474             : 
     475          51 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, alignof(fd_alloc_t) ) ) ) {
     476           3 :     FD_LOG_WARNING(( "misaligned shmem" ));
     477           3 :     return NULL;
     478           3 :   }
     479             : 
     480          48 :   fd_wksp_t * wksp = fd_wksp_containing( shmem );
     481          48 :   if( FD_UNLIKELY( !wksp ) ) {
     482           3 :     FD_LOG_WARNING(( "shmem must be in a workspace" ));
     483           3 :     return NULL;
     484           3 :   }
     485             : 
     486          45 :   if( FD_UNLIKELY( !tag ) ) {
     487           3 :     FD_LOG_WARNING(( "bad tag" ));
     488           3 :     return NULL;
     489           3 :   }
     490             : 
     491          42 :   fd_alloc_t * alloc = (fd_alloc_t *)shmem;
     492          42 :   fd_memset( alloc, 0, sizeof(fd_alloc_t) );
     493             : 
     494          42 :   alloc->wksp_off = (ulong)alloc - (ulong)wksp;
     495          42 :   alloc->tag      = tag;
     496             : 
     497          42 :   FD_COMPILER_MFENCE();
     498          42 :   FD_VOLATILE( alloc->magic ) = FD_ALLOC_MAGIC;
     499          42 :   FD_COMPILER_MFENCE();
     500             : 
     501          42 :   return shmem;
     502          45 : }
     503             : 
     504             : fd_alloc_t *
     505             : fd_alloc_join( void * shalloc,
     506         156 :                ulong  cgroup_hint ) {
     507         156 :   fd_alloc_t * alloc = shalloc;
     508             : 
     509         156 :   if( FD_UNLIKELY( !alloc ) ) {
     510           3 :     FD_LOG_WARNING(( "NULL shalloc" ));
     511           3 :     return NULL;
     512           3 :   }
     513             : 
     514         153 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)alloc, alignof(fd_alloc_t) ) ) ) {
     515           3 :     FD_LOG_WARNING(( "misaligned shalloc" ));
     516           3 :     return NULL;
     517           3 :   }
     518             : 
     519         150 :   if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) {
     520           9 :     FD_LOG_WARNING(( "bad magic" ));
     521           9 :     return NULL;
     522           9 :   }
     523         141 :   return fd_alloc_join_cgroup_hint_set( alloc, cgroup_hint );
     524         150 : }
     525             : 
     526             : void *
     527          69 : fd_alloc_leave( fd_alloc_t * join ) {
     528             : 
     529          69 :   if( FD_UNLIKELY( !join ) ) {
     530           3 :     FD_LOG_WARNING(( "NULL join" ));
     531           3 :     return NULL;
     532           3 :   }
     533          66 :   return fd_alloc_private_join_alloc( join );
     534          69 : }
     535             : 
     536             : void *
     537          36 : fd_alloc_delete( void * shalloc ) {
     538             : 
     539          36 :   if( FD_UNLIKELY( !shalloc ) ) {
     540           3 :     FD_LOG_WARNING(( "NULL shalloc" ));
     541           3 :     return NULL;
     542           3 :   }
     543             : 
     544          33 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shalloc, alignof(fd_alloc_t) ) ) ) {
     545           3 :     FD_LOG_WARNING(( "misaligned shalloc" ));
     546           3 :     return NULL;
     547           3 :   }
     548             : 
     549          30 :   fd_alloc_t * alloc = (fd_alloc_t *)shalloc;
     550             : 
     551          30 :   if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) {
     552           3 :     FD_LOG_WARNING(( "bad magic" ));
     553           3 :     return NULL;
     554           3 :   }
     555             : 
     556          27 :   FD_COMPILER_MFENCE();
     557          27 :   FD_VOLATILE( alloc->magic ) = 0UL;
     558          27 :   FD_COMPILER_MFENCE();
     559             : 
     560             :   /* Clean up as much as we can.  For each sizeclass, delete all active
     561             :      superblocks and all inactive superblocks.  We will not be able
     562             :      cleanup any superblocks that are fully allocated (and any
     563             :      outstanding large allocations) as we rely on the application to
     564             :      implicitly track them (because they nominally will eventually call
     565             :      free on them).  So hopefully the application did a good job and
     566             :      cleaned up all their outstanding stuff before calling delete. */
     567             : 
     568          27 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     569             : 
     570        3429 :   for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
     571             : 
     572        3402 :     ulong cgroup_cnt = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
     573       57834 :     for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
     574       54432 :       ulong superblock_gaddr =
     575       54432 :         fd_alloc_private_active_slot_replace( alloc->active_slot + sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup_idx, 0UL );
     576       54432 :       if( FD_UNLIKELY( superblock_gaddr ) )
     577         387 :         fd_alloc_free( alloc, (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr ) );
     578       54432 :     }
     579             : 
     580        3402 :     fd_alloc_vgaddr_t * inactive_stack = alloc->inactive_stack + sizeclass;
     581        3417 :     for(;;) {
     582        3417 :       ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( inactive_stack, wksp );
     583        3417 :       if( FD_LIKELY( !superblock_gaddr ) ) break;
     584          15 :       fd_alloc_free( alloc, (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr ) );
     585          15 :     }
     586             : 
     587        3402 :   }
     588             : 
     589          27 :   return shalloc;
     590          30 : }
     591             : 
     592             : fd_wksp_t *
     593          42 : fd_alloc_wksp( fd_alloc_t * join ) {
     594          42 :   return FD_LIKELY( join ) ? fd_alloc_private_wksp( fd_alloc_private_join_alloc( join ) ) : NULL;
     595          42 : }
     596             : 
     597             : ulong
     598          15 : fd_alloc_tag( fd_alloc_t * join ) {
     599          15 :   return FD_LIKELY( join ) ? fd_alloc_private_join_alloc( join )->tag : 0UL;
     600          15 : }
     601             : 
     602             : static inline void *
     603             : fd_alloc_malloc_at_least_impl( fd_alloc_t * join,
     604             :                                ulong        align,
     605             :                                ulong        sz,
     606     4251429 :                                ulong *      max ) {
     607             : 
     608     4251429 :   if( FD_UNLIKELY( !max ) ) return NULL;
     609             : 
     610             :   /* Handle default align, NULL alloc, 0 size, non-power-of-two align
     611             :      and unreasonably large sz.  footprint has room for fd_alloc_hdr_t,
     612             :      sz bytes with enough padding to allow for the arbitrary alignment
     613             :      of blocks in a superblock.  Note that footprint is guaranteed not
     614             :      to overflow if align is a power of 2 as align at most 2^63 and
     615             :      sizeof is 4 and we abort is align is not a power of 2.  So we don't
     616             :      need to do elaborate overflow checking. */
     617             : 
     618     4251429 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
     619             : 
     620     4251429 :   align = fd_ulong_if( !align, FD_ALLOC_MALLOC_ALIGN_DEFAULT, align );
     621             : 
     622             : # if FD_HAS_DEEPASAN
     623             :   /* The header is prepended and needs to be unpoisoned. Ensure that
     624             :      there is padding for the alloc_hdr to be properly aligned. We
     625             :      want to exit silently if the sz passed in is 0. The alignment must be
     626             :      at least 8. */
     627             :   ulong fd_alloc_hdr_footprint = fd_ulong_align_up( sizeof(fd_alloc_hdr_t), FD_ASAN_ALIGN );
     628             :   if( FD_LIKELY(sz && sz < ULONG_MAX) ) {
     629             :     sz = fd_ulong_align_up( sz, FD_ASAN_ALIGN );
     630             :   }
     631             :   align = fd_ulong_if( align < FD_ASAN_ALIGN, FD_ASAN_ALIGN, align );
     632             : # else
     633     4251429 :   ulong fd_alloc_hdr_footprint = sizeof(fd_alloc_hdr_t);
     634     4251429 : # endif
     635             : 
     636     4251429 :   ulong footprint = sz + fd_alloc_hdr_footprint + align - 1UL;
     637             : 
     638     4251429 :   if( FD_UNLIKELY( (!alloc) | (!fd_ulong_is_pow2( align )) | (!sz) | (footprint<=sz) ) ) {
     639          66 :     *max = 0UL;
     640          66 :     return NULL;
     641          66 :   }
     642             : 
     643     4251363 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     644             : 
     645             :   /* At this point, alloc is non-NULL and backed by wksp, align is a
     646             :      power-of-2, footprint is a reasonable non-zero value.  If the
     647             :      footprint is large, just allocate the memory directly, prepend the
     648             :      appropriate header and return.  TODO: consider clearing alignment
     649             :      padding for better alloc parameter recovery in diagnostics here? */
     650             : 
     651     4251363 :   if( FD_UNLIKELY( footprint > FD_ALLOC_FOOTPRINT_SMALL_THRESH ) ) {
     652             : 
     653      352605 :     ulong glo;
     654      352605 :     ulong ghi;
     655      352605 :     ulong wksp_gaddr = fd_wksp_alloc_at_least( wksp, 1UL, footprint, alloc->tag, &glo, &ghi );
     656      352605 :     if( FD_UNLIKELY( !wksp_gaddr ) ) {
     657           0 :       *max = 0UL;
     658           0 :       return NULL;
     659           0 :     }
     660             : 
     661      352605 :     ulong alloc_gaddr = fd_ulong_align_up( wksp_gaddr + sizeof(fd_alloc_hdr_t), align );
     662      352605 :     *max = (ghi - glo) - (alloc_gaddr - wksp_gaddr);
     663      352605 :     return fd_alloc_hdr_store_large( fd_wksp_laddr_fast( wksp, alloc_gaddr ), 0 /* !sb */ );
     664      352605 :   }
     665             : 
     666             :   /* At this point, the footprint is small.  Determine the preferred
     667             :      sizeclass for this allocation and the preferred active superblock
     668             :      to use for this sizeclass and join. */
     669             : 
     670     3898758 :   ulong sizeclass = fd_alloc_preferred_sizeclass( footprint );
     671     3898758 :   ulong cgroup    = fd_alloc_preferred_sizeclass_cgroup( sizeclass, fd_alloc_join_cgroup_hint( join ) );
     672             : 
     673     3898758 :   ulong * active_slot = alloc->active_slot + sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup;
     674             : 
     675             :   /* Try to get exclusive access to the preferred active superblock.
     676             :      Note that all all active superblocks have at least one free block.
     677             : 
     678             :      TODO: consider doing something test-and_test-and-set?  E.g.:
     679             :        superblock_gaddr = FD_VOLATILE_CONST( *active_slot );
     680             :        if( FD_LIKELY( superblock_gaddr ) ) superblock_gaddr = fd_alloc_replace_active( active_slot, 0UL );
     681             :      This would avoid an atomic operation if there currently isn't an
     682             :      active superblock for this sizeclass and cgroup. */
     683             : 
     684     3898758 :   ulong superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, 0UL );
     685             : 
     686             :   /* At this point, if superblock_gaddr is non-zero, we have exclusive
     687             :      access to the superblock and only we can allocate blocks from it.
     688             :      (Other threads could free blocks to it concurrently though.)
     689             : 
     690             :      If superblock_gaddr is zero, there was no preferred active
     691             :      superblock for this sizeclass,cgroup pair when we looked.  So, we
     692             :      try to pop the inactive superblock stack for this sizeclass.  Note
     693             :      that all inactive superblocks also have at least one free block.
     694             : 
     695             :      If that fails, we try to allocate a new superblock to hold this
     696             :      allocation.  If we are able to do so, obviously the new superblock
     697             :      would have at least one free block for this allocation.  (Yes,
     698             :      malloc calls itself recursively.  The base case is the large
     699             :      allocation above.  Note that we expand out the base case explicitly
     700             :      here so we can distinguish user large allocations from gigantic
     701             :      superblock allocations in analytics without having to change the
     702             :      APIs or use the public API as a wrapper.)
     703             : 
     704             :      If that fails, we are in trouble and fail (we are either out of
     705             :      memory or have too much wksp fragmentation). */
     706             : 
     707     3898758 :   if( FD_UNLIKELY( !superblock_gaddr ) ) {
     708             : 
     709      146019 :     superblock_gaddr = fd_alloc_private_inactive_stack_pop( alloc->inactive_stack + sizeclass, wksp );
     710             : 
     711      146019 :     if( FD_UNLIKELY( !superblock_gaddr ) ) {
     712             : 
     713       14901 :       fd_alloc_superblock_t * superblock;
     714             : 
     715       14901 :       ulong superblock_footprint = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].superblock_footprint;
     716       14901 :       if( FD_UNLIKELY( superblock_footprint > FD_ALLOC_FOOTPRINT_SMALL_THRESH ) ) {
     717             : 
     718        9672 :         ulong wksp_footprint = superblock_footprint + fd_alloc_hdr_footprint + FD_ALLOC_SUPERBLOCK_ALIGN - 1UL;
     719        9672 :         ulong wksp_gaddr     = fd_wksp_alloc( wksp, 1UL, wksp_footprint, alloc->tag );
     720        9672 :         if( FD_UNLIKELY( !wksp_gaddr ) ) {
     721           0 :           *max = 0UL;
     722           0 :           return NULL;
     723           0 :         }
     724             : 
     725        9672 :         superblock_gaddr = fd_ulong_align_up( wksp_gaddr + fd_alloc_hdr_footprint, FD_ALLOC_SUPERBLOCK_ALIGN );
     726             : #       if FD_HAS_DEEPASAN
     727             :         /* At this point, a new superblock is allocated from the wksp and the header
     728             :            is prepended. The alignment needs to be taken into account: the padding
     729             :            should also be unpoisoned.
     730             : 
     731             :            Due to ASan requiring 8 byte word alignment for poisoning regions, must
     732             :            guarantee 8 bytes for the header instead of just sizeof(fd_alloc_hdr_t).
     733             :            We have a worst case padding of 15 bytes. Due to forced alignment in
     734             :            fd_wksp_alloc of at least 8 bytes, in the worst case we will use 8 bytes
     735             :            to align up the superblock_gaddr. The remaining 7 padding bytes will be
     736             :            used to safely allow for the superblock_footprint to be aligned up to
     737             :            an 8 byte multiple.  */
     738             :         void * unpoison_laddr = fd_wksp_laddr_fast( wksp, superblock_gaddr - fd_alloc_hdr_footprint );
     739             :         ulong aligned_superblock_footprint = fd_ulong_align_up( superblock_footprint, FD_ASAN_ALIGN );
     740             :         fd_asan_unpoison( unpoison_laddr, aligned_superblock_footprint + fd_alloc_hdr_footprint );
     741             : #       endif
     742             : 
     743        9672 :         superblock = (fd_alloc_superblock_t *)
     744        9672 :           fd_alloc_hdr_store_large( fd_wksp_laddr_fast( wksp, superblock_gaddr ), 1 /* sb */ );
     745             : 
     746        9672 :       } else {
     747             : 
     748             :         /* TODO: consider having user facing API wrap an internal API so
     749             :            that recursive calls don't have to do redundant error
     750             :            trapping?
     751             : 
     752             :            TODO: consider using at_least semantics to adapt the actual
     753             :            size of the superblock to the location it is stored
     754             :            (currently should be irrelevant as the sizeclasses are
     755             :            designed to tightly nest). */
     756             : 
     757        5229 :         superblock = fd_alloc_malloc( join, FD_ALLOC_SUPERBLOCK_ALIGN, superblock_footprint );
     758        5229 :         if( FD_UNLIKELY( !superblock ) ) {
     759           0 :           *max = 0UL;
     760           0 :           return NULL;
     761           0 :         }
     762        5229 :         superblock_gaddr = fd_wksp_gaddr_fast( wksp, superblock );
     763             : 
     764        5229 :       }
     765             : 
     766       14901 :       FD_COMPILER_MFENCE();
     767       14901 :       FD_VOLATILE( superblock->free_blocks ) = fd_ulong_mask_lsb( (int)(uint)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt );
     768       14901 :       FD_VOLATILE( superblock->next_gaddr  ) = 0UL;
     769       14901 :       FD_COMPILER_MFENCE();
     770             : 
     771       14901 :     }
     772      146019 :   }
     773             : 
     774             :   /* At this point, we have a superblock with space for at least one
     775             :      allocation and only we can allocate blocks from it.  Other threads
     776             :      could free blocks in this superblock concurrently though.  As such,
     777             :      we can non-atomically find a set bit in free_blocks (there will be
     778             :      at least one and no other thread will clear it behind our back) but
     779             :      we must atomically clear the bit we found so we don't mess up the
     780             :      other bits that might be concurrently set by free. */
     781             : 
     782     3898758 :   fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
     783             : 
     784     3898758 :   fd_alloc_block_set_t free_blocks;
     785     3898758 :   FD_COMPILER_MFENCE();
     786     3898758 :   free_blocks = FD_VOLATILE_CONST( superblock->free_blocks );
     787     3898758 :   FD_COMPILER_MFENCE();
     788             : 
     789     3898758 :   ulong                block_idx = fd_alloc_block_set_first( free_blocks );
     790     3898758 :   fd_alloc_block_set_t block     = fd_alloc_block_set_ele( block_idx );
     791             : 
     792     3898758 :   free_blocks = fd_alloc_block_set_sub( &superblock->free_blocks, block );
     793             : 
     794             :   /* At this point, free_blocks gives the set of free blocks in the
     795             :      superblock immediately before the allocation occurred. */
     796             : 
     797     3898758 :   if( FD_LIKELY( free_blocks!=block ) ) {
     798             : 
     799             :     /* At this point, we know the superblock has at least one
     800             :        allocated block in it (the one we just allocated) and one free
     801             :        block in it.  And this will hold true until we put this
     802             :        superblock back into circulation.  Specifically, nobody can free
     803             :        the block we just allocated until we return to tell them about it
     804             :        and nobody can allocate any remaining free blocks until we get
     805             :        this superblock back into circulation.  To get this superblock
     806             :        back into circulation, we make it the active superblock for this
     807             :        sizeclass,cgroup pair. */
     808             : 
     809     3668910 :     ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, superblock_gaddr );
     810             : 
     811             :     /* And if this displaced a previously active superblock (e.g.
     812             :        another thread made an different superblock the active one while
     813             :        we were doing the above), we add the displaced superblock to the
     814             :        sizeclass's inactive superblocks.  Note that any such displaced
     815             :        superblock also has at least one free block in it (the active
     816             :        superblock always has at least one free block) that nobody can
     817             :        allocate from as, at this point, it is not in circulation.  Thus,
     818             :        pushing it onto the superblock's inactive stack will preserve the
     819             :        invariant that all inactive superblocks have at least one free
     820             :        block. */
     821             : 
     822     3668910 :     if( FD_UNLIKELY( displaced_superblock_gaddr ) )
     823           0 :       fd_alloc_private_inactive_stack_push( alloc->inactive_stack + sizeclass, wksp, displaced_superblock_gaddr );
     824             : 
     825     3668910 :   } //else {
     826             : 
     827             :     /* The superblock had no more free blocks immediately after the
     828             :        allocation occurred.  We should not make this superblock the
     829             :        preferred superblock or push it onto the sizeclass's nonfull
     830             :        superblock stack; it would break the invariants that all
     831             :        superblocks in circulation for a sizeclass have at least one
     832             :        free block.
     833             : 
     834             :        And, as this superblock had no free blocks, we don't need to
     835             :        track the superblock anyway as malloc can't use the superblock
     836             :        until some of the blocks in it have been freed.  As such, this
     837             :        superblock will not be used in a malloc until after the next call
     838             :        to free on a block in this superblock returns this superblock to
     839             :        circulation.  Note that this superblock will have at least one
     840             :        allocated block until after this function returns (the block we
     841             :        just allocated) and thus cannot ever be considered as a deletion
     842             :        candidate until after this function returns and this allocation
     843             :        is freed.
     844             : 
     845             :        As discussed in free below, we could update a superblock cgroup
     846             :        hint here (such that the when the superblock goes back into
     847             :        circulation, it will be put into circulation as the active
     848             :        superblock for this cgroup to encourage for additional mallocs
     849             :        from this thread for good spatial locality).  This doesn't need
     850             :        to be atomic.  Even though a concurrent free on another thread
     851             :        might get this into superblock into circulation before this
     852             :        executes (and thus also have other mallocs occurred that changed
     853             :        the active_hint), it doesn't matter.  So long as the hint is a
     854             :        sane value at all points in time, free will work fine. */
     855             : 
     856             :   //}
     857             : 
     858             :   /* Carve the requested allocation out of the newly allocated block,
     859             :      prepend the allocation header for use by free and return.  TODO:
     860             :      considering clearing alignment padding for better alloc parameter
     861             :      recovery in diagnostics here? */
     862             : 
     863     3898758 :   ulong block_footprint = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
     864     3898758 :   ulong block_laddr     = (ulong)superblock + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
     865     3898758 :   ulong alloc_laddr     = fd_ulong_align_up( block_laddr + fd_alloc_hdr_footprint, align );
     866             : 
     867             : # if FD_HAS_DEEPASAN
     868             :   /* The block and the header must be unpoisoned to accomodate the block
     869             :      footprint. The block footprint is determined by the sizeclass which
     870             :      provides the minimum size that accomodates the footprint which is the
     871             :      sz that's passed in, the padded fd_alloc_hdr, and the worst case amount
     872             :      of alignment bytes. Because sz % FD_ASAN_ALIGN == 0, it is known that
     873             :      we will have unused bytes at the end of the block since alloc_laddr %
     874             :      FD_ASAN_ALIGN == 0. To ensure ASAN alignment, the range of bytes used
     875             :      in the block can be safely rounded down.
     876             :   */
     877             : 
     878             :   void* laddr = (void*)(alloc_laddr - fd_alloc_hdr_footprint);
     879             : 
     880             :   ulong block_hi_addr = block_laddr + block_footprint;
     881             :   ulong block_unpoison_sz = fd_ulong_align_dn( block_hi_addr - alloc_laddr, FD_ASAN_ALIGN );
     882             :   fd_asan_unpoison( laddr, block_unpoison_sz + fd_alloc_hdr_footprint );
     883             : # endif
     884             : 
     885     3898758 :   *max = block_footprint - (alloc_laddr - block_laddr);
     886             : 
     887     3898758 :   return fd_alloc_hdr_store( (void *)alloc_laddr, superblock, block_idx, sizeclass );
     888     3898758 : }
     889             : 
     890             : void *
     891             : fd_alloc_malloc_at_least( fd_alloc_t * join,
     892             :                           ulong        align,
     893             :                           ulong        sz,
     894     4251429 :                           ulong *      max ) {
     895     4251429 :   void * res = fd_alloc_malloc_at_least_impl( join, align, sz, max );
     896     4251429 :   fd_msan_poison( res, *max );
     897     4251429 :   return res;
     898     4251429 : }
     899             : 
     900             : void
     901             : fd_alloc_free( fd_alloc_t * join,
     902     4260495 :                void *       laddr ) {
     903             : 
     904             :   /* Handle NULL alloc and/or NULL laddr */
     905             : 
     906     4260495 :   fd_alloc_t * alloc  = fd_alloc_private_join_alloc( join );
     907     4260495 :   if( FD_UNLIKELY( (!alloc) | (!laddr) ) ) return;
     908             : 
     909             :   /* At this point, we have a non-NULL alloc and a pointer to the first
     910             :      byte to an allocation done by it.  Load the allocation header and
     911             :      extract the sizeclass.  If the sizeclass indicates this is a large
     912             :      allocation, use fd_wksp_free to free this allocation (note that
     913             :      fd_wksp_free works for any byte within the wksp allocation). */
     914             : 
     915     4260438 :   fd_alloc_hdr_t hdr       = fd_alloc_hdr_load( laddr );
     916     4260438 :   ulong          sizeclass = fd_alloc_hdr_sizeclass( hdr );
     917             : 
     918     4260438 :   if( FD_UNLIKELY( sizeclass==FD_ALLOC_SIZECLASS_LARGE ) ) {
     919      362256 :     fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
     920      362256 :     fd_wksp_free( wksp, fd_wksp_gaddr_fast( wksp, laddr ) );
     921      362256 :     return;
     922      362256 :   }
     923             : 
     924             :   /* At this point, we have a small allocation.  Determine the
     925             :      superblock and block index from the header and then free the block. */
     926             : 
     927     3898182 :   fd_alloc_superblock_t * superblock  = fd_alloc_hdr_superblock( hdr, laddr );
     928     3898182 :   ulong                   block_idx   = fd_alloc_hdr_block_idx( hdr );
     929     3898182 :   fd_alloc_block_set_t    block       = fd_alloc_block_set_ele( block_idx );
     930     3898182 :   fd_alloc_block_set_t    free_blocks = fd_alloc_block_set_add( &superblock->free_blocks, block );
     931             : 
     932             : 
     933             : # if FD_HAS_DEEPASAN
     934             :   /* The portion of the block which is used for the header and the allocation
     935             :      should get poisoned. The alloc's laddr is already at least 8 byte aligned.
     936             :      The 8 bytes prior to the start of the laddr are used by the fd_alloc_hdr_t.
     937             :      These should get poisoned as the block is freed again. The region used by
     938             :      the allocation should also get poisoned: [laddr,block_laddr+block_footprint].
     939             :      However, we know that the size of the initial allocation was also 8 byte
     940             :      aligned so we align down the size of the range to poison safely.  */
     941             :   ulong block_footprint        = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
     942             :   ulong block_laddr            = (ulong)superblock + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
     943             :   ulong block_hi_addr          = fd_ulong_align_dn( block_laddr + block_footprint, FD_ASAN_ALIGN );
     944             :   ulong fd_alloc_hdr_footprint = fd_ulong_align_up( sizeof(fd_alloc_hdr_t), FD_ASAN_ALIGN );
     945             :   ulong fd_alloc_hdr_laddr     = (ulong)laddr - fd_alloc_hdr_footprint;
     946             :   ulong sz                     = block_hi_addr - (ulong)laddr + fd_alloc_hdr_footprint;
     947             :   fd_asan_poison( (void*)fd_alloc_hdr_laddr, sz );
     948             : # endif
     949             : 
     950             :   /* At this point, free_blocks is the set of free blocks just before
     951             :      the free. */
     952             : 
     953     3898182 :   ulong free_cnt = fd_alloc_block_set_cnt( free_blocks );
     954     3898182 :   if( FD_UNLIKELY( !free_cnt ) ) {
     955             : 
     956             :     /* The superblock containing this block had no free blocks
     957             :        immediately before we freed the allocation.  Thus, at this point,
     958             :        nobody can allocate any blocks from this superblock (the
     959             :        superblock is neither an active superblock nor on the inactive
     960             :        stack as per the note in malloc above) and we need to get the
     961             :        superblock back into circulation for reuse.  It is okay if other
     962             :        threads concurrently free other blocks in this superblock while
     963             :        we are doing this (they know the superblock is either in
     964             :        circulation or is being put into circulation).
     965             : 
     966             :        Since there is at least one free block in the superblock and
     967             :        nobody can allocate from it until it is circulation, putting it
     968             :        into circulation preserves the invariant that all superblocks in
     969             :        circulation have at least one free block.
     970             : 
     971             :        We have a bunch of options for putting this superblock back into
     972             :        circulation:
     973             : 
     974             :        - By pushing it onto the inactive stack
     975             :        - By making it the active superblock of the caller's cgroup
     976             :        - By making it the active superblock of the cgroup that most did
     977             :          the most recent malloc from it.
     978             :        - By making it the active superblock based on explicitly provided
     979             :          hint.
     980             :        - ...
     981             : 
     982             :        The first option is simplest to implement and balanced between
     983             :        common use cases single threaded, malloc/free pairs have thread
     984             :        affinity, and pipelined use cases.  (E.g. single threaded will
     985             :        take an extra time to hop from inactive and active and potential
     986             :        has slightly worse overallocation, similar story for paired.
     987             :        Cache affinity in these two cases might be slightly degraded from
     988             :        empty superblocks hopping between concurrency groups via the
     989             :        inactive stack, pipelined naturally gets the page back to the
     990             :        malloc-ing thread albeit with a brief hop through the inactive
     991             :        stack though).
     992             : 
     993             :        The second option is about as simple and optimizes the
     994             :        single/threaded and paired use cases as this thread is also the
     995             :        thread likely the same thread that malloc'd this.  Pipelined is
     996             :        marginally worse as the superblock will have to take two hops
     997             :        before it gets reused again (from the free-ing thread active
     998             :        superblock to the inactive stack to the malloc-ing active
     999             :        superblock).
    1000             : 
    1001             :        The third and fourth options can simultaneously get all options
    1002             :        optimized but they require extra plumbing (either under the hood
    1003             :        as per the note in malloc above from to the caller to get the
    1004             :        extra context).
    1005             : 
    1006             :        Currently we do the second option for simplicity and optimal
    1007             :        behaviors in the single threaded and paired use cases. */
    1008             : 
    1009      229833 :     fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1010             : 
    1011      229833 :     ulong   cgroup      = fd_alloc_preferred_sizeclass_cgroup( sizeclass, fd_alloc_join_cgroup_hint( join ) );
    1012      229833 :     ulong * active_slot = alloc->active_slot + sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup;
    1013             : 
    1014      229833 :     ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, fd_wksp_gaddr_fast( wksp, superblock ) );
    1015             : 
    1016             :     /* If this displaced an already active superblock, we need to push
    1017             :        the displaced superblock onto the inactive stack (note that the
    1018             :        superblock cannot be the same as the currently active superblock
    1019             :        because the superblock was not in circulation before). */
    1020             : 
    1021      229833 :     if( FD_UNLIKELY( displaced_superblock_gaddr ) )
    1022      145488 :       fd_alloc_private_inactive_stack_push( alloc->inactive_stack + sizeclass, wksp, displaced_superblock_gaddr );
    1023      229833 :     return;
    1024      229833 :   }
    1025             : 
    1026             :   /* This point, we know the superblock had some free blocks before our
    1027             :      free and thus is currently in circulation. */
    1028             : 
    1029     3668349 :   free_cnt++; /* Number of free blocks immediately after above free */
    1030             : 
    1031     3668349 :   ulong block_cnt = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt;
    1032     3668349 :   if( FD_UNLIKELY( free_cnt==block_cnt ) ) {
    1033             : 
    1034             :     /* None of the blocks were in use after the above free.  We might
    1035             :        consider freeing it to reclaim space for other sizeclasses or
    1036             :        large allocations.  But we don't mind having a few totally empty
    1037             :        superblocks in circulation for a sizeclass as this prevents
    1038             :        things like:
    1039             : 
    1040             :          addr = malloc(sz);
    1041             :          free(addr);
    1042             :          addr = malloc(sz);
    1043             :          free(addr)
    1044             :          ...
    1045             :          addr = malloc(sz);
    1046             :          free(addr)
    1047             : 
    1048             :        from repeatedly needing to invoke malloc recursively to recreate
    1049             :        superblock hierarchies that were prematurely freed.
    1050             : 
    1051             :        Regardless, since this superblock is in circulation, we can't be
    1052             :        sure it is safe to delete because something might be malloc-ing
    1053             :        from it concurrently.  Thus, we are going to keep this superblock
    1054             :        in circulation as is.
    1055             : 
    1056             :        But, since we know we have at least 1 completely empty superblock
    1057             :        in circulation now, to prevent the unbounded accumulation of
    1058             :        completely empty superblocks, we will try to get an inactive
    1059             :        superblock and, if that is empty, delete that.
    1060             : 
    1061             :        This is pretty tricky as it is possible other threads are
    1062             :        concurrently trying to pop the inactive stack to do a malloc.  If
    1063             :        we actually unmapped the memory here, such a thread could seg
    1064             :        fault if it stalls after it reads the top of the stack but before
    1065             :        it queries the top for the next_gaddr (and we'd have to use
    1066             :        another strategy).  But that is not an issue here as the
    1067             :        underlying wksp memory is still mapped post-deletion regardless.
    1068             : 
    1069             :        Likewise, though the post deletion top->next_gaddr read will get
    1070             :        a stale value in this scenario, it highly likely will not be
    1071             :        injected into the inactive_stack because the CAS will detect that
    1072             :        inactive_stack top has changed and fail.
    1073             : 
    1074             :        And, lastly, we version inactive_stack top such that, even if
    1075             :        somehow we had a thread stall in pop after reading
    1076             :        top->next_gaddr / other threads do other operations that
    1077             :        ultimately keep top the same change the value of top->next_gaddr
    1078             :        / stalled thread resumes, the version number on the stalled
    1079             :        thread will be wrong cause the CAS to fail.  (There is a
    1080             :        theoretical risk of version number reuse but the version number
    1081             :        is wide enough to make that risk zero on any practical
    1082             :        timescale.) */
    1083             : 
    1084      909828 :     fd_wksp_t * wksp                     = fd_alloc_private_wksp( alloc );
    1085      909828 :     ulong       deletion_candidate_gaddr = fd_alloc_private_inactive_stack_pop( alloc->inactive_stack + sizeclass, wksp );
    1086             : 
    1087      909828 :     if( FD_UNLIKELY( !deletion_candidate_gaddr ) ) return; /* No deletion_candidate, unclear branch prob */
    1088             : 
    1089       22581 :     fd_alloc_superblock_t * deletion_candidate = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, deletion_candidate_gaddr );
    1090             : 
    1091       22581 :     ulong deletion_candidate_free_blocks;
    1092       22581 :     FD_COMPILER_MFENCE();
    1093       22581 :     deletion_candidate_free_blocks = FD_VOLATILE_CONST( deletion_candidate->free_blocks );
    1094       22581 :     FD_COMPILER_MFENCE();
    1095             : 
    1096       22581 :     if( FD_UNLIKELY( fd_alloc_block_set_cnt( deletion_candidate_free_blocks )==block_cnt ) ) /* Candidate empty -> delete it */
    1097       14331 :       fd_alloc_free( join, deletion_candidate );
    1098        8250 :     else /* Candidate not empty -> return it to circulation */
    1099        8250 :       fd_alloc_private_inactive_stack_push( alloc->inactive_stack + sizeclass, wksp, deletion_candidate_gaddr );
    1100             : 
    1101       22581 :     return;
    1102      909828 :   }
    1103     3668349 : }
    1104             : 
    1105             : void
    1106         399 : fd_alloc_compact( fd_alloc_t * join ) {
    1107         399 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
    1108         399 :   if( FD_UNLIKELY( !alloc ) ) {
    1109           0 :     FD_LOG_WARNING(( "bad join" ));
    1110           0 :     return;
    1111           0 :   }
    1112             : 
    1113         399 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1114             : 
    1115             :   /* We scan each sizeclass (in monotonically increasing order) for
    1116             :      completely empty superblocks that thus can be freed.  This has the
    1117             :      pleasant side effect that, as smaller empty superblocks get freed,
    1118             :      it can potentially allow for any larger superblocks in which they
    1119             :      are nested to be subsequently freed.  If no other operations are
    1120             :      running concurrently, any remaining gigantic superblocks should
    1121             :      contain at least one application allocation somewhere in them. */
    1122             : 
    1123       50673 :   for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
    1124       50274 :     ulong               block_cnt      = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt;
    1125       50274 :     ulong               cgroup_cnt     = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
    1126       50274 :     fd_alloc_vgaddr_t * inactive_stack = alloc->inactive_stack + sizeclass;
    1127             : 
    1128             :     /* For each active superblock in this sizeclass */
    1129             : 
    1130      854658 :     for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1131      804384 :       ulong * active_slot      = alloc->active_slot + sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup_idx;
    1132      804384 :       ulong   superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, 0UL );
    1133      804384 :       if( !superblock_gaddr ) continue; /* application dependent branch prob */
    1134             : 
    1135        1893 :       fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1136             : 
    1137             :       /* At this point, we have atomically acquired the cgroup_idx's
    1138             :          active superblock, there is no active superblock for
    1139             :          cgroup_idx, and it has at least one free block.  Since the
    1140             :          superblock is out of circulation for malloc, nobody will malloc
    1141             :          anything from this superblock behind our back.  And if this
    1142             :          superblock is completely empty, nobody will call free on any
    1143             :          blocks in this superblock behind our back.  Thus we can free
    1144             :          this superblock safely.
    1145             : 
    1146             :          If there are some blocks still allocated, we put the superblock
    1147             :          back into circulation (we know from the above it still has at
    1148             :          least one free block, preserving the invariant).  This might
    1149             :          displace a superblock that another thread made active behind
    1150             :          our back.  We push any such superblock block onto the inactive
    1151             :          stack (it also will have at least one free block for the same
    1152             :          reasons). */
    1153             : 
    1154        1893 :       if( fd_alloc_block_set_cnt( superblock->free_blocks )==block_cnt ) fd_alloc_free( join, superblock );
    1155        1827 :       else {
    1156        1827 :         ulong displaced_superblock_gaddr = fd_alloc_private_active_slot_replace( active_slot, superblock_gaddr );
    1157        1827 :         if( FD_UNLIKELY( displaced_superblock_gaddr ) )
    1158           0 :           fd_alloc_private_inactive_stack_push( inactive_stack, wksp, displaced_superblock_gaddr );
    1159        1827 :       }
    1160        1893 :     }
    1161             : 
    1162             :     /* Drain the inactive stack for this sizeclass.  All empty
    1163             :        superblocks found will be freed (safe for the same reasons as
    1164             :        above).  All remaining superblocks will be pushed onto a local
    1165             :        stack (and every one will have at least one free block it while
    1166             :        there for the same reasons).  After the inactive stack drain, we
    1167             :        drain the local stack back into the inactive stack to get all
    1168             :        these remaining superblocks back into circulation (also safe for
    1169             :        the same reasons) and with same relative ordering (not required).
    1170             :        We technically don't need to use a lockfree push / pop for the
    1171             :        local stack but no sense in implementing a second version for
    1172             :        this mostly diagnostic / teardown oriented use case. */
    1173             : 
    1174       50274 :     fd_alloc_vgaddr_t local_stack[1];
    1175             : 
    1176       50274 :     local_stack[0] = fd_alloc_vgaddr( 0UL, 0UL );
    1177             : 
    1178       50652 :     for(;;) {
    1179       50652 :       ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( inactive_stack, wksp );
    1180       50652 :       if( !superblock_gaddr ) break; /* application dependent branch prob */
    1181         378 :       fd_alloc_superblock_t * superblock = (fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1182             : 
    1183         378 :       if( fd_alloc_block_set_cnt( superblock->free_blocks )==block_cnt ) fd_alloc_free( join, superblock );
    1184         378 :       else fd_alloc_private_inactive_stack_push( local_stack, wksp, superblock_gaddr );
    1185         378 :     }
    1186             : 
    1187       50652 :     for(;;) {
    1188       50652 :       ulong superblock_gaddr = fd_alloc_private_inactive_stack_pop( local_stack, wksp );
    1189       50652 :       if( !superblock_gaddr ) break; /* application dependent branch prob */
    1190             : 
    1191         378 :       fd_alloc_private_inactive_stack_push( inactive_stack, wksp, superblock_gaddr );
    1192         378 :     }
    1193       50274 :   }
    1194         399 : }
    1195             : 
    1196             : #include "../wksp/fd_wksp_private.h"
    1197             : 
    1198             : int
    1199         204 : fd_alloc_is_empty( fd_alloc_t * join ) {
    1200         204 :   fd_alloc_t * alloc = fd_alloc_private_join_alloc( join );
    1201         204 :   if( FD_UNLIKELY( !alloc ) ) return 0;
    1202             : 
    1203         204 :   fd_alloc_compact( join );
    1204             : 
    1205             :   /* At this point (assuming no concurrent operations on this alloc),
    1206             :      all remaining large allocations contain at least one user
    1207             :      allocation.  Thus if there are any large allocs remaining for this
    1208             :      alloc, we know the alloc is not empty.  Since the wksp alloc that
    1209             :      holds the alloc itself might use the tag the used for large
    1210             :      allocations, we handle that as well.  We do this in a brute force
    1211             :      way to avoid taking a lock (note that this calculation should
    1212             :      really only be done as non-performance critical diagnostic and then
    1213             :      on a quiescent system). */
    1214             : 
    1215         204 :   fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1216             : 
    1217         204 :   ulong alloc_lo  = fd_wksp_gaddr_fast( wksp, alloc );
    1218         204 :   ulong alloc_hi  = alloc_lo + FD_ALLOC_FOOTPRINT;
    1219         204 :   ulong alloc_tag = alloc->tag;
    1220             : 
    1221         204 :   ulong                     part_max = wksp->part_max;
    1222         204 :   fd_wksp_private_pinfo_t * pinfo    = fd_wksp_private_pinfo( wksp );
    1223             : 
    1224         204 :   ulong i;
    1225      344316 :   for( i=0UL; i<part_max; i++ ) {
    1226      344304 :     if( FD_LIKELY( pinfo[ i ].tag!=alloc_tag ) ) continue; /* optimize for no leak case */
    1227         195 :     ulong gaddr_lo = pinfo[ i ].gaddr_lo;
    1228         195 :     ulong gaddr_hi = pinfo[ i ].gaddr_hi;
    1229         195 :     if( FD_UNLIKELY( !((gaddr_lo<=alloc_lo) & (alloc_hi<=gaddr_hi)) ) ) break; /* optimize for no leak case */
    1230         195 :   }
    1231             : 
    1232         204 :   return i==part_max;
    1233         204 : }
    1234             : 
    1235             : /**********************************************************************/
    1236             : 
    1237             : #include <stdio.h>
    1238             : 
    1239        8178 : #define TRAP(x) do { int _cnt = (x); if( _cnt<0 ) { return _cnt; } cnt += _cnt; } while(0)
    1240             : 
    1241             : /* fd_alloc_superblock_fprintf pretty prints to the given stream
    1242             :    exhaustive details about the current state of the given superblock in
    1243             :    wksp at superblock_gaddr.  The superblock's parameters are given by
    1244             :    sizeclass, block_cnt, block_footprint.  Diagnostics will be accumulated
    1245             :    ctr.  Returns behavior matches fprintf return behavior (i.e. the
    1246             :    number of characters output to stream or a negative error code).
    1247             :    This is meant to be called exclusively from fd_alloc_fprintf and does
    1248             :    no error trapping of its inputs. */
    1249             : 
    1250             : static int
    1251             : fd_alloc_superblock_fprintf( fd_wksp_t * wksp,             /* non-NULL */
    1252             :                              ulong       superblock_gaddr, /* valid gaddr for wksp */
    1253             :                              ulong       sizeclass,        /* valid sizeclass */
    1254             :                              ulong       block_cnt,        /* matches sizeclass cfg */
    1255             :                              ulong       block_footprint,  /* matches sizeclass cfg */
    1256             :                              FILE *      stream,           /* non-NULL */
    1257        1488 :                              ulong *     ctr ) {           /* non-NULL, room for 2 */
    1258        1488 :   int cnt = 0;
    1259             : 
    1260             :   /* Print the block header */
    1261             : 
    1262        1488 :   fd_alloc_superblock_t * superblock = fd_wksp_laddr_fast( wksp, superblock_gaddr );
    1263             : 
    1264        1488 :   fd_alloc_block_set_t free_blocks = superblock->free_blocks;
    1265             : 
    1266        1488 :   ulong msb = fd_ulong_shift_right( free_blocks, (int)block_cnt ); /* Note: block_cnt==64 possible */
    1267        1488 :   if( FD_UNLIKELY( msb ) ) ctr[0]++;
    1268        1488 :   TRAP( fprintf( stream, "free_blocks 0x%lx (%s)\n", free_blocks, msb==0UL ? "good" : "bad" ) );
    1269             : 
    1270             :   /* For each block */
    1271             : 
    1272       41337 :   for( ulong block_idx=0UL; block_idx<block_cnt; block_idx++ ) {
    1273       39849 :     ulong gaddr_lo = superblock_gaddr + sizeof(fd_alloc_superblock_t) + block_idx*block_footprint;
    1274       39849 :     ulong gaddr_hi = gaddr_lo + block_footprint;
    1275             : 
    1276       39849 :     if( fd_alloc_block_set_test( free_blocks, block_idx ) ) { /* Free block */
    1277             : 
    1278             :     //TRAP( fprintf( stream, "\t\t\tblock %2lu: gaddr %s:%lu-%s:%lu, malloc_est -, align_est -, sz_est - (free)\n",
    1279             :     //               block_idx, wksp->name, gaddr_lo, wksp->name, gaddr_hi-1UL ) );
    1280             : 
    1281       39111 :     } else { /* Used block */
    1282             : 
    1283             :       /* Search the partition for a plausible fd_alloc_hdr_t.  This
    1284             :          process nearly identical to the one described for large
    1285             :          allocations below. */
    1286             : 
    1287        6282 :       for( ulong align_est = 1UL << fd_ulong_find_msb( block_footprint - sizeof(fd_alloc_hdr_t) );;) {
    1288        6282 :         ulong   gaddr_est = fd_ulong_align_up( gaddr_lo + sizeof(fd_alloc_hdr_t), align_est );
    1289        6282 :         uchar * laddr_est = (uchar *)fd_wksp_laddr_fast( wksp, gaddr_est );
    1290        6282 :         fd_alloc_hdr_t hdr = FD_LOAD( fd_alloc_hdr_t, laddr_est - sizeof(fd_alloc_hdr_t) );
    1291             : 
    1292        6282 :         fd_msan_unpoison( &hdr, sizeof( hdr ) );
    1293        6282 :         if( fd_alloc_hdr_sizeclass ( hdr )           ==sizeclass &&
    1294        6282 :             fd_alloc_hdr_block_idx ( hdr )           ==block_idx &&
    1295        6282 :             fd_alloc_hdr_superblock( hdr, laddr_est )==superblock ) {
    1296         738 :           ulong sz_est = block_footprint - sizeof(fd_alloc_hdr_t) - align_est + 1UL;
    1297         738 :           TRAP( fprintf( stream, "\t\t\tblock %2lu: gaddr %s:%lu-%s:%lu, malloc_est %s:%lu, align_est %lu, sz_est %lu\n",
    1298         738 :                          block_idx, wksp->name, gaddr_lo, wksp->name, gaddr_hi-1UL, wksp->name, gaddr_est, align_est, sz_est ) );
    1299         738 :           ctr[1]++;
    1300         738 :           break;
    1301         738 :         }
    1302        5544 :         fd_msan_poison( &hdr, sizeof( hdr ) );
    1303             : 
    1304        5544 :         align_est >>= 1;
    1305        5544 :         if( FD_UNLIKELY( !align_est ) ) {
    1306           0 :           TRAP( fprintf( stream, "\t\t\tblock %2lu: gaddr %s:%lu-%s:%lu, malloc_est -, align est -, sz_est - (bad)\n",
    1307           0 :                          block_idx, wksp->name, gaddr_lo, wksp->name, gaddr_hi-1UL ) );
    1308           0 :           ctr[0]++;
    1309           0 :           break;
    1310           0 :         }
    1311        5544 :       }
    1312         738 :     }
    1313       39849 :   }
    1314             : 
    1315        1488 :   return cnt;
    1316        1488 : }
    1317             : 
    1318             : /* fd_alloc_fprintf pretty prints to the given stream exhaustive details
    1319             :    about the current state of the fd_alloc corresponding to the current
    1320             :    local join.  As this function is typically for debugging,
    1321             :    end-of-program diagnostics, etc, this function assumes there are no
    1322             :    concurrent operations on the fd_alloc while running.  Note also it
    1323             :    might not be able to find all small allocations and determine precise
    1324             :    details about allocations in general.  It will however be able to
    1325             :    find all large allocations assuming the user tagged the structure
    1326             :    properly (and that includes finding all the gigantic superblocks in
    1327             :    which all individual small allocations are contained).  Return
    1328             :    behavior matches fprintf return behavior (i.e. the number of
    1329             :    characters printed to stream or a negative error code). */
    1330             : 
    1331             : int
    1332             : fd_alloc_fprintf( fd_alloc_t * join,
    1333          12 :                   FILE *       stream ) {
    1334          12 :   if( FD_UNLIKELY( !stream ) ) return 0; /* NULL stream, can't print anything */
    1335             : 
    1336          12 :   int cnt = 0;
    1337             : 
    1338          12 :   ulong ctr[6];
    1339          12 :   ctr[0] = 0UL; /* errors detected */
    1340          12 :   ctr[1] = 0UL; /* small alloc found */
    1341          12 :   ctr[2] = 0UL; /* wksp partitions used */
    1342          12 :   ctr[3] = 0UL; /* wksp bytes used */
    1343          12 :   ctr[4] = 0UL; /* wksp partitions used for large alloc */
    1344          12 :   ctr[5] = 0UL; /* wksp bytes used for large alloc */
    1345             : 
    1346          12 :   fd_alloc_t * alloc       = fd_alloc_private_join_alloc( join );
    1347          12 :   ulong        cgroup_hint = fd_alloc_join_cgroup_hint  ( join );
    1348             : 
    1349          12 :   if( FD_UNLIKELY( !alloc ) ) { /* NULL join passed */
    1350             : 
    1351           0 :     TRAP( fprintf( stream, "alloc: gaddr -, join_cgroup_hint %lu, magic 0x0 (bad)\n", cgroup_hint ) );
    1352           0 :     ctr[0]++;
    1353             : 
    1354          12 :   } else { /* Normal join */
    1355             : 
    1356             :     /* Count the space used by alloc metadata. */
    1357             : 
    1358          12 :     ctr[2] += 1UL;
    1359          12 :     ctr[3] += FD_ALLOC_FOOTPRINT;
    1360             : 
    1361          12 :     fd_wksp_t * wksp = fd_alloc_private_wksp( alloc );
    1362             : 
    1363             :     /* Print the summary header */
    1364             : 
    1365          12 :     TRAP( fprintf( stream, "alloc: gaddr %s:%lu, join_cgroup_hint %lu, magic 0x%lx (%s)\n",
    1366          12 :                    wksp->name, fd_wksp_gaddr_fast( wksp, alloc ), cgroup_hint,
    1367          12 :                    alloc->magic, alloc->magic==FD_ALLOC_MAGIC ? "good" : "bad" ) );
    1368          12 :     if( FD_UNLIKELY( alloc->magic!=FD_ALLOC_MAGIC ) ) ctr[0]++;
    1369             : 
    1370             :     /* Print known details about each sizeclass */
    1371             : 
    1372          12 :     ulong block_footprint = 0UL;
    1373        1524 :     for( ulong sizeclass=0UL; sizeclass<FD_ALLOC_SIZECLASS_CNT; sizeclass++ ) {
    1374        1512 :       ulong block_footprint_prev = block_footprint;
    1375        1512 :       /**/  block_footprint      = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_footprint;
    1376             : 
    1377        1512 :       ulong superblock_footprint = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].superblock_footprint;
    1378        1512 :       ulong block_cnt            = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].block_cnt;
    1379        1512 :       ulong cgroup_cnt           = (ulong)fd_alloc_sizeclass_cfg[ sizeclass ].cgroup_mask + 1UL;
    1380             : 
    1381        1512 :       fd_alloc_vgaddr_t inactive_stack = alloc->inactive_stack[ sizeclass ];
    1382             : 
    1383        1512 :       ulong inactive_stack_ver   = (ulong)fd_alloc_vgaddr_ver( inactive_stack );
    1384        1512 :       ulong inactive_stack_gaddr = (ulong)fd_alloc_vgaddr_off( inactive_stack );
    1385             : 
    1386             :       /* Omit sizeclasses that have no superblocks in circulation */
    1387             : 
    1388        1512 :       int do_print = !!inactive_stack_gaddr;
    1389        1512 :       if( !do_print ) {
    1390        2076 :         for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1391        2040 :           if( alloc->active_slot[ sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup_idx ] ) {
    1392        1464 :             do_print = 1;
    1393        1464 :             break;
    1394        1464 :           }
    1395        2040 :         }
    1396        1500 :         if( !do_print ) continue;
    1397        1500 :       }
    1398             : 
    1399             :       /* Print size class header */
    1400             : 
    1401        1476 :       TRAP( fprintf( stream,
    1402        1476 :                      "\tsizeclass %lu: superblock_footprint %lu, block_footprint %lu-%lu, block_cnt %lu, cgroup_cnt %lu\n",
    1403        1476 :                      sizeclass, superblock_footprint, block_footprint_prev+1UL, block_footprint, block_cnt, cgroup_cnt ) );
    1404             : 
    1405             :       /* Print inactive stack top */
    1406             : 
    1407        1476 :       TRAP( fprintf( stream, "\t\tinactive_stack: gaddr %s:%lu, version %lu\n",
    1408        1476 :                      wksp->name, inactive_stack_gaddr, inactive_stack_ver ) );
    1409             : 
    1410             :       /* Print active superblock details */
    1411             : 
    1412        1476 :       ulong superblock_gaddr;
    1413             : 
    1414       25092 :       for( ulong cgroup_idx=0UL; cgroup_idx<cgroup_cnt; cgroup_idx++ ) {
    1415       23616 :         superblock_gaddr = alloc->active_slot[ sizeclass + FD_ALLOC_SIZECLASS_CNT*cgroup_idx ];
    1416       23616 :         if( !superblock_gaddr ) {
    1417             :         //TRAP( fprintf( stream, "\t\tcgroup 2lu active superblock -, next -\n", cgroup_idx ) );
    1418       22143 :           continue;
    1419       22143 :         }
    1420        1473 :         ulong next_gaddr = ((fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr))->next_gaddr;
    1421        1473 :         TRAP( fprintf( stream, "\t\tsuperblock: cgroup_idx %lu, gaddr %s:%lu, next %s:%lu (ignored), ",
    1422        1473 :                        cgroup_idx, wksp->name, superblock_gaddr, wksp->name, next_gaddr ) );
    1423        1473 :         TRAP( fd_alloc_superblock_fprintf( wksp, superblock_gaddr, sizeclass, block_cnt, block_footprint, stream, ctr ) );
    1424        1473 :       }
    1425             : 
    1426             :       /* Print inactive superblock details */
    1427             : 
    1428        1476 :       superblock_gaddr = inactive_stack_gaddr;
    1429        1491 :       while( superblock_gaddr ) {
    1430          15 :         ulong next_gaddr = ((fd_alloc_superblock_t *)fd_wksp_laddr_fast( wksp, superblock_gaddr))->next_gaddr;
    1431          15 :         TRAP( fprintf( stream, "\t\tsuperblock: cgroup_idx -, gaddr %s:%lu, next %s:%lu, ",
    1432          15 :                        wksp->name, superblock_gaddr, wksp->name, next_gaddr ) );
    1433          15 :         TRAP( fd_alloc_superblock_fprintf( wksp, superblock_gaddr, sizeclass, block_cnt, block_footprint, stream, ctr ) );
    1434          15 :         superblock_gaddr = next_gaddr;
    1435          15 :       }
    1436        1476 :     }
    1437             : 
    1438             :     /* Scan the wksp partition table for partitions that match this
    1439             :        allocation tag.  Like the is_empty diagnostic, we do this in a
    1440             :        brute force way that is not algo efficient to avoid taking a
    1441             :        lock. */
    1442             : 
    1443          12 :     ulong wksp_lo   = wksp->gaddr_lo;
    1444          12 :     ulong wksp_hi   = wksp->gaddr_hi;
    1445             : 
    1446          12 :     ulong alloc_lo  = fd_wksp_gaddr_fast( wksp, alloc );
    1447          12 :     ulong alloc_hi  = alloc_lo + FD_ALLOC_FOOTPRINT;
    1448          12 :     ulong alloc_tag = alloc->tag;
    1449             : 
    1450          12 :     ulong                     part_max = wksp->part_max;
    1451          12 :     fd_wksp_private_pinfo_t * pinfo    = fd_wksp_private_pinfo( wksp );
    1452             : 
    1453          12 :     ulong i;
    1454          48 :     for( i=0UL; i<part_max; i++ ) {
    1455          48 :       if( pinfo[ i ].tag!=alloc_tag ) continue; /* skip ones that don't match */
    1456          12 :       ulong gaddr_lo = pinfo[ i ].gaddr_lo;
    1457          12 :       ulong gaddr_hi = pinfo[ i ].gaddr_hi;
    1458          12 :       if( FD_UNLIKELY( (wksp_lo<=gaddr_lo) & (gaddr_lo<gaddr_hi) & (gaddr_hi<=wksp_hi) ) ) break; /* malformed */
    1459             : 
    1460             :       /* If the user used the same tag for both the alloc metadata and the
    1461             :          alloc allocations, skip the metadata partition */
    1462             : 
    1463           0 :       if( FD_UNLIKELY( (gaddr_lo<=alloc_lo) & (alloc_hi<=gaddr_lo) ) ) continue; /* skip partition containing metadata */
    1464             : 
    1465           0 :       ulong part_footprint = gaddr_hi - gaddr_lo;
    1466             : 
    1467           0 :       if( FD_UNLIKELY( part_footprint<=sizeof(fd_alloc_hdr_t) ) ) continue; /* skip obviously not an allocation */
    1468             : 
    1469             :       /* Search the partition for a plausible fd_alloc_hdr_t.  There
    1470             :          will be at least one if the partition isn't bad.  We scan
    1471             :          potential alignments in reverse order such that noise in the
    1472             :          alignment padding from older and less aligned large allocs will
    1473             :          not trigger the detection logic.  It is still possible for this
    1474             :          logic to spuriously fire if the user just happened to have some
    1475             :          data that looked like a suitable header (hence these are
    1476             :          estimates).  The estimates could be improved if we clear zero
    1477             :          padding before the headers as noted above at a greater time
    1478             :          overhead in fd_alloc and scanned alignments forward.  Once we
    1479             :          have a plausible location and alignment, we can compute bounds
    1480             :          to how large a size was used.  We use the upper bound the size
    1481             :          estimate for simplicity (it would take a lot more space and
    1482             :          time overhead in normal operation to track this explicitly). */
    1483             : 
    1484           0 :       for( ulong align_est = 1UL << fd_ulong_find_msb( part_footprint-sizeof(fd_alloc_hdr_t) );;) {
    1485             : 
    1486             :         /* Look at a potential header location */
    1487             : 
    1488           0 :         ulong   gaddr_est = fd_ulong_align_up( gaddr_lo + sizeof(fd_alloc_hdr_t), align_est );
    1489           0 :         uchar * laddr_est = (uchar *)fd_wksp_laddr_fast( wksp, gaddr_est );
    1490           0 :         fd_alloc_hdr_t hdr = FD_LOAD( fd_alloc_hdr_t, laddr_est - sizeof(fd_alloc_hdr_t) );
    1491             : 
    1492             :         /* If it matches what a header at this location should be,
    1493             :            print out the estimated allocation details. */
    1494             : 
    1495           0 :         if( fd_alloc_hdr_is_large( hdr ) ) {
    1496           0 :           int is_superblock = fd_alloc_hdr_large_is_superblock( hdr );
    1497             : 
    1498           0 :           ulong sz_est = part_footprint - sizeof(fd_alloc_hdr_t) - align_est + 1UL;
    1499           0 :           TRAP( fprintf( stream, "\tlarge: gaddr %s:%lu-%s:%lu, malloc_est %s:%lu, align_est %lu, sz_est %lu %s\n",
    1500           0 :                          wksp->name, gaddr_lo, wksp->name, gaddr_hi-1UL, wksp->name, gaddr_est, align_est, sz_est,
    1501           0 :                          is_superblock ? "(superblock)" : "(large)" ) );
    1502           0 :           ctr[2]++;
    1503           0 :           ctr[3] += part_footprint;
    1504           0 :           ctr[4] += fd_ulong_if( is_superblock, 0UL, 1UL            );
    1505           0 :           ctr[5] += fd_ulong_if( is_superblock, 0UL, part_footprint );
    1506             : 
    1507           0 :           break;
    1508           0 :         }
    1509             : 
    1510             :         /* Nope ... try the next.  If no more potential locations, the
    1511             :            partition is corrupt. */
    1512             : 
    1513           0 :         align_est >>= 1;
    1514           0 :         if( FD_UNLIKELY( !align_est ) ) {
    1515           0 :           TRAP( fprintf( stream, "\tlarge: gaddr %s:%lu-%s:%lu, malloc_est -, align_est -, sz_est - (bad)\n",
    1516           0 :                          wksp->name, gaddr_lo, wksp->name, gaddr_hi-1UL ) );
    1517           0 :           ctr[0]++;
    1518           0 :           break;
    1519           0 :         }
    1520           0 :       }
    1521           0 :     }
    1522          12 :   }
    1523             : 
    1524             :   /* Print summary statistics */
    1525             : 
    1526          12 :   TRAP( fprintf( stream,
    1527          12 :                  "\terrors detected       %21lu\n"
    1528          12 :                  "\tsmall alloc found     %21lu\n"
    1529          12 :                  "\twksp part cnt         %21lu\n"
    1530          12 :                  "\twksp used             %21lu\n"
    1531          12 :                  "\tlarge alloc cnt       %21lu\n"
    1532          12 :                  "\tlarge alloc wksp used %21lu\n",
    1533          12 :                  ctr[0], ctr[1], ctr[2], ctr[3], ctr[4], ctr[5] ) );
    1534             : 
    1535          12 :   return cnt;
    1536          12 : }
    1537             : 
    1538             : /* Virtual function table
    1539             :    TODO type pun functions instead of using virtual wrappers? */
    1540             : 
    1541             : static void *
    1542             : fd_alloc_malloc_virtual( void * self,
    1543             :                          ulong  align,
    1544           0 :                          ulong  sz ) {
    1545           0 :   return fd_alloc_malloc( (fd_alloc_t *)self, align, sz );
    1546           0 : }
    1547             : 
    1548             : static void
    1549             : fd_alloc_free_virtual( void * self,
    1550           0 :                        void * addr ) {
    1551           0 :   fd_alloc_free( (fd_alloc_t *)self, addr );
    1552           0 : }
    1553             : 
    1554             : const fd_valloc_vtable_t
    1555             : fd_alloc_vtable = {
    1556             :   .malloc = fd_alloc_malloc_virtual,
    1557             :   .free   = fd_alloc_free_virtual
    1558             : };
    1559             : 
    1560             : #undef TRAP

Generated by: LCOV version 1.14