1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <assert.h> 4 #include <pthread.h> 5 #include <semaphore.h> 6 #include <unistd.h> 7 /* This is really a test of semaphore handling 8 (sem_{init,destroy,post,wait}). Using semaphores a barrier 9 function is created. Helgrind-3.3 (p.k.a Thrcheck) does understand 10 the barrier semantics implied by the barrier, as pieced together 11 from happens-before relationships obtained from the component 12 semaphores. However, it does falsely report one race. Ah well. 13 Helgrind-3.4 is pure h-b and so reports no races (yay!). */ 14 /* This code is derived from 15 gcc-4.3-20071012/libgomp/config/posix/bar.c, which is 16 17 Copyright (C) 2005 Free Software Foundation, Inc. 18 Contributed by Richard Henderson <rth (at) redhat.com>. 19 20 and available under version 2.1 or later of the GNU Lesser General 21 Public License. 22 23 Relative to the libgomp sources, the gomp_barrier_t type here has 24 an extra semaphore field, xxx. This is not functionally useful, 25 but it is used to create enough extra inter-thread dependencies 26 that the barrier-like behaviour of gomp_barrier_t is evident to 27 Thrcheck. There is no other purpose for the .xxx field. */ 28 static int my_sem_init(sem_t*, char*, int, unsigned); 29 static int my_sem_destroy(sem_t*); 30 static int my_sem_wait(sem_t*); static int my_sem_post(sem_t*); 31 typedef struct 32 { 33 pthread_mutex_t mutex1; 34 pthread_mutex_t mutex2; 35 sem_t sem1; 36 sem_t sem2; 37 unsigned total; 38 unsigned arrived; 39 sem_t xxx; 40 } gomp_barrier_t; 41 42 typedef long bool; 43 44 void 45 gomp_barrier_init (gomp_barrier_t *bar, unsigned count) 46 { 47 pthread_mutex_init (&bar->mutex1, NULL); 48 pthread_mutex_init (&bar->mutex2, NULL); 49 my_sem_init (&bar->sem1, "sem1", 0, 0); 50 my_sem_init (&bar->sem2, "sem2", 0, 0); 51 my_sem_init (&bar->xxx, "xxx", 0, 0); 52 bar->total = count; 53 bar->arrived = 0; 54 } 55 56 void 57 gomp_barrier_destroy (gomp_barrier_t *bar) 58 { 59 /* Before destroying, make sure all threads have left the barrier. */ 60 pthread_mutex_lock (&bar->mutex1); 61 pthread_mutex_unlock (&bar->mutex1); 62 63 pthread_mutex_destroy (&bar->mutex1); 64 pthread_mutex_destroy (&bar->mutex2); 65 my_sem_destroy (&bar->sem1); 66 my_sem_destroy (&bar->sem2); 67 my_sem_destroy(&bar->xxx); 68 } 69 70 void 71 gomp_barrier_reinit (gomp_barrier_t *bar, unsigned count) 72 { 73 pthread_mutex_lock (&bar->mutex1); 74 bar->total = count; 75 pthread_mutex_unlock (&bar->mutex1); 76 } 77 78 void 79 gomp_barrier_wait (gomp_barrier_t *bar) 80 { 81 unsigned int n; 82 pthread_mutex_lock (&bar->mutex1); 83 84 ++bar->arrived; 85 86 if (bar->arrived == bar->total) 87 { 88 bar->arrived--; 89 n = bar->arrived; 90 if (n > 0) 91 { 92 { unsigned int i; 93 for (i = 0; i < n; i++) 94 my_sem_wait(&bar->xxx); // acquire an obvious dependency from 95 // all other threads arriving at the barrier 96 } 97 // 1 up n times, 2 down once 98 // now let all the other threads past the barrier, giving them 99 // an obvious dependency with this thread. 100 do 101 my_sem_post (&bar->sem1); // 1 up 102 while (--n != 0); 103 // and wait till the last thread has left 104 my_sem_wait (&bar->sem2); // 2 down 105 } 106 pthread_mutex_unlock (&bar->mutex1); 107 /* Resultats professionnels! First we made this thread have an 108 obvious (Thrcheck-visible) dependency on all other threads 109 calling gomp_barrier_wait. Then, we released them all again, 110 so they all have a (visible) dependency on this thread. 111 Transitively, the result is that all threads leaving the 112 barrier have a a Thrcheck-visible dependency on all threads 113 arriving at the barrier. As required. */ 114 } 115 else 116 { 117 pthread_mutex_unlock (&bar->mutex1); 118 my_sem_post(&bar->xxx); 119 // first N-1 threads wind up waiting here 120 my_sem_wait (&bar->sem1); // 1 down 121 122 pthread_mutex_lock (&bar->mutex2); 123 n = --bar->arrived; /* XXX see below */ 124 pthread_mutex_unlock (&bar->mutex2); 125 126 if (n == 0) 127 my_sem_post (&bar->sem2); // 2 up 128 } 129 } 130 131 132 /* re XXX, thrcheck reports a race at this point. It doesn't 133 understand that bar->arrived is protected by mutex1 whilst threads 134 are arriving at the barrier and by mutex2 whilst they are leaving, 135 but not consistently by either of them. Oh well. */ 136 137 static gomp_barrier_t bar; 138 139 /* What's with the volatile here? It stops gcc compiling 140 "if (myid == 4) { unprotected = 99; }" and 141 "if (myid == 3) { unprotected = 88; }" into a conditional 142 load followed by a store. The cmov/store sequence reads and 143 writes memory in all threads and cause Thrcheck to (correctly) 144 report a race, the underlying cause of which is that gcc is 145 generating non threadsafe code. 146 147 (The lack of) thread safe code generation by gcc is currently a 148 hot topic. See the following discussions: 149 http://gcc.gnu.org/ml/gcc/2007-10/msg00266.html 150 http://lkml.org/lkml/2007/10/24/673 151 and this is interesting background: 152 www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf 153 */ 154 static volatile long unprotected = 0; 155 156 void* child ( void* argV ) 157 { 158 long myid = (long)argV; 159 // assert(myid >= 2 && myid <= 5); 160 161 /* First, we all wait to get to this point. */ 162 gomp_barrier_wait( &bar ); 163 164 /* Now, thread #4 writes to 'unprotected' and so becomes its 165 owner. */ 166 if (myid == 4) { 167 unprotected = 99; 168 } 169 170 /* Now we all wait again. */ 171 gomp_barrier_wait( &bar ); 172 173 /* This time, thread #3 writes to 'unprotected'. If all goes well, 174 Thrcheck sees the dependency through the barrier back to thread 175 #4 before it, and so thread #3 becomes the exclusive owner of 176 'unprotected'. */ 177 if (myid == 3) { 178 unprotected = 88; 179 } 180 181 /* And just to be on the safe side ... */ 182 gomp_barrier_wait( &bar ); 183 return NULL; 184 } 185 186 187 int main (int argc, char *argv[]) 188 { 189 long i; int res; 190 pthread_t thr[4]; 191 fprintf(stderr, "starting\n"); 192 193 gomp_barrier_init( &bar, 4 ); 194 195 for (i = 0; i < 4; i++) { 196 res = pthread_create( &thr[i], NULL, child, (void*)(i+2) ); 197 assert(!res); 198 } 199 200 for (i = 0; i < 4; i++) { 201 res = pthread_join( thr[i], NULL ); 202 assert(!res); 203 } 204 205 gomp_barrier_destroy( &bar ); 206 207 /* And finally here, the root thread can get exclusive ownership 208 back from thread #4, because #4 has exited by this point and so 209 we have a dependency edge back to the write it did. */ 210 fprintf(stderr, "done, result is %ld, should be 88\n", unprotected); 211 212 return 0; 213 } 214 215 216 217 218 219 220 221 static int my_sem_init (sem_t* s, char* identity, int pshared, unsigned count) 222 { 223 #if defined(VGO_linux) 224 return sem_init(s, pshared, count); 225 #elif defined(VGO_darwin) 226 char name[100]; 227 sem_t** fakeptr = (sem_t**)s; 228 assert(sizeof(sem_t) >= sizeof(sem_t*)); 229 { int i; for (i = 0; i < sizeof(name); i++) name[i] = 0; } 230 sprintf(name, "anonsem_%s_pid%d", identity, (int)getpid()); 231 name[ sizeof(name)-1 ] = 0; 232 if (0) printf("name = %s\n", name); 233 *fakeptr = sem_open(name, O_CREAT, 0600, count); 234 if (*fakeptr == (sem_t*)SEM_FAILED) 235 return -1; 236 else 237 return 0; 238 #else 239 # error "Unsupported OS" 240 #endif 241 } 242 243 static int my_sem_destroy ( sem_t* s ) 244 { 245 #if defined(VGO_linux) 246 return sem_destroy(s); 247 #elif defined(VGO_darwin) 248 sem_t** fakeptr = (sem_t**)s; 249 return sem_close(*fakeptr); 250 #else 251 # error "Unsupported OS" 252 #endif 253 } 254 255 static int my_sem_wait(sem_t* s) 256 { 257 #if defined(VGO_linux) 258 return sem_wait(s); 259 #elif defined(VGO_darwin) 260 return sem_wait( *(sem_t**)s ); 261 #else 262 # error "Unsupported OS" 263 #endif 264 } 265 266 static int my_sem_post(sem_t* s) 267 { 268 #if defined(VGO_linux) 269 return sem_post(s); 270 #elif defined(VGO_darwin) 271 return sem_post( *(sem_t**)s ); 272 #else 273 # error "Unsupported OS" 274 #endif 275 } 276