Line data Source code
1 : #include "fd_progcache.h"
2 : #include "fd_progcache_clock.h"
3 :
4 : #define MAP_NAME fd_prog_recm
5 9386 : #define MAP_ELE_T fd_progcache_rec_t
6 0 : #define MAP_KEY_T fd_progcache_rec_key_t
7 4687 : #define MAP_KEY pair
8 : #define MAP_KEY_EQ(k0,k1) fd_progcache_rec_key_eq((k0),(k1))
9 : #define MAP_KEY_HASH(k0,seed) fd_progcache_rec_key_hash( &(k0)->prog, (seed) )
10 9374 : #define MAP_IDX_T uint
11 9376 : #define MAP_NEXT map_next
12 12 : #define MAP_MAGIC (0xf173da2ce77ecdb8UL)
13 : #define MAP_IMPL_STYLE 2
14 : #include "../../util/tmpl/fd_map_chain_para.c"
15 :
16 : #define POOL_NAME fd_prog_txnp
17 36 : #define POOL_T fd_progcache_txn_t
18 : #define POOL_IDX_T uint
19 192 : #define POOL_NEXT map_next
20 : #define POOL_IMPL_STYLE 2
21 : #include "../../util/tmpl/fd_pool.c"
22 :
23 : #define MAP_NAME fd_prog_txnm
24 : #define MAP_ELE_T fd_progcache_txn_t
25 120400 : #define MAP_KEY xid
26 365289 : #define MAP_IDX_T uint
27 241209 : #define MAP_NEXT map_next
28 12 : #define MAP_MAGIC (0xf173da2ce77ecdb9UL)
29 : #define MAP_IMPL_STYLE 2
30 : #include "../../util/tmpl/fd_map_chain.c"
31 :
32 : /* Metadata one record costs: the record and its map chain. */
33 :
34 480 : #define FD_PROGCACHE_REC_META_SZ ( sizeof(fd_progcache_rec_t) + \
35 480 : sizeof(fd_prog_recm_shmem_private_chain_t) )
36 :
37 : /* fd_progcache_slot_cost returns the amount of memory a single slot consumes. */
38 : FD_FN_CONST static inline ulong
39 480 : fd_progcache_slot_cost( ulong c ) {
40 480 : return fd_progcache_cache_slot_sz[ c ] + FD_PROGCACHE_REC_META_SZ;
41 480 : }
42 :
43 : /* fd_progcache_arena_sz returns the amount of memory an entire arena
44 : (with slot_cnt[c] for each class c) consumes. */
45 : static ulong
46 12 : fd_progcache_arena_sz( ulong const * slot_cnt ) {
47 12 : ulong sz = 0UL;
48 84 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
49 72 : sz += slot_cnt[ c ]*fd_progcache_cache_slot_sz[ c ];
50 72 : }
51 12 : return sz;
52 12 : }
53 :
54 : /* fd_progcache_layout_footprint returns the exact shmem footprint of a cache
55 : holding txn_max transactions, rec_max records and arena_sz bytes of value arena.
56 : fd_progcache_new lays out these same regions in this same order. */
57 : static ulong
58 : fd_progcache_layout_footprint( ulong txn_max,
59 : ulong rec_max,
60 48 : ulong arena_sz ) {
61 48 : ulong l = FD_LAYOUT_INIT;
62 :
63 48 : l = FD_LAYOUT_APPEND( l, alignof(fd_progcache_shmem_t), sizeof(fd_progcache_shmem_t) );
64 :
65 48 : ulong txn_chain_cnt = fd_prog_txnm_chain_cnt_est( txn_max );
66 48 : l = FD_LAYOUT_APPEND( l, fd_prog_txnm_align(), fd_prog_txnm_footprint( txn_chain_cnt ) );
67 48 : l = FD_LAYOUT_APPEND( l, fd_prog_txnp_align(), fd_prog_txnp_footprint( txn_max ) );
68 :
69 48 : ulong rec_chain_cnt = fd_prog_recm_chain_cnt_est( rec_max );
70 48 : l = FD_LAYOUT_APPEND( l, fd_prog_recm_align(), fd_prog_recm_footprint( rec_chain_cnt ) );
71 48 : l = FD_LAYOUT_APPEND( l, alignof(fd_progcache_rec_t), sizeof(fd_progcache_rec_t)*rec_max );
72 48 : l = FD_LAYOUT_APPEND( l, fd_progcache_val_align(), arena_sz );
73 :
74 48 : return FD_LAYOUT_FINI( l, fd_progcache_shmem_align() );
75 48 : }
76 :
77 : /* fd_progcache_setup_slots provisions progcache_sz across the classes. Returns rec_max (== sum(slot_cnt)),
78 : or 0 if the budget cannot cover class_min for every class. */
79 :
80 : ulong
81 : fd_progcache_setup_slots( ulong txn_max,
82 : ulong progcache_sz,
83 24 : ulong * slot_cnt ) {
84 : /* Share of the surplus per data class, from the mainnet size distribution.
85 : Class 0 is not distributed by share: it takes the remainder, so its entry
86 : below is nominal and it also absorbs every other class's rounding slack. */
87 24 : static const uint pct[ FD_PROGCACHE_CACHE_CLASS_CNT ] = {
88 24 : 4U, /* class 0: 128 KiB */
89 24 : 29U, /* class 1: 512 KiB */
90 24 : 25U, /* class 2: 1 MiB */
91 24 : 18U, /* class 3: 2 MiB */
92 24 : 14U, /* class 4: 4 MiB */
93 24 : 10U, /* class 5: ~10 MiB */
94 24 : };
95 :
96 24 : ulong min_sz = fd_progcache_shmem_min_sz( txn_max );
97 24 : if( FD_UNLIKELY( progcache_sz<min_sz ) ) return 0UL;
98 :
99 : /* Seed every class with its guaranteed minimum, which is what min_sz paid for. */
100 168 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
101 144 : slot_cnt[ c ] = fd_progcache_cache_class_min( c );
102 144 : }
103 :
104 : /* Distribute the surplus by percentage, rounding down. */
105 24 : ulong surplus = progcache_sz - min_sz;
106 24 : ulong leftover = surplus;
107 144 : for( ulong c=1UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
108 120 : ulong want = ((surplus/100UL)*(ulong)pct[ c ]) / fd_progcache_slot_cost( c );
109 120 : slot_cnt[ c ] += want;
110 120 : leftover -= want*fd_progcache_slot_cost( c );
111 120 : }
112 24 : slot_cnt[ 0 ] += leftover / fd_progcache_slot_cost( 0 );
113 :
114 24 : ulong rec_max = 0UL;
115 168 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) rec_max += slot_cnt[ c ];
116 24 : return rec_max;
117 24 : }
118 :
119 : FD_FN_CONST ulong
120 168 : fd_progcache_shmem_align( void ) {
121 168 : return fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max(
122 168 : alignof(fd_progcache_shmem_t),
123 168 : fd_prog_txnm_align() ),
124 168 : fd_prog_txnp_align() ),
125 168 : alignof(fd_progcache_txn_t) ),
126 168 : fd_prog_recm_align() ),
127 168 : alignof(fd_progcache_rec_t) ),
128 168 : fd_progcache_val_align() );
129 168 : }
130 :
131 : /* Fork depth cannot exceed txn_max, so bounding txn_max by the lineage array's
132 : depth is what keeps fd_progcache_load_fork from overrunning it. */
133 :
134 : ulong
135 36 : fd_progcache_shmem_min_sz( ulong txn_max ) {
136 36 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) return 0UL;
137 :
138 36 : ulong min_sz = fd_progcache_layout_footprint( txn_max, 0UL, 0UL ) + fd_progcache_val_align();
139 252 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
140 216 : min_sz += fd_progcache_cache_class_min( c )*fd_progcache_slot_cost( c );
141 216 : }
142 :
143 : /* round at 1MiB */
144 36 : return ( (min_sz>>20UL)+1UL )<<20UL;
145 36 : }
146 :
147 : ulong
148 : fd_progcache_shmem_footprint( ulong txn_max,
149 12 : ulong progcache_sz ) {
150 12 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) return 0UL;
151 :
152 : /* explicitly validate that progcache_sz is enough */
153 12 : ulong slot_cnt[ FD_PROGCACHE_CACHE_CLASS_CNT ];
154 12 : if( FD_UNLIKELY( !fd_progcache_setup_slots( txn_max, progcache_sz, slot_cnt ) ) ) return 0UL;
155 :
156 12 : return progcache_sz;
157 12 : }
158 :
159 : fd_progcache_shmem_t *
160 : fd_progcache_shmem_new( void * shmem,
161 : ulong wksp_tag,
162 : ulong seed,
163 : ulong txn_max,
164 12 : ulong progcache_sz ) {
165 12 : fd_progcache_shmem_t * pc = shmem;
166 12 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
167 :
168 12 : if( FD_UNLIKELY( !pc ) ) {
169 0 : FD_LOG_WARNING(( "NULL shmem" ));
170 0 : return NULL;
171 0 : }
172 :
173 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)pc, fd_progcache_shmem_align() ) ) ) {
174 0 : FD_LOG_WARNING(( "misaligned shmem" ));
175 0 : return NULL;
176 0 : }
177 :
178 12 : if( FD_UNLIKELY( !wksp_tag ) ) {
179 0 : FD_LOG_WARNING(( "bad wksp_tag" ));
180 0 : return NULL;
181 0 : }
182 :
183 12 : if( FD_UNLIKELY( !wksp ) ) {
184 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
185 0 : return NULL;
186 0 : }
187 :
188 12 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) {
189 0 : FD_LOG_WARNING(( "invalid txn_max" ));
190 0 : return NULL;
191 0 : }
192 :
193 12 : ulong slot_cnt[ FD_PROGCACHE_CACHE_CLASS_CNT ];
194 12 : ulong rec_max = fd_progcache_setup_slots( txn_max, progcache_sz, slot_cnt );
195 12 : if( FD_UNLIKELY( !rec_max || rec_max>UINT_MAX ) ) {
196 0 : FD_LOG_WARNING(( "invalid progcache_sz (%lu B)", progcache_sz ));
197 0 : return NULL;
198 0 : }
199 12 : ulong arena_sz = fd_progcache_arena_sz( slot_cnt );
200 :
201 12 : FD_SCRATCH_ALLOC_INIT( l, pc+1 );
202 :
203 12 : ulong txn_chain_cnt = fd_prog_txnm_chain_cnt_est( txn_max );
204 12 : void * txn_map = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_txnm_align(), fd_prog_txnm_footprint( txn_chain_cnt ) );
205 12 : void * txn_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_txnp_align(), fd_prog_txnp_footprint( txn_max ) );
206 :
207 0 : ulong rec_chain_cnt = fd_prog_recm_chain_cnt_est( rec_max );
208 12 : void * rec_map = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_recm_align(), fd_prog_recm_footprint( rec_chain_cnt ) );
209 12 : fd_progcache_rec_t * rec_ele = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_progcache_rec_t), sizeof(fd_progcache_rec_t)*rec_max );
210 12 : uchar * arena = FD_SCRATCH_ALLOC_APPEND( l, fd_progcache_val_align(), arena_sz );
211 :
212 0 : ulong layout_sz = fd_progcache_layout_footprint( txn_max, rec_max, arena_sz );
213 12 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_progcache_shmem_align() ) == (ulong)pc + layout_sz );
214 12 : FD_TEST( layout_sz<=progcache_sz );
215 :
216 12 : fd_memset( pc, 0, offsetof(fd_progcache_shmem_t, spill) ); /* spill fields set below */
217 12 : fd_memset( &pc->cache, 0, sizeof(pc->cache) );
218 12 : fd_memset( rec_ele, 0, rec_max*sizeof(fd_progcache_rec_t) );
219 :
220 12 : pc->wksp_tag = wksp_tag;
221 12 : pc->seed = seed;
222 :
223 : /* Fork graph */
224 :
225 12 : pc->txn.map_gaddr = fd_wksp_gaddr_fast( wksp, fd_prog_txnm_new( txn_map, txn_chain_cnt, seed ) );
226 12 : void * txn_pool2 = fd_prog_txnp_new( txn_pool, txn_max );
227 12 : pc->txn.pool_gaddr = fd_wksp_gaddr_fast( wksp, txn_pool2 );
228 12 : fd_progcache_txn_t * txn_ele = fd_prog_txnp_join( txn_pool2 );
229 12 : pc->txn.ele_gaddr = fd_wksp_gaddr_fast( wksp, txn_ele );
230 12 : pc->txn.max = txn_max;
231 12 : pc->txn.child_head_idx = UINT_MAX;
232 12 : pc->txn.child_tail_idx = UINT_MAX;
233 12 : pc->txn.root = fd_progcache_fork_id_initial();
234 12 : pc->txn.seq = fd_progcache_fork_id_initial();
235 12 : fd_rwlock_new( &pc->txn.rwlock );
236 204 : for( ulong i=0UL; i<txn_max; i++ ) {
237 192 : fd_rwlock_new( &txn_ele[ i ].lock );
238 192 : }
239 12 : fd_prog_txnp_leave( txn_ele );
240 :
241 : /* Record map + array. Records double as value slots: free records are
242 : kept write-locked (a stale speculative reader can never lock one) on
243 : their class's free list. */
244 :
245 12 : pc->rec.map_gaddr = fd_wksp_gaddr_fast( wksp, fd_prog_recm_new( rec_map, rec_chain_cnt, seed ) );
246 12 : pc->rec.ele_gaddr = fd_wksp_gaddr_fast( wksp, rec_ele );
247 12 : pc->rec.max = (uint)rec_max;
248 2088 : for( ulong i=0UL; i<rec_max; i++ ) rec_ele[ i ].lock.value = FD_RWLOCK_WRITE_LOCK;
249 :
250 12 : fd_rwlock_new( &pc->spill.lock );
251 12 : pc->spill.rec_used = 0U;
252 12 : pc->spill.spad_used = 0U;
253 :
254 : /* Size-class cache: partition the record array and the value arena by class. */
255 12 : ulong rec_base = 0UL;
256 12 : ulong arena_gaddr = arena_sz ? fd_wksp_gaddr_fast( wksp, arena ) : 0UL;
257 :
258 12 : ulong arena_off = 0UL;
259 84 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
260 72 : ulong n = slot_cnt[ c ];
261 72 : ulong slot_sz = fd_progcache_cache_slot_sz[ c ];
262 72 : pc->cache.class_max [ c ] = n;
263 72 : pc->cache.rec_base [ c ] = rec_base;
264 72 : pc->cache.clock_hand[ c ].val = 0UL; /* ticket, not an index */
265 :
266 : /* Every record starts free. Construction is single threaded, so chain them
267 : directly: the top is the highest index and each links to the one below, so
268 : a class hands out descending indices. An empty class parks UINT_MAX. */
269 72 : pc->cache.free_cnt[ c ].val = n;
270 72 : pc->cache.free_top[ c ].ver_top = n ? (ulong)(uint)( rec_base+n-1UL ) : (ulong)UINT_MAX;
271 2148 : for( ulong s=0UL; s<n; s++ )
272 2076 : rec_ele[ rec_base+s ].free_next = (uint)( s ? ( rec_base+s-1UL ) : (ulong)UINT_MAX );
273 :
274 72 : if( n ) {
275 72 : pc->cache.arena_gaddr[ c ] = arena_gaddr + arena_off;
276 72 : arena_off += n*slot_sz;
277 72 : }
278 :
279 72 : rec_base += n;
280 72 : }
281 :
282 12 : FD_COMPILER_MFENCE();
283 12 : FD_VOLATILE( pc->magic ) = FD_PROGCACHE_SHMEM_MAGIC;
284 12 : FD_COMPILER_MFENCE();
285 :
286 12 : return (void *)pc;
287 :
288 12 : }
289 :
290 : fd_progcache_join_t *
291 : fd_progcache_shmem_join( fd_progcache_join_t * ljoin,
292 12 : fd_progcache_shmem_t * shmem ) {
293 :
294 12 : if( FD_UNLIKELY( !shmem ) ) {
295 0 : FD_LOG_WARNING(( "NULL shmem" ));
296 0 : return NULL;
297 0 : }
298 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_progcache_shmem_align() ) ) ) {
299 0 : FD_LOG_WARNING(( "misaligned shmem" ));
300 0 : return NULL;
301 0 : }
302 12 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
303 12 : if( FD_UNLIKELY( !wksp ) ) {
304 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
305 0 : return NULL;
306 0 : }
307 12 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
308 0 : FD_LOG_WARNING(( "bad magic" ));
309 0 : return NULL;
310 0 : }
311 :
312 12 : if( FD_UNLIKELY( !ljoin ) ) {
313 0 : FD_LOG_WARNING(( "NULL join" ));
314 0 : return NULL;
315 0 : }
316 :
317 12 : memset( ljoin, 0, sizeof(fd_progcache_join_t) );
318 :
319 12 : ljoin->shmem = shmem;
320 12 : ljoin->data_base = wksp; /* all value/bookkeeping gaddrs are wksp-relative */
321 :
322 12 : ljoin->txn.map = fd_prog_txnm_join( fd_wksp_laddr( wksp, shmem->txn.map_gaddr ) );
323 12 : if( FD_UNLIKELY( !ljoin->txn.map ) ) {
324 0 : FD_LOG_WARNING(( "fd_prog_txnm_join failed" ));
325 0 : return NULL;
326 0 : }
327 12 : ljoin->txn.pool = fd_prog_txnp_join( fd_wksp_laddr( wksp, shmem->txn.pool_gaddr ) );
328 12 : if( FD_UNLIKELY( !ljoin->txn.pool ) ) {
329 0 : FD_LOG_WARNING(( "fd_prog_txnp_join failed" ));
330 0 : return NULL;
331 0 : }
332 12 : if( FD_UNLIKELY( !fd_prog_recm_join( ljoin->rec.map, fd_wksp_laddr( wksp, shmem->rec.map_gaddr ), fd_wksp_laddr( wksp, shmem->rec.ele_gaddr ), shmem->rec.max ) ) ) {
333 0 : FD_LOG_WARNING(( "fd_prog_recm_join failed" ));
334 0 : return NULL;
335 0 : }
336 12 : ljoin->rec.ele = fd_wksp_laddr( wksp, shmem->rec.ele_gaddr );
337 12 : ljoin->rec.max = shmem->rec.max;
338 :
339 12 : return ljoin;
340 12 : }
341 :
342 : void *
343 : fd_progcache_shmem_leave( fd_progcache_join_t * ljoin,
344 12 : fd_progcache_shmem_t ** opt_shmem ) {
345 :
346 12 : if( FD_UNLIKELY( !ljoin ) ) {
347 0 : FD_LOG_WARNING(( "NULL join" ));
348 0 : if( opt_shmem ) *opt_shmem = NULL;
349 0 : return NULL;
350 0 : }
351 :
352 12 : void * shmem = ljoin->shmem;
353 :
354 12 : memset( ljoin, 0, sizeof(fd_progcache_join_t) );
355 :
356 12 : if( opt_shmem ) *opt_shmem = shmem;
357 12 : return shmem;
358 12 : }
359 :
360 : void *
361 12 : fd_progcache_shmem_delete( fd_progcache_shmem_t * shmem ) {
362 :
363 12 : if( FD_UNLIKELY( !shmem ) ) {
364 0 : FD_LOG_WARNING(( "NULL shmem" ));
365 0 : return NULL;
366 0 : }
367 :
368 12 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_progcache_shmem_align() ) ) ) {
369 0 : FD_LOG_WARNING(( "misaligned shmem" ));
370 0 : return NULL;
371 0 : }
372 :
373 12 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
374 12 : if( FD_UNLIKELY( !wksp ) ) {
375 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
376 0 : return NULL;
377 0 : }
378 :
379 12 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
380 0 : FD_LOG_WARNING(( "bad magic" ));
381 0 : return NULL;
382 0 : }
383 :
384 12 : FD_TEST( !shmem->txn.rwlock.value );
385 12 : fd_progcache_txn_t * txn0 = fd_wksp_laddr_fast( wksp, shmem->txn.ele_gaddr );
386 204 : for( ulong i=0UL; i<shmem->txn.max; i++ ) FD_TEST( !txn0[ i ].lock.value );
387 12 : FD_TEST( !shmem->spill.lock.value );
388 :
389 12 : FD_COMPILER_MFENCE();
390 12 : FD_VOLATILE( shmem->magic ) = 0UL;
391 12 : FD_COMPILER_MFENCE();
392 :
393 12 : return shmem;
394 12 : }
395 :
396 : void *
397 0 : fd_progcache_shmem_delete_fast( fd_progcache_shmem_t * shmem ) {
398 :
399 0 : if( FD_UNLIKELY( !shmem ) ) {
400 0 : FD_LOG_WARNING(( "NULL shmem" ));
401 0 : return NULL;
402 0 : }
403 :
404 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_progcache_shmem_align() ) ) ) {
405 0 : FD_LOG_WARNING(( "misaligned shmem" ));
406 0 : return NULL;
407 0 : }
408 :
409 0 : if( FD_UNLIKELY( !fd_wksp_containing( shmem ) ) ) {
410 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
411 0 : return NULL;
412 0 : }
413 :
414 0 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
415 0 : FD_LOG_WARNING(( "bad magic" ));
416 0 : return NULL;
417 0 : }
418 :
419 0 : FD_COMPILER_MFENCE();
420 0 : FD_VOLATILE( shmem->magic ) = 0UL;
421 0 : FD_COMPILER_MFENCE();
422 :
423 0 : fd_wksp_free_laddr( shmem );
424 :
425 0 : return shmem;
426 0 : }
|