diff options
| -rw-r--r-- | rts/CheckUnload.c | 303 | ||||
| -rw-r--r-- | rts/CheckUnload.h | 20 | ||||
| -rw-r--r-- | rts/Linker.c | 96 | ||||
| -rw-r--r-- | rts/LinkerInternals.h | 12 | ||||
| -rw-r--r-- | rts/sm/GC.c | 5 |
5 files changed, 419 insertions, 17 deletions
diff --git a/rts/CheckUnload.c b/rts/CheckUnload.c new file mode 100644 index 0000000000..a758b06db3 --- /dev/null +++ b/rts/CheckUnload.c @@ -0,0 +1,303 @@ +/* ---------------------------------------------------------------------------- + * + * (c) The GHC Team, 2013- + * + * Check whether dynamically-loaded object code can be safely + * unloaded, by searching for references to it from the heap and RTS + * data structures. + * + * --------------------------------------------------------------------------*/ + +#include "PosixSource.h" +#include "Rts.h" + +#include "RtsUtils.h" +#include "Hash.h" +#include "LinkerInternals.h" +#include "CheckUnload.h" +#include "sm/Storage.h" +#include "sm/GCThread.h" + +// +// Code that we unload may be referenced from: +// - info pointers in heap objects and stack frames +// - pointers to static objects from the heap +// - StablePtrs to static objects +// +// We can find live static objects after a major GC, so we don't have +// to look at every closure pointer in the heap. However, we do have +// to look at every info pointer. So this is like a heap census +// traversal: we look at the header of every object, but not its +// contents. +// +// On the assumption that there aren't many different info pointers in +// a typical heap, we insert addresses into a hash table. The +// first time we see an address, we check it against the pending +// unloadable objects and if it lies within any of them, we mark that +// object as referenced so that it won't get unloaded in this round. +// + +static void checkAddress (HashTable *addrs, void *addr) +{ + ObjectCode *oc; + + if (!lookupHashTable(addrs, (W_)addr)) { + insertHashTable(addrs, (W_)addr, addr); + + for (oc = unloaded_objects; oc; oc = oc->next) { + if ((W_)addr >= (W_)oc->image && + (W_)addr < (W_)oc->image + oc->fileSize) { + oc->referenced = 1; + break; + } + } + } +} + +static void searchStackChunk (HashTable *addrs, StgPtr sp, StgPtr stack_end) +{ + StgPtr p; + const StgRetInfoTable *info; + + p = sp; + while (p < stack_end) { + info = get_ret_itbl((StgClosure *)p); + + switch (info->i.type) { + case RET_SMALL: + case RET_BIG: + checkAddress(addrs, (void*)info); + break; + + default: + break; + } + + p += stack_frame_sizeW((StgClosure*)p); + } +} + + +static void searchHeapBlocks (HashTable *addrs, bdescr *bd) +{ + StgPtr p; + StgInfoTable *info; + nat size; + rtsBool prim; + + for (; bd != NULL; bd = bd->link) { + + if (bd->flags & BF_PINNED) { + // Assume that objects in PINNED blocks cannot refer to + continue; + } + + p = bd->start; + while (p < bd->free) { + info = get_itbl((StgClosure *)p); + prim = rtsFalse; + + switch (info->type) { + + case THUNK: + size = thunk_sizeW_fromITBL(info); + break; + + case THUNK_1_1: + case THUNK_0_2: + case THUNK_2_0: + size = sizeofW(StgThunkHeader) + 2; + break; + + case THUNK_1_0: + case THUNK_0_1: + case THUNK_SELECTOR: + size = sizeofW(StgThunkHeader) + 1; + break; + + case CONSTR: + case FUN: + case FUN_1_0: + case FUN_0_1: + case FUN_1_1: + case FUN_0_2: + case FUN_2_0: + case CONSTR_1_0: + case CONSTR_0_1: + case CONSTR_1_1: + case CONSTR_0_2: + case CONSTR_2_0: + size = sizeW_fromITBL(info); + break; + + case IND_PERM: + case BLACKHOLE: + case BLOCKING_QUEUE: + prim = rtsTrue; + size = sizeW_fromITBL(info); + break; + + case IND: + // Special case/Delicate Hack: INDs don't normally + // appear, since we're doing this heap census right + // after GC. However, GarbageCollect() also does + // resurrectThreads(), which can update some + // blackholes when it calls raiseAsync() on the + // resurrected threads. So we know that any IND will + // be the size of a BLACKHOLE. + prim = rtsTrue; + size = BLACKHOLE_sizeW(); + break; + + case BCO: + prim = rtsTrue; + size = bco_sizeW((StgBCO *)p); + break; + + case MVAR_CLEAN: + case MVAR_DIRTY: + case TVAR: + case WEAK: + case PRIM: + case MUT_PRIM: + case MUT_VAR_CLEAN: + case MUT_VAR_DIRTY: + prim = rtsTrue; + size = sizeW_fromITBL(info); + break; + + case AP: + prim = rtsTrue; + size = ap_sizeW((StgAP *)p); + break; + + case PAP: + prim = rtsTrue; + size = pap_sizeW((StgPAP *)p); + break; + + case AP_STACK: + { + StgAP_STACK *ap = (StgAP_STACK *)p; + prim = rtsTrue; + size = ap_stack_sizeW(ap); + searchStackChunk(addrs, (StgPtr)ap->payload, + (StgPtr)ap->payload + ap->size); + break; + } + + case ARR_WORDS: + prim = rtsTrue; + size = arr_words_sizeW((StgArrWords*)p); + break; + + case MUT_ARR_PTRS_CLEAN: + case MUT_ARR_PTRS_DIRTY: + case MUT_ARR_PTRS_FROZEN: + case MUT_ARR_PTRS_FROZEN0: + prim = rtsTrue; + size = mut_arr_ptrs_sizeW((StgMutArrPtrs *)p); + break; + + case TSO: + prim = rtsTrue; + size = sizeofW(StgTSO); + break; + + case STACK: { + StgStack *stack = (StgStack*)p; + prim = rtsTrue; + searchStackChunk(addrs, stack->sp, + stack->stack + stack->stack_size); + size = stack_sizeW(stack); + break; + } + + case TREC_CHUNK: + prim = rtsTrue; + size = sizeofW(StgTRecChunk); + break; + + default: + barf("heapCensus, unknown object: %d", info->type); + } + + if (!prim) { + checkAddress(addrs,info); + } + + p += size; + } + } +} + +// +// Check whether we can unload any object code. This is called at the +// appropriate point during a GC, where all the heap data is nice and +// packed together and we have a linked list of the static objects. +// +// The check involves a complete heap traversal, but you only pay for +// this (a) when you have called unloadObj(), and (b) at a major GC, +// which is much more expensive than the traversal we're doing here. +// +void checkUnload (StgClosure *static_objects) +{ + nat g, n; + HashTable *addrs; + StgClosure* p; + const StgInfoTable *info; + ObjectCode *oc, *prev; + gen_workspace *ws; + StgClosure* link; + + if (unloaded_objects == NULL) return; + + // Mark every unloadable object as unreferenced initially + for (oc = unloaded_objects; oc; oc = oc->next) { + IF_DEBUG(linker, debugBelch("Checking whether to unload %s\n", + oc->fileName)); + oc->referenced = rtsFalse; + } + + addrs = allocHashTable(); + + for (p = static_objects; p != END_OF_STATIC_LIST; p = link) { + checkAddress(addrs, p); + info = get_itbl(p); + link = *STATIC_LINK(info, p); + } + + for (g = 0; g < RtsFlags.GcFlags.generations; g++) { + searchHeapBlocks (addrs, generations[g].blocks); + searchHeapBlocks (addrs, generations[g].large_objects); + + for (n = 0; n < n_capabilities; n++) { + ws = &gc_threads[n]->gens[g]; + searchHeapBlocks(addrs, ws->todo_bd); + searchHeapBlocks(addrs, ws->part_list); + searchHeapBlocks(addrs, ws->scavd_list); + } + } + + // Look through the unloadable objects, and any object that is still + // marked as unreferenced can be physically unloaded, because we + // have no references to it. + prev = NULL; + for (oc = unloaded_objects; oc; prev = oc, oc = oc->next) { + if (oc->referenced == 0) { + if (prev == NULL) { + unloaded_objects = oc->next; + } else { + prev->next = oc->next; + } + IF_DEBUG(linker, debugBelch("Unloading object file %s\n", + oc->fileName)); + freeObjectCode(oc); + } else { + IF_DEBUG(linker, debugBelch("Object file still in use: %s\n", + oc->fileName)); + } + } + + freeHashTable(addrs, NULL); +} diff --git a/rts/CheckUnload.h b/rts/CheckUnload.h new file mode 100644 index 0000000000..7d2e5b1321 --- /dev/null +++ b/rts/CheckUnload.h @@ -0,0 +1,20 @@ +/* ---------------------------------------------------------------------------- + * + * (c) The GHC Team, 2013- + * + * Check whether dynamically-loaded object code can be safely + * unloaded, by searching for references to it from the heap and RTS + * data structures. + * + * --------------------------------------------------------------------------*/ + +#ifndef CHECKUNLOAD_H +#define CHECKUNLOAD_H + +#include "BeginPrivate.h" + +void checkUnload (StgClosure *static_objects); + +#include "EndPrivate.h" + +#endif // CHECKUNLOAD_H diff --git a/rts/Linker.c b/rts/Linker.c index 0c7dfd2d40..81a267db1b 100644 --- a/rts/Linker.c +++ b/rts/Linker.c @@ -156,6 +156,10 @@ static /*Str*/HashTable *stablehash; /* List of currently loaded objects */ ObjectCode *objects = NULL; /* initially empty */ +/* List of objects that have been unloaded via unloadObj(), but are waiting + to be actually freed via checkUnload() */ +ObjectCode *unloaded_objects = NULL; /* initially empty */ + static HsInt loadOc( ObjectCode* oc ); static ObjectCode* mkOc( pathchar *path, char *image, int imageSize, char *archiveMemberName @@ -229,6 +233,8 @@ static void machoInitSymbolsWithoutUnderscore( void ); #endif #endif +static void freeProddableBlocks (ObjectCode *oc); + /* on x86_64 we have a problem with relocating symbol references in * code that was compiled without -fPIC. By default, the small memory * model is used, which assumes that symbol references can fit in a @@ -1518,6 +1524,9 @@ initLinker( void ) linker_init_done = 1; } + objects = NULL; + unloaded_objects = NULL; + #if defined(THREADED_RTS) && (defined(OBJFORMAT_ELF) || defined(OBJFORMAT_MACHO)) initMutex(&dl_mutex); #endif @@ -2044,6 +2053,40 @@ mmap_again: } #endif // USE_MMAP + +void freeObjectCode (ObjectCode *oc) +{ + int pagesize, size, r; + +#ifdef USE_MMAP + + pagesize = getpagesize(); + size = ROUND_UP(oc->fileSize, pagesize); + + r = munmap(oc->image, size); + if (r == -1) { + sysErrorBelch("munmap"); + } + + if (!USE_CONTIGUOUS_MMAP) + { + munmap(oc->symbol_extras, + ROUND_UP(sizeof(SymbolExtra) * oc->n_symbol_extras, pagesize)); + } + +#else + + stgFree(oc->image); + stgFree(oc->symbol_extras); + +#endif + + stgFree(oc->fileName); + stgFree(oc->archiveMemberName); + stgFree(oc); +} + + static ObjectCode* mkOc( pathchar *path, char *image, int imageSize, char *archiveMemberName @@ -2746,7 +2789,7 @@ resolveObjs( void ) HsInt unloadObj( pathchar *path ) { - ObjectCode *oc, *prev; + ObjectCode *oc, *prev, *next; HsBool unloadedAnyObj = HS_BOOL_FALSE; ASSERT(symhash != NULL); @@ -2754,8 +2797,12 @@ unloadObj( pathchar *path ) initLinker(); + IF_DEBUG(linker, debugBelch("unloadObj: %s\n", path)); + prev = NULL; - for (oc = objects; oc; prev = oc, oc = oc->next) { + for (oc = objects; oc; prev = oc, oc = next) { + next = oc->next; + if (!pathcmp(oc->fileName,path)) { /* Remove all the mappings for the symbols within this @@ -2775,22 +2822,27 @@ unloadObj( pathchar *path ) } else { prev->next = oc->next; } + oc->next = unloaded_objects; + unloaded_objects = oc; - // We're going to leave this in place, in case there are - // any pointers from the heap into it: - // #ifdef mingw32_HOST_OS - // If uncommenting, note that currently oc->image is - // not the right address to free on Win64, as we added - // 4 bytes of padding at the start - // VirtualFree(oc->image); - // #else - // stgFree(oc->image); - // #endif - stgFree(oc->fileName); - stgFree(oc->archiveMemberName); + // The data itself and a few other bits (oc->fileName, + // oc->archiveMemberName) are kept until freeObjectCode(), + // which is only called when it has been determined that + // it is safe to unload the object. stgFree(oc->symbols); - stgFree(oc->sections); - stgFree(oc); + + { + Section *s, *nexts; + + for (s = oc->sections; s != NULL; s = nexts) { + nexts = s->next; + stgFree(s); + } + } + + freeProddableBlocks(oc); + + oc->status = OBJECT_UNLOADED; /* This could be a member of an archive so continue * unloading other members. */ @@ -2840,6 +2892,17 @@ checkProddableBlock (ObjectCode *oc, void *addr, size_t size ) barf("checkProddableBlock: invalid fixup in runtime linker: %p", addr); } +static void freeProddableBlocks (ObjectCode *oc) +{ + ProddableBlock *pb, *next; + + for (pb = oc->proddables; pb != NULL; pb = next) { + next = pb->next; + stgFree(pb); + } + oc->proddables = NULL; +} + /* ----------------------------------------------------------------------------- * Section management. */ @@ -2928,6 +2991,7 @@ static int ocAllocateSymbolExtras( ObjectCode* oc, int count, int first ) memcpy(new, oc->image, oc->fileSize); munmap(oc->image, n); oc->image = new; + oc->fileSize = n + (sizeof(SymbolExtra) * count); oc->symbol_extras = (SymbolExtra *) (oc->image + n); } else diff --git a/rts/LinkerInternals.h b/rts/LinkerInternals.h index 864e0d1f2f..753279d547 100644 --- a/rts/LinkerInternals.h +++ b/rts/LinkerInternals.h @@ -9,7 +9,11 @@ #ifndef LINKERINTERNALS_H #define LINKERINTERNALS_H -typedef enum { OBJECT_LOADED, OBJECT_RESOLVED } OStatus; +typedef enum { + OBJECT_LOADED, + OBJECT_RESOLVED, + OBJECT_UNLOADED +} OStatus; /* Indication of section kinds for loaded objects. Needed by the GC for deciding whether or not a pointer on the stack @@ -82,6 +86,9 @@ typedef struct _ObjectCode { /* ptr to malloc'd lump of memory holding the obj file */ char* image; + /* flag used when deciding whether to unload an object file */ + int referenced; + #ifdef darwin_HOST_OS /* record by how much image has been deliberately misaligned after allocation, so that we can use realloc */ @@ -121,7 +128,10 @@ typedef struct _ObjectCode { ) extern ObjectCode *objects; +extern ObjectCode *unloaded_objects; void exitLinker( void ); +void freeObjectCode (ObjectCode *oc); + #endif /* LINKERINTERNALS_H */ diff --git a/rts/sm/GC.c b/rts/sm/GC.c index 28593f5c71..1b2cb12212 100644 --- a/rts/sm/GC.c +++ b/rts/sm/GC.c @@ -47,6 +47,7 @@ #include "RaiseAsync.h" #include "Papi.h" #include "Stable.h" +#include "CheckUnload.h" #include <string.h> // for memset() #include <unistd.h> @@ -661,6 +662,10 @@ GarbageCollect (nat collect_gen, resetNurseries(); + if (major_gc) { + checkUnload (gct->scavenged_static_objects); + } + // mark the garbage collected CAFs as dead #if 0 && defined(DEBUG) // doesn't work at the moment if (major_gc) { gcCAFs(); } |
