LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_user.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 350 396 88.4 %
Date: 2026-09-17 04:28:31 Functions: 16 16 100.0 %

          Line data    Source code
       1             : #include "fd_prog_load.h"
       2             : #include "fd_progcache_user.h"
       3             : #include "fd_progcache_reclaim.h"
       4             : #include "fd_progcache_clock.h"
       5             : #include "../../util/racesan/fd_racesan_target.h"
       6             : #include "../../disco/metrics/generated/fd_metrics_enums.h"
       7             : 
       8             : /* FD_PROGCACHE_METRICS_WRITE copies CLASS_CNT-sized arrays through the metrics
       9             :    enum, so a class added without regenerating the enum would read past them. */
      10             : 
      11             : FD_STATIC_ASSERT( FD_METRICS_ENUM_PROGCACHE_CLASS_CNT==FD_PROGCACHE_CACHE_CLASS_CNT,
      12             :                   progcache_metrics_class_cnt );
      13             : 
      14             : /* Counts class c in a per-class metrics array; an out-of-range class goes uncounted. */
      15             : 
      16             : static inline void
      17             : progcache_metric_per_class( ulong * per_class,
      18      919970 :                             ulong   c ) {
      19      919970 :   if( FD_LIKELY( c<FD_PROGCACHE_CACHE_CLASS_CNT ) ) per_class[ c ]++;
      20      919970 : }
      21             : 
      22             : FD_TL fd_progcache_metrics_t fd_progcache_metrics_default;
      23             : 
      24             : fd_progcache_t *
      25             : fd_progcache_join( fd_progcache_t *       cache,
      26             :                    fd_progcache_shmem_t * shmem,
      27             :                    uchar *                scratch,
      28         252 :                    ulong                  scratch_sz ) {
      29         252 :   if( FD_UNLIKELY( !cache ) ) {
      30           0 :     FD_LOG_WARNING(( "NULL cache" ));
      31           0 :     return NULL;
      32           0 :   }
      33         252 :   if( FD_LIKELY( scratch_sz ) ) {
      34         252 :     if( FD_UNLIKELY( !scratch ) ) {
      35           3 :       FD_LOG_WARNING(( "NULL scratch" ));
      36           3 :       return NULL;
      37           3 :     }
      38         249 :     if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)scratch, FD_PROGCACHE_SCRATCH_ALIGN ) ) ) {
      39           3 :       FD_LOG_WARNING(( "misaligned scratch" ));
      40           3 :       return NULL;
      41           3 :     }
      42         249 :   }
      43         246 :   memset( cache, 0, sizeof(fd_progcache_t) );
      44         246 :   if( FD_UNLIKELY( !fd_progcache_shmem_join( cache->join, shmem ) ) ) return NULL;
      45             : 
      46         246 :   cache->metrics    = &fd_progcache_metrics_default;
      47         246 :   cache->scratch    = scratch;
      48         246 :   cache->scratch_sz = scratch_sz;
      49             : 
      50         246 :   return cache;
      51         246 : }
      52             : 
      53             : void *
      54             : fd_progcache_leave( fd_progcache_t *        cache,
      55         246 :                     fd_progcache_shmem_t ** opt_shmem ) {
      56         246 :   if( FD_UNLIKELY( !cache ) ) {
      57           0 :     FD_LOG_WARNING(( "NULL cache" ));
      58           0 :     return NULL;
      59           0 :   }
      60             : 
      61         246 :   fd_prog_reclaim_work( cache->join );
      62         246 :   if( FD_UNLIKELY( !fd_progcache_shmem_leave( cache->join, opt_shmem ) ) ) return NULL;
      63         246 :   cache->scratch    = NULL;
      64         246 :   cache->scratch_sz = 0UL;
      65         246 :   return cache;
      66         246 : }
      67             : 
      68             : /* fd_progcache_load_fork pivots the progcache object to the selected fork
      69             :    (identified by tip XID): populates cache->lineage with the fork's XIDs,
      70             :    newest to oldest.  Cache lookups only respect records on that lineage.
      71             : 
      72             :    load_fork_slow and fd_progcache_query below are internal but not static:
      73             :    the test suite composes them into a lookup that does not fill (see
      74             :    test_progcache_common.c).  They are absent from fd_progcache_user.h. */
      75             : 
      76             : void
      77             : fd_progcache_load_fork_slow( fd_progcache_t *       cache,
      78         552 :                              fd_progcache_fork_id_t fork_id ) {
      79         552 :   fd_progcache_lineage_t *    lineage = cache->lineage;
      80         552 :   fd_progcache_join_t const * ljoin   = cache->join;
      81         552 :   fd_rwlock_read( &ljoin->shmem->txn.rwlock );
      82         552 :   lineage->fork_depth  = 0UL;
      83         552 :   lineage->tip_txn_idx = ULONG_MAX;
      84         552 :   lineage->root = __atomic_load_n( &ljoin->shmem->txn.root, memory_order_acquire );
      85             : 
      86         552 :   ulong txn_max = fd_prog_txnp_max( ljoin->txn.pool );
      87         552 :   ulong i;
      88         705 :   for( i=0UL;; i++ ) {
      89         705 :     if( FD_UNLIKELY( i>=FD_PROGCACHE_DEPTH_MAX ) ) {
      90           0 :       FD_LOG_CRIT(( "fd_progcache_load_fork: fork depth exceeded max of %lu", (ulong)FD_PROGCACHE_DEPTH_MAX ));
      91           0 :     }
      92         705 :     uint next_idx = (uint)fd_prog_txnm_idx_query_const( ljoin->txn.map, &fork_id, UINT_MAX, ljoin->txn.pool );
      93         705 :     if( FD_UNLIKELY( next_idx==UINT_MAX ) ) break;
      94         702 :     if( FD_UNLIKELY( (ulong)next_idx >= txn_max ) )
      95           0 :       FD_LOG_CRIT(( "progcache: corruption detected (load_fork txn_idx=%u txn_max=%lu)", next_idx, txn_max ));
      96         702 :     fd_progcache_txn_t * candidate = &ljoin->txn.pool[ next_idx ];
      97             : 
      98         702 :     uint parent_idx = candidate->parent_idx;
      99         702 :     FD_TEST( parent_idx!=next_idx );
     100         702 :     lineage->fork[ i ] = fork_id;
     101         702 :     if( FD_LIKELY( !i ) ) lineage->tip_txn_idx = next_idx;
     102         702 :     if( parent_idx==UINT_MAX ) {
     103         549 :       i++;
     104         549 :       break;
     105         549 :     }
     106         153 :     if( FD_UNLIKELY( (ulong)parent_idx >= txn_max ) )
     107           0 :       FD_LOG_CRIT(( "progcache: corruption detected (load_fork parent_idx=%u txn_max=%lu)", parent_idx, txn_max ));
     108         153 :     fork_id = ljoin->txn.pool[ parent_idx ].xid;
     109         153 :   }
     110             : 
     111         552 :   lineage->fork_depth = i;
     112             : 
     113         552 :   fd_rwlock_unread( &ljoin->shmem->txn.rwlock );
     114             : 
     115         552 :   lineage->root = __atomic_load_n( &ljoin->shmem->txn.root, memory_order_acquire );
     116         552 : }
     117             : 
     118             : static inline void
     119             : fd_progcache_load_fork( fd_progcache_t *       cache,
     120      919982 :                         fd_progcache_fork_id_t fork_id ) {
     121             :   /* Skip if already on the correct fork */
     122      919982 :   fd_progcache_lineage_t * lineage = cache->lineage;
     123      919982 :   if( FD_LIKELY( (!!lineage->fork_depth) & (lineage->fork[ 0 ]==fork_id ) ) ) return;
     124         423 :   fd_progcache_load_fork_slow( cache, fork_id ); /* switch fork */
     125         423 : }
     126             : 
     127             : /* fd_prog_wait_if_loading waits out a peer's in-flight load of rec, so the caller
     128             :    only ever sees a record whose program is in.  Returns rec. */
     129             : 
     130             : static inline fd_progcache_rec_t *
     131             : fd_prog_wait_if_loading( fd_progcache_t *     cache,
     132          96 :                          fd_progcache_rec_t * rec ) {
     133          96 :   if( FD_UNLIKELY( fd_prog_state_is_loading( rec ) ) ) {
     134           0 :     cache->metrics->hit_loading_cnt++;
     135           0 :     while( fd_prog_state_is_loading( rec ) ) {
     136           0 :       fd_racesan_hook( "prog_wait_if_loading:spin" );
     137           0 :       FD_SPIN_PAUSE();
     138           0 :     }
     139           0 :   }
     140          96 :   return rec;
     141          96 : }
     142             : 
     143             : /* fd_progcache_search_chain walks one chain for an exact (key, feature_slot,
     144             :    deploy_slot) match whose fork is on the current lineage. */
     145             : 
     146             : static int
     147             : fd_progcache_search_chain( fd_progcache_t const * cache,
     148             :                            ulong                  chain_idx,
     149             :                            fd_pubkey_t const *    key,
     150             :                            ulong                  feature_slot,
     151             :                            ulong                  deploy_slot,
     152     2253788 :                            fd_progcache_rec_t **  out_rec ) { /* read locked */
     153     2253788 :   *out_rec = NULL;
     154             : 
     155     2253788 :   fd_progcache_join_t const *                ljoin     = cache->join;
     156     2253788 :   fd_progcache_lineage_t const *             lineage   = cache->lineage;
     157     2253788 :   fd_prog_recm_shmem_t *                     shmap     = ljoin->rec.map->map;
     158     2253788 :   fd_prog_recm_shmem_private_chain_t const * chain_tbl = fd_prog_recm_shmem_private_chain_const( shmap, 0UL );
     159     2253788 :   fd_prog_recm_shmem_private_chain_t const * chain     = chain_tbl + chain_idx;
     160     2253788 :   fd_progcache_rec_t *                       rec_tbl   = ljoin->rec.ele;
     161     2253788 :   ulong                                      rec_max   = ljoin->rec.max;
     162     2253788 :   ulong                                      ver_cnt   = FD_VOLATILE_CONST( chain->ver_cnt );
     163             : 
     164             :   /* Start a speculative transaction for the chain containing revisions
     165             :      of the program cache key we are looking for. */
     166     2253788 :   ulong cnt = fd_prog_recm_private_vcnt_cnt( ver_cnt );
     167     2253788 :   if( FD_UNLIKELY( fd_prog_recm_private_vcnt_ver( ver_cnt )&1 ) ) {
     168     1333686 :     return FD_MAP_ERR_AGAIN; /* chain is locked */
     169     1333686 :   }
     170      920102 :   FD_COMPILER_MFENCE();
     171      920102 :   fd_racesan_hook( "prog_search_chain:post_ver_cnt" );
     172      920102 :   uint ele_idx = chain->head_cidx;
     173             : 
     174             :   /* Walk the map chain, remember the best entry */
     175      920102 :   fd_progcache_rec_t * best = NULL;
     176     1284304 :   for( ulong i=0UL; i<cnt; i++, ele_idx=FD_VOLATILE_CONST( rec_tbl[ ele_idx ].map_next ) ) {
     177      364298 :     if( FD_UNLIKELY( (ulong)ele_idx >= rec_max ) ) return FD_MAP_ERR_AGAIN;
     178      364298 :     fd_progcache_rec_t * rec = &rec_tbl[ ele_idx ];
     179             : 
     180      364298 :     if( FD_UNLIKELY( ( !fd_pubkey_eq( &rec->pair.prog, key ) ) |
     181      364298 :                      ( rec->feature_slot != feature_slot   ) |
     182      364298 :                      ( rec->deploy_slot  != deploy_slot    ) ) ) {
     183      364202 :       continue;
     184      364202 :     }
     185             : 
     186          96 :     fd_progcache_fork_id_t rec_fork_id = __atomic_load_n( &rec->pair.xid, memory_order_relaxed );
     187          96 :     if( FD_UNLIKELY( !fd_progcache_lineage_has_xid( lineage, rec_fork_id ) ) ) continue;
     188             : 
     189          96 :     if( FD_UNLIKELY( rec->map_next==ele_idx ) ) return FD_MAP_ERR_AGAIN;
     190          96 :     if( FD_UNLIKELY( rec->map_next!=UINT_MAX && rec->map_next>=rec_max ) ) return FD_MAP_ERR_AGAIN;
     191          96 :     best = rec;
     192          96 :     break;
     193          96 :   }
     194      920102 :   fd_racesan_hook( "prog_search_chain:pre_tryread" );
     195      920102 :   if( best && FD_UNLIKELY( !fd_rwlock_tryread( &best->lock ) ) ) {
     196           0 :     return FD_MAP_ERR_AGAIN;
     197           0 :   }
     198      920102 :   fd_racesan_hook( "prog_search_chain:post_tryread" );
     199             : 
     200             :   /* Retry if we were overrun */
     201      920102 :   if( FD_UNLIKELY( FD_VOLATILE_CONST( chain->ver_cnt )!=ver_cnt ) ) {
     202           3 :     if( best ) fd_rwlock_unread( &best->lock );
     203           3 :     return FD_MAP_ERR_AGAIN;
     204           3 :   }
     205             : 
     206      920099 :   *out_rec = best;
     207      920099 :   return FD_MAP_SUCCESS;
     208      920102 : }
     209             : 
     210             : fd_progcache_rec_t * /* read locked */
     211             : fd_progcache_query( fd_progcache_t *    cache,
     212             :                     fd_pubkey_t const * key,
     213             :                     ulong               feature_slot,
     214      920099 :                     ulong               deploy_slot ) {
     215             :   /* Hash key to chain */
     216      920099 :   fd_prog_recm_t const * rec_map = cache->join->rec.map;
     217      920099 :   ulong hash      = fd_progcache_rec_key_hash( key, rec_map->map->seed );
     218      920099 :   ulong chain_idx = (hash & (rec_map->map->chain_cnt-1UL) );
     219             : 
     220             :   /* Traverse chain for candidate */
     221      920099 :   fd_progcache_rec_t * rec = NULL;
     222     2253788 :   for(;;) {
     223     2253788 :     int err = fd_progcache_search_chain( cache, chain_idx, key, feature_slot, deploy_slot, &rec );
     224     2253788 :     if( FD_LIKELY( err==FD_MAP_SUCCESS ) ) break;
     225     1333689 :     fd_racesan_hook( "prog_query:retry" );
     226     1333689 :     FD_SPIN_PAUSE();
     227             :     /* FIXME backoff */
     228     1333689 :   }
     229      920099 :   if( FD_LIKELY( !rec ) ) return NULL; /* Program not found, need an insert */
     230             : 
     231          96 :   return fd_prog_wait_if_loading( cache, rec );
     232      920099 : }
     233             : 
     234             : static void
     235             : fd_progcache_rec_push_tail( fd_progcache_rec_t * rec_pool,
     236             :                             fd_progcache_rec_t * rec,
     237             :                             uint *               rec_head_idx, /* write locked (txn) */
     238             :                             uint *               rec_tail_idx,
     239      917026 :                             ulong                rec_max ) {
     240      917026 :   uint rec_idx      = (uint)( rec - rec_pool );
     241      917026 :   uint rec_prev_idx = *rec_tail_idx;
     242             : 
     243      917026 :   if( FD_UNLIKELY( (ulong)rec_idx >= rec_max ) )
     244           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_idx=%u rec_max=%lu)", rec_idx, rec_max ));
     245      917026 :   if( FD_UNLIKELY( rec_prev_idx!=UINT_MAX && (ulong)rec_prev_idx >= rec_max ) )
     246           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push_tail rec_prev_idx=%u rec_max=%lu)", rec_prev_idx, rec_max ));
     247             : 
     248      917026 :   rec->prev_idx = rec_prev_idx;
     249      917026 :   rec->next_idx = UINT_MAX;
     250             : 
     251      917026 :   if( rec_prev_idx==UINT_MAX ) {
     252        2820 :     *rec_head_idx = rec_idx;
     253      914206 :   } else {
     254      914206 :     rec_pool[ rec_prev_idx ].next_idx = rec_idx;
     255      914206 :   }
     256      917026 :   *rec_tail_idx = rec_idx;
     257      917026 : }
     258             : 
     259             : /* Publishes rec (complete, read-locked) under the txn.  Returns the record now
     260             :    serving this key: rec, or the winner's read-locked record if another tile won
     261             :    the race, or NULL if a mapped record has this key with different
     262             :    feature/deploy slots. */
     263             : 
     264             : __attribute__((warn_unused_result))
     265             : static fd_progcache_rec_t *
     266             : fd_progcache_push( fd_progcache_join_t * cache,
     267             :                    fd_progcache_txn_t *  txn, /* write locked */
     268             :                    fd_progcache_rec_t *  rec,
     269      917026 :                    void const *          prog_addr ) {
     270      917026 :   FD_TEST( fd_prog_state_is_loading( rec ) );
     271             : 
     272             :   /* Determine record's xid-key pair */
     273             : 
     274      917026 :   rec->prev_idx = UINT_MAX;
     275      917026 :   rec->next_idx = UINT_MAX;
     276      917026 :   memcpy( &rec->pair.prog, prog_addr, 32UL );
     277      917026 :   if( FD_UNLIKELY( !txn ) ) FD_LOG_CRIT(( "NULL txn" ));
     278      917026 :   __atomic_store_n( &rec->pair.xid, txn->xid, memory_order_relaxed );
     279             : 
     280             :   /* Lock rec_map chain, entering critical section */
     281             : 
     282      917026 :   struct {
     283      917026 :     fd_prog_recm_txn_t txn[1];
     284      917026 :     fd_prog_recm_txn_private_info_t info[1];
     285      917026 :   } _map_txn;
     286      917026 :   fd_prog_recm_txn_t * map_txn = fd_prog_recm_txn_init( _map_txn.txn, cache->rec.map, 1UL );
     287      917026 :   fd_prog_recm_txn_add( map_txn, &rec->pair, 1 );
     288      917026 :   int txn_err = fd_prog_recm_txn_try( map_txn, FD_MAP_FLAG_BLOCKING );
     289      917026 :   if( FD_UNLIKELY( txn_err!=FD_MAP_SUCCESS ) ) {
     290           0 :     FD_LOG_CRIT(( "Failed to insert progcache record: cannot lock rec map chain: %i-%s", txn_err, fd_map_strerror( txn_err ) ));
     291           0 :   }
     292      917026 :   fd_racesan_hook( "prog_push:post_chain_lock" );
     293             : 
     294             :   /* Check if record exists */
     295             : 
     296      917026 :   fd_prog_recm_query_t query[1];
     297      917026 :   int query_err = fd_prog_recm_txn_query( cache->rec.map, &rec->pair, NULL, query, 0 );
     298      917026 :   if( FD_UNLIKELY( query_err==FD_MAP_SUCCESS ) ) {
     299             :     /* Duplicate: adopt the winner.  Requires that a mapped record is never
     300             :        write-locked. */
     301           0 :     fd_progcache_rec_t * winner = query->ele;
     302           0 :     int match = ( winner->feature_slot==rec->feature_slot ) & ( winner->deploy_slot==rec->deploy_slot );
     303           0 :     if( FD_LIKELY( match ) ) fd_rwlock_read( &winner->lock );
     304           0 :     fd_prog_recm_txn_test( map_txn );
     305           0 :     fd_prog_recm_txn_fini( map_txn );
     306           0 :     return match ? winner : NULL;
     307      917026 :   } else if( FD_UNLIKELY( query_err!=FD_MAP_ERR_KEY ) ) {
     308           0 :     FD_LOG_CRIT(( "fd_prog_recm_txn_query failed: %i-%s", query_err, fd_map_strerror( query_err ) ));
     309           0 :   }
     310             : 
     311      917026 :   ulong rec_max = cache->rec.max;
     312             : 
     313             :   /* Insert new record */
     314             : 
     315             :   /* Link record into the transaction's record list.  Ownership is established
     316             :      before the record becomes findable, so a mapped record has an owner unless
     317             :      rooting deliberately detached it. */
     318             : 
     319      917026 :   fd_progcache_rec_push_tail( cache->rec.ele,
     320      917026 :       rec,
     321      917026 :       &txn->rec_head_idx,
     322      917026 :       &txn->rec_tail_idx,
     323      917026 :       rec_max );
     324      917026 :   uint txn_idx_computed = (uint)( txn - cache->txn.pool );
     325      917026 :   ulong txn_max = fd_prog_txnp_max( cache->txn.pool );
     326      917026 :   if( FD_UNLIKELY( (ulong)txn_idx_computed >= txn_max ) )
     327           0 :     FD_LOG_CRIT(( "progcache: corruption detected (push txn_idx=%u txn_max=%lu)", txn_idx_computed, txn_max ));
     328      917026 :   atomic_store_explicit( &rec->txn_idx, txn_idx_computed, memory_order_release );
     329             : 
     330      917026 :   int insert_err = fd_prog_recm_txn_insert( cache->rec.map, rec );
     331      917026 :   if( FD_UNLIKELY( insert_err!=FD_MAP_SUCCESS ) ) {
     332           0 :     FD_LOG_CRIT(( "fd_prog_recm_txn_insert failed: %i-%s", insert_err, fd_map_strerror( insert_err ) ));
     333           0 :   }
     334      917026 :   fd_racesan_hook( "prog_push:post_map_insert" );
     335             : 
     336             :   /* Finish rec_map transaction */
     337             : 
     338      917026 :   int test_err = fd_prog_recm_txn_test( map_txn );
     339      917026 :   if( FD_UNLIKELY( test_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_prog_recm_txn_test failed: %i-%s", test_err, fd_map_strerror( test_err ) ));
     340      917026 :   fd_prog_recm_txn_fini( map_txn );
     341             : 
     342      917026 :   return rec;
     343      917026 : }
     344             : 
     345             : /* insert_params captures all environment parameters required to load a
     346             :    program revision into cache. */
     347             : 
     348             : struct insert_params {
     349             :   fd_pubkey_t             prog_addr;
     350             :   ulong                   feature_slot;
     351             :   ulong                   deploy_slot;
     352             :   fd_sbpf_elf_info_t      elf_info;
     353             :   fd_sbpf_loader_config_t config;
     354             :   fd_features_t const *   features;
     355             :   uchar const *           bin;
     356             :   ulong                   bin_sz;
     357             :   int                     peek_err;
     358             : };
     359             : 
     360             : typedef struct insert_params insert_params_t;
     361             : 
     362             : static insert_params_t *
     363             : insert_params( insert_params_t *          p,
     364             :                fd_pubkey_t const *        prog_addr,
     365             :                fd_prog_load_env_t const * env,
     366             :                fd_acc_t const *           prog_ro,
     367      919955 :                fd_prog_info_t const *     info ) {
     368      919955 :   memset( p, 0, sizeof(insert_params_t) );
     369             : 
     370             :   /* Derive executable info */
     371      919955 :   uchar const * bin    = (uchar const *)prog_ro->data + info->elf_off;
     372      919955 :   ulong         bin_sz = info->elf_sz;
     373             : 
     374             :   /* Pre-flight checks, determine required buffer size */
     375             : 
     376      919955 :   fd_features_t const * features = env->features;
     377      919955 :   fd_prog_versions_t versions = fd_prog_versions( features, env->feature_slot );
     378      919955 :   fd_sbpf_elf_info_t elf_info = {0};
     379      919955 :   fd_sbpf_loader_config_t config = {
     380      919955 :     .sbpf_min_version = versions.min_sbpf_version,
     381      919955 :     .sbpf_max_version = versions.max_sbpf_version,
     382      919955 :   };
     383      919955 :   int peek_err = fd_sbpf_elf_peek( &elf_info, bin, bin_sz, &config );
     384             : 
     385      919955 :   *p = (insert_params_t) {
     386      919955 :     .prog_addr    = *prog_addr,
     387      919955 :     .feature_slot = env->feature_slot,
     388      919955 :     .deploy_slot  = info->deploy_slot,
     389      919955 :     .features     = features,
     390      919955 :     .bin          = !peek_err ? bin    : NULL,
     391      919955 :     .bin_sz       = !peek_err ? bin_sz : 0UL,
     392      919955 :     .peek_err     = peek_err,
     393      919955 :     .elf_info     = elf_info,
     394      919955 :     .config       = config
     395      919955 :   };
     396      919955 :   return p;
     397      919955 : }
     398             : 
     399             : /* fd_progcache_spill_acquire takes the next spill frame: an unloaded record with
     400             :    its spad slot reserved.  Requires the spill write lock. */
     401             : 
     402             : static fd_progcache_rec_t * /* read locked */
     403             : fd_progcache_spill_acquire( fd_progcache_t *        cache,
     404        2929 :                             insert_params_t const * params ) {
     405        2929 :   fd_progcache_join_t *  join  = cache->join;
     406        2929 :   fd_progcache_shmem_t * shmem = join->shmem;
     407        2929 :   FD_TEST( FD_VOLATILE_CONST( shmem->spill.lock.value )==FD_RWLOCK_WRITE_LOCK );
     408             :   /* Allocate record */
     409             : 
     410        2929 :   if( FD_UNLIKELY( shmem->spill.rec_used >= FD_MAX_INSTRUCTION_STACK_DEPTH ) ) {
     411           0 :     FD_LOG_CRIT(( "spill buffer overflow: rec_used=%u rec_max=%lu", shmem->spill.rec_used, FD_MAX_INSTRUCTION_STACK_DEPTH ));
     412           0 :   }
     413        2929 :   cache->spill_active++;
     414        2929 :   uint rec_idx = shmem->spill.rec_used++;
     415        2929 :   shmem->spill.spad_off[ rec_idx ] = shmem->spill.spad_used;
     416        2929 :   fd_progcache_rec_t * rec = &shmem->spill.rec[ rec_idx ];
     417        2929 :   memset( rec, 0, sizeof(fd_progcache_rec_t) );
     418        2929 :   rec->lock.value    = 1; /* read lock; no concurrency, don't need CAS */
     419        2929 :   rec->exists        = 1;
     420        2929 :   rec->feature_slot  = params->feature_slot;
     421        2929 :   rec->deploy_slot   = params->deploy_slot;
     422        2929 :   rec->calldests_off = UINT_MAX; /* non-executable until a load says otherwise */
     423             : 
     424        2929 :   if( params->peek_err==FD_SBPF_ELF_SUCCESS ) {
     425        2926 :     ulong off0 = fd_ulong_align_up( shmem->spill.spad_used, fd_progcache_val_align() );
     426        2926 :     ulong off1 = off0 + fd_progcache_val_footprint( &params->elf_info );
     427        2926 :     if( FD_UNLIKELY( off1 > FD_PROGCACHE_SPAD_MAX ) ) {
     428           0 :       FD_LOG_CRIT(( "spill buffer overflow: spad_used=%u val_sz=%lu spad_max=%lu", shmem->spill.spad_used, off1-off0, FD_PROGCACHE_SPAD_MAX ));
     429           0 :     }
     430        2926 :     rec->data_gaddr        = fd_wksp_gaddr_fast( join->data_base, shmem->spill.spad + off0 );
     431        2926 :     rec->data_max          = (uint)( off1 - off0 );
     432        2926 :     shmem->spill.spad_used = (uint)off1;
     433        2926 :   }
     434             : 
     435        2929 :   return rec;
     436        2929 : }
     437             : 
     438             : static fd_progcache_rec_t * /* read locked */
     439             : fd_progcache_insert( fd_progcache_t *        cache,
     440      919955 :                      insert_params_t const * params ) {
     441      919955 :   fd_progcache_join_t *  ljoin = cache->join;
     442      919955 :   fd_progcache_shmem_t * shmem = ljoin->shmem;
     443             : 
     444      919955 :   ulong val_footprint = ( params->peek_err==FD_SBPF_ELF_SUCCESS ) ? fd_progcache_val_footprint( &params->elf_info ) : 0UL;
     445      919955 :   ulong size_class = fd_progcache_cache_class( val_footprint );
     446             : 
     447      919955 :   fd_progcache_rec_t * rec        = NULL;
     448      919955 :   int                  from_spill = 0;
     449             : 
     450      919955 :   if( FD_UNLIKELY( cache->spill_active ) ) {
     451        2184 :     rec        = fd_progcache_spill_acquire( cache, params );
     452        2184 :     from_spill = 1;
     453      917771 :   } else {
     454             :     /* first acquire attempt (outside loop to increase metrics) */
     455      917771 :     rec = fd_progcache_rec_acquire( ljoin, val_footprint );
     456      917771 :     if( FD_UNLIKELY( !rec ) ) cache->metrics->class_full_cnt++;
     457      917771 :   }
     458             : 
     459             :   /* spin loop: evict, try spill, acquire (in case another thread freed a slot) */
     460     1355080 :   while( !rec ) {
     461     1347727 :     rec = fd_prog_evict( cache, val_footprint );
     462     1347727 :     if( FD_LIKELY( rec ) ) break;
     463             : 
     464      435870 :     if( fd_rwlock_trywrite( &shmem->spill.lock ) ) {
     465         745 :       rec = fd_progcache_spill_acquire( cache, params );
     466         745 :       from_spill = 1;
     467         745 :       break;
     468         745 :     }
     469             : 
     470      435125 :     FD_SPIN_PAUSE();
     471      435125 :     rec = fd_progcache_rec_acquire( ljoin, val_footprint );
     472      435125 :   }
     473             : 
     474             :   /* Claim the key before loading it.  A peer that wants this program finds the
     475             :      record LOADING and waits, so only one tile runs fd_sbpf_program_load for a
     476             :      given revision.  A spill record is never published. */
     477             : 
     478      919955 :   rec->feature_slot = params->feature_slot;
     479      919955 :   rec->deploy_slot  = params->deploy_slot;
     480             : 
     481      919955 :   if( FD_LIKELY( !from_spill ) ) {
     482             :     /* Under the loading sentinel: not LIVE, so the sweep steps over it, and once
     483             :        push makes it findable a peer waits rather than loading it again. */
     484      917026 :     fd_prog_state_load_begin( ljoin->rec.ele, (ulong)( rec - ljoin->rec.ele ) );
     485             : 
     486      917026 :     fd_racesan_hook( "prog_insert:pre_push" );
     487      917026 :     fd_rwlock_read( &shmem->txn.rwlock );
     488      917026 :     ulong txn_idx = cache->lineage->tip_txn_idx;
     489      917026 :     if( FD_UNLIKELY( txn_idx==ULONG_MAX ) ) FD_LOG_CRIT(( "progcache insert requires a non-root transaction" ));
     490             :     /* tip_txn_idx may be stale, so revalidate it against the map under the read
     491             :        lock.  A mismatch means execution was dispatched on a dead fork. */
     492      917026 :     uint live_idx = (uint)fd_prog_txnm_idx_query_const( ljoin->txn.map, &cache->lineage->fork[ 0 ], UINT_MAX, ljoin->txn.pool );
     493      917026 :     if( FD_UNLIKELY( (ulong)live_idx!=txn_idx ) )
     494           0 :       FD_LOG_CRIT(( "progcache insert on a published/canceled fork (fork_id=%lu txn_idx=%lu live_idx=%u)",
     495      917026 :                     (ulong)cache->lineage->fork[ 0 ], txn_idx, live_idx ));
     496      917026 :     fd_progcache_txn_t * txn = &ljoin->txn.pool[ txn_idx ];
     497      917026 :     fd_rwlock_write( &txn->lock );
     498      917026 :     fd_progcache_rec_t * mapped = fd_progcache_push( ljoin, txn, rec, &params->prog_addr );
     499      917026 :     fd_rwlock_unwrite( &txn->lock );
     500      917026 :     fd_rwlock_unread( &shmem->txn.rwlock );
     501             : 
     502             :     /* fd_progcache_push returns rec, the winner of a same-revision race, or
     503             :        NULL for another revision of this key, which delayed visibility excludes. */
     504      917026 :     if( FD_UNLIKELY( !mapped ) ) {
     505           0 :       FD_BASE58_ENCODE_32_BYTES( params->prog_addr.uc, prog_b58 );
     506           0 :       FD_LOG_CRIT(( "progcache insert found another revision of this program mapped (prog=%s fork_id=%lu feature_slot=%lu deploy_slot=%lu)",
     507           0 :                     prog_b58, (ulong)cache->lineage->fork[ 0 ], params->feature_slot, params->deploy_slot ));
     508             : 
     509      917026 :     } else if( FD_UNLIKELY( mapped!=rec ) ) {
     510             :       /* Another thread published this revision first and may still be loading.
     511             :          The lookup was counted as a miss; the winner serves it, a hit. */
     512           0 :       fd_progcache_rec_abandon( ljoin, rec );
     513           0 :       cache->metrics->miss_cnt--;
     514           0 :       cache->metrics->hit_cnt++;
     515           0 :       progcache_metric_per_class( cache->metrics->hit_per_class, size_class );
     516           0 :       return fd_prog_wait_if_loading( cache, mapped );
     517             : 
     518      917026 :     } else {
     519      917026 :       fd_racesan_hook( "prog_insert:post_claim" );
     520      917026 :     }
     521      917026 :   }
     522             : 
     523             :   /* Load program */
     524             : 
     525      919955 :   if( FD_LIKELY( params->peek_err==FD_SBPF_ELF_SUCCESS ) ) {
     526      919382 :     cache->metrics->load_cnt++;
     527      919382 :     long dt = -fd_tickcount();
     528      919382 :     if( FD_UNLIKELY( !fd_progcache_rec_load( rec, ljoin->data_base, &params->elf_info, &params->config, params->feature_slot,
     529      919382 :                                              params->features, params->bin, params->bin_sz, cache->scratch, cache->scratch_sz ) ) ) {
     530             :       /* Not a valid program (mark cache entry as non-executable) */
     531           6 :       fd_progcache_rec_nx( rec );
     532           6 :     }
     533      919382 :     dt += fd_tickcount();
     534      919382 :     cache->metrics->cum_load_ticks += (ulong)dt;
     535      919382 :   } else {
     536         573 :     fd_progcache_rec_nx( rec );
     537         573 :   }
     538             : 
     539      919955 :   if( FD_UNLIKELY( from_spill ) ) {
     540        2929 :     cache->metrics->spill_cnt++;
     541        2929 :     cache->metrics->spill_tot_sz += rec->rodata_sz;
     542        2929 :     progcache_metric_per_class( cache->metrics->spill_per_class, size_class );
     543             : 
     544      917026 :   } else {
     545             :     /* LOADING -> LIVE, releasing the program to waiters */
     546      917026 :     fd_prog_state_touch( ljoin->rec.ele, (ulong)( rec - ljoin->rec.ele ) );
     547             : 
     548      917026 :     cache->metrics->fill_cnt++;
     549      917026 :     cache->metrics->fill_tot_sz += rec->rodata_sz;
     550      917026 :     progcache_metric_per_class( cache->metrics->fill_per_class, size_class );
     551      917026 :   }
     552             : 
     553      919955 :   FD_TEST( rec->exists );
     554      919955 :   return rec; /* read locked since acquire */
     555      919955 : }
     556             : 
     557             : fd_progcache_rec_t * /* read locked */
     558             : fd_progcache_pull( fd_progcache_t *           cache,
     559             :                    fd_progcache_fork_id_t     fork_id,
     560             :                    fd_pubkey_t const *        prog_addr,
     561             :                    fd_prog_load_env_t const * env,
     562      919982 :                    fd_acc_t const *           prog_ro ) {
     563      919982 :   if( FD_UNLIKELY( !cache || !cache->join->shmem ) ) FD_LOG_CRIT(( "NULL progcache" ));
     564      919982 :   long dt = -fd_tickcount();
     565      919982 :   fd_progcache_load_fork( cache, fork_id );
     566      919982 :   cache->metrics->lookup_cnt++;
     567             : 
     568      919982 :   fd_prog_info_t info[1];
     569      919982 :   if( FD_UNLIKELY( !fd_prog_info( info, prog_ro ) ) ) return NULL;
     570             : 
     571      919970 :   fd_progcache_rec_t * found_rec =
     572      919970 :       fd_progcache_query( cache, prog_addr, env->feature_slot, info->deploy_slot );
     573      919970 :   if( FD_LIKELY( found_rec ) ) {
     574          15 :     cache->metrics->hit_cnt++;
     575             :     /* Mark the record as recently accessed for CLOCK replacement */
     576          15 :     fd_prog_state_touch( cache->join->rec.ele, (ulong)( found_rec - cache->join->rec.ele ) );
     577          15 :     progcache_metric_per_class( cache->metrics->hit_per_class, found_rec->size_class );
     578      919955 :   } else {
     579      919955 :     cache->metrics->miss_cnt++;
     580      919955 :     insert_params_t insert[1];
     581      919955 :     found_rec = fd_progcache_insert( cache, insert_params( insert, prog_addr, env, prog_ro, info ) );
     582      919955 :   }
     583             : 
     584      919970 :   dt += fd_tickcount();
     585      919970 :   cache->metrics->cum_pull_ticks += (ulong)dt;
     586      919970 :   return found_rec;
     587      919982 : }
     588             : 
     589             : static void
     590        2929 : fd_progcache_spill_close( fd_progcache_t * cache ) {
     591        2929 :   FD_TEST( cache->spill_active );
     592        2929 :   cache->spill_active--;
     593             : 
     594        2929 :   fd_progcache_shmem_t * shmem = cache->join->shmem;
     595             : 
     596             :   /* Cascade: rewind rec_used and spad_used while the top record is
     597             :      closed.  This reclaims spill spad memory in LIFO order. */
     598        5858 :   while( shmem->spill.rec_used > 0 &&
     599        5858 :          !shmem->spill.rec[ shmem->spill.rec_used-1 ].exists ) {
     600        2929 :     shmem->spill.rec_used--;
     601        2929 :     shmem->spill.spad_used = shmem->spill.spad_off[ shmem->spill.rec_used ];
     602        2929 :   }
     603             : 
     604        2929 :   if( cache->spill_active==0 ) {
     605         745 :     fd_rwlock_t * spill_lock = &shmem->spill.lock;
     606         745 :     FD_TEST( spill_lock->value==FD_RWLOCK_WRITE_LOCK );
     607         745 :     FD_TEST( shmem->spill.rec_used==0 );
     608         745 :     FD_TEST( shmem->spill.spad_used==0 );
     609         745 :     fd_rwlock_unwrite( spill_lock );
     610         745 :   }
     611        2929 : }
     612             : 
     613             : void
     614             : fd_progcache_rec_close( fd_progcache_t *     cache,
     615      920051 :                         fd_progcache_rec_t * rec ) {
     616      920051 :   if( FD_UNLIKELY( !rec ) ) return;
     617      920051 :   if( FD_UNLIKELY( !rec->exists ) ) FD_LOG_CRIT(( "use-after-free: progcache record %p is dead", (void *)rec ));
     618      920051 :   FD_TEST( FD_VOLATILE_CONST( rec->lock.value )!=0 );
     619      920051 :   fd_rwlock_unread( &rec->lock );
     620      920051 :   fd_progcache_shmem_t * shmem = cache->join->shmem;
     621      920051 :   if( rec >= shmem->spill.rec &&
     622      920051 :       rec <  shmem->spill.rec + FD_MAX_INSTRUCTION_STACK_DEPTH ) {
     623        2929 :     rec->exists = 0;
     624        2929 :     fd_progcache_spill_close( cache );
     625        2929 :   }}

Generated by: LCOV version 1.14