Line data Source code
1 : #include "fd_rdisp.h"
2 : #include <math.h> /* for the EMA */
3 :
4 : /* The conflict graph that this file builds is not a general DAG, but
5 : the union of several special account-conflict graphs. Each
6 : account-conflict graph has special structure:
7 : ---> 3 --
8 : / \
9 : / v
10 : 1 ---> 2 -----> 4 --> 6 ----> 7
11 : \ ^
12 : \ /
13 : ----> 5 --
14 :
15 : That is, the graph is almost a line, but may have fan-out and fan-in
16 : regions. The tricky part about representing a graph like this
17 : without dynamic memory allocation is that nodes may have arbitrary
18 : in-degree and out-degree. Thus, we use a pretty standard trick and
19 : use sibling pointers, denoted below with dotted lines. Each node
20 : maintains at most one successor pointer and at most one sibling
21 : pointer. Although not shown below, the sibling pointers are
22 : circularly linked, so 5's sibling is 3. Additionally, though not
23 : shown below, the child of the last node (7) stores information about
24 : what account address all this is for, which facilitates deleting the
25 : last node in the graph.
26 :
27 :
28 : --> 3 --
29 : / ^ \
30 : / : \
31 : / V v
32 : 1 ---> 2 4 --> 6 ----> 7
33 : ^ ^
34 : : /
35 : V /
36 : 5 --
37 :
38 : The normal edge 2->3 along with the sibling edge 3..>4 implies a
39 : normal edge 2->4. That then transitively implies an edge 2->5.
40 :
41 : We want each node to maintain a count of its in-degree so that we
42 : know when it can be executed. The implied edges also count for the
43 : in-degree. In this example, node 1 has in-degree 0, node 6 has
44 : in-degree 3, and the rest have in-degree 1.
45 :
46 : Maintaining each account-conflict graph is relatively easy given the
47 : operations we want to support. Only the details about sibling edges
48 : are worth mentioning. For example, when deleting node 2, we
49 : decrement the in-degree count for its successor, and then follow the
50 : sibling pointers, decrementing all the in-degree counts as we go to
51 : mark the deletion of the implied edges. Sibling edges also need to
52 : be doubly linked, so that e.g. nodes 3 and 5 can be re-linked in O(1)
53 : if node 4 is deleted. They're also circularly linked as well for
54 : convenience.
55 :
56 : When building the graph, we maintain a map of account to the last
57 : node that references it, whether that was a read or write, and
58 : whether there are any writers to that account in the graph right now.
59 : If the new node reads from an account that was last read, the new
60 : node becomes a sibling of the last read, with in-degree increased if
61 : there are any writers. Otherwise, it becomes a successor of the node
62 : that last referenced the account. */
63 :
64 : /* For a task like this with lots of graph traversal and pointer
65 : chasing, performance is typically limited by memory latency. That
66 : means that the more that can fit in cache, the better the
67 : performance. This implementation uses a lot of bit-packing to
68 : improve cache footprint. */
69 :
70 : /* The following structs are all very local to this compilation unit,
71 : and so they don't have globally acceptable type names (e.g.
72 : fd_rdisp_edge_t). */
73 :
74 155445 : #define MAX_ACCT_PER_TXN FD_TXN_ACCT_ADDR_MAX
75 : FD_STATIC_ASSERT( MAX_ACCT_PER_TXN<=128UL, max_acct_per_txn );
76 :
77 : /* edge_t: Fields typed edge_t represent an edge in one of the parallel
78 : account-conflict DAGs. Each transaction stores a list of all its
79 : outgoing edges. The type is actually a union of bitfield, but C
80 : bitfields are gross, so we just do it manually with macros. If the
81 : high bit is set, that means the transaction storing this edge_t value
82 : is the last in this specific DAG, and the lower 31 bits of the
83 : value are an index in the map_pool for the account pubkey for this
84 : DAG. See the comments about the hidden edge outgoing from node 7 in
85 : the DAG at the top of this file for an example.
86 : If the high bit is not set, then the next 23 bits store the
87 : destination of the edge, represented by its index in the pool of
88 : transactions. Then the lowest 8 bits store the account index within
89 : that transaction of the edge that is part of the same
90 : account-specific DAG as this edge. Because the max depth is 2^23-1,
91 : and each transaction can reference FD_TXN_ACCT_ADDR_MAX accounts,
92 : the max accounts that can be referenced fits in 30 bits.
93 : The proper type would be something like
94 : typedef union {
95 : struct {
96 : uint is_last:1;
97 : uint map_pool_idx:31;
98 : } last;
99 : struct {
100 : uint is_last:1;
101 : uint txn_idx:23;
102 : uint acct_idx:8;
103 : } e;
104 : } edge_t;
105 :
106 : */
107 : typedef uint edge_t;
108 :
109 :
110 : /* fd_rdisp_txn is the representation of a transaction as a node in the
111 : DAG. */
112 : struct fd_rdisp_txn {
113 : /* in_degree: The total number of edges summed across all account DAGs
114 : with this node as their destination. In the worst case, all the
115 : other transactions in the pool read from each of the max number of
116 : accounts that this transaction writes to, so there are
117 : MAX_ACCT_PER_TXN*depth edges that come into this node, which fits in
118 : about 30 bits, so we have some room for special values. If
119 : in_degree is one of the following values, then
120 : the transaction is: */
121 47354940 : #define IN_DEGREE_FREE (UINT_MAX )
122 11532921 : #define IN_DEGREE_UNSTAGED (UINT_MAX-1U)/* unstaged, not dispatched */
123 552831 : #define IN_DEGREE_DISPATCHED (UINT_MAX-2U)/* staged, dispatched */
124 11532603 : #define IN_DEGREE_UNSTAGED_DISPATCHED (UINT_MAX-3U)/* unstaged, dispatched */
125 11532288 : #define IN_DEGREE_ZOMBIE (UINT_MAX-4U)/* zombie */
126 : /* a transaction that is staged and dispatched is must have an
127 : in_degree of 0. in_degree isn't a meaningful concept for unstaged
128 : transactions. */
129 : uint in_degree;
130 :
131 : /* score: integer part stores how many transactions in the block must
132 : have completed before this transaction can be scheduled. This is
133 : useful for transactions marked as serializing. The fractional part
134 : gives some measure of how urgent the transaction is, where lower is
135 : more urgent. This means we can't have more transactions in a block
136 : marked as serializing than the first integer that a float cannot
137 : represent. That value is about 16M, which is much higher than the
138 : maximum number of transactions in a block, so this is not a
139 : problem. If in the very rare case that there are more than just a
140 : few serializing transactions, we will lose a bit of precision for
141 : the fractional portion, which makes total sense; if there's not
142 : much room for parallel execution, having the optimal parallel
143 : execution is not very important. */
144 : float score;
145 :
146 : /* edge_cnt_etc:
147 : 0xFFFF0000 (16 bits) for linear block number,
148 : 0x0000C000 (2 bits) for concurrency lane,
149 : 0x00003F80 (7 bits) for r_cnt
150 : 0x0000007F (7 bits) for w_cnt_1. Transactions have at least one
151 : writable account and at most MAX_ACCT_PER_TXN total accounts, so
152 : storing w_cnt_1 = w_cnt - 1 fits in 7 bits. */
153 : union {
154 : uint edge_cnt_etc;
155 : /* edge_cnt_etc is only used when the transaction is STAGED and
156 : PENDING, READY, or DISPATCHED. If UNSTAGED or FREE, the next
157 : pointer is also here. It can't be UNSTAGED and FREE at the same
158 : time, so there's no conflict with storage there either. */
159 : uint unstaged_next;
160 : uint free_next;
161 : /* If ZOMBIE, pointer back to block is here. No storage conflict
162 : because ZOMBIE is an exclusive state. */
163 : uint block_idx;
164 : };
165 :
166 :
167 : /* When a transaction writes to an account, it only creates one
168 : link. When a transaction reads from an account, we need the full
169 : doubly linked list with its siblings, so it creates 3 edges
170 : (child, next, prev). All edges from writable accounts come first,
171 : and we keep track of how many there are. In the worst case, all
172 : accounts are reads, so we size it appropriately. */
173 : edge_t edges[3UL*MAX_ACCT_PER_TXN]; /* addressed [0, w_cnt_1+1+3*r_cnt) */
174 : };
175 : typedef struct fd_rdisp_txn fd_rdisp_txn_t;
176 :
177 : #define EDGE_IS_LAST(x) ((x)&0x80000000U)
178 :
179 : /* Two more definitions:
180 : An edge index is an array position within the edges array. An
181 : account index is a position within a transaction's account addresses,
182 : reordered so that the writable ones come first. Since writable
183 : accounts use one position in the edges array per account address,
184 : these often coincide. */
185 :
186 :
187 : /* FOLLOW_EDGE and FOLLOW_EDGE_TXN are helper macros for dealing with
188 : edges. Given an edge_t x, and transaction pool base, FOLLOW_EDGE_TXN
189 : returns a pointer to transaction that the edge points to; FOLLOW_EDGE
190 : returns a pointer to the (first) edge_t within that transaction that
191 : is part of the same DAG as this edge. FOLLOW_EDGE and
192 : FOLLOW_EDGE_TXN must not be called if EDGE_IS_LAST is non-zero.
193 :
194 : Then the edge index, i.e. the position in the edges array of the
195 : (first) edge for an account index is:
196 : acct_idx if acct_idx<=w_cnt_1
197 : w_cnt_1+1 + 3*(acct_idx-w_cnt_1-1) else.
198 : Simplifying gives
199 : acct_idx + 2*signed_max( 0, acct_idx-w_cnt_1-1 ).
200 : In doing this calculation, we also basically get for free whether the
201 : edge is for a writable account or a readonly account, so we return
202 : that as well via the w parameter. If the child transaction only reads
203 : the account address for this DAG, then the next and prev pointers can
204 : be accessed using the returned value +1 and +2, respectively. */
205 38507106 : #define FOLLOW_EDGE(base, x, w) (__extension__({ \
206 38507106 : uint __e = (x); \
207 38507106 : fd_rdisp_txn_t * __txn = ((base)+(__e>>8)); \
208 38507106 : uint __w_cnt_1 = __txn->edge_cnt_etc & 0x7FU; \
209 38507106 : uint __idx = (__e & 0xFFU); \
210 38507106 : (w) = __idx<=__w_cnt_1; \
211 38507106 : (void)(w); /* not robust... */ \
212 38507106 : __txn->edges + __idx + 2*fd_int_max( 0, (int)(__idx)-(int)(__w_cnt_1)-1 ); }))
213 31259313 : #define FOLLOW_EDGE_TXN(base, x) ( (base)+((x)>>8) )
214 :
215 : /* The pool and slist are almost the same, but they are used
216 : differently, so keep them as different structures for now. */
217 :
218 : #define POOL_NAME pool
219 63 : #define POOL_T fd_rdisp_txn_t
220 : #define POOL_IDX_T uint
221 1310289 : #define POOL_NEXT free_next
222 : #define POOL_SENTINEL 1
223 : #include "../../util/tmpl/fd_pool.c"
224 :
225 : #define SLIST_NAME unstaged_txn_ll
226 : #define SLIST_ELE_T fd_rdisp_txn_t
227 318 : #define SLIST_IDX_T uint
228 1272 : #define SLIST_NEXT unstaged_next
229 : #include "../../util/tmpl/fd_slist.c"
230 :
231 : #define DLIST_IDX_T uint
232 6 : #define DLIST_PREV edges[0]
233 6 : #define DLIST_NEXT edges[1]
234 : #define DLIST_NAME zombie_dlist
235 : #define DLIST_ELE_T fd_rdisp_txn_t
236 : #include "../../util/tmpl/fd_dlist.c"
237 :
238 :
239 : /* ACCT_INFO_FLAG: It's a bit unfortunate that we have to maintain these
240 : flags, but basically we need to be able to distinguish the case where
241 : there are only readers so that we don't increment in_degree when
242 : adding a new fd_rdisp_txn. If we have any writers, the only way to
243 : transition into a state where there are only readers is to complete
244 : the last writer. We know we are in this case when the completed
245 : node's child doesn't have a child, and the completed node's child is
246 : a reader, as indicated by the LAST_REF_WAS_WRITE bit.
247 : LAST_REF_WAS_WRITE also has the advantage of being easy to
248 : maintain. */
249 6087027 : #define ACCT_INFO_FLAG_LAST_REF_WAS_WRITE(lane) (((uchar)1)<<(2*(lane)))
250 5994810 : #define ACCT_INFO_FLAG_ANY_WRITERS( lane) (((uchar)2)<<(2*(lane)))
251 :
252 : /* acct_info_t is a node in a map_chain that contains the metadata for a
253 : single account address's conflict graph DAG. In particular, it
254 : contains the information needed to know where to insert a node that
255 : reads from or writes to the account. The objects of this type follow
256 : this state machine:
257 :
258 : FREE -----> ACTIVE ----> CACHED
259 : ^ |
260 : |-------------
261 : When FREE, it is in free_acct_dlist only. When ACTIVE, it is in
262 : acct_map only. When CACHED, it is in both free_acct_dlist and
263 : cached_acct_map.
264 : */
265 : struct acct_info {
266 : /* key, next, and prev are the map_chain fields. Used in the ACTIVE
267 : and CACHED states. next and prev set to 0 when in the FREE state.
268 : Element 0 is a sentinel and isn't inserted to the free_acct_dlist,
269 : so this is unambiguous. */
270 : fd_acct_addr_t key;
271 : uint next;
272 : uint prev;
273 :
274 : union {
275 : struct {
276 : /* This is effectively a pointer to the last node in the DAG for
277 : this pubkey, one for each staging lane.
278 : EDGE_IS_LAST(FOLLOW_EDGE(base, last_reference[i])) is non-zero.
279 : */
280 : edge_t last_reference[4];
281 :
282 : }; /* When in the ACTIVE state */
283 : struct {
284 : uint free_ll_next;
285 : uint free_ll_prev;
286 : /* 8 bytes of padding here */
287 : }; /* When not in the ACTIVE state, used by the free_acct_dlist */
288 : };
289 : /* flags: a combination of ACCT_INFO_FLAG_* bitfields above. Used
290 : when ACTIVE. */
291 : uint flags:8;
292 :
293 :
294 : /* We want to dispatch the READY transactions in an order that
295 : maximizes parallelism, but we also want to be able to start
296 : dispatching transactions decently well before we have the full
297 : conflict graph. We can do that because we know that contentious
298 : accounts tend to stay contentious and uncontentious accounts tend
299 : to stay uncontentious.
300 :
301 : To accomplish this, we maintain a special EMA. Let x_i be 1 if
302 : transaction i references it and 0 if not. If we squint and assume
303 : the transactions are independent and all drawn from some
304 : distribution (which is not true, but that's why we squint), an EMA
305 : of x_i estimates the probability that the next transaction
306 : references this account. How we use this value is detailed later.
307 :
308 : We can't update this value for every pubkey for each transaction,
309 : so we maintain it in a lazy way, by applying updates only when we
310 : need to read the value, which also happens to be every time we want
311 : to add a 1 value to the EMA. We then just need to maintain the
312 : last index i at which x_i was updated and the current value. We
313 : only have 24 bits for the index, which means that we can't maintain
314 : it in a fork-aware way, which doesn't seem like a problem. Also,
315 : it's possible it can overflow, and that can result in an incorrect
316 : value, but that means the account is only referenced ~ 1/2^24
317 : transactions, which is also fine.
318 :
319 : last_ref is in the domain of global_inserted_txn_cnt. ema_refs is
320 : in [0, 1]. Both fields are used in ACTIVE and CACHED. Maintaining
321 : these fields is actually the main reason CACHED exists. */
322 : uint last_ref:24;
323 : float ema_refs;
324 : };
325 : typedef struct acct_info acct_info_t;
326 :
327 : FD_STATIC_ASSERT( sizeof(acct_info_t)==64UL, acct_info_t );
328 :
329 : /* For the acct_map and the free_acct_map */
330 : #define MAP_NAME acct_map
331 1759350 : #define MAP_ELE_T acct_info_t
332 119613072 : #define MAP_IDX_T uint
333 : #define MAP_KEY_T fd_acct_addr_t
334 225795540 : #define MAP_KEY_HASH(k,s) fd_hash( (s), (k)->b, 32UL )
335 113182446 : #define MAP_KEY_EQ(k0,k1) (!memcmp( (k0)->b, (k1)->b, 32UL ))
336 : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
337 : #include "../../util/tmpl/fd_map_chain.c"
338 :
339 :
340 : #define DLIST_IDX_T uint
341 15226614 : #define DLIST_PREV free_ll_prev
342 15226614 : #define DLIST_NEXT free_ll_next
343 : #define DLIST_NAME free_dlist
344 : #define DLIST_ELE_T acct_info_t
345 : #include "../../util/tmpl/fd_dlist.c"
346 :
347 :
348 : struct pending_prq_ele {
349 : /* lower score means should be scheduled sooner. Integer part has a
350 : special meaning as explained above. */
351 : float score;
352 :
353 : uint linear_block_number;
354 : uint txn_idx;
355 :
356 : /* It seems like we should be able to get this whole struct in 8
357 : bytes, but we're a few bits over.
358 :
359 : 23 bits for txn_idx
360 : 16 bit for linear_block_number
361 : Then the integer part of the score needs to be at least 18 or 19
362 : bits, but we can't use a custom floating point number. */
363 : };
364 :
365 : typedef struct pending_prq_ele pending_prq_ele_t;
366 :
367 :
368 : #define PRQ_NAME pending_prq
369 13532583 : #define PRQ_T pending_prq_ele_t
370 : #define PRQ_EXPLICIT_TIMEOUT 0
371 : /* returns 1 if x is strictly after y */
372 12067920 : #define PRQ_AFTER(x,y) (__extension__( { \
373 12067920 : int cmp0 = (int)(x).linear_block_number - (int)(y).linear_block_number; \
374 12067920 : fd_int_if( cmp0!=0, cmp0>0, (x).score>(y).score ); \
375 12067920 : }))
376 : #include "../../util/tmpl/fd_prq.c"
377 :
378 : /* fd_rdisp_blockinfo_t maintains a little metadata about transactions for each
379 : slot. */
380 : struct fd_rdisp_blockinfo {
381 : FD_RDISP_BLOCK_TAG_T block;
382 : uint linear_block_number;
383 :
384 : uint insert_ready:1;
385 : uint schedule_ready:1;
386 : uint staged:1;
387 : uint staging_lane:2; /* ignored if staged==0 */
388 : uint last_insert_was_serializing:1;
389 :
390 : uint inserted_cnt;
391 : uint dispatched_cnt;
392 : uint completed_cnt;
393 : uint last_serializing;
394 :
395 : uint map_chain_next;
396 : uint ll_next;
397 : unstaged_txn_ll_t ll[ 1 ]; /* used only when unstaged */
398 : zombie_dlist_t zombie_list[ 1 ];
399 : };
400 : typedef struct fd_rdisp_blockinfo fd_rdisp_blockinfo_t;
401 :
402 : #define POOL_NAME block_pool
403 63 : #define POOL_T fd_rdisp_blockinfo_t
404 : #define POOL_IDX_T uint
405 988128 : #define POOL_NEXT ll_next
406 : #define POOL_SENTINEL 1
407 : #include "../../util/tmpl/fd_pool.c"
408 :
409 : #define MAP_NAME block_map
410 : #define MAP_ELE_T fd_rdisp_blockinfo_t
411 : #define MAP_KEY_T FD_RDISP_BLOCK_TAG_T
412 395694 : #define MAP_KEY block
413 4045578 : #define MAP_NEXT map_chain_next
414 5304921 : #define MAP_IDX_T uint
415 : #include "../../util/tmpl/fd_map_chain.c"
416 :
417 : #define SLIST_NAME block_slist
418 : #define SLIST_ELE_T fd_rdisp_blockinfo_t
419 395679 : #define SLIST_IDX_T uint
420 791361 : #define SLIST_NEXT ll_next
421 : #include "../../util/tmpl/fd_slist.c"
422 :
423 : struct fd_rdisp_unstaged {
424 : FD_RDISP_BLOCK_TAG_T block;
425 :
426 : uint writable_cnt;
427 : uint readonly_cnt;
428 : fd_acct_addr_t keys[MAX_ACCT_PER_TXN];
429 : };
430 : typedef struct fd_rdisp_unstaged fd_rdisp_unstaged_t;
431 :
432 : typedef struct {
433 : pending_prq_ele_t * pending;
434 : ulong linear_block_number;
435 : block_slist_t block_ll[1];
436 : ulong inserted_cnt;
437 : ulong dispatched_cnt;
438 : ulong completed_cnt;
439 : } per_lane_info_t;
440 :
441 : /* We maintain two maps from pubkeys to acct_info_t. The first one is
442 : the main acct_map, just called acct_map. All pubkeys in this map
443 : have >0 references in the one of the staging lane DAGs. When an
444 : account goes to 0 references, it gets removed from main map_chain and
445 : moved to free map_chain, called free_acct_map. The free_acct_map
446 : exists to maintain the reference count EMA information lazily.
447 : Unless we need the acct_info_t for something in the DAG, we might as
448 : well maintain the EMA info.
449 :
450 : When we start up, all the acct_info_t structs are in the
451 : free_acct_dlist. Whenever something is added to the free_acct_map,
452 : it's also added to the tail of the free_acct_dlist. When we need an
453 : acct_info_t that's not in the free_acct_map, we pop the head of the
454 : free_acct_dlist. In general, the free_acct_dlist contains everything
455 : in the free_acct_map, potentially plus some elements that have never
456 : been used; all acct_info_t objects are in exactly one of the main
457 : acct_map and the free_acct_dlist (not free_acct_map). See
458 : acct_info_t for more information about this. */
459 :
460 : struct fd_rdisp {
461 : ulong depth;
462 : ulong block_depth;
463 :
464 : ulong global_insert_cnt;
465 : ulong unstaged_lblk_num;
466 :
467 : /* pool: an fd_pool, indexed [0, depth+1), with 0 being a sentinel */
468 : fd_rdisp_txn_t * pool;
469 : fd_rdisp_unstaged_t * unstaged; /* parallel to pool with additional info */
470 :
471 : block_map_t * blockmap; /* map chain */
472 : fd_rdisp_blockinfo_t * block_pool;
473 :
474 : int free_lanes; /* a bitmask */
475 : per_lane_info_t lanes[4];
476 :
477 : acct_map_t * acct_map;
478 : acct_map_t * free_acct_map;
479 : /* acct_pool is not an fd_pool, but is just a flat array, since we
480 : don't need to acquire and release from it because of the dlist. */
481 : acct_info_t * acct_pool;
482 : free_dlist_t free_acct_dlist[1];
483 : };
484 :
485 : typedef struct fd_rdisp fd_rdisp_t;
486 :
487 :
488 : #define ACCT_ITER_TO_PTR( iter ) (__extension__( { \
489 : ulong __idx = fd_txn_acct_iter_idx( iter ); \
490 : fd_ptr_if( __idx<fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ), accts, alt_adj )+__idx; \
491 : }))
492 :
493 :
494 549 : ulong fd_rdisp_align( void ) { return 128UL; }
495 :
496 : ulong
497 : fd_rdisp_footprint( ulong depth,
498 51 : ulong block_depth ) {
499 51 : if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH) | (depth<2UL) |
500 51 : (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return 0UL;
501 :
502 51 : ulong chain_cnt = block_map_chain_cnt_est( block_depth );
503 51 : ulong acct_depth = depth*MAX_ACCT_PER_TXN;
504 51 : ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
505 :
506 51 : ulong l = FD_LAYOUT_INIT;
507 51 : l = FD_LAYOUT_APPEND( l, fd_rdisp_align(), sizeof(fd_rdisp_t) );
508 51 : l = FD_LAYOUT_APPEND( l, pool_align(), pool_footprint ( depth+1UL ) ); /* pool */
509 51 : l = FD_LAYOUT_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL ) ); /* unstaged */
510 51 : l = FD_LAYOUT_APPEND( l, block_map_align(), block_map_footprint ( chain_cnt ) ); /* blockmap */
511 51 : l = FD_LAYOUT_APPEND( l, block_pool_align(), block_pool_footprint ( block_depth+1UL ) ); /* block_pool */
512 51 : l = FD_LAYOUT_APPEND( l, pending_prq_align(), 4UL*pending_prq_footprint ( depth ) ); /* pending */
513 51 : l = FD_LAYOUT_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) ); /* acct_map */
514 51 : l = FD_LAYOUT_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) ); /* free_acct_map */
515 51 : l = FD_LAYOUT_APPEND( l, alignof(acct_info_t), (acct_depth+1UL)*sizeof(acct_info_t) ); /* acct_pool */
516 51 : return FD_LAYOUT_FINI( l, fd_rdisp_align() );
517 51 : }
518 :
519 : void *
520 : fd_rdisp_new( void * mem,
521 : ulong depth,
522 : ulong block_depth,
523 21 : ulong seed ) {
524 21 : if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH) | (depth<2UL) |
525 21 : (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return NULL;
526 :
527 21 : ulong chain_cnt = block_map_chain_cnt_est( block_depth );
528 21 : ulong acct_depth = depth*MAX_ACCT_PER_TXN;
529 21 : ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
530 :
531 21 : FD_SCRATCH_ALLOC_INIT( l, mem );
532 21 : fd_rdisp_t * disp = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(), sizeof(fd_rdisp_t) );
533 21 : void * _pool = FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint ( depth+1UL ) );
534 21 : void * _unstaged = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL ) );
535 21 : void * _bmap = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(), block_map_footprint ( chain_cnt ) );
536 21 : void * _bpool = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(), block_pool_footprint ( block_depth+1UL ) );
537 21 : uchar * _pending = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(), 4UL*pending_prq_footprint ( depth ) );
538 21 : void * _acct_map = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) );
539 21 : void * _freea_map = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) );
540 21 : acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t), (acct_depth+1UL)*sizeof(acct_info_t) );
541 21 : FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
542 :
543 21 : disp->depth = depth;
544 21 : disp->block_depth = block_depth;
545 21 : disp->global_insert_cnt = 0UL;
546 21 : disp->unstaged_lblk_num = 0UL;
547 :
548 21 : fd_rdisp_txn_t * temp_pool_join = pool_join( pool_new( _pool, depth+1UL ) );
549 203991 : for( ulong i=0UL; i<depth+1UL; i++ ) temp_pool_join[ i ].in_degree = IN_DEGREE_FREE;
550 21 : pool_leave( temp_pool_join );
551 :
552 21 : memset( _unstaged, '\0', sizeof(fd_rdisp_unstaged_t)*(depth+1UL) );
553 :
554 21 : block_map_new ( _bmap, chain_cnt, seed );
555 21 : block_pool_new( _bpool, block_depth+1UL );
556 :
557 21 : fd_rdisp_blockinfo_t * bpool_temp_join = block_pool_join( _bpool );
558 196767 : for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_new( bpool_temp_join[ i ].zombie_list );
559 21 : block_pool_leave( bpool_temp_join );
560 :
561 21 : disp->free_lanes = 0xF;
562 105 : for( ulong i=0UL; i<4UL; i++ ) {
563 84 : pending_prq_new( _pending, depth );
564 84 : _pending += pending_prq_footprint( depth );
565 :
566 84 : disp->lanes[i].linear_block_number = 0UL;
567 84 : disp->lanes[i].inserted_cnt = 0U;
568 84 : disp->lanes[i].dispatched_cnt = 0U;
569 84 : disp->lanes[i].completed_cnt = 0U;
570 84 : }
571 :
572 21 : acct_map_new( _acct_map, acct_chain_cnt, fd_ulong_hash( seed+1UL ) );
573 21 : acct_map_new( _freea_map, acct_chain_cnt, fd_ulong_hash( seed+2UL ) );
574 :
575 21 : free_dlist_t * temp_join = free_dlist_join( free_dlist_new( disp->free_acct_dlist ) );
576 13052757 : for( ulong i=1UL; i<acct_depth+1UL; i++ ) {
577 13052736 : apool[ i ].next = apool[ i ].prev = 0U;
578 13052736 : free_dlist_idx_push_tail( disp->free_acct_dlist, i, apool );
579 13052736 : }
580 21 : free_dlist_leave( temp_join );
581 :
582 21 : return disp;
583 21 : }
584 :
585 : fd_rdisp_t *
586 21 : fd_rdisp_join( void * mem ) {
587 21 : fd_rdisp_t * disp = (fd_rdisp_t *)mem;
588 :
589 21 : ulong depth = disp->depth;
590 21 : ulong block_depth = disp->block_depth;
591 21 : ulong chain_cnt = block_map_chain_cnt_est( block_depth );
592 21 : ulong acct_depth = depth*MAX_ACCT_PER_TXN;
593 21 : ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
594 :
595 21 : FD_SCRATCH_ALLOC_INIT( l, mem );
596 21 : /* */ FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(), sizeof(fd_rdisp_t) );
597 21 : void * _pool = FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint ( depth+1UL ) );
598 21 : void * _unstaged = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL ) );
599 21 : void * _bmap = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(), block_map_footprint ( chain_cnt ) );
600 21 : void * _bpool = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(), block_pool_footprint ( block_depth+1UL ) );
601 21 : uchar * _pending = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(), 4UL*pending_prq_footprint ( depth ) );
602 21 : void * _acct_map = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) );
603 21 : void * _freea_map = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(), acct_map_footprint ( acct_chain_cnt ) );
604 21 : acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t), (acct_depth+1UL)*sizeof(acct_info_t) );
605 21 : FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
606 :
607 21 : disp->pool = pool_join( _pool );
608 21 : disp->unstaged = (fd_rdisp_unstaged_t *)_unstaged;
609 21 : disp->blockmap = block_map_join( _bmap );
610 21 : disp->block_pool = block_pool_join( _bpool );
611 :
612 196767 : for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_join( disp->block_pool[ i ].zombie_list );
613 :
614 105 : for( ulong i=0UL; i<4UL; i++ ) {
615 84 : disp->lanes[i].pending = pending_prq_join( _pending );
616 84 : _pending += pending_prq_footprint( depth );
617 84 : }
618 :
619 21 : disp->acct_map = acct_map_join( _acct_map );
620 21 : disp->free_acct_map = acct_map_join( _freea_map );
621 21 : disp->acct_pool = apool;
622 21 : free_dlist_join( disp->free_acct_dlist );
623 :
624 21 : return disp;
625 21 : }
626 :
627 : static inline void
628 : free_lane( fd_rdisp_t * disp,
629 2460 : ulong staging_lane ) {
630 2460 : disp->free_lanes |= 1<<staging_lane;
631 2460 : per_lane_info_t * l = disp->lanes+staging_lane;
632 2460 : FD_TEST( pending_prq_cnt( l->pending )==0UL );
633 2460 : l->linear_block_number = 0UL;
634 2460 : l->inserted_cnt = 0UL;
635 2460 : l->dispatched_cnt = 0UL;
636 2460 : l->completed_cnt = 0UL;
637 2460 : block_slist_delete( block_slist_leave( l->block_ll ) );
638 2460 : }
639 :
640 : static inline void
641 : alloc_lane( fd_rdisp_t * disp,
642 2463 : ulong staging_lane ) {
643 2463 : disp->free_lanes &= ~(1<<staging_lane);
644 2463 : block_slist_join( block_slist_new( disp->lanes[staging_lane].block_ll ) );
645 2463 : }
646 :
647 : ulong
648 : fd_rdisp_suggest_staging_lane( fd_rdisp_t const * disp,
649 : FD_RDISP_BLOCK_TAG_T parent_block,
650 0 : int duplicate ) {
651 :
652 : /* 1. If it's a duplicate, suggest FD_RDISP_UNSTAGED */
653 0 : if( FD_UNLIKELY( duplicate ) ) return FD_RDISP_UNSTAGED;
654 :
655 : /* 2. If parent is the last block in any existing staging lane, suggest
656 : that lane */
657 0 : fd_rdisp_blockinfo_t const * block_pool = disp->block_pool;
658 0 : fd_rdisp_blockinfo_t const * block = block_map_ele_query_const( disp->blockmap, &parent_block, NULL, block_pool );
659 0 : if( FD_LIKELY( block && block->insert_ready && block->staged ) ) return block->staging_lane;
660 :
661 : /* 3. If there is at least one free lane, suggest a free lane */
662 0 : if( FD_LIKELY( disp->free_lanes!=0 ) ) return (ulong)fd_uint_find_lsb( (uint)disp->free_lanes );
663 :
664 : /* 4. Else, suggest FD_RDISP_UNSTAGED */
665 0 : return FD_RDISP_UNSTAGED;
666 0 : }
667 :
668 : int
669 : fd_rdisp_add_block( fd_rdisp_t * disp,
670 : FD_RDISP_BLOCK_TAG_T new_block,
671 395694 : ulong staging_lane ) {
672 395694 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
673 :
674 395694 : if( FD_UNLIKELY( !block_pool_free( block_pool ) ) ) return -1;
675 395694 : if( FD_UNLIKELY( ULONG_MAX!=block_map_idx_query_const( disp->blockmap, &new_block, ULONG_MAX, block_pool ) ) ) return -1;
676 395688 : fd_rdisp_blockinfo_t * block = block_pool_ele_acquire( block_pool );
677 395688 : block->block = new_block;
678 395688 : block_map_ele_insert( disp->blockmap, block, block_pool );
679 :
680 395688 : block->insert_ready = 1;
681 395688 : block->staged = staging_lane!=FD_RDISP_UNSTAGED;
682 395688 : block->staging_lane = (uint)(staging_lane & 0x3UL);
683 395688 : block->last_insert_was_serializing = 0U;
684 :
685 395688 : block->inserted_cnt = 0U;
686 395688 : block->dispatched_cnt = 0U;
687 395688 : block->completed_cnt = 0U;
688 395688 : block->last_serializing = 0U;
689 :
690 395688 : if( FD_UNLIKELY( staging_lane==FD_RDISP_UNSTAGED ) ) {
691 18 : block->schedule_ready = 1;
692 18 : block->linear_block_number = (uint)disp->unstaged_lblk_num++;
693 18 : unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll ) );
694 395670 : } else {
695 395670 : block->linear_block_number = (uint)disp->lanes[staging_lane].linear_block_number++;
696 395670 : block->schedule_ready = (uint)(1 & (disp->free_lanes >> staging_lane));
697 :
698 395670 : if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
699 :
700 395670 : block_slist_t * sl = disp->lanes[staging_lane].block_ll;
701 395670 : if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
702 395670 : block_slist_ele_push_tail( sl, block, block_pool );
703 395670 : }
704 395688 : return 0;
705 395694 : }
706 :
707 :
708 :
709 : int
710 : fd_rdisp_remove_block( fd_rdisp_t * disp,
711 395670 : FD_RDISP_BLOCK_TAG_T block_tag ) {
712 395670 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
713 :
714 395670 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
715 395670 : if( FD_UNLIKELY( block==NULL ) ) return -1;
716 :
717 395661 : FD_TEST( block->schedule_ready );
718 395661 : FD_TEST( block->completed_cnt==block->inserted_cnt );
719 395661 : FD_TEST( zombie_dlist_is_empty( block->zombie_list, disp->pool ) );
720 :
721 395661 : if( FD_LIKELY( block->staged ) ) {
722 395652 : ulong staging_lane = (ulong)block->staging_lane;
723 395652 : block_slist_t * sl = disp->lanes[staging_lane].block_ll;
724 :
725 395652 : FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
726 395652 : block_slist_idx_pop_head( sl, block_pool );
727 395652 : if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
728 2436 : else free_lane( disp, staging_lane );
729 395652 : } else {
730 9 : unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
731 9 : }
732 395661 : block_pool_idx_release( block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, block_pool ) );
733 :
734 395661 : return 0;
735 395661 : }
736 :
737 :
738 : int
739 : fd_rdisp_abandon_block( fd_rdisp_t * disp,
740 15 : FD_RDISP_BLOCK_TAG_T block_tag ) {
741 15 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
742 :
743 15 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &block_tag, NULL, disp->block_pool );
744 15 : if( FD_UNLIKELY( block==NULL ) ) return -1;
745 :
746 12 : FD_TEST( block->schedule_ready );
747 12 : FD_TEST( block->dispatched_cnt==block->completed_cnt ); /* TODO: remove this when it can call complete properly */
748 21 : while( block->completed_cnt<block->inserted_cnt ) {
749 : /* because there is nothing DISPATCHED, there has to be something
750 : READY */
751 9 : ulong txn = fd_rdisp_get_next_ready( disp, block_tag );
752 9 : FD_TEST( txn );
753 9 : fd_rdisp_complete_txn( disp, txn, 1 );
754 9 : }
755 12 : while( !zombie_dlist_is_empty( block->zombie_list, disp->pool ) ) {
756 0 : fd_rdisp_complete_txn( disp, zombie_dlist_idx_pop_head( block->zombie_list, disp->pool ), 1 );
757 0 : }
758 :
759 12 : if( FD_LIKELY( block->staged ) ) {
760 12 : ulong staging_lane = (ulong)block->staging_lane;
761 12 : block_slist_t * sl = disp->lanes[staging_lane].block_ll;
762 :
763 12 : FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
764 12 : block_slist_idx_pop_head( sl, block_pool );
765 12 : if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
766 9 : else free_lane( disp, staging_lane );
767 12 : } else {
768 0 : unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
769 0 : }
770 12 : block_pool_idx_release( disp->block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, disp->block_pool ) );
771 :
772 12 : return 0;
773 12 : }
774 :
775 : static void
776 : add_edges( fd_rdisp_t * disp,
777 : fd_rdisp_txn_t * ele,
778 : fd_acct_addr_t const * addr,
779 : ulong addr_cnt,
780 : uint staging_lane,
781 : int writable,
782 : int update_score );
783 : /* updates in_degree, edge_cnt_etc */
784 :
785 : int
786 : fd_rdisp_promote_block( fd_rdisp_t * disp,
787 : FD_RDISP_BLOCK_TAG_T block_tag,
788 12 : ulong staging_lane ) {
789 12 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
790 12 : per_lane_info_t * lane = disp->lanes + staging_lane;
791 12 : block_slist_t * sl = lane->block_ll;
792 :
793 12 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
794 12 : if( FD_UNLIKELY( block==NULL ) ) return -1;
795 12 : if( FD_UNLIKELY( block->staged ) ) return -1;
796 :
797 12 : block->staged = 1;
798 12 : block->staging_lane = (uint)(staging_lane & 0x3);
799 12 : block->insert_ready = 1;
800 12 : block->schedule_ready = (uint)(1 & (disp->free_lanes >> staging_lane));
801 :
802 12 : if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
803 :
804 12 : if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
805 12 : block_slist_ele_push_tail( sl, block, block_pool );
806 12 : uint linear_block_number = (uint)lane->linear_block_number++;
807 12 : block->linear_block_number = linear_block_number;
808 :
809 12 : unstaged_txn_ll_iter_t next;
810 12 : for( unstaged_txn_ll_iter_t iter = unstaged_txn_ll_iter_init( block->ll, disp->pool );
811 330 : !unstaged_txn_ll_iter_done( iter, block->ll, disp->pool );
812 318 : iter = next ) {
813 318 : next = unstaged_txn_ll_iter_next( iter, block->ll, disp->pool );
814 :
815 318 : fd_rdisp_txn_t * ele = unstaged_txn_ll_iter_ele( iter, block->ll, disp->pool );
816 318 : fd_rdisp_unstaged_t * uns = disp->unstaged + unstaged_txn_ll_iter_idx( iter, block->ll, disp->pool );
817 318 : FD_TEST( ele->in_degree==IN_DEGREE_UNSTAGED );
818 :
819 318 : ele->in_degree = 0U;
820 318 : ele->edge_cnt_etc = (uint)(-1); /* set w_cnt_1 to -1 */
821 :
822 318 : add_edges( disp, ele, uns->keys, uns->writable_cnt, (uint)staging_lane, 1, 0 );
823 318 : add_edges( disp, ele, uns->keys+uns->writable_cnt, uns->readonly_cnt, (uint)staging_lane, 0, 0 );
824 :
825 318 : ele->edge_cnt_etc &= 0x3FFFU;
826 318 : ele->edge_cnt_etc |= (uint)staging_lane<<14;
827 318 : ele->edge_cnt_etc |= linear_block_number<<16;
828 :
829 318 : if( FD_UNLIKELY( ele->in_degree==0U ) ) {
830 6 : pending_prq_ele_t temp[1] = {{ .score = ele->score, .linear_block_number = linear_block_number, .txn_idx = (uint)(ele-disp->pool)}};
831 6 : pending_prq_insert( lane->pending, temp );
832 6 : }
833 318 : }
834 12 : unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
835 :
836 12 : lane->inserted_cnt += block->inserted_cnt;
837 12 : lane->dispatched_cnt += block->dispatched_cnt;
838 12 : lane->completed_cnt += block->completed_cnt;
839 :
840 12 : return 0;
841 12 : }
842 :
843 : int
844 : fd_rdisp_demote_block( fd_rdisp_t * disp,
845 15 : FD_RDISP_BLOCK_TAG_T block_tag ) {
846 15 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
847 :
848 15 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
849 15 : if( FD_UNLIKELY( block==NULL ) ) return -1;
850 15 : if( FD_UNLIKELY( !block->staged ) ) return -1;
851 15 : if( FD_UNLIKELY( !block->schedule_ready ) ) return -1;
852 15 : if( FD_UNLIKELY( block->completed_cnt!=block->inserted_cnt ) ) FD_LOG_ERR(( "demote_block called with non-empty block" ));
853 15 : ulong staging_lane = block->staging_lane;
854 15 : block->staged = 0;
855 :
856 15 : per_lane_info_t * lane = disp->lanes + staging_lane;
857 15 : block_slist_t * sl = lane->block_ll;
858 :
859 15 : lane->inserted_cnt -= block->inserted_cnt;
860 15 : lane->dispatched_cnt -= block->dispatched_cnt;
861 15 : lane->completed_cnt -= block->completed_cnt;
862 :
863 15 : block->linear_block_number = (uint)disp->unstaged_lblk_num++;
864 :
865 : /* staged and schedule_ready means it must be the head of the staging lane */
866 15 : FD_TEST( block_slist_ele_peek_head( sl, block_pool )==block );
867 15 : block_slist_idx_pop_head( sl, block_pool );
868 :
869 15 : unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll ) );
870 :
871 15 : if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
872 15 : else free_lane( disp, staging_lane );
873 15 : return 0;
874 15 : }
875 :
876 : int
877 : fd_rdisp_rekey_block( fd_rdisp_t * disp,
878 : FD_RDISP_BLOCK_TAG_T new_tag,
879 6 : FD_RDISP_BLOCK_TAG_T old_tag ) {
880 6 : fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
881 :
882 6 : if( FD_UNLIKELY( NULL!= block_map_ele_query_const( disp->blockmap, &new_tag, NULL, block_pool ) ) ) return -1;
883 6 : fd_rdisp_blockinfo_t * block = block_map_ele_query ( disp->blockmap, &old_tag, NULL, block_pool );
884 6 : if( FD_UNLIKELY( NULL== block ) ) return -1;
885 :
886 6 : block_map_idx_remove( disp->blockmap, &old_tag, ULONG_MAX, block_pool );
887 6 : block->block = new_tag;
888 6 : block_map_ele_insert( disp->blockmap, block, block_pool );
889 6 : return 0;
890 6 : }
891 :
892 :
893 : /* "Registers" a reference to the account in info at transaction
894 : global_insert_cnt. Returns the value of the EMA, which is an
895 : estimate of the probability that the next transaction also references
896 : the account. This value does not matter for correctness, other than
897 : that it is in [0, 1), which is why floating point arithmetic is
898 : acceptable here. */
899 : static inline float
900 : update_ema( acct_info_t * info,
901 2940675 : ulong global_insert_cnt ) {
902 : #if FD_RDISP_DISABLE_EMA
903 : (void)info;
904 : (void)global_insert_cnt;
905 : return 0.0f;
906 : #else
907 5881350 : #define ALPHA 0.005f
908 : /* The normal EMA update equation is
909 : e_i = (alpha) * x_i + (1-alpha)*e_{i-1},
910 : where alpha is a constant in (0,1). Let L be the last reference of
911 : the account, and G be the current global_insert_cnt value. We know
912 : that e_L = ema_refs, and that for i in [L+1, G), x_i = 0.
913 : That means
914 : e_{G-1} = (1-alpha)^(G-L-1) * ema_refs
915 : e_G = alpha + (1-alpha)^(G-L) * ema_refs
916 : */
917 : /* last_ref only captures the low 24 bits, so we guess its the highest
918 : value for them that would still make it less than
919 : global_insert_cnt. Turns out, we can calculate that with just an
920 : AND. We need to make sure that in the rare case it is actually
921 : 2^24 away, we don't use a delta of 0. We also want to make sure
922 : that even with inexact float math, we never get to 1.0, so we bias
923 : the exponent up slightly. */
924 2940675 : ulong last_ref = (ulong)info->last_ref;
925 2940675 : ulong delta = fd_ulong_max( (global_insert_cnt - last_ref) & 0xFFFFFFUL, 1UL );
926 2940675 : float ema_refs = ALPHA + powf( 1.0f-ALPHA, 0.05f+(float)delta ) * info->ema_refs;
927 :
928 2940675 : info->ema_refs = ema_refs;
929 2940675 : info->last_ref = (uint)(global_insert_cnt & 0xFFFFFFUL);
930 2940675 : #undef ALPHA
931 :
932 2940675 : return ema_refs;
933 2940675 : #endif
934 2940675 : }
935 :
936 : static void
937 : add_edges( fd_rdisp_t * disp,
938 : fd_rdisp_txn_t * ele,
939 : fd_acct_addr_t const * addrs,
940 : ulong addr_cnt,
941 : uint lane,
942 : int writable,
943 3315708 : int update_score ) {
944 : /* When we first start, the low 14 bits are 0x3FFF, so adding the first
945 : non-empty writable batch wraps them to w_cnt-1 with r_cnt==0.
946 : Thereafter, the low 7 bits store w_cnt_1 and the next 7 bits store
947 : r_cnt. Both counts fit without overflow because their sum is at
948 : most MAX_ACCT_PER_TXN. */
949 :
950 3315708 : ulong w_cnt = (ele->edge_cnt_etc + 1U) & 0x7FU;
951 3315708 : ulong r_cnt = ((ele->edge_cnt_etc + 1U)>>7) & 0x7FU;
952 3315708 : ulong acct_idx = w_cnt + r_cnt;
953 3315708 : ulong edge_idx = w_cnt + 3UL*r_cnt;
954 :
955 6237813 : for( ulong i=0UL; i<addr_cnt; i++ ) {
956 2922105 : fd_acct_addr_t const * addr = addrs+i;
957 2922105 : acct_info_t * ai = NULL;
958 :
959 : /* Step 1: lookup the pubkey */
960 2922105 : ulong idx = acct_map_idx_query( disp->acct_map, addr, ULONG_MAX, disp->acct_pool );
961 2922105 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
962 1086939 : idx = acct_map_idx_query( disp->free_acct_map, addr, ULONG_MAX, disp->acct_pool );
963 1086939 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
964 : /* The acct pool is sized so that the list cannot be empty at this
965 : point. However, the element at the head might be the free
966 : map with a different pubkey. */
967 597951 : idx = free_dlist_idx_peek_head( disp->free_acct_dlist, disp->acct_pool );
968 597951 : ai = disp->acct_pool+idx;
969 :
970 : /* CACHED -> FREE transition */
971 597951 : if( FD_LIKELY( ai->next!=0U ) ) {
972 183423 : acct_map_idx_remove_fast( disp->free_acct_map, idx, disp->acct_pool );
973 183423 : }
974 :
975 : /* FREE -> ACTIVE transition */
976 597951 : ai->key = *addr;
977 597951 : ai->flags = 0U;
978 597951 : ai->last_ref = 0U;
979 597951 : ai->ema_refs = 0.0f;
980 597951 : } else {
981 : /* CACHED -> ACTIVE transition */
982 488988 : ai = disp->acct_pool+idx;
983 488988 : ai->flags = 0U; /* FIXME: unnecessary */
984 488988 : acct_map_idx_remove_fast( disp->free_acct_map, idx, disp->acct_pool );
985 488988 : }
986 : /* In either case, at this point, the element is not in any map
987 : but is in free_acct_dlist. It has the right key. last_ref, and
988 : ema_refs are valid. flags is 0. */
989 1086939 : free_dlist_idx_remove( disp->free_acct_dlist, idx, disp->acct_pool );
990 1086939 : memset( ai->last_reference, '\0', sizeof(ai->last_reference) );
991 1086939 : acct_map_idx_insert( disp->acct_map, idx, disp->acct_pool );
992 1086939 : }
993 2922105 : ai = disp->acct_pool+idx;
994 : /* At this point, in all cases, the acct_info is now in the ACTIVE
995 : state. It's in acct_map, not in free_acct_map, and not in
996 : free_acct_dlist. */
997 :
998 : /* Assume that transactions are drawn randomly from some large
999 : distribution of potential transactions. We want to estimate the
1000 : expected value of the probability that this transaction conflicts
1001 : with the next transaction that is sampled. Two transactions
1002 : conflict if they conflict on any account, and in general, they
1003 : conflict if they both reference the same account, unless both
1004 : this transaction and the next one only read it. We don't have
1005 : read/write info, so the best guess we have is that the next
1006 : transaction does the same thing to the account that this one
1007 : does. That means we only really care about the accounts that
1008 : this transaction writes to. Label those a_1, a_2, ..., a_i, and
1009 : suppose the probability that the next transaction references a_i
1010 : is p_i (determined using the EMA). Now then, assuming accounts
1011 : are independent (which is false, but whatever), then the
1012 : probability that the next transaction does not conflict with this
1013 : account is:
1014 : (1-p_1) * (1-p_2) * (1 - p_i).
1015 :
1016 : Since for the treap, a lower value means we'll schedule it
1017 : earlier, we'll use the probability of non-conflict as the
1018 : fractional part of the score. */
1019 2922105 : if( FD_LIKELY( update_score ) ) {
1020 2902812 : float score_change = 1.0f - update_ema( ai, disp->global_insert_cnt );
1021 2902812 : ele->score *= fd_float_if( writable, score_change, 1.0f );
1022 2902812 : }
1023 :
1024 : /* Step 2: add edge. There are 4 cases depending on whether this is
1025 : a writer or not and whether the previous reference was a writer
1026 : or not. */
1027 2922105 : int _ignore;
1028 :
1029 2922105 : edge_t ref_to_pa = ai->last_reference[ lane ];
1030 2922105 : edge_t ref_to_me = (uint)((ulong)((ele - disp->pool)<<8) | acct_idx);
1031 2922105 : edge_t * pa = FOLLOW_EDGE( disp->pool, ai->last_reference[ lane ], _ignore );
1032 2922105 : edge_t * me = ele->edges + edge_idx;
1033 :
1034 : /* In the case that this is the first txn in the DAG, pa will point
1035 : to edges[0] of the sentinel element, pool[0]. We don't care
1036 : about what is stored there, so just set it up as a dummy element
1037 : to make the rest of the code work properly in this case too. If
1038 : this is a read, we want me->sibli to ref_to_me in case 4; if this
1039 : is a write, we want to set me->sibli to 0 in case 2, but we need
1040 : to make sure pb==pa. */
1041 2922105 : disp->pool->edges[0] = (1U<<31) | (uint)idx;
1042 2922105 : disp->pool->edges[1] = fd_uint_if( writable, 0U, ref_to_me );
1043 2922105 : disp->pool->edges[2] = fd_uint_if( writable, 0U, ref_to_me );
1044 :
1045 2922105 : int flags = ai->flags;
1046 :
1047 2922105 : if( writable ) { /* also should be known at compile time */
1048 1386027 : if( flags & ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) ) { /* unclear prob */
1049 : /* Case 1: w-w. The parent is the special last pointer. Point
1050 : the parent to me, and set me to the last pointer. */
1051 256695 : *me = *pa;
1052 256695 : *pa = ref_to_me;
1053 1129332 : } else {
1054 : /* Case 2: r-w. This is the tricky case because there could be
1055 : multiple readers. We need to set all the last readers' child
1056 : pointers to me. */
1057 1129332 : *me = *pa;
1058 1129332 : *pa = ref_to_me;
1059 1129332 : edge_t * pb = FOLLOW_EDGE( disp->pool, pa[1], _ignore );
1060 : /* Intentionally skip the first in_degree increment, because it
1061 : will be done later */
1062 1212087 : while( pb!=pa ) {
1063 82755 : *pb = ref_to_me;
1064 82755 : ele->in_degree++;
1065 82755 : pb = FOLLOW_EDGE( disp->pool, pb[1], _ignore );
1066 82755 : }
1067 1129332 : flags |= ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) | ACCT_INFO_FLAG_ANY_WRITERS( lane );
1068 1129332 : }
1069 1536078 : } else {
1070 1536078 : if( flags & ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) ) { /* unclear prob */
1071 : /* Case 3: w-r. Similar to case 1, but need to initialize my
1072 : next and prev sibling pointers too. */
1073 92217 : *me = *pa;
1074 92217 : *pa = ref_to_me;
1075 92217 : me[1] = ref_to_me; /* next */
1076 92217 : me[2] = ref_to_me; /* prev */
1077 92217 : flags &= ~(int)ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ); /* clear bit */
1078 1443861 : } else {
1079 : /* Case 4: r-r. Add myself as a sibling instead of a child */
1080 1443861 : *me = *pa;
1081 1443861 : FOLLOW_EDGE( disp->pool, pa[1], _ignore )[2] = ref_to_me; /* prev->next->prev = me */
1082 1443861 : me[1] = pa[1]; /* me->next = prev->next */
1083 1443861 : me[2] = ref_to_pa; /* me->prev = prev */
1084 1443861 : pa[1] = ref_to_me; /* prev->next = me */
1085 1443861 : }
1086 1536078 : }
1087 :
1088 : /* Step 3: Update the final values */
1089 : /* In general, we want to increment the in_degree unless this
1090 : transaction is the first to reference this account. The
1091 : exception is that if this account has only readers, including
1092 : this transaction, we don't want to increment the in_degree
1093 : either. At this point, we can tell if that is the case based on
1094 : ANY_WRITERS. */
1095 2922105 : ele->in_degree += (uint)((ai->last_reference[ lane ]!=0U) & !!(flags & ACCT_INFO_FLAG_ANY_WRITERS( lane )));
1096 2922105 : ai->last_reference[ lane ] = ref_to_me;
1097 2922105 : ai->flags = (uchar)flags;
1098 2922105 : edge_idx += fd_uint_if( writable, 1U, 3U );
1099 2922105 : acct_idx++;
1100 2922105 : }
1101 : /* Can't overflow by construction, except for the intentional overflow on the first time. */
1102 3315708 : ele->edge_cnt_etc += (uint)addr_cnt<<fd_int_if( writable, 0, 7 );
1103 3315708 : }
1104 :
1105 :
1106 : /* should be called with all writable accounts first */
1107 : static void
1108 : add_unstaged_edges( fd_rdisp_t * disp,
1109 : fd_rdisp_txn_t * ele,
1110 : fd_rdisp_unstaged_t * unstaged,
1111 : fd_acct_addr_t const * addr,
1112 : ulong addr_cnt,
1113 : int writable,
1114 3816 : int update_score ) {
1115 3816 : ulong base_idx = unstaged->writable_cnt+unstaged->readonly_cnt;
1116 3816 : FD_TEST( !writable || unstaged->readonly_cnt==0U );
1117 42402 : for( ulong i=0UL; i<addr_cnt; i++ ) {
1118 38586 : unstaged->keys[ base_idx+i ] = addr[i];
1119 38586 : if( FD_LIKELY( update_score ) ) {
1120 38586 : ulong idx = acct_map_idx_query( disp->acct_map, addr+i, ULONG_MAX, disp->acct_pool );
1121 38586 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) idx = acct_map_idx_query( disp->free_acct_map, addr+i, ULONG_MAX, disp->acct_pool );
1122 : /* since these are unstaged, we don't bother moving accounts
1123 : around */
1124 38586 : float score_change = 1.0f;
1125 38586 : if( FD_LIKELY( idx!=ULONG_MAX ) ) score_change = 1.0f - update_ema( disp->acct_pool+idx, disp->global_insert_cnt );
1126 38586 : ele->score *= fd_float_if( writable & update_score, score_change, 1.0f );
1127 38586 : }
1128 38586 : }
1129 3816 : *(fd_ptr_if( writable, &(unstaged->writable_cnt), &(unstaged->readonly_cnt) ) ) += (uint)addr_cnt;
1130 3816 : }
1131 :
1132 : ulong
1133 : fd_rdisp_add_txn( fd_rdisp_t * disp,
1134 : FD_RDISP_BLOCK_TAG_T insert_block,
1135 : fd_txn_t const * txn,
1136 : uchar const * payload,
1137 : fd_acct_addr_t const * alts,
1138 553152 : int serializing ) {
1139 :
1140 553152 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &insert_block, NULL, disp->block_pool );
1141 553152 : if( FD_UNLIKELY( !block || !block->insert_ready ) ) return 0UL;
1142 553149 : if( FD_UNLIKELY( !pool_free( disp->pool ) ) ) return 0UL;
1143 :
1144 553149 : ulong idx = pool_idx_acquire( disp->pool );
1145 553149 : fd_rdisp_txn_t * rtxn = disp->pool + idx;
1146 553149 : if( FD_UNLIKELY( rtxn->in_degree!=IN_DEGREE_FREE ) ) FD_LOG_CRIT(( "pool[%lu].in_degree==%u but free", idx, rtxn->in_degree ));
1147 :
1148 553149 : fd_acct_addr_t const * imm_addrs = fd_txn_get_acct_addrs( txn, payload );
1149 :
1150 553149 : if( FD_UNLIKELY( !block->staged ) ) {
1151 636 : rtxn->in_degree = IN_DEGREE_UNSTAGED;
1152 636 : rtxn->score = 1.0f;
1153 :
1154 636 : fd_rdisp_unstaged_t * unstaged = disp->unstaged + idx;
1155 636 : unstaged->block = insert_block;
1156 636 : unstaged->writable_cnt = 0U;
1157 636 : unstaged->readonly_cnt = 0U;
1158 :
1159 636 : add_unstaged_edges( disp, rtxn, unstaged, imm_addrs,
1160 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ), 1, 1 );
1161 636 : add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ),
1162 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ), 1, 1 );
1163 636 : if( FD_LIKELY( alts ) )
1164 636 : add_unstaged_edges( disp, rtxn, unstaged, alts,
1165 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ), 1, 1 );
1166 636 : add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ),
1167 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_SIGNER ), 0, 1 );
1168 636 : add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER | FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ),
1169 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM ), 0, 1 );
1170 636 : if( FD_LIKELY( alts ) )
1171 636 : add_unstaged_edges( disp, rtxn, unstaged, alts +fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),
1172 636 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_ALT ), 0, 1 );
1173 :
1174 636 : unstaged_txn_ll_ele_push_tail( block->ll, rtxn, disp->pool );
1175 552513 : } else {
1176 552513 : uint lane = block->staging_lane;
1177 :
1178 552513 : rtxn->in_degree = 0U;
1179 552513 : rtxn->score = 1.0f;
1180 : /* There's not a good way to initialize w_cnt_1 to -1, which is what
1181 : it should be at this point, but this is close. We must be sure
1182 : to add at least one writer (which we are assured of because we
1183 : know the transaction passed fd_txn_parse) to correct this value. */
1184 552513 : rtxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)) - 1U;
1185 :
1186 552513 : add_edges( disp, rtxn, imm_addrs,
1187 552513 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ), lane, 1, 1 );
1188 552513 : add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ),
1189 552513 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ), lane, 1, 1 );
1190 552513 : if( FD_LIKELY( alts ) )
1191 552510 : add_edges( disp, rtxn, alts,
1192 552510 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ), lane, 1, 1 );
1193 552513 : add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ),
1194 552513 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_SIGNER ), lane, 0, 1 );
1195 552513 : add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER | FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ),
1196 552513 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM ), lane, 0, 1 );
1197 552513 : if( FD_LIKELY( alts ) )
1198 552510 : add_edges( disp, rtxn, alts +fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),
1199 552510 : fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_ALT ), lane, 0, 1 );
1200 552513 : }
1201 553149 : if( FD_UNLIKELY( rtxn->score>FD_RDISP_MAX_SCORE ) ) rtxn->score=FD_RDISP_MAX_SCORE;
1202 :
1203 553149 : if( FD_UNLIKELY( serializing | block->last_insert_was_serializing ) ) {
1204 6 : block->last_serializing = block->inserted_cnt;
1205 6 : }
1206 553149 : block->last_insert_was_serializing = (uint)!!serializing;
1207 553149 : rtxn->score += (float)block->last_serializing;
1208 :
1209 553149 : block->inserted_cnt++;
1210 553149 : disp->global_insert_cnt++;
1211 :
1212 553149 : if( FD_LIKELY( (block->staged) & (rtxn->in_degree==0U) ) ) {
1213 433476 : pending_prq_ele_t temp[1] = {{ .score = rtxn->score, .linear_block_number = block->linear_block_number, .txn_idx = (uint)idx }};
1214 433476 : pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
1215 433476 : }
1216 :
1217 553149 : return idx;
1218 553149 : }
1219 :
1220 : ulong
1221 : fd_rdisp_get_next_ready( fd_rdisp_t * disp,
1222 724374 : FD_RDISP_BLOCK_TAG_T schedule_block ) {
1223 724374 : fd_rdisp_blockinfo_t * block = block_map_ele_query( disp->blockmap, &schedule_block, NULL, disp->block_pool );
1224 724374 : if( FD_UNLIKELY( !block || !block->schedule_ready ) ) return 0UL;
1225 :
1226 724359 : ulong idx;
1227 724359 : if( FD_LIKELY( block->staged ) ) {
1228 723732 : ulong staging_lane = block->staging_lane;
1229 723732 : per_lane_info_t * l = disp->lanes + staging_lane;
1230 :
1231 723732 : if( FD_UNLIKELY( !pending_prq_cnt( l->pending ) ) ) return 0UL;
1232 552837 : if( FD_UNLIKELY( l->pending->linear_block_number != block->linear_block_number ) ) return 0UL;
1233 : /* e.g. when completed_cnt==0, we can accept any score below 1.0 */
1234 552831 : if( FD_UNLIKELY( l->pending->score>=(float)(block->completed_cnt+1U) ) ) return 0UL;
1235 552831 : idx = l->pending->txn_idx;
1236 552831 : pending_prq_remove_min( l->pending );
1237 552831 : disp->pool[ idx ].in_degree = IN_DEGREE_DISPATCHED;
1238 552831 : } else {
1239 627 : if( FD_UNLIKELY( block->dispatched_cnt!=block->completed_cnt ) ) return 0UL;
1240 324 : if( FD_UNLIKELY( unstaged_txn_ll_is_empty( block->ll, disp->pool ) ) ) return 0UL;
1241 318 : idx = unstaged_txn_ll_idx_peek_head( block->ll, disp->pool );
1242 318 : disp->pool[ idx ].in_degree = IN_DEGREE_UNSTAGED_DISPATCHED;
1243 318 : }
1244 553149 : block->dispatched_cnt++;
1245 :
1246 553149 : return idx;
1247 724359 : }
1248 :
1249 : void
1250 : fd_rdisp_complete_txn( fd_rdisp_t * disp,
1251 : ulong txn_idx,
1252 553152 : int reclaim ) {
1253 :
1254 553152 : fd_rdisp_txn_t * rtxn = disp->pool + txn_idx;
1255 553152 : fd_rdisp_blockinfo_t * block = NULL;
1256 :
1257 553152 : if( FD_UNLIKELY( rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ) ) {
1258 : /* Unstaged */
1259 318 : block = block_map_ele_query( disp->blockmap, &disp->unstaged[ txn_idx ].block, NULL, disp->block_pool );
1260 318 : FD_TEST( rtxn==unstaged_txn_ll_ele_peek_head( block->ll, disp->pool ) );
1261 318 : unstaged_txn_ll_ele_pop_head( block->ll, disp->pool );
1262 318 : block->completed_cnt++;
1263 552834 : } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_DISPATCHED ) ) {
1264 : /* Staged */
1265 552831 : ulong w_cnt_1 = (rtxn->edge_cnt_etc ) & 0x7FU;
1266 552831 : ulong r_cnt = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
1267 552831 : ulong lane = (rtxn->edge_cnt_etc>>14) & 0x3U;
1268 552831 : uint tail_linear_block_num = (uint)(disp->lanes[lane].linear_block_number);
1269 552831 : ulong edge_idx = 0UL;
1270 3474936 : for( ulong i=0UL; i<=w_cnt_1+r_cnt; i++ ) {
1271 2922105 : edge_t const * e = rtxn->edges+edge_idx;
1272 2922105 : edge_t const e0 = *e;
1273 2922105 : edge_t ref_to_me = (uint)((txn_idx<<8) | i);
1274 :
1275 : /* To help with explanations, consider the following DAG:
1276 :
1277 : --> B --\ --> E --\
1278 : / V / V
1279 : A D (G)
1280 : \ ^ \ ^
1281 : --> C --/ --> F --/ */
1282 :
1283 2922105 : if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) {
1284 2378694 : ulong acct_idx = e0 & 0x7FFFFFFFU;
1285 2378694 : acct_info_t * ai = disp->acct_pool + acct_idx;
1286 : /* If this is a writer, e.g. node G above, we know it's the last
1287 : one, so we can clear last_reference.
1288 : If this is a reader, e.g. node E and F above, if node G
1289 : didn't exist, we need to check if it's the last one
1290 : (me==me->next). If so, we can clear last_reference. If not,
1291 : we need to delete this node from the linked list */
1292 2378694 : if( edge_idx<=w_cnt_1 || e[1]==ref_to_me ) {
1293 1543824 : ai->last_reference[ lane ] = 0U;
1294 1543824 : ai->flags = (uchar)(ai->flags & (~(ACCT_INFO_FLAG_ANY_WRITERS( lane ) | ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ))));
1295 1543824 : } else {
1296 834870 : int _ignore;
1297 834870 : FOLLOW_EDGE( disp->pool, e[1], _ignore )[2] = e[2]; /* me->next->prev = me->prev */
1298 834870 : FOLLOW_EDGE( disp->pool, e[2], _ignore )[1] = e[1]; /* me->prev->next = me->next */
1299 834870 : ai->last_reference[ lane ]= fd_uint_if( ai->last_reference[ lane ]==ref_to_me, e[1], ai->last_reference[ lane ] );
1300 834870 : }
1301 :
1302 : /* Potentially transition from ACTIVE -> CACHED */
1303 2378694 : if( FD_UNLIKELY( (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
1304 2378694 : (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) ) {
1305 1086939 : ai->flags = 0;
1306 1086939 : acct_map_idx_remove_fast( disp->acct_map, acct_idx, disp->acct_pool );
1307 1086939 : acct_map_idx_insert ( disp->free_acct_map, acct_idx, disp->acct_pool );
1308 1086939 : free_dlist_idx_push_tail( disp->free_acct_dlist, acct_idx, disp->acct_pool );
1309 1086939 : }
1310 2378694 : } else {
1311 543411 : int child_is_writer;
1312 :
1313 543411 : edge_t next_e = e0;
1314 543411 : edge_t const * child_edge;
1315 587814 : while( 1 ) {
1316 : /* This loop first traverses the me->child link, and then
1317 : traverses any sibling links. For example, in the case that
1318 : we're completing node D above, the first child_txn is E,
1319 : and the second child_txn is F. */
1320 587814 : /* */ child_edge = FOLLOW_EDGE( disp->pool, next_e, child_is_writer );
1321 587814 : fd_rdisp_txn_t * child_txn = FOLLOW_EDGE_TXN( disp->pool, next_e );
1322 :
1323 : /* Sanity test */
1324 587814 : FD_TEST( child_txn->in_degree>0U );
1325 587814 : FD_TEST( child_txn->in_degree<IN_DEGREE_DISPATCHED );
1326 :
1327 587814 : if( FD_UNLIKELY( 0U==(--(child_txn->in_degree)) ) ) {
1328 : /* We need an operation something like
1329 : fd_frag_meta_ts_decomp. child_txn has the low 16 bits,
1330 : and tail_linear_block_num has the full 32 bits, except
1331 : for tail_linear_block_num refers to a block < block_depth
1332 : later. Since block_depth<2^16, that means we can resolve
1333 : this unambiguously. Basically, we copy the high 16 bits
1334 : frorm tail_linear_block_num unless that would make
1335 : linear_block_num larger than tail_linear_block_num, in
1336 : which case, we subtract 2^16. */
1337 119349 : uint low_16_bits = child_txn->edge_cnt_etc>>16;
1338 119349 : uint linear_block_num = ((tail_linear_block_num & ~0xFFFFU) | low_16_bits) - (uint)((low_16_bits>(tail_linear_block_num&0xFFFFU))<<16);
1339 119349 : pending_prq_ele_t temp[1] = {{ .score = child_txn->score,
1340 119349 : .linear_block_number = linear_block_num,
1341 119349 : .txn_idx = (uint)(child_txn-disp->pool) }};
1342 119349 : pending_prq_insert( disp->lanes[ lane ].pending, temp );
1343 119349 : }
1344 587814 : if( child_is_writer || child_edge[1]==e0 ) break;
1345 44403 : next_e = child_edge[1];
1346 44403 : }
1347 : /* In the case that the completed transaction is a reader, say B
1348 : or C above, it seems like we should need to remove it from
1349 : the doubly linked list, but we actually don't. The times
1350 : that we need to read the sibling pointers are:
1351 : 1. Completing the writer before a reader (e.g. completing A)
1352 : 2. Completing a reader that's the last in the DAG (e.g.
1353 : completing E/F if G didn't exist)
1354 : 3. Adding another reader to the same set of readers (e.g. if
1355 : G were added as a reader instead of a writer)
1356 : 4. Adding a writer after a set of readers (e.g. adding G).
1357 :
1358 : Supposing that the completed transaction is a reader, since
1359 : we checked EDGE_IS_LAST( e0 ), we know that there is at least
1360 : one writer that follows this reader, e.g. D above.
1361 :
1362 : And so none of these reason can apply to this group of readers:
1363 : 1. Completing B or C implies that A has already completed,
1364 : so it can't complete again.
1365 : 2. We know that we're not in that case because we checked
1366 : EDGE_IS_LAST( e0 ), and if we're not last now, we cannot
1367 : become last later, because the growth happens in the
1368 : other direction.
1369 : 3. Similarly, because we checked EDGE_IS_LAST, any future
1370 : additions of readers won't be to this group of readers.
1371 : 4. Similarly, we know that there already is a writer that
1372 : follows this group of readers, so a later writer would
1373 : not read this set of readers.
1374 :
1375 : So then, we don't need to deal with the sibling edges. The
1376 : fact that we only need to do it in one case almost calls into
1377 : question whether we need to maintain the whole circular
1378 : system in the first place, and whether we could get away with
1379 : a reference count or something instead, but it's important in
1380 : one critical case: suppose we add a bunch of readers, then
1381 : some of them complete, and then we add a writer (case 4
1382 : above). We need to be able to enumerate the nodes in the DAG
1383 : that have not yet completed, and we need to be able to remove
1384 : them from that set in O(1). Those two requirements don't
1385 : leave us with many alternatives besides a doubly linked list. */
1386 :
1387 543411 : if( FD_UNLIKELY( EDGE_IS_LAST( *child_edge ) ) ) {
1388 : /* For example, either:
1389 : 1. completing D when if G didn't exist
1390 : 2. completing E or F with G in the DAG
1391 :
1392 : After completing this transaction, there's either 0 writers
1393 : left (case 1) or 1 writer left (case 2) in the DAG. and if
1394 : there is one, it is the last reference.
1395 :
1396 : Either way, we want to set ANY_WRITERS to
1397 : LAST_REF_WAS_WRITE. */
1398 399549 : acct_info_t * ai = disp->acct_pool + (*child_edge & 0x7FFFFFFFU);
1399 399549 : ulong flags = ai->flags;
1400 399549 : flags &= ~(ulong)ACCT_INFO_FLAG_ANY_WRITERS( lane );
1401 399549 : flags |= (flags & (ulong)ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ))<<1;
1402 399549 : ai->flags = (uchar)flags;
1403 399549 : }
1404 543411 : }
1405 2922105 : edge_idx += fd_ulong_if( i<=w_cnt_1, 1UL, 3UL );
1406 2922105 : }
1407 552831 : block = block_slist_ele_peek_head( disp->lanes[ lane ].block_ll, disp->block_pool );
1408 552831 : block->completed_cnt++;
1409 552831 : } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_ZOMBIE ) ) {
1410 3 : FD_TEST( reclaim );
1411 3 : block = block_pool_ele( disp->block_pool, rtxn->block_idx );
1412 3 : zombie_dlist_ele_remove( block->zombie_list, rtxn, disp->pool );
1413 : /* Fall through to the pool release branch below. */
1414 3 : } else {
1415 0 : FD_LOG_CRIT(( "completed un-dispatched transaction %lu", txn_idx ));
1416 0 : }
1417 553152 : if( reclaim ) {
1418 : /* For testing purposes, to make sure we don't read a completed
1419 : transaction, we can clobber the memory. */
1420 : /* memset( disp->pool+txn_idx, '\xCC', sizeof(fd_rdisp_txn_t) ); */
1421 553149 : rtxn->in_degree = IN_DEGREE_FREE;
1422 553149 : pool_idx_release( disp->pool, txn_idx );
1423 553149 : } else {
1424 3 : rtxn->in_degree = IN_DEGREE_ZOMBIE;
1425 3 : zombie_dlist_ele_push_tail( block->zombie_list, rtxn, disp->pool );
1426 3 : rtxn->block_idx = (uint)block_pool_idx( disp->block_pool, block );
1427 3 : }
1428 553152 : }
1429 :
1430 :
1431 : ulong
1432 : fd_rdisp_staging_lane_info( fd_rdisp_t const * disp,
1433 45 : fd_rdisp_staging_lane_info_t out_sched[ static 4 ] ) {
1434 225 : for( ulong i=0UL; i<4UL; i++ ) {
1435 180 : if( !(disp->free_lanes & (1<<i) ) ) {
1436 66 : block_slist_t const * sl = disp->lanes[ i ].block_ll;
1437 66 : out_sched[ i ].insert_ready_block = block_slist_ele_peek_tail( sl, disp->block_pool )->block;
1438 66 : out_sched[ i ].schedule_ready_block = block_slist_ele_peek_head( sl, disp->block_pool )->block;
1439 66 : }
1440 180 : }
1441 45 : return 0xFUL & ~(ulong)disp->free_lanes;
1442 45 : }
1443 :
1444 : void
1445 : fd_rdisp_verify( fd_rdisp_t const * disp,
1446 155352 : uint * scratch ) {
1447 155352 : ulong acct_depth = disp->depth*MAX_ACCT_PER_TXN;
1448 155352 : ulong block_depth = disp->block_depth;
1449 155352 : FD_TEST( 0==acct_map_verify ( disp->acct_map, acct_depth+1UL, disp->acct_pool ) );
1450 155352 : FD_TEST( 0==acct_map_verify ( disp->free_acct_map, acct_depth+1UL, disp->acct_pool ) );
1451 155352 : FD_TEST( 0==block_map_verify( disp->blockmap, block_depth+1UL, disp->block_pool ) );
1452 :
1453 : /* Check all the in degree counts are right */
1454 155352 : memset( scratch, '\0', sizeof(uint)*(disp->depth+1UL) );
1455 155352 : scratch[ 0 ] = UINT_MAX;
1456 46753152 : for( ulong j=1UL; j<disp->depth+1UL; j++ ) {
1457 46597800 : fd_rdisp_txn_t const * rtxn = disp->pool+j;
1458 46597800 : if( rtxn->in_degree==IN_DEGREE_FREE ) { scratch[ j ]=UINT_MAX; continue; }
1459 :
1460 11532285 : if( (rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED) |
1461 11532285 : (rtxn->in_degree==IN_DEGREE_UNSTAGED) |
1462 11532285 : (rtxn->in_degree==IN_DEGREE_ZOMBIE) ) continue;
1463 :
1464 11531658 : ulong w_cnt_1 = (rtxn->edge_cnt_etc ) & 0x7FU;
1465 11531658 : ulong r_cnt = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
1466 11531658 : ulong edge_idx = 0UL;
1467 :
1468 157731339 : for( ulong i=0UL; i<=w_cnt_1+r_cnt; i++ ) {
1469 146199681 : edge_t const * e = rtxn->edges+edge_idx;
1470 146199681 : edge_t const e0 = *e;
1471 :
1472 146199681 : edge_idx += fd_ulong_if( i<=w_cnt_1, 1UL, 3UL );
1473 :
1474 146199681 : if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) continue;
1475 :
1476 29429448 : edge_t next_e = e0;
1477 29429448 : edge_t const * child_edge;
1478 29429448 : edge_t last_e = 0U;
1479 30671499 : while( 1 ) {
1480 30671499 : int child_is_writer;
1481 : /* This loop first traverses the me->child link, and then
1482 : traverses any sibling links. For example, in the case that
1483 : we're completing node D above, the first child_txn is E,
1484 : and the second child_txn is F. */
1485 30671499 : /* */ child_edge = FOLLOW_EDGE( disp->pool, next_e, child_is_writer );
1486 30671499 : fd_rdisp_txn_t * child_txn = FOLLOW_EDGE_TXN( disp->pool, next_e );
1487 30671499 : scratch[ child_txn - disp->pool ]++;
1488 30671499 : if( child_is_writer || child_edge[1]==e0 ) break;
1489 1242051 : if( last_e!=0U ) FD_TEST( child_edge[2]==last_e );
1490 1242051 : last_e = next_e;
1491 1242051 : next_e = child_edge[1];
1492 1242051 : FD_TEST( next_e>=0x100U );
1493 1242051 : }
1494 29429448 : }
1495 11531658 : }
1496 46753152 : for( ulong i=1UL; i<disp->depth+1UL; i++ ) {
1497 46597800 : FD_TEST( scratch[ i ]==UINT_MAX ||
1498 46597800 : disp->pool[ i ].in_degree==IN_DEGREE_DISPATCHED ||
1499 46597800 : disp->pool[ i ].in_degree==IN_DEGREE_UNSTAGED ||
1500 46597800 : disp->pool[ i ].in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ||
1501 46597800 : disp->pool[ i ].in_degree==IN_DEGREE_ZOMBIE ||
1502 46597800 : disp->pool[ i ].in_degree==scratch[ i ] );
1503 46597800 : }
1504 155352 : }
1505 :
1506 9 : void * fd_rdisp_leave ( fd_rdisp_t * disp ) { return disp; }
1507 9 : void * fd_rdisp_delete( void * mem ) { return mem; }
|