1 /* fibers.c -- extremely simple lightweight thread (fiber) implementation 2 Copyright (C) 2016-2020 Free Software Foundation, Inc. 3 Contributed by Pekka Jaaskelainen <pekka.jaaskelainen@parmance.com> 4 for General Processor Tech. 5 6 Copyright (C) 2015-2020 Free Software Foundation, Inc. 7 Contributed by Pekka Jaaskelainen <pekka.jaaskelainen@parmance.com> 8 for General Processor Tech. 9 10 Permission is hereby granted, free of charge, to any person obtaining a 11 copy of this software and associated documentation files 12 (the "Software"), to deal in the Software without restriction, including 13 without limitation the rights to use, copy, modify, merge, publish, 14 distribute, sublicense, and/or sell copies of the Software, and to 15 permit persons to whom the Software is furnished to do so, subject to 16 the following conditions: 17 18 The above copyright notice and this permission notice shall be included 19 in all copies or substantial portions of the Software. 20 21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 22 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 25 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 26 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 27 USE OR OTHER DEALINGS IN THE SOFTWARE. 28 */ 29 30 #include <stdlib.h> 31 #include <stdio.h> 32 #include <stdint.h> 33 34 #include "target-config.h" 35 36 #include "fibers.h" 37 38 void 39 phsa_fatal_error (int code); 40 41 ucontext_t main_context; 42 43 /* The last fiber in the linked list. */ 44 static fiber_t *tail_fiber = NULL; 45 /* The first fiber in the linked list. */ 46 static fiber_t *head_fiber = NULL; 47 /* The fiber currently being executed. */ 48 static fiber_t *current_fiber = NULL; 49 50 /* Makecontext accepts only integer arguments. We need to split the 51 pointer argument in case pointer does not fit into int. This helper 52 function can be used to restore the pointer from the arguments. */ 53 54 void * 55 fiber_int_args_to_ptr (int arg0, int arg1) 56 { 57 void *ptr = NULL; 58 #if SIZEOF_VOIDP == 8 && SIZEOF_INT == 4 59 ptr = (void*)(((uint64_t) arg0 & (uint64_t) 0xFFFFFFFF) 60 | ((uint64_t) arg1 << 32)); 61 #elif SIZEOF_VOIDP == 4 && SIZEOF_INT == 4 62 ptr = (void*)arg0; 63 #else 64 # error Unsupported pointer/int size. 65 #endif 66 return ptr; 67 } 68 69 void 70 fiber_init (fiber_t *fiber, fiber_function_t start_function, void *arg, 71 size_t stack_size, size_t stack_align) 72 { 73 int arg0, arg1; 74 if (getcontext (&fiber->context) != 0) 75 phsa_fatal_error (3); 76 if (posix_memalign (&fiber->context.uc_stack.ss_sp, stack_align, stack_size) 77 != 0) 78 phsa_fatal_error (4); 79 fiber->context.uc_stack.ss_size = stack_size; 80 fiber->context.uc_link = &main_context; 81 82 /* makecontext () accepts only integer arguments. Split the 83 pointer argument to two args in the case pointer does not fit 84 into one int. */ 85 #if SIZEOF_VOIDP == 8 && SIZEOF_INT == 4 86 arg0 = (int32_t) 0xFFFFFFFF & (uint64_t)arg; 87 arg1 = (int32_t) 0xFFFFFFFF & ((uint64_t)arg >> 32); 88 #elif SIZEOF_VOIDP == 4 && SIZEOF_INT == 4 89 arg0 = (int)arg; 90 arg1 = 0; 91 #else 92 # error Unsupported pointer/int size. 93 #endif 94 95 makecontext (&fiber->context, (void*)start_function, 2, arg0, arg1); 96 97 fiber->status = FIBER_STATUS_READY; 98 fiber->next = NULL; 99 fiber->prev = NULL; 100 101 /* Create a linked list of the created fibers. Append the new one at 102 the end. */ 103 if (tail_fiber == NULL) 104 tail_fiber = fiber; 105 else 106 { 107 tail_fiber->next = fiber; 108 fiber->prev = tail_fiber; 109 tail_fiber = fiber; 110 } 111 112 if (head_fiber == NULL) 113 head_fiber = fiber; 114 } 115 116 void 117 fiber_exit () 118 { 119 fiber_status_t old_status = current_fiber->status; 120 current_fiber->status = FIBER_STATUS_EXITED; 121 if (old_status == FIBER_STATUS_JOINED) 122 /* In case this thread has been joined, return back to the joiner. */ 123 swapcontext (¤t_fiber->context, &main_context); 124 else 125 /* In case the thread exited while being yielded from another thread, 126 switch back to another fiber. */ 127 fiber_yield (); 128 } 129 130 void 131 fiber_join (fiber_t *fiber) 132 { 133 fiber_t *next_ready_fiber = NULL; 134 current_fiber = fiber; 135 if (fiber->status != FIBER_STATUS_EXITED) 136 { 137 fiber->status = FIBER_STATUS_JOINED; 138 while (fiber->status != FIBER_STATUS_EXITED) 139 swapcontext (&main_context, &fiber->context); 140 } 141 142 /* Remove the successfully joined fiber from the linked list so we won't 143 access it later (the fiber itself might be freed after the join). */ 144 if (fiber->prev != NULL) 145 fiber->prev->next = fiber->next; 146 147 if (fiber->next != NULL) 148 fiber->next->prev = fiber->prev; 149 150 if (head_fiber == fiber) 151 head_fiber = fiber->next; 152 153 if (tail_fiber == fiber) 154 tail_fiber = fiber->prev; 155 156 free (fiber->context.uc_stack.ss_sp); 157 } 158 159 void 160 fiber_yield () 161 { 162 fiber_t *next_ready_fiber = current_fiber; 163 164 if (current_fiber == head_fiber 165 && current_fiber == tail_fiber) 166 { 167 /* If the last fiber exits independently, there is no 168 fiber to switch to. Switch to the main context in that 169 case. */ 170 if (current_fiber->status == FIBER_STATUS_EXITED) 171 swapcontext (¤t_fiber->context, &main_context); 172 } 173 174 do { 175 next_ready_fiber = next_ready_fiber->next != NULL 176 ? next_ready_fiber->next : head_fiber; 177 } while (next_ready_fiber != current_fiber 178 && next_ready_fiber->status == FIBER_STATUS_EXITED); 179 180 fiber_t *old_current_fiber = current_fiber; 181 current_fiber = next_ready_fiber; 182 swapcontext (&old_current_fiber->context, &next_ready_fiber->context); 183 } 184 185 size_t 186 fiber_barrier_reach (fiber_barrier_t *barrier) 187 { 188 /* Yield once to ensure that there are no fibers waiting for 189 a previous triggering of the barrier in the waiting_count 190 loop. This should release them before we update the reached 191 counter again. */ 192 fiber_yield (); 193 194 barrier->reached++; 195 ++barrier->waiting_count; 196 while (barrier->reached < barrier->threshold) 197 fiber_yield (); 198 --barrier->waiting_count; 199 200 /* Wait until all the fibers have reached this point. */ 201 while (barrier->waiting_count > 0) 202 fiber_yield (); 203 204 /* Now all fibers have been released from the barrier waiting 205 loop. We can now safely reset the reach count for new triggering. */ 206 if (barrier->reached > 0) 207 { 208 barrier->reached = 0; 209 return 0; 210 } 211 return 1; 212 } 213 214 void 215 fiber_barrier_init (fiber_barrier_t *barrier, size_t threshold) 216 { 217 barrier->threshold = threshold; 218 barrier->waiting_count = 0; 219 barrier->reached = 0; 220 } 221