Line data Source code
1 : #include "fd_progcache.h"
2 : #include "fd_progcache_clock.h"
3 :
4 : #define MAP_NAME fd_prog_recm
5 2748292 : #define MAP_ELE_T fd_progcache_rec_t
6 1029 : #define MAP_KEY_T fd_progcache_rec_key_t
7 918055 : #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 2747857 : #define MAP_IDX_T uint
11 2665731 : #define MAP_NEXT map_next
12 204 : #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 654 : #define POOL_T fd_progcache_txn_t
18 : #define POOL_IDX_T uint
19 3696 : #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 4575 : #define MAP_KEY xid
26 928708 : #define MAP_IDX_T uint
27 9282 : #define MAP_NEXT map_next
28 204 : #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 14514 : #define FD_PROGCACHE_REC_META_SZ ( sizeof(fd_progcache_rec_t) + \
35 14514 : 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 14514 : fd_progcache_slot_cost( ulong c ) {
40 14514 : return fd_progcache_cache_slot_sz[ c ] + FD_PROGCACHE_REC_META_SZ;
41 14514 : }
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 204 : fd_progcache_arena_sz( ulong const * slot_cnt ) {
47 204 : ulong sz = 0UL;
48 1428 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
49 1224 : sz += slot_cnt[ c ]*fd_progcache_cache_slot_sz[ c ];
50 1224 : }
51 204 : return sz;
52 204 : }
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 1611 : ulong arena_sz ) {
61 1611 : ulong l = FD_LAYOUT_INIT;
62 :
63 1611 : l = FD_LAYOUT_APPEND( l, alignof(fd_progcache_shmem_t), sizeof(fd_progcache_shmem_t) );
64 :
65 1611 : ulong txn_chain_cnt = fd_prog_txnm_chain_cnt_est( txn_max );
66 1611 : l = FD_LAYOUT_APPEND( l, fd_prog_txnm_align(), fd_prog_txnm_footprint( txn_chain_cnt ) );
67 1611 : l = FD_LAYOUT_APPEND( l, fd_prog_txnp_align(), fd_prog_txnp_footprint( txn_max ) );
68 :
69 1611 : ulong rec_chain_cnt = fd_prog_recm_chain_cnt_est( rec_max );
70 1611 : l = FD_LAYOUT_APPEND( l, fd_prog_recm_align(), fd_prog_recm_footprint( rec_chain_cnt ) );
71 1611 : l = FD_LAYOUT_APPEND( l, alignof(fd_progcache_rec_t), sizeof(fd_progcache_rec_t)*rec_max );
72 1611 : l = FD_LAYOUT_APPEND( l, fd_progcache_val_align(), arena_sz );
73 :
74 1611 : return FD_LAYOUT_FINI( l, fd_progcache_shmem_align() );
75 1611 : }
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 1122 : 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 1122 : static const uint pct[ FD_PROGCACHE_CACHE_CLASS_CNT ] = {
88 1122 : 4U, /* class 0: 128 KiB */
89 1122 : 29U, /* class 1: 512 KiB */
90 1122 : 25U, /* class 2: 1 MiB */
91 1122 : 18U, /* class 3: 2 MiB */
92 1122 : 14U, /* class 4: 4 MiB */
93 1122 : 10U, /* class 5: ~10 MiB */
94 1122 : };
95 :
96 1122 : ulong min_sz = fd_progcache_shmem_min_sz( txn_max );
97 1122 : 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 3864 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
101 3312 : slot_cnt[ c ] = fd_progcache_cache_class_min( c );
102 3312 : }
103 :
104 : /* Distribute the surplus by percentage, rounding down. */
105 552 : ulong surplus = progcache_sz - min_sz;
106 552 : ulong leftover = surplus;
107 3312 : for( ulong c=1UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
108 2760 : ulong want = ((surplus/100UL)*(ulong)pct[ c ]) / fd_progcache_slot_cost( c );
109 2760 : slot_cnt[ c ] += want;
110 2760 : leftover -= want*fd_progcache_slot_cost( c );
111 2760 : }
112 552 : slot_cnt[ 0 ] += leftover / fd_progcache_slot_cost( 0 );
113 :
114 552 : ulong rec_max = 0UL;
115 3864 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) rec_max += slot_cnt[ c ];
116 552 : return rec_max;
117 1122 : }
118 :
119 : FD_FN_CONST ulong
120 4587 : fd_progcache_shmem_align( void ) {
121 4587 : return fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max( fd_ulong_max(
122 4587 : alignof(fd_progcache_shmem_t),
123 4587 : fd_prog_txnm_align() ),
124 4587 : fd_prog_txnp_align() ),
125 4587 : alignof(fd_progcache_txn_t) ),
126 4587 : fd_prog_recm_align() ),
127 4587 : alignof(fd_progcache_rec_t) ),
128 4587 : fd_progcache_val_align() );
129 4587 : }
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 1410 : fd_progcache_shmem_min_sz( ulong txn_max ) {
136 1410 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) return 0UL;
137 :
138 1407 : ulong min_sz = fd_progcache_layout_footprint( txn_max, 0UL, 0UL ) + fd_progcache_val_align();
139 9849 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
140 8442 : min_sz += fd_progcache_cache_class_min( c )*fd_progcache_slot_cost( c );
141 8442 : }
142 :
143 : /* round at 1MiB */
144 1407 : return ( (min_sz>>20UL)+1UL )<<20UL;
145 1410 : }
146 :
147 : ulong
148 : fd_progcache_shmem_footprint( ulong txn_max,
149 897 : ulong progcache_sz ) {
150 897 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) return 0UL;
151 :
152 : /* explicitly validate that progcache_sz is enough */
153 897 : ulong slot_cnt[ FD_PROGCACHE_CACHE_CLASS_CNT ];
154 897 : if( FD_UNLIKELY( !fd_progcache_setup_slots( txn_max, progcache_sz, slot_cnt ) ) ) return 0UL;
155 :
156 333 : return progcache_sz;
157 897 : }
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 213 : ulong progcache_sz ) {
165 213 : fd_progcache_shmem_t * pc = shmem;
166 213 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
167 :
168 213 : if( FD_UNLIKELY( !pc ) ) {
169 0 : FD_LOG_WARNING(( "NULL shmem" ));
170 0 : return NULL;
171 0 : }
172 :
173 213 : 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 213 : if( FD_UNLIKELY( !wksp_tag ) ) {
179 0 : FD_LOG_WARNING(( "bad wksp_tag" ));
180 0 : return NULL;
181 0 : }
182 :
183 213 : if( FD_UNLIKELY( !wksp ) ) {
184 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
185 0 : return NULL;
186 0 : }
187 :
188 213 : if( FD_UNLIKELY( !txn_max || txn_max>FD_PROGCACHE_DEPTH_MAX ) ) {
189 6 : FD_LOG_WARNING(( "invalid txn_max" ));
190 6 : return NULL;
191 6 : }
192 :
193 207 : ulong slot_cnt[ FD_PROGCACHE_CACHE_CLASS_CNT ];
194 207 : ulong rec_max = fd_progcache_setup_slots( txn_max, progcache_sz, slot_cnt );
195 207 : if( FD_UNLIKELY( !rec_max || rec_max>UINT_MAX ) ) {
196 3 : FD_LOG_WARNING(( "invalid progcache_sz (%lu B)", progcache_sz ));
197 3 : return NULL;
198 3 : }
199 204 : ulong arena_sz = fd_progcache_arena_sz( slot_cnt );
200 :
201 204 : FD_SCRATCH_ALLOC_INIT( l, pc+1 );
202 :
203 204 : ulong txn_chain_cnt = fd_prog_txnm_chain_cnt_est( txn_max );
204 204 : void * txn_map = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_txnm_align(), fd_prog_txnm_footprint( txn_chain_cnt ) );
205 204 : void * txn_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_txnp_align(), fd_prog_txnp_footprint( txn_max ) );
206 :
207 204 : ulong rec_chain_cnt = fd_prog_recm_chain_cnt_est( rec_max );
208 204 : void * rec_map = FD_SCRATCH_ALLOC_APPEND( l, fd_prog_recm_align(), fd_prog_recm_footprint( rec_chain_cnt ) );
209 204 : fd_progcache_rec_t * rec_ele = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_progcache_rec_t), sizeof(fd_progcache_rec_t)*rec_max );
210 204 : uchar * arena = FD_SCRATCH_ALLOC_APPEND( l, fd_progcache_val_align(), arena_sz );
211 :
212 204 : ulong layout_sz = fd_progcache_layout_footprint( txn_max, rec_max, arena_sz );
213 204 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_progcache_shmem_align() ) == (ulong)pc + layout_sz );
214 204 : FD_TEST( layout_sz<=progcache_sz );
215 :
216 204 : fd_memset( pc, 0, offsetof(fd_progcache_shmem_t, spill) ); /* spill fields set below */
217 204 : fd_memset( &pc->cache, 0, sizeof(pc->cache) );
218 204 : fd_memset( rec_ele, 0, rec_max*sizeof(fd_progcache_rec_t) );
219 :
220 204 : pc->wksp_tag = wksp_tag;
221 204 : pc->seed = seed;
222 :
223 : /* Fork graph */
224 :
225 204 : pc->txn.map_gaddr = fd_wksp_gaddr_fast( wksp, fd_prog_txnm_new( txn_map, txn_chain_cnt, seed ) );
226 204 : void * txn_pool2 = fd_prog_txnp_new( txn_pool, txn_max );
227 204 : pc->txn.pool_gaddr = fd_wksp_gaddr_fast( wksp, txn_pool2 );
228 204 : fd_progcache_txn_t * txn_ele = fd_prog_txnp_join( txn_pool2 );
229 204 : pc->txn.ele_gaddr = fd_wksp_gaddr_fast( wksp, txn_ele );
230 204 : pc->txn.max = txn_max;
231 204 : pc->txn.child_head_idx = UINT_MAX;
232 204 : pc->txn.child_tail_idx = UINT_MAX;
233 204 : pc->txn.root = fd_progcache_fork_id_initial();
234 204 : pc->txn.seq = fd_progcache_fork_id_initial();
235 204 : fd_rwlock_new( &pc->txn.rwlock );
236 3900 : for( ulong i=0UL; i<txn_max; i++ ) {
237 3696 : fd_rwlock_new( &txn_ele[ i ].lock );
238 3696 : }
239 204 : 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 204 : pc->rec.map_gaddr = fd_wksp_gaddr_fast( wksp, fd_prog_recm_new( rec_map, rec_chain_cnt, seed ) );
246 204 : pc->rec.ele_gaddr = fd_wksp_gaddr_fast( wksp, rec_ele );
247 204 : pc->rec.max = (uint)rec_max;
248 41526 : for( ulong i=0UL; i<rec_max; i++ ) rec_ele[ i ].lock.value = FD_RWLOCK_WRITE_LOCK;
249 :
250 204 : fd_rwlock_new( &pc->spill.lock );
251 204 : pc->spill.rec_used = 0U;
252 204 : pc->spill.spad_used = 0U;
253 :
254 : /* Size-class cache: partition the record array and the value arena by class. */
255 204 : ulong rec_base = 0UL;
256 204 : ulong arena_gaddr = arena_sz ? fd_wksp_gaddr_fast( wksp, arena ) : 0UL;
257 :
258 204 : ulong arena_off = 0UL;
259 1428 : for( ulong c=0UL; c<FD_PROGCACHE_CACHE_CLASS_CNT; c++ ) {
260 1224 : ulong n = slot_cnt[ c ];
261 1224 : ulong slot_sz = fd_progcache_cache_slot_sz[ c ];
262 1224 : pc->cache.class_max [ c ] = n;
263 1224 : pc->cache.rec_base [ c ] = rec_base;
264 1224 : 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 1224 : pc->cache.free_cnt[ c ].val = n;
270 1224 : pc->cache.free_top[ c ].ver_top = n ? (ulong)(uint)( rec_base+n-1UL ) : (ulong)UINT_MAX;
271 42546 : for( ulong s=0UL; s<n; s++ )
272 41322 : rec_ele[ rec_base+s ].free_next = (uint)( s ? ( rec_base+s-1UL ) : (ulong)UINT_MAX );
273 :
274 1224 : if( n ) {
275 1224 : pc->cache.arena_gaddr[ c ] = arena_gaddr + arena_off;
276 1224 : arena_off += n*slot_sz;
277 1224 : }
278 :
279 1224 : rec_base += n;
280 1224 : }
281 :
282 204 : FD_COMPILER_MFENCE();
283 204 : FD_VOLATILE( pc->magic ) = FD_PROGCACHE_SHMEM_MAGIC;
284 204 : FD_COMPILER_MFENCE();
285 :
286 204 : return (void *)pc;
287 :
288 204 : }
289 :
290 : fd_progcache_join_t *
291 : fd_progcache_shmem_join( fd_progcache_join_t * ljoin,
292 246 : fd_progcache_shmem_t * shmem ) {
293 :
294 246 : if( FD_UNLIKELY( !shmem ) ) {
295 0 : FD_LOG_WARNING(( "NULL shmem" ));
296 0 : return NULL;
297 0 : }
298 246 : 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 246 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
303 246 : if( FD_UNLIKELY( !wksp ) ) {
304 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
305 0 : return NULL;
306 0 : }
307 246 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
308 0 : FD_LOG_WARNING(( "bad magic" ));
309 0 : return NULL;
310 0 : }
311 :
312 246 : if( FD_UNLIKELY( !ljoin ) ) {
313 0 : FD_LOG_WARNING(( "NULL join" ));
314 0 : return NULL;
315 0 : }
316 :
317 246 : memset( ljoin, 0, sizeof(fd_progcache_join_t) );
318 :
319 246 : ljoin->shmem = shmem;
320 246 : ljoin->data_base = wksp; /* all value/bookkeeping gaddrs are wksp-relative */
321 :
322 246 : ljoin->txn.map = fd_prog_txnm_join( fd_wksp_laddr( wksp, shmem->txn.map_gaddr ) );
323 246 : if( FD_UNLIKELY( !ljoin->txn.map ) ) {
324 0 : FD_LOG_WARNING(( "fd_prog_txnm_join failed" ));
325 0 : return NULL;
326 0 : }
327 246 : ljoin->txn.pool = fd_prog_txnp_join( fd_wksp_laddr( wksp, shmem->txn.pool_gaddr ) );
328 246 : if( FD_UNLIKELY( !ljoin->txn.pool ) ) {
329 0 : FD_LOG_WARNING(( "fd_prog_txnp_join failed" ));
330 0 : return NULL;
331 0 : }
332 246 : 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 246 : ljoin->rec.ele = fd_wksp_laddr( wksp, shmem->rec.ele_gaddr );
337 246 : ljoin->rec.max = shmem->rec.max;
338 :
339 246 : return ljoin;
340 246 : }
341 :
342 : void *
343 : fd_progcache_shmem_leave( fd_progcache_join_t * ljoin,
344 246 : fd_progcache_shmem_t ** opt_shmem ) {
345 :
346 246 : 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 246 : void * shmem = ljoin->shmem;
353 :
354 246 : memset( ljoin, 0, sizeof(fd_progcache_join_t) );
355 :
356 246 : if( opt_shmem ) *opt_shmem = shmem;
357 246 : return shmem;
358 246 : }
359 :
360 : void *
361 201 : fd_progcache_shmem_delete( fd_progcache_shmem_t * shmem ) {
362 :
363 201 : if( FD_UNLIKELY( !shmem ) ) {
364 0 : FD_LOG_WARNING(( "NULL shmem" ));
365 0 : return NULL;
366 0 : }
367 :
368 201 : 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 201 : fd_wksp_t * wksp = fd_wksp_containing( shmem );
374 201 : if( FD_UNLIKELY( !wksp ) ) {
375 0 : FD_LOG_WARNING(( "shmem must be part of a workspace" ));
376 0 : return NULL;
377 0 : }
378 :
379 201 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
380 0 : FD_LOG_WARNING(( "bad magic" ));
381 0 : return NULL;
382 0 : }
383 :
384 201 : FD_TEST( !shmem->txn.rwlock.value );
385 201 : fd_progcache_txn_t * txn0 = fd_wksp_laddr_fast( wksp, shmem->txn.ele_gaddr );
386 3849 : for( ulong i=0UL; i<shmem->txn.max; i++ ) FD_TEST( !txn0[ i ].lock.value );
387 201 : FD_TEST( !shmem->spill.lock.value );
388 :
389 201 : FD_COMPILER_MFENCE();
390 201 : FD_VOLATILE( shmem->magic ) = 0UL;
391 201 : FD_COMPILER_MFENCE();
392 :
393 201 : return shmem;
394 201 : }
395 :
396 : void *
397 3 : fd_progcache_shmem_delete_fast( fd_progcache_shmem_t * shmem ) {
398 :
399 3 : if( FD_UNLIKELY( !shmem ) ) {
400 0 : FD_LOG_WARNING(( "NULL shmem" ));
401 0 : return NULL;
402 0 : }
403 :
404 3 : 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 3 : 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 3 : if( FD_UNLIKELY( shmem->magic!=FD_PROGCACHE_SHMEM_MAGIC ) ) {
415 0 : FD_LOG_WARNING(( "bad magic" ));
416 0 : return NULL;
417 0 : }
418 :
419 3 : FD_COMPILER_MFENCE();
420 3 : FD_VOLATILE( shmem->magic ) = 0UL;
421 3 : FD_COMPILER_MFENCE();
422 :
423 3 : fd_wksp_free_laddr( shmem );
424 :
425 3 : return shmem;
426 3 : }
|