[project @ 2001-11-26 16:54:21 by simonmar]
[ghc-hetmet.git] / ghc / rts / Schedule.c
index b2fb90a..b027115 100644 (file)
@@ -1,5 +1,5 @@
 /* ---------------------------------------------------------------------------
- * $Id: Schedule.c,v 1.102 2001/10/23 11:28:51 simonmar Exp $
+ * $Id: Schedule.c,v 1.108 2001/11/26 16:54:22 simonmar Exp $
  *
  * (c) The GHC Team, 1998-2000
  *
 #include "Stats.h"
 #include "Itimer.h"
 #include "Prelude.h"
+#ifdef PROFILING
+#include "Proftimer.h"
+#include "ProfHeap.h"
+#include "RetainerProfile.h"
+#endif
 #if defined(GRAN) || defined(PAR)
 # include "GranSimRts.h"
 # include "GranSim.h"
@@ -181,7 +186,6 @@ StgTSO *all_threads;
  */
 static StgTSO *suspended_ccalling_threads;
 
-static void GetRoots(evac_fn);
 static StgTSO *threadStackOverflow(StgTSO *tso);
 
 /* KH: The following two flags are shared memory locations.  There is no need
@@ -225,13 +229,10 @@ StgThreadID next_thread_id = 1;
  * Locks required: sched_mutex.
  */
 #ifdef SMP
-//@cindex free_capabilities
-//@cindex n_free_capabilities
 Capability *free_capabilities; /* Available capabilities for running threads */
 nat n_free_capabilities;       /* total number of available capabilities */
 #else
-//@cindex MainRegTable
-Capability MainRegTable;       /* for non-SMP, we have one global capability */
+Capability MainCapability;     /* for non-SMP, we have one global capability */
 #endif
 
 #if defined(GRAN)
@@ -460,7 +461,8 @@ schedule( void )
       }
     }
 
-#else
+#else // not SMP
+
 # if defined(PAR)
     /* in GUM do this only on the Main PE */
     if (IAmMainThread)
@@ -527,7 +529,14 @@ schedule( void )
          pthread_cond_signal(&thread_ready_cond);
       }
     }
-#endif /* SMP */
+#endif // SMP
+
+    /* check for signals each time around the scheduler */
+#ifndef mingw32_TARGET_OS
+    if (signals_pending()) {
+      startSignalHandlers();
+    }
+#endif
 
     /* Check whether any waiting threads need to be woken up.  If the
      * run queue is empty, and there are no other tasks running, we
@@ -546,13 +555,6 @@ schedule( void )
     /* we can be interrupted while waiting for I/O... */
     if (interrupted) continue;
 
-    /* check for signals each time around the scheduler */
-#ifndef mingw32_TARGET_OS
-    if (signals_pending()) {
-      start_signal_handlers();
-    }
-#endif
-
     /* 
      * Detect deadlock: when we have no threads to run, there are no
      * threads waiting on I/O or sleeping, and all the other tasks are
@@ -902,6 +904,9 @@ schedule( void )
      */
     ASSERT(run_queue_hd != END_TSO_QUEUE);
     t = POP_RUN_QUEUE();
+
+    // Sanity check the thread we're about to run.  This can be
+    // expensive if there is lots of thread switching going on...
     IF_DEBUG(sanity,checkTSO(t));
 
 #endif
@@ -913,19 +918,23 @@ schedule( void )
     free_capabilities = cap->link;
     n_free_capabilities--;
 #else
-    cap = &MainRegTable;
+    cap = &MainCapability;
 #endif
 
-    cap->rCurrentTSO = t;
+    cap->r.rCurrentTSO = t;
     
     /* context switches are now initiated by the timer signal, unless
      * the user specified "context switch as often as possible", with
      * +RTS -C0
      */
-    if (RtsFlags.ConcFlags.ctxtSwitchTicks == 0
-       && (run_queue_hd != END_TSO_QUEUE
-           || blocked_queue_hd != END_TSO_QUEUE
-           || sleeping_queue != END_TSO_QUEUE))
+    if (
+#ifdef PROFILING
+       RtsFlags.ProfFlags.profileInterval == 0 ||
+#endif
+       (RtsFlags.ConcFlags.ctxtSwitchTicks == 0
+        && (run_queue_hd != END_TSO_QUEUE
+            || blocked_queue_hd != END_TSO_QUEUE
+            || sleeping_queue != END_TSO_QUEUE)))
        context_switch = 1;
     else
        context_switch = 0;
@@ -935,20 +944,24 @@ schedule( void )
     IF_DEBUG(scheduler, sched_belch("-->> Running TSO %ld (%p) %s ...", 
                              t->id, t, whatNext_strs[t->what_next]));
 
+#ifdef PROFILING
+    startHeapProfTimer();
+#endif
+
     /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
     /* Run the current thread 
      */
-    switch (cap->rCurrentTSO->what_next) {
+    switch (cap->r.rCurrentTSO->what_next) {
     case ThreadKilled:
     case ThreadComplete:
        /* Thread already finished, return to scheduler. */
        ret = ThreadFinished;
        break;
     case ThreadEnterGHC:
-       ret = StgRun((StgFunPtr) stg_enterStackTop, cap);
+       ret = StgRun((StgFunPtr) stg_enterStackTop, &cap->r);
        break;
     case ThreadRunGHC:
-       ret = StgRun((StgFunPtr) stg_returnToStackTop, cap);
+       ret = StgRun((StgFunPtr) stg_returnToStackTop, &cap->r);
        break;
     case ThreadEnterInterp:
        ret = interpretBCO(cap);
@@ -960,6 +973,7 @@ schedule( void )
     
     /* Costs for the scheduler are assigned to CCS_SYSTEM */
 #ifdef PROFILING
+    stopHeapProfTimer();
     CCCS = CCS_SYSTEM;
 #endif
     
@@ -970,7 +984,7 @@ schedule( void )
 #elif !defined(GRAN) && !defined(PAR)
     IF_DEBUG(scheduler,fprintf(stderr,"scheduler: "););
 #endif
-    t = cap->rCurrentTSO;
+    t = cap->r.rCurrentTSO;
     
 #if defined(PAR)
     /* HACK 675: if the last thread didn't yield, make sure to print a 
@@ -983,14 +997,65 @@ schedule( void )
     switch (ret) {
     case HeapOverflow:
 #if defined(GRAN)
-      IF_DEBUG(gran, 
-              DumpGranEvent(GR_DESCHEDULE, t));
+      IF_DEBUG(gran, DumpGranEvent(GR_DESCHEDULE, t));
       globalGranStats.tot_heapover++;
 #elif defined(PAR)
-      // IF_DEBUG(par, 
-      //DumpGranEvent(GR_DESCHEDULE, t);
       globalParStats.tot_heapover++;
 #endif
+
+      // did the task ask for a large block?
+      if (cap->r.rHpAlloc > BLOCK_SIZE_W) {
+         // if so, get one and push it on the front of the nursery.
+         bdescr *bd;
+         nat blocks;
+         
+         blocks = (nat)BLOCK_ROUND_UP(cap->r.rHpAlloc * sizeof(W_)) / BLOCK_SIZE;
+
+         IF_DEBUG(scheduler,belch("--<< thread %ld (%p; %s) stopped: requesting a large block (size %d)", 
+                                  t->id, t,
+                                  whatNext_strs[t->what_next], blocks));
+
+         // don't do this if it would push us over the
+         // alloc_blocks_lim limit; we'll GC first.
+         if (alloc_blocks + blocks < alloc_blocks_lim) {
+
+             alloc_blocks += blocks;
+             bd = allocGroup( blocks );
+
+             // link the new group into the list
+             bd->link = cap->r.rCurrentNursery;
+             bd->u.back = cap->r.rCurrentNursery->u.back;
+             if (cap->r.rCurrentNursery->u.back != NULL) {
+                 cap->r.rCurrentNursery->u.back->link = bd;
+             } else {
+                 ASSERT(g0s0->blocks == cap->r.rCurrentNursery &&
+                        g0s0->blocks == cap->r.rNursery);
+                 cap->r.rNursery = g0s0->blocks = bd;
+             }           
+             cap->r.rCurrentNursery->u.back = bd;
+
+             // initialise it as a nursery block
+             bd->step = g0s0;
+             bd->gen_no = 0;
+             bd->flags = 0;
+             bd->free = bd->start;
+
+             // don't forget to update the block count in g0s0.
+             g0s0->n_blocks += blocks;
+             ASSERT(countBlocks(g0s0->blocks) == g0s0->n_blocks);
+
+             // now update the nursery to point to the new block
+             cap->r.rCurrentNursery = bd;
+
+             // we might be unlucky and have another thread get on the
+             // run queue before us and steal the large block, but in that
+             // case the thread will just end up requesting another large
+             // block.
+             PUSH_ON_RUN_QUEUE(t);
+             break;
+         }
+      }
+
       /* make all the running tasks block on a condition variable,
        * maybe set context_switch and wait till they all pile in,
        * then have them wait on a GC condition variable.
@@ -1210,6 +1275,15 @@ schedule( void )
     n_free_capabilities++;
 #endif
 
+#ifdef PROFILING
+    if (RtsFlags.ProfFlags.profileInterval==0 || performHeapProfile) {
+       GarbageCollect(GetRoots, rtsTrue);
+       heapCensus();
+       performHeapProfile = rtsFalse;
+       ready_to_gc = rtsFalse; // we already GC'd
+    }
+#endif
+
 #ifdef SMP
     if (ready_to_gc && n_free_capabilities == RtsFlags.ParFlags.nNodes) 
 #else
@@ -1240,24 +1314,20 @@ schedule( void )
               G_CURR_THREADQ(0));
 #endif /* GRAN */
     }
+
 #if defined(GRAN)
   next_thread:
     IF_GRAN_DEBUG(unused,
                  print_eventq(EventHd));
 
     event = get_next_event();
-
 #elif defined(PAR)
   next_thread:
     /* ToDo: wait for next message to arrive rather than busy wait */
-
-#else /* GRAN */
-  /* not any more
-  next_thread:
-    t = take_off_run_queue(END_TSO_QUEUE);
-  */
 #endif /* GRAN */
+
   } /* end of while(1) */
+
   IF_PAR_DEBUG(verbose,
               belch("== Leaving schedule() after having received Finish"));
 }
@@ -1308,21 +1378,25 @@ void deleteAllThreads ( void )
  * ------------------------------------------------------------------------- */
    
 StgInt
-suspendThread( Capability *cap )
+suspendThread( StgRegTable *reg )
 {
   nat tok;
+  Capability *cap;
+
+  // assume that *reg is a pointer to the StgRegTable part of a Capability
+  cap = (Capability *)((void *)reg - sizeof(StgFunTable));
 
   ACQUIRE_LOCK(&sched_mutex);
 
   IF_DEBUG(scheduler,
-          sched_belch("thread %d did a _ccall_gc", cap->rCurrentTSO->id));
+          sched_belch("thread %d did a _ccall_gc", cap->r.rCurrentTSO->id));
 
-  threadPaused(cap->rCurrentTSO);
-  cap->rCurrentTSO->link = suspended_ccalling_threads;
-  suspended_ccalling_threads = cap->rCurrentTSO;
+  threadPaused(cap->r.rCurrentTSO);
+  cap->r.rCurrentTSO->link = suspended_ccalling_threads;
+  suspended_ccalling_threads = cap->r.rCurrentTSO;
 
   /* Use the thread ID as the token; it should be unique */
-  tok = cap->rCurrentTSO->id;
+  tok = cap->r.rCurrentTSO->id;
 
 #ifdef SMP
   cap->link = free_capabilities;
@@ -1334,7 +1408,7 @@ suspendThread( Capability *cap )
   return tok; 
 }
 
-Capability *
+StgRegTable *
 resumeThread( StgInt tok )
 {
   StgTSO *tso, **prev;
@@ -1366,13 +1440,13 @@ resumeThread( StgInt tok )
   free_capabilities = cap->link;
   n_free_capabilities--;
 #else  
-  cap = &MainRegTable;
+  cap = &MainCapability;
 #endif
 
-  cap->rCurrentTSO = tso;
+  cap->r.rCurrentTSO = tso;
 
   RELEASE_LOCK(&sched_mutex);
-  return cap;
+  return &cap->r;
 }
 
 
@@ -1399,6 +1473,16 @@ int cmp_thread(const StgTSO *tso1, const StgTSO *tso2)
 }
 
 /* ---------------------------------------------------------------------------
+ * Fetching the ThreadID from an StgTSO.
+ *
+ * This is used in the implementation of Show for ThreadIds.
+ * ------------------------------------------------------------------------ */
+int rts_getThreadId(const StgTSO *tso) 
+{
+  return tso->id;
+}
+
+/* ---------------------------------------------------------------------------
    Create a new thread.
 
    The new thread starts with the given stack size.  Before the
@@ -1728,7 +1812,15 @@ term_handler(int sig STG_UNUSED)
 }
 #endif
 
-//@cindex initScheduler
+static void
+initCapability( Capability *cap )
+{
+    cap->f.stgChk0         = (F_)__stg_chk_0;
+    cap->f.stgChk1         = (F_)__stg_chk_1;
+    cap->f.stgGCEnter1     = (F_)__stg_gc_enter_1;
+    cap->f.stgUpdatePAP    = (F_)__stg_update_PAP;
+}
+
 void 
 initScheduler(void)
 {
@@ -1785,6 +1877,7 @@ initScheduler(void)
     prev = NULL;
     for (i = 0; i < RtsFlags.ParFlags.nNodes; i++) {
       cap = stgMallocBytes(sizeof(Capability), "initScheduler:capabilities");
+      initCapability(cap);
       cap->link = prev;
       prev = cap;
     }
@@ -1793,6 +1886,8 @@ initScheduler(void)
   }
   IF_DEBUG(scheduler,fprintf(stderr,"scheduler: Allocated %d capabilities\n",
                             n_free_capabilities););
+#else
+  initCapability(&MainCapability);
 #endif
 
 #if defined(SMP) || defined(PAR)
@@ -2097,7 +2192,7 @@ take_off_run_queue(StgTSO *tso) {
        KH @ 25/10/99
 */
 
-static void
+void
 GetRoots(evac_fn evac)
 {
   StgMainThread *m;