| 1 | /* Copyright (C) 2005, 2009 Free Software Foundation, Inc. | 
|---|
| 2 | Contributed by Richard Henderson <rth@redhat.com>. | 
|---|
| 3 |  | 
|---|
| 4 | This file is part of the GNU OpenMP Library (libgomp). | 
|---|
| 5 |  | 
|---|
| 6 | Libgomp is free software; you can redistribute it and/or modify it | 
|---|
| 7 | under the terms of the GNU General Public License as published by | 
|---|
| 8 | the Free Software Foundation; either version 3, or (at your option) | 
|---|
| 9 | any later version. | 
|---|
| 10 |  | 
|---|
| 11 | Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY | 
|---|
| 12 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|---|
| 13 | FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|---|
| 14 | more details. | 
|---|
| 15 |  | 
|---|
| 16 | Under Section 7 of GPL version 3, you are granted additional | 
|---|
| 17 | permissions described in the GCC Runtime Library Exception, version | 
|---|
| 18 | 3.1, as published by the Free Software Foundation. | 
|---|
| 19 |  | 
|---|
| 20 | You should have received a copy of the GNU General Public License and | 
|---|
| 21 | a copy of the GCC Runtime Library Exception along with this program; | 
|---|
| 22 | see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see | 
|---|
| 23 | <http://www.gnu.org/licenses/>.  */ | 
|---|
| 24 |  | 
|---|
| 25 | /* This file handles the ORDERED construct.  */ | 
|---|
| 26 |  | 
|---|
| 27 | #include <gomp/libgomp.h> | 
|---|
| 28 |  | 
|---|
| 29 |  | 
|---|
| 30 | /* This function is called when first allocating an iteration block.  That | 
|---|
| 31 | is, the thread is not currently on the queue.  The work-share lock must | 
|---|
| 32 | be held on entry.  */ | 
|---|
| 33 |  | 
|---|
| 34 | void | 
|---|
| 35 | gomp_ordered_first (void) | 
|---|
| 36 | { | 
|---|
| 37 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 38 | struct gomp_team *team = thr->ts.team; | 
|---|
| 39 | struct gomp_work_share *ws = thr->ts.work_share; | 
|---|
| 40 | unsigned index; | 
|---|
| 41 |  | 
|---|
| 42 | /* Work share constructs can be orphaned.  */ | 
|---|
| 43 | if (team == NULL || team->nthreads == 1) | 
|---|
| 44 | return; | 
|---|
| 45 |  | 
|---|
| 46 | index = ws->ordered_cur + ws->ordered_num_used; | 
|---|
| 47 | if (index >= team->nthreads) | 
|---|
| 48 | index -= team->nthreads; | 
|---|
| 49 | ws->ordered_team_ids[index] = thr->ts.team_id; | 
|---|
| 50 |  | 
|---|
| 51 | /* If this is the first and only thread in the queue, then there is | 
|---|
| 52 | no one to release us when we get to our ordered section.  Post to | 
|---|
| 53 | our own release queue now so that we won't block later.  */ | 
|---|
| 54 | if (ws->ordered_num_used++ == 0) | 
|---|
| 55 | gomp_sem_post (team->ordered_release[thr->ts.team_id]); | 
|---|
| 56 | } | 
|---|
| 57 |  | 
|---|
| 58 | /* This function is called when completing the last iteration block.  That | 
|---|
| 59 | is, there are no more iterations to perform and so the thread should be | 
|---|
| 60 | removed from the queue entirely.  Because of the way ORDERED blocks are | 
|---|
| 61 | managed, it follows that we currently own access to the ORDERED block, | 
|---|
| 62 | and should now pass it on to the next thread.  The work-share lock must | 
|---|
| 63 | be held on entry.  */ | 
|---|
| 64 |  | 
|---|
| 65 | void | 
|---|
| 66 | gomp_ordered_last (void) | 
|---|
| 67 | { | 
|---|
| 68 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 69 | struct gomp_team *team = thr->ts.team; | 
|---|
| 70 | struct gomp_work_share *ws = thr->ts.work_share; | 
|---|
| 71 | unsigned next_id; | 
|---|
| 72 |  | 
|---|
| 73 | /* Work share constructs can be orphaned.  */ | 
|---|
| 74 | if (team == NULL || team->nthreads == 1) | 
|---|
| 75 | return; | 
|---|
| 76 |  | 
|---|
| 77 | /* We're no longer the owner.  */ | 
|---|
| 78 | ws->ordered_owner = -1; | 
|---|
| 79 |  | 
|---|
| 80 | /* If we're not the last thread in the queue, then wake the next.  */ | 
|---|
| 81 | if (--ws->ordered_num_used > 0) | 
|---|
| 82 | { | 
|---|
| 83 | unsigned next = ws->ordered_cur + 1; | 
|---|
| 84 | if (next == team->nthreads) | 
|---|
| 85 | next = 0; | 
|---|
| 86 | ws->ordered_cur = next; | 
|---|
| 87 |  | 
|---|
| 88 | next_id = ws->ordered_team_ids[next]; | 
|---|
| 89 | gomp_sem_post (team->ordered_release[next_id]); | 
|---|
| 90 | } | 
|---|
| 91 | } | 
|---|
| 92 |  | 
|---|
| 93 |  | 
|---|
| 94 | /* This function is called when allocating a subsequent allocation block. | 
|---|
| 95 | That is, we're done with the current iteration block and we're allocating | 
|---|
| 96 | another.  This is the logical combination of a call to gomp_ordered_last | 
|---|
| 97 | followed by a call to gomp_ordered_first.  The work-share lock must be | 
|---|
| 98 | held on entry. */ | 
|---|
| 99 |  | 
|---|
| 100 | void | 
|---|
| 101 | gomp_ordered_next (void) | 
|---|
| 102 | { | 
|---|
| 103 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 104 | struct gomp_team *team = thr->ts.team; | 
|---|
| 105 | struct gomp_work_share *ws = thr->ts.work_share; | 
|---|
| 106 | unsigned index, next_id; | 
|---|
| 107 |  | 
|---|
| 108 | /* Work share constructs can be orphaned.  */ | 
|---|
| 109 | if (team == NULL || team->nthreads == 1) | 
|---|
| 110 | return; | 
|---|
| 111 |  | 
|---|
| 112 | /* We're no longer the owner.  */ | 
|---|
| 113 | ws->ordered_owner = -1; | 
|---|
| 114 |  | 
|---|
| 115 | /* If there's only one thread in the queue, that must be us.  */ | 
|---|
| 116 | if (ws->ordered_num_used == 1) | 
|---|
| 117 | { | 
|---|
| 118 | /* We have a similar situation as in gomp_ordered_first | 
|---|
| 119 | where we need to post to our own release semaphore.  */ | 
|---|
| 120 | gomp_sem_post (team->ordered_release[thr->ts.team_id]); | 
|---|
| 121 | return; | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | /* If the queue is entirely full, then we move ourself to the end of | 
|---|
| 125 | the queue merely by incrementing ordered_cur.  Only if it's not | 
|---|
| 126 | full do we have to write our id.  */ | 
|---|
| 127 | if (ws->ordered_num_used < team->nthreads) | 
|---|
| 128 | { | 
|---|
| 129 | index = ws->ordered_cur + ws->ordered_num_used; | 
|---|
| 130 | if (index >= team->nthreads) | 
|---|
| 131 | index -= team->nthreads; | 
|---|
| 132 | ws->ordered_team_ids[index] = thr->ts.team_id; | 
|---|
| 133 | } | 
|---|
| 134 |  | 
|---|
| 135 | index = ws->ordered_cur + 1; | 
|---|
| 136 | if (index == team->nthreads) | 
|---|
| 137 | index = 0; | 
|---|
| 138 | ws->ordered_cur = index; | 
|---|
| 139 |  | 
|---|
| 140 | next_id = ws->ordered_team_ids[index]; | 
|---|
| 141 | gomp_sem_post (team->ordered_release[next_id]); | 
|---|
| 142 | } | 
|---|
| 143 |  | 
|---|
| 144 |  | 
|---|
| 145 | /* This function is called when a statically scheduled loop is first | 
|---|
| 146 | being created.  */ | 
|---|
| 147 |  | 
|---|
| 148 | void | 
|---|
| 149 | gomp_ordered_static_init (void) | 
|---|
| 150 | { | 
|---|
| 151 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 152 | struct gomp_team *team = thr->ts.team; | 
|---|
| 153 |  | 
|---|
| 154 | if (team == NULL || team->nthreads == 1) | 
|---|
| 155 | return; | 
|---|
| 156 |  | 
|---|
| 157 | gomp_sem_post (team->ordered_release[0]); | 
|---|
| 158 | } | 
|---|
| 159 |  | 
|---|
| 160 | /* This function is called when a statically scheduled loop is moving to | 
|---|
| 161 | the next allocation block.  Static schedules are not first come first | 
|---|
| 162 | served like the others, so we're to move to the numerically next thread, | 
|---|
| 163 | not the next thread on a list.  The work-share lock should *not* be held | 
|---|
| 164 | on entry.  */ | 
|---|
| 165 |  | 
|---|
| 166 | void | 
|---|
| 167 | gomp_ordered_static_next (void) | 
|---|
| 168 | { | 
|---|
| 169 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 170 | struct gomp_team *team = thr->ts.team; | 
|---|
| 171 | struct gomp_work_share *ws = thr->ts.work_share; | 
|---|
| 172 | unsigned id = thr->ts.team_id; | 
|---|
| 173 |  | 
|---|
| 174 | if (team == NULL || team->nthreads == 1) | 
|---|
| 175 | return; | 
|---|
| 176 |  | 
|---|
| 177 | ws->ordered_owner = -1; | 
|---|
| 178 |  | 
|---|
| 179 | /* This thread currently owns the lock.  Increment the owner.  */ | 
|---|
| 180 | if (++id == team->nthreads) | 
|---|
| 181 | id = 0; | 
|---|
| 182 | ws->ordered_team_ids[0] = id; | 
|---|
| 183 | gomp_sem_post (team->ordered_release[id]); | 
|---|
| 184 | } | 
|---|
| 185 |  | 
|---|
| 186 | /* This function is called when we need to assert that the thread owns the | 
|---|
| 187 | ordered section.  Due to the problem of posted-but-not-waited semaphores, | 
|---|
| 188 | this needs to happen before completing a loop iteration.  */ | 
|---|
| 189 |  | 
|---|
| 190 | void | 
|---|
| 191 | gomp_ordered_sync (void) | 
|---|
| 192 | { | 
|---|
| 193 | struct gomp_thread *thr = gomp_thread (); | 
|---|
| 194 | struct gomp_team *team = thr->ts.team; | 
|---|
| 195 | struct gomp_work_share *ws = thr->ts.work_share; | 
|---|
| 196 |  | 
|---|
| 197 | /* Work share constructs can be orphaned.  But this clearly means that | 
|---|
| 198 | we are the only thread, and so we automatically own the section.  */ | 
|---|
| 199 | if (team == NULL || team->nthreads == 1) | 
|---|
| 200 | return; | 
|---|
| 201 |  | 
|---|
| 202 | /* ??? I believe it to be safe to access this data without taking the | 
|---|
| 203 | ws->lock.  The only presumed race condition is with the previous | 
|---|
| 204 | thread on the queue incrementing ordered_cur such that it points | 
|---|
| 205 | to us, concurrently with our check below.  But our team_id is | 
|---|
| 206 | already present in the queue, and the other thread will always | 
|---|
| 207 | post to our release semaphore.  So the two cases are that we will | 
|---|
| 208 | either win the race an momentarily block on the semaphore, or lose | 
|---|
| 209 | the race and find the semaphore already unlocked and so not block. | 
|---|
| 210 | Either way we get correct results.  */ | 
|---|
| 211 |  | 
|---|
| 212 | if (ws->ordered_owner != thr->ts.team_id) | 
|---|
| 213 | { | 
|---|
| 214 | gomp_sem_wait (team->ordered_release[thr->ts.team_id]); | 
|---|
| 215 | ws->ordered_owner = thr->ts.team_id; | 
|---|
| 216 | } | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | /* This function is called by user code when encountering the start of an | 
|---|
| 220 | ORDERED block.  We must check to see if the current thread is at the | 
|---|
| 221 | head of the queue, and if not, block.  */ | 
|---|
| 222 |  | 
|---|
| 223 | #ifdef HAVE_ATTRIBUTE_ALIAS | 
|---|
| 224 | extern void GOMP_ordered_start (void) | 
|---|
| 225 | __attribute__((alias ("gomp_ordered_sync"))); | 
|---|
| 226 | #else | 
|---|
| 227 | void | 
|---|
| 228 | GOMP_ordered_start (void) | 
|---|
| 229 | { | 
|---|
| 230 | gomp_ordered_sync (); | 
|---|
| 231 | } | 
|---|
| 232 | #endif | 
|---|
| 233 |  | 
|---|
| 234 | /* This function is called by user code when encountering the end of an | 
|---|
| 235 | ORDERED block.  With the current ORDERED implementation there's nothing | 
|---|
| 236 | for us to do. | 
|---|
| 237 |  | 
|---|
| 238 | However, the current implementation has a flaw in that it does not allow | 
|---|
| 239 | the next thread into the ORDERED section immediately after the current | 
|---|
| 240 | thread exits the ORDERED section in its last iteration.  The existance | 
|---|
| 241 | of this function allows the implementation to change.  */ | 
|---|
| 242 |  | 
|---|
| 243 | void | 
|---|
| 244 | GOMP_ordered_end (void) | 
|---|
| 245 | { | 
|---|
| 246 | } | 
|---|