X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=rts%2FGCCompact.c;fp=rts%2FGCCompact.c;h=4dfe84bbe0fac8fecb8f2aa1772f1d6add15b1f9;hb=0065d5ab628975892cea1ec7303f968c3338cbe1;hp=0000000000000000000000000000000000000000;hpb=28a464a75e14cece5db40f2765a29348273ff2d2;p=ghc-hetmet.git diff --git a/rts/GCCompact.c b/rts/GCCompact.c new file mode 100644 index 0000000..4dfe84b --- /dev/null +++ b/rts/GCCompact.c @@ -0,0 +1,949 @@ +/* ----------------------------------------------------------------------------- + * + * (c) The GHC Team 2001 + * + * Compacting garbage collector + * + * ---------------------------------------------------------------------------*/ + +#include "PosixSource.h" +#include "Rts.h" +#include "RtsUtils.h" +#include "RtsFlags.h" +#include "OSThreads.h" +#include "Storage.h" +#include "BlockAlloc.h" +#include "MBlock.h" +#include "GCCompact.h" +#include "Schedule.h" +#include "Apply.h" + +// Turn off inlining when debugging - it obfuscates things +#ifdef DEBUG +# undef STATIC_INLINE +# define STATIC_INLINE static +#endif + +/* ----------------------------------------------------------------------------- + Threading / unthreading pointers. + + The basic idea here is to chain together all the fields pointing at + a particular object, with the root of the chain in the object's + info table field. The original contents of the info pointer goes + at the end of the chain. + + Adding a new field to the chain is a matter of swapping the + contents of the field with the contents of the object's info table + field. + + To unthread the chain, we walk down it updating all the fields on + the chain with the new location of the object. We stop when we + reach the info pointer at the end. + + We use a trick to identify the info pointer: when swapping pointers + for threading, we set the low bit of the original pointer, with the + result that all the pointers in the chain have their low bits set + except for the info pointer. + -------------------------------------------------------------------------- */ + +STATIC_INLINE void +thread( StgPtr p ) +{ + StgPtr q = (StgPtr)*p; + bdescr *bd; + + // It doesn't look like a closure at the moment, because the info + // ptr is possibly threaded: + // ASSERT(LOOKS_LIKE_CLOSURE_PTR(q)); + + if (HEAP_ALLOCED(q)) { + bd = Bdescr(q); + // a handy way to discover whether the ptr is into the + // compacted area of the old gen, is that the EVACUATED flag + // is zero (it's non-zero for all the other areas of live + // memory). + if ((bd->flags & BF_EVACUATED) == 0) { + *p = (StgWord)*q; + *q = (StgWord)p + 1; // set the low bit + } + } +} + +STATIC_INLINE void +unthread( StgPtr p, StgPtr free ) +{ + StgWord q = *p, r; + + while ((q & 1) != 0) { + q -= 1; // unset the low bit again + r = *((StgPtr)q); + *((StgPtr)q) = (StgWord)free; + q = r; + } + *p = q; +} + +STATIC_INLINE StgInfoTable * +get_threaded_info( StgPtr p ) +{ + StgPtr q = (P_)GET_INFO((StgClosure *)p); + + while (((StgWord)q & 1) != 0) { + q = (P_)*((StgPtr)((StgWord)q-1)); + } + + ASSERT(LOOKS_LIKE_INFO_PTR(q)); + return INFO_PTR_TO_STRUCT((StgInfoTable *)q); +} + +// A word-aligned memmove will be faster for small objects than libc's or gcc's. +// Remember, the two regions *might* overlap, but: to <= from. +STATIC_INLINE void +move(StgPtr to, StgPtr from, nat size) +{ + for(; size > 0; --size) { + *to++ = *from++; + } +} + +static void +thread_static( StgClosure* p ) +{ + const StgInfoTable *info; + + // keep going until we've threaded all the objects on the linked + // list... + while (p != END_OF_STATIC_LIST) { + + info = get_itbl(p); + switch (info->type) { + + case IND_STATIC: + thread((StgPtr)&((StgInd *)p)->indirectee); + p = *IND_STATIC_LINK(p); + continue; + + case THUNK_STATIC: + p = *THUNK_STATIC_LINK(p); + continue; + case FUN_STATIC: + p = *FUN_STATIC_LINK(p); + continue; + case CONSTR_STATIC: + p = *STATIC_LINK(info,p); + continue; + + default: + barf("thread_static: strange closure %d", (int)(info->type)); + } + + } +} + +STATIC_INLINE void +thread_large_bitmap( StgPtr p, StgLargeBitmap *large_bitmap, nat size ) +{ + nat i, b; + StgWord bitmap; + + b = 0; + bitmap = large_bitmap->bitmap[b]; + for (i = 0; i < size; ) { + if ((bitmap & 1) == 0) { + thread(p); + } + i++; + p++; + if (i % BITS_IN(W_) == 0) { + b++; + bitmap = large_bitmap->bitmap[b]; + } else { + bitmap = bitmap >> 1; + } + } +} + +STATIC_INLINE StgPtr +thread_arg_block (StgFunInfoTable *fun_info, StgClosure **args) +{ + StgPtr p; + StgWord bitmap; + nat size; + + p = (StgPtr)args; + switch (fun_info->f.fun_type) { + case ARG_GEN: + bitmap = BITMAP_BITS(fun_info->f.b.bitmap); + size = BITMAP_SIZE(fun_info->f.b.bitmap); + goto small_bitmap; + case ARG_GEN_BIG: + size = GET_FUN_LARGE_BITMAP(fun_info)->size; + thread_large_bitmap(p, GET_FUN_LARGE_BITMAP(fun_info), size); + p += size; + break; + default: + bitmap = BITMAP_BITS(stg_arg_bitmaps[fun_info->f.fun_type]); + size = BITMAP_SIZE(stg_arg_bitmaps[fun_info->f.fun_type]); + small_bitmap: + while (size > 0) { + if ((bitmap & 1) == 0) { + thread(p); + } + p++; + bitmap = bitmap >> 1; + size--; + } + break; + } + return p; +} + +static void +thread_stack(StgPtr p, StgPtr stack_end) +{ + const StgRetInfoTable* info; + StgWord bitmap; + nat size; + + // highly similar to scavenge_stack, but we do pointer threading here. + + while (p < stack_end) { + + // *p must be the info pointer of an activation + // record. All activation records have 'bitmap' style layout + // info. + // + info = get_ret_itbl((StgClosure *)p); + + switch (info->i.type) { + + // Dynamic bitmap: the mask is stored on the stack + case RET_DYN: + { + StgWord dyn; + dyn = ((StgRetDyn *)p)->liveness; + + // traverse the bitmap first + bitmap = RET_DYN_LIVENESS(dyn); + p = (P_)&((StgRetDyn *)p)->payload[0]; + size = RET_DYN_BITMAP_SIZE; + while (size > 0) { + if ((bitmap & 1) == 0) { + thread(p); + } + p++; + bitmap = bitmap >> 1; + size--; + } + + // skip over the non-ptr words + p += RET_DYN_NONPTRS(dyn) + RET_DYN_NONPTR_REGS_SIZE; + + // follow the ptr words + for (size = RET_DYN_PTRS(dyn); size > 0; size--) { + thread(p); + p++; + } + continue; + } + + // small bitmap (<= 32 entries, or 64 on a 64-bit machine) + case CATCH_RETRY_FRAME: + case CATCH_STM_FRAME: + case ATOMICALLY_FRAME: + case UPDATE_FRAME: + case STOP_FRAME: + case CATCH_FRAME: + case RET_SMALL: + case RET_VEC_SMALL: + bitmap = BITMAP_BITS(info->i.layout.bitmap); + size = BITMAP_SIZE(info->i.layout.bitmap); + p++; + // NOTE: the payload starts immediately after the info-ptr, we + // don't have an StgHeader in the same sense as a heap closure. + while (size > 0) { + if ((bitmap & 1) == 0) { + thread(p); + } + p++; + bitmap = bitmap >> 1; + size--; + } + continue; + + case RET_BCO: { + StgBCO *bco; + nat size; + + p++; + bco = (StgBCO *)*p; + thread(p); + p++; + size = BCO_BITMAP_SIZE(bco); + thread_large_bitmap(p, BCO_BITMAP(bco), size); + p += size; + continue; + } + + // large bitmap (> 32 entries, or 64 on a 64-bit machine) + case RET_BIG: + case RET_VEC_BIG: + p++; + size = GET_LARGE_BITMAP(&info->i)->size; + thread_large_bitmap(p, GET_LARGE_BITMAP(&info->i), size); + p += size; + continue; + + case RET_FUN: + { + StgRetFun *ret_fun = (StgRetFun *)p; + StgFunInfoTable *fun_info; + + fun_info = itbl_to_fun_itbl( + get_threaded_info((StgPtr)ret_fun->fun)); + // *before* threading it! + thread((StgPtr)&ret_fun->fun); + p = thread_arg_block(fun_info, ret_fun->payload); + continue; + } + + default: + barf("thread_stack: weird activation record found on stack: %d", + (int)(info->i.type)); + } + } +} + +STATIC_INLINE StgPtr +thread_PAP_payload (StgClosure *fun, StgClosure **payload, StgWord size) +{ + StgPtr p; + StgWord bitmap; + StgFunInfoTable *fun_info; + + fun_info = itbl_to_fun_itbl(get_threaded_info((StgPtr)fun)); + ASSERT(fun_info->i.type != PAP); + + p = (StgPtr)payload; + + switch (fun_info->f.fun_type) { + case ARG_GEN: + bitmap = BITMAP_BITS(fun_info->f.b.bitmap); + goto small_bitmap; + case ARG_GEN_BIG: + thread_large_bitmap(p, GET_FUN_LARGE_BITMAP(fun_info), size); + p += size; + break; + case ARG_BCO: + thread_large_bitmap((StgPtr)payload, BCO_BITMAP(fun), size); + p += size; + break; + default: + bitmap = BITMAP_BITS(stg_arg_bitmaps[fun_info->f.fun_type]); + small_bitmap: + while (size > 0) { + if ((bitmap & 1) == 0) { + thread(p); + } + p++; + bitmap = bitmap >> 1; + size--; + } + break; + } + + return p; +} + +STATIC_INLINE StgPtr +thread_PAP (StgPAP *pap) +{ + StgPtr p; + p = thread_PAP_payload(pap->fun, pap->payload, pap->n_args); + thread((StgPtr)&pap->fun); + return p; +} + +STATIC_INLINE StgPtr +thread_AP (StgAP *ap) +{ + StgPtr p; + p = thread_PAP_payload(ap->fun, ap->payload, ap->n_args); + thread((StgPtr)&ap->fun); + return p; +} + +STATIC_INLINE StgPtr +thread_AP_STACK (StgAP_STACK *ap) +{ + thread((StgPtr)&ap->fun); + thread_stack((P_)ap->payload, (P_)ap->payload + ap->size); + return (P_)ap + sizeofW(StgAP_STACK) + ap->size; +} + +static StgPtr +thread_TSO (StgTSO *tso) +{ + thread((StgPtr)&tso->link); + thread((StgPtr)&tso->global_link); + + if ( tso->why_blocked == BlockedOnMVar + || tso->why_blocked == BlockedOnBlackHole + || tso->why_blocked == BlockedOnException +#if defined(PAR) + || tso->why_blocked == BlockedOnGA + || tso->why_blocked == BlockedOnGA_NoSend +#endif + ) { + thread((StgPtr)&tso->block_info.closure); + } + if ( tso->blocked_exceptions != NULL ) { + thread((StgPtr)&tso->blocked_exceptions); + } + + thread((StgPtr)&tso->trec); + + thread_stack(tso->sp, &(tso->stack[tso->stack_size])); + return (StgPtr)tso + tso_sizeW(tso); +} + + +static void +update_fwd_large( bdescr *bd ) +{ + StgPtr p; + const StgInfoTable* info; + + for (; bd != NULL; bd = bd->link) { + + p = bd->start; + info = get_itbl((StgClosure *)p); + + switch (info->type) { + + case ARR_WORDS: + // nothing to follow + continue; + + case MUT_ARR_PTRS_CLEAN: + case MUT_ARR_PTRS_DIRTY: + case MUT_ARR_PTRS_FROZEN: + case MUT_ARR_PTRS_FROZEN0: + // follow everything + { + StgPtr next; + + next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p); + for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) { + thread(p); + } + continue; + } + + case TSO: + thread_TSO((StgTSO *)p); + continue; + + case AP_STACK: + thread_AP_STACK((StgAP_STACK *)p); + continue; + + case PAP: + thread_PAP((StgPAP *)p); + continue; + + case TREC_CHUNK: + { + StgWord i; + StgTRecChunk *tc = (StgTRecChunk *)p; + TRecEntry *e = &(tc -> entries[0]); + thread((StgPtr)&tc->prev_chunk); + for (i = 0; i < tc -> next_entry_idx; i ++, e++ ) { + thread((StgPtr)&e->tvar); + thread((StgPtr)&e->expected_value); + thread((StgPtr)&e->new_value); + } + continue; + } + + default: + barf("update_fwd_large: unknown/strange object %d", (int)(info->type)); + } + } +} + +STATIC_INLINE StgPtr +thread_obj (StgInfoTable *info, StgPtr p) +{ + switch (info->type) { + case THUNK_0_1: + return p + sizeofW(StgThunk) + 1; + + case FUN_0_1: + case CONSTR_0_1: + return p + sizeofW(StgHeader) + 1; + + case FUN_1_0: + case CONSTR_1_0: + thread((StgPtr)&((StgClosure *)p)->payload[0]); + return p + sizeofW(StgHeader) + 1; + + case THUNK_1_0: + thread((StgPtr)&((StgThunk *)p)->payload[0]); + return p + sizeofW(StgThunk) + 1; + + case THUNK_0_2: + return p + sizeofW(StgThunk) + 2; + + case FUN_0_2: + case CONSTR_0_2: + return p + sizeofW(StgHeader) + 2; + + case THUNK_1_1: + thread((StgPtr)&((StgThunk *)p)->payload[0]); + return p + sizeofW(StgThunk) + 2; + + case FUN_1_1: + case CONSTR_1_1: + thread((StgPtr)&((StgClosure *)p)->payload[0]); + return p + sizeofW(StgHeader) + 2; + + case THUNK_2_0: + thread((StgPtr)&((StgThunk *)p)->payload[0]); + thread((StgPtr)&((StgThunk *)p)->payload[1]); + return p + sizeofW(StgThunk) + 2; + + case FUN_2_0: + case CONSTR_2_0: + thread((StgPtr)&((StgClosure *)p)->payload[0]); + thread((StgPtr)&((StgClosure *)p)->payload[1]); + return p + sizeofW(StgHeader) + 2; + + case BCO: { + StgBCO *bco = (StgBCO *)p; + thread((StgPtr)&bco->instrs); + thread((StgPtr)&bco->literals); + thread((StgPtr)&bco->ptrs); + thread((StgPtr)&bco->itbls); + return p + bco_sizeW(bco); + } + + case THUNK: + { + StgPtr end; + + end = (P_)((StgThunk *)p)->payload + + info->layout.payload.ptrs; + for (p = (P_)((StgThunk *)p)->payload; p < end; p++) { + thread(p); + } + return p + info->layout.payload.nptrs; + } + + case FUN: + case CONSTR: + case STABLE_NAME: + case IND_PERM: + case MUT_VAR_CLEAN: + case MUT_VAR_DIRTY: + case CAF_BLACKHOLE: + case SE_CAF_BLACKHOLE: + case SE_BLACKHOLE: + case BLACKHOLE: + { + StgPtr end; + + end = (P_)((StgClosure *)p)->payload + + info->layout.payload.ptrs; + for (p = (P_)((StgClosure *)p)->payload; p < end; p++) { + thread(p); + } + return p + info->layout.payload.nptrs; + } + + case WEAK: + { + StgWeak *w = (StgWeak *)p; + thread((StgPtr)&w->key); + thread((StgPtr)&w->value); + thread((StgPtr)&w->finalizer); + if (w->link != NULL) { + thread((StgPtr)&w->link); + } + return p + sizeofW(StgWeak); + } + + case MVAR: + { + StgMVar *mvar = (StgMVar *)p; + thread((StgPtr)&mvar->head); + thread((StgPtr)&mvar->tail); + thread((StgPtr)&mvar->value); + return p + sizeofW(StgMVar); + } + + case IND_OLDGEN: + case IND_OLDGEN_PERM: + thread((StgPtr)&((StgInd *)p)->indirectee); + return p + sizeofW(StgInd); + + case THUNK_SELECTOR: + { + StgSelector *s = (StgSelector *)p; + thread((StgPtr)&s->selectee); + return p + THUNK_SELECTOR_sizeW(); + } + + case AP_STACK: + return thread_AP_STACK((StgAP_STACK *)p); + + case PAP: + return thread_PAP((StgPAP *)p); + + case AP: + return thread_AP((StgAP *)p); + + case ARR_WORDS: + return p + arr_words_sizeW((StgArrWords *)p); + + case MUT_ARR_PTRS_CLEAN: + case MUT_ARR_PTRS_DIRTY: + case MUT_ARR_PTRS_FROZEN: + case MUT_ARR_PTRS_FROZEN0: + // follow everything + { + StgPtr next; + + next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p); + for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) { + thread(p); + } + return p; + } + + case TSO: + return thread_TSO((StgTSO *)p); + + case TVAR_WAIT_QUEUE: + { + StgTVarWaitQueue *wq = (StgTVarWaitQueue *)p; + thread((StgPtr)&wq->waiting_tso); + thread((StgPtr)&wq->next_queue_entry); + thread((StgPtr)&wq->prev_queue_entry); + return p + sizeofW(StgTVarWaitQueue); + } + + case TVAR: + { + StgTVar *tvar = (StgTVar *)p; + thread((StgPtr)&tvar->current_value); + thread((StgPtr)&tvar->first_wait_queue_entry); + return p + sizeofW(StgTVar); + } + + case TREC_HEADER: + { + StgTRecHeader *trec = (StgTRecHeader *)p; + thread((StgPtr)&trec->enclosing_trec); + thread((StgPtr)&trec->current_chunk); + return p + sizeofW(StgTRecHeader); + } + + case TREC_CHUNK: + { + StgWord i; + StgTRecChunk *tc = (StgTRecChunk *)p; + TRecEntry *e = &(tc -> entries[0]); + thread((StgPtr)&tc->prev_chunk); + for (i = 0; i < tc -> next_entry_idx; i ++, e++ ) { + thread((StgPtr)&e->tvar); + thread((StgPtr)&e->expected_value); + thread((StgPtr)&e->new_value); + } + return p + sizeofW(StgTRecChunk); + } + + default: + barf("update_fwd: unknown/strange object %d", (int)(info->type)); + return NULL; + } +} + +static void +update_fwd( bdescr *blocks ) +{ + StgPtr p; + bdescr *bd; + StgInfoTable *info; + + bd = blocks; + +#if defined(PAR) + barf("update_fwd: ToDo"); +#endif + + // cycle through all the blocks in the step + for (; bd != NULL; bd = bd->link) { + p = bd->start; + + // linearly scan the objects in this block + while (p < bd->free) { + ASSERT(LOOKS_LIKE_CLOSURE_PTR(p)); + info = get_itbl((StgClosure *)p); + p = thread_obj(info, p); + } + } +} + +static void +update_fwd_compact( bdescr *blocks ) +{ + StgPtr p, q, free; +#if 0 + StgWord m; +#endif + bdescr *bd, *free_bd; + StgInfoTable *info; + nat size; + + bd = blocks; + free_bd = blocks; + free = free_bd->start; + +#if defined(PAR) + barf("update_fwd: ToDo"); +#endif + + // cycle through all the blocks in the step + for (; bd != NULL; bd = bd->link) { + p = bd->start; + + while (p < bd->free ) { + + while ( p < bd->free && !is_marked(p,bd) ) { + p++; + } + if (p >= bd->free) { + break; + } + +#if 0 + next: + m = * ((StgPtr)bd->u.bitmap + ((p - bd->start) / (BITS_IN(StgWord)))); + m >>= ((p - bd->start) & (BITS_IN(StgWord) - 1)); + + while ( p < bd->free ) { + + if ((m & 1) == 0) { + m >>= 1; + p++; + if (((StgWord)p & (sizeof(W_) * BITS_IN(StgWord))) == 0) { + goto next; + } else { + continue; + } + } +#endif + + // Problem: we need to know the destination for this cell + // in order to unthread its info pointer. But we can't + // know the destination without the size, because we may + // spill into the next block. So we have to run down the + // threaded list and get the info ptr first. + info = get_threaded_info(p); + + q = p; + + p = thread_obj(info, p); + + size = p - q; + if (free + size > free_bd->start + BLOCK_SIZE_W) { + // unset the next bit in the bitmap to indicate that + // this object needs to be pushed into the next + // block. This saves us having to run down the + // threaded info pointer list twice during the next pass. + unmark(q+1,bd); + free_bd = free_bd->link; + free = free_bd->start; + } else { + ASSERT(is_marked(q+1,bd)); + } + + unthread(q,free); + free += size; +#if 0 + goto next; +#endif + } + } +} + +static nat +update_bkwd_compact( step *stp ) +{ + StgPtr p, free; +#if 0 + StgWord m; +#endif + bdescr *bd, *free_bd; + StgInfoTable *info; + nat size, free_blocks; + + bd = free_bd = stp->old_blocks; + free = free_bd->start; + free_blocks = 1; + +#if defined(PAR) + barf("update_bkwd: ToDo"); +#endif + + // cycle through all the blocks in the step + for (; bd != NULL; bd = bd->link) { + p = bd->start; + + while (p < bd->free ) { + + while ( p < bd->free && !is_marked(p,bd) ) { + p++; + } + if (p >= bd->free) { + break; + } + +#if 0 + next: + m = * ((StgPtr)bd->u.bitmap + ((p - bd->start) / (BITS_IN(StgWord)))); + m >>= ((p - bd->start) & (BITS_IN(StgWord) - 1)); + + while ( p < bd->free ) { + + if ((m & 1) == 0) { + m >>= 1; + p++; + if (((StgWord)p & (sizeof(W_) * BITS_IN(StgWord))) == 0) { + goto next; + } else { + continue; + } + } +#endif + + if (!is_marked(p+1,bd)) { + // don't forget to update the free ptr in the block desc. + free_bd->free = free; + free_bd = free_bd->link; + free = free_bd->start; + free_blocks++; + } + + unthread(p,free); + ASSERT(LOOKS_LIKE_INFO_PTR(((StgClosure *)p)->header.info)); + info = get_itbl((StgClosure *)p); + size = closure_sizeW_((StgClosure *)p,info); + + if (free != p) { + move(free,p,size); + } + + // relocate TSOs + if (info->type == TSO) { + move_TSO((StgTSO *)p, (StgTSO *)free); + } + + free += size; + p += size; +#if 0 + goto next; +#endif + } + } + + // free the remaining blocks and count what's left. + free_bd->free = free; + if (free_bd->link != NULL) { + freeChain(free_bd->link); + free_bd->link = NULL; + } + + return free_blocks; +} + +void +compact( void (*get_roots)(evac_fn) ) +{ + nat g, s, blocks; + step *stp; + + // 1. thread the roots + get_roots((evac_fn)thread); + + // the weak pointer lists... + if (weak_ptr_list != NULL) { + thread((StgPtr)(void *)&weak_ptr_list); + } + if (old_weak_ptr_list != NULL) { + thread((StgPtr)(void *)&old_weak_ptr_list); // tmp + } + + // mutable lists + for (g = 1; g < RtsFlags.GcFlags.generations; g++) { + bdescr *bd; + StgPtr p; + for (bd = generations[g].mut_list; bd != NULL; bd = bd->link) { + for (p = bd->start; p < bd->free; p++) { + thread(p); + } + } + } + + // the global thread list + thread((StgPtr)(void *)&all_threads); + + // any threads resurrected during this GC + thread((StgPtr)(void *)&resurrected_threads); + + // the task list + { + Task *task; + for (task = all_tasks; task != NULL; task = task->all_link) { + if (task->tso) { + thread((StgPtr)&task->tso); + } + } + } + + // the static objects + thread_static(scavenged_static_objects); + + // the stable pointer table + threadStablePtrTable((evac_fn)thread); + + // the CAF list (used by GHCi) + markCAFs((evac_fn)thread); + + // 2. update forward ptrs + for (g = 0; g < RtsFlags.GcFlags.generations; g++) { + for (s = 0; s < generations[g].n_steps; s++) { + if (g==0 && s ==0) continue; + stp = &generations[g].steps[s]; + IF_DEBUG(gc, debugBelch("update_fwd: %d.%d\n", stp->gen->no, stp->no);); + + update_fwd(stp->blocks); + update_fwd_large(stp->scavenged_large_objects); + if (g == RtsFlags.GcFlags.generations-1 && stp->old_blocks != NULL) { + IF_DEBUG(gc, debugBelch("update_fwd: %d.%d (compact)\n", stp->gen->no, stp->no);); + update_fwd_compact(stp->old_blocks); + } + } + } + + // 3. update backward ptrs + stp = &oldest_gen->steps[0]; + if (stp->old_blocks != NULL) { + blocks = update_bkwd_compact(stp); + IF_DEBUG(gc, debugBelch("update_bkwd: %d.%d (compact, old: %d blocks, now %d blocks)\n", + stp->gen->no, stp->no, + stp->n_old_blocks, blocks);); + stp->n_old_blocks = blocks; + } +}