Added a generational garbage collector.
The collector is reliable but fairly untuned as yet. It works with an
arbitrary number of generations: use +RTS -G<gens> to change the
number of generations used (default 2).
Stats: +RTS -Sstderr is quite useful, but to really see what's going
on compile the RTS with -DDEBUG and use +RTS -D32.
ARR_PTRS removed - it wasn't used anywhere.
Sanity checking improved:
- free blocks are now spammed when sanity checking is turned on
- a check for leaking blocks is performed after each GC.
maximum performance.
<descrip>
+<tag>@-G<generations>@:</tag>
+<nidx>-G<generations> RTS option</nidx>
+<nidx>generations, number of</nidx>
+
+[Default: 2] Set the number of generations used by the garbage
+collector. The default of 2 seems to be good, but the garbage
+collector can support any number of generations. NOTE: -G1 (i.e. a
+two-space copying collector) is currently not supported.
+
<tag>@-A<size>@:</tag>
<nidx>-A<size> RTS option</nidx>
<nidx>allocation area, size</nidx>
-[Default: 256k] Set the minimum (and initial) allocation area size
-used by the garbage collector. The allocation area is resized after
-each garbage collection to be a multiple of the size of the current
-live data (currently a factor of 2).
+[Default: 256k] Set the allocation area size used by the garbage
+collector. The allocation area (actually generation 0 step 0) is
+fixed and is never resized.
-Increasing the minimum allocation area size will typically give better
-performance for programs which quickly generate a large amount of live
-data.
+Increasing the allocation area size may or may not give better
+performance (a bigger allocation area means worse cache behaviour but
+fewer garbage collections and less promotion).
<tag>@-k<size>@:</tag>
<nidx>-k<size> RTS option</nidx>
%PostScript), using the @stat2resid@<nidx>stat2resid</nidx> utility in
%the GHC distribution (@ghc/utils/stat2resid@).
-<tag>@-F2s@:</tag>
-<nidx>-F2s RTS option</nidx>
-
-Forces a program compiled for generational GC to use two-space copying
-collection. The two-space collector may outperform the generational
-collector for programs which have a very low heap residency. It can
-also be used to generate a statistics file from which a basic heap
-residency profile can be produced (see Section <ref name="stat2resid -
-residency info from GC stats" id="stat2resid">).
-
-There will still be a small execution overhead imposed by the
-generational compilation as the test for old generation updates will
-still be executed (of course none will actually happen). This
-overhead is typically less than 1\%.
-
-<tag>@-j<size>@:</tag>
-<nidx>-j<size> RTS option</nidx>
-Force a major garbage collection every @<size>@ bytes. (Normally
-used because you're keen on getting major-GC stats, notably heap residency
-info.)
+% <tag>@-F2s@:</tag>
+% <nidx>-F2s RTS option</nidx>
+%
+% Forces a program compiled for generational GC to use two-space copying
+% collection. The two-space collector may outperform the generational
+% collector for programs which have a very low heap residency. It can
+% also be used to generate a statistics file from which a basic heap
+% residency profile can be produced (see Section <ref name="stat2resid -
+% residency info from GC stats" id="stat2resid">).
+%
+% There will still be a small execution overhead imposed by the
+% generational compilation as the test for old generation updates will
+% still be executed (of course none will actually happen). This
+% overhead is typically less than 1\%.
+%
+% <tag>@-j<size>@:</tag>
+% <nidx>-j<size> RTS option</nidx>
+% Force a major garbage collection every @<size>@ bytes. (Normally
+% used because you're keen on getting major-GC stats, notably heap residency
+% info.)
</descrip>
$MaxResidency = $1; $ResidencySamples = $2;
}
- $GCs = $1 if /^\s*([0-9,]+) garbage collections? performed/;
+ $GCs = $1 if /^\s*([0-9,]+) (collections? in generation 0|garbage collections? performed)/;
# The presence of -? in the following pattern is only there to
# accommodate 0.29 && <= 2.05 RTS'
/* -----------------------------------------------------------------------------
- * $Id: Block.h,v 1.2 1998/12/02 13:20:53 simonm Exp $
+ * $Id: Block.h,v 1.3 1999/01/13 17:25:51 simonm Exp $
*
* Block structure for the storage manager
*
StgPtr free; /* first free byte of memory */
struct _bdescr *link; /* used for chaining blocks together */
struct _bdescr *back; /* used (occasionally) for doubly-linked lists*/
- StgNat32 gen; /* generation */
- StgNat32 step; /* step */
+ struct _generation *gen; /* generation */
+ struct _step *step; /* step */
StgNat32 blocks; /* no. of blocks (if grp head, 0 otherwise) */
+ StgNat32 evacuated; /* block is in to-space */
#if SIZEOF_VOID_P == 8
- StgNat32 _padding[5];
+ StgNat32 _padding[2];
#else
- StgNat32 _padding[1];
+ StgNat32 _padding[0];
#endif
} bdescr;
/* ----------------------------------------------------------------------------
- * $Id: ClosureMacros.h,v 1.2 1998/12/02 13:20:58 simonm Exp $
+ * $Id: ClosureMacros.h,v 1.3 1999/01/13 17:25:52 simonm Exp $
*
* Macros for building and manipulating closures
*
*/
static __inline__ StgOffset arr_words_sizeW( StgArrWords* x )
{ return sizeofW(StgArrWords) + x->words; }
-static __inline__ StgOffset arr_ptrs_sizeW( StgArrPtrs* x )
-{ return sizeofW(StgArrPtrs) + x->ptrs; }
+static __inline__ StgOffset mut_arr_ptrs_sizeW( StgMutArrPtrs* x )
+{ return sizeofW(StgMutArrPtrs) + x->ptrs; }
static __inline__ StgWord bco_sizeW( StgBCO* bco )
{ return BCO_sizeW(bco->n_ptrs,bco->n_words,bco->n_instrs); }
SET_TICKY_HDR((StgClosure *)(c),0); \
}
-/* works for all ARR_WORDS, ARR_PTRS variants (at the moment...) */
-
#define SET_ARR_HDR(c,info,costCentreStack,n_words) \
SET_HDR(c,info,costCentreStack); \
(c)->words = n_words;
/* ----------------------------------------------------------------------------
- * $Id: ClosureTypes.h,v 1.2 1998/12/02 13:20:58 simonm Exp $
+ * $Id: ClosureTypes.h,v 1.3 1999/01/13 17:25:52 simonm Exp $
*
* Closure Type Constants
*
/* Object tag 0 raises an internal error */
#define INVALID_OBJECT 0
-
#define CONSTR 1
/* #define CONSTR_p_np */
#define CONSTR_INTLIKE 2
#define CONSTR_CHARLIKE 3
#define CONSTR_STATIC 4
#define CONSTR_NOCAF_STATIC 5
-
#define FUN 6
#define FUN_STATIC 7
-
#define THUNK 8
/* #define THUNK_p_np */
#define THUNK_STATIC 9
#define THUNK_SELECTOR 10
-
#define BCO 11
-
#define AP_UPD 12
#define PAP 13
-
#define IND 14
#define IND_OLDGEN 15
#define IND_PERM 16
#define IND_OLDGEN_PERM 17
#define IND_STATIC 18
-
#define CAF_UNENTERED 19
#define CAF_ENTERED 20
#define CAF_BLACKHOLE 21
-
#define RET_BCO 22
#define RET_SMALL 23
#define RET_VEC_SMALL 24
#define RET_VEC_BIG 26
#define RET_DYN 27
#define UPDATE_FRAME 28
-#define CATCH_FRAME 29
-#define STOP_FRAME 30
-#define SEQ_FRAME 31
-
-#define BLACKHOLE 32
-#define MVAR 33
-
-#define ARR_WORDS 34
-#define ARR_PTRS 35
-
-#define MUT_ARR_WORDS 36
-#define MUT_ARR_PTRS 37
-#define MUT_ARR_PTRS_FROZEN 38
-#define MUT_VAR 39
-
-#define WEAK 40
-#define FOREIGN 41
-
-#define TSO 42
-#define BLOCKED_FETCH 43
-#define FETCH_ME 44
-
-#define EVACUATED 45
+#define UPDATE_STATIC_FRAME 29
+#define CATCH_FRAME 30
+#define STOP_FRAME 31
+#define SEQ_FRAME 32
+#define BLACKHOLE 33
+#define BLACKHOLE_STATIC 34
+#define MVAR 35
+#define ARR_WORDS 36
+#define MUT_ARR_WORDS 37
+#define MUT_ARR_PTRS 38
+#define MUT_ARR_PTRS_FROZEN 39
+#define MUT_VAR 40
+#define WEAK 41
+#define FOREIGN 42
+#define TSO 43
+#define BLOCKED_FETCH 44
+#define FETCH_ME 45
+#define EVACUATED 46
#endif CLOSURETYPES_H
/* ----------------------------------------------------------------------------
- * $Id: Closures.h,v 1.2 1998/12/02 13:20:59 simonm Exp $
+ * $Id: Closures.h,v 1.3 1999/01/13 17:25:53 simonm Exp $
*
* Closures
*
struct StgClosure_ *payload[0];
} StgClosure;
+/* What a stroke of luck - all our mutable closures follow the same
+ * basic layout, with the mutable link field as the second field after
+ * the header. This means the following structure is the supertype of
+ * mutable closures.
+ */
+
+typedef struct StgMutClosure_ {
+ StgHeader header;
+ StgPtr *padding;
+ struct StgMutClosure_ *mut_link;
+ struct StgClosure_ *payload[0];
+} StgMutClosure;
+
typedef struct {
StgHeader header;
StgClosure *selectee;
typedef struct {
StgHeader header;
- StgClosure *mut_link;
StgClosure *indirectee;
+ StgMutClosure *mut_link;
} StgIndOldGen;
typedef struct {
typedef struct {
StgHeader header;
StgWord ptrs;
+ StgMutClosure *mut_link; /* mutable list */
StgClosure *payload[0];
-} StgArrPtrs;
+} StgMutArrPtrs;
typedef struct {
StgHeader header;
StgClosure *var;
+ StgMutClosure *mut_link;
} StgMutVar;
typedef struct _StgUpdateFrame {
typedef struct {
StgHeader header;
- struct StgTSO_* head;
- struct StgTSO_* tail;
+ struct StgTSO_ *head;
+ StgMutClosure *mut_link;
+ struct StgTSO_ *tail;
StgClosure* value;
} StgMVar;
/* ----------------------------------------------------------------------------
- * $Id: InfoTables.h,v 1.2 1998/12/02 13:21:10 simonm Exp $
+ * $Id: InfoTables.h,v 1.3 1999/01/13 17:25:53 simonm Exp $
*
* Info Tables
*
, MVAR
, ARR_WORDS
- , ARR_PTRS
, MUT_ARR_WORDS
, MUT_ARR_PTRS
#define _UPT (1<<6) /* unpointed? */
#define _SRT (1<<7) /* has an SRT? */
-#define isSTATIC(flags) ((flags)&_STA)
+#define isSTATIC(flags) ((flags)&_STA)
+#define isMUTABLE(flags) ((flags) &_MUT)
+
#define closure_STATIC(closure) ( get_itbl(closure)->flags & _STA)
#define closure_SHOULD_SPARK(closure) (!(get_itbl(closure)->flags & _NS))
+#define closure_MUTABLE(closure) ( get_itbl(closure)->flags & _MUT)
#define closure_UNPOINTED(closure) ( get_itbl(closure)->flags & _UPT)
/* HNF BTM NS STA THU MUT UPT SRT */
#define FLAGS_EVACUATED 0
#define FLAGS_ARR_WORDS (_HNF| _NS| _UPT )
#define FLAGS_MUT_ARR_WORDS (_HNF| _NS| _MUT|_UPT )
-#define FLAGS_ARR_PTRS (_HNF| _NS| _UPT )
#define FLAGS_MUT_ARR_PTRS (_HNF| _NS| _MUT|_UPT )
#define FLAGS_MUT_ARR_PTRS_FROZEN (_HNF| _NS| _MUT|_UPT )
#define FLAGS_MUT_VAR (_HNF| _NS| _MUT|_UPT )
#define FLAGS_FOREIGN (_HNF| _NS| _UPT )
#define FLAGS_WEAK (_HNF| _NS| _UPT )
-#define FLAGS_BLACKHOLE ( _BTM|_NS| _UPT )
-#define FLAGS_MVAR (_HNF| _NS| _UPT )
+#define FLAGS_BLACKHOLE ( _NS| _UPT )
+#define FLAGS_MVAR (_HNF| _NS| _MUT|_UPT )
#define FLAGS_FETCH_ME (_HNF| _NS )
-#define FLAGS_TSO 0
+#define FLAGS_TSO (_HNF| _NS| _MUT|_UPT )
#define FLAGS_RET_BCO ( _BTM )
#define FLAGS_RET_SMALL ( _BTM| _SRT)
#define FLAGS_RET_VEC_SMALL ( _BTM| _SRT)
#define FLAGS_RET_BIG ( _SRT)
#define FLAGS_RET_VEC_BIG ( _SRT)
#define FLAGS_RET_DYN ( _SRT)
-#define FLAGS_CATCH_FRAME 0
-#define FLAGS_STOP_FRAME 0
-#define FLAGS_SEQ_FRAME 0
-#define FLAGS_UPDATE_FRAME 0
+#define FLAGS_CATCH_FRAME ( _BTM )
+#define FLAGS_STOP_FRAME ( _BTM )
+#define FLAGS_SEQ_FRAME ( _BTM )
+#define FLAGS_UPDATE_FRAME ( _BTM )
/* -----------------------------------------------------------------------------
Info Tables
-------------------------------------------------------------------------- */
/* A large bitmap. Small 32-bit ones live in the info table, but sometimes
- * 32 bits isn't enough and we have to generate a larger one.
+ * 32 bits isn't enough and we have to generate a larger one. (sizes
+ * differ for 64-bit machines.
*/
typedef struct {
- StgNat32 size;
- StgNat32 bitmap[0];
+ StgWord size;
+ StgWord bitmap[0];
} StgLargeBitmap;
/*
* Stuff describing the closure layout. Well, actually, it might
- * contain the selector index for a THUNK_SELECTOR.
+ * contain the selector index for a THUNK_SELECTOR. If we're on a
+ * 64-bit architecture then we can enlarge some of these fields, since
+ * the union contains a pointer field.
*/
typedef union {
- StgNat32 bitmap; /* bit pattern, 1 = pointer, 0 = non-pointer */
-
+ StgWord bitmap; /* bit pattern, 1 = pointer, 0 = non-pointer */
+ StgWord selector_offset; /* used in THUNK_SELECTORs */
StgLargeBitmap* large_bitmap; /* pointer to large bitmap structure */
+#if SIZEOF_VOID_P == 8
+ struct {
+ StgNat32 ptrs; /* number of pointers */
+ StgNat32 nptrs; /* number of non-pointers */
+ } payload;
+#else
struct {
StgNat16 ptrs; /* number of pointers */
StgNat16 nptrs; /* number of non-pointers */
} payload;
+#endif
- StgNat32 selector_offset; /* used in THUNK_SELECTORs */
-
} StgClosureInfo;
/*
StgParInfo par;
StgProfInfo prof;
StgDebugInfo debug;
- StgClosureInfo layout; /* closure layout info */
+ StgClosureInfo layout; /* closure layout info (pointer-sized) */
+#if SIZEOF_VOID_P == 8
+ StgNat16 flags; /* } */
+ StgClosureType type : 16; /* } These 4 elements fit into 64 bits */
+ StgNat32 srt_len; /* } */
+#else
StgNat8 flags; /* } */
StgClosureType type : 8; /* } These 4 elements fit into 32 bits */
StgNat16 srt_len; /* } */
+#endif
#if USE_MINIINTERPRETER
StgFunPtr (*vector)[];
StgFunPtr entry;
/* -----------------------------------------------------------------------------
- * $Id: PrimOps.h,v 1.2 1998/12/02 13:21:18 simonm Exp $
+ * $Id: PrimOps.h,v 1.3 1999/01/13 17:25:53 simonm Exp $
*
* Macros for primitive operations in STG-ish C code.
*
c = z.i[C]; \
}
+
+
#define subWithCarryZh(r,c,a,b) \
{ long_long_u z; \
z.l = a + b; \
* about increasing the alignment requirements.
*/
#define REAL_BYTE_ARR_CTS(a) ((void *) (((StgArrWords *)(a))->payload))
-#define REAL_PTRS_ARR_CTS(a) ((P_) (((StgArrPtrs *)(a))->payload))
+#define REAL_PTRS_ARR_CTS(a) ((P_) (((StgMutArrPtrs *)(a))->payload))
#ifdef DEBUG
#define BYTE_ARR_CTS(a) \
({ ASSERT(GET_INFO(a) == &ARR_WORDS_info); \
REAL_BYTE_ARR_CTS(a); })
#define PTRS_ARR_CTS(a) \
- ({ ASSERT((GET_INFO(a) == &ARR_PTRS_info) \
- || (GET_INFO(a) == &MUT_ARR_PTRS_info));\
+ ({ ASSERT((GET_INFO(a) == &MUT_ARR_PTRS_info));\
REAL_PTRS_ARR_CTS(a); })
#else
#define BYTE_ARR_CTS(a) REAL_BYTE_ARR_CTS(a)
#define PTRS_ARR_CTS(a) REAL_PTRS_ARR_CTS(a)
#endif
-/* Todo: define... */
extern I_ genSymZh(void);
extern I_ resetGenSymZh(void);
-extern I_ incSeqWorldZh(void);
/*--- everything except new*Array is done inline: */
/* -----------------------------------------------------------------------------
- * $Id: Rts.h,v 1.2 1998/12/02 13:21:21 simonm Exp $
+ * $Id: Rts.h,v 1.3 1999/01/13 17:25:54 simonm Exp $
*
* Top-level include file for the RTS itself
*
/* -----------------------------------------------------------------------------
- * $Id: StgMiscClosures.h,v 1.2 1998/12/02 13:21:39 simonm Exp $
+ * $Id: StgMiscClosures.h,v 1.3 1999/01/13 17:25:54 simonm Exp $
*
* Entry code for various built-in closure types.
*
STGFUN(EMPTY_MVAR_entry);
STGFUN(ARR_WORDS_entry);
STGFUN(MUT_ARR_WORDS_entry);
-STGFUN(ARR_PTRS_entry);
STGFUN(MUT_ARR_PTRS_entry);
STGFUN(MUT_ARR_PTRS_FROZEN_entry);
STGFUN(MUT_VAR_entry);
STGFUN(END_TSO_QUEUE_entry);
+STGFUN(MUT_CONS_entry);
+STGFUN(END_MUT_LIST_entry);
STGFUN(dummy_ret_entry);
/* info tables */
extern const StgInfoTable TSO_info;
extern const StgInfoTable ARR_WORDS_info;
extern const StgInfoTable MUT_ARR_WORDS_info;
-extern const StgInfoTable ARR_PTRS_info;
extern const StgInfoTable MUT_ARR_PTRS_info;
extern const StgInfoTable MUT_ARR_PTRS_FROZEN_info;
extern const StgInfoTable MUT_VAR_info;
extern const StgInfoTable END_TSO_QUEUE_info;
+extern const StgInfoTable MUT_CONS_info;
+extern const StgInfoTable END_MUT_LIST_info;
extern const StgInfoTable catch_info;
extern const StgInfoTable seq_info;
extern const StgInfoTable dummy_ret_info;
/* closures */
extern const StgClosure END_TSO_QUEUE_closure;
+extern const StgClosure END_MUT_LIST_closure;
extern const StgClosure dummy_ret_closure;
extern StgIntCharlikeClosure CHARLIKE_closure[];
/* -----------------------------------------------------------------------------
- * $Id: TSO.h,v 1.2 1998/12/02 13:21:43 simonm Exp $
+ * $Id: TSO.h,v 1.3 1999/01/13 17:25:55 simonm Exp $
*
* The definitions for Thread State Objects.
*
* even doing 10^6 forks per second would take 35 million years to
* overflow a 64 bit thread ID :-)
*/
-typedef StgNat64 StgThreadID;
+typedef StgNat32 StgThreadID;
/*
* This type is returned to the scheduler by a thread that has
typedef struct StgTSO_ {
StgHeader header;
struct StgTSO_* link;
+ StgMutClosure * mut_link; /* TSO's are mutable of course! */
StgTSOWhatNext whatNext;
StgTSOState state; /* necessary? */
StgThreadID id;
/* -----------------------------------------------------------------------------
- * $Id: Updates.h,v 1.2 1998/12/02 13:21:47 simonm Exp $
+ * $Id: Updates.h,v 1.3 1999/01/13 17:25:55 simonm Exp $
*
* Definitions related to updates.
*
#ifndef UPDATES_H
#define UPDATES_H
-/*
- ticky-ticky wants to use permanent indirections when it's doing
- update entry counts.
- */
-
-#ifndef TICKY_TICKY
-# define Ind_info_TO_USE &IND_info
-#else
-# define Ind_info_TO_USE ((AllFlags.doUpdEntryCounts) ? &IND_PERM_info : &IND_info
-)
-#endif
-
/* -----------------------------------------------------------------------------
Update a closure with an indirection. This may also involve waking
up a queue of blocked threads waiting on the result of this
* (I think the fancy version of the GC is supposed to do this too.)
*/
+/* This expands to a fair chunk of code, what with waking up threads
+ * and checking whether we're updating something in a old generation.
+ * preferably don't use this macro inline in compiled code.
+ */
+
#define UPD_IND(updclosure, heapptr) \
TICK_UPDATED_SET_UPDATED(updclosure); \
AWAKEN_BQ(updclosure); \
- SET_INFO((StgInd*)updclosure,Ind_info_TO_USE); \
- ((StgInd *)updclosure)->indirectee = (StgClosure *)(heapptr)
+ updateWithIndirection((StgClosure *)updclosure, \
+ (StgClosure *)heapptr);
/* -----------------------------------------------------------------------------
Update a closure inplace with an infotable that expects 1 (closure)
- for the parallel system, which can implement updates more
easily if the updatee is always in the heap. (allegedly).
+
+ When debugging, we maintain a separate CAF list so we can tell when
+ a CAF has been garbage collected.
-------------------------------------------------------------------------- */
-EI_(Caf_info);
-EF_(Caf_entry);
-
/* ToDo: only call newCAF when debugging. */
extern void newCAF(StgClosure*);
/* -----------------------------------------------------------------------------
- * $Id: BlockAlloc.c,v 1.2 1998/12/02 13:28:12 simonm Exp $
+ * $Id: BlockAlloc.c,v 1.3 1999/01/13 17:25:37 simonm Exp $
*
* The block allocator and free list manager.
*
return;
}
+#ifdef DEBUG
+ p->free = (void *)-1; /* indicates that this block is free */
+ p->step = NULL;
+ p->gen = NULL;
+ /* fill the block group with garbage if sanity checking is on */
+ IF_DEBUG(sanity,memset(p->start, 0xaa, p->blocks * BLOCK_SIZE));
+#endif
+
/* find correct place in free list to place new group */
last = NULL;
for (bd = free_list; bd != NULL && bd->start < p->start;
bdescr *next_bd;
while (bd != NULL) {
next_bd = bd->link;
-#ifdef DEBUG
- bd->free = (void *)-1; /* indicates that this block is free */
-#endif
freeGroup(bd);
bd = next_bd;
}
}
}
}
+
+nat /* BLOCKS */
+countFreeList(void)
+{
+ bdescr *bd;
+ lnat total_blocks = 0;
+
+ for (bd = free_list; bd != NULL; bd = bd->link) {
+ total_blocks += bd->blocks;
+ }
+ return total_blocks;
+}
#endif
/* -----------------------------------------------------------------------------
- * $Id: BlockAlloc.h,v 1.2 1998/12/02 13:28:13 simonm Exp $
+ * $Id: BlockAlloc.h,v 1.3 1999/01/13 17:25:38 simonm Exp $
*
* Block Allocator Interface
*
#ifdef DEBUG
extern void checkFreeListSanity(void);
+nat countFreeList(void);
#endif
#endif BLOCK_ALLOC_H
/* -----------------------------------------------------------------------------
- * $Id: DebugProf.c,v 1.2 1998/12/02 13:28:14 simonm Exp $
+ * $Id: DebugProf.c,v 1.3 1999/01/13 17:25:38 simonm Exp $
*
* (c) The GHC Team 1998
*
, "MVAR"
, "ARR_WORDS"
- , "ARR_PTRS"
, "MUT_ARR_WORDS"
, "MUT_ARR_PTRS"
size = arr_words_sizeW(stgCast(StgArrWords*,p));
break;
- case ARR_PTRS:
case MUT_ARR_PTRS:
case MUT_ARR_PTRS_FROZEN:
- size = arr_ptrs_sizeW((StgArrPtrs *)p);
+ size = mut_arr_ptrs_sizeW((StgMutArrPtrs *)p);
break;
case TSO:
/* -----------------------------------------------------------------------------
- * $Id: GC.c,v 1.5 1999/01/06 12:27:47 simonm Exp $
+ * $Id: GC.c,v 1.6 1999/01/13 17:25:39 simonm Exp $
*
* Two-space garbage collector
*
StgCAF* enteredCAFs;
-static P_ toHp; /* to-space heap pointer */
-static P_ toHpLim; /* end of current to-space block */
-static bdescr *toHp_bd; /* descriptor of current to-space block */
-static nat blocks = 0; /* number of to-space blocks allocated */
-static bdescr *old_to_space = NULL; /* to-space from the last GC */
-static nat old_to_space_blocks = 0; /* size of previous to-space */
-
/* STATIC OBJECT LIST.
*
+ * During GC:
* We maintain a linked list of static objects that are still live.
* The requirements for this list are:
*
*
* An object is on the list if its static link field is non-zero; this
* means that we have to mark the end of the list with '1', not NULL.
+ *
+ * Extra notes for generational GC:
+ *
+ * Each generation has a static object list associated with it. When
+ * collecting generations up to N, we treat the static object lists
+ * from generations > N as roots.
+ *
+ * We build up a static object list while collecting generations 0..N,
+ * which is then appended to the static object list of generation N+1.
+ */
+StgClosure* static_objects; /* live static objects */
+StgClosure* scavenged_static_objects; /* static objects scavenged so far */
+
+/* N is the oldest generation being collected, where the generations
+ * are numbered starting at 0. A major GC (indicated by the major_gc
+ * flag) is when we're collecting all generations. We only attempt to
+ * deal with static objects and GC CAFs when doing a major GC.
+ */
+static nat N;
+static rtsBool major_gc;
+
+/* Youngest generation that objects should be evacuated to in
+ * evacuate(). (Logically an argument to evacuate, but it's static
+ * a lot of the time so we optimise it into a global variable).
*/
-#define END_OF_STATIC_LIST stgCast(StgClosure*,1)
-static StgClosure* static_objects;
-static StgClosure* scavenged_static_objects;
+static nat evac_gen;
/* WEAK POINTERS
*/
static StgWeak *old_weak_ptr_list; /* also pending finaliser list */
static rtsBool weak_done; /* all done for this pass */
-/* LARGE OBJECTS.
+/* Flag indicating failure to evacuate an object to the desired
+ * generation.
*/
-static bdescr *new_large_objects; /* large objects evacuated so far */
-static bdescr *scavenged_large_objects; /* large objects scavenged */
+static rtsBool failed_to_evac;
/* -----------------------------------------------------------------------------
Static function declarations
static StgClosure *evacuate(StgClosure *q);
static void zeroStaticObjectList(StgClosure* first_static);
-static void scavenge_stack(StgPtr p, StgPtr stack_end);
-static void scavenge_static(void);
-static void scavenge_large(void);
-static StgPtr scavenge(StgPtr to_scan);
static rtsBool traverse_weak_ptr_list(void);
+static void zeroMutableList(StgMutClosure *first);
static void revertDeadCAFs(void);
+static void scavenge_stack(StgPtr p, StgPtr stack_end);
+static void scavenge_large(step *step);
+static void scavenge(step *step);
+static void scavenge_static(void);
+static StgMutClosure *scavenge_mutable_list(StgMutClosure *p, nat gen);
+
#ifdef DEBUG
static void gcCAFs(void);
#endif
/* -----------------------------------------------------------------------------
GarbageCollect
- This function performs a full copying garbage collection.
+ For garbage collecting generation N (and all younger generations):
+
+ - follow all pointers in the root set. the root set includes all
+ mutable objects in all steps in all generations.
+
+ - for each pointer, evacuate the object it points to into either
+ + to-space in the next higher step in that generation, if one exists,
+ + if the object's generation == N, then evacuate it to the next
+ generation if one exists, or else to-space in the current
+ generation.
+ + if the object's generation < N, then evacuate it to to-space
+ in the next generation.
+
+ - repeatedly scavenge to-space from each step in each generation
+ being collected until no more objects can be evacuated.
+
+ - free from-space in each step, and set from-space = to-space.
+
-------------------------------------------------------------------------- */
void GarbageCollect(void (*get_roots)(void))
{
- bdescr *bd, *scan_bd, *to_space;
- StgPtr scan;
- lnat allocated, live;
- nat old_nursery_blocks = nursery_blocks; /* for stats */
- nat old_live_blocks = old_to_space_blocks; /* ditto */
+ bdescr *bd;
+ step *step;
+ lnat live, allocated, collected = 0;
+ nat g, s;
+
#ifdef PROFILING
CostCentreStack *prev_CCS;
#endif
* which case we need to call threadPaused() because the scheduler
* won't have done it.
*/
- if (CurrentTSO)
- threadPaused(CurrentTSO);
+ if (CurrentTSO) { threadPaused(CurrentTSO); }
/* Approximate how much we allocated: number of blocks in the
* nursery + blocks allocated via allocate() - unused nusery blocks.
for ( bd = current_nursery->link; bd != NULL; bd = bd->link ) {
allocated -= BLOCK_SIZE_W;
}
-
+
+ /* Figure out which generation to collect
+ */
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ if (generations[g].steps[0].n_blocks >= generations[g].max_blocks) {
+ N = g;
+ }
+ }
+ major_gc = (N == RtsFlags.GcFlags.generations-1);
+
/* check stack sanity *before* GC (ToDo: check all threads) */
/*IF_DEBUG(sanity, checkTSO(MainTSO,0)); */
IF_DEBUG(sanity, checkFreeListSanity());
+ /* Initialise the static object lists
+ */
static_objects = END_OF_STATIC_LIST;
scavenged_static_objects = END_OF_STATIC_LIST;
- new_large_objects = NULL;
- scavenged_large_objects = NULL;
+ /* zero the mutable list for the oldest generation (see comment by
+ * zeroMutableList below).
+ */
+ if (major_gc) {
+ zeroMutableList(generations[RtsFlags.GcFlags.generations-1].mut_list);
+ }
+
+ /* Initialise to-space in all the generations/steps that we're
+ * collecting.
+ */
+ for (g = 0; g <= N; g++) {
+ generations[g].mut_list = END_MUT_LIST;
+
+ for (s = 0; s < generations[g].n_steps; s++) {
+ /* generation 0, step 0 doesn't need to-space */
+ if (g == 0 && s == 0) { continue; }
+ /* Get a free block for to-space. Extra blocks will be chained on
+ * as necessary.
+ */
+ bd = allocBlock();
+ step = &generations[g].steps[s];
+ ASSERT(step->gen->no == g);
+ ASSERT(step->hp ? Bdescr(step->hp)->step == step : rtsTrue);
+ bd->gen = &generations[g];
+ bd->step = step;
+ bd->link = NULL;
+ bd->evacuated = 1; /* it's a to-space block */
+ step->hp = bd->start;
+ step->hpLim = step->hp + BLOCK_SIZE_W;
+ step->hp_bd = bd;
+ step->to_space = bd;
+ step->to_blocks = 1; /* ???? */
+ step->scan = bd->start;
+ step->scan_bd = bd;
+ step->new_large_objects = NULL;
+ step->scavenged_large_objects = NULL;
+ /* mark the large objects as not evacuated yet */
+ for (bd = step->large_objects; bd; bd = bd->link) {
+ bd->evacuated = 0;
+ }
+ }
+ }
+
+ /* make sure the older generations have at least one block to
+ * allocate into (this makes things easier for copy(), see below.
+ */
+ for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
+ for (s = 0; s < generations[g].n_steps; s++) {
+ step = &generations[g].steps[s];
+ if (step->hp_bd == NULL) {
+ bd = allocBlock();
+ bd->gen = &generations[g];
+ bd->step = step;
+ bd->link = NULL;
+ bd->evacuated = 0; /* *not* a to-space block */
+ step->hp = bd->start;
+ step->hpLim = step->hp + BLOCK_SIZE_W;
+ step->hp_bd = bd;
+ step->blocks = bd;
+ step->n_blocks = 1;
+ }
+ /* Set the scan pointer for older generations: remember we
+ * still have to scavenge objects that have been promoted. */
+ step->scan = step->hp;
+ step->scan_bd = step->hp_bd;
+ step->to_space = NULL;
+ step->to_blocks = 0;
+ step->new_large_objects = NULL;
+ step->scavenged_large_objects = NULL;
+#ifdef DEBUG
+ /* retain these so we can sanity-check later on */
+ step->old_scan = step->scan;
+ step->old_scan_bd = step->scan_bd;
+#endif
+ }
+ }
- /* Get a free block for to-space. Extra blocks will be chained on
- * as necessary.
+ /* -----------------------------------------------------------------------
+ * follow all the roots that the application knows about.
*/
- bd = allocBlock();
- bd->step = 1; /* step 1 identifies to-space */
- toHp = bd->start;
- toHpLim = toHp + BLOCK_SIZE_W;
- toHp_bd = bd;
- to_space = bd;
- blocks = 0;
-
- scan = toHp;
- scan_bd = bd;
-
- /* follow all the roots that the application knows about */
+ evac_gen = 0;
get_roots();
+ /* follow all the roots that we know about:
+ * - mutable lists from each generation > N
+ * we want to *scavenge* these roots, not evacuate them: they're not
+ * going to move in this GC.
+ * Also: do them in reverse generation order. This is because we
+ * often want to promote objects that are pointed to by older
+ * generations early, so we don't have to repeatedly copy them.
+ * Doing the generations in reverse order ensures that we don't end
+ * up in the situation where we want to evac an object to gen 3 and
+ * it has already been evaced to gen 2.
+ */
+ {
+ StgMutClosure *tmp, **pp;
+ for (g = RtsFlags.GcFlags.generations-1; g > N; g--) {
+ /* the act of scavenging the mutable list for this generation
+ * might place more objects on the mutable list itself. So we
+ * place the current mutable list in a temporary, scavenge it,
+ * and then append it to the new list.
+ */
+ tmp = generations[g].mut_list;
+ generations[g].mut_list = END_MUT_LIST;
+ tmp = scavenge_mutable_list(tmp, g);
+
+ pp = &generations[g].mut_list;
+ while (*pp != END_MUT_LIST) {
+ pp = &(*pp)->mut_link;
+ }
+ *pp = tmp;
+ }
+ }
/* And don't forget to mark the TSO if we got here direct from
* Haskell! */
if (CurrentTSO) {
}
#endif
- /* Then scavenge all the objects we picked up on the first pass.
- * We may require multiple passes to find all the static objects,
- * large objects and normal objects.
+ /* -------------------------------------------------------------------------
+ * Repeatedly scavenge all the areas we know about until there's no
+ * more scavenging to be done.
*/
{
+ rtsBool flag;
loop:
- if (static_objects != END_OF_STATIC_LIST) {
+ flag = rtsFalse;
+
+ /* scavenge static objects */
+ if (major_gc && static_objects != END_OF_STATIC_LIST) {
scavenge_static();
}
- if (toHp_bd != scan_bd || scan < toHp) {
- scan = scavenge(scan);
- scan_bd = Bdescr(scan);
- goto loop;
- }
- if (new_large_objects != NULL) {
- scavenge_large();
- goto loop;
+
+ /* When scavenging the older generations: Objects may have been
+ * evacuated from generations <= N into older generations, and we
+ * need to scavenge these objects. We're going to try to ensure that
+ * any evacuations that occur move the objects into at least the
+ * same generation as the object being scavenged, otherwise we
+ * have to create new entries on the mutable list for the older
+ * generation.
+ */
+
+ /* scavenge each step in generations 0..maxgen */
+ {
+ int gen;
+ for (gen = RtsFlags.GcFlags.generations-1; gen >= 0; gen--) {
+ for (s = 0; s < generations[gen].n_steps; s++) {
+ step = &generations[gen].steps[s];
+ evac_gen = gen;
+ if (step->hp_bd != step->scan_bd || step->scan < step->hp) {
+ scavenge(step);
+ flag = rtsTrue;
+ }
+ if (step->new_large_objects != NULL) {
+ scavenge_large(step);
+ flag = rtsTrue;
+ }
+ }
+ }
}
+ if (flag) { goto loop; }
+
/* must be last... */
if (traverse_weak_ptr_list()) { /* returns rtsTrue if evaced something */
goto loop;
}
}
- /* tidy up the end of the to-space chain */
- toHp_bd->free = toHp;
- toHp_bd->link = NULL;
+ /* run through all the generations/steps and tidy up
+ */
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ for (s = 0; s < generations[g].n_steps; s++) {
+ bdescr *next;
+ step = &generations[g].steps[s];
+
+ if (!(g == 0 && s == 0)) {
+ /* Tidy the end of the to-space chains */
+ step->hp_bd->free = step->hp;
+ step->hp_bd->link = NULL;
+ }
+
+ /* for generations we collected... */
+ if (g <= N) {
+
+ generations[g].collections++; /* for stats */
+ collected += step->n_blocks * BLOCK_SIZE_W; /* for stats */
+
+ /* free old memory and shift to-space into from-space for all
+ * the collected steps (except the allocation area). These
+ * freed blocks will probaby be quickly recycled.
+ */
+ if (!(g == 0 && s == 0)) {
+ freeChain(step->blocks);
+ step->blocks = step->to_space;
+ step->n_blocks = step->to_blocks;
+ step->to_space = NULL;
+ step->to_blocks = 0;
+ for (bd = step->blocks; bd != NULL; bd = bd->link) {
+ bd->evacuated = 0; /* now from-space */
+ }
+ }
+
+ /* LARGE OBJECTS. The current live large objects are chained on
+ * scavenged_large, having been moved during garbage
+ * collection from large_objects. Any objects left on
+ * large_objects list are therefore dead, so we free them here.
+ */
+ for (bd = step->large_objects; bd != NULL; bd = next) {
+ next = bd->link;
+ freeGroup(bd);
+ bd = next;
+ }
+ for (bd = step->scavenged_large_objects; bd != NULL; bd = bd->link) {
+ bd->evacuated = 0;
+ }
+ step->large_objects = step->scavenged_large_objects;
+
+ /* Set the maximum blocks for this generation,
+ * using an arbitrary factor of the no. of blocks in step 0.
+ */
+ if (g != 0) {
+ generation *gen = &generations[g];
+ gen->max_blocks =
+ stg_max(gen->steps[s].n_blocks * 2,
+ RtsFlags.GcFlags.minAllocAreaSize * 4);
+ if (gen->max_blocks > RtsFlags.GcFlags.maxHeapSize / 2) {
+ gen->max_blocks = RtsFlags.GcFlags.maxHeapSize / 2;
+ if (((int)gen->max_blocks - (int)gen->steps[0].n_blocks) <
+ (RtsFlags.GcFlags.pcFreeHeap *
+ RtsFlags.GcFlags.maxHeapSize / 200)) {
+ heapOverflow();
+ }
+ }
+ }
+
+ /* for older generations... */
+ } else {
+
+ /* For older generations, we need to append the
+ * scavenged_large_object list (i.e. large objects that have been
+ * promoted during this GC) to the large_object list for that step.
+ */
+ for (bd = step->scavenged_large_objects; bd; bd = next) {
+ next = bd->link;
+ bd->evacuated = 0;
+ dbl_link_onto(bd, &step->large_objects);
+ }
+
+ /* add the new blocks we promoted during this GC */
+ step->n_blocks += step->to_blocks;
+ }
+ }
+ }
/* revert dead CAFs and update enteredCAFs list */
revertDeadCAFs();
/* mark the garbage collected CAFs as dead */
#ifdef DEBUG
- gcCAFs();
+ if (major_gc) { gcCAFs(); }
#endif
- zeroStaticObjectList(scavenged_static_objects);
-
- /* approximate amount of live data (doesn't take into account slop
- * at end of each block). ToDo: this more accurately.
- */
- live = blocks * BLOCK_SIZE_W + ((lnat)toHp_bd->free -
- (lnat)toHp_bd->start) / sizeof(W_);
+ /* zero the scavenged static object list */
+ if (major_gc) {
+ zeroStaticObjectList(scavenged_static_objects);
+ }
- /* Free the to-space from the last GC, as it has now been collected.
- * we may be able to re-use these blocks in creating a new nursery,
- * below. If not, the blocks will probably be re-used for to-space
- * in the next GC.
+ /* Reset the nursery
*/
- if (old_to_space != NULL) {
- freeChain(old_to_space);
+ for (bd = g0s0->blocks; bd; bd = bd->link) {
+ bd->free = bd->start;
+ ASSERT(bd->gen == g0);
+ ASSERT(bd->step == g0s0);
+ }
+ current_nursery = g0s0->blocks;
+
+ live = 0;
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ for (s = 0; s < generations[g].n_steps; s++) {
+ /* approximate amount of live data (doesn't take into account slop
+ * at end of each block). ToDo: this more accurately.
+ */
+ if (g == 0 && s == 0) { continue; }
+ step = &generations[g].steps[s];
+ live += step->n_blocks * BLOCK_SIZE_W +
+ ((lnat)step->hp_bd->free -(lnat)step->hp_bd->start) / sizeof(W_);
+ }
}
- old_to_space = to_space;
- old_to_space_blocks = blocks;
/* Free the small objects allocated via allocate(), since this will
- * all have been copied into to-space now.
+ * all have been copied into G0S1 now.
*/
if (small_alloc_list != NULL) {
freeChain(small_alloc_list);
}
small_alloc_list = NULL;
alloc_blocks = 0;
- alloc_blocks_lim = stg_max(blocks,RtsFlags.GcFlags.minAllocAreaSize);
-
- /* LARGE OBJECTS. The current live large objects are chained on
- * scavenged_large_objects, having been moved during garbage
- * collection from large_alloc_list. Any objects left on
- * large_alloc list are therefore dead, so we free them here.
- */
- {
- bdescr *bd, *next;
- bd = large_alloc_list;
- while (bd != NULL) {
- next = bd->link;
- freeGroup(bd);
- bd = next;
- }
- large_alloc_list = scavenged_large_objects;
- }
-
+ alloc_blocks_lim = RtsFlags.GcFlags.minAllocAreaSize;
+ /* start any pending finalisers */
+ scheduleFinalisers(old_weak_ptr_list);
+
/* check sanity after GC */
- IF_DEBUG(sanity, checkHeap(to_space,1));
- /*IF_DEBUG(sanity, checkTSO(MainTSO,1)); */
- IF_DEBUG(sanity, checkFreeListSanity());
-
#ifdef DEBUG
- /* symbol-table based profiling */
- heapCensus(to_space);
-#endif
-
- /* set up a new nursery. Allocate a nursery size based on a
- * function of the amount of live data (currently a factor of 2,
- * should be configurable (ToDo)). Use the blocks from the old
- * nursery if possible, freeing up any left over blocks.
- *
- * If we get near the maximum heap size, then adjust our nursery
- * size accordingly. If the nursery is the same size as the live
- * data (L), then we need 3L bytes. We can reduce the size of the
- * nursery to bring the required memory down near 2L bytes.
- *
- * A normal 2-space collector would need 4L bytes to give the same
- * performance we get from 3L bytes, reducing to the same
- * performance at 2L bytes.
- */
- if ( blocks * 4 > RtsFlags.GcFlags.maxHeapSize ) {
- int adjusted_blocks; /* signed on purpose */
- int pc_free;
-
- adjusted_blocks = (RtsFlags.GcFlags.maxHeapSize - 2 * blocks);
- IF_DEBUG(gc, fprintf(stderr, "Near maximum heap size of 0x%x blocks, blocks = %d, adjusted to %d\n", RtsFlags.GcFlags.maxHeapSize, blocks, adjusted_blocks));
- pc_free = adjusted_blocks * 100 / RtsFlags.GcFlags.maxHeapSize;
- if (pc_free < RtsFlags.GcFlags.pcFreeHeap) /* might even be < 0 */ {
- heapOverflow();
- }
- blocks = adjusted_blocks;
-
- } else {
- blocks *= 2;
- if (blocks < RtsFlags.GcFlags.minAllocAreaSize) {
- blocks = RtsFlags.GcFlags.minAllocAreaSize;
+ for (g = 0; g <= N; g++) {
+ for (s = 0; s < generations[g].n_steps; s++) {
+ if (g == 0 && s == 0) { continue; }
+ IF_DEBUG(sanity, checkHeap(generations[g].steps[s].blocks, NULL));
+ IF_DEBUG(sanity, checkChain(generations[g].steps[s].large_objects));
}
}
-
- if (nursery_blocks < blocks) {
- IF_DEBUG(gc, fprintf(stderr, "Increasing size of nursery to %d blocks\n",
- blocks));
- nursery = allocNursery(nursery,blocks-nursery_blocks);
- } else {
- bdescr *next_bd = nursery;
-
- IF_DEBUG(gc, fprintf(stderr, "Decreasing size of nursery to %d blocks\n",
- blocks));
- for (bd = nursery; nursery_blocks > blocks; nursery_blocks--) {
- next_bd = bd->link;
- freeGroup(bd);
- bd = next_bd;
+ for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
+ for (s = 0; s < generations[g].n_steps; s++) {
+ IF_DEBUG(sanity, checkHeap(generations[g].steps[s].old_scan_bd,
+ generations[g].steps[s].old_scan));
+ IF_DEBUG(sanity, checkChain(generations[g].steps[s].large_objects));
}
- nursery = bd;
}
-
- current_nursery = nursery;
- nursery_blocks = blocks;
+ IF_DEBUG(sanity, checkFreeListSanity());
+#endif
- /* set the step number for each block in the nursery to zero */
- for (bd = nursery; bd != NULL; bd = bd->link) {
- bd->step = 0;
- bd->free = bd->start;
- }
- for (bd = to_space; bd != NULL; bd = bd->link) {
- bd->step = 0;
- }
- for (bd = large_alloc_list; bd != NULL; bd = bd->link) {
- bd->step = 0;
- }
+ IF_DEBUG(gc, stat_describe_gens());
#ifdef DEBUG
- /* check that we really have the right number of blocks in the
- * nursery, or things could really get screwed up.
- */
- {
- nat i = 0;
- for (bd = nursery; bd != NULL; bd = bd->link) {
- ASSERT(bd->free == bd->start);
- ASSERT(bd->step == 0);
- i++;
- }
- ASSERT(i == nursery_blocks);
- }
+ /* symbol-table based profiling */
+ /* heapCensus(to_space); */ /* ToDo */
#endif
- /* start any pending finalisers */
- scheduleFinalisers(old_weak_ptr_list);
-
/* restore enclosing cost centre */
#ifdef PROFILING
CCCS = prev_CCS;
#endif
+ /* check for memory leaks if sanity checking is on */
+ IF_DEBUG(sanity, memInventory());
+
/* ok, GC over: tell the stats department what happened. */
- stat_endGC(allocated,
- (old_nursery_blocks + old_live_blocks) * BLOCK_SIZE_W,
- live, "");
+ stat_endGC(allocated, collected, live, N);
}
/* -----------------------------------------------------------------------------
pointer code decide which weak pointers are dead - if there are no
new live weak pointers, then all the currently unreachable ones are
dead.
+
+ For generational GC: we just don't try to finalise weak pointers in
+ older generations than the one we're collecting. This could
+ probably be optimised by keeping per-generation lists of weak
+ pointers, but for a few weak pointers this scheme will work.
-------------------------------------------------------------------------- */
static rtsBool
if (weak_done) { return rtsFalse; }
+ /* doesn't matter where we evacuate values/finalisers to, since
+ * these pointers are treated as roots (iff the keys are alive).
+ */
+ evac_gen = 0;
+
last_w = &old_weak_ptr_list;
for (w = old_weak_ptr_list; w; w = next_w) {
target = w->key;
loop:
+ /* ignore weak pointers in older generations */
+ if (!LOOKS_LIKE_STATIC(target) && Bdescr((P_)target)->gen->no > N) {
+ IF_DEBUG(weak, fprintf(stderr,"Weak pointer still alive (in old gen) at %p\n", w));
+ /* remove this weak ptr from the old_weak_ptr list */
+ *last_w = w->link;
+ /* and put it on the new weak ptr list */
+ next_w = w->link;
+ w->link = weak_ptr_list;
+ weak_ptr_list = w;
+ flag = rtsTrue;
+ continue;
+ }
+
info = get_itbl(target);
switch (info->type) {
case IND:
case IND_STATIC:
case IND_PERM:
- case IND_OLDGEN:
+ case IND_OLDGEN: /* rely on compatible layout with StgInd */
case IND_OLDGEN_PERM:
/* follow indirections */
target = ((StgInd *)target)->indirectee;
return rtsTrue;
}
-StgClosure *MarkRoot(StgClosure *root)
+StgClosure *
+MarkRoot(StgClosure *root)
{
root = evacuate(root);
return root;
}
-static __inline__ StgClosure *copy(StgClosure *src, W_ size)
+static inline void addBlock(step *step)
+{
+ bdescr *bd = allocBlock();
+ bd->gen = step->gen;
+ bd->step = step;
+
+ if (step->gen->no <= N) {
+ bd->evacuated = 1;
+ } else {
+ bd->evacuated = 0;
+ }
+
+ step->hp_bd->free = step->hp;
+ step->hp_bd->link = bd;
+ step->hp = bd->start;
+ step->hpLim = step->hp + BLOCK_SIZE_W;
+ step->hp_bd = bd;
+ step->to_blocks++;
+}
+
+static __inline__ StgClosure *
+copy(StgClosure *src, W_ size, bdescr *bd)
{
+ step *step;
P_ to, from, dest;
- if (toHp + size >= toHpLim) {
- bdescr *bd = allocBlock();
- toHp_bd->free = toHp;
- toHp_bd->link = bd;
- bd->step = 1; /* step 1 identifies to-space */
- toHp = bd->start;
- toHpLim = toHp + BLOCK_SIZE_W;
- toHp_bd = bd;
- blocks++;
+ /* Find out where we're going, using the handy "to" pointer in
+ * the step of the source object. If it turns out we need to
+ * evacuate to an older generation, adjust it here (see comment
+ * by evacuate()).
+ */
+ step = bd->step->to;
+ if (step->gen->no < evac_gen) {
+ step = &generations[evac_gen].steps[0];
+ }
+
+ /* chain a new block onto the to-space for the destination step if
+ * necessary.
+ */
+ if (step->hp + size >= step->hpLim) {
+ addBlock(step);
}
- dest = toHp;
- toHp += size;
+ dest = step->hp;
+ step->hp += size;
for(to = dest, from = (P_)src; size>0; --size) {
*to++ = *from++;
}
return (StgClosure *)dest;
}
-static __inline__ void upd_evacuee(StgClosure *p, StgClosure *dest)
+static __inline__ void
+upd_evacuee(StgClosure *p, StgClosure *dest)
{
StgEvacuated *q = (StgEvacuated *)p;
}
/* -----------------------------------------------------------------------------
+ Evacuate a mutable object
+
+ If we evacuate a mutable object to an old generation, cons the
+ object onto the older generation's mutable list.
+ -------------------------------------------------------------------------- */
+
+static inline void
+evacuate_mutable(StgMutClosure *c)
+{
+ bdescr *bd;
+
+ bd = Bdescr((P_)c);
+ if (bd->gen->no > 0) {
+ c->mut_link = bd->gen->mut_list;
+ bd->gen->mut_list = c;
+ }
+}
+
+/* -----------------------------------------------------------------------------
Evacuate a large object
This just consists of removing the object from the (doubly-linked)
large_alloc_list, and linking it on to the (singly-linked)
new_large_objects list, from where it will be scavenged later.
+
+ Convention: bd->evacuated is /= 0 for a large object that has been
+ evacuated, or 0 otherwise.
-------------------------------------------------------------------------- */
-static inline void evacuate_large(StgPtr p)
+static inline void
+evacuate_large(StgPtr p, rtsBool mutable)
{
bdescr *bd = Bdescr(p);
+ step *step;
/* should point to the beginning of the block */
ASSERT(((W_)p & BLOCK_MASK) == 0);
/* already evacuated? */
- if (bd->step == 1) {
+ if (bd->evacuated) {
+ /* Don't forget to set the failed_to_evac flag if we didn't get
+ * the desired destination (see comments in evacuate()).
+ */
+ if (bd->gen->no < evac_gen) {
+ failed_to_evac = rtsTrue;
+ }
return;
}
- /* remove from large_alloc_list */
+ step = bd->step;
+ /* remove from large_object list */
if (bd->back) {
bd->back->link = bd->link;
} else { /* first object in the list */
- large_alloc_list = bd->link;
+ step->large_objects = bd->link;
}
if (bd->link) {
bd->link->back = bd->back;
}
- /* link it on to the evacuated large object list */
- bd->link = new_large_objects;
- new_large_objects = bd;
- bd->step = 1;
-}
+ /* link it on to the evacuated large object list of the destination step
+ */
+ step = bd->step->to;
+ if (step->gen->no < evac_gen) {
+ step = &generations[evac_gen].steps[0];
+ }
+
+ bd->step = step;
+ bd->gen = step->gen;
+ bd->link = step->new_large_objects;
+ step->new_large_objects = bd;
+ bd->evacuated = 1;
+
+ if (mutable) {
+ evacuate_mutable((StgMutClosure *)p);
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ Adding a MUT_CONS to an older generation.
+
+ This is necessary from time to time when we end up with an
+ old-to-new generation pointer in a non-mutable object. We defer
+ the promotion until the next GC.
+ -------------------------------------------------------------------------- */
+
+static StgClosure *
+mkMutCons(StgClosure *ptr, generation *gen)
+{
+ StgMutVar *q;
+ step *step;
+
+ step = &gen->steps[0];
+
+ /* chain a new block onto the to-space for the destination step if
+ * necessary.
+ */
+ if (step->hp + sizeofW(StgIndOldGen) >= step->hpLim) {
+ addBlock(step);
+ }
+
+ q = (StgMutVar *)step->hp;
+ step->hp += sizeofW(StgMutVar);
+
+ SET_HDR(q,&MUT_CONS_info,CCS_GC);
+ q->var = ptr;
+ evacuate_mutable((StgMutClosure *)q);
+
+ return (StgClosure *)q;
+}
/* -----------------------------------------------------------------------------
Evacuate
This is called (eventually) for every live object in the system.
+
+ The caller to evacuate specifies a desired generation in the
+ evac_gen global variable. The following conditions apply to
+ evacuating an object which resides in generation M when we're
+ collecting up to generation N
+
+ if M >= evac_gen
+ if M > N do nothing
+ else evac to step->to
+
+ if M < evac_gen evac to evac_gen, step 0
+
+ if the object is already evacuated, then we check which generation
+ it now resides in.
+
+ if M >= evac_gen do nothing
+ if M < evac_gen set failed_to_evac flag to indicate that we
+ didn't manage to evacuate this object into evac_gen.
+
-------------------------------------------------------------------------- */
-static StgClosure *evacuate(StgClosure *q)
+
+static StgClosure *
+evacuate(StgClosure *q)
{
StgClosure *to;
+ bdescr *bd = NULL;
const StgInfoTable *info;
loop:
+ if (!LOOKS_LIKE_STATIC(q)) {
+ bd = Bdescr((P_)q);
+ if (bd->gen->no > N) {
+ /* Can't evacuate this object, because it's in a generation
+ * older than the ones we're collecting. Let's hope that it's
+ * in evac_gen or older, or we will have to make an IND_OLDGEN object.
+ */
+ if (bd->gen->no < evac_gen) {
+ /* nope */
+ failed_to_evac = rtsTrue;
+ }
+ return q;
+ }
+ }
+
/* make sure the info pointer is into text space */
ASSERT(q && (LOOKS_LIKE_GHC_INFO(GET_INFO(q))
|| IS_HUGS_CONSTR_INFO(GET_INFO(q))));
switch (info -> type) {
case BCO:
- to = copy(q,bco_sizeW(stgCast(StgBCO*,q)));
+ to = copy(q,bco_sizeW(stgCast(StgBCO*,q)),bd);
upd_evacuee(q,to);
return to;
+ case MUT_VAR:
+ case MVAR:
+ to = copy(q,sizeW_fromITBL(info),bd);
+ upd_evacuee(q,to);
+ evacuate_mutable((StgMutClosure *)to);
+ return to;
+
case FUN:
case THUNK:
case CONSTR:
case CAF_ENTERED:
case WEAK:
case FOREIGN:
- case MUT_VAR:
- case MVAR:
- to = copy(q,sizeW_fromITBL(info));
+ to = copy(q,sizeW_fromITBL(info),bd);
upd_evacuee(q,to);
return to;
case CAF_BLACKHOLE:
case BLACKHOLE:
- to = copy(q,BLACKHOLE_sizeW());
+ to = copy(q,BLACKHOLE_sizeW(),bd);
upd_evacuee(q,to);
return to;
case THUNK_SELECTOR:
{
const StgInfoTable* selectee_info;
- StgClosure* selectee = stgCast(StgSelector*,q)->selectee;
+ StgClosure* selectee = ((StgSelector*)q)->selectee;
selector_loop:
selectee_info = get_itbl(selectee);
* with the evacuation, just update the source address with
* a pointer to the (evacuated) constructor field.
*/
- if (IS_USER_PTR(q) && Bdescr((P_)q)->step == 1) {
+ if (IS_USER_PTR(q) && Bdescr((P_)q)->evacuated) {
return q;
}
barf("evacuate: THUNK_SELECTOR: strange selectee");
}
}
- to = copy(q,THUNK_SELECTOR_sizeW());
+ to = copy(q,THUNK_SELECTOR_sizeW(),bd);
upd_evacuee(q,to);
return to;
case IND:
case IND_OLDGEN:
/* follow chains of indirections, don't evacuate them */
- q = stgCast(StgInd*,q)->indirectee;
+ q = ((StgInd*)q)->indirectee;
goto loop;
- case CONSTR_STATIC:
+ /* ToDo: optimise STATIC_LINK for known cases.
+ - FUN_STATIC : payload[0]
+ - THUNK_STATIC : payload[1]
+ - IND_STATIC : payload[1]
+ */
case THUNK_STATIC:
case FUN_STATIC:
+ if (info->srt_len == 0) { /* small optimisation */
+ return q;
+ }
+ /* fall through */
+ case CONSTR_STATIC:
case IND_STATIC:
/* don't want to evacuate these, but we do want to follow pointers
* from SRTs - see scavenge_static.
/* put the object on the static list, if necessary.
*/
- if (STATIC_LINK(info,(StgClosure *)q) == NULL) {
+ if (major_gc && STATIC_LINK(info,(StgClosure *)q) == NULL) {
STATIC_LINK(info,(StgClosure *)q) = static_objects;
static_objects = (StgClosure *)q;
}
case PAP:
/* these are special - the payload is a copy of a chunk of stack,
tagging and all. */
- to = copy(q,pap_sizeW(stgCast(StgPAP*,q)));
+ to = copy(q,pap_sizeW(stgCast(StgPAP*,q)),bd);
upd_evacuee(q,to);
return to;
case EVACUATED:
- /* Already evacuated, just return the forwarding address */
- return stgCast(StgEvacuated*,q)->evacuee;
+ /* Already evacuated, just return the forwarding address.
+ * HOWEVER: if the requested destination generation (evac_gen) is
+ * older than the actual generation (because the object was
+ * already evacuated to a younger generation) then we have to
+ * set the failed_to_evac flag to indicate that we couldn't
+ * manage to promote the object to the desired generation.
+ */
+ if (evac_gen > 0) { /* optimisation */
+ StgClosure *p = ((StgEvacuated*)q)->evacuee;
+ if (Bdescr((P_)p)->gen->no < evac_gen) {
+ /* fprintf(stderr,"evac failed!\n");*/
+ failed_to_evac = rtsTrue;
+ }
+ }
+ return ((StgEvacuated*)q)->evacuee;
case MUT_ARR_WORDS:
case ARR_WORDS:
- case MUT_ARR_PTRS:
- case MUT_ARR_PTRS_FROZEN:
- case ARR_PTRS:
{
nat size = arr_words_sizeW(stgCast(StgArrWords*,q));
if (size >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
- evacuate_large((P_)q);
+ evacuate_large((P_)q, rtsFalse);
return q;
} else {
/* just copy the block */
- to = copy(q,size);
+ to = copy(q,size,bd);
upd_evacuee(q,to);
return to;
}
}
+ case MUT_ARR_PTRS:
+ case MUT_ARR_PTRS_FROZEN:
+ {
+ nat size = mut_arr_ptrs_sizeW(stgCast(StgMutArrPtrs*,q));
+
+ if (size >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
+ evacuate_large((P_)q, info->type == MUT_ARR_PTRS);
+ to = q;
+ } else {
+ /* just copy the block */
+ to = copy(q,size,bd);
+ upd_evacuee(q,to);
+ if (info->type == MUT_ARR_PTRS) {
+ evacuate_mutable((StgMutClosure *)to);
+ }
+ }
+ return to;
+ }
+
case TSO:
{
StgTSO *tso = stgCast(StgTSO *,q);
/* Large TSOs don't get moved, so no relocation is required.
*/
if (size >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
- evacuate_large((P_)q);
+ evacuate_large((P_)q, rtsFalse);
+ tso->mut_link = NULL; /* see below */
return q;
/* To evacuate a small TSO, we need to relocate the update frame
* list it contains.
*/
} else {
- StgTSO *new_tso = (StgTSO *)copy((StgClosure *)tso,tso_sizeW(tso));
+ StgTSO *new_tso = (StgTSO *)copy((StgClosure *)tso,tso_sizeW(tso),bd);
diff = (StgPtr)new_tso - (StgPtr)tso; /* In *words* */
relocate_TSO(tso, new_tso);
upd_evacuee(q,(StgClosure *)new_tso);
+
+ /* don't evac_mutable - these things are marked mutable as
+ * required. We *do* need to zero the mut_link field, though:
+ * this TSO might have been on the mutable list for this
+ * generation, but we're collecting this generation anyway so
+ * we didn't follow the mutable list.
+ */
+ new_tso->mut_link = NULL;
+
return (StgClosure *)new_tso;
}
}
}
static inline void
-evacuate_srt(const StgInfoTable *info)
+scavenge_srt(const StgInfoTable *info)
{
StgClosure **srt, **srt_end;
}
}
-static StgPtr
-scavenge(StgPtr to_scan)
+/* -----------------------------------------------------------------------------
+ Scavenge a given step until there are no more objects in this step
+ to scavenge.
+
+ evac_gen is set by the caller to be either zero (for a step in a
+ generation < N) or G where G is the generation of the step being
+ scavenged.
+
+ We sometimes temporarily change evac_gen back to zero if we're
+ scavenging a mutable object where early promotion isn't such a good
+ idea.
+ -------------------------------------------------------------------------- */
+
+
+static void
+scavenge(step *step)
{
- StgPtr p;
+ StgPtr p, q;
const StgInfoTable *info;
bdescr *bd;
+ nat saved_evac_gen = evac_gen; /* used for temporarily changing evac_gen */
+
+ p = step->scan;
+ bd = step->scan_bd;
- p = to_scan;
- bd = Bdescr((P_)p);
+ failed_to_evac = rtsFalse;
/* scavenge phase - standard breadth-first scavenging of the
* evacuated objects
*/
- while (bd != toHp_bd || p < toHp) {
+ while (bd != step->hp_bd || p < step->hp) {
/* If we're at the end of this block, move on to the next block */
- if (bd != toHp_bd && p == bd->free) {
+ if (bd != step->hp_bd && p == bd->free) {
bd = bd->link;
p = bd->start;
continue;
}
+ q = p; /* save ptr to object */
+
ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO((StgClosure *)p))
|| IS_HUGS_CONSTR_INFO(GET_INFO((StgClosure *)p))));
bcoConstCPtr(bco,i) = evacuate(bcoConstCPtr(bco,i));
}
p += bco_sizeW(bco);
- continue;
+ break;
+ }
+
+ case MVAR:
+ /* treat MVars specially, because we don't want to evacuate the
+ * mut_link field in the middle of the closure.
+ */
+ {
+ StgMVar *mvar = ((StgMVar *)p);
+ evac_gen = 0;
+ (StgClosure *)mvar->head = evacuate((StgClosure *)mvar->head);
+ (StgClosure *)mvar->tail = evacuate((StgClosure *)mvar->tail);
+ (StgClosure *)mvar->value = evacuate((StgClosure *)mvar->value);
+ p += sizeofW(StgMVar);
+ evac_gen = saved_evac_gen;
+ break;
}
case FUN:
case THUNK:
- evacuate_srt(info);
+ scavenge_srt(info);
/* fall through */
case CONSTR:
case WEAK:
case FOREIGN:
- case MVAR:
- case MUT_VAR:
case IND_PERM:
case IND_OLDGEN_PERM:
case CAF_UNENTERED:
(StgClosure *)*p = evacuate((StgClosure *)*p);
}
p += info->layout.payload.nptrs;
- continue;
+ break;
}
+ case MUT_VAR:
+ /* ignore MUT_CONSs */
+ if (((StgMutVar *)p)->header.info != &MUT_CONS_info) {
+ evac_gen = 0;
+ ((StgMutVar *)p)->var = evacuate(((StgMutVar *)p)->var);
+ evac_gen = saved_evac_gen;
+ }
+ p += sizeofW(StgMutVar);
+ break;
+
case CAF_BLACKHOLE:
case BLACKHOLE:
{
(StgClosure *)bh->blocking_queue =
evacuate((StgClosure *)bh->blocking_queue);
p += BLACKHOLE_sizeW();
- continue;
+ break;
}
case THUNK_SELECTOR:
StgSelector *s = (StgSelector *)p;
s->selectee = evacuate(s->selectee);
p += THUNK_SELECTOR_sizeW();
- continue;
+ break;
}
case IND:
pap->fun = evacuate(pap->fun);
scavenge_stack((P_)pap->payload, (P_)pap->payload + pap->n_args);
p += pap_sizeW(pap);
- continue;
+ break;
}
case ARR_WORDS:
case MUT_ARR_WORDS:
/* nothing to follow */
p += arr_words_sizeW(stgCast(StgArrWords*,p));
- continue;
+ break;
- case ARR_PTRS:
case MUT_ARR_PTRS:
- case MUT_ARR_PTRS_FROZEN:
/* follow everything */
{
StgPtr next;
- next = p + arr_ptrs_sizeW(stgCast(StgArrPtrs*,p));
- for (p = (P_)((StgArrPtrs *)p)->payload; p < next; p++) {
+ evac_gen = 0; /* repeatedly mutable */
+ next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
(StgClosure *)*p = evacuate((StgClosure *)*p);
}
- continue;
+ evac_gen = saved_evac_gen;
+ break;
+ }
+
+ case MUT_ARR_PTRS_FROZEN:
+ /* follow everything */
+ {
+ StgPtr start = p, next;
+
+ next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
+ (StgClosure *)*p = evacuate((StgClosure *)*p);
+ }
+ if (failed_to_evac) {
+ /* we can do this easier... */
+ evacuate_mutable((StgMutClosure *)start);
+ failed_to_evac = rtsFalse;
+ }
+ break;
}
case TSO:
StgTSO *tso;
tso = (StgTSO *)p;
+ evac_gen = 0;
/* chase the link field for any TSOs on the same queue */
(StgClosure *)tso->link = evacuate((StgClosure *)tso->link);
/* scavenge this thread's stack */
scavenge_stack(tso->sp, &(tso->stack[tso->stack_size]));
+ evac_gen = saved_evac_gen;
p += tso_sizeW(tso);
- continue;
+ break;
}
case BLOCKED_FETCH:
default:
barf("scavenge");
}
+
+ /* If we didn't manage to promote all the objects pointed to by
+ * the current object, then we have to designate this object as
+ * mutable (because it contains old-to-new generation pointers).
+ */
+ if (failed_to_evac) {
+ mkMutCons((StgClosure *)q, &generations[evac_gen]);
+ failed_to_evac = rtsFalse;
+ }
}
- return (P_)p;
+
+ step->scan_bd = bd;
+ step->scan = p;
}
-/* scavenge_static is the scavenge code for a static closure.
- */
+/* -----------------------------------------------------------------------------
+ Scavenge one object.
+
+ This is used for objects that are temporarily marked as mutable
+ because they contain old-to-new generation pointers. Only certain
+ objects can have this property.
+ -------------------------------------------------------------------------- */
+static rtsBool
+scavenge_one(StgPtr p)
+{
+ StgInfoTable *info;
+ rtsBool no_luck;
+
+ ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO((StgClosure *)p))
+ || IS_HUGS_CONSTR_INFO(GET_INFO((StgClosure *)p))));
+
+ info = get_itbl((StgClosure *)p);
+
+ switch (info -> type) {
+
+ case FUN:
+ case THUNK:
+ case CONSTR:
+ case WEAK:
+ case FOREIGN:
+ case IND_PERM:
+ case IND_OLDGEN_PERM:
+ case CAF_UNENTERED:
+ case CAF_ENTERED:
+ {
+ StgPtr end;
+
+ end = (P_)((StgClosure *)p)->payload + info->layout.payload.ptrs;
+ for (p = (P_)((StgClosure *)p)->payload; p < end; p++) {
+ (StgClosure *)*p = evacuate((StgClosure *)*p);
+ }
+ break;
+ }
+
+ case CAF_BLACKHOLE:
+ case BLACKHOLE:
+ {
+ StgBlackHole *bh = (StgBlackHole *)p;
+ (StgClosure *)bh->blocking_queue =
+ evacuate((StgClosure *)bh->blocking_queue);
+ break;
+ }
+
+ case THUNK_SELECTOR:
+ {
+ StgSelector *s = (StgSelector *)p;
+ s->selectee = evacuate(s->selectee);
+ break;
+ }
+
+ case AP_UPD: /* same as PAPs */
+ case PAP:
+ /* Treat a PAP just like a section of stack, not forgetting to
+ * evacuate the function pointer too...
+ */
+ {
+ StgPAP* pap = stgCast(StgPAP*,p);
+
+ pap->fun = evacuate(pap->fun);
+ scavenge_stack((P_)pap->payload, (P_)pap->payload + pap->n_args);
+ break;
+ }
+
+ case IND_OLDGEN:
+ /* This might happen if for instance a MUT_CONS was pointing to a
+ * THUNK which has since been updated. The IND_OLDGEN will
+ * be on the mutable list anyway, so we don't need to do anything
+ * here.
+ */
+ break;
+
+ default:
+ barf("scavenge_one: strange object");
+ }
+
+ no_luck = failed_to_evac;
+ failed_to_evac = rtsFalse;
+ return (no_luck);
+}
+
+
+/* -----------------------------------------------------------------------------
+ Scavenging mutable lists.
+
+ We treat the mutable list of each generation > N (i.e. all the
+ generations older than the one being collected) as roots. We also
+ remove non-mutable objects from the mutable list at this point.
+ -------------------------------------------------------------------------- */
+
+static StgMutClosure *
+scavenge_mutable_list(StgMutClosure *p, nat gen)
+{
+ StgInfoTable *info;
+ StgMutClosure *start;
+ StgMutClosure **prev;
+
+ evac_gen = 0;
+
+ prev = &start;
+ start = p;
+
+ failed_to_evac = rtsFalse;
+
+ for (; p != END_MUT_LIST; p = *prev) {
+
+ /* make sure the info pointer is into text space */
+ ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO(p))
+ || IS_HUGS_CONSTR_INFO(GET_INFO(p))));
+
+ info = get_itbl(p);
+ switch(info->type) {
+
+ case MUT_ARR_PTRS_FROZEN:
+ /* remove this guy from the mutable list, but follow the ptrs
+ * anyway (and make sure they get promoted to this gen).
+ */
+ {
+ StgPtr end, q;
+
+ end = (P_)p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ evac_gen = gen;
+ for (q = (P_)((StgMutArrPtrs *)p)->payload; q < end; q++) {
+ (StgClosure *)*q = evacuate((StgClosure *)*q);
+ }
+ evac_gen = 0;
+
+ if (failed_to_evac) {
+ failed_to_evac = rtsFalse;
+ prev = &p->mut_link;
+ } else {
+ *prev = p->mut_link;
+ }
+ continue;
+ }
+
+ case MUT_ARR_PTRS:
+ /* follow everything */
+ prev = &p->mut_link;
+ {
+ StgPtr end, q;
+
+ end = (P_)p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ for (q = (P_)((StgMutArrPtrs *)p)->payload; q < end; q++) {
+ (StgClosure *)*q = evacuate((StgClosure *)*q);
+ }
+ continue;
+ }
+
+ case MUT_VAR:
+ /* MUT_CONS is a kind of MUT_VAR, except that we try to remove
+ * it from the mutable list if possible by promoting whatever it
+ * points to.
+ */
+ if (p->header.info == &MUT_CONS_info) {
+ evac_gen = gen;
+ if (scavenge_one((P_)((StgMutVar *)p)->var) == rtsTrue) {
+ /* didn't manage to promote everything, so leave the
+ * MUT_CONS on the list.
+ */
+ prev = &p->mut_link;
+ } else {
+ *prev = p->mut_link;
+ }
+ evac_gen = 0;
+ } else {
+ ((StgMutVar *)p)->var = evacuate(((StgMutVar *)p)->var);
+ prev = &p->mut_link;
+ }
+ continue;
+
+ case TSO:
+ /* follow ptrs and remove this from the mutable list */
+ {
+ StgTSO *tso = (StgTSO *)p;
+
+ /* Don't bother scavenging if this thread is dead
+ */
+ if (!(tso->whatNext == ThreadComplete ||
+ tso->whatNext == ThreadKilled)) {
+ /* Don't need to chase the link field for any TSOs on the
+ * same queue. Just scavenge this thread's stack
+ */
+ scavenge_stack(tso->sp, &(tso->stack[tso->stack_size]));
+ }
+
+ /* Don't take this TSO off the mutable list - it might still
+ * point to some younger objects (because we set evac_gen to 0
+ * above).
+ */
+ prev = &tso->mut_link;
+ continue;
+ }
+
+ case IND_OLDGEN:
+ case IND_OLDGEN_PERM:
+ case IND_STATIC:
+ /* Try to pull the indirectee into this generation, so we can
+ * remove the indirection from the mutable list.
+ */
+ evac_gen = gen;
+ ((StgIndOldGen *)p)->indirectee =
+ evacuate(((StgIndOldGen *)p)->indirectee);
+ evac_gen = 0;
+
+ if (failed_to_evac) {
+ failed_to_evac = rtsFalse;
+ prev = &p->mut_link;
+ } else {
+ *prev = p->mut_link;
+ /* the mut_link field of an IND_STATIC is overloaded as the
+ * static link field too (it just so happens that we don't need
+ * both at the same time), so we need to NULL it out when
+ * removing this object from the mutable list because the static
+ * link fields are all assumed to be NULL before doing a major
+ * collection.
+ */
+ p->mut_link = NULL;
+ }
+ continue;
+
+ default:
+ /* shouldn't have anything else on the mutables list */
+ barf("scavenge_mutable_object: non-mutable object?");
+ }
+ }
+ return start;
+}
static void
scavenge_static(void)
StgClosure* p = static_objects;
const StgInfoTable *info;
+ /* Always evacuate straight to the oldest generation for static
+ * objects */
+ evac_gen = oldest_gen->no;
+
/* keep going until we've scavenged all the objects on the linked
list... */
while (p != END_OF_STATIC_LIST) {
+ info = get_itbl(p);
+
/* make sure the info pointer is into text space */
- ASSERT(p && LOOKS_LIKE_GHC_INFO(GET_INFO(p)));
ASSERT(p && (LOOKS_LIKE_GHC_INFO(GET_INFO(p))
|| IS_HUGS_CONSTR_INFO(GET_INFO(p))));
-
- info = get_itbl(p);
-
+
/* Take this object *off* the static_objects list,
* and put it on the scavenged_static_objects list.
*/
static_objects = STATIC_LINK(info,p);
STATIC_LINK(info,p) = scavenged_static_objects;
scavenged_static_objects = p;
-
+
switch (info -> type) {
-
+
case IND_STATIC:
{
StgInd *ind = (StgInd *)p;
case THUNK_STATIC:
case FUN_STATIC:
- evacuate_srt(info);
+ scavenge_srt(info);
/* fall through */
-
+
case CONSTR_STATIC:
{
StgPtr q, next;
StgClosure *to;
StgClosureType type = get_itbl(frame->updatee)->type;
+ p += sizeofW(StgUpdateFrame);
if (type == EVACUATED) {
frame->updatee = evacuate(frame->updatee);
- p += sizeofW(StgUpdateFrame);
continue;
} else {
+ bdescr *bd = Bdescr((P_)frame->updatee);
ASSERT(type == BLACKHOLE || type == CAF_BLACKHOLE);
- to = copy(frame->updatee, BLACKHOLE_sizeW());
+ if (bd->gen->no >= evac_gen && bd->gen->no > N) { continue; }
+ to = copy(frame->updatee, BLACKHOLE_sizeW(), bd);
upd_evacuee(frame->updatee,to);
frame->updatee = to;
- p += sizeofW(StgUpdateFrame);
continue;
}
}
- /* small bitmap (< 32 entries) */
+ /* small bitmap (< 32 entries, or 64 on a 64-bit machine) */
case RET_BCO:
case RET_SMALL:
case RET_VEC_SMALL:
}
follow_srt:
- evacuate_srt(info);
+ scavenge_srt(info);
continue;
/* large bitmap (> 32 entries) */
/*-----------------------------------------------------------------------------
scavenge the large object list.
+
+ evac_gen set by caller; similar games played with evac_gen as with
+ scavenge() - see comment at the top of scavenge(). Most large
+ objects are (repeatedly) mutable, so most of the time evac_gen will
+ be zero.
--------------------------------------------------------------------------- */
static void
-scavenge_large(void)
+scavenge_large(step *step)
{
bdescr *bd;
StgPtr p;
const StgInfoTable* info;
+ nat saved_evac_gen = evac_gen; /* used for temporarily changing evac_gen */
- bd = new_large_objects;
+ evac_gen = 0; /* most objects are mutable */
+ bd = step->new_large_objects;
- for (; bd != NULL; bd = new_large_objects) {
+ for (; bd != NULL; bd = step->new_large_objects) {
/* take this object *off* the large objects list and put it on
* the scavenged large objects list. This is so that we can
* treat new_large_objects as a stack and push new objects on
* the front when evacuating.
*/
- new_large_objects = bd->link;
- /* scavenged_large_objects is doubly linked */
- bd->link = scavenged_large_objects;
- bd->back = NULL;
- if (scavenged_large_objects) {
- scavenged_large_objects->back = bd;
- }
- scavenged_large_objects = bd;
+ step->new_large_objects = bd->link;
+ dbl_link_onto(bd, &step->scavenged_large_objects);
p = bd->start;
info = get_itbl(stgCast(StgClosure*,p));
/* nothing to follow */
continue;
- case ARR_PTRS:
case MUT_ARR_PTRS:
- case MUT_ARR_PTRS_FROZEN:
/* follow everything */
{
StgPtr next;
- next = p + arr_ptrs_sizeW(stgCast(StgArrPtrs*,p));
- for (p = (P_)((StgArrPtrs *)p)->payload; p < next; p++) {
+ next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
(StgClosure *)*p = evacuate((StgClosure *)*p);
}
continue;
}
+ case MUT_ARR_PTRS_FROZEN:
+ /* follow everything */
+ {
+ StgPtr start = p, next;
+
+ evac_gen = saved_evac_gen; /* not really mutable */
+ next = p + mut_arr_ptrs_sizeW((StgMutArrPtrs*)p);
+ for (p = (P_)((StgMutArrPtrs *)p)->payload; p < next; p++) {
+ (StgClosure *)*p = evacuate((StgClosure *)*p);
+ }
+ evac_gen = 0;
+ if (failed_to_evac) {
+ evacuate_mutable((StgMutClosure *)start);
+ }
+ continue;
+ }
+
case BCO:
{
StgBCO* bco = stgCast(StgBCO*,p);
nat i;
+ evac_gen = saved_evac_gen;
for (i = 0; i < bco->n_ptrs; i++) {
bcoConstCPtr(bco,i) = evacuate(bcoConstCPtr(bco,i));
}
+ evac_gen = 0;
continue;
}
}
}
}
+
static void
zeroStaticObjectList(StgClosure* first_static)
{
}
}
+/* This function is only needed because we share the mutable link
+ * field with the static link field in an IND_STATIC, so we have to
+ * zero the mut_link field before doing a major GC, which needs the
+ * static link field.
+ *
+ * It doesn't do any harm to zero all the mutable link fields on the
+ * mutable list.
+ */
+static void
+zeroMutableList(StgMutClosure *first)
+{
+ StgMutClosure *next, *c;
+
+ for (c = first; c != END_MUT_LIST; c = next) {
+ next = c->mut_link;
+ c->mut_link = NULL;
+ }
+}
+
/* -----------------------------------------------------------------------------
Reverting CAFs
-
-------------------------------------------------------------------------- */
void RevertCAFs(void)
{
- while (enteredCAFs != END_CAF_LIST) {
- StgCAF* caf = enteredCAFs;
- const StgInfoTable *info = get_itbl(caf);
-
- enteredCAFs = caf->link;
- ASSERT(get_itbl(caf)->type == CAF_ENTERED);
- SET_INFO(caf,&CAF_UNENTERED_info);
- caf->value = stgCast(StgClosure*,0xdeadbeef);
- caf->link = stgCast(StgCAF*,0xdeadbeef);
- }
+ while (enteredCAFs != END_CAF_LIST) {
+ StgCAF* caf = enteredCAFs;
+
+ enteredCAFs = caf->link;
+ ASSERT(get_itbl(caf)->type == CAF_ENTERED);
+ SET_INFO(caf,&CAF_UNENTERED_info);
+ caf->value = stgCast(StgClosure*,0xdeadbeef);
+ caf->link = stgCast(StgCAF*,0xdeadbeef);
+ }
}
void revertDeadCAFs(void)
if (bh->header.info != &BLACKHOLE_info
&& bh->header.info != &CAF_BLACKHOLE_info) {
SET_INFO(bh,&BLACKHOLE_info);
- bh->blocking_queue = stgCast(StgTSO*,&END_TSO_QUEUE_closure);
+ bh->blocking_queue = END_TSO_QUEUE;
}
update_frame = update_frame->link;
&& bh->header.info != &CAF_BLACKHOLE_info
) {
SET_INFO(bh,&BLACKHOLE_info);
- bh->blocking_queue = stgCast(StgTSO*,&END_TSO_QUEUE_closure);
+ bh->blocking_queue = END_TSO_QUEUE;
}
}
/* -----------------------------------------------------------------------------
- * $Id: MBlock.c,v 1.2 1998/12/02 13:28:28 simonm Exp $
+ * $Id: MBlock.c,v 1.3 1999/01/13 17:25:40 simonm Exp $
*
* MegaBlock Allocator Interface. This file contains all the dirty
* architecture-dependent hackery required to get a chunk of aligned
/* ToDo: memory locations on other architectures */
#endif
+lnat mblocks_allocated = 0;
+
void *
getMBlock(void)
{
next_request += size;
+ mblocks_allocated += n;
+
return ret;
}
/* -----------------------------------------------------------------------------
- * $Id: MBlock.h,v 1.2 1998/12/02 13:28:30 simonm Exp $
+ * $Id: MBlock.h,v 1.3 1999/01/13 17:25:41 simonm Exp $
*
* MegaBlock Allocator interface.
*
* ---------------------------------------------------------------------------*/
+extern lnat mblocks_allocated;
+
extern void * getMBlock(void);
extern void * getMBlocks(nat n);
#-----------------------------------------------------------------------------
-# $Id: Makefile,v 1.2 1998/12/02 13:28:32 simonm Exp $
+# $Id: Makefile,v 1.3 1999/01/13 17:25:41 simonm Exp $
# This is the Makefile for the runtime-system stuff.
# This stuff is written in C (and cannot be written in Haskell).
/* -----------------------------------------------------------------------------
- * $Id: PrimOps.hc,v 1.2 1998/12/02 13:28:32 simonm Exp $
+ * $Id: PrimOps.hc,v 1.3 1999/01/13 17:25:41 simonm Exp $
*
* Primitive functions / data
*
FN_(newArrayZh_fast)
{
W_ size, n, init;
- StgArrPtrs* arr;
+ StgMutArrPtrs* arr;
StgPtr p;
FB_
n = R1.w;
MAYBE_GC(R2_PTR,newArrayZh_fast);
- size = sizeofW(StgArrPtrs) + n;
- arr = (StgArrPtrs *)allocate(size);
+ size = sizeofW(StgMutArrPtrs) + n;
+ arr = (StgMutArrPtrs *)allocate(size);
SET_HDR(arr,&MUT_ARR_PTRS_info,CCCS);
arr->ptrs = n;
init = R2.w;
- for (p = (P_)arr + sizeofW(StgArrPtrs);
+ for (p = (P_)arr + sizeofW(StgMutArrPtrs);
p < (P_)arr + size; p++) {
*p = (W_)init;
}
/* -----------------------------------------------------------------------------
- * $Id: RtsFlags.c,v 1.2 1998/12/02 13:28:39 simonm Exp $
+ * $Id: RtsFlags.c,v 1.3 1999/01/13 17:25:42 simonm Exp $
*
* Functions for parsing the argument list.
*
RtsFlags.GcFlags.minAllocAreaSize = (256 * 1024) / BLOCK_SIZE;
RtsFlags.GcFlags.maxHeapSize = (64 * 1024 * 1024) / BLOCK_SIZE;
RtsFlags.GcFlags.pcFreeHeap = 3; /* 3% */
+ RtsFlags.GcFlags.generations = 2;
- RtsFlags.GcFlags.force2s = rtsFalse;
RtsFlags.GcFlags.forceGC = rtsFalse;
RtsFlags.GcFlags.forcingInterval = 5000000; /* 5MB (or words?) */
RtsFlags.GcFlags.ringBell = rtsFalse;
" -A<size> Sets the minimum allocation area size (default 256k) Egs: -A1m -A10k",
" -M<size> Sets the maximum heap size (default 64M) Egs: -H256k -H1G",
" -m<n>% Minimum % of heap which must be available (default 3%)",
+" -G<n> Number of generations (default: 2)",
" -s<file> Summary GC statistics (default file: <program>.stat)",
" -S<file> Detailed GC statistics (with -Sstderr going to stderr)",
"",
break;
#endif
- case 'F':
- if (strequal(rts_argv[arg]+2, "2s")) {
- RtsFlags.GcFlags.force2s = rtsTrue;
- } else {
- bad_option( rts_argv[arg] );
- }
- break;
-
case 'K':
RtsFlags.GcFlags.maxStkSize =
decode(rts_argv[arg]+2) / sizeof(W_);
bad_option( rts_argv[arg] );
break;
+ case 'G':
+ RtsFlags.GcFlags.generations = decode(rts_argv[arg]+2);
+ if (RtsFlags.GcFlags.generations <= 1) {
+ bad_option(rts_argv[arg]);
+ }
+ break;
+
case 'H':
/* ignore for compatibility with older versions */
break;
/* -----------------------------------------------------------------------------
- * $Id: RtsFlags.h,v 1.2 1998/12/02 13:28:40 simonm Exp $
+ * $Id: RtsFlags.h,v 1.3 1999/01/13 17:25:43 simonm Exp $
*
* Datatypes that holds the command-line flag settings.
*
nat minAllocAreaSize; /* in *blocks* */
double pcFreeHeap;
- rtsBool force2s; /* force the use of 2-space copying collection;
- forced to rtsTrue if we do *heap* profiling.
- */
+ nat generations;
+
rtsBool forceGC; /* force a major GC every <interval> bytes */
int forcingInterval; /* actually, stored as a number of *words* */
rtsBool ringBell;
/* -----------------------------------------------------------------------------
- * $Id: Sanity.c,v 1.2 1998/12/02 13:28:43 simonm Exp $
+ * $Id: Sanity.c,v 1.3 1999/01/13 17:25:43 simonm Exp $
*
* Sanity checking code for the heap and stack.
*
#include "BlockAlloc.h"
#include "Sanity.h"
-static nat heap_step;
-
#define LOOKS_LIKE_PTR(r) \
- (IS_DATA_PTR(r) || ((IS_USER_PTR(r) && Bdescr((P_)r)->step == heap_step)))
+ (IS_DATA_PTR(r) || ((IS_USER_PTR(r) && Bdescr((P_)r)->free != (void *)-1)))
/* -----------------------------------------------------------------------------
Check stack sanity
case THUNK:
case CONSTR:
case IND_PERM:
+ case IND_OLDGEN:
case IND_OLDGEN_PERM:
case CAF_UNENTERED:
case CAF_ENTERED:
return sizeofW(StgHeader) + MIN_UPD_SIZE;
case IND:
- case IND_OLDGEN:
{
/* we don't expect to see any of these after GC
* but they might appear during execution
*/
+ P_ q;
StgInd *ind = stgCast(StgInd*,p);
ASSERT(LOOKS_LIKE_PTR(ind->indirectee));
- return sizeofW(StgInd);
+ q = (P_)p + sizeofW(StgInd);
+ while (!*q) { q++; }; /* skip padding words (see GC.c: evacuate())*/
+ return q - (P_)p;
}
case RET_BCO:
case MUT_ARR_WORDS:
return arr_words_sizeW(stgCast(StgArrWords*,p));
- case ARR_PTRS:
case MUT_ARR_PTRS:
case MUT_ARR_PTRS_FROZEN:
{
- StgArrPtrs* a = stgCast(StgArrPtrs*,p);
+ StgMutArrPtrs* a = stgCast(StgMutArrPtrs*,p);
nat i;
for (i = 0; i < a->ptrs; i++) {
- ASSERT(LOOKS_LIKE_PTR(payloadPtr(a,i)));
+ ASSERT(LOOKS_LIKE_PTR(a->payload[i]));
}
- return arr_ptrs_sizeW(a);
+ return mut_arr_ptrs_sizeW(a);
}
case TSO:
- checkTSO((StgTSO *)p, heap_step);
+ checkTSO((StgTSO *)p);
return tso_sizeW((StgTSO *)p);
case BLOCKED_FETCH:
After garbage collection, the live heap is in a state where we can
run through and check that all the pointers point to the right
- place.
+ place. This function starts at a given position and sanity-checks
+ all the objects in the remainder of the chain.
-------------------------------------------------------------------------- */
extern void
-checkHeap(bdescr *bd, nat step)
+checkHeap(bdescr *bd, StgPtr start)
{
StgPtr p;
- heap_step = step;
+ if (start == NULL) {
+ p = bd->start;
+ } else {
+ p = start;
+ }
while (bd != NULL) {
- p = bd->start;
while (p < bd->free) {
nat size = checkClosure(stgCast(StgClosure*,p));
/* This is the smallest size of closure that can live in the heap. */
ASSERT( size >= MIN_NONUPD_SIZE + sizeofW(StgHeader) );
p += size;
+ while (*p == 0) { p++; } /* skip over slop */
}
bd = bd->link;
+ if (bd != NULL) {
+ p = bd->start;
+ }
}
-}
+}
+
+extern void
+checkChain(bdescr *bd)
+{
+ while (bd != NULL) {
+ checkClosure((StgClosure *)bd->start);
+ bd = bd->link;
+ }
+}
/* check stack - making sure that update frames are linked correctly */
void
}
extern void
-checkTSO(StgTSO *tso, nat step)
+checkTSO(StgTSO *tso)
{
StgPtr sp = tso->sp;
StgPtr stack = tso->stack;
StgOffset stack_size = tso->stack_size;
StgPtr stack_end = stack + stack_size;
- heap_step = step;
+ if (tso->whatNext == ThreadComplete || tso->whatNext == ThreadKilled) {
+ /* The garbage collector doesn't bother following any pointers
+ * from dead threads, so don't check sanity here.
+ */
+ return;
+ }
ASSERT(stack <= sp && sp < stack_end);
ASSERT(sp <= stgCast(StgPtr,su));
/* -----------------------------------------------------------------------------
- * $Id: Sanity.h,v 1.2 1998/12/02 13:28:44 simonm Exp $
+ * $Id: Sanity.h,v 1.3 1999/01/13 17:25:44 simonm Exp $
*
* Prototypes for functions in Sanity.c
*
#ifdef DEBUG
/* debugging routines */
-extern void checkHeap ( bdescr *bd, nat step );
+extern void checkHeap ( bdescr *bd, StgPtr start );
+extern void checkChain ( bdescr *bd );
extern void checkStack ( StgPtr sp, StgPtr stack_end, StgUpdateFrame* su );
-extern void checkTSO ( StgTSO* tso, nat step );
+extern void checkTSO ( StgTSO* tso );
extern StgOffset checkClosure( StgClosure* p );
/* -----------------------------------------------------------------------------
- * $Id: Schedule.c,v 1.3 1999/01/06 11:44:44 simonm Exp $
+ * $Id: Schedule.c,v 1.4 1999/01/13 17:25:44 simonm Exp $
*
* Scheduler
*
SET_HDR((StgClosure*)tso->sp,(StgInfoTable *)&stg_stop_thread_info,CCS_MAIN);
tso->su = (StgUpdateFrame*)tso->sp;
- IF_DEBUG(scheduler,belch("Initialised thread %lld, stack size = %lx words\n",
+ IF_DEBUG(scheduler,belch("Initialised thread %ld, stack size = %lx words\n",
tso->id, tso->stack_size));
/* Put the new thread on the head of the runnable queue.
return;
}
- IF_DEBUG(scheduler, belch("Killing thread %lld.", tso->id));
+ IF_DEBUG(scheduler, belch("Killing thread %ld.", tso->id));
tso->whatNext = ThreadKilled; /* changed to ThreadComplete in schedule() */
tso->link = END_TSO_QUEUE; /* Just to be on the safe side... */
ccalling_threads = CurrentTSO;
in_ccall_gc = rtsTrue;
IF_DEBUG(scheduler,
- fprintf(stderr, "Re-entry, thread %lld did a _ccall_gc\n",
+ fprintf(stderr, "Re-entry, thread %d did a _ccall_gc\n",
CurrentTSO->id););
} else {
in_ccall_gc = rtsFalse;
} else {
context_switch = 0;
}
- IF_DEBUG(scheduler, belch("Running thread %lld...\n", t->id));
+ IF_DEBUG(scheduler, belch("Running thread %ld...\n", t->id));
+
+ /* Be friendly to the storage manager: we're about to *run* this
+ * thread, so we better make sure the TSO is mutable.
+ */
+ recordMutable((StgMutClosure *)t);
/* Run the current thread */
switch (t->whatNext) {
switch (ret) {
case HeapOverflow:
- IF_DEBUG(scheduler,belch("Thread %lld stopped: HeapOverflow\n", t->id));
+ IF_DEBUG(scheduler,belch("Thread %ld stopped: HeapOverflow\n", t->id));
threadPaused(t);
PUSH_ON_RUN_QUEUE(t);
GarbageCollect(GetRoots);
break;
case StackOverflow:
- IF_DEBUG(scheduler,belch("Thread %lld stopped, StackOverflow\n", t->id));
+ IF_DEBUG(scheduler,belch("Thread %ld stopped, StackOverflow\n", t->id));
{
nat i;
/* enlarge the stack */
/* ToDo: or maybe a timer expired when we were in Hugs?
* or maybe someone hit ctrl-C
*/
- belch("Thread %lld stopped to switch to Hugs\n", t->id);
+ belch("Thread %ld stopped to switch to Hugs\n", t->id);
} else {
- belch("Thread %lld stopped, timer expired\n", t->id);
+ belch("Thread %ld stopped, timer expired\n", t->id);
}
);
threadPaused(t);
break;
case ThreadBlocked:
- IF_DEBUG(scheduler,belch("Thread %lld stopped, blocking\n", t->id));
+ IF_DEBUG(scheduler,belch("Thread %ld stopped, blocking\n", t->id));
threadPaused(t);
/* assume the thread has put itself on some blocked queue
* somewhere.
break;
case ThreadFinished:
- IF_DEBUG(scheduler,belch("Thread %lld finished\n", t->id));
+ IF_DEBUG(scheduler,belch("Thread %ld finished\n", t->id));
deleteThread(t);
t->whatNext = ThreadComplete;
break;
/* and relocate the update frame list */
relocate_TSO(tso, dest);
- IF_DEBUG(sanity,checkTSO(tso,0)); /* Step 0 because we're not GC'ing. */
+ /* Mark the old one as dead so we don't try to scavenge it during
+ * garbage collection (the TSO will likely be on a mutables list in
+ * some generation, but it'll get collected soon enough).
+ */
+ tso->whatNext = ThreadKilled;
+ dest->mut_link = NULL;
+
+ IF_DEBUG(sanity,checkTSO(tso));
#if 0
IF_DEBUG(scheduler,printTSO(dest));
#endif
tso = q;
q = tso->link;
PUSH_ON_RUN_QUEUE(tso);
- IF_DEBUG(scheduler,belch("Waking up thread %lld", tso->id));
+ IF_DEBUG(scheduler,belch("Waking up thread %ld", tso->id));
}
}
/* -----------------------------------------------------------------------------
- * $Id: Stats.c,v 1.2 1998/12/02 13:28:49 simonm Exp $
+ * $Id: Stats.c,v 1.3 1999/01/13 17:25:46 simonm Exp $
*
* Statistics and timing-related functions.
*
#include "Rts.h"
#include "RtsFlags.h"
#include "RtsUtils.h"
+#include "StoragePriv.h"
+#include "MBlock.h"
/**
* Ian: For the moment we just want to ignore
static StgDouble GC_start_time, GC_tot_time = 0; /* User GC Time */
static StgDouble GCe_start_time, GCe_tot_time = 0; /* Elapsed GC time */
-static StgDouble GC_min_time = 0;
-static StgDouble GCe_min_time = 0;
-static lnat GC_maj_no = 0;
-static lnat GC_min_no = 0;
-static lnat GC_min_since_maj = 0;
-static lnat GC_live_maj = 0; /* Heap live at last major collection */
-static lnat GC_alloc_since_maj = 0; /* Heap alloc since collection major */
-
lnat MaxResidency = 0; /* in words; for stats only */
lnat ResidencySamples = 0; /* for stats only */
FILE *sf = RtsFlags.GcFlags.statsFile;
if (RtsFlags.GcFlags.giveStats) {
- fprintf(sf, " Alloc Collect Live Resid GC GC TOT TOT Page Flts\n");
- fprintf(sf, " bytes bytes bytes ency user elap user elap\n");
+ fprintf(sf, " Alloc Collect Live GC GC TOT TOT Page Flts\n");
+ fprintf(sf, " bytes bytes bytes user elap user elap\n");
}
}
-------------------------------------------------------------------------- */
void
-stat_endGC(lnat alloc, lnat collect, lnat live, char *comment)
+stat_endGC(lnat alloc, lnat collect, lnat live, lnat gen)
{
FILE *sf = RtsFlags.GcFlags.statsFile;
if (RtsFlags.GcFlags.giveStats) {
nat faults = pagefaults();
- fprintf(sf, "%8ld %7ld %7ld %5.1f%%",
- alloc*sizeof(W_), collect*sizeof(W_), live*sizeof(W_), collect == 0 ? 0.0 : (live / (StgDouble) collect * 100));
- fprintf(sf, " %5.2f %5.2f %7.2f %7.2f %4ld %4ld %s\n",
+ fprintf(sf, "%9ld %9ld %9ld",
+ alloc*sizeof(W_), collect*sizeof(W_), live*sizeof(W_));
+ fprintf(sf, " %5.2f %5.2f %7.2f %7.2f %4ld %4ld (Gen: %2ld)\n",
(time-GC_start_time),
(etime-GCe_start_time),
time,
etime,
faults - GC_start_faults,
GC_start_faults - GC_end_faults,
- comment);
+ gen);
GC_end_faults = faults;
fflush(sf);
}
- GC_maj_no++;
GC_tot_alloc += (ullong) alloc;
GC_tot_time += time-GC_start_time;
GCe_tot_time += etime-GCe_start_time;
+
+ if (gen == RtsFlags.GcFlags.generations-1) { /* major GC? */
+ if (live > MaxResidency) {
+ MaxResidency = live;
+ }
+ ResidencySamples++;
+ }
}
if (rub_bell) {
if (etime == 0.0) etime = 0.0001;
- if (RtsFlags.GcFlags.giveStats) {
- fprintf(sf, "%8d\n\n", alloc*sizeof(W_));
- }
+ fprintf(sf, "%9ld %9.9s %9.9s",
+ (lnat)alloc*sizeof(W_), "", "");
+ fprintf(sf, " %5.2f %5.2f\n\n", 0.0, 0.0);
+
+ GC_tot_alloc += alloc;
- else {
- fprintf(sf, "%8ld %7.7s %6.6s %7.7s %6.6s",
- (GC_alloc_since_maj + alloc)*sizeof(W_), "", "", "", "");
- fprintf(sf, " %3ld %5.2f %5.2f\n\n",
- GC_min_since_maj, GC_min_time, GCe_min_time);
- }
- GC_min_no += GC_min_since_maj;
- GC_tot_time += GC_min_time;
- GCe_tot_time += GCe_min_time;
- GC_tot_alloc += GC_alloc_since_maj + alloc;
ullong_format_string(GC_tot_alloc*sizeof(W_), temp, rtsTrue/*commas*/);
fprintf(sf, "%11s bytes allocated in the heap\n", temp);
+
if ( ResidencySamples > 0 ) {
ullong_format_string(MaxResidency*sizeof(W_), temp, rtsTrue/*commas*/);
- fprintf(sf, "%11s bytes maximum residency (%.1f%%, %ld sample(s))\n",
+ fprintf(sf, "%11s bytes maximum residency (%ld sample(s))\n",
temp,
- MaxResidency / (StgDouble) RtsFlags.GcFlags.maxHeapSize * 100,
ResidencySamples);
}
- fprintf(sf, "%11ld garbage collections performed (%ld major, %ld minor)\n\n",
- GC_maj_no + GC_min_no, GC_maj_no, GC_min_no);
+ fprintf(sf,"\n");
+
+ { /* Count garbage collections */
+ nat g;
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ fprintf(sf, "%11d collections in generation %d\n",
+ generations[g].collections, g);
+ }
+ }
+ fprintf(sf,"\n%11ld Mb total memory in use\n\n",
+ mblocks_allocated * MBLOCK_SIZE / (1024 * 1024));
MutTime = time - GC_tot_time - InitUserTime;
if (MutTime < 0) { MutTime = 0; }
fclose(sf);
}
}
+
+/* -----------------------------------------------------------------------------
+ stat_describe_gens
+
+ Produce some detailed info on the state of the generational GC.
+ -------------------------------------------------------------------------- */
+void
+stat_describe_gens(void)
+{
+ nat g, s, mut, lge, live;
+ StgMutClosure *m;
+ bdescr *bd;
+ step *step;
+
+ fprintf(stderr, " Gen Steps Max Mutable Step Blocks Live Large\n" " Blocks Closures Objects\n");
+
+ for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ for (m = generations[g].mut_list, mut = 0; m != END_MUT_LIST;
+ m = m->mut_link)
+ mut++;
+ fprintf(stderr, "%8d %8d %8d %9d", g, generations[g].n_steps,
+ generations[g].max_blocks, mut);
+
+ for (s = 0; s < generations[g].n_steps; s++) {
+ step = &generations[g].steps[s];
+ for (bd = step->large_objects, lge = 0; bd; bd = bd->link)
+ lge++;
+ live = 0;
+ for (bd = step->blocks; bd; bd = bd->link) {
+ live += (bd->free - bd->start) * sizeof(W_);
+ }
+ if (s != 0) {
+ fprintf(stderr,"%36s","");
+ }
+ fprintf(stderr,"%6d %8d %8d %8d\n", s, step->n_blocks,
+ live, lge);
+ }
+ }
+ fprintf(stderr,"\n");
+}
/* -----------------------------------------------------------------------------
- * $Id: Stats.h,v 1.2 1998/12/02 13:28:50 simonm Exp $
+ * $Id: Stats.h,v 1.3 1999/01/13 17:25:46 simonm Exp $
*
* Statistics and timing-related functions.
*
extern void end_init(void);
extern void stat_exit(int alloc);
extern void stat_startGC(void);
-extern void stat_endGC(lnat alloc, lnat collect, lnat live,
- char *comment);
+extern void stat_endGC(lnat alloc, lnat collect, lnat live, lnat gen);
extern void initStats(void);
+extern void stat_describe_gens(void);
/* -----------------------------------------------------------------------------
- * $Id: StgMiscClosures.hc,v 1.2 1998/12/02 13:28:52 simonm Exp $
+ * $Id: StgMiscClosures.hc,v 1.3 1999/01/13 17:25:46 simonm Exp $
*
* Entry code for various built-in closure types.
*
FE_
}
-INFO_TABLE(IND_OLDGEN_info,IND_OLDGEN_entry,1,0,IND_OLDGEN,const,EF_,0,0);
+INFO_TABLE(IND_OLDGEN_info,IND_OLDGEN_entry,1,1,IND_OLDGEN,const,EF_,0,0);
STGFUN(IND_OLDGEN_entry)
{
FB_
FE_
}
-INFO_TABLE(IND_OLDGEN_PERM_info,IND_OLDGEN_PERM_entry,1,0,IND_OLDGEN_PERM,const,EF_,0,0);
+INFO_TABLE(IND_OLDGEN_PERM_info,IND_OLDGEN_PERM_entry,1,1,IND_OLDGEN_PERM,const,EF_,0,0);
STGFUN(IND_OLDGEN_PERM_entry)
{
FB_
-------------------------------------------------------------------------- */
/* Note: a black hole must be big enough to be overwritten with an
- * indirection/evacuee/catch. Thus we claim it has 2 non-pointer words of
- * payload, which should be big enough for an old-generation
- * indirection.
+ * indirection/evacuee/catch. Thus we claim it has 1 non-pointer word of
+ * payload (in addition to the pointer word for the blocking queue), which
+ * should be big enough for an old-generation indirection.
*/
-INFO_TABLE(BLACKHOLE_info, BLACKHOLE_entry,0,2,BLACKHOLE,const,EF_,0,0);
+INFO_TABLE(BLACKHOLE_info, BLACKHOLE_entry,1,1,BLACKHOLE,const,EF_,0,0);
STGFUN(BLACKHOLE_entry)
{
FB_
}
/* identical to BLACKHOLEs except for the infotag */
-INFO_TABLE(CAF_BLACKHOLE_info, CAF_BLACKHOLE_entry,0,2,CAF_BLACKHOLE,const,EF_,0,0);
+INFO_TABLE(CAF_BLACKHOLE_info, CAF_BLACKHOLE_entry,1,1,CAF_BLACKHOLE,const,EF_,0,0);
STGFUN(CAF_BLACKHOLE_entry)
{
FB_
and entry code for each type.
-------------------------------------------------------------------------- */
-INFO_TABLE(FULL_MVAR_info,FULL_MVAR_entry,3,0,MVAR,const,EF_,0,0);
+INFO_TABLE(FULL_MVAR_info,FULL_MVAR_entry,4,0,MVAR,const,EF_,0,0);
NON_ENTERABLE_ENTRY_CODE(FULL_MVAR);
-INFO_TABLE(EMPTY_MVAR_info,EMPTY_MVAR_entry,3,0,MVAR,const,EF_,0,0);
+INFO_TABLE(EMPTY_MVAR_info,EMPTY_MVAR_entry,4,0,MVAR,const,EF_,0,0);
NON_ENTERABLE_ENTRY_CODE(EMPTY_MVAR);
/* -----------------------------------------------------------------------------
};
/* -----------------------------------------------------------------------------
+ Mutable lists
+
+ Mutable lists (used by the garbage collector) consist of a chain of
+ StgMutClosures connected through their mut_link fields, ending in
+ an END_MUT_LIST closure.
+ -------------------------------------------------------------------------- */
+
+INFO_TABLE_CONSTR(END_MUT_LIST_info,END_MUT_LIST_entry,0,0,0,CONSTR_NOCAF_STATIC,const,EF_,0,0);
+NON_ENTERABLE_ENTRY_CODE(END_MUT_LIST);
+
+SET_STATIC_HDR(END_MUT_LIST_closure,END_MUT_LIST_info,0/*CC*/,const,EI_)
+};
+
+INFO_TABLE(MUT_CONS_info, MUT_CONS_entry, 1, 1, MUT_VAR, const, EF_, 0, 0);
+NON_ENTERABLE_ENTRY_CODE(MUT_CONS);
+
+/* -----------------------------------------------------------------------------
Arrays
These come in two basic flavours: arrays of data (StgArrWords) and arrays of
ArrayInfo(ARR_WORDS);
ArrayInfo(MUT_ARR_WORDS);
-ArrayInfo(ARR_PTRS);
ArrayInfo(MUT_ARR_PTRS);
ArrayInfo(MUT_ARR_PTRS_FROZEN);
Mutable Variables
-------------------------------------------------------------------------- */
-INFO_TABLE(MUT_VAR_info, MUT_VAR_entry, 1, 0, MUT_VAR, const, EF_, 0, 0);
+INFO_TABLE(MUT_VAR_info, MUT_VAR_entry, 1, 1, MUT_VAR, const, EF_, 0, 0);
NON_ENTERABLE_ENTRY_CODE(MUT_VAR);
/* -----------------------------------------------------------------------------
/* -----------------------------------------------------------------------------
- * $Id: Storage.c,v 1.2 1998/12/02 13:28:57 simonm Exp $
+ * $Id: Storage.c,v 1.3 1999/01/13 17:25:47 simonm Exp $
*
* Storage manager front end
*
#include "Stats.h"
#include "Hooks.h"
#include "BlockAlloc.h"
+#include "MBlock.h"
#include "gmp.h"
#include "Weak.h"
#include "Storage.h"
#include "StoragePriv.h"
-bdescr *nursery; /* chained-blocks in the nursery */
bdescr *current_nursery; /* next available nursery block, or NULL */
nat nursery_blocks; /* number of blocks in the nursery */
StgPtr alloc_Hp = NULL; /* next free byte in small_alloc_list */
StgPtr alloc_HpLim = NULL; /* end of block at small_alloc_list */
+generation *generations; /* all the generations */
+generation *g0; /* generation 0, for convenience */
+generation *oldest_gen; /* oldest generation, for convenience */
+step *g0s0; /* generation 0, step 0, for convenience */
+
/*
* Forward references
*/
+static bdescr *allocNursery (nat blocks);
static void *stgAllocForGMP (size_t size_in_bytes);
static void *stgReallocForGMP (void *ptr, size_t old_size, size_t new_size);
static void stgDeallocForGMP (void *ptr, size_t size);
void
initStorage (void)
{
+ nat g, s;
+ step *step;
+
initBlockAllocator();
- nursery = allocNursery(NULL, RtsFlags.GcFlags.minAllocAreaSize);
+ /* allocate generation info array */
+ generations = (generation *)stgMallocBytes(RtsFlags.GcFlags.generations
+ * sizeof(struct _generation),
+ "initStorage: gens");
+
+ /* set up all generations */
+ for(g = 0; g < RtsFlags.GcFlags.generations; g++) {
+ generations[g].no = g;
+ generations[g].mut_list = END_MUT_LIST;
+ generations[g].collections = 0;
+ generations[g].failed_promotions = 0;
+ }
+
+ /* Oldest generation: one step */
+ g = RtsFlags.GcFlags.generations-1;
+ generations[g].n_steps = 1;
+ generations[g].steps =
+ stgMallocBytes(1 * sizeof(struct _step), "initStorage: last step");
+ generations[g].max_blocks = RtsFlags.GcFlags.minAllocAreaSize * 4;
+ step = &generations[g].steps[0];
+ step->no = 0;
+ step->gen = &generations[g];
+ step->blocks = NULL;
+ step->n_blocks = 0;
+ step->to = step; /* destination is this step */
+ step->hp = NULL;
+ step->hpLim = NULL;
+ step->hp_bd = NULL;
+
+ /* set up all except the oldest generation with 2 steps */
+ for(g = 0; g < RtsFlags.GcFlags.generations-1; g++) {
+ generations[g].n_steps = 2;
+ generations[g].steps = stgMallocBytes (2 * sizeof(struct _step),
+ "initStorage: steps");
+ generations[g].max_blocks = RtsFlags.GcFlags.minAllocAreaSize * 4;
+ }
+
+ for (g = 0; g < RtsFlags.GcFlags.generations-1; 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];
+ if ( s == 1 ) {
+ step->to = &generations[g+1].steps[0];
+ } else {
+ step->to = &generations[g].steps[s+1];
+ }
+ step->hp = NULL;
+ step->hpLim = NULL;
+ step->hp_bd = NULL;
+ step->large_objects = NULL;
+ step->new_large_objects = NULL;
+ step->scavenged_large_objects = NULL;
+ }
+ }
+
+ oldest_gen = &generations[RtsFlags.GcFlags.generations-1];
+
+ /* generation 0 is special: that's the nursery */
+ g0 = &generations[0];
+ generations[0].max_blocks = 0;
+
+ /* G0S0: the allocation area */
+ step = &generations[0].steps[0];
+ g0s0 = step;
+ step->blocks = allocNursery(RtsFlags.GcFlags.minAllocAreaSize);
+ step->n_blocks = RtsFlags.GcFlags.minAllocAreaSize;
+ nursery_blocks = RtsFlags.GcFlags.minAllocAreaSize;
+ current_nursery = step->blocks;
+ /* hp, hpLim, hp_bd, to_space etc. aren't used in G0S0 */
weak_ptr_list = NULL;
caf_list = NULL;
/* Tell GNU multi-precision pkg about our custom alloc functions */
mp_set_memory_functions(stgAllocForGMP, stgReallocForGMP, stgDeallocForGMP);
#endif
+
+ IF_DEBUG(gc, stat_describe_gens());
}
-bdescr *
-allocNursery (bdescr *last_bd, nat blocks)
+static bdescr *
+allocNursery (nat blocks)
{
- bdescr *bd;
+ bdescr *last_bd, *bd;
nat i;
+ last_bd = NULL;
/* Allocate a nursery */
for (i=0; i < blocks; i++) {
bd = allocBlock();
bd->link = last_bd;
- bd->step = 0;
+ bd->step = g0s0;
+ bd->gen = g0;
+ bd->evacuated = 0;
bd->free = bd->start;
last_bd = bd;
}
- nursery_blocks = blocks;
- current_nursery = last_bd;
return last_bd;
}
}
void
+recordMutable(StgMutClosure *p)
+{
+ bdescr *bd;
+
+ ASSERT(closure_MUTABLE(p));
+
+ bd = Bdescr((P_)p);
+
+ /* no need to bother in generation 0 */
+ if (bd->gen == g0) {
+ return;
+ }
+
+ if (p->mut_link == NULL) {
+ p->mut_link = bd->gen->mut_list;
+ bd->gen->mut_list = p;
+ }
+}
+
+void
newCAF(StgClosure* caf)
{
- const StgInfoTable *info = get_itbl(caf);
+ const StgInfoTable *info;
+
+ /* 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_list;
+ oldest_gen->mut_list = (StgMutClosure *)caf;
+#ifdef DEBUG
+ info = get_itbl(caf);
ASSERT(info->type == IND_STATIC);
STATIC_LINK2(info,caf) = caf_list;
caf_list = caf;
+#endif
}
/* -----------------------------------------------------------------------------
CCS_ALLOC(CCCS,n);
/* big allocation (>LARGE_OBJECT_THRESHOLD) */
+ /* ToDo: allocate directly into generation 1 */
if (n >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
nat req_blocks = (lnat)BLOCK_ROUND_UP(n*sizeof(W_)) / BLOCK_SIZE;
bd = allocGroup(req_blocks);
- bd->link = large_alloc_list;
- bd->back = NULL;
- if (large_alloc_list) {
- large_alloc_list->back = bd; /* double-link the list */
- }
- large_alloc_list = bd;
- bd->step = 0;
+ dbl_link_onto(bd, &g0s0->large_objects);
+ bd->gen = g0;
+ bd->step = g0s0;
+ bd->evacuated = 0;
+ 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
bd = allocBlock();
bd->link = small_alloc_list;
small_alloc_list = bd;
- bd->step = 0;
+ bd->gen = g0;
+ bd->step = g0s0;
+ bd->evacuated = 0;
alloc_Hp = bd->start;
alloc_HpLim = bd->start + BLOCK_SIZE_W;
alloc_blocks++;
{
/* easy for us: the garbage collector does the dealloc'n */
}
+
+/* -----------------------------------------------------------------------------
+ Debugging
+
+ memInventory() checks for memory leaks by counting up all the
+ blocks we know about and comparing that to the number of blocks
+ allegedly floating around in the system.
+ -------------------------------------------------------------------------- */
+
+#ifdef DEBUG
+
+extern void
+memInventory(void)
+{
+ nat g, s;
+ step *step;
+ bdescr *bd;
+ lnat total_blocks = 0, free_blocks = 0;
+
+ /* count the blocks we current have */
+ 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;
+ for (bd = step->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
+ descriptors would normally go.
+ */
+ if (bd->blocks > BLOCKS_PER_MBLOCK) {
+ total_blocks -= (MBLOCK_SIZE / BLOCK_SIZE - BLOCKS_PER_MBLOCK)
+ * bd->blocks/(MBLOCK_SIZE/BLOCK_SIZE);
+ }
+ }
+ }
+ }
+
+ /* any blocks held by allocate() */
+ for (bd = small_alloc_list; bd; bd = bd->link) {
+ total_blocks += bd->blocks;
+ }
+ for (bd = large_alloc_list; bd; bd = bd->link) {
+ total_blocks += bd->blocks;
+ }
+
+ /* 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
+}
+
+#endif
/* -----------------------------------------------------------------------------
- * $Id: Storage.h,v 1.2 1998/12/02 13:28:58 simonm Exp $
+ * $Id: Storage.h,v 1.3 1999/01/13 17:25:48 simonm Exp $
*
* External Storage Manger Interface
*
-------------------------------------------------------------------------- */
-extern void RecordMutable(StgPtr p);
-extern void UpdateWithIndirection(StgPtr p1, StgPtr p2);
+extern void recordMutable(StgMutClosure *p);
+
+#ifdef TICKY_TICKY
+#error updateWithIndirection: maybe permanent indirection?
+# define Ind_info_TO_USE ((AllFlags.doUpdEntryCounts) ? &IND_PERM_info : &IND_info
+)
+#endif
+
+static inline void
+updateWithIndirection(StgClosure *p1, StgClosure *p2)
+{
+ bdescr *bd;
+
+ bd = Bdescr((P_)p1);
+ if (bd->gen->no == 0) {
+ SET_INFO(p1,&IND_info);
+ ((StgInd *)p1)->indirectee = p2;
+ } else {
+ SET_INFO(p1,&IND_OLDGEN_info);
+ ((StgIndOldGen *)p1)->indirectee = p2;
+ ((StgIndOldGen *)p1)->mut_link = bd->gen->mut_list;
+ bd->gen->mut_list = (StgMutClosure *)p1;
+ }
+}
/* -----------------------------------------------------------------------------
The CAF list - used to let us revert CAFs
/* -----------------------------------------------------------------------------
- * $Id: StoragePriv.h,v 1.2 1998/12/02 13:28:59 simonm Exp $
+ * $Id: StoragePriv.h,v 1.3 1999/01/13 17:25:48 simonm Exp $
*
* Internal Storage Manger Interface
*
* ---------------------------------------------------------------------------*/
-extern bdescr *allocNursery (bdescr *last_bd, nat blocks);
+#ifndef STORAGEPRIV_H
+#define STORAGEPRIV_H
+
+/* GENERATION GC NOTES
+ *
+ * We support an arbitrary number of generations, with an arbitrary number
+ * of steps per generation. Notes (in no particular order):
+ *
+ * - all generations except the oldest should have two steps. This gives
+ * objects a decent chance to age before being promoted, and in
+ * particular will ensure that we don't end up with too many
+ * thunks being updated in older generations.
+ *
+ * - the oldest generation has one step. There's no point in aging
+ * objects in the oldest generation.
+ *
+ * - generation 0, step 0 (G0S0) is the allocation area. It is given
+ * a fixed set of blocks during initialisation, and these blocks
+ * are never freed.
+ *
+ * - during garbage collection, each step which is an evacuation
+ * destination (i.e. all steps except G0S0) is allocated a to-space.
+ * evacuated objects are allocated into the step's to-space until
+ * GC is finished, when the original step's contents may be freed
+ * and replaced by the to-space.
+ *
+ * - the mutable-list is per-generation (not per-step). G0 doesn't
+ * have one (since every garbage collection collects at least G0).
+ *
+ * - block descriptors contain pointers to both the step and the
+ * generation that the block belongs to, for convenience.
+ *
+ * - static objects are stored in per-generation lists. See GC.c for
+ * details of how we collect CAFs in the generational scheme.
+ *
+ * - large objects are per-step, and are promoted in the same way
+ * as small objects, except that we may allocate large objects into
+ * generation 1 initially.
+ */
+
+typedef struct _step {
+ nat no; /* step number */
+ bdescr *blocks; /* blocks in this step */
+ nat n_blocks; /* number of blocks */
+ struct _step *to; /* where collected objects from this step go */
+ struct _generation *gen; /* generation this step belongs to */
+ bdescr *large_objects; /* large objects (doubly linked) */
+
+ /* temporary use during GC: */
+ StgPtr hp; /* next free locn in to-space */
+ StgPtr hpLim; /* end of current to-space block */
+ bdescr *hp_bd; /* bdescr of current to-space block */
+ bdescr *to_space; /* bdescr of first to-space block */
+ nat to_blocks; /* number of blocks in to-space */
+ bdescr *scan_bd; /* block currently being scanned */
+ StgPtr scan; /* scan pointer in current block */
+ bdescr *new_large_objects; /* large objects collected so far */
+ bdescr *scavenged_large_objects; /* live large objects after GC (dbl link) */
+
+#ifdef DEBUG
+ /* for sanity checking: */
+ bdescr *old_scan_bd;
+ StgPtr old_scan;
+#endif
+} step;
+
+typedef struct _generation {
+ nat no; /* generation number */
+ step *steps; /* steps */
+ nat n_steps; /* number of steps */
+ nat max_blocks; /* max blocks in step 0 */
+ StgMutClosure *mut_list; /* mutable objects in this generation (not G0)*/
+
+ /* stats information */
+ nat collections;
+ nat failed_promotions;
+} generation;
+
+#define END_OF_STATIC_LIST stgCast(StgClosure*,1)
+
+extern generation *generations;
+
+extern generation *g0;
+extern step *g0s0;
+extern generation *oldest_gen;
+
extern void newCAF(StgClosure*);
extern StgTSO *relocate_TSO(StgTSO *src, StgTSO *dest);
extern nat alloc_blocks;
extern nat alloc_blocks_lim;
+static inline void
+dbl_link_onto(bdescr *bd, bdescr **list)
+{
+ bd->link = *list;
+ bd->back = NULL;
+ if (*list) {
+ (*list)->back = bd; /* double-link the list */
+ }
+ *list = bd;
+}
+
+/* MUTABLE LISTS
+ * A mutable list is ended with END_MUT_LIST, so that we can use NULL
+ * as an indication that an object is not on a mutable list.
+ */
+#define END_MUT_LIST ((StgMutClosure *)(void *)&END_MUT_LIST_closure)
+
+#ifdef DEBUG
+extern void memInventory(void);
+#endif
+
+#endif /* STORAGEPRIV_H */
/* -----------------------------------------------------------------------------
- * $Id: Updates.hc,v 1.2 1998/12/02 13:29:00 simonm Exp $
+ * $Id: Updates.hc,v 1.3 1999/01/13 17:25:49 simonm Exp $
*
* Code to perform updates.
*
#include "Rts.h"
#include "RtsUtils.h"
#include "HeapStackCheck.h"
+#include "Storage.h"
/*
The update frame return address must be *polymorphic*, that means
TICK_UPD_EXISTING(); \
\
updatee = ((StgUpdateFrame *)Sp)->updatee; \
- \
+ \
/* update the updatee with an indirection to the return value */\
UPD_IND(updatee,R1.p); \
\
UPD_FRAME_ENTRY_TEMPLATE(Upd_frame_6_entry,RET_VEC(Sp[0],6));
UPD_FRAME_ENTRY_TEMPLATE(Upd_frame_7_entry,RET_VEC(Sp[0],7));
-
/*
Make sure this table is big enough to handle the maximum vectored
return size!
/* -----------------------------------------------------------------------------
- * $Id: Weak.c,v 1.2 1998/12/02 13:29:01 simonm Exp $
+ * $Id: Weak.c,v 1.3 1999/01/13 17:25:49 simonm Exp $
*
* Weak pointers / finalisers
*
createIOThread(RtsFlags.GcFlags.initialStkSize, w->finaliser);
#endif
w->header.info = &DEAD_WEAK_info;
+
+ /* need to fill the slop with zeros if we're sanity checking */
+ IF_DEBUG(sanity, {
+ nat dw_size = sizeW_fromITBL(get_itbl(w));
+ memset((P_)w + dw_size, 0, (sizeofW(StgWeak) - dw_size) * sizeof(W_));
+ });
}
}