summaryrefslogtreecommitdiff
path: root/rts/Stable.c
diff options
context:
space:
mode:
Diffstat (limited to 'rts/Stable.c')
-rw-r--r--rts/Stable.c460
1 files changed, 460 insertions, 0 deletions
diff --git a/rts/Stable.c b/rts/Stable.c
new file mode 100644
index 0000000000..a4db5cd749
--- /dev/null
+++ b/rts/Stable.c
@@ -0,0 +1,460 @@
+/* -----------------------------------------------------------------------------
+ *
+ * (c) The GHC Team, 1998-2002
+ *
+ * Stable names and stable pointers.
+ *
+ * ---------------------------------------------------------------------------*/
+
+// Make static versions of inline functions in Stable.h:
+#define RTS_STABLE_C
+
+#include "PosixSource.h"
+#include "Rts.h"
+#include "Hash.h"
+#include "RtsUtils.h"
+#include "OSThreads.h"
+#include "Storage.h"
+#include "RtsAPI.h"
+#include "RtsFlags.h"
+#include "OSThreads.h"
+
+/* Comment from ADR's implementation in old RTS:
+
+ This files (together with @ghc/runtime/storage/PerformIO.lhc@ and a
+ small change in @HpOverflow.lc@) consists of the changes in the
+ runtime system required to implement "Stable Pointers". But we're
+ getting a bit ahead of ourselves --- what is a stable pointer and what
+ is it used for?
+
+ When Haskell calls C, it normally just passes over primitive integers,
+ floats, bools, strings, etc. This doesn't cause any problems at all
+ for garbage collection because the act of passing them makes a copy
+ from the heap, stack or wherever they are onto the C-world stack.
+ However, if we were to pass a heap object such as a (Haskell) @String@
+ and a garbage collection occured before we finished using it, we'd run
+ into problems since the heap object might have been moved or even
+ deleted.
+
+ So, if a C call is able to cause a garbage collection or we want to
+ store a pointer to a heap object between C calls, we must be careful
+ when passing heap objects. Our solution is to keep a table of all
+ objects we've given to the C-world and to make sure that the garbage
+ collector collects these objects --- updating the table as required to
+ make sure we can still find the object.
+
+
+ Of course, all this rather begs the question: why would we want to
+ pass a boxed value?
+
+ One very good reason is to preserve laziness across the language
+ interface. Rather than evaluating an integer or a string because it
+ {\em might\/} be required by the C function, we can wait until the C
+ function actually wants the value and then force an evaluation.
+
+ Another very good reason (the motivating reason!) is that the C code
+ might want to execute an object of sort $IO ()$ for the side-effects
+ it will produce. For example, this is used when interfacing to an X
+ widgets library to allow a direct implementation of callbacks.
+
+
+ The @makeStablePointer :: a -> IO (StablePtr a)@ function
+ converts a value into a stable pointer. It is part of the @PrimIO@
+ monad, because we want to be sure we don't allocate one twice by
+ accident, and then only free one of the copies.
+
+ \begin{verbatim}
+ makeStablePtr# :: a -> State# RealWorld -> (# RealWorld, a #)
+ freeStablePtr# :: StablePtr# a -> State# RealWorld -> State# RealWorld
+ deRefStablePtr# :: StablePtr# a -> State# RealWorld ->
+ (# State# RealWorld, a #)
+ \end{verbatim}
+
+ There may be additional functions on the C side to allow evaluation,
+ application, etc of a stable pointer.
+
+*/
+
+snEntry *stable_ptr_table = NULL;
+static snEntry *stable_ptr_free = NULL;
+
+static unsigned int SPT_size = 0;
+
+#ifdef THREADED_RTS
+static Mutex stable_mutex;
+#endif
+
+/* This hash table maps Haskell objects to stable names, so that every
+ * call to lookupStableName on a given object will return the same
+ * stable name.
+ *
+ * OLD COMMENTS about reference counting follow. The reference count
+ * in a stable name entry is now just a counter.
+ *
+ * Reference counting
+ * ------------------
+ * A plain stable name entry has a zero reference count, which means
+ * the entry will dissappear when the object it points to is
+ * unreachable. For stable pointers, we need an entry that sticks
+ * around and keeps the object it points to alive, so each stable name
+ * entry has an associated reference count.
+ *
+ * A stable pointer has a weighted reference count N attached to it
+ * (actually in its upper 5 bits), which represents the weight
+ * 2^(N-1). The stable name entry keeps a 32-bit reference count, which
+ * represents any weight between 1 and 2^32 (represented as zero).
+ * When the weight is 2^32, the stable name table owns "all" of the
+ * stable pointers to this object, and the entry can be garbage
+ * collected if the object isn't reachable.
+ *
+ * A new stable pointer is given the weight log2(W/2), where W is the
+ * weight stored in the table entry. The new weight in the table is W
+ * - 2^log2(W/2).
+ *
+ * A stable pointer can be "split" into two stable pointers, by
+ * dividing the weight by 2 and giving each pointer half.
+ * When freeing a stable pointer, the weight of the pointer is added
+ * to the weight stored in the table entry.
+ * */
+
+static HashTable *addrToStableHash = NULL;
+
+#define INIT_SPT_SIZE 64
+
+STATIC_INLINE void
+initFreeList(snEntry *table, nat n, snEntry *free)
+{
+ snEntry *p;
+
+ for (p = table + n - 1; p >= table; p--) {
+ p->addr = (P_)free;
+ p->old = NULL;
+ p->ref = 0;
+ p->sn_obj = NULL;
+ free = p;
+ }
+ stable_ptr_free = table;
+}
+
+void
+initStablePtrTable(void)
+{
+ if (SPT_size > 0)
+ return;
+
+ SPT_size = INIT_SPT_SIZE;
+ stable_ptr_table = stgMallocBytes(SPT_size * sizeof(snEntry),
+ "initStablePtrTable");
+
+ /* we don't use index 0 in the stable name table, because that
+ * would conflict with the hash table lookup operations which
+ * return NULL if an entry isn't found in the hash table.
+ */
+ initFreeList(stable_ptr_table+1,INIT_SPT_SIZE-1,NULL);
+ addrToStableHash = allocHashTable();
+
+#ifdef THREADED_RTS
+ initMutex(&stable_mutex);
+#endif
+}
+
+/*
+ * get at the real stuff...remove indirections.
+ *
+ * ToDo: move to a better home.
+ */
+static
+StgClosure*
+removeIndirections(StgClosure* p)
+{
+ StgClosure* q = p;
+
+ while (get_itbl(q)->type == IND ||
+ get_itbl(q)->type == IND_STATIC ||
+ get_itbl(q)->type == IND_OLDGEN ||
+ get_itbl(q)->type == IND_PERM ||
+ get_itbl(q)->type == IND_OLDGEN_PERM ) {
+ q = ((StgInd *)q)->indirectee;
+ }
+ return q;
+}
+
+static StgWord
+lookupStableName_(StgPtr p)
+{
+ StgWord sn;
+ void* sn_tmp;
+
+ if (stable_ptr_free == NULL) {
+ enlargeStablePtrTable();
+ }
+
+ /* removing indirections increases the likelihood
+ * of finding a match in the stable name hash table.
+ */
+ p = (StgPtr)removeIndirections((StgClosure*)p);
+
+ sn_tmp = lookupHashTable(addrToStableHash,(W_)p);
+ sn = (StgWord)sn_tmp;
+
+ if (sn != 0) {
+ ASSERT(stable_ptr_table[sn].addr == p);
+ IF_DEBUG(stable,debugBelch("cached stable name %ld at %p\n",sn,p));
+ return sn;
+ } else {
+ sn = stable_ptr_free - stable_ptr_table;
+ stable_ptr_free = (snEntry*)(stable_ptr_free->addr);
+ stable_ptr_table[sn].ref = 0;
+ stable_ptr_table[sn].addr = p;
+ stable_ptr_table[sn].sn_obj = NULL;
+ /* IF_DEBUG(stable,debugBelch("new stable name %d at %p\n",sn,p)); */
+
+ /* add the new stable name to the hash table */
+ insertHashTable(addrToStableHash, (W_)p, (void *)sn);
+
+ return sn;
+ }
+}
+
+StgWord
+lookupStableName(StgPtr p)
+{
+ StgWord res;
+
+ initStablePtrTable();
+ ACQUIRE_LOCK(&stable_mutex);
+ res = lookupStableName_(p);
+ RELEASE_LOCK(&stable_mutex);
+ return res;
+}
+
+STATIC_INLINE void
+freeStableName(snEntry *sn)
+{
+ ASSERT(sn->sn_obj == NULL);
+ if (sn->addr != NULL) {
+ removeHashTable(addrToStableHash, (W_)sn->addr, NULL);
+ }
+ sn->addr = (P_)stable_ptr_free;
+ stable_ptr_free = sn;
+}
+
+StgStablePtr
+getStablePtr(StgPtr p)
+{
+ StgWord sn;
+
+ initStablePtrTable();
+ ACQUIRE_LOCK(&stable_mutex);
+ sn = lookupStableName_(p);
+ stable_ptr_table[sn].ref++;
+ RELEASE_LOCK(&stable_mutex);
+ return (StgStablePtr)(sn);
+}
+
+void
+freeStablePtr(StgStablePtr sp)
+{
+ snEntry *sn;
+
+ initStablePtrTable();
+ ACQUIRE_LOCK(&stable_mutex);
+
+ sn = &stable_ptr_table[(StgWord)sp];
+
+ ASSERT((StgWord)sp < SPT_size && sn->addr != NULL && sn->ref > 0);
+
+ sn->ref--;
+
+ // If this entry has no StableName attached, then just free it
+ // immediately. This is important; it might be a while before the
+ // next major GC which actually collects the entry.
+ if (sn->sn_obj == NULL && sn->ref == 0) {
+ freeStableName(sn);
+ }
+
+ RELEASE_LOCK(&stable_mutex);
+}
+
+void
+enlargeStablePtrTable(void)
+{
+ nat old_SPT_size = SPT_size;
+
+ // 2nd and subsequent times
+ SPT_size *= 2;
+ stable_ptr_table =
+ stgReallocBytes(stable_ptr_table,
+ SPT_size * sizeof(snEntry),
+ "enlargeStablePtrTable");
+
+ initFreeList(stable_ptr_table + old_SPT_size, old_SPT_size, NULL);
+}
+
+/* -----------------------------------------------------------------------------
+ * Treat stable pointers as roots for the garbage collector.
+ *
+ * A stable pointer is any stable name entry with a ref > 0. We'll
+ * take the opportunity to zero the "keep" flags at the same time.
+ * -------------------------------------------------------------------------- */
+
+void
+markStablePtrTable(evac_fn evac)
+{
+ snEntry *p, *end_stable_ptr_table;
+ StgPtr q;
+
+ end_stable_ptr_table = &stable_ptr_table[SPT_size];
+
+ // Mark all the stable *pointers* (not stable names).
+ // _starting_ at index 1; index 0 is unused.
+ for (p = stable_ptr_table+1; p < end_stable_ptr_table; p++) {
+ q = p->addr;
+
+ // Internal pointers are free slots. If q == NULL, it's a
+ // stable name where the object has been GC'd, but the
+ // StableName object (sn_obj) is still alive.
+ if (q && (q < (P_)stable_ptr_table || q >= (P_)end_stable_ptr_table)) {
+
+ // save the current addr away: we need to be able to tell
+ // whether the objects moved in order to be able to update
+ // the hash table later.
+ p->old = p->addr;
+
+ // if the ref is non-zero, treat addr as a root
+ if (p->ref != 0) {
+ evac((StgClosure **)&p->addr);
+ }
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Thread the stable pointer table for compacting GC.
+ *
+ * Here we must call the supplied evac function for each pointer into
+ * the heap from the stable pointer table, because the compacting
+ * collector may move the object it points to.
+ * -------------------------------------------------------------------------- */
+
+void
+threadStablePtrTable( evac_fn evac )
+{
+ snEntry *p, *end_stable_ptr_table;
+ StgPtr q;
+
+ end_stable_ptr_table = &stable_ptr_table[SPT_size];
+
+ for (p = stable_ptr_table+1; p < end_stable_ptr_table; p++) {
+
+ if (p->sn_obj != NULL) {
+ evac((StgClosure **)&p->sn_obj);
+ }
+
+ q = p->addr;
+ if (q && (q < (P_)stable_ptr_table || q >= (P_)end_stable_ptr_table)) {
+ evac((StgClosure **)&p->addr);
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Garbage collect any dead entries in the stable pointer table.
+ *
+ * A dead entry has:
+ *
+ * - a zero reference count
+ * - a dead sn_obj
+ *
+ * Both of these conditions must be true in order to re-use the stable
+ * name table entry. We can re-use stable name table entries for live
+ * heap objects, as long as the program has no StableName objects that
+ * refer to the entry.
+ * -------------------------------------------------------------------------- */
+
+void
+gcStablePtrTable( void )
+{
+ snEntry *p, *end_stable_ptr_table;
+ StgPtr q;
+
+ end_stable_ptr_table = &stable_ptr_table[SPT_size];
+
+ // NOTE: _starting_ at index 1; index 0 is unused.
+ for (p = stable_ptr_table + 1; p < end_stable_ptr_table; p++) {
+
+ // Update the pointer to the StableName object, if there is one
+ if (p->sn_obj != NULL) {
+ p->sn_obj = isAlive(p->sn_obj);
+ }
+
+ // Internal pointers are free slots. If q == NULL, it's a
+ // stable name where the object has been GC'd, but the
+ // StableName object (sn_obj) is still alive.
+ q = p->addr;
+ if (q && (q < (P_)stable_ptr_table || q >= (P_)end_stable_ptr_table)) {
+
+ // StableNames only:
+ if (p->ref == 0) {
+ if (p->sn_obj == NULL) {
+ // StableName object is dead
+ freeStableName(p);
+ IF_DEBUG(stable, debugBelch("GC'd Stable name %ld\n",
+ p - stable_ptr_table));
+ continue;
+
+ } else {
+ p->addr = (StgPtr)isAlive((StgClosure *)p->addr);
+ IF_DEBUG(stable, debugBelch("Stable name %ld still alive at %p, ref %ld\n", p - stable_ptr_table, p->addr, p->ref));
+ }
+ }
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Update the StablePtr/StableName hash table
+ *
+ * The boolean argument 'full' indicates that a major collection is
+ * being done, so we might as well throw away the hash table and build
+ * a new one. For a minor collection, we just re-hash the elements
+ * that changed.
+ * -------------------------------------------------------------------------- */
+
+void
+updateStablePtrTable(rtsBool full)
+{
+ snEntry *p, *end_stable_ptr_table;
+
+ if (full && addrToStableHash != NULL) {
+ freeHashTable(addrToStableHash,NULL);
+ addrToStableHash = allocHashTable();
+ }
+
+ end_stable_ptr_table = &stable_ptr_table[SPT_size];
+
+ // NOTE: _starting_ at index 1; index 0 is unused.
+ for (p = stable_ptr_table + 1; p < end_stable_ptr_table; p++) {
+
+ if (p->addr == NULL) {
+ if (p->old != NULL) {
+ // The target has been garbage collected. Remove its
+ // entry from the hash table.
+ removeHashTable(addrToStableHash, (W_)p->old, NULL);
+ p->old = NULL;
+ }
+ }
+ else if (p->addr < (P_)stable_ptr_table
+ || p->addr >= (P_)end_stable_ptr_table) {
+ // Target still alive, Re-hash this stable name
+ if (full) {
+ insertHashTable(addrToStableHash, (W_)p->addr,
+ (void *)(p - stable_ptr_table));
+ } else if (p->addr != p->old) {
+ removeHashTable(addrToStableHash, (W_)p->old, NULL);
+ insertHashTable(addrToStableHash, (W_)p->addr,
+ (void *)(p - stable_ptr_table));
+ }
+ }
+ }
+}