Commit f4692220 authored by Simon Marlow's avatar Simon Marlow

Change the representation of the MVar blocked queue

The list of threads blocked on an MVar is now represented as a list of
separately allocated objects rather than being linked through the TSOs
themselves.  This lets us remove a TSO from the list in O(1) time
rather than O(n) time, by marking the list object.  Removing this
linear component fixes some pathalogical performance cases where many
threads were blocked on an MVar and became unreachable simultaneously
(nofib/smp/threads007), or when sending an asynchronous exception to a
TSO in a long list of thread blocked on an MVar.

MVar performance has actually improved by a few percent as a result of
this change, slightly to my surprise.

This is the final cleanup in the sequence, which let me remove the old
way of waking up threads (unblockOne(), MSG_WAKEUP) in favour of the
new way (tryWakeupThread and MSG_TRY_WAKEUP, which is idempotent).  It
is now the case that only the Capability that owns a TSO may modify
its state (well, almost), and this simplifies various things.  More of
the RTS is based on message-passing between Capabilities now.
parent 7c4cb84e
......@@ -372,6 +372,10 @@ main(int argc, char *argv[])
closure_field(StgMVar,tail);
closure_field(StgMVar,value);
closure_size(StgMVarTSOQueue);
closure_field(StgMVarTSOQueue, link);
closure_field(StgMVarTSOQueue, tso);
closure_size(StgBCO);
closure_field(StgBCO, instrs);
closure_field(StgBCO, literals);
......
......@@ -227,8 +227,12 @@
/* same as above but don't unblock async exceptions in resumeThread() */
/* Involved in a message sent to tso->msg_cap */
#define BlockedOnMsgWakeup 12
#define BlockedOnMsgThrowTo 13
#define BlockedOnMsgThrowTo 12
/* The thread is not on any run queues, but can be woken up
by tryWakeupThread() */
#define ThreadMigrating 13
/*
* These constants are returned to the scheduler by a thread that has
* stopped for one reason or another. See typedef StgThreadReturnCode
......
......@@ -109,7 +109,8 @@ typedef struct bdescr_ {
#else
INLINE_HEADER bdescr *Bdescr(StgPtr p)
EXTERN_INLINE bdescr *Bdescr(StgPtr p);
EXTERN_INLINE bdescr *Bdescr(StgPtr p)
{
return (bdescr *)
((((W_)p & MBLOCK_MASK & ~BLOCK_MASK) >> (BLOCK_SHIFT-BDESCR_SHIFT))
......
......@@ -305,11 +305,17 @@ typedef struct {
/* Concurrent communication objects */
typedef struct StgMVarTSOQueue_ {
StgHeader header;
struct StgMVarTSOQueue_ *link;
struct StgTSO_ *tso;
} StgMVarTSOQueue;
typedef struct {
StgHeader header;
struct StgTSO_ *head;
struct StgTSO_ *tail;
StgClosure* value;
StgHeader header;
struct StgMVarTSOQueue_ *head;
struct StgMVarTSOQueue_ *tail;
StgClosure* value;
} StgMVar;
......
......@@ -82,7 +82,6 @@ typedef struct StgTSO_ {
/*
Currently used for linking TSOs on:
* cap->run_queue_{hd,tl}
* MVAR queue
* (non-THREADED_RTS); the blocked_queue
* and pointing to the relocated version of a ThreadRelocated
......
......@@ -42,10 +42,10 @@
# define RTS_FUN(f) RTS_FUN_INFO(f##_info)
# define RTS_THUNK(f) RTS_THUNK_INFO(f##_info)
#else
# define RTS_RET(f) RTS_INFO(f##_info) RTS_FUN_DECL(f##_ret)
# define RTS_ENTRY(f) RTS_INFO(f##_info) RTS_FUN_DECL(f##_entry)
# define RTS_FUN(f) RTS_FUN_INFO(f##_info) RTS_FUN_DECL(f##_entry)
# define RTS_THUNK(f) RTS_THUNK_INFO(f##_info) RTS_FUN_DECL(f##_entry)
# define RTS_RET(f) RTS_INFO(f##_info); RTS_FUN_DECL(f##_ret)
# define RTS_ENTRY(f) RTS_INFO(f##_info); RTS_FUN_DECL(f##_entry)
# define RTS_FUN(f) RTS_FUN_INFO(f##_info); RTS_FUN_DECL(f##_entry)
# define RTS_THUNK(f) RTS_THUNK_INFO(f##_info); RTS_FUN_DECL(f##_entry)
#endif
/* Stack frames */
......@@ -109,7 +109,6 @@ RTS_ENTRY(stg_MUT_ARR_PTRS_FROZEN0);
RTS_ENTRY(stg_MUT_VAR_CLEAN);
RTS_ENTRY(stg_MUT_VAR_DIRTY);
RTS_ENTRY(stg_END_TSO_QUEUE);
RTS_ENTRY(stg_MSG_WAKEUP);
RTS_ENTRY(stg_MSG_TRY_WAKEUP);
RTS_ENTRY(stg_MSG_THROWTO);
RTS_ENTRY(stg_MSG_BLACKHOLE);
......
......@@ -199,9 +199,9 @@ extern volatile StgWord waiting_for_gc;
//
void waitForReturnCapability (Capability **cap/*in/out*/, Task *task);
INLINE_HEADER void recordMutableCap (StgClosure *p, Capability *cap, nat gen);
EXTERN_INLINE void recordMutableCap (StgClosure *p, Capability *cap, nat gen);
INLINE_HEADER void recordClosureMutated (Capability *cap, StgClosure *p);
EXTERN_INLINE void recordClosureMutated (Capability *cap, StgClosure *p);
#if defined(THREADED_RTS)
......@@ -291,7 +291,7 @@ INLINE_HEADER rtsBool emptyInbox(Capability *cap);;
* INLINE functions... private below here
* -------------------------------------------------------------------------- */
INLINE_HEADER void
EXTERN_INLINE void
recordMutableCap (StgClosure *p, Capability *cap, nat gen)
{
bdescr *bd;
......@@ -310,7 +310,7 @@ recordMutableCap (StgClosure *p, Capability *cap, nat gen)
*bd->free++ = (StgWord)p;
}
INLINE_HEADER void
EXTERN_INLINE void
recordClosureMutated (Capability *cap, StgClosure *p)
{
bdescr *bd;
......
......@@ -481,9 +481,13 @@ INFO_TABLE_RET( stg_gc_gen, RET_DYN )
stg_gc_gen
{
// Hack; see Note [mvar-heap-check] in PrimOps.cmm
if (R10 == stg_putMVarzh || R10 == stg_takeMVarzh) {
unlockClosure(R1, stg_MVAR_DIRTY_info)
}
SAVE_EVERYTHING;
GC_GENERIC
}
}
// A heap check at an unboxed tuple return point. The return address
// is on the stack, and we can find it by using the offsets given
......@@ -583,11 +587,7 @@ INFO_TABLE_RET( stg_block_takemvar, RET_SMALL, P_ unused )
// code fragment executed just before we return to the scheduler
stg_block_takemvar_finally
{
#ifdef THREADED_RTS
unlockClosure(R3, stg_MVAR_DIRTY_info);
#else
SET_INFO(R3, stg_MVAR_DIRTY_info);
#endif
jump StgReturn;
}
......
......@@ -5,3 +5,4 @@
#include "PosixSource.h"
#include "Rts.h"
#include "Schedule.h"
#include "Capability.h"
......@@ -28,8 +28,7 @@ void sendMessage(Capability *from_cap, Capability *to_cap, Message *msg)
#ifdef DEBUG
{
const StgInfoTable *i = msg->header.info;
if (i != &stg_MSG_WAKEUP_info &&
i != &stg_MSG_THROWTO_info &&
if (i != &stg_MSG_THROWTO_info &&
i != &stg_MSG_BLACKHOLE_info &&
i != &stg_MSG_TRY_WAKEUP_info &&
i != &stg_IND_info && // can happen if a MSG_BLACKHOLE is revoked
......@@ -71,21 +70,7 @@ executeMessage (Capability *cap, Message *m)
loop:
write_barrier(); // allow m->header to be modified by another thread
i = m->header.info;
if (i == &stg_MSG_WAKEUP_info)
{
// the plan is to eventually get rid of these and use
// TRY_WAKEUP instead.
MessageWakeup *w = (MessageWakeup *)m;
StgTSO *tso = w->tso;
debugTraceCap(DEBUG_sched, cap, "message: wakeup thread %ld",
(lnat)tso->id);
ASSERT(tso->cap == cap);
ASSERT(tso->why_blocked == BlockedOnMsgWakeup);
ASSERT(tso->block_info.closure == (StgClosure *)m);
tso->why_blocked = NotBlocked;
appendToRunQueue(cap, tso);
}
else if (i == &stg_MSG_TRY_WAKEUP_info)
if (i == &stg_MSG_TRY_WAKEUP_info)
{
StgTSO *tso = ((MessageWakeup *)m)->tso;
debugTraceCap(DEBUG_sched, cap, "message: try wakeup thread %ld",
......
......@@ -1140,7 +1140,7 @@ stg_newMVarzh
stg_takeMVarzh
{
W_ mvar, val, info, tso;
W_ mvar, val, info, tso, q;
/* args: R1 = MVar closure */
mvar = R1;
......@@ -1159,72 +1159,85 @@ stg_takeMVarzh
* and wait until we're woken up.
*/
if (StgMVar_value(mvar) == stg_END_TSO_QUEUE_closure) {
// Note [mvar-heap-check] We want to do the heap check in the
// branch here, to avoid the conditional in the common case.
// However, we've already locked the MVar above, so we better
// be careful to unlock it again if the the heap check fails.
// Unfortunately we don't have an easy way to inject any code
// into the heap check generated by the code generator, so we
// have to do it in stg_gc_gen (see HeapStackCheck.cmm).
HP_CHK_GEN_TICKY(SIZEOF_StgMVarTSOQueue, R1_PTR, stg_takeMVarzh);
q = Hp - SIZEOF_StgMVarTSOQueue + WDS(1);
StgHeader_info(q) = stg_MVAR_TSO_QUEUE_info;
StgMVarTSOQueue_link(q) = END_TSO_QUEUE;
StgMVarTSOQueue_tso(q) = CurrentTSO;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_head(mvar) = CurrentTSO;
StgMVar_head(mvar) = q;
} else {
foreign "C" setTSOLink(MyCapability() "ptr",
StgMVar_tail(mvar) "ptr",
CurrentTSO) [];
StgMVarTSOQueue_link(StgMVar_tail(mvar)) = q;
foreign "C" recordClosureMutated(MyCapability() "ptr",
StgMVar_tail(mvar)) [];
}
StgTSO__link(CurrentTSO) = stg_END_TSO_QUEUE_closure;
StgTSO__link(CurrentTSO) = q;
StgTSO_block_info(CurrentTSO) = mvar;
// write barrier for throwTo(), which looks at block_info
// if why_blocked==BlockedOnMVar.
prim %write_barrier() [];
StgTSO_why_blocked(CurrentTSO) = BlockedOnMVar::I16;
StgMVar_tail(mvar) = CurrentTSO;
StgMVar_tail(mvar) = q;
R1 = mvar;
jump stg_block_takemvar;
}
/* we got the value... */
val = StgMVar_value(mvar);
if (StgMVar_head(mvar) != stg_END_TSO_QUEUE_closure)
{
/* There are putMVar(s) waiting...
* wake up the first thread on the queue
*/
ASSERT(StgTSO_why_blocked(StgMVar_head(mvar)) == BlockedOnMVar::I16);
/* actually perform the putMVar for the thread that we just woke up */
tso = StgMVar_head(mvar);
PerformPut(tso,StgMVar_value(mvar));
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
("ptr" tso) = foreign "C" unblockOne_(MyCapability() "ptr",
StgMVar_head(mvar) "ptr", 1) [];
StgMVar_head(mvar) = tso;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
unlockClosure(mvar, stg_MVAR_DIRTY_info);
RET_P(val);
}
else
{
/* No further putMVars, MVar is now empty */
StgMVar_value(mvar) = stg_END_TSO_QUEUE_closure;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
}
/* we got the value... */
val = StgMVar_value(mvar);
q = StgMVar_head(mvar);
loop:
if (q == stg_END_TSO_QUEUE_closure) {
/* No further putMVars, MVar is now empty */
StgMVar_value(mvar) = stg_END_TSO_QUEUE_closure;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
RET_P(val);
}
if (StgHeader_info(q) == stg_IND_info ||
StgHeader_info(q) == stg_MSG_NULL_info) {
q = StgInd_indirectee(q);
goto loop;
}
// There are putMVar(s) waiting... wake up the first thread on the queue
tso = StgMVarTSOQueue_tso(q);
ASSERT(StgTSO_why_blocked(tso) == BlockedOnMVar::I16);
ASSERT(StgTSO_block_info(tso) == mvar);
// actually perform the putMVar for the thread that we just woke up
PerformPut(tso,StgMVar_value(mvar));
StgMVar_head(mvar) = StgMVarTSOQueue_link(q);
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
// indicate that the putMVar has now completed:
StgTSO__link(tso) = stg_END_TSO_QUEUE_closure;
// no need to mark the TSO dirty, we have only written END_TSO_QUEUE.
RET_P(val);
}
foreign "C" tryWakeupThread(MyCapability() "ptr", tso) [];
unlockClosure(mvar, stg_MVAR_DIRTY_info);
RET_P(val);
}
stg_tryTakeMVarzh
{
W_ mvar, val, info, tso;
W_ mvar, val, info, tso, q;
/* args: R1 = MVar closure */
mvar = R1;
#if defined(THREADED_RTS)
......@@ -1232,7 +1245,10 @@ stg_tryTakeMVarzh
#else
info = GET_INFO(mvar);
#endif
/* If the MVar is empty, put ourselves on its blocking queue,
* and wait until we're woken up.
*/
if (StgMVar_value(mvar) == stg_END_TSO_QUEUE_closure) {
#if defined(THREADED_RTS)
unlockClosure(mvar, info);
......@@ -1242,51 +1258,56 @@ stg_tryTakeMVarzh
*/
RET_NP(0, stg_NO_FINALIZER_closure);
}
if (info == stg_MVAR_CLEAN_info) {
foreign "C" dirty_MVAR(BaseReg "ptr", mvar "ptr");
foreign "C" dirty_MVAR(BaseReg "ptr", mvar "ptr") [];
}
/* we got the value... */
val = StgMVar_value(mvar);
if (StgMVar_head(mvar) != stg_END_TSO_QUEUE_closure) {
/* There are putMVar(s) waiting...
* wake up the first thread on the queue
*/
ASSERT(StgTSO_why_blocked(StgMVar_head(mvar)) == BlockedOnMVar::I16);
/* actually perform the putMVar for the thread that we just woke up */
tso = StgMVar_head(mvar);
PerformPut(tso,StgMVar_value(mvar));
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
("ptr" tso) = foreign "C" unblockOne_(MyCapability() "ptr",
StgMVar_head(mvar) "ptr", 1) [];
StgMVar_head(mvar) = tso;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
q = StgMVar_head(mvar);
loop:
if (q == stg_END_TSO_QUEUE_closure) {
/* No further putMVars, MVar is now empty */
StgMVar_value(mvar) = stg_END_TSO_QUEUE_closure;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
RET_NP(1, val);
}
else
{
/* No further putMVars, MVar is now empty */
StgMVar_value(mvar) = stg_END_TSO_QUEUE_closure;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
if (StgHeader_info(q) == stg_IND_info ||
StgHeader_info(q) == stg_MSG_NULL_info) {
q = StgInd_indirectee(q);
goto loop;
}
RET_NP(1, val);
// There are putMVar(s) waiting... wake up the first thread on the queue
tso = StgMVarTSOQueue_tso(q);
ASSERT(StgTSO_why_blocked(tso) == BlockedOnMVar::I16);
ASSERT(StgTSO_block_info(tso) == mvar);
// actually perform the putMVar for the thread that we just woke up
PerformPut(tso,StgMVar_value(mvar));
StgMVar_head(mvar) = StgMVarTSOQueue_link(q);
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
// indicate that the putMVar has now completed:
StgTSO__link(tso) = stg_END_TSO_QUEUE_closure;
// no need to mark the TSO dirty, we have only written END_TSO_QUEUE.
foreign "C" tryWakeupThread(MyCapability() "ptr", tso) [];
unlockClosure(mvar, stg_MVAR_DIRTY_info);
RET_P(val);
}
stg_putMVarzh
{
W_ mvar, val, info, tso;
W_ mvar, val, info, tso, q;
/* args: R1 = MVar, R2 = value */
mvar = R1;
......@@ -1303,76 +1324,92 @@ stg_putMVarzh
}
if (StgMVar_value(mvar) != stg_END_TSO_QUEUE_closure) {
// see Note [mvar-heap-check] above
HP_CHK_GEN_TICKY(SIZEOF_StgMVarTSOQueue, R1_PTR, stg_putMVarzh);
q = Hp - SIZEOF_StgMVarTSOQueue + WDS(1);
StgHeader_info(q) = stg_MVAR_TSO_QUEUE_info;
StgMVarTSOQueue_link(q) = END_TSO_QUEUE;
StgMVarTSOQueue_tso(q) = CurrentTSO;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_head(mvar) = CurrentTSO;
StgMVar_head(mvar) = q;
} else {
foreign "C" setTSOLink(MyCapability() "ptr",
StgMVar_tail(mvar) "ptr",
CurrentTSO) [];
StgMVarTSOQueue_link(StgMVar_tail(mvar)) = q;
foreign "C" recordClosureMutated(MyCapability() "ptr",
StgMVar_tail(mvar)) [];
}
StgTSO__link(CurrentTSO) = stg_END_TSO_QUEUE_closure;
StgTSO__link(CurrentTSO) = q;
StgTSO_block_info(CurrentTSO) = mvar;
// write barrier for throwTo(), which looks at block_info
// if why_blocked==BlockedOnMVar.
prim %write_barrier() [];
StgTSO_why_blocked(CurrentTSO) = BlockedOnMVar::I16;
StgMVar_tail(mvar) = CurrentTSO;
StgMVar_tail(mvar) = q;
R1 = mvar;
R2 = val;
jump stg_block_putmvar;
}
if (StgMVar_head(mvar) != stg_END_TSO_QUEUE_closure) {
/* There are takeMVar(s) waiting: wake up the first one
*/
ASSERT(StgTSO_why_blocked(StgMVar_head(mvar)) == BlockedOnMVar::I16);
/* actually perform the takeMVar */
tso = StgMVar_head(mvar);
PerformTake(tso, val);
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
("ptr" tso) = foreign "C" unblockOne_(MyCapability() "ptr",
StgMVar_head(mvar) "ptr", 1) [];
StgMVar_head(mvar) = tso;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
unlockClosure(mvar, stg_MVAR_DIRTY_info);
jump %ENTRY_CODE(Sp(0));
}
else
{
q = StgMVar_head(mvar);
loop:
if (q == stg_END_TSO_QUEUE_closure) {
/* No further takes, the MVar is now full. */
StgMVar_value(mvar) = val;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
jump %ENTRY_CODE(Sp(0));
}
if (StgHeader_info(q) == stg_IND_info ||
StgHeader_info(q) == stg_MSG_NULL_info) {
q = StgInd_indirectee(q);
goto loop;
}
// There are takeMVar(s) waiting: wake up the first one
tso = StgMVarTSOQueue_tso(q);
ASSERT(StgTSO_why_blocked(tso) == BlockedOnMVar::I16);
ASSERT(StgTSO_block_info(tso) == mvar);
// actually perform the takeMVar
PerformTake(tso, val);
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
StgMVar_head(mvar) = StgMVarTSOQueue_link(q);
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
// indicate that the takeMVar has now completed:
StgTSO__link(tso) = stg_END_TSO_QUEUE_closure;
/* ToDo: yield afterward for better communication performance? */
foreign "C" tryWakeupThread(MyCapability() "ptr", tso) [];
unlockClosure(mvar, stg_MVAR_DIRTY_info);
jump %ENTRY_CODE(Sp(0));
}
stg_tryPutMVarzh
{
W_ mvar, info, tso;
W_ mvar, val, info, tso, q;
/* args: R1 = MVar, R2 = value */
mvar = R1;
val = R2;
#if defined(THREADED_RTS)
("ptr" info) = foreign "C" lockClosure(mvar "ptr") [R2];
("ptr" info) = foreign "C" lockClosure(mvar "ptr") [];
#else
info = GET_INFO(mvar);
#endif
if (info == stg_MVAR_CLEAN_info) {
foreign "C" dirty_MVAR(BaseReg "ptr", mvar "ptr");
}
if (StgMVar_value(mvar) != stg_END_TSO_QUEUE_closure) {
#if defined(THREADED_RTS)
unlockClosure(mvar, info);
......@@ -1380,43 +1417,46 @@ stg_tryPutMVarzh
RET_N(0);
}
if (info == stg_MVAR_CLEAN_info) {
foreign "C" dirty_MVAR(BaseReg "ptr", mvar "ptr");
}
if (StgMVar_head(mvar) != stg_END_TSO_QUEUE_closure) {
/* There are takeMVar(s) waiting: wake up the first one
*/
ASSERT(StgTSO_why_blocked(StgMVar_head(mvar)) == BlockedOnMVar::I16);
/* actually perform the takeMVar */
tso = StgMVar_head(mvar);
PerformTake(tso, R2);
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
("ptr" tso) = foreign "C" unblockOne_(MyCapability() "ptr",
StgMVar_head(mvar) "ptr", 1) [];
StgMVar_head(mvar) = tso;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
q = StgMVar_head(mvar);
loop:
if (q == stg_END_TSO_QUEUE_closure) {
/* No further takes, the MVar is now full. */
StgMVar_value(mvar) = val;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
jump %ENTRY_CODE(Sp(0));
}
if (StgHeader_info(q) == stg_IND_info ||
StgHeader_info(q) == stg_MSG_NULL_info) {
q = StgInd_indirectee(q);
goto loop;
}
else
{
/* No further takes, the MVar is now full. */
StgMVar_value(mvar) = R2;
unlockClosure(mvar, stg_MVAR_DIRTY_info);
/* There are takeMVar(s) waiting: wake up the first one
*/
// There are takeMVar(s) waiting: wake up the first one
tso = StgMVarTSOQueue_tso(q);
ASSERT(StgTSO_why_blocked(tso) == BlockedOnMVar::I16);
ASSERT(StgTSO_block_info(tso) == mvar);
// actually perform the takeMVar
PerformTake(tso, val);
if (TO_W_(StgTSO_dirty(tso)) == 0) {
foreign "C" dirty_TSO(MyCapability() "ptr", tso "ptr") [];
}
RET_N(1);
/* ToDo: yield afterward for better communication performance? */
StgMVar_head(mvar) = StgMVarTSOQueue_link(q);
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
StgMVar_tail(mvar) = stg_END_TSO_QUEUE_closure;
}
// indicate that the takeMVar has now completed:
StgTSO__link(tso) = stg_END_TSO_QUEUE_closure;
foreign "C" tryWakeupThread(MyCapability() "ptr", tso) [];
unlockClosure(mvar, stg_MVAR_DIRTY_info);
jump %ENTRY_CODE(Sp(0));