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 *
fiber_int_args_to_ptr(int arg0,int arg1)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
fiber_init(fiber_t * fiber,fiber_function_t start_function,void * arg,size_t stack_size,size_t stack_align)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
fiber_exit()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
fiber_join(fiber_t * fiber)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
fiber_yield()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
fiber_barrier_reach(fiber_barrier_t * barrier)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
fiber_barrier_init(fiber_barrier_t * barrier,size_t threshold)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