+++ /dev/null
-/* -----------------------------------------------------------------------------
- *
- * (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));
- }
- }
- }
-}