summaryrefslogtreecommitdiff
path: root/rts/StableName.c
diff options
context:
space:
mode:
authorDavid Feuer <david.feuer@gmail.com>2018-08-29 16:34:21 -0400
committerDavid Feuer <David.Feuer@gmail.com>2018-08-29 16:34:22 -0400
commitf48e276a5ba68d8b6fcb4a558022581fb30f9326 (patch)
tree8ea324b84d137d59cda566d6a559016d2c7437ea /rts/StableName.c
parent65eec9cfd4410c0e30b0ed06116c15f8ce3de49d (diff)
downloadhaskell-f48e276a5ba68d8b6fcb4a558022581fb30f9326.tar.gz
Finish stable split
Long ago, the stable name table and stable pointer tables were one. Now, they are separate, and have significantly different implementations. I believe the time has come to finish the split that began in #7674. * Divide `rts/Stable` into `rts/StableName` and `rts/StablePtr`. * Give each table its own mutex. * Add FFI functions `hs_lock_stable_ptr_table` and `hs_unlock_stable_ptr_table` and document them. These are intended to replace the previously undocumented `hs_lock_stable_tables` and `hs_lock_stable_tables`, which are now documented as deprecated synonyms. * Make `eqStableName#` use pointer equality instead of unnecessarily comparing stable name table indices. Reviewers: simonmar, bgamari, erikd Reviewed By: bgamari Subscribers: rwbarton, carter GHC Trac Issues: #15555 Differential Revision: https://phabricator.haskell.org/D5084
Diffstat (limited to 'rts/StableName.c')
-rw-r--r--rts/StableName.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/rts/StableName.c b/rts/StableName.c
new file mode 100644
index 0000000000..abe7b692e0
--- /dev/null
+++ b/rts/StableName.c
@@ -0,0 +1,364 @@
+/* -*- tab-width: 4 -*- */
+
+/* -----------------------------------------------------------------------------
+ *
+ * (c) The GHC Team, 1998-2002
+ *
+ * Stable names
+ *
+ * ---------------------------------------------------------------------------*/
+
+#include "PosixSource.h"
+#include "Rts.h"
+#include "RtsAPI.h"
+
+#include "Hash.h"
+#include "RtsUtils.h"
+#include "Trace.h"
+#include "StableName.h"
+
+#include <string.h>
+
+snEntry *stable_name_table = NULL;
+static snEntry *stable_name_free = NULL;
+static unsigned int SNT_size = 0;
+#define INIT_SNT_SIZE 64
+
+#if defined(THREADED_RTS)
+Mutex stable_name_mutex;
+#endif
+
+static void enlargeStableNameTable(void);
+
+/*
+ * 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.
+ */
+
+static HashTable *addrToStableHash = NULL;
+
+void
+stableNameLock(void)
+{
+ initStableNameTable();
+ ACQUIRE_LOCK(&stable_name_mutex);
+}
+
+void
+stableNameUnlock(void)
+{
+ RELEASE_LOCK(&stable_name_mutex);
+}
+
+/* -----------------------------------------------------------------------------
+ * Initialising the table
+ * -------------------------------------------------------------------------- */
+
+STATIC_INLINE void
+initSnEntryFreeList(snEntry *table, uint32_t n, snEntry *free)
+{
+ snEntry *p;
+ for (p = table + n - 1; p >= table; p--) {
+ p->addr = (P_)free;
+ p->old = NULL;
+ p->sn_obj = NULL;
+ free = p;
+ }
+ stable_name_free = table;
+}
+
+void
+initStableNameTable(void)
+{
+ if (SNT_size > 0) return;
+ SNT_size = INIT_SNT_SIZE;
+ stable_name_table = stgMallocBytes(SNT_size * sizeof(snEntry),
+ "initStableNameTable");
+ /* 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.
+ */
+ initSnEntryFreeList(stable_name_table + 1,INIT_SNT_SIZE-1,NULL);
+ addrToStableHash = allocHashTable();
+
+#if defined(THREADED_RTS)
+ initMutex(&stable_name_mutex);
+#endif
+}
+
+/* -----------------------------------------------------------------------------
+ * Enlarging the tables
+ * -------------------------------------------------------------------------- */
+
+static void
+enlargeStableNameTable(void)
+{
+ uint32_t old_SNT_size = SNT_size;
+
+ // 2nd and subsequent times
+ SNT_size *= 2;
+ stable_name_table =
+ stgReallocBytes(stable_name_table,
+ SNT_size * sizeof(snEntry),
+ "enlargeStableNameTable");
+
+ initSnEntryFreeList(stable_name_table + old_SNT_size, old_SNT_size, NULL);
+}
+
+/* Note [Enlarging the stable pointer table]
+ *
+ * To enlarge the stable pointer table, we allocate a new table, copy the
+ * existing entries, and then store the old version of the table in old_SPTs
+ * until we free it during GC. By not immediately freeing the old version
+ * (or equivalently by not growing the table using realloc()), we ensure that
+ * another thread simultaneously dereferencing a stable pointer using the old
+ * version can safely access the table without causing a segfault (see Trac
+ * #10296).
+ *
+ * Note that because the stable pointer table is doubled in size each time it is
+ * enlarged, the total memory needed to store the old versions is always less
+ * than that required to hold the current version.
+ */
+
+
+/* -----------------------------------------------------------------------------
+ * Freeing entries and tables
+ * -------------------------------------------------------------------------- */
+
+void
+exitStableNameTable(void)
+{
+ if (addrToStableHash)
+ freeHashTable(addrToStableHash, NULL);
+ addrToStableHash = NULL;
+
+ if (stable_name_table)
+ stgFree(stable_name_table);
+ stable_name_table = NULL;
+ SNT_size = 0;
+
+#if defined(THREADED_RTS)
+ closeMutex(&stable_name_mutex);
+#endif
+}
+
+STATIC_INLINE void
+freeSnEntry(snEntry *sn)
+{
+ ASSERT(sn->sn_obj == NULL);
+ removeHashTable(addrToStableHash, (W_)sn->old, NULL);
+ sn->addr = (P_)stable_name_free;
+ stable_name_free = sn;
+}
+
+/* -----------------------------------------------------------------------------
+ * Looking up
+ * -------------------------------------------------------------------------- */
+
+/*
+ * get at the real stuff...remove indirections.
+ */
+static StgClosure*
+removeIndirections (StgClosure* p)
+{
+ StgClosure* q;
+
+ while (1)
+ {
+ q = UNTAG_CLOSURE(p);
+
+ switch (get_itbl(q)->type) {
+ case IND:
+ case IND_STATIC:
+ p = ((StgInd *)q)->indirectee;
+ continue;
+
+ case BLACKHOLE:
+ p = ((StgInd *)q)->indirectee;
+ if (GET_CLOSURE_TAG(p) != 0) {
+ continue;
+ } else {
+ break;
+ }
+
+ default:
+ break;
+ }
+ return p;
+ }
+}
+
+StgWord
+lookupStableName (StgPtr p)
+{
+ stableNameLock();
+
+ if (stable_name_free == NULL) {
+ enlargeStableNameTable();
+ }
+
+ /* removing indirections increases the likelihood
+ * of finding a match in the stable name hash table.
+ */
+ p = (StgPtr)removeIndirections((StgClosure*)p);
+
+ // register the untagged pointer. This just makes things simpler.
+ p = (StgPtr)UNTAG_CLOSURE((StgClosure*)p);
+
+ StgWord sn = (StgWord)lookupHashTable(addrToStableHash,(W_)p);
+
+ if (sn != 0) {
+ ASSERT(stable_name_table[sn].addr == p);
+ debugTrace(DEBUG_stable, "cached stable name %ld at %p",sn,p);
+ stableNameUnlock();
+ return sn;
+ }
+
+ sn = stable_name_free - stable_name_table;
+ stable_name_free = (snEntry*)(stable_name_free->addr);
+ stable_name_table[sn].addr = p;
+ stable_name_table[sn].sn_obj = NULL;
+ /* debugTrace(DEBUG_stable, "new stable name %d at %p\n",sn,p); */
+
+ /* add the new stable name to the hash table */
+ insertHashTable(addrToStableHash, (W_)p, (void *)sn);
+
+ stableNameUnlock();
+
+ return sn;
+}
+
+/* -----------------------------------------------------------------------------
+ * Remember old stable name addresses
+ * -------------------------------------------------------------------------- */
+
+#define FOR_EACH_STABLE_NAME(p, CODE) \
+ do { \
+ snEntry *p; \
+ snEntry *__end_ptr = &stable_name_table[SNT_size]; \
+ for (p = stable_name_table + 1; p < __end_ptr; p++) { \
+ /* Internal pointers are free slots. */ \
+ /* If p->addr == NULL, it's a */ \
+ /* stable name where the object has been GC'd, but the */ \
+ /* StableName object (sn_obj) is still alive. */ \
+ if ((p->addr < (P_)stable_name_table || \
+ p->addr >= (P_)__end_ptr)) \
+ { \
+ /* NOTE: There is an ambiguity here if p->addr == NULL */ \
+ /* it is either the last item in the free list or it */ \
+ /* is a stable name whose pointee died. sn_obj == NULL */ \
+ /* disambiguates as last free list item. */ \
+ do { CODE } while(0); \
+ } \
+ } \
+ } while(0)
+
+void
+rememberOldStableNameAddresses(void)
+{
+ /* TODO: Only if !full GC */
+ FOR_EACH_STABLE_NAME(p, p->old = 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 tables, because the compacting
+ * collector may move the object it points to.
+ * -------------------------------------------------------------------------- */
+
+void
+threadStableNameTable( evac_fn evac, void *user )
+{
+ FOR_EACH_STABLE_NAME(p, {
+ if (p->sn_obj != NULL) {
+ evac(user, (StgClosure **)&p->sn_obj);
+ }
+ if (p->addr != NULL) {
+ evac(user, (StgClosure **)&p->addr);
+ }
+ });
+}
+
+/* -----------------------------------------------------------------------------
+ * Garbage collect any dead entries in the stable name 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
+gcStableNameTable( void )
+{
+ FOR_EACH_STABLE_NAME(
+ p, {
+ // FOR_EACH_STABLE_NAME traverses free entries too, so
+ // check sn_obj
+ if (p->sn_obj != NULL) {
+ // Update the pointer to the StableName object, if there is one
+ p->sn_obj = isAlive(p->sn_obj);
+ if (p->sn_obj == NULL) {
+ // StableName object died
+ debugTrace(DEBUG_stable, "GC'd StableName %ld (addr=%p)",
+ (long)(p - stable_name_table), p->addr);
+ freeSnEntry(p);
+ } else if (p->addr != NULL) {
+ // sn_obj is alive, update pointee
+ p->addr = (StgPtr)isAlive((StgClosure *)p->addr);
+ if (p->addr == NULL) {
+ // Pointee died
+ debugTrace(DEBUG_stable, "GC'd pointee %ld",
+ (long)(p - stable_name_table));
+ }
+ }
+ }
+ });
+}
+
+/* -----------------------------------------------------------------------------
+ * Update the 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
+updateStableNameTable(bool full)
+{
+ if (full && addrToStableHash != NULL && 0 != keyCountHashTable(addrToStableHash)) {
+ freeHashTable(addrToStableHash,NULL);
+ addrToStableHash = allocHashTable();
+ }
+
+ if(full) {
+ FOR_EACH_STABLE_NAME(
+ p, {
+ if (p->addr != NULL) {
+ // Target still alive, Re-hash this stable name
+ insertHashTable(addrToStableHash, (W_)p->addr, (void *)(p - stable_name_table));
+ }
+ });
+ } else {
+ FOR_EACH_STABLE_NAME(
+ p, {
+ if (p->addr != p->old) {
+ removeHashTable(addrToStableHash, (W_)p->old, NULL);
+ /* Movement happened: */
+ if (p->addr != NULL) {
+ insertHashTable(addrToStableHash, (W_)p->addr, (void *)(p - stable_name_table));
+ }
+ }
+ });
+ }
+}