xref: /dragonfly/contrib/gcc-8.0/libgomp/work.c (revision ed183f8c)
1 /* Copyright (C) 2005-2018 Free Software Foundation, Inc.
2    Contributed by Richard Henderson <rth@redhat.com>.
3 
4    This file is part of the GNU Offloading and Multi Processing Library
5    (libgomp).
6 
7    Libgomp is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3, or (at your option)
10    any later version.
11 
12    Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY
13    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14    FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15    more details.
16 
17    Under Section 7 of GPL version 3, you are granted additional
18    permissions described in the GCC Runtime Library Exception, version
19    3.1, as published by the Free Software Foundation.
20 
21    You should have received a copy of the GNU General Public License and
22    a copy of the GCC Runtime Library Exception along with this program;
23    see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
24    <http://www.gnu.org/licenses/>.  */
25 
26 /* This file contains routines to manage the work-share queue for a team
27    of threads.  */
28 
29 #include "libgomp.h"
30 #include <stddef.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 
35 /* Allocate a new work share structure, preferably from current team's
36    free gomp_work_share cache.  */
37 
38 static struct gomp_work_share *
39 alloc_work_share (struct gomp_team *team)
40 {
41   struct gomp_work_share *ws;
42   unsigned int i;
43 
44   /* This is called in a critical section.  */
45   if (team->work_share_list_alloc != NULL)
46     {
47       ws = team->work_share_list_alloc;
48       team->work_share_list_alloc = ws->next_free;
49       return ws;
50     }
51 
52 #ifdef HAVE_SYNC_BUILTINS
53   ws = team->work_share_list_free;
54   /* We need atomic read from work_share_list_free,
55      as free_work_share can be called concurrently.  */
56   __asm ("" : "+r" (ws));
57 
58   if (ws && ws->next_free)
59     {
60       struct gomp_work_share *next = ws->next_free;
61       ws->next_free = NULL;
62       team->work_share_list_alloc = next->next_free;
63       return next;
64     }
65 #else
66   gomp_mutex_lock (&team->work_share_list_free_lock);
67   ws = team->work_share_list_free;
68   if (ws)
69     {
70       team->work_share_list_alloc = ws->next_free;
71       team->work_share_list_free = NULL;
72       gomp_mutex_unlock (&team->work_share_list_free_lock);
73       return ws;
74     }
75   gomp_mutex_unlock (&team->work_share_list_free_lock);
76 #endif
77 
78   team->work_share_chunk *= 2;
79   ws = gomp_malloc (team->work_share_chunk * sizeof (struct gomp_work_share));
80   ws->next_alloc = team->work_shares[0].next_alloc;
81   team->work_shares[0].next_alloc = ws;
82   team->work_share_list_alloc = &ws[1];
83   for (i = 1; i < team->work_share_chunk - 1; i++)
84     ws[i].next_free = &ws[i + 1];
85   ws[i].next_free = NULL;
86   return ws;
87 }
88 
89 /* Initialize an already allocated struct gomp_work_share.
90    This shouldn't touch the next_alloc field.  */
91 
92 void
93 gomp_init_work_share (struct gomp_work_share *ws, bool ordered,
94 		      unsigned nthreads)
95 {
96   gomp_mutex_init (&ws->lock);
97   if (__builtin_expect (ordered, 0))
98     {
99 #define INLINE_ORDERED_TEAM_IDS_CNT \
100   ((sizeof (struct gomp_work_share) \
101     - offsetof (struct gomp_work_share, inline_ordered_team_ids)) \
102    / sizeof (((struct gomp_work_share *) 0)->inline_ordered_team_ids[0]))
103 
104       if (nthreads > INLINE_ORDERED_TEAM_IDS_CNT)
105 	ws->ordered_team_ids
106 	  = gomp_malloc (nthreads * sizeof (*ws->ordered_team_ids));
107       else
108 	ws->ordered_team_ids = ws->inline_ordered_team_ids;
109       memset (ws->ordered_team_ids, '\0',
110 	      nthreads * sizeof (*ws->ordered_team_ids));
111       ws->ordered_num_used = 0;
112       ws->ordered_owner = -1;
113       ws->ordered_cur = 0;
114     }
115   else
116     ws->ordered_team_ids = NULL;
117   gomp_ptrlock_init (&ws->next_ws, NULL);
118   ws->threads_completed = 0;
119 }
120 
121 /* Do any needed destruction of gomp_work_share fields before it
122    is put back into free gomp_work_share cache or freed.  */
123 
124 void
125 gomp_fini_work_share (struct gomp_work_share *ws)
126 {
127   gomp_mutex_destroy (&ws->lock);
128   if (ws->ordered_team_ids != ws->inline_ordered_team_ids)
129     free (ws->ordered_team_ids);
130   gomp_ptrlock_destroy (&ws->next_ws);
131 }
132 
133 /* Free a work share struct, if not orphaned, put it into current
134    team's free gomp_work_share cache.  */
135 
136 static inline void
137 free_work_share (struct gomp_team *team, struct gomp_work_share *ws)
138 {
139   gomp_fini_work_share (ws);
140   if (__builtin_expect (team == NULL, 0))
141     free (ws);
142   else
143     {
144       struct gomp_work_share *next_ws;
145 #ifdef HAVE_SYNC_BUILTINS
146       do
147 	{
148 	  next_ws = team->work_share_list_free;
149 	  ws->next_free = next_ws;
150 	}
151       while (!__sync_bool_compare_and_swap (&team->work_share_list_free,
152 					    next_ws, ws));
153 #else
154       gomp_mutex_lock (&team->work_share_list_free_lock);
155       next_ws = team->work_share_list_free;
156       ws->next_free = next_ws;
157       team->work_share_list_free = ws;
158       gomp_mutex_unlock (&team->work_share_list_free_lock);
159 #endif
160     }
161 }
162 
163 /* The current thread is ready to begin the next work sharing construct.
164    In all cases, thr->ts.work_share is updated to point to the new
165    structure.  In all cases the work_share lock is locked.  Return true
166    if this was the first thread to reach this point.  */
167 
168 bool
169 gomp_work_share_start (bool ordered)
170 {
171   struct gomp_thread *thr = gomp_thread ();
172   struct gomp_team *team = thr->ts.team;
173   struct gomp_work_share *ws;
174 
175   /* Work sharing constructs can be orphaned.  */
176   if (team == NULL)
177     {
178       ws = gomp_malloc (sizeof (*ws));
179       gomp_init_work_share (ws, ordered, 1);
180       thr->ts.work_share = ws;
181       return ws;
182     }
183 
184   ws = thr->ts.work_share;
185   thr->ts.last_work_share = ws;
186   ws = gomp_ptrlock_get (&ws->next_ws);
187   if (ws == NULL)
188     {
189       /* This thread encountered a new ws first.  */
190       struct gomp_work_share *ws = alloc_work_share (team);
191       gomp_init_work_share (ws, ordered, team->nthreads);
192       thr->ts.work_share = ws;
193       return true;
194     }
195   else
196     {
197       thr->ts.work_share = ws;
198       return false;
199     }
200 }
201 
202 /* The current thread is done with its current work sharing construct.
203    This version does imply a barrier at the end of the work-share.  */
204 
205 void
206 gomp_work_share_end (void)
207 {
208   struct gomp_thread *thr = gomp_thread ();
209   struct gomp_team *team = thr->ts.team;
210   gomp_barrier_state_t bstate;
211 
212   /* Work sharing constructs can be orphaned.  */
213   if (team == NULL)
214     {
215       free_work_share (NULL, thr->ts.work_share);
216       thr->ts.work_share = NULL;
217       return;
218     }
219 
220   bstate = gomp_barrier_wait_start (&team->barrier);
221 
222   if (gomp_barrier_last_thread (bstate))
223     {
224       if (__builtin_expect (thr->ts.last_work_share != NULL, 1))
225 	{
226 	  team->work_shares_to_free = thr->ts.work_share;
227 	  free_work_share (team, thr->ts.last_work_share);
228 	}
229     }
230 
231   gomp_team_barrier_wait_end (&team->barrier, bstate);
232   thr->ts.last_work_share = NULL;
233 }
234 
235 /* The current thread is done with its current work sharing construct.
236    This version implies a cancellable barrier at the end of the work-share.  */
237 
238 bool
239 gomp_work_share_end_cancel (void)
240 {
241   struct gomp_thread *thr = gomp_thread ();
242   struct gomp_team *team = thr->ts.team;
243   gomp_barrier_state_t bstate;
244 
245   /* Cancellable work sharing constructs cannot be orphaned.  */
246   bstate = gomp_barrier_wait_cancel_start (&team->barrier);
247 
248   if (gomp_barrier_last_thread (bstate))
249     {
250       if (__builtin_expect (thr->ts.last_work_share != NULL, 1))
251 	{
252 	  team->work_shares_to_free = thr->ts.work_share;
253 	  free_work_share (team, thr->ts.last_work_share);
254 	}
255     }
256   thr->ts.last_work_share = NULL;
257 
258   return gomp_team_barrier_wait_cancel_end (&team->barrier, bstate);
259 }
260 
261 /* The current thread is done with its current work sharing construct.
262    This version does NOT imply a barrier at the end of the work-share.  */
263 
264 void
265 gomp_work_share_end_nowait (void)
266 {
267   struct gomp_thread *thr = gomp_thread ();
268   struct gomp_team *team = thr->ts.team;
269   struct gomp_work_share *ws = thr->ts.work_share;
270   unsigned completed;
271 
272   /* Work sharing constructs can be orphaned.  */
273   if (team == NULL)
274     {
275       free_work_share (NULL, ws);
276       thr->ts.work_share = NULL;
277       return;
278     }
279 
280   if (__builtin_expect (thr->ts.last_work_share == NULL, 0))
281     return;
282 
283 #ifdef HAVE_SYNC_BUILTINS
284   completed = __sync_add_and_fetch (&ws->threads_completed, 1);
285 #else
286   gomp_mutex_lock (&ws->lock);
287   completed = ++ws->threads_completed;
288   gomp_mutex_unlock (&ws->lock);
289 #endif
290 
291   if (completed == team->nthreads)
292     {
293       team->work_shares_to_free = thr->ts.work_share;
294       free_work_share (team, thr->ts.last_work_share);
295     }
296   thr->ts.last_work_share = NULL;
297 }
298