mirror of
https://github.com/ioacademy-jikim/debugging
synced 2025-06-08 08:26:14 +00:00
265 lines
7.2 KiB
C
265 lines
7.2 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <unistd.h>
|
|
/* 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 <rth@redhat.com>.
|
|
|
|
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);
|
|
}
|