2 % (c) The AQUA Project, Glasgow University, 1994
4 %************************************************************************
6 \section[StgThreads.lhc]{Threaded Threads Support}
8 %************************************************************************
10 Some of the threads support is done in threaded code. How's that for ambiguous
17 #define MAIN_REG_MAP /* STG world */
22 #include "Statistics.h"
28 %************************************************************************
30 \subsection[thread-objects]{Special objects for thread support}
32 %************************************************************************
34 TSO's are Thread State Objects, where the thread context is stored when the
35 thread is sleeping, and where we have slots for STG registers that don't
36 live in real machine registers.
46 fprintf(stderr, "TSO Entry: panic");
53 Stack objects are chunks of stack words allocated out of the heap and
54 linked together in a chain.
64 fprintf(stderr, "StkO Entry: panic");
74 STGFUN(StkO_static_entry)
78 fprintf(stderr, "StkO_static Entry: panic");
88 Blocking queues are essentially black holes with threads attached. These
89 are the threads to be awakened when the closure is updated.
93 EXTFUN(EnterNodeCode);
100 STGCALL0(void,(),GranSimBlock); /* Before overwriting TSO_LINK */
103 TSO_LINK(CurrentTSO) = (P_) BQ_ENTRIES(Node);
104 BQ_ENTRIES(Node) = (W_) CurrentTSO;
106 LivenessReg = LIVENESS_R1;
108 TSO_PC1(CurrentTSO) = EnterNodeCode;
111 QP_Event1("GR", CurrentTSO);
115 /* Note that CURRENT_TIME may perform an unsafe call */
116 TIME now = CURRENT_TIME;
117 TSO_EXECTIME(CurrentTSO) += now - TSO_BLOCKEDAT(CurrentTSO);
118 TSO_BLOCKCOUNT(CurrentTSO)++;
119 TSO_QUEUE(CurrentTSO) = Q_BLOCKED;
120 TSO_BLOCKEDAT(CurrentTSO) = now;
121 DumpGranEvent(GR_BLOCK, CurrentTSO);
125 ReSchedule(NEW_THREAD);
136 Revertible black holes are needed in the parallel world, to handle
137 negative acknowledgements of messages containing updatable closures.
138 The idea is that when the original message is transmitted, the closure
139 is turned into a revertible black hole...an object which acts like a
140 black hole when local threads try to enter it, but which can be
141 reverted back to the original closure if necessary.
143 It's actually a lot like a blocking queue (BQ) entry, because
144 revertible black holes are initially set up with an empty blocking
147 The combination of GrAnSim with revertible black holes has not been
159 STGCALL0(void, (), GranSimBlock); /* Before overwriting TSO_LINK */
162 switch (INFO_TYPE(InfoPtr)) {
163 case INFO_SPEC_RBH_TYPE:
164 TSO_LINK(CurrentTSO) = (P_) SPEC_RBH_BQ(Node);
165 SPEC_RBH_BQ(Node) = (W_) CurrentTSO;
167 case INFO_GEN_RBH_TYPE:
168 TSO_LINK(CurrentTSO) = (P_) GEN_RBH_BQ(Node);
169 GEN_RBH_BQ(Node) = (W_) CurrentTSO;
173 fprintf(stderr, "Panic: non-{SPEC,GEN} RBH %#lx (IP %#lx)\n", Node, InfoPtr);
177 LivenessReg = LIVENESS_R1;
179 TSO_PC1(CurrentTSO) = EnterNodeCode;
182 QP_Event1("GR", CurrentTSO);
186 /* Note that CURRENT_TIME may perform an unsafe call */
187 TIME now = CURRENT_TIME;
188 TSO_EXECTIME(CurrentTSO) += now - TSO_BLOCKEDAT(CurrentTSO);
189 TSO_BLOCKCOUNT(CurrentTSO)++;
190 TSO_QUEUE(CurrentTSO) = Q_BLOCKED;
191 TSO_BLOCKEDAT(CurrentTSO) = now;
192 DumpGranEvent(GR_BLOCK, CurrentTSO);
196 ReSchedule(NEW_THREAD);
208 %************************************************************************
210 \subsection[thread-entrypoints]{Scheduler-Thread Interfaces}
212 %************************************************************************
214 The normal way of entering a thread is through resumeThread, which
215 short-circuits and indirections to the TSO and StkO, sets up STG registers,
216 and jumps to the saved PC.
224 while((P_) INFO_PTR(CurrentTSO) == Ind_info) {
225 CurrentTSO = (P_) IND_CLOSURE_PTR(CurrentTSO);
230 TSO_QUEUE(CurrentTSO) = Q_RUNNING;
231 /* Note that CURRENT_TIME may perform an unsafe call */
232 TSO_BLOCKEDAT(CurrentTSO) = CURRENT_TIME;
236 CurrentRegTable = TSO_INTERNAL_PTR(CurrentTSO);
238 while((P_) INFO_PTR(SAVE_StkO) == Ind_info) {
239 SAVE_StkO = (P_) IND_CLOSURE_PTR(SAVE_StkO);
243 SET_TASK_ACTIVITY(ST_REDUCING);
244 SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
245 RESTORE_CCC(TSO_CCC(CurrentTSO));
246 JMP_(TSO_PC1(CurrentTSO));
252 Since we normally context switch during a heap check, it is possible
253 that we will return to a previously suspended thread without
254 sufficient heap for the thread to continue. However, we have cleverly
255 stashed away the heap requirements in @TSO_ARG1@ so that we can decide
256 whether or not to perform a garbage collection before resuming the
257 thread. The actual thread resumption address (either @EnterNodeCode@
258 or elsewhere) is stashed in TSO_PC2.
262 STGFUN(CheckHeapCode)
266 ALLOC_HEAP(TSO_ARG1(CurrentTSO)); /* ticky profiling */
267 SET_ACTIVITY(ACT_HEAP_CHK); /* SPAT counting */
268 if ((Hp += TSO_ARG1(CurrentTSO)) > HpLim) {
269 ReallyPerformThreadGC(TSO_ARG1(CurrentTSO), rtsFalse);
272 SET_TASK_ACTIVITY(ST_REDUCING);
273 SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
274 RESUME_(TSO_PC2(CurrentTSO));
280 Often, a thread starts (or rather, resumes) by entering the closure
281 that Node points to. Here's a tiny code fragment to do just that.
282 The saved PC in the TSO can be set to @EnterNodeCode@ whenever we
283 want this to happen upon resumption of the thread.
287 STGFUN(EnterNodeCode)
291 InfoPtr=(D_)(INFO_PTR(Node));
292 GRAN_EXEC(5,1,2,0,0);
293 JMP_(ENTRY_CODE(InfoPtr));
299 Then, there are the occasions when we just want to pick up where we left off.
300 We use RESUME_ here instead of JMP_, because when we return to a call site,
301 the alpha is going to try to load %gp from %ra rather than %pv, and JMP_ only
302 sets %pv. Resuming to the start of a function is currently okay, but an
303 extremely bad practice. As we add support for more architectures, we can expect
304 the difference between RESUME_ and JMP_ to become more acute.
312 SET_TASK_ACTIVITY(ST_REDUCING);
313 SET_ACTIVITY(ACT_REDN); /* back to normal reduction */
314 RESUME_(TSO_PC2(CurrentTSO));
320 %************************************************************************
322 \subsection[stack-chunk-underflow-code]{Underflow code for stack chunks}
324 %************************************************************************
328 extern P_ AvailableStack;
334 On a uniprocessor, stack underflow causes us no great headaches. The
335 old value of RetReg is squirreled away at the base of the top stack
336 object (the one that's about to get blown away). We just yank it
337 outta there and perform the same kind of return that got us here in
340 This simplicity is due to the fact that we never have to fetch a stack
345 #define DO_RETURN_TEMPLATE(label, cont) \
350 temp = STKO_LINK(StkOReg); \
351 RetReg = STKO_RETURN(StkOReg); \
353 RestoreStackStgRegs(); \
358 DO_RETURN_TEMPLATE(UnderflowDirectReturn, DIRECT(((P_)RetReg)))
359 DO_RETURN_TEMPLATE(UnderflowVect0, ((P_)RetReg)[RVREL(0)])
360 DO_RETURN_TEMPLATE(UnderflowVect1, ((P_)RetReg)[RVREL(1)])
361 DO_RETURN_TEMPLATE(UnderflowVect2, ((P_)RetReg)[RVREL(2)])
362 DO_RETURN_TEMPLATE(UnderflowVect3, ((P_)RetReg)[RVREL(3)])
363 DO_RETURN_TEMPLATE(UnderflowVect4, ((P_)RetReg)[RVREL(4)])
365 DO_RETURN_TEMPLATE(UnderflowVect5, ((P_)RetReg)[RVREL(5)])
366 DO_RETURN_TEMPLATE(UnderflowVect6, ((P_)RetReg)[RVREL(6)])
367 DO_RETURN_TEMPLATE(UnderflowVect7, ((P_)RetReg)[RVREL(7)])
369 DO_RETURN_TEMPLATE(StackUnderflowEnterNode, EnterNodeCode)
375 In the parallel world, we may have to fetch the StkO from a remote
376 location before we can load up the stack registers and perform the
377 return. Our convention is that we load RetReg up with the exact
378 continuation address (after a vector table lookup, if necessary),
379 and tail-call the code to fetch the stack object. (Of course, if
380 the stack object is already local, we then just jump to the
381 continuation address.)
385 STGFUN(CommonUnderflow)
390 temp = STKO_LINK(StkOReg);
392 /* ToDo: Fetch the remote stack object here! */
393 RestoreStackStgRegs();
398 #define DO_RETURN_TEMPLATE(label, cont) \
402 RetReg = STKO_RETURN(StkOReg); \
403 RetReg = (StgRetAddr)(cont); \
404 LivenessReg = INFO_LIVENESS(InfoPtr); \
405 JMP_(CommonUnderflow); \
409 DO_RETURN_TEMPLATE(UnderflowDirectReturn, DIRECT(((P_)RetReg)))
410 DO_RETURN_TEMPLATE(UnderflowVect0, ((P_)RetReg)[RVREL(0)])
411 DO_RETURN_TEMPLATE(UnderflowVect1, ((P_)RetReg)[RVREL(1)])
412 DO_RETURN_TEMPLATE(UnderflowVect2, ((P_)RetReg)[RVREL(2)])
413 DO_RETURN_TEMPLATE(UnderflowVect3, ((P_)RetReg)[RVREL(3)])
414 DO_RETURN_TEMPLATE(UnderflowVect4, ((P_)RetReg)[RVREL(4)])
415 DO_RETURN_TEMPLATE(UnderflowVect5, ((P_)RetReg)[RVREL(5)])
416 DO_RETURN_TEMPLATE(UnderflowVect6, ((P_)RetReg)[RVREL(6)])
417 DO_RETURN_TEMPLATE(UnderflowVect7, ((P_)RetReg)[RVREL(7)])
419 STGFUN(PrimUnderflow)
422 RetReg = STKO_RETURN(StkOReg);
423 RetReg = (StgRetAddr)DIRECT(((P_)RetReg));
424 LivenessReg = NO_LIVENESS;
425 JMP_(CommonUnderflow);
430 * This one is similar, but isn't part of the return vector. It's only used
431 * when we fall off of a stack chunk and want to enter Node rather than
432 * returning through RetReg. (This occurs during UpdatePAP, when the updatee
433 * isn't on the current stack chunk.) It can't be done with the template,
434 * because R2 is dead, and R1 points to a PAP. Only R1 is live.
437 STGFUN(StackUnderflowEnterNode)
440 RetReg = (StgRetAddr)(EnterNodeCode);
441 LivenessReg = LIVENESS_R1;
442 JMP_(CommonUnderflow);
450 /* "MAX_VECTORED_RTN" elements (see GhcConstants.lh) */
465 IFN_(seqDirectReturn) {
469 RetReg = (StgRetAddr) SpB[BREL(0)];
470 cont = (void *) SpB[BREL(1)];
472 /* GRAN_EXEC(1,1,2,0,0); /? ToDo: RE-CHECK (WDP) */
478 NB: For direct returns to work properly, the name of the routine must be
479 the same as the name of the vector table with vtbl_ removed and DirectReturn
480 appended. This is all the mangler understands.
485 (W_) seqDirectReturn,
486 (W_) seqDirectReturn,
487 (W_) seqDirectReturn,
488 (W_) seqDirectReturn,
489 (W_) seqDirectReturn,
490 (W_) seqDirectReturn,
491 (W_) seqDirectReturn,
495 #endif /* CONCURRENT */