X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=ghc%2Frts%2FStorage.c;h=1e537583b8e5482f6dbbb090d49122d1f2358cd3;hb=76a51a41211a151d68a90e8dd732aeea1da17847;hp=fc3c409af6942e3ee5879f28cd2cc10b2ab3339a;hpb=5c67176de89fee19a02056216a7c58579e765148;p=ghc-hetmet.git diff --git a/ghc/rts/Storage.c b/ghc/rts/Storage.c index fc3c409..1e53758 100644 --- a/ghc/rts/Storage.c +++ b/ghc/rts/Storage.c @@ -1,5 +1,5 @@ /* ----------------------------------------------------------------------------- - * $Id: Storage.c,v 1.19 1999/10/13 16:39:23 simonmar Exp $ + * $Id: Storage.c,v 1.42 2001/07/24 16:36:43 simonmar Exp $ * * (c) The GHC Team, 1998-1999 * @@ -14,15 +14,16 @@ #include "Hooks.h" #include "BlockAlloc.h" #include "MBlock.h" -#include "gmp.h" #include "Weak.h" #include "Sanity.h" #include "Storage.h" +#include "Schedule.h" #include "StoragePriv.h" -bdescr *current_nursery; /* next available nursery block, or NULL */ +#ifndef SMP nat nursery_blocks; /* number of blocks in the nursery */ +#endif StgClosure *caf_list = NULL; @@ -39,6 +40,16 @@ generation *g0; /* generation 0, for convenience */ generation *oldest_gen; /* oldest generation, for convenience */ step *g0s0; /* generation 0, step 0, for convenience */ +lnat total_allocated = 0; /* total memory allocated during run */ + +/* + * Storage manager mutex: protects all the above state from + * simultaneous access by two STG threads. + */ +#ifdef SMP +pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + /* * Forward references */ @@ -50,13 +61,17 @@ void initStorage (void) { nat g, s; - step *step; + step *stp; generation *gen; /* If we're doing heap profiling, we want a two-space heap with a * fixed-size allocation area so that we get roughly even-spaced * samples. */ + + /* As an experiment, try a 2 generation collector + */ + #if defined(PROFILING) || defined(DEBUG) if (RtsFlags.ProfFlags.doHeapProfile) { RtsFlags.GcFlags.generations = 1; @@ -119,19 +134,21 @@ initStorage (void) /* Initialise all steps */ for (g = 0; g < RtsFlags.GcFlags.generations; g++) { for (s = 0; s < generations[g].n_steps; s++) { - step = &generations[g].steps[s]; - step->no = s; - step->blocks = NULL; - step->n_blocks = 0; - step->gen = &generations[g]; - step->hp = NULL; - step->hpLim = NULL; - step->hp_bd = NULL; - step->scan = NULL; - step->scan_bd = NULL; - step->large_objects = NULL; - step->new_large_objects = NULL; - step->scavenged_large_objects = NULL; + stp = &generations[g].steps[s]; + stp->no = s; + stp->blocks = NULL; + stp->n_blocks = 0; + stp->gen = &generations[g]; + stp->gen_no = g; + stp->hp = NULL; + stp->hpLim = NULL; + stp->hp_bd = NULL; + stp->scan = NULL; + stp->scan_bd = NULL; + stp->large_objects = NULL; + stp->new_large_objects = NULL; + stp->scavenged_large_objects = NULL; + stp->is_compacted = 0; } } @@ -143,8 +160,10 @@ initStorage (void) generations[g].steps[s].to = &generations[g+1].steps[0]; } - /* The oldest generation has one step and its destination is the - * same step. */ + /* The oldest generation has one step and it is compacted. */ + if (RtsFlags.GcFlags.compact) { + oldest_gen->steps[0].is_compacted = 1; + } oldest_gen->steps[0].to = &oldest_gen->steps[0]; /* generation 0 is special: that's the nursery */ @@ -156,14 +175,9 @@ initStorage (void) * don't want it to be a big one. This vague idea is borne out by * rigorous experimental evidence. */ - step = &generations[0].steps[0]; - g0s0 = step; - nursery_blocks = RtsFlags.GcFlags.minAllocAreaSize; - step->blocks = allocNursery(NULL, nursery_blocks); - step->n_blocks = nursery_blocks; - current_nursery = step->blocks; - g0s0->to_space = NULL; - /* hp, hpLim, hp_bd, to_space etc. aren't used in G0S0 */ + g0s0 = &generations[0].steps[0]; + + allocNurseries(); weak_ptr_list = NULL; caf_list = NULL; @@ -174,15 +188,166 @@ initStorage (void) alloc_blocks = 0; alloc_blocks_lim = RtsFlags.GcFlags.minAllocAreaSize; -#ifdef COMPILER /* Tell GNU multi-precision pkg about our custom alloc functions */ mp_set_memory_functions(stgAllocForGMP, stgReallocForGMP, stgDeallocForGMP); + +#ifdef SMP + pthread_mutex_init(&sm_mutex, NULL); #endif - IF_DEBUG(gc, stat_describe_gens()); + IF_DEBUG(gc, statDescribeGens()); +} + +void +exitStorage (void) +{ + stat_exit(calcAllocated()); } -extern bdescr * +/* ----------------------------------------------------------------------------- + CAF management. + + The entry code for every CAF does the following: + + - builds a CAF_BLACKHOLE in the heap + - pushes an update frame pointing to the CAF_BLACKHOLE + - invokes UPD_CAF(), which: + - calls newCaf, below + - updates the CAF with a static indirection to the CAF_BLACKHOLE + + Why do we build a BLACKHOLE in the heap rather than just updating + the thunk directly? It's so that we only need one kind of update + frame - otherwise we'd need a static version of the update frame too. + + newCaf() does the following: + + - it puts the CAF on the oldest generation's mut-once list. + This is so that we can treat the CAF as a root when collecting + younger generations. + + For GHCI, we have additional requirements when dealing with CAFs: + + - we must *retain* all dynamically-loaded CAFs ever entered, + just in case we need them again. + - we must be able to *revert* CAFs that have been evaluated, to + their pre-evaluated form. + + To do this, we use an additional CAF list. When newCaf() is + called on a dynamically-loaded CAF, we add it to the CAF list + instead of the old-generation mutable list, and save away its + old info pointer (in caf->saved_info) for later reversion. + + To revert all the CAFs, we traverse the CAF list and reset the + info pointer to caf->saved_info, then throw away the CAF list. + (see GC.c:revertCAFs()). + + -- SDM 29/1/01 + + -------------------------------------------------------------------------- */ + +void +newCAF(StgClosure* caf) +{ + /* Put this CAF on the mutable list for the old generation. + * This is a HACK - the IND_STATIC closure doesn't really have + * a mut_link field, but we pretend it has - in fact we re-use + * the STATIC_LINK field for the time being, because when we + * come to do a major GC we won't need the mut_link field + * any more and can use it as a STATIC_LINK. + */ + ACQUIRE_LOCK(&sm_mutex); + + if (is_dynamically_loaded_rwdata_ptr((StgPtr)caf)) { + ((StgIndStatic *)caf)->saved_info = (StgInfoTable *)caf->header.info; + ((StgIndStatic *)caf)->static_link = caf_list; + caf_list = caf; + } else { + ((StgIndStatic *)caf)->saved_info = NULL; + ((StgMutClosure *)caf)->mut_link = oldest_gen->mut_once_list; + oldest_gen->mut_once_list = (StgMutClosure *)caf; + } + + RELEASE_LOCK(&sm_mutex); + +#ifdef PAR + /* If we are PAR or DIST then we never forget a CAF */ + { globalAddr *newGA; + //belch("<##> Globalising CAF %08x %s",caf,info_type(caf)); + newGA=makeGlobal(caf,rtsTrue); /*given full weight*/ + ASSERT(newGA); + } +#endif /* PAR */ +} + +/* ----------------------------------------------------------------------------- + Nursery management. + -------------------------------------------------------------------------- */ + +void +allocNurseries( void ) +{ +#ifdef SMP + { + Capability *cap; + bdescr *bd; + + g0s0->blocks = NULL; + g0s0->n_blocks = 0; + for (cap = free_capabilities; cap != NULL; cap = cap->link) { + cap->rNursery = allocNursery(NULL, RtsFlags.GcFlags.minAllocAreaSize); + cap->rCurrentNursery = cap->rNursery; + for (bd = cap->rNursery; bd != NULL; bd = bd->link) { + bd->u.back = (bdescr *)cap; + } + } + /* Set the back links to be equal to the Capability, + * so we can do slightly better informed locking. + */ + } +#else /* SMP */ + nursery_blocks = RtsFlags.GcFlags.minAllocAreaSize; + g0s0->blocks = allocNursery(NULL, nursery_blocks); + g0s0->n_blocks = nursery_blocks; + g0s0->to_blocks = NULL; + g0s0->n_to_blocks = 0; + MainRegTable.rNursery = g0s0->blocks; + MainRegTable.rCurrentNursery = g0s0->blocks; + /* hp, hpLim, hp_bd, to_space etc. aren't used in G0S0 */ +#endif +} + +void +resetNurseries( void ) +{ + bdescr *bd; +#ifdef SMP + Capability *cap; + + /* All tasks must be stopped */ + ASSERT(n_free_capabilities == RtsFlags.ParFlags.nNodes); + + for (cap = free_capabilities; cap != NULL; cap = cap->link) { + for (bd = cap->rNursery; bd; bd = bd->link) { + bd->free = bd->start; + ASSERT(bd->gen_no == 0); + ASSERT(bd->step == g0s0); + IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE)); + } + cap->rCurrentNursery = cap->rNursery; + } +#else + for (bd = g0s0->blocks; bd; bd = bd->link) { + bd->free = bd->start; + ASSERT(bd->gen_no == 0); + ASSERT(bd->step == g0s0); + IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE)); + } + MainRegTable.rNursery = g0s0->blocks; + MainRegTable.rCurrentNursery = g0s0->blocks; +#endif +} + +bdescr * allocNursery (bdescr *last_bd, nat blocks) { bdescr *bd; @@ -193,19 +358,23 @@ allocNursery (bdescr *last_bd, nat blocks) bd = allocBlock(); bd->link = last_bd; bd->step = g0s0; - bd->gen = g0; - bd->evacuated = 0; + bd->gen_no = 0; + bd->flags = 0; bd->free = bd->start; last_bd = bd; } return last_bd; } -extern void +void resizeNursery ( nat blocks ) { bdescr *bd; +#ifdef SMP + barf("resizeNursery: can't resize in SMP mode"); +#endif + if (nursery_blocks == blocks) { ASSERT(g0s0->n_blocks == blocks); return; @@ -233,48 +402,6 @@ resizeNursery ( nat blocks ) g0s0->n_blocks = nursery_blocks = blocks; } -void -exitStorage (void) -{ - lnat allocated; - bdescr *bd; - - /* Return code ignored for now */ - /* ToDo: allocation figure is slightly wrong (see also GarbageCollect()) */ - allocated = (nursery_blocks * BLOCK_SIZE_W) + allocated_bytes(); - for ( bd = current_nursery->link; bd != NULL; bd = bd->link ) { - allocated -= BLOCK_SIZE_W; - } - stat_exit(allocated); -} - -void -newCAF(StgClosure* caf) -{ - /* Put this CAF on the mutable list for the old generation. - * This is a HACK - the IND_STATIC closure doesn't really have - * a mut_link field, but we pretend it has - in fact we re-use - * the STATIC_LINK field for the time being, because when we - * come to do a major GC we won't need the mut_link field - * any more and can use it as a STATIC_LINK. - */ - ((StgMutClosure *)caf)->mut_link = oldest_gen->mut_once_list; - oldest_gen->mut_once_list = (StgMutClosure *)caf; - -#ifdef DEBUG - { - const StgInfoTable *info; - - info = get_itbl(caf); - ASSERT(info->type == IND_STATIC); -#if 0 - STATIC_LINK2(info,caf) = caf_list; - caf_list = caf; -#endif - } -#endif -} - /* ----------------------------------------------------------------------------- The allocate() interface @@ -289,6 +416,8 @@ allocate(nat n) bdescr *bd; StgPtr p; + ACQUIRE_LOCK(&sm_mutex); + TICK_ALLOC_HEAP_NOCTR(n); CCS_ALLOC(CCCS,n); @@ -298,15 +427,17 @@ allocate(nat n) nat req_blocks = (lnat)BLOCK_ROUND_UP(n*sizeof(W_)) / BLOCK_SIZE; bd = allocGroup(req_blocks); dbl_link_onto(bd, &g0s0->large_objects); - bd->gen = g0; + bd->gen_no = 0; bd->step = g0s0; - bd->evacuated = 0; + bd->flags = BF_LARGE; bd->free = bd->start; /* don't add these blocks to alloc_blocks, since we're assuming * that large objects are likely to remain live for quite a while * (eg. running threads), so garbage collecting early won't make * much difference. */ + alloc_blocks += req_blocks; + RELEASE_LOCK(&sm_mutex); return bd->start; /* small allocation (link = small_alloc_list; small_alloc_list = bd; - bd->gen = g0; + bd->gen_no = 0; bd->step = g0s0; - bd->evacuated = 0; + bd->flags = 0; alloc_Hp = bd->start; alloc_HpLim = bd->start + BLOCK_SIZE_W; alloc_blocks++; } - + p = alloc_Hp; alloc_Hp += n; + RELEASE_LOCK(&sm_mutex); return p; } @@ -360,7 +492,7 @@ stgAllocForGMP (size_t size_in_bytes) /* allocate and fill it in. */ arr = (StgArrWords *)allocate(total_size_in_words); - SET_ARR_HDR(arr, &ARR_WORDS_info, CCCS, data_size_in_words); + SET_ARR_HDR(arr, &stg_ARR_WORDS_info, CCCS, data_size_in_words); /* and return a ptr to the goods inside the array */ return(BYTE_ARR_CTS(arr)); @@ -389,8 +521,64 @@ stgDeallocForGMP (void *ptr STG_UNUSED, } /* ----------------------------------------------------------------------------- - Stats and stuff - -------------------------------------------------------------------------- */ + * Stats and stuff + * -------------------------------------------------------------------------- */ + +/* ----------------------------------------------------------------------------- + * calcAllocated() + * + * Approximate how much we've allocated: number of blocks in the + * nursery + blocks allocated via allocate() - unused nusery blocks. + * This leaves a little slop at the end of each block, and doesn't + * take into account large objects (ToDo). + * -------------------------------------------------------------------------- */ + +lnat +calcAllocated( void ) +{ + nat allocated; + bdescr *bd; + +#ifdef SMP + Capability *cap; + + /* All tasks must be stopped. Can't assert that all the + capabilities are owned by the scheduler, though: one or more + tasks might have been stopped while they were running (non-main) + threads. */ + /* ASSERT(n_free_capabilities == RtsFlags.ParFlags.nNodes); */ + + allocated = + n_free_capabilities * RtsFlags.GcFlags.minAllocAreaSize * BLOCK_SIZE_W + + allocated_bytes(); + + for (cap = free_capabilities; cap != NULL; cap = cap->link) { + for ( bd = cap->rCurrentNursery->link; bd != NULL; bd = bd->link ) { + allocated -= BLOCK_SIZE_W; + } + if (cap->rCurrentNursery->free < cap->rCurrentNursery->start + + BLOCK_SIZE_W) { + allocated -= (cap->rCurrentNursery->start + BLOCK_SIZE_W) + - cap->rCurrentNursery->free; + } + } + +#else /* !SMP */ + bdescr *current_nursery = MainRegTable.rCurrentNursery; + + allocated = (nursery_blocks * BLOCK_SIZE_W) + allocated_bytes(); + for ( bd = current_nursery->link; bd != NULL; bd = bd->link ) { + allocated -= BLOCK_SIZE_W; + } + if (current_nursery->free < current_nursery->start + BLOCK_SIZE_W) { + allocated -= (current_nursery->start + BLOCK_SIZE_W) + - current_nursery->free; + } +#endif + + total_allocated += allocated; + return allocated; +} /* Approximate the amount of live data in the heap. To be called just * after garbage collection (see GarbageCollect()). @@ -400,10 +588,10 @@ calcLive(void) { nat g, s; lnat live = 0; - step *step; + step *stp; if (RtsFlags.GcFlags.generations == 1) { - live = (g0s0->to_blocks - 1) * BLOCK_SIZE_W + + live = (g0s0->n_to_blocks - 1) * BLOCK_SIZE_W + ((lnat)g0s0->hp_bd->free - (lnat)g0s0->hp_bd->start) / sizeof(W_); return live; } @@ -416,9 +604,12 @@ calcLive(void) if (g == 0 && s == 0) { continue; } - step = &generations[g].steps[s]; - live += (step->n_blocks - 1) * BLOCK_SIZE_W + - ((lnat)step->hp_bd->free - (lnat)step->hp_bd->start) / sizeof(W_); + stp = &generations[g].steps[s]; + live += (stp->n_large_blocks + stp->n_blocks - 1) * BLOCK_SIZE_W; + if (stp->hp_bd != NULL) { + live += ((lnat)stp->hp_bd->free - (lnat)stp->hp_bd->start) + / sizeof(W_); + } } } return live; @@ -436,16 +627,19 @@ calcNeeded(void) { lnat needed = 0; nat g, s; - step *step; + step *stp; for (g = 0; g < RtsFlags.GcFlags.generations; g++) { for (s = 0; s < generations[g].n_steps; s++) { if (g == 0 && s == 0) { continue; } - step = &generations[g].steps[s]; - if (generations[g].steps[0].n_blocks > generations[g].max_blocks) { - needed += 2 * step->n_blocks; + stp = &generations[g].steps[s]; + if (generations[g].steps[0].n_blocks + + generations[g].steps[0].n_large_blocks + > generations[g].max_blocks + && stp->is_compacted == 0) { + needed += 2 * stp->n_blocks; } else { - needed += step->n_blocks; + needed += stp->n_blocks; } } } @@ -462,11 +656,11 @@ calcNeeded(void) #ifdef DEBUG -extern void +void memInventory(void) { nat g, s; - step *step; + step *stp; bdescr *bd; lnat total_blocks = 0, free_blocks = 0; @@ -474,13 +668,13 @@ memInventory(void) for (g = 0; g < RtsFlags.GcFlags.generations; g++) { for (s = 0; s < generations[g].n_steps; s++) { - step = &generations[g].steps[s]; - total_blocks += step->n_blocks; + stp = &generations[g].steps[s]; + total_blocks += stp->n_blocks; if (RtsFlags.GcFlags.generations == 1) { /* two-space collector has a to-space too :-) */ - total_blocks += g0s0->to_blocks; + total_blocks += g0s0->n_to_blocks; } - for (bd = step->large_objects; bd; bd = bd->link) { + for (bd = stp->large_objects; bd; bd = bd->link) { total_blocks += bd->blocks; /* hack for megablock groups: they have an extra block or two in the second and subsequent megablocks where the block @@ -488,7 +682,7 @@ memInventory(void) */ if (bd->blocks > BLOCKS_PER_MBLOCK) { total_blocks -= (MBLOCK_SIZE / BLOCK_SIZE - BLOCKS_PER_MBLOCK) - * bd->blocks/(MBLOCK_SIZE/BLOCK_SIZE); + * (bd->blocks/(MBLOCK_SIZE/BLOCK_SIZE)); } } } @@ -505,45 +699,54 @@ memInventory(void) /* count the blocks on the free list */ free_blocks = countFreeList(); - ASSERT(total_blocks + free_blocks == mblocks_allocated * BLOCKS_PER_MBLOCK); - -#if 0 if (total_blocks + free_blocks != mblocks_allocated * BLOCKS_PER_MBLOCK) { fprintf(stderr, "Blocks: %ld live + %ld free = %ld total (%ld around)\n", total_blocks, free_blocks, total_blocks + free_blocks, mblocks_allocated * BLOCKS_PER_MBLOCK); } -#endif -} -/* Full heap sanity check. */ + ASSERT(total_blocks + free_blocks == mblocks_allocated * BLOCKS_PER_MBLOCK); +} -extern void -checkSanity(nat N) +static nat +countBlocks(bdescr *bd) { - nat g, s; - - if (RtsFlags.GcFlags.generations == 1) { - checkHeap(g0s0->to_space, NULL); - checkChain(g0s0->large_objects); - } else { - - for (g = 0; g <= N; g++) { - for (s = 0; s < generations[g].n_steps; s++) { - if (g == 0 && s == 0) { continue; } - checkHeap(generations[g].steps[s].blocks, NULL); - } + nat n; + for (n=0; bd != NULL; bd=bd->link) { + n += bd->blocks; } - for (g = N+1; g < RtsFlags.GcFlags.generations; g++) { - for (s = 0; s < generations[g].n_steps; s++) { - checkHeap(generations[g].steps[s].blocks, - generations[g].steps[s].blocks->start); - checkChain(generations[g].steps[s].large_objects); - } + return n; +} + +/* Full heap sanity check. */ +void +checkSanity( void ) +{ + nat g, s; + + if (RtsFlags.GcFlags.generations == 1) { + checkHeap(g0s0->to_blocks); + checkChain(g0s0->large_objects); + } else { + + for (g = 0; g < RtsFlags.GcFlags.generations; g++) { + for (s = 0; s < generations[g].n_steps; s++) { + if (g == 0 && s == 0) { continue; } + checkHeap(generations[g].steps[s].blocks); + checkChain(generations[g].steps[s].large_objects); + ASSERT(countBlocks(generations[g].steps[s].blocks) + == generations[g].steps[s].n_blocks); + ASSERT(countBlocks(generations[g].steps[s].large_objects) + == generations[g].steps[s].n_large_blocks); + if (g > 0) { + checkMutableList(generations[g].mut_list, g); + checkMutOnceList(generations[g].mut_once_list, g); + } + } + } + checkFreeListSanity(); } - checkFreeListSanity(); - } } #endif