[project @ 1996-01-08 20:28:12 by partain]
[ghc-hetmet.git] / ghc / runtime / main / StgThreads.lhc
diff --git a/ghc/runtime/main/StgThreads.lhc b/ghc/runtime/main/StgThreads.lhc
new file mode 100644 (file)
index 0000000..b3f9f28
--- /dev/null
@@ -0,0 +1,496 @@
+%
+% (c) The AQUA Project, Glasgow University, 1994
+%
+%************************************************************************
+%*                                                                      *
+\section[StgThreads.lhc]{Threaded Threads Support}
+%*                                                                     *
+%************************************************************************
+
+Some of the threads support is done in threaded code.  How's that for ambiguous
+overloading?
+
+\begin{code}
+
+#ifdef CONCURRENT
+
+#define MAIN_REG_MAP       /* STG world */
+#include "rtsdefs.h"
+
+#if 0
+#ifdef PAR
+#include "Statistics.h"
+#endif
+#endif
+
+\end{code}
+
+%************************************************************************
+%*                                                                      *
+\subsection[thread-objects]{Special objects for thread support}
+%*                                                                     *
+%************************************************************************
+
+TSO's are Thread State Objects, where the thread context is stored when the
+thread is sleeping, and where we have slots for STG registers that don't 
+live in real machine registers.
+
+\begin{code}
+
+TSO_ITBL();
+
+STGFUN(TSO_entry)
+{
+    FB_
+    fflush(stdout);
+    fprintf(stderr, "TSO Entry: panic");
+    abort();
+    FE_
+}
+
+\end{code}
+
+Stack objects are chunks of stack words allocated out of the heap and
+linked together in a chain.
+
+\begin{code}
+
+STKO_ITBL();
+
+STGFUN(StkO_entry)
+{
+    FB_
+    fflush(stdout);
+    fprintf(stderr, "StkO Entry: panic");
+    abort();
+    FE_
+
+}
+
+#ifndef PAR
+
+STKO_STATIC_ITBL();
+
+STGFUN(StkO_static_entry)
+{
+    FB_
+    fflush(stdout);
+    fprintf(stderr, "StkO_static Entry: panic");
+    abort();
+    FE_
+
+}
+
+#endif
+
+\end{code}
+
+Blocking queues are essentially black holes with threads attached.  These
+are the threads to be awakened when the closure is updated.
+
+\begin{code}
+
+EXTFUN(EnterNodeCode);
+
+STGFUN(BQ_entry)
+{   
+    FB_
+
+#if defined(GRAN)
+    STGCALL0(void,(),GranSimBlock);    /* Before overwriting TSO_LINK */
+#endif
+
+    TSO_LINK(CurrentTSO) = (P_) BQ_ENTRIES(Node);
+    BQ_ENTRIES(Node) = (W_) CurrentTSO;
+
+    LivenessReg = LIVENESS_R1;
+    SaveAllStgRegs();
+    TSO_PC1(CurrentTSO) = EnterNodeCode;
+
+    if (DO_QP_PROF) {
+       QP_Event1("GR", CurrentTSO);
+    }
+#ifdef PAR
+    if(do_gr_profile) {
+        /* Note that CURRENT_TIME may perform an unsafe call */
+       TIME now = CURRENT_TIME;
+        TSO_EXECTIME(CurrentTSO) += now - TSO_BLOCKEDAT(CurrentTSO);
+        TSO_BLOCKCOUNT(CurrentTSO)++;
+       TSO_QUEUE(CurrentTSO) = Q_BLOCKED;
+        TSO_BLOCKEDAT(CurrentTSO) = now;
+        DumpGranEvent(GR_BLOCK, CurrentTSO);
+    }
+#endif
+#if defined(GRAN)
+    ReSchedule(NEW_THREAD);
+#else
+    ReSchedule(0);
+#endif
+    FE_
+}
+
+BQ_ITBL();
+
+\end{code}
+
+Revertible black holes are needed in the parallel world, to handle
+negative acknowledgements of messages containing updatable closures.
+The idea is that when the original message is transmitted, the closure
+is turned into a revertible black hole...an object which acts like a
+black hole when local threads try to enter it, but which can be
+reverted back to the original closure if necessary.
+
+It's actually a lot like a blocking queue (BQ) entry, because
+revertible black holes are initially set up with an empty blocking
+queue.
+
+The combination of GrAnSim with revertible black holes has not been
+checked, yet. -- HWL
+
+\begin{code}
+
+#ifdef PAR
+
+STGFUN(RBH_entry)
+{
+    FB_
+
+#if defined(GRAN)
+    STGCALL0(void, (), GranSimBlock);  /* Before overwriting TSO_LINK */
+#endif
+
+    switch (INFO_TYPE(InfoPtr)) {
+    case INFO_SPEC_RBH_TYPE:
+       TSO_LINK(CurrentTSO) = (P_) SPEC_RBH_BQ(Node);
+       SPEC_RBH_BQ(Node) = (W_) CurrentTSO;
+       break;
+    case INFO_GEN_RBH_TYPE:
+       TSO_LINK(CurrentTSO) = (P_) GEN_RBH_BQ(Node);
+       GEN_RBH_BQ(Node) = (W_) CurrentTSO;
+       break;
+    default:
+       fflush(stdout);
+       fprintf(stderr, "Panic: non-{SPEC,GEN} RBH %#lx (IP %#lx)\n", Node, InfoPtr);
+       EXIT(EXIT_FAILURE);
+    }
+
+    LivenessReg = LIVENESS_R1;
+    SaveAllStgRegs();
+    TSO_PC1(CurrentTSO) = EnterNodeCode;
+
+    if (DO_QP_PROF) {
+       QP_Event1("GR", CurrentTSO);
+    }
+
+    if(do_gr_profile) {
+        /* Note that CURRENT_TIME may perform an unsafe call */
+       TIME now = CURRENT_TIME;
+        TSO_EXECTIME(CurrentTSO) += now - TSO_BLOCKEDAT(CurrentTSO);
+        TSO_BLOCKCOUNT(CurrentTSO)++;
+       TSO_QUEUE(CurrentTSO) = Q_BLOCKED;
+        TSO_BLOCKEDAT(CurrentTSO) = now;
+        DumpGranEvent(GR_BLOCK, CurrentTSO);
+    }
+
+#if defined(GRAN)
+    ReSchedule(NEW_THREAD);
+#else
+    ReSchedule(0);
+#endif
+
+    FE_
+}
+
+#endif
+
+\end{code}
+
+%************************************************************************
+%*                                                                      *
+\subsection[thread-entrypoints]{Scheduler-Thread Interfaces}
+%*                                                                     *
+%************************************************************************
+
+The normal way of entering a thread is through resumeThread, which 
+short-circuits and indirections to the TSO and StkO, sets up STG registers,
+and jumps to the saved PC.
+
+\begin{code}
+
+STGFUN(resumeThread)
+{
+    FB_
+
+    while((P_) INFO_PTR(CurrentTSO) == Ind_info) {
+       CurrentTSO = (P_) IND_CLOSURE_PTR(CurrentTSO);
+    }
+
+#ifdef PAR
+    if (do_gr_profile) {
+       TSO_QUEUE(CurrentTSO) = Q_RUNNING;
+       /* Note that CURRENT_TIME may perform an unsafe call */
+        TSO_BLOCKEDAT(CurrentTSO) = CURRENT_TIME;
+    }
+#endif
+
+    CurrentRegTable = TSO_INTERNAL_PTR(CurrentTSO);
+
+    while((P_) INFO_PTR(SAVE_StkO) == Ind_info) {
+       SAVE_StkO = (P_) IND_CLOSURE_PTR(SAVE_StkO);
+    }
+    RestoreAllStgRegs();
+
+    SET_TASK_ACTIVITY(ST_REDUCING);
+    SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
+    RESTORE_CCC(TSO_CCC(CurrentTSO));
+    JMP_(TSO_PC1(CurrentTSO));
+    FE_
+}
+
+\end{code}
+
+Since we normally context switch during a heap check, it is possible
+that we will return to a previously suspended thread without
+sufficient heap for the thread to continue.  However, we have cleverly
+stashed away the heap requirements in @TSO_ARG1@ so that we can decide
+whether or not to perform a garbage collection before resuming the
+thread.  The actual thread resumption address (either @EnterNodeCode@
+or elsewhere) is stashed in TSO_PC2.
+
+\begin{code}
+
+STGFUN(CheckHeapCode)
+{
+    FB_
+
+    ALLOC_HEAP(TSO_ARG1(CurrentTSO)); /* ticky profiling */
+    SET_ACTIVITY(ACT_HEAP_CHK); /* SPAT counting */
+    if ((Hp += TSO_ARG1(CurrentTSO)) > HpLim) {
+       ReallyPerformThreadGC(TSO_ARG1(CurrentTSO), rtsFalse);
+       JMP_(resumeThread);
+    }
+    SET_TASK_ACTIVITY(ST_REDUCING);
+    SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
+    RESUME_(TSO_PC2(CurrentTSO));
+    FE_
+}
+
+\end{code}
+
+Often, a thread starts (or rather, resumes) by entering the closure
+that Node points to.  Here's a tiny code fragment to do just that.
+The saved PC in the TSO can be set to @EnterNodeCode@ whenever we
+want this to happen upon resumption of the thread.
+
+\begin{code}
+
+STGFUN(EnterNodeCode)
+{
+    FB_
+    ENT_VIA_NODE();
+    InfoPtr=(D_)(INFO_PTR(Node));
+    GRAN_EXEC(5,1,2,0,0);
+    JMP_(ENTRY_CODE(InfoPtr));
+    FE_
+}
+
+\end{code}
+
+Then, there are the occasions when we just want to pick up where we left off.
+We use RESUME_ here instead of JMP_, because when we return to a call site,
+the alpha is going to try to load %gp from %ra rather than %pv, and JMP_ only
+sets %pv.  Resuming to the start of a function is currently okay, but an
+extremely bad practice.  As we add support for more architectures, we can expect 
+the difference between RESUME_ and JMP_ to become more acute.
+
+\begin{code}
+
+STGFUN(Continue)
+{
+    FB_
+
+    SET_TASK_ACTIVITY(ST_REDUCING);
+    SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
+    RESUME_(TSO_PC2(CurrentTSO));
+    FE_
+}
+
+\end{code}
+
+%************************************************************************
+%*                                                                     *
+\subsection[stack-chunk-underflow-code]{Underflow code for stack chunks}
+%*                                                                     *
+%************************************************************************
+
+\begin{code}
+
+extern P_ AvailableStack;
+
+#ifndef PAR
+
+\end{code}
+
+On a uniprocessor, stack underflow causes us no great headaches.  The
+old value of RetReg is squirreled away at the base of the top stack
+object (the one that's about to get blown away).  We just yank it
+outta there and perform the same kind of return that got us here in
+the first place.
+
+This simplicity is due to the fact that we never have to fetch a stack
+object on underflow.
+
+\begin{code}
+
+#define DO_RETURN_TEMPLATE(label, cont)                \
+    STGFUN(label)                              \
+    {                                          \
+      P_ temp;                                 \
+      FB_                                      \
+      temp = STKO_LINK(StkOReg);               \
+      RetReg = STKO_RETURN(StkOReg);           \
+      StkOReg = temp;                          \
+      RestoreStackStgRegs();                   \
+      JMP_(cont);                              \
+      FE_                                      \
+    }
+
+DO_RETURN_TEMPLATE(UnderflowDirectReturn, DIRECT(((P_)RetReg)))
+DO_RETURN_TEMPLATE(UnderflowVect0, ((P_)RetReg)[RVREL(0)])
+DO_RETURN_TEMPLATE(UnderflowVect1, ((P_)RetReg)[RVREL(1)])
+DO_RETURN_TEMPLATE(UnderflowVect2, ((P_)RetReg)[RVREL(2)])
+DO_RETURN_TEMPLATE(UnderflowVect3, ((P_)RetReg)[RVREL(3)])
+DO_RETURN_TEMPLATE(UnderflowVect4, ((P_)RetReg)[RVREL(4)])
+
+DO_RETURN_TEMPLATE(UnderflowVect5, ((P_)RetReg)[RVREL(5)])
+DO_RETURN_TEMPLATE(UnderflowVect6, ((P_)RetReg)[RVREL(6)])
+DO_RETURN_TEMPLATE(UnderflowVect7, ((P_)RetReg)[RVREL(7)])
+
+DO_RETURN_TEMPLATE(StackUnderflowEnterNode, EnterNodeCode)
+
+#else
+
+\end{code}
+
+In the parallel world, we may have to fetch the StkO from a remote
+location before we can load up the stack registers and perform the
+return.  Our convention is that we load RetReg up with the exact
+continuation address (after a vector table lookup, if necessary),
+and tail-call the code to fetch the stack object.  (Of course, if
+the stack object is already local, we then just jump to the 
+continuation address.)
+
+\begin{code}
+
+STGFUN(CommonUnderflow)
+{
+    P_ temp;
+
+    FB_
+    temp = STKO_LINK(StkOReg);
+    StkOReg = temp;
+    /* ToDo: Fetch the remote stack object here! */
+    RestoreStackStgRegs();
+    JMP_(RetReg);
+    FE_
+}
+
+#define DO_RETURN_TEMPLATE(label, cont)                \
+    STGFUN(label)                              \
+    {                                          \
+      FB_                                      \
+      RetReg = STKO_RETURN(StkOReg);           \
+      RetReg = (StgRetAddr)(cont);             \
+      LivenessReg = INFO_LIVENESS(InfoPtr);    \
+      JMP_(CommonUnderflow);                   \
+      FE_                                      \
+    }
+
+DO_RETURN_TEMPLATE(UnderflowDirectReturn, DIRECT(((P_)RetReg)))
+DO_RETURN_TEMPLATE(UnderflowVect0, ((P_)RetReg)[RVREL(0)])
+DO_RETURN_TEMPLATE(UnderflowVect1, ((P_)RetReg)[RVREL(1)])
+DO_RETURN_TEMPLATE(UnderflowVect2, ((P_)RetReg)[RVREL(2)])
+DO_RETURN_TEMPLATE(UnderflowVect3, ((P_)RetReg)[RVREL(3)])
+DO_RETURN_TEMPLATE(UnderflowVect4, ((P_)RetReg)[RVREL(4)])
+DO_RETURN_TEMPLATE(UnderflowVect5, ((P_)RetReg)[RVREL(5)])
+DO_RETURN_TEMPLATE(UnderflowVect6, ((P_)RetReg)[RVREL(6)])
+DO_RETURN_TEMPLATE(UnderflowVect7, ((P_)RetReg)[RVREL(7)])
+
+STGFUN(PrimUnderflow)
+{
+    FB_
+    RetReg = STKO_RETURN(StkOReg);
+    RetReg = (StgRetAddr)DIRECT(((P_)RetReg));
+    LivenessReg = NO_LIVENESS;
+    JMP_(CommonUnderflow);
+    FE_
+}
+
+/* 
+ * This one is similar, but isn't part of the return vector.  It's only used
+ * when we fall off of a stack chunk and want to enter Node rather than
+ * returning through RetReg.  (This occurs during UpdatePAP, when the updatee
+ * isn't on the current stack chunk.)  It can't be done with the template,
+ * because R2 is dead, and R1 points to a PAP.  Only R1 is live.
+ */
+
+STGFUN(StackUnderflowEnterNode)
+{
+    FB_
+    RetReg = (StgRetAddr)(EnterNodeCode);
+    LivenessReg = LIVENESS_R1;
+    JMP_(CommonUnderflow);
+    FE_
+}
+
+#endif
+
+const W_
+vtbl_Underflow[] = {
+    /* "MAX_VECTORED_RTN" elements (see GhcConstants.lh) */
+    (W_) UnderflowVect0,
+    (W_) UnderflowVect1,
+    (W_) UnderflowVect2,
+    (W_) UnderflowVect3,
+    (W_) UnderflowVect4,
+    (W_) UnderflowVect5,
+    (W_) UnderflowVect6,
+    (W_) UnderflowVect7
+};
+
+\end{code}
+
+\begin{code}
+
+IFN_(seqDirectReturn) {
+    void *cont;
+
+    FB_
+    RetReg = (StgRetAddr) SpB[BREL(0)];
+    cont = (void *) SpB[BREL(1)];
+    SpB += BREL(2);
+/*  GRAN_EXEC(1,1,2,0,0); /? ToDo: RE-CHECK (WDP) */
+    JMP_(cont);
+    FE_
+}
+
+/*
+   NB: For direct returns to work properly, the name of the routine must be
+   the same as the name of the vector table with vtbl_ removed and DirectReturn
+   appended.  This is all the mangler understands.
+ */
+
+const W_
+vtbl_seq[] = {
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn,
+    (W_) seqDirectReturn
+};
+
+#endif /* CONCURRENT */
+\end{code}