#define TRACE_PTH_FNS 0
#define TRACE_QT4_FNS 0
+#define TRACE_GNAT_FNS 0
/*----------------------------------------------------------------*/
done. No way the joiner can return before the thread is gone.
*/
+//-----------------------------------------------------------
+// Ada gcc gnat runtime:
+// The gnat gcc Ada runtime does not use pthread_join. Instead, it uses
+// a combination of other pthread primitives to ensure a child thread
+// is gone. This combination is somewhat functionally equivalent to a
+// pthread_join.
+// We wrap two hook procedures called by the gnat gcc Ada runtime
+// that allows helgrind to understand the semantic of Ada task dependencies
+// and termination.
+
+// System.Tasking.Debug.Master_Hook is called by a task Dependent to
+// indicate that its master is identified by master+master_level.
+void I_WRAP_SONAME_FNNAME_ZU
+ (Za,
+ system__tasking__debug__master_hook)
+ (void *dependent, void *master, int master_level);
+void I_WRAP_SONAME_FNNAME_ZU
+ (Za,
+ system__tasking__debug__master_hook)
+ (void *dependent, void *master, int master_level)
+{
+ OrigFn fn;
+ VALGRIND_GET_ORIG_FN(fn);
+ if (TRACE_GNAT_FNS) {
+ fprintf(stderr, "<< GNAT master_hook wrapper "
+ "dependent %p master %p master_level %d\n",
+ dependent, master, master_level); fflush(stderr);
+ }
+
+ // We call the wrapped function, even if it is a null body.
+ CALL_FN_v_WWW(fn, dependent, master, master_level);
+
+ DO_CREQ_v_WWW(_VG_USERREQ__HG_GNAT_MASTER_HOOK,
+ void*,dependent, void*,master,
+ Word, (Word)master_level);
+
+ if (TRACE_GNAT_FNS) {
+ fprintf(stderr, " :: GNAT master_hook >>\n");
+ }
+}
+
+// System.Tasking.Debug.Master_Completed_Hook is called by a task to
+// indicate that it has completed a master.
+// This indicates that all its Dependent tasks (that identified themselves
+// with the Master_Hook call) are terminated. Helgrind can consider
+// at this point that the equivalent of a 'pthread_join' has been done
+// between self_id and all dependent tasks at master_level.
+void I_WRAP_SONAME_FNNAME_ZU
+ (Za,
+ system__tasking__debug__master_completed_hook)
+ (void *self_id, int master_level);
+void I_WRAP_SONAME_FNNAME_ZU
+ (Za,
+ system__tasking__debug__master_completed_hook)
+ (void *self_id, int master_level)
+{
+ OrigFn fn;
+ VALGRIND_GET_ORIG_FN(fn);
+ if (TRACE_GNAT_FNS) {
+ fprintf(stderr, "<< GNAT master_completed_hook wrapper "
+ "self_id %p master_level %d\n",
+ self_id, master_level); fflush(stderr);
+ }
+
+ // We call the wrapped function, even if it is a null body.
+ CALL_FN_v_WW(fn, self_id, master_level);
+
+ DO_CREQ_v_WW(_VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK,
+ void*,self_id, Word,(Word)master_level);
+
+ if (TRACE_GNAT_FNS) {
+ fprintf(stderr, " :: GNAT master_completed_hook >>\n");
+ }
+}
/*----------------------------------------------------------------*/
/*--- pthread_mutex_t functions ---*/
}
}
+/* generate a dependence from the hbthr_q quitter to the hbthr_s stayer. */
+static
+void generate_quitter_stayer_dependence (Thr* hbthr_q, Thr* hbthr_s)
+{
+ SO* so;
+ /* Allocate a temporary synchronisation object and use it to send
+ an imaginary message from the quitter to the stayer, the purpose
+ being to generate a dependence from the quitter to the
+ stayer. */
+ so = libhb_so_alloc();
+ tl_assert(so);
+ /* Send last arg of _so_send as False, since the sending thread
+ doesn't actually exist any more, so we don't want _so_send to
+ try taking stack snapshots of it. */
+ libhb_so_send(hbthr_q, so, True/*strong_send*//*?!? wrt comment above*/);
+ libhb_so_recv(hbthr_s, so, True/*strong_recv*/);
+ libhb_so_dealloc(so);
+
+ /* Tell libhb that the quitter has been reaped. Note that we might
+ have to be cleverer about this, to exclude 2nd and subsequent
+ notifications for the same hbthr_q, in the case where the app is
+ buggy (calls pthread_join twice or more on the same thread) AND
+ where libpthread is also buggy and doesn't return ESRCH on
+ subsequent calls. (If libpthread isn't thusly buggy, then the
+ wrapper for pthread_join in hg_intercepts.c will stop us getting
+ notified here multiple times for the same joinee.) See also
+ comments in helgrind/tests/jointwice.c. */
+ libhb_joinedwith_done(hbthr_q);
+}
+
static
void evh__HG_PTHREAD_JOIN_POST ( ThreadId stay_tid, Thread* quit_thr )
Thread* thr_q;
Thr* hbthr_s;
Thr* hbthr_q;
- SO* so;
if (SHOW_EVENTS >= 1)
VG_(printf)("evh__post_thread_join(stayer=%d, quitter=%p)\n",
tl_assert( libhb_get_Thr_hgthread(hbthr_s) == thr_s );
tl_assert( libhb_get_Thr_hgthread(hbthr_q) == thr_q );
- /* Allocate a temporary synchronisation object and use it to send
- an imaginary message from the quitter to the stayer, the purpose
- being to generate a dependence from the quitter to the
- stayer. */
- so = libhb_so_alloc();
- tl_assert(so);
- /* Send last arg of _so_send as False, since the sending thread
- doesn't actually exist any more, so we don't want _so_send to
- try taking stack snapshots of it. */
- libhb_so_send(hbthr_q, so, True/*strong_send*//*?!? wrt comment above*/);
- libhb_so_recv(hbthr_s, so, True/*strong_recv*/);
- libhb_so_dealloc(so);
-
- /* Tell libhb that the quitter has been reaped. Note that we might
- have to be cleverer about this, to exclude 2nd and subsequent
- notifications for the same hbthr_q, in the case where the app is
- buggy (calls pthread_join twice or more on the same thread) AND
- where libpthread is also buggy and doesn't return ESRCH on
- subsequent calls. (If libpthread isn't thusly buggy, then the
- wrapper for pthread_join in hg_intercepts.c will stop us getting
- notified here multiple times for the same joinee.) See also
- comments in helgrind/tests/jointwice.c. */
- libhb_joinedwith_done(hbthr_q);
+ generate_quitter_stayer_dependence (hbthr_q, hbthr_s);
/* evh__pre_thread_ll_exit issues an error message if the exiting
thread holds any locks. No need to check here. */
}
}
+/* A list of Ada dependent tasks and their masters. Used for implementing
+ the Ada task termination semantic as implemented by the
+ gcc gnat Ada runtime. */
+typedef
+ struct {
+ void* dependent; // Ada Task Control Block of the Dependent
+ void* master; // ATCB of the master
+ Word master_level; // level of dependency between master and dependent
+ Thread* hg_dependent; // helgrind Thread* for dependent task.
+ }
+ GNAT_dmml;
+static XArray* gnat_dmmls; /* of GNAT_dmml */
+static void gnat_dmmls_INIT (void)
+{
+ if (UNLIKELY(gnat_dmmls == NULL)) {
+ gnat_dmmls = VG_(newXA) (HG_(zalloc), "hg.gnat_md.1",
+ HG_(free),
+ sizeof(GNAT_dmml) );
+ }
+}
static void print_monitor_help ( void )
{
VG_(gdb_printf)
break;
}
+ /* This thread (tid) is informing us of its master. */
+ case _VG_USERREQ__HG_GNAT_MASTER_HOOK: {
+ GNAT_dmml dmml;
+ dmml.dependent = (void*)args[1];
+ dmml.master = (void*)args[2];
+ dmml.master_level = (Word)args[3];
+ dmml.hg_dependent = map_threads_maybe_lookup( tid );
+ tl_assert(dmml.hg_dependent);
+
+ if (0)
+ VG_(printf)("HG_GNAT_MASTER_HOOK (tid %d): "
+ "dependent = %p master = %p master_level = %ld"
+ " dependent Thread* = %p\n",
+ (Int)tid, dmml.dependent, dmml.master, dmml.master_level,
+ dmml.hg_dependent);
+ gnat_dmmls_INIT();
+ VG_(addToXA) (gnat_dmmls, &dmml);
+ break;
+ }
+
+ /* This thread (tid) is informing us that it has completed a
+ master. */
+ case _VG_USERREQ__HG_GNAT_MASTER_COMPLETED_HOOK: {
+ Word n;
+ const Thread *stayer = map_threads_maybe_lookup( tid );
+ const void *master = (void*)args[1];
+ const Word master_level = (Word) args[2];
+ tl_assert(stayer);
+
+ if (0)
+ VG_(printf)("HG_GNAT_MASTER_COMPLETED_HOOK (tid %d): "
+ "self_id = %p master_level = %ld Thread* = %p\n",
+ (Int)tid, master, master_level, stayer);
+
+ gnat_dmmls_INIT();
+ /* Reverse loop on the array, simulating a pthread_join for
+ the Dependent tasks of the completed master, and removing
+ them from the array. */
+ for (n = VG_(sizeXA) (gnat_dmmls) - 1; n >= 0; n--) {
+ GNAT_dmml *dmml = (GNAT_dmml*) VG_(indexXA)(gnat_dmmls, n);
+ if (dmml->master == master
+ && dmml->master_level == master_level) {
+ if (0)
+ VG_(printf)("quitter %p dependency to stayer %p\n",
+ dmml->hg_dependent->hbthr, stayer->hbthr);
+ tl_assert(dmml->hg_dependent->hbthr != stayer->hbthr);
+ generate_quitter_stayer_dependence (dmml->hg_dependent->hbthr,
+ stayer->hbthr);
+ VG_(removeIndexXA) (gnat_dmmls, n);
+ }
+ }
+ break;
+ }
+
/* EXPOSITION only: by intercepting lock init events we can show
the user where the lock was initialised, rather than only
being able to show where it was first locked. Intercepting