#include #include #include #include #include #include /* This is really a test of semaphore handling (sem_{init,destroy,post,wait}). Using semaphores a barrier function is created. Helgrind-3.3 (p.k.a Thrcheck) does understand the barrier semantics implied by the barrier, as pieced together from happens-before relationships obtained from the component semaphores. However, it does falsely report one race. Ah well. Helgrind-3.4 is pure h-b and so reports no races (yay!). */ /* This code is derived from gcc-4.3-20071012/libgomp/config/posix/bar.c, which is Copyright (C) 2005 Free Software Foundation, Inc. Contributed by Richard Henderson . and available under version 2.1 or later of the GNU Lesser General Public License. Relative to the libgomp sources, the gomp_barrier_t type here has an extra semaphore field, xxx. This is not functionally useful, but it is used to create enough extra inter-thread dependencies that the barrier-like behaviour of gomp_barrier_t is evident to Thrcheck. There is no other purpose for the .xxx field. */ static sem_t* my_sem_init(char*, int, unsigned); static int my_sem_destroy(sem_t*); static int my_sem_wait(sem_t*); static int my_sem_post(sem_t*); typedef struct { pthread_mutex_t mutex1; pthread_mutex_t mutex2; sem_t* sem1; sem_t* sem2; unsigned total; unsigned arrived; sem_t* xxx; } gomp_barrier_t; typedef long bool; void gomp_barrier_init (gomp_barrier_t *bar, unsigned count) { pthread_mutex_init (&bar->mutex1, NULL); pthread_mutex_init (&bar->mutex2, NULL); bar->sem1 = my_sem_init ("sem1", 0, 0); bar->sem2 = my_sem_init ("sem2", 0, 0); bar->xxx = my_sem_init ("xxx", 0, 0); bar->total = count; bar->arrived = 0; } void gomp_barrier_destroy (gomp_barrier_t *bar) { /* Before destroying, make sure all threads have left the barrier. */ pthread_mutex_lock (&bar->mutex1); pthread_mutex_unlock (&bar->mutex1); pthread_mutex_destroy (&bar->mutex1); pthread_mutex_destroy (&bar->mutex2); my_sem_destroy(bar->sem1); my_sem_destroy(bar->sem2); my_sem_destroy(bar->xxx); } void gomp_barrier_reinit (gomp_barrier_t *bar, unsigned count) { pthread_mutex_lock (&bar->mutex1); bar->total = count; pthread_mutex_unlock (&bar->mutex1); } void gomp_barrier_wait (gomp_barrier_t *bar) { unsigned int n; pthread_mutex_lock (&bar->mutex1); ++bar->arrived; if (bar->arrived == bar->total) { bar->arrived--; n = bar->arrived; if (n > 0) { { unsigned int i; for (i = 0; i < n; i++) my_sem_wait(bar->xxx); // acquire an obvious dependency from // all other threads arriving at the barrier } // 1 up n times, 2 down once // now let all the other threads past the barrier, giving them // an obvious dependency with this thread. do my_sem_post (bar->sem1); // 1 up while (--n != 0); // and wait till the last thread has left my_sem_wait (bar->sem2); // 2 down } pthread_mutex_unlock (&bar->mutex1); /* "Resultats professionnels!" First we made this thread have an obvious (Thrcheck-visible) dependency on all other threads calling gomp_barrier_wait. Then, we released them all again, so they all have a (visible) dependency on this thread. Transitively, the result is that all threads leaving the barrier have a a Thrcheck-visible dependency on all threads arriving at the barrier. As required. */ } else { pthread_mutex_unlock (&bar->mutex1); my_sem_post(bar->xxx); // first N-1 threads wind up waiting here my_sem_wait (bar->sem1); // 1 down pthread_mutex_lock (&bar->mutex2); n = --bar->arrived; /* XXX see below */ pthread_mutex_unlock (&bar->mutex2); if (n == 0) my_sem_post (bar->sem2); // 2 up } } /* re XXX, thrcheck reports a race at this point. It doesn't understand that bar->arrived is protected by mutex1 whilst threads are arriving at the barrier and by mutex2 whilst they are leaving, but not consistently by either of them. Oh well. */ static gomp_barrier_t bar; /* What's with the volatile here? It stops gcc compiling "if (myid == 4) { unprotected = 99; }" and "if (myid == 3) { unprotected = 88; }" into a conditional load followed by a store. The cmov/store sequence reads and writes memory in all threads and cause Thrcheck to (correctly) report a race, the underlying cause of which is that gcc is generating non threadsafe code. (The lack of) thread safe code generation by gcc is currently a hot topic. See the following discussions: http://gcc.gnu.org/ml/gcc/2007-10/msg00266.html http://lkml.org/lkml/2007/10/24/673 and this is interesting background: www.hpl.hp.com/techreports/2004/HPL-2004-209.pdf */ static volatile long unprotected = 0; void* child ( void* argV ) { long myid = (long)argV; // assert(myid >= 2 && myid <= 5); /* First, we all wait to get to this point. */ gomp_barrier_wait( &bar ); /* Now, thread #4 writes to 'unprotected' and so becomes its owner. */ if (myid == 4) { unprotected = 99; } /* Now we all wait again. */ gomp_barrier_wait( &bar ); /* This time, thread #3 writes to 'unprotected'. If all goes well, Thrcheck sees the dependency through the barrier back to thread #4 before it, and so thread #3 becomes the exclusive owner of 'unprotected'. */ if (myid == 3) { unprotected = 88; } /* And just to be on the safe side ... */ gomp_barrier_wait( &bar ); return NULL; } int main (int argc, char *argv[]) { long i; int res; pthread_t thr[4]; fprintf(stderr, "starting\n"); gomp_barrier_init( &bar, 4 ); for (i = 0; i < 4; i++) { res = pthread_create( &thr[i], NULL, child, (void*)(i+2) ); assert(!res); } for (i = 0; i < 4; i++) { res = pthread_join( thr[i], NULL ); assert(!res); } gomp_barrier_destroy( &bar ); /* And finally here, the root thread can get exclusive ownership back from thread #4, because #4 has exited by this point and so we have a dependency edge back to the write it did. */ fprintf(stderr, "done, result is %ld, should be 88\n", unprotected); return 0; } static sem_t* my_sem_init (char* identity, int pshared, unsigned count) { sem_t* s; #if defined(VGO_linux) || defined(VGO_solaris) s = malloc(sizeof(*s)); if (s) { if (sem_init(s, pshared, count) < 0) { perror("sem_init"); free(s); s = NULL; } } #elif defined(VGO_darwin) char name[100]; sprintf(name, "anonsem_%s_pid%d", identity, (int)getpid()); name[ sizeof(name)-1 ] = 0; if (0) printf("name = %s\n", name); s = sem_open(name, O_CREAT | O_EXCL, 0600, count); if (s == SEM_FAILED) { perror("sem_open"); s = NULL; } #else # error "Unsupported OS" #endif return s; } static int my_sem_destroy ( sem_t* s ) { return sem_destroy(s); } static int my_sem_wait(sem_t* s) { return sem_wait(s); } static int my_sem_post(sem_t* s) { return sem_post(s); }