| 1 | #pragma once
|
|---|
| 2 |
|
|---|
| 3 | #include "containers/list.hfa"
|
|---|
| 4 | // #include "alarm.hfa"
|
|---|
| 5 | #include "stdint.h"
|
|---|
| 6 | #include "kernel.hfa"
|
|---|
| 7 | #include "time.hfa"
|
|---|
| 8 |
|
|---|
| 9 | struct select_node;
|
|---|
| 10 |
|
|---|
| 11 | // node status
|
|---|
| 12 | static const unsigned long int __SELECT_UNSAT = 0;
|
|---|
| 13 | static const unsigned long int __SELECT_PENDING = 1; // used only by special OR case
|
|---|
| 14 | static const unsigned long int __SELECT_SAT = 2;
|
|---|
| 15 | static const unsigned long int __SELECT_RUN = 3;
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 | // these are used inside the compiler to aid in code generation
|
|---|
| 19 | static inline bool __CFA_has_clause_run( unsigned long int status ) { return status == __SELECT_RUN; }
|
|---|
| 20 | static inline void __CFA_maybe_park( int * park_counter ) {
|
|---|
| 21 | if ( __atomic_sub_fetch( park_counter, 1, __ATOMIC_SEQ_CST) < 0 )
|
|---|
| 22 | park();
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | // node used for coordinating waituntil synchronization
|
|---|
| 26 | struct select_node {
|
|---|
| 27 | int * park_counter; // If this is 0p then the node is in a special OR case waituntil
|
|---|
| 28 | unsigned long int * clause_status; // needs to point at ptr sized location, if this is 0p then node is not part of a waituntil
|
|---|
| 29 |
|
|---|
| 30 | void * extra; // used to store arbitrary data needed by some primitives
|
|---|
| 31 |
|
|---|
| 32 | thread$ * blocked_thread;
|
|---|
| 33 | inline dlink(select_node);
|
|---|
| 34 | };
|
|---|
| 35 | P9_EMBEDDED( select_node, dlink(select_node) )
|
|---|
| 36 |
|
|---|
| 37 | static inline void ?{}( select_node & this ) {
|
|---|
| 38 | this.blocked_thread = active_thread();
|
|---|
| 39 | this.clause_status = 0p;
|
|---|
| 40 | this.park_counter = 0p;
|
|---|
| 41 | this.extra = 0p;
|
|---|
| 42 | }
|
|---|
| 43 |
|
|---|
| 44 | static inline void ?{}( select_node & this, thread$ * blocked_thread ) {
|
|---|
| 45 | this.blocked_thread = blocked_thread;
|
|---|
| 46 | this.clause_status = 0p;
|
|---|
| 47 | this.park_counter = 0p;
|
|---|
| 48 | this.extra = 0p;
|
|---|
| 49 | }
|
|---|
| 50 |
|
|---|
| 51 | static inline void ?{}( select_node & this, thread$ * blocked_thread, void * extra ) {
|
|---|
| 52 | this.blocked_thread = blocked_thread;
|
|---|
| 53 | this.clause_status = 0p;
|
|---|
| 54 | this.park_counter = 0p;
|
|---|
| 55 | this.extra = extra;
|
|---|
| 56 | }
|
|---|
| 57 | static inline void ^?{}( select_node & this ) {}
|
|---|
| 58 |
|
|---|
| 59 | // this is used inside the compiler to aid in code generation
|
|---|
| 60 | static inline unsigned long int * __get_clause_status( select_node & s ) { return s.clause_status; }
|
|---|
| 61 |
|
|---|
| 62 | // this is used inside the compiler to attempt to establish an else clause as a winner in the OR special case race
|
|---|
| 63 | static inline bool __select_node_else_race( select_node & this ) with( this ) {
|
|---|
| 64 | unsigned long int cmp_status = __SELECT_UNSAT;
|
|---|
| 65 | return *clause_status == 0
|
|---|
| 66 | && __atomic_compare_exchange_n( clause_status, &cmp_status, __SELECT_SAT, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST );
|
|---|
| 67 | }
|
|---|
| 68 |
|
|---|
| 69 | //-----------------------------------------------------------------------------
|
|---|
| 70 | // is_selectable
|
|---|
| 71 | forall(T & | sized(T))
|
|---|
| 72 | trait is_selectable {
|
|---|
| 73 | // For registering a select stmt on a selectable concurrency primitive
|
|---|
| 74 | // Returns bool that indicates if operation is already SAT
|
|---|
| 75 | bool register_select( T &, select_node & );
|
|---|
| 76 |
|
|---|
| 77 | // For unregistering a select stmt on a selectable concurrency primitive
|
|---|
| 78 | // If true is returned then the corresponding code block is run (only in non-special OR case and only if node status is not RUN)
|
|---|
| 79 | bool unregister_select( T &, select_node & );
|
|---|
| 80 |
|
|---|
| 81 | // This routine is run on the selecting thread prior to executing the statement corresponding to the select_node
|
|---|
| 82 | // passed as an arg to this routine
|
|---|
| 83 | // If on_selected returns false, the statement is not run, if it returns true it is run.
|
|---|
| 84 | bool on_selected( T &, select_node & );
|
|---|
| 85 | };
|
|---|
| 86 |
|
|---|
| 87 | //=============================================================================================
|
|---|
| 88 | // Waituntil Helpers
|
|---|
| 89 | //=============================================================================================
|
|---|
| 90 |
|
|---|
| 91 | // used for the 2-stage avail needed by the special OR case
|
|---|
| 92 | static inline bool __mark_select_node( select_node & this, unsigned long int val ) with( this ) {
|
|---|
| 93 | /* paranoid */ verify( park_counter == 0p );
|
|---|
| 94 | /* paranoid */ verify( clause_status != 0p );
|
|---|
| 95 |
|
|---|
| 96 | unsigned long int cmp_status = __SELECT_UNSAT;
|
|---|
| 97 | while( !__atomic_compare_exchange_n( clause_status, &cmp_status, val, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) ) {
|
|---|
| 98 | if ( cmp_status != __SELECT_PENDING ) return false;
|
|---|
| 99 | cmp_status = __SELECT_UNSAT;
|
|---|
| 100 | }
|
|---|
| 101 | return true;
|
|---|
| 102 | }
|
|---|
| 103 |
|
|---|
| 104 | static inline void __make_select_node_unsat( select_node & this ) with( this ) {
|
|---|
| 105 | __atomic_store_n( clause_status, __SELECT_UNSAT, __ATOMIC_SEQ_CST );
|
|---|
| 106 | }
|
|---|
| 107 |
|
|---|
| 108 | static inline bool __make_select_node_pending( select_node & this ) with( this ) {
|
|---|
| 109 | return __mark_select_node( this, __SELECT_PENDING );
|
|---|
| 110 | }
|
|---|
| 111 |
|
|---|
| 112 | // when a primitive becomes available it calls the following routine on it's node to update the select state:
|
|---|
| 113 | // return true if we want to unpark the thd
|
|---|
| 114 | static inline bool __make_select_node_available( select_node & this ) with( this ) {
|
|---|
| 115 | /* paranoid */ verify( clause_status != 0p );
|
|---|
| 116 | if( !park_counter )
|
|---|
| 117 | return __mark_select_node( this, (unsigned long int)&this );
|
|---|
| 118 | // return *clause_status == 0
|
|---|
| 119 | // && __atomic_compare_exchange_n( clause_status, &cmp_status, (unsigned long int)&this, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ); // OR specific case where race was won
|
|---|
| 120 |
|
|---|
| 121 | unsigned long int cmp_status = __SELECT_UNSAT;
|
|---|
| 122 |
|
|---|
| 123 | return *clause_status == 0 // C_TODO might not need a cmp_xchg in non special OR case
|
|---|
| 124 | && __atomic_compare_exchange_n( clause_status, &cmp_status, __SELECT_SAT, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST ) // can maybe just use atomic write
|
|---|
| 125 | && !__atomic_add_fetch( park_counter, 1, __ATOMIC_SEQ_CST);
|
|---|
| 126 | }
|
|---|
| 127 |
|
|---|
| 128 | // Handles the special OR case of the waituntil statement
|
|---|
| 129 | // Since only one select node can win in the OR case, we need to race to set the node available BEFORE
|
|---|
| 130 | // performing the operation since if we lose the race the operation should not be performed as it will be lost
|
|---|
| 131 | // Returns true if execution can continue normally and false if the queue has now been drained
|
|---|
| 132 | static inline bool __handle_waituntil_OR( dlist( select_node ) & queue ) {
|
|---|
| 133 | if ( queue`isEmpty ) return false;
|
|---|
| 134 | if ( queue`first.clause_status && !queue`first.park_counter ) {
|
|---|
| 135 | while ( !queue`isEmpty ) {
|
|---|
| 136 | // if node not a special OR case or if we win the special OR case race break
|
|---|
| 137 | if ( !queue`first.clause_status || queue`first.park_counter || __make_select_node_available( queue`first ) ) { return true; }
|
|---|
| 138 | // otherwise we lost the special OR race so discard node
|
|---|
| 139 | try_pop_front( queue );
|
|---|
| 140 | }
|
|---|
| 141 | return false;
|
|---|
| 142 | }
|
|---|
| 143 | return true;
|
|---|
| 144 | }
|
|---|
| 145 |
|
|---|
| 146 | // wake one thread from the list
|
|---|
| 147 | static inline void wake_one( dlist( select_node ) & queue, select_node & popped ) {
|
|---|
| 148 | if ( !popped.clause_status // normal case, node is not a select node
|
|---|
| 149 | || ( popped.clause_status && !popped.park_counter ) // If popped link is special case OR selecting unpark but don't call __make_select_node_available
|
|---|
| 150 | || __make_select_node_available( popped ) ) // check if popped link belongs to a selecting thread
|
|---|
| 151 | unpark( popped.blocked_thread );
|
|---|
| 152 | }
|
|---|
| 153 |
|
|---|
| 154 | static inline void wake_one( dlist( select_node ) & queue ) { wake_one( queue, try_pop_front( queue ) ); }
|
|---|
| 155 |
|
|---|
| 156 | static inline void setup_clause( select_node & this, unsigned long int * clause_status, int * park_counter ) {
|
|---|
| 157 | this.blocked_thread = active_thread();
|
|---|
| 158 | this.clause_status = clause_status;
|
|---|
| 159 | this.park_counter = park_counter;
|
|---|
| 160 | }
|
|---|
| 161 |
|
|---|
| 162 |
|
|---|