LCOV - code coverage report
Current view: top level - flamenco/progcache - fd_progcache_rec.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 173 181 95.6 %
Date: 2026-09-17 04:28:31 Functions: 10 10 100.0 %

          Line data    Source code
       1             : #include "fd_progcache.h"
       2             : #include "fd_progcache_clock.h"
       3             : #include "../../util/racesan/fd_racesan_target.h"
       4             : #include "../vm/fd_vm.h" /* fd_vm_syscall_register_slot, fd_vm_validate */
       5             : 
       6             : /* A free record is always write-locked: fd_progcache_rec_release leaves it so
       7             :    and rec_init_inflight hands it to the acquirer read-locked, so a stale
       8             :    speculative reader can never lock a free record. */
       9             : 
      10             : /* rec_init_inflight turns a write-locked record of class c into an in-flight
      11             :    one owned by the caller.  Callers hold the write lock and have the record
      12             :    out of the map, so no other thread can observe the intermediate state. */
      13             : 
      14             : static fd_progcache_rec_t *
      15             : rec_init_inflight( fd_progcache_join_t * join,
      16             :                    ulong                 idx,
      17      917077 :                    ulong                 c ) {
      18      917077 :   fd_progcache_shmem_t * pc  = join->shmem;
      19      917077 :   fd_progcache_rec_t *   rec = join->rec.ele + idx;
      20             : 
      21             :   /* Not written here: the lock (stays write-locked; a stale speculative reader
      22             :      must never see it clear), free_next (a stale popper may be reading it) and
      23             :      the fork-list links (owned by the fork lock). */
      24      917077 :   memset( rec, 0, offsetof(fd_progcache_rec_t, lock) );
      25      917077 :   rec->txn_idx       = UINT_MAX;
      26      917077 :   rec->entry_pc      = 0U;
      27      917077 :   rec->text_cnt      = 0U;
      28      917077 :   rec->text_off      = 0U;
      29      917077 :   rec->text_sz       = 0U;
      30      917077 :   rec->rodata_sz     = 0U;
      31      917077 :   rec->calldests_off = UINT_MAX;
      32      917077 :   rec->rodata_off    = 0U;
      33      917077 :   rec->sbpf_version  = 0;
      34      917077 :   rec->exists        = 1;
      35      917077 :   rec->size_class    = c & 0x7UL; /* c<FD_PROGCACHE_CACHE_CLASS_CNT, checked by callers */
      36             : 
      37             :   /* Attach value storage: the record's own arena slot */
      38      917077 :   ulong slot_sz   = fd_progcache_cache_slot_sz[ c ];
      39      917077 :   rec->data_gaddr = pc->cache.arena_gaddr[ c ] + (ulong)( idx - pc->cache.rec_base[ c ] )*slot_sz;
      40      917077 :   rec->data_max   = (uint)slot_sz;
      41             : 
      42      917077 :   fd_prog_state_clear( join->rec.ele, idx );
      43      917077 :   atomic_store_explicit( &rec->lock.value, (ushort)1, memory_order_release ); /* write-locked -> read-locked by caller */
      44      917077 :   return rec;
      45      917077 : }
      46             : 
      47             : /* free_push returns record idx to class c's free list.  free_pop takes one, or
      48             :    returns UINT_MAX when the class is full.  Both are lock-free: the link lives in
      49             :    the record being moved, which no other thread can be touching, so there is no
      50             :    shared slot to race on, and the version in the top word defeats ABA. */
      51             : 
      52             : static void
      53             : free_push( fd_progcache_join_t * join,
      54             :            ulong                 c,
      55        4758 :            ulong                 idx ) {
      56        4758 :   fd_progcache_shmem_t * pc  = join->shmem;
      57        4758 :   fd_progcache_rec_t *   rec = join->rec.ele + idx;
      58        4758 :   for(;;) {
      59        4758 :     ulong old_vt  = __atomic_load_n( &pc->cache.free_top[ c ].ver_top, __ATOMIC_RELAXED );
      60        4758 :     uint  old_top = (uint)( old_vt & (ulong)UINT_MAX );
      61        4758 :     uint  old_ver = (uint)( old_vt >> 32 );
      62        4758 :     __atomic_store_n( &rec->free_next, old_top, __ATOMIC_RELAXED );
      63        4758 :     FD_COMPILER_MFENCE();
      64        4758 :     ulong new_vt = ( (ulong)(uint)( old_ver+1U ) << 32 ) | (ulong)(uint)idx;
      65        4758 :     fd_racesan_hook( "prog_free_push:pre_cas" );
      66        4758 :     if( FD_LIKELY( __atomic_compare_exchange_n( &pc->cache.free_top[ c ].ver_top, &old_vt, new_vt,
      67        4758 :                                                 0, __ATOMIC_SEQ_CST, __ATOMIC_RELAXED ) ) ) {
      68        4758 :       __atomic_fetch_add( &pc->cache.free_cnt[ c ].val, 1UL, __ATOMIC_RELAXED );
      69        4758 :       return;
      70        4758 :     }
      71           0 :     FD_SPIN_PAUSE();
      72           0 :   }
      73        4758 : }
      74             : 
      75             : static uint
      76             : free_pop( fd_progcache_join_t * join,
      77     1352896 :           ulong                 c ) {
      78     1352896 :   fd_progcache_shmem_t * pc = join->shmem;
      79     1352896 :   for(;;) {
      80     1352896 :     ulong old_vt  = __atomic_load_n( &pc->cache.free_top[ c ].ver_top, __ATOMIC_RELAXED );
      81     1352896 :     uint  old_top = (uint)( old_vt & (ulong)UINT_MAX );
      82     1352896 :     if( FD_UNLIKELY( old_top==UINT_MAX ) ) return UINT_MAX; /* class full */
      83        5169 :     uint  old_ver = (uint)( old_vt >> 32 );
      84        5169 :     uint  next    = __atomic_load_n( &join->rec.ele[ old_top ].free_next, __ATOMIC_RELAXED );
      85        5169 :     ulong new_vt  = ( (ulong)(uint)( old_ver+1U ) << 32 ) | (ulong)next;
      86        5169 :     fd_racesan_hook( "prog_free_pop:pre_cas" );
      87        5169 :     if( FD_LIKELY( __atomic_compare_exchange_n( &pc->cache.free_top[ c ].ver_top, &old_vt, new_vt,
      88        5169 :                                                 0, __ATOMIC_SEQ_CST, __ATOMIC_RELAXED ) ) ) {
      89        5169 :       __atomic_fetch_sub( &pc->cache.free_cnt[ c ].val, 1UL, __ATOMIC_RELAXED );
      90        5169 :       return old_top;
      91        5169 :     }
      92           0 :     FD_SPIN_PAUSE();
      93           0 :   }
      94     1352896 : }
      95             : 
      96             : fd_progcache_rec_t *
      97             : fd_progcache_rec_acquire( fd_progcache_join_t * join,
      98     1352896 :                           ulong                 val_footprint ) {
      99     1352896 :   ulong c = fd_progcache_cache_class( val_footprint );
     100     1352896 :   FD_TEST( c<FD_PROGCACHE_CACHE_CLASS_CNT );
     101             : 
     102             :   /* Pop a free record from the class fitting val_footprint. */
     103     1352896 :   uint idx = free_pop( join, c );
     104     1352896 :   if( FD_UNLIKELY( idx==UINT_MAX ) ) return NULL; /* class full */
     105             : 
     106        5169 :   return rec_init_inflight( join, idx, c );
     107     1352896 : }
     108             : 
     109             : fd_progcache_rec_t *
     110             : fd_progcache_rec_reinit( fd_progcache_join_t * join,
     111      911908 :                          fd_progcache_rec_t *  rec ) {
     112      911908 :   return rec_init_inflight( join, (ulong)( rec - join->rec.ele ), rec->size_class );
     113      911908 : }
     114             : 
     115             : void
     116             : fd_progcache_rec_release( fd_progcache_join_t * join,
     117        4758 :                           fd_progcache_rec_t *  rec ) {
     118        4758 :   ulong idx = (ulong)( rec - join->rec.ele );
     119        4758 :   ulong c   = rec->size_class;
     120             : 
     121        4758 :   rec->exists     = 0;
     122        4758 :   rec->data_gaddr = 0UL;
     123        4758 :   rec->data_max   = 0U;
     124             :   /* lock stays write-locked (the free-record invariant) */
     125             : 
     126        4758 :   fd_prog_state_clear( join->rec.ele, idx );
     127        4758 :   free_push( join, c, idx );
     128        4758 : }
     129             : 
     130             : void
     131             : fd_progcache_rec_abandon( fd_progcache_join_t * join,
     132          51 :                           fd_progcache_rec_t *  rec ) {
     133             :   /* Never in the map, so no new reader can find it, but a stale reader of a
     134             :      previous incarnation may hold a transient tryread: trade our read lock for
     135             :      the write lock to drain them. */
     136          51 :   fd_rwlock_unread( &rec->lock );
     137          51 :   fd_rwlock_write( &rec->lock );
     138          51 :   fd_progcache_rec_release( join, rec );
     139          51 : }
     140             : 
     141             : FD_FN_PURE ulong
     142     1841690 : fd_progcache_val_footprint( fd_sbpf_elf_info_t const * elf_info ) {
     143     1841690 :   int   has_calldests = !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version );
     144     1841690 :   ulong pc_max        = fd_ulong_max( 1UL, elf_info->text_cnt );
     145             : 
     146             :   /* load_buf_sz is the exact buffer the loader needs (peek-computed):
     147             :      text_off+text_sz for strict, the rodata image for lenient-fast, or bin_sz
     148             :      for legacy lenient. */
     149     1841690 :   ulong l = FD_LAYOUT_INIT;
     150     1841690 :   if( has_calldests ) {
     151     1841690 :     l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( pc_max ) );
     152     1841690 :   }
     153     1841690 :   l = FD_LAYOUT_APPEND( l, 8UL, elf_info->load_buf_sz );
     154     1841690 :   return FD_LAYOUT_FINI( l, fd_progcache_val_align() );
     155     1841690 : }
     156             : 
     157             : /* Program loader wrapper */
     158             : 
     159             : fd_progcache_rec_t *
     160             : fd_progcache_rec_load( fd_progcache_rec_t *            rec,
     161             :                        fd_wksp_t *                     wksp,
     162             :                        fd_sbpf_elf_info_t const *      elf_info,
     163             :                        fd_sbpf_loader_config_t const * config,
     164             :                        ulong                           load_slot,
     165             :                        fd_features_t const *           features,
     166             :                        void const *                    progdata,
     167             :                        ulong                           progdata_sz,
     168             :                        void *                          scratch,
     169      919382 :                        ulong                           scratch_sz ) {
     170             : 
     171             :   /* Format object */
     172             : 
     173      919382 :   int has_calldests = !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version );
     174             : 
     175      919382 :   void * val           = fd_wksp_laddr_fast( wksp, rec->data_gaddr );
     176      919382 :   void * calldests_mem = NULL;
     177      919382 :   void * rodata_mem;
     178      919382 :   if( has_calldests ) {
     179             :     /* Lenient (v0-v2): [ calldests | rodata ] laid out inside val.  The rodata
     180             :        buffer is load_buf_sz (rodata image on the fast path, bin_sz on the
     181             :        legacy path); must match fd_progcache_val_footprint. */
     182      919382 :     FD_SCRATCH_ALLOC_INIT( l, val );
     183      919382 :     calldests_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( fd_ulong_max( 1UL, elf_info->text_cnt ) ) );
     184      919382 :     rodata_mem    = FD_SCRATCH_ALLOC_APPEND( l, 8UL, elf_info->load_buf_sz );
     185      919382 :     FD_SCRATCH_ALLOC_FINI( l, fd_progcache_val_align() );
     186      919382 :     FD_TEST( _l-(ulong)val == fd_progcache_val_footprint( elf_info ) );
     187      919382 :   } else {
     188             :     /* Strict (v3+): no calldests, so rodata is just the start of val
     189             :        (val is fd_progcache_val_align()-aligned, which is >= 8). */
     190           0 :     rodata_mem = val;
     191           0 :   }
     192             : 
     193      919382 :   rec->calldests_off = has_calldests ? (uint)( (ulong)calldests_mem - (ulong)val ) : UINT_MAX;
     194      919382 :   rec->rodata_off    = (uint)( (ulong)rodata_mem - (ulong)val );
     195      919382 :   rec->entry_pc      = 0;
     196      919382 :   rec->rodata_sz     = 0;
     197             : 
     198      919382 :   rec->text_cnt      = elf_info->text_cnt;
     199      919382 :   rec->text_off      = elf_info->text_off;
     200      919382 :   rec->text_sz       = (uint)elf_info->text_sz;
     201      919382 :   rec->sbpf_version  = (uchar)elf_info->sbpf_version;
     202             : 
     203             :   /* Set up sbpf_loader (redirect writes into progcache_rec object) */
     204             : 
     205      919382 :   fd_sbpf_program_t prog[1] = {{
     206      919382 :     .info     = *elf_info,
     207      919382 :     .rodata   = rodata_mem,
     208      919382 :     .text     = (ulong *)((ulong)rodata_mem + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
     209      919382 :     .entry_pc = ULONG_MAX
     210      919382 :   }};
     211      919382 :   if( has_calldests && elf_info->text_cnt>0UL ) {
     212      919382 :     prog->calldests_shmem = calldests_mem;
     213      919382 :     prog->calldests = fd_sbpf_calldests_join( fd_sbpf_calldests_new( calldests_mem, elf_info->text_cnt ) );
     214      919382 :   }
     215             : 
     216             :   /* Loader requires syscall table */
     217             : 
     218      919382 :   fd_sbpf_syscalls_t _syscalls[ FD_SBPF_SYSCALLS_SLOT_CNT ];
     219      919382 :   fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( _syscalls ) );
     220      919382 :   int syscalls_err = fd_vm_syscall_register_slot( syscalls, load_slot, features, /* is_deploy */ 0 );
     221      919382 :   if( FD_UNLIKELY( syscalls_err!=FD_VM_SUCCESS ) ) FD_LOG_CRIT(( "fd_vm_syscall_register_slot failed" ));
     222             : 
     223             :   /* Run ELF loader.
     224             : 
     225             :      Scratch is needed only by the lenient (v0-v2) fallback path, which
     226             :      assembles the rodata segment via a scratch buffer.  The lenient fast
     227             :      path and strict (v3+) loads write directly into the destination buffer;
     228             :      passing NULL both selects the loader's fast/no-scratch path and faults
     229             :      loudly if it ever starts relying on scratch. */
     230             : 
     231      919382 :   int    use_scratch     = fd_sbpf_loader_is_legacy_lenient( elf_info );
     232      919382 :   void * load_scratch    = use_scratch ? scratch    : NULL;
     233      919382 :   ulong  load_scratch_sz = use_scratch ? scratch_sz : 0UL;
     234             : 
     235      919382 :   if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, progdata, progdata_sz, syscalls, config, load_scratch, load_scratch_sz ) ) ) {
     236           0 :     return NULL;
     237           0 :   }
     238             : 
     239      919382 :   rec->entry_pc  = (uint)prog->entry_pc;
     240      919382 :   rec->rodata_sz = (uint)prog->rodata_sz;
     241             : 
     242             :   /* Run bytecode validator */
     243             : 
     244      919382 :   fd_vm_t _vm[1];
     245      919382 :   fd_vm_t * vm = fd_vm_join( fd_vm_new( _vm ) );
     246      919382 :   if( FD_UNLIKELY( !vm ) ) FD_LOG_CRIT(( "fd_vm_new failed" ));
     247      919382 :   vm = fd_vm_init( vm,
     248      919382 :                    NULL, /* OK since unused in `fd_vm_validate()` */
     249      919382 :                    0UL,
     250      919382 :                    0UL,
     251      919382 :                    prog->rodata,
     252      919382 :                    prog->rodata_sz,
     253      919382 :                    prog->text,
     254      919382 :                    prog->info.text_cnt,
     255      919382 :                    prog->info.text_off,
     256      919382 :                    prog->info.text_sz,
     257      919382 :                    prog->entry_pc,
     258      919382 :                    prog->calldests,
     259      919382 :                    elf_info->sbpf_version,
     260      919382 :                    syscalls,
     261      919382 :                    NULL,
     262      919382 :                    NULL,
     263      919382 :                    NULL,
     264      919382 :                    0U,
     265      919382 :                    NULL,
     266      919382 :                    0,
     267      919382 :                    FD_FEATURE_ACTIVE( load_slot, features, account_data_direct_mapping ),
     268      919382 :                    FD_FEATURE_ACTIVE( load_slot, features, syscall_parameter_address_restrictions ),
     269      919382 :                    FD_FEATURE_ACTIVE( load_slot, features, virtual_address_space_adjustments ),
     270      919382 :                    0,
     271      919382 :                    0UL );
     272      919382 :   if( FD_UNLIKELY( !vm ) ) FD_LOG_CRIT(( "fd_vm_init failed" ));
     273             : 
     274      919382 :   if( FD_UNLIKELY( fd_vm_validate( vm )!=FD_VM_SUCCESS ) ) return NULL;
     275             : 
     276      919376 :   return rec;
     277      919382 : }
     278             : 
     279             : fd_progcache_rec_t *
     280         579 : fd_progcache_rec_nx( fd_progcache_rec_t * rec ) {
     281         579 :   rec->data_gaddr    = 0UL;
     282         579 :   rec->data_max      = 0U;
     283         579 :   rec->entry_pc      = 0;
     284         579 :   rec->text_cnt      = 0;
     285         579 :   rec->text_off      = 0;
     286         579 :   rec->text_sz       = 0;
     287         579 :   rec->rodata_sz     = 0;
     288             :   rec->calldests_off = UINT_MAX;
     289         579 :   rec->rodata_off    = 0;
     290         579 :   rec->sbpf_version  = 0;
     291         579 :   return rec;
     292         579 : }

Generated by: LCOV version 1.14