[project @ 2002-04-10 11:43:43 by stolz]
[ghc-hetmet.git] / ghc / rts / Schedule.c
index fef2795..edc13b0 100644 (file)
@@ -1,5 +1,5 @@
 /* ---------------------------------------------------------------------------
- * $Id: Schedule.c,v 1.123 2002/02/14 07:52:05 sof Exp $
+ * $Id: Schedule.c,v 1.136 2002/04/10 11:43:45 stolz Exp $
  *
  * (c) The GHC Team, 1998-2000
  *
 #include "OSThreads.h"
 #include  "Task.h"
 
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
 #include <stdarg.h>
 
 //@node Variables and Data structures, Prototypes, Includes, Main scheduling code
 //@subsection Variables and Data structures
 
-/* Main threads:
- *
- * These are the threads which clients have requested that we run.  
- *
- * In a 'threaded' build, we might have several concurrent clients all
- * waiting for results, and each one will wait on a condition variable
- * until the result is available.
- *
- * In non-SMP, clients are strictly nested: the first client calls
- * into the RTS, which might call out again to C with a _ccall_GC, and
- * eventually re-enter the RTS.
- *
- * Main threads information is kept in a linked list:
- */
-//@cindex StgMainThread
-typedef struct StgMainThread_ {
-  StgTSO *         tso;
-  SchedulerStatus  stat;
-  StgClosure **    ret;
-#if defined(RTS_SUPPORTS_THREADS)
-  Condition        wakeup;
-#endif
-  struct StgMainThread_ *link;
-} StgMainThread;
-
 /* Main thread queue.
  * Locks required: sched_mutex.
  */
-static StgMainThread *main_threads;
+StgMainThread *main_threads;
 
 /* Thread queues.
  * Locks required: sched_mutex.
@@ -266,38 +248,6 @@ static void sched_belch(char *s, ...);
 Mutex     sched_mutex       = INIT_MUTEX_VAR;
 Mutex     term_mutex        = INIT_MUTEX_VAR;
 
-
-
-
-/* thread_ready_cond: when signalled, a thread has become runnable for a
- * task to execute.
- *
- * In the non-SMP case, it also implies that the thread that is woken up has
- * exclusive access to the RTS and all its data structures (that are not
- * under sched_mutex's control).
- *
- * thread_ready_cond is signalled whenever COND_NO_THREADS_READY doesn't hold.
- *
- */
-Condition thread_ready_cond = INIT_COND_VAR;
-#if 0
-/* For documentation purposes only */
-#define COND_NO_THREADS_READY() (noCapabilities() || EMPTY_RUN_QUEUE())
-#endif
-
-/*
- * To be able to make an informed decision about whether or not 
- * to create a new task when making an external call, keep track of
- * the number of tasks currently blocked waiting on thread_ready_cond.
- * (if > 0 => no need for a new task, just unblock an existing one).
- *
- * waitForWork() takes care of keeping it up-to-date; Task.startTask()
- * uses its current value.
- */
-nat rts_n_waiting_tasks = 0;
-
-static void waitForWork(void);
-
 # if defined(SMP)
 static Condition gc_pending_cond = INIT_COND_VAR;
 nat await_death;
@@ -410,36 +360,19 @@ schedule( void )
 # endif
 #endif
   rtsBool was_interrupted = rtsFalse;
-
-#if defined(RTS_SUPPORTS_THREADS)
-schedule_start:
-#endif
   
-#if defined(RTS_SUPPORTS_THREADS)
   ACQUIRE_LOCK(&sched_mutex);
-#endif
  
 #if defined(RTS_SUPPORTS_THREADS)
-  /* ToDo: consider SMP support */
-  if ( rts_n_waiting_workers > 0 && noCapabilities() ) {
-    /* (At least) one native thread is waiting to
-     * deposit the result of an external call. So,
-     * be nice and hand over our capability.
-     */
-    yieldCapability(cap);
-    /* Lost our sched_mutex lock, try to re-enter the scheduler. */
-    goto schedule_start;
-  }
-#endif
+  /* Check to see whether there are any worker threads
+     waiting to deposit external call results. If so,
+     yield our capability */
+  yieldToReturningWorker(&sched_mutex, cap);
 
-#if defined(RTS_SUPPORTS_THREADS)
-  while ( noCapabilities() ) {
-    waitForWork();
-  }
+  waitForWorkCapability(&sched_mutex, &cap, rtsFalse);
 #endif
 
 #if defined(GRAN)
-
   /* set up first event to get things going */
   /* ToDo: assign costs for system setup and init MainTSO ! */
   new_event(CurrentProc, CurrentProc, CurrentTime[CurrentProc],
@@ -503,6 +436,9 @@ schedule_start:
          *prev = m->link;
          m->stat = Success;
          broadcastCondition(&m->wakeup);
+#ifdef DEBUG
+         free(m->tso->label);
+#endif
          break;
        case ThreadKilled:
          if (m->ret) *(m->ret) = NULL;
@@ -513,6 +449,9 @@ schedule_start:
            m->stat = Killed;
          }
          broadcastCondition(&m->wakeup);
+#ifdef DEBUG
+         free(m->tso->label);
+#endif
          break;
        default:
          break;
@@ -532,6 +471,9 @@ schedule_start:
       StgMainThread *m = main_threads;
       if (m->tso->what_next == ThreadComplete
          || m->tso->what_next == ThreadKilled) {
+#ifdef DEBUG
+       free(m->tso->label);
+#endif
        main_threads = main_threads->link;
        if (m->tso->what_next == ThreadComplete) {
          /* we finished successfully, fill in the return value */
@@ -628,9 +570,7 @@ schedule_start:
      * inform all the main threads.
      */
 #ifndef PAR
-    if (   EMPTY_RUN_QUEUE()
-       && EMPTY_QUEUE(blocked_queue_hd)
-       && EMPTY_QUEUE(sleeping_queue)
+    if (   EMPTY_THREAD_QUEUES()
 #if defined(RTS_SUPPORTS_THREADS)
        && EMPTY_QUEUE(suspended_ccalling_threads)
 #endif
@@ -644,39 +584,51 @@ schedule_start:
        /* and SMP mode ..? */
        releaseCapability(cap);
 #endif
-       RELEASE_LOCK(&sched_mutex);
+       // Garbage collection can release some new threads due to
+       // either (a) finalizers or (b) threads resurrected because
+       // they are about to be send BlockedOnDeadMVar.  Any threads
+       // thus released will be immediately runnable.
        GarbageCollect(GetRoots,rtsTrue);
-       ACQUIRE_LOCK(&sched_mutex);
-       if (   EMPTY_QUEUE(blocked_queue_hd)
-           && EMPTY_RUN_QUEUE()
-           && EMPTY_QUEUE(sleeping_queue) ) {
-
-           IF_DEBUG(scheduler, sched_belch("still deadlocked, checking for black holes..."));
-           detectBlackHoles();
-
-           /* No black holes, so probably a real deadlock.  Send the
-            * current main thread the Deadlock exception (or in the SMP
-            * build, send *all* main threads the deadlock exception,
-            * since none of them can make progress).
-            */
-           if ( EMPTY_RUN_QUEUE() ) {
-               StgMainThread *m;
+
+       if ( !EMPTY_RUN_QUEUE() ) { goto not_deadlocked; }
+
+       IF_DEBUG(scheduler, 
+                sched_belch("still deadlocked, checking for black holes..."));
+       detectBlackHoles();
+
+       if ( !EMPTY_RUN_QUEUE() ) { goto not_deadlocked; }
+
+#ifndef mingw32_TARGET_OS
+       /* If we have user-installed signal handlers, then wait
+        * for signals to arrive rather then bombing out with a
+        * deadlock.
+        */
+       if ( anyUserHandlers() ) {
+           IF_DEBUG(scheduler, 
+                    sched_belch("still deadlocked, waiting for signals..."));
+
+           awaitUserSignals();
+
+           // we might be interrupted...
+           if (interrupted) { continue; }
+
+           if (signals_pending()) {
+               startSignalHandlers();
+           }
+           ASSERT(!EMPTY_RUN_QUEUE());
+           goto not_deadlocked;
+       }
+#endif
+
+       /* Probably a real deadlock.  Send the current main thread the
+        * Deadlock exception (or in the SMP build, send *all* main
+        * threads the deadlock exception, since none of them can make
+        * progress).
+        */
+       {
+           StgMainThread *m;
 #if defined(RTS_SUPPORTS_THREADS)
-               for (m = main_threads; m != NULL; m = m->link) {
-                   switch (m->tso->why_blocked) {
-                   case BlockedOnBlackHole:
-                       raiseAsync(m->tso, (StgClosure *)NonTermination_closure);
-                       break;
-                   case BlockedOnException:
-                   case BlockedOnMVar:
-                       raiseAsync(m->tso, (StgClosure *)Deadlock_closure);
-                       break;
-                   default:
-                       barf("deadlock: main thread blocked in a strange way");
-                   }
-               }
-#else
-               m = main_threads;
+           for (m = main_threads; m != NULL; m = m->link) {
                switch (m->tso->why_blocked) {
                case BlockedOnBlackHole:
                    raiseAsync(m->tso, (StgClosure *)NonTermination_closure);
@@ -688,19 +640,32 @@ schedule_start:
                default:
                    barf("deadlock: main thread blocked in a strange way");
                }
-#endif
            }
-#if defined(RTS_SUPPORTS_THREADS)
-           /* ToDo: revisit conditions (and mechanism) for shutting
-              down a multi-threaded world  */
-           if ( EMPTY_RUN_QUEUE() ) {
-             IF_DEBUG(scheduler, sched_belch("all done, i think...shutting down."));
-             shutdownHaskellAndExit(0);
+#else
+           m = main_threads;
+           switch (m->tso->why_blocked) {
+           case BlockedOnBlackHole:
+               raiseAsync(m->tso, (StgClosure *)NonTermination_closure);
+               break;
+           case BlockedOnException:
+           case BlockedOnMVar:
+               raiseAsync(m->tso, (StgClosure *)Deadlock_closure);
+               break;
+           default:
+               barf("deadlock: main thread blocked in a strange way");
            }
 #endif
-           ASSERT( !EMPTY_RUN_QUEUE() );
        }
+
+#if defined(RTS_SUPPORTS_THREADS)
+       /* ToDo: revisit conditions (and mechanism) for shutting
+          down a multi-threaded world  */
+       IF_DEBUG(scheduler, sched_belch("all done, i think...shutting down."));
+       shutdownHaskellAndExit(0);
+#endif
     }
+  not_deadlocked:
+
 #elif defined(PAR)
     /* ToDo: add deadlock detection in GUM (similar to SMP) -- HWL */
 #endif
@@ -723,16 +688,19 @@ schedule_start:
     if ( EMPTY_RUN_QUEUE() ) {
       /* Give up our capability */
       releaseCapability(cap);
-      while ( noCapabilities() || EMPTY_RUN_QUEUE() ) {
-       IF_DEBUG(scheduler, sched_belch("thread %d: waiting for work", osThreadId()));
-       waitForWork();
-       IF_DEBUG(scheduler, sched_belch("thread %d: work now available %d %d", osThreadId(), getFreeCapabilities(),EMPTY_RUN_QUEUE()));
+      IF_DEBUG(scheduler, sched_belch("thread %d: waiting for work", osThreadId()));
+      waitForWorkCapability(&sched_mutex, &cap, rtsTrue);
+      IF_DEBUG(scheduler, sched_belch("thread %d: work now available", osThreadId()));
+#if 0
+      while ( EMPTY_RUN_QUEUE() ) {
+       waitForWorkCapability(&sched_mutex, &cap);
+       IF_DEBUG(scheduler, sched_belch("thread %d: work now available", osThreadId()));
       }
+#endif
     }
 #endif
 
 #if defined(GRAN)
-
     if (RtsFlags.GranFlags.Light)
       GranSimLight_enter_system(event, &ActiveTSO); // adjust ActiveTSO etc
 
@@ -978,7 +946,7 @@ schedule_start:
             belch("--=^ %d threads, %d sparks on [%#x]", 
                   run_queue_len(), spark_queue_len(pool), CURRENT_PROC));
 
-#if 1
+# if 1
     if (0 && RtsFlags.ParFlags.ParStats.Full && 
        t && LastTSO && t->id != LastTSO->id && 
        LastTSO->why_blocked == NotBlocked && 
@@ -1003,7 +971,7 @@ schedule_start:
       emitSchedule = rtsFalse;
     }
      
-#endif
+# endif
 #else /* !GRAN && !PAR */
   
     /* grab a thread from the run queue */
@@ -1377,12 +1345,11 @@ schedule_start:
     }
 #endif
 
+    if (ready_to_gc 
 #ifdef SMP
-    if (ready_to_gc && allFreeCapabilities() )
-#else
-    if (ready_to_gc) 
+       && allFreeCapabilities() 
 #endif
-      {
+       ) {
       /* everybody back, start the GC.
        * Could do it in this thread, or signal a condition var
        * to do it in another thread.  Either way, we need to
@@ -1426,6 +1393,46 @@ schedule_start:
 }
 
 /* ---------------------------------------------------------------------------
+ * Singleton fork(). Do not copy any running threads.
+ * ------------------------------------------------------------------------- */
+
+StgInt forkProcess(StgTSO* tso) {
+
+#ifndef mingw32_TARGET_OS
+  pid_t pid;
+  StgTSO* t,*next;
+
+  IF_DEBUG(scheduler,sched_belch("forking!"));
+
+  pid = fork();
+  if (pid) { /* parent */
+
+  /* just return the pid */
+    
+  } else { /* child */
+  /* wipe all other threads */
+  run_queue_hd = tso;
+  tso->link = END_TSO_QUEUE;
+
+  /* DO NOT TOUCH THE QUEUES directly because most of the code around
+     us is picky about finding the threat still in its queue when
+     handling the deleteThread() */
+
+  for (t = all_threads; t != END_TSO_QUEUE; t = next) {
+    next = t->link;
+    if (t->id != tso->id) {
+      deleteThread(t);
+    }
+  }
+  }
+  return pid;
+#else /* mingw32 */
+  barf("forkProcess#: primop not implemented for mingw32, sorry!");
+  return -1;
+#endif /* mingw32 */
+}
+
+/* ---------------------------------------------------------------------------
  * deleteAllThreads():  kill all the live threads.
  *
  * This is used when we catch a user interrupt (^C), before performing
@@ -1436,18 +1443,10 @@ void deleteAllThreads ( void )
 {
   StgTSO* t, *next;
   IF_DEBUG(scheduler,sched_belch("deleting all threads"));
-  for (t = run_queue_hd; t != END_TSO_QUEUE; t = next) {
-      next = t->link;
-      deleteThread(t);
-  }
-  for (t = blocked_queue_hd; t != END_TSO_QUEUE; t = next) {
-      next = t->link;
-      deleteThread(t);
-  }
-  for (t = sleeping_queue; t != END_TSO_QUEUE; t = next) {
-      next = t->link;
+  for (t = all_threads; t != END_TSO_QUEUE; t = next) {
+      next = t->global_link;
       deleteThread(t);
-  }
+  }      
   run_queue_hd = run_queue_tl = END_TSO_QUEUE;
   blocked_queue_hd = blocked_queue_tl = END_TSO_QUEUE;
   sleeping_queue = END_TSO_QUEUE;
@@ -1475,7 +1474,12 @@ void deleteAllThreads ( void )
  * ------------------------------------------------------------------------- */
    
 StgInt
-suspendThread( StgRegTable *reg )
+suspendThread( StgRegTable *reg, 
+              rtsBool concCall
+#if !defined(RTS_SUPPORTS_THREADS)
+              STG_UNUSED
+#endif
+              )
 {
   nat tok;
   Capability *cap;
@@ -1504,7 +1508,7 @@ suspendThread( StgRegTable *reg )
   /* Hand back capability */
   releaseCapability(cap);
   
-#if defined(RTS_SUPPORTS_THREADS) && !defined(SMP)
+#if defined(RTS_SUPPORTS_THREADS)
   /* Preparing to leave the RTS, so ensure there's a native thread/task
      waiting to take over.
      
@@ -1512,24 +1516,36 @@ suspendThread( StgRegTable *reg )
      for one (i.e., if there's only one Concurrent Haskell thread alive,
      there's no need to create a new task).
   */
-  IF_DEBUG(scheduler, sched_belch("worker thread (%d): leaving RTS\n", tok));
-  startTask(taskStart);
+  IF_DEBUG(scheduler, sched_belch("worker thread (%d): leaving RTS", tok));
+  if (concCall) {
+    startTask(taskStart);
+  }
 #endif
 
+  /* Other threads _might_ be available for execution; signal this */
   THREAD_RUNNABLE();
   RELEASE_LOCK(&sched_mutex);
   return tok; 
 }
 
 StgRegTable *
-resumeThread( StgInt tok )
+resumeThread( StgInt tok,
+             rtsBool concCall
+#if !defined(RTS_SUPPORTS_THREADS)
+              STG_UNUSED
+#endif
+             )
 {
   StgTSO *tso, **prev;
   Capability *cap;
 
 #if defined(RTS_SUPPORTS_THREADS)
-  /* Wait for permission to re-enter the RTS with the result.. */
-  grabReturnCapability(&cap);
+  /* Wait for permission to re-enter the RTS with the result. */
+  if ( concCall ) {
+    grabReturnCapability(&sched_mutex, &cap);
+  } else {
+    grabCapability(&cap);
+  }
 #else
   grabCapability(&cap);
 #endif
@@ -1558,18 +1574,6 @@ resumeThread( StgInt tok )
 }
 
 
-#if defined(RTS_SUPPORTS_THREADS)
-static void
-waitForWork()
-{
-  rts_n_waiting_tasks++;
-  waitCondition(&thread_ready_cond, &sched_mutex);
-  rts_n_waiting_tasks--;
-  return;
-}
-#endif
-
-
 /* ---------------------------------------------------------------------------
  * Static functions
  * ------------------------------------------------------------------------ */
@@ -1602,6 +1606,24 @@ int rts_getThreadId(const StgTSO *tso)
   return tso->id;
 }
 
+#ifdef DEBUG
+void labelThread(StgTSO *tso, char *label)
+{
+  int len;
+  void *buf;
+
+  /* Caveat: Once set, you can only set the thread name to "" */
+  len = strlen(label)+1;
+  buf = realloc(tso->label,len);
+  if (buf == NULL) {
+    fprintf(stderr,"insufficient memory for labelThread!\n");
+    free(tso->label);
+  } else
+    strncpy(buf,label,len);
+  tso->label = buf;
+}
+#endif /* DEBUG */
+
 /* ---------------------------------------------------------------------------
    Create a new thread.
 
@@ -1676,6 +1698,10 @@ createThread_(nat size, rtsBool have_lock)
 #endif
   tso->what_next     = ThreadEnterGHC;
 
+#ifdef DEBUG
+  tso->label = NULL;
+#endif
+
   /* tso->id needs to be unique.  For now we use a heavyweight mutex to
    * protect the increment operation on next_thread_id.
    * In future, we could use an atomic increment instead.
@@ -1870,10 +1896,13 @@ activateSpark (rtsSpark spark)
  * on this thread's stack before the scheduler is invoked.
  * ------------------------------------------------------------------------ */
 
+static void scheduleThread_ (StgTSO* tso, rtsBool createTask);
+
 void
 scheduleThread_(StgTSO *tso
-#if defined(THREADED_RTS)
               , rtsBool createTask
+#if !defined(THREADED_RTS)
+                STG_UNUSED
 #endif
              )
 {
@@ -1903,11 +1932,12 @@ scheduleThread_(StgTSO *tso
 
 void scheduleThread(StgTSO* tso)
 {
-#if defined(THREADED_RTS)
+  return scheduleThread_(tso, rtsFalse);
+}
+
+void scheduleExtThread(StgTSO* tso)
+{
   return scheduleThread_(tso, rtsTrue);
-#else
-  return scheduleThread_(tso);
-#endif
 }
 
 /* ---------------------------------------------------------------------------
@@ -2296,8 +2326,6 @@ take_off_run_queue(StgTSO *tso) {
 void
 GetRoots(evac_fn evac)
 {
-  StgMainThread *m;
-
 #if defined(GRAN)
   {
     nat i;
@@ -2336,9 +2364,6 @@ GetRoots(evac_fn evac)
   }
 #endif 
 
-  for (m = main_threads; m != NULL; m = m->link) {
-      evac((StgClosure **)&m->tso);
-  }
   if (suspended_ccalling_threads != END_TSO_QUEUE) {
       evac((StgClosure **)&suspended_ccalling_threads);
   }
@@ -2366,13 +2391,18 @@ void (*extra_roots)(evac_fn);
 void
 performGC(void)
 {
+  /* Obligated to hold this lock upon entry */
+  ACQUIRE_LOCK(&sched_mutex);
   GarbageCollect(GetRoots,rtsFalse);
+  RELEASE_LOCK(&sched_mutex);
 }
 
 void
 performMajorGC(void)
 {
+  ACQUIRE_LOCK(&sched_mutex);
   GarbageCollect(GetRoots,rtsTrue);
+  RELEASE_LOCK(&sched_mutex);
 }
 
 static void
@@ -2385,8 +2415,10 @@ AllRoots(evac_fn evac)
 void
 performGCWithRoots(void (*get_roots)(evac_fn))
 {
+  ACQUIRE_LOCK(&sched_mutex);
   extra_roots = get_roots;
   GarbageCollect(AllRoots,rtsFalse);
+  RELEASE_LOCK(&sched_mutex);
 }
 
 /* -----------------------------------------------------------------------------
@@ -3127,51 +3159,41 @@ raiseAsync(StgTSO *tso, StgClosure *exception)
     StgAP_UPD * ap;
 
     /* If we find a CATCH_FRAME, and we've got an exception to raise,
-     * then build PAP(handler,exception,realworld#), and leave it on
-     * top of the stack ready to enter.
+     * then build the THUNK raise(exception), and leave it on
+     * top of the CATCH_FRAME ready to enter.
      */
     if (get_itbl(su)->type == CATCH_FRAME && exception != NULL) {
+#ifdef PROFILING
       StgCatchFrame *cf = (StgCatchFrame *)su;
+#endif
+      StgClosure *raise;
+
       /* we've got an exception to raise, so let's pass it to the
        * handler in this frame.
        */
-      ap = (StgAP_UPD *)allocate(sizeofW(StgPAP) + 2);
-      TICK_ALLOC_UPD_PAP(3,0);
-      SET_HDR(ap,&stg_PAP_info,cf->header.prof.ccs);
-             
-      ap->n_args = 2;
-      ap->fun = cf->handler;   /* :: Exception -> IO a */
-      ap->payload[0] = exception;
-      ap->payload[1] = ARG_TAG(0); /* realworld token */
-
-      /* throw away the stack from Sp up to and including the
-       * CATCH_FRAME.
-       */
-      sp = (P_)su + sizeofW(StgCatchFrame) - 1; 
-      tso->su = cf->link;
-
-      /* Restore the blocked/unblocked state for asynchronous exceptions
-       * at the CATCH_FRAME.  
-       *
-       * If exceptions were unblocked at the catch, arrange that they
-       * are unblocked again after executing the handler by pushing an
-       * unblockAsyncExceptions_ret stack frame.
+      raise = (StgClosure *)allocate(sizeofW(StgClosure)+1);
+      TICK_ALLOC_SE_THK(1,0);
+      SET_HDR(raise,&stg_raise_info,cf->header.prof.ccs);
+      raise->payload[0] = exception;
+
+      /* throw away the stack from Sp up to the CATCH_FRAME.
        */
-      if (!cf->exceptions_blocked) {
-       *(sp--) = (W_)&stg_unblockAsyncExceptionszh_ret_info;
-      }
-      
-      /* Ensure that async exceptions are blocked when running the handler.
+      sp = (P_)su - 1;
+
+      /* Ensure that async excpetions are blocked now, so we don't get
+       * a surprise exception before we get around to executing the
+       * handler.
        */
       if (tso->blocked_exceptions == NULL) {
-       tso->blocked_exceptions = END_TSO_QUEUE;
+         tso->blocked_exceptions = END_TSO_QUEUE;
       }
-      
-      /* Put the newly-built PAP on top of the stack, ready to execute
+
+      /* Put the newly-built THUNK on top of the stack, ready to execute
        * when the thread restarts.
        */
-      sp[0] = (W_)ap;
+      sp[0] = (W_)raise;
       tso->sp = sp;
+      tso->su = su;
       tso->what_next = ThreadEnterGHC;
       IF_DEBUG(sanity, checkTSO(tso));
       return;
@@ -3492,6 +3514,7 @@ printAllThreads(void)
 
   for (t = all_threads; t != END_TSO_QUEUE; t = t->global_link) {
     fprintf(stderr, "\tthread %d ", t->id);
+    if (t->label) fprintf(stderr,"[\"%s\"] ",t->label);
     printThreadStatus(t);
     fprintf(stderr,"\n");
   }
@@ -3688,7 +3711,6 @@ sched_belch(char *s, ...)
 //@subsection Index
 
 //@index
-//* MainRegTable::  @cindex\s-+MainRegTable
 //* StgMainThread::  @cindex\s-+StgMainThread
 //* awaken_blocked_queue::  @cindex\s-+awaken_blocked_queue
 //* blocked_queue_hd::  @cindex\s-+blocked_queue_hd
@@ -3706,5 +3728,4 @@ sched_belch(char *s, ...)
 //* schedule::  @cindex\s-+schedule
 //* take_off_run_queue::  @cindex\s-+take_off_run_queue
 //* term_mutex::  @cindex\s-+term_mutex
-//* thread_ready_cond::  @cindex\s-+thread_ready_cond
 //@end index