From: Julian Seward Date: Fri, 14 Aug 2009 11:08:24 +0000 (+0000) Subject: Skeletal support for TSan-compatible annotations. X-Git-Tag: svn/VALGRIND_3_5_0~40 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9d0325a75809a3bd5a2cb57aa145c71ea1405f2a;p=thirdparty%2Fvalgrind.git Skeletal support for TSan-compatible annotations. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@10810 --- diff --git a/helgrind/helgrind.h b/helgrind/helgrind.h index f42d5c45fa..317695ba6b 100644 --- a/helgrind/helgrind.h +++ b/helgrind/helgrind.h @@ -60,6 +60,10 @@ #include "valgrind.h" +/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! + This enum comprises an ABI exported by Valgrind to programs + which use client requests. DO NOT CHANGE THE ORDER OF THESE + ENTRIES, NOR DELETE ANY -- add new ones at the end. */ typedef enum { VG_USERREQ__HG_CLEAN_MEMORY = VG_USERREQ_TOOL_BASE('H','G'), @@ -100,20 +104,389 @@ typedef _VG_USERREQ__HG_PTHREAD_SPIN_INIT_OR_UNLOCK_POST, /* pth_slk_t* */ _VG_USERREQ__HG_PTHREAD_SPIN_LOCK_PRE, /* pth_slk_t* */ _VG_USERREQ__HG_PTHREAD_SPIN_LOCK_POST, /* pth_slk_t* */ - _VG_USERREQ__HG_PTHREAD_SPIN_DESTROY_PRE /* pth_slk_t* */ + _VG_USERREQ__HG_PTHREAD_SPIN_DESTROY_PRE, /* pth_slk_t* */ + _VG_USERREQ__HG_CLIENTREQ_UNIMP, /* char* */ + _VG_USERREQ__HG_USERSO_SEND_PRE, /* arbitrary UWord SO-tag */ + _VG_USERREQ__HG_USERSO_RECV_POST, /* arbitrary UWord SO-tag */ + _VG_USERREQ__HG_RESERVED1, /* Do not use */ + _VG_USERREQ__HG_RESERVED2 /* Do not use */ } Vg_TCheckClientRequest; + +/*----------------------------------------------------------------*/ +/*--- An implementation-only request -- not for end user use ---*/ +/*----------------------------------------------------------------*/ + +#define _HG_CLIENTREQ_UNIMP(_qzz_str) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + _VG_USERREQ__HG_CLIENTREQ_UNIMP, \ + _qzz_str, 0, 0, 0, 0); \ + (void)0; \ + } while(0) + + +/*----------------------------------------------------------------*/ +/*--- Misc requests ---*/ +/*----------------------------------------------------------------*/ + /* Clean memory state. This makes Helgrind forget everything it knew - about the specified memory range, and resets it to New. This is - particularly useful for memory allocators that wish to recycle - memory. */ -#define VALGRIND_HG_CLEAN_MEMORY(_qzz_start, _qzz_len) \ - do { \ - unsigned long _qzz_res; \ - VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, VG_USERREQ__HG_CLEAN_MEMORY, \ - _qzz_start, _qzz_len, 0, 0, 0); \ - (void)0; \ + about the specified memory range. Effectively this announces that + the specified memory range now "belongs" to the calling thread, so + that: (1) the calling thread can access it safely without + synchronisation, and (2) all other threads must sync with this one + to access it safely. This is particularly useful for memory + allocators that wish to recycle memory. */ +#define VALGRIND_HG_CLEAN_MEMORY(_qzz_start, _qzz_len) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST( \ + (_qzz_res), 0, VG_USERREQ__HG_CLEAN_MEMORY, \ + (_qzz_start), (_qzz_len), 0, 0, 0 \ + ); \ + (void)0; \ + } while(0) + + +/*----------------------------------------------------------------*/ +/*--- ThreadSanitizer-compatible requests ---*/ +/*----------------------------------------------------------------*/ + +/* A quite-broad set of annotations, as used in the ThreadSanitizer + project. This implementation aims to be a (source-level) + compatible implementation of the macros defined in: + + http://code.google.com/p/google-perftools/source \ + /browse/trunk/src/base/dynamic_annotations.h + + (some of the comments below are taken from the above file) + + The implementation here is very incomplete, and intended as a + starting point. Many of the macros are unimplemented. Rather than + allowing unimplemented macros to silently do nothing, they cause an + assertion. Intention is to implement them on demand. + + The major use of these macros is to make visible to race detectors, + the behaviour (effects) of user-implemented synchronisation + primitives, that the detectors could not otherwise deduce from the + normal observation of pthread etc calls. + + Some of the macros are no-ops in Helgrind. That's because Helgrind + is a pure happens-before detector, whereas ThreadSanitizer uses a + hybrid lockset and happens-before scheme, which requires more + accurate annotations for correct operation. + + The macros are listed in the same order as in dynamic_annotations.h + (URL just above). + + I should point out that I am less than clear about the intended + semantics of quite a number of them. Comments and clarifications + welcomed! +*/ + +/* ---------------------------------------------------------------- + These four allow description of user-level condition variables, + apparently in the style of POSIX's pthread_cond_t. Currently + unimplemented and will assert. + ---------------------------------------------------------------- +*/ +/* Report that wait on the condition variable at address CV has + succeeded and the lock at address LOCK is now held. CV and LOCK + are completely arbitrary memory addresses which presumably mean + something to the application, but are meaningless to Helgrind. */ +#define ANNOTATE_CONDVAR_LOCK_WAIT(cv, lock) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_LOCK_WAIT") + +/* Report that wait on the condition variable at CV has succeeded. + Variant w/o lock. */ +#define ANNOTATE_CONDVAR_WAIT(cv) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_WAIT") + +/* Report that we are about to signal on the condition variable at + address CV. */ +#define ANNOTATE_CONDVAR_SIGNAL(cv) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_SIGNAL") + +/* Report that we are about to signal_all on the condition variable at + CV. */ +#define ANNOTATE_CONDVAR_SIGNAL_ALL(cv) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_CONDVAR_SIGNAL_ALL") + + +/* ---------------------------------------------------------------- + Create completely arbitrary happens-before edges between threads. + If thread T1 does ANNOTATE_HAPPENS_BEFORE(obj) and later (w.r.t. + some notional global clock for the computation) thread T2 does + ANNOTATE_HAPPENS_AFTER(obj), then Helgrind will regard all memory + accesses done by T1 before the ..BEFORE.. call as happening-before + all memory accesses done by T2 after the ..AFTER.. call. Hence + Helgrind won't complain about races if T2's accesses afterwards are + to the same locations as T1's accesses before. + + OBJ is a machine word (unsigned long, or void*), is completely + arbitrary, and denotes the identity of some synchronisation object + you're modelling. + + You must do the _BEFORE call just before the real sync event on the + signaller's side, and _AFTER just after the real sync event on the + waiter's side. + + If none of the rest of these macros make sense to you, at least + take the time to understand these two. They form the very essence + of describing arbitrary inter-thread synchronisation events to + Helgrind. You can get a long way just with them alone. + ---------------------------------------------------------------- +*/ +#define ANNOTATE_HAPPENS_BEFORE(obj) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + _VG_USERREQ__HG_USERSO_SEND_PRE, \ + obj, 0, 0, 0, 0); \ + (void)0; \ + } while (0) + +#define ANNOTATE_HAPPENS_AFTER(obj) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \ + _VG_USERREQ__HG_USERSO_RECV_POST, \ + obj, 0, 0, 0, 0); \ + (void)0; \ + } while (0) + + +/* ---------------------------------------------------------------- + Memory publishing. The TSan sources say: + + Report that the bytes in the range [pointer, pointer+size) are about + to be published safely. The race checker will create a happens-before + arc from the call ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) to + subsequent accesses to this memory. + + I'm not sure I understand what this means exactly, nor whether it + is relevant for a pure h-b detector. Leaving unimplemented for + now. + ---------------------------------------------------------------- +*/ +#define ANNOTATE_PUBLISH_MEMORY_RANGE(pointer, size) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_PUBLISH_MEMORY_RANGE") + + +/* ---------------------------------------------------------------- + TSan sources say: + + Instruct the tool to create a happens-before arc between + MU->Unlock() and MU->Lock(). This annotation may slow down the + race detector; normally it is used only when it would be + difficult to annotate each of the mutex's critical sections + individually using the annotations above. + + If MU is a posix pthread_mutex_t then Helgrind will do this anyway. + In any case, leave as unimp for now. I'm unsure about the intended + behaviour. + ---------------------------------------------------------------- +*/ +#define ANNOTATE_MUTEX_IS_USED_AS_CONDVAR(mu) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_MUTEX_IS_USED_AS_CONDVAR") + + +/* ---------------------------------------------------------------- + TSan sources say: + + Annotations useful when defining memory allocators, or when + memory that was protected in one way starts to be protected in + another. + + Report that a new memory at "address" of size "size" has been + allocated. This might be used when the memory has been retrieved + from a free list and is about to be reused, or when a the locking + discipline for a variable changes. + + AFAICS this is the same as VALGRIND_HG_CLEAN_MEMORY. + ---------------------------------------------------------------- +*/ +#define ANNOTATE_NEW_MEMORY(address, size) \ + VALGRIND_HG_CLEAN_MEMORY((address), (size)) + + +/* ---------------------------------------------------------------- + TSan sources say: + + Annotations useful when defining FIFO queues that transfer data + between threads. + + All unimplemented. Am not claiming to understand this (yet). + ---------------------------------------------------------------- +*/ + +/* Report that the producer-consumer queue object at address PCQ has + been created. The ANNOTATE_PCQ_* annotations should be used only + for FIFO queues. For non-FIFO queues use ANNOTATE_HAPPENS_BEFORE + (for put) and ANNOTATE_HAPPENS_AFTER (for get). */ +#define ANNOTATE_PCQ_CREATE(pcq) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_CREATE") + +/* Report that the queue at address PCQ is about to be destroyed. */ +#define ANNOTATE_PCQ_DESTROY(pcq) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_DESTROY") + +/* Report that we are about to put an element into a FIFO queue at + address PCQ. */ +#define ANNOTATE_PCQ_PUT(pcq) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_PUT") + +/* Report that we've just got an element from a FIFO queue at address + PCQ. */ +#define ANNOTATE_PCQ_GET(pcq) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_PCQ_GET") + + +/* ---------------------------------------------------------------- + Annotations that suppress errors. It is usually better to express + the program's synchronization using the other annotations, but + these can be used when all else fails. + + Currently these are all unimplemented. I can't think of a simple + way to implement them without at least some performance overhead. + ---------------------------------------------------------------- +*/ + +/* Report that we may have a benign race on ADDRESS. Insert at the + point where ADDRESS has been allocated, preferably close to the + point where the race happens. See also ANNOTATE_BENIGN_RACE_STATIC. + + XXX: what's this actually supposed to do? And what's the type of + DESCRIPTION? When does the annotation stop having an effect? +*/ +#define ANNOTATE_BENIGN_RACE(address, description) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_BENIGN_RACE") + + +/* Request the analysis tool to ignore all reads in the current thread + until ANNOTATE_IGNORE_READS_END is called. Useful to ignore + intentional racey reads, while still checking other reads and all + writes. */ +#define ANNOTATE_IGNORE_READS_BEGIN() \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_READS_BEGIN") + +/* Stop ignoring reads. */ +#define ANNOTATE_IGNORE_READS_END() \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_READS_END") + +/* Similar to ANNOTATE_IGNORE_READS_BEGIN, but ignore writes. */ +#define ANNOTATE_IGNORE_WRITES_BEGIN() \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_WRITES_BEGIN") + +/* Stop ignoring writes. */ +#define ANNOTATE_IGNORE_WRITES_END() \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_IGNORE_WRITES_END") + +/* Start ignoring all memory accesses (reads and writes). */ +#define ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN() \ + do { \ + ANNOTATE_IGNORE_READS_BEGIN(); \ + ANNOTATE_IGNORE_WRITES_BEGIN(); \ + } while (0) + +/* Stop ignoring all memory accesses. */ +#define ANNOTATE_IGNORE_READS_AND_WRITES_END() \ + do { \ + ANNOTATE_IGNORE_WRITES_END(); \ + ANNOTATE_IGNORE_READS_END(); \ + } while (0) + + +/* ---------------------------------------------------------------- + Annotations useful for debugging. + + Again, so for unimplemented, partly for performance reasons. + ---------------------------------------------------------------- +*/ + +/* Request to trace every access to ADDRESS. */ +#define ANNOTATE_TRACE_MEMORY(address) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_TRACE_MEMORY") + +/* Report the current thread name to a race detector. */ +#define ANNOTATE_THREAD_NAME(name) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_THREAD_NAME") + + +/* ---------------------------------------------------------------- + Annotations for describing behaviour of user-implemented lock + primitives. In all cases, the LOCK argument is a completely + arbitrary machine word (unsigned long, or void*) and can be any + value which gives a unique identity to the lock objects being + modelled. + + We just pretend they're ordinary posix rwlocks. That'll probably + give some rather confusing wording in error messages, claiming that + the arbitrary LOCK values are pthread_rwlock_t*'s, when in fact + they are not. Ah well. + ---------------------------------------------------------------- +*/ +/* Report that a lock has just been created at address LOCK. */ +#define ANNOTATE_RWLOCK_CREATE(lock) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST( \ + _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_INIT_POST, \ + lock, 0, 0, 0, 0 \ + ); \ + (void)0; \ } while(0) + +/* Report that the lock at address LOCK is about to be destroyed. */ +#define ANNOTATE_RWLOCK_DESTROY(lock) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST( \ + _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_DESTROY_PRE, \ + lock, 0, 0, 0, 0 \ + ); \ + (void)0; \ + } while(0) + +/* Report that the lock at address LOCK has just been acquired. + is_w=1 for writer lock, is_w=0 for reader lock. */ +#define ANNOTATE_RWLOCK_ACQUIRED(lock, is_w) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST( \ + _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_LOCK_POST, \ + lock, is_w ? 1 : 0, 0, 0, 0 \ + ); \ + (void)0; \ + } while(0) + +/* Report that the lock at address LOCK is about to be released. */ + #define ANNOTATE_RWLOCK_RELEASED(lock, is_w) \ + do { \ + unsigned long _qzz_res; \ + VALGRIND_DO_CLIENT_REQUEST( \ + _qzz_res, 0, _VG_USERREQ__HG_PTHREAD_RWLOCK_UNLOCK_PRE, \ + lock, 0, 0, 0, 0 \ + ); \ + (void)0; \ + } while(0) + + +/* ---------------------------------------------------------------- + Annotations useful for testing race detectors. + ---------------------------------------------------------------- +*/ + +/* Report that we expect a race on the variable at ADDRESS. Use only + in unit tests for a race detector. */ +#define ANNOTATE_EXPECT_RACE(address, description) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_EXPECT_RACE") + +/* A no-op. Insert where you like to test the interceptors. */ +#define ANNOTATE_NO_OP(arg) \ + _HG_CLIENTREQ_UNIMP("ANNOTATE_NO_OP") + #endif /* __HELGRIND_H */ diff --git a/helgrind/hg_main.c b/helgrind/hg_main.c index e5ba43884e..ee72e30745 100644 --- a/helgrind/hg_main.c +++ b/helgrind/hg_main.c @@ -2966,6 +2966,107 @@ static void evh__HG_PTHREAD_BARRIER_WAIT_PRE ( ThreadId tid, } +/* ----------------------------------------------------- */ +/* ----- events to do with user-specified HB edges ----- */ +/* ----------------------------------------------------- */ + +/* A mapping from arbitrary UWord tag to the SO associated with it. + The UWord tags are meaningless to us, interpreted only by the + user. */ + + + +/* UWord -> SO* */ +static WordFM* map_usertag_to_SO = NULL; + +static void map_usertag_to_SO_INIT ( void ) { + if (UNLIKELY(map_usertag_to_SO == NULL)) { + map_usertag_to_SO = VG_(newFM)( HG_(zalloc), + "hg.mutS.1", HG_(free), NULL ); + tl_assert(map_usertag_to_SO != NULL); + } +} + +static SO* map_usertag_to_SO_lookup_or_alloc ( UWord usertag ) { + UWord key, val; + map_usertag_to_SO_INIT(); + if (VG_(lookupFM)( map_usertag_to_SO, &key, &val, usertag )) { + tl_assert(key == (UWord)usertag); + return (SO*)val; + } else { + SO* so = libhb_so_alloc(); + VG_(addToFM)( map_usertag_to_SO, usertag, (UWord)so ); + return so; + } +} + +// If it's ever needed (XXX check before use) +//static void map_usertag_to_SO_delete ( UWord usertag ) { +// UWord keyW, valW; +// map_usertag_to_SO_INIT(); +// if (VG_(delFromFM)( map_usertag_to_SO, &keyW, &valW, usertag )) { +// SO* so = (SO*)valW; +// tl_assert(keyW == usertag); +// tl_assert(so); +// libhb_so_dealloc(so); +// } +//} + + +static +void evh__HG_USERSO_SEND_PRE ( ThreadId tid, UWord usertag ) +{ + /* TID is just about to notionally sent a message on a notional + abstract synchronisation object whose identity is given by + USERTAG. Bind USERTAG to a real SO if it is not already so + bound, and do a 'strong send' on the SO. This is later used by + other thread(s) which successfully 'receive' from the SO, + thereby acquiring a dependency on this signalling event. */ + Thread* thr; + SO* so; + + if (SHOW_EVENTS >= 1) + VG_(printf)("evh__HG_USERSO_SEND_PRE(ctid=%d, usertag=%#lx)\n", + (Int)tid, usertag ); + + thr = map_threads_maybe_lookup( tid ); + tl_assert(thr); /* cannot fail - Thread* must already exist */ + + so = map_usertag_to_SO_lookup_or_alloc( usertag ); + tl_assert(so); + + libhb_so_send( thr->hbthr, so, True/*strong_send*/ ); +} + +static +void evh__HG_USERSO_RECV_POST ( ThreadId tid, UWord usertag ) +{ + /* TID has just notionally received a message from a notional + abstract synchronisation object whose identity is given by + USERTAG. Bind USERTAG to a real SO if it is not already so + bound. If the SO has at some point in the past been 'sent' on, + to a 'strong receive' on it, thereby acquiring a dependency on + the sender. */ + Thread* thr; + SO* so; + + if (SHOW_EVENTS >= 1) + VG_(printf)("evh__HG_USERSO_RECV_POST(ctid=%d, usertag=%#lx)\n", + (Int)tid, usertag ); + + thr = map_threads_maybe_lookup( tid ); + tl_assert(thr); /* cannot fail - Thread* must already exist */ + + so = map_usertag_to_SO_lookup_or_alloc( usertag ); + tl_assert(so); + + /* Acquire a dependency on it. If the SO has never so far been + sent on, then libhb_so_recv will do nothing. So we're safe + regardless of SO's history. */ + libhb_so_recv( thr->hbthr, so, True/*strong_recv*/ ); +} + + /*--------------------------------------------------------------*/ /*--- Lock acquisition order monitoring ---*/ /*--------------------------------------------------------------*/ @@ -4242,6 +4343,30 @@ Bool hg_handle_client_request ( ThreadId tid, UWord* args, UWord* ret) evh__HG_PTHREAD_SPIN_DESTROY_PRE( tid, (void*)args[1] ); break; + case _VG_USERREQ__HG_CLIENTREQ_UNIMP: { + /* char* who */ + HChar* who = (HChar*)args[1]; + HChar buf[50 + 50]; + Thread* thr = map_threads_maybe_lookup( tid ); + tl_assert( thr ); /* I must be mapped */ + tl_assert( who ); + tl_assert( VG_(strlen)(who) <= 50 ); + VG_(sprintf)(buf, "Unimplemented client request macro \"%s\"", who ); + /* record_error_Misc strdup's buf, so this is safe: */ + HG_(record_error_Misc)( thr, buf ); + break; + } + + case _VG_USERREQ__HG_USERSO_SEND_PRE: + /* UWord arbitrary-SO-tag */ + evh__HG_USERSO_SEND_PRE( tid, args[1] ); + break; + + case _VG_USERREQ__HG_USERSO_RECV_POST: + /* UWord arbitrary-SO-tag */ + evh__HG_USERSO_RECV_POST( tid, args[1] ); + break; + default: /* Unhandled Helgrind client request! */ tl_assert2(0, "unhandled Helgrind client request 0x%lx",