[project @ 2000-11-13 14:40:36 by simonmar]
[ghc-hetmet.git] / ghc / rts / Storage.c
1 /* -----------------------------------------------------------------------------
2  * $Id: Storage.c,v 1.28 2000/11/13 14:40:37 simonmar Exp $
3  *
4  * (c) The GHC Team, 1998-1999
5  *
6  * Storage manager front end
7  *
8  * ---------------------------------------------------------------------------*/
9
10 #include "Rts.h"
11 #include "RtsUtils.h"
12 #include "RtsFlags.h"
13 #include "Stats.h"
14 #include "Hooks.h"
15 #include "BlockAlloc.h"
16 #include "MBlock.h"
17 #include "Weak.h"
18 #include "Sanity.h"
19
20 #include "Storage.h"
21 #include "Schedule.h"
22 #include "StoragePriv.h"
23
24 #ifndef SMP
25 nat nursery_blocks;             /* number of blocks in the nursery */
26 #endif
27
28 StgClosure    *caf_list         = NULL;
29
30 bdescr *small_alloc_list;       /* allocate()d small objects */
31 bdescr *large_alloc_list;       /* allocate()d large objects */
32 nat alloc_blocks;               /* number of allocate()d blocks since GC */
33 nat alloc_blocks_lim;           /* approximate limit on alloc_blocks */
34
35 StgPtr alloc_Hp    = NULL;      /* next free byte in small_alloc_list */
36 StgPtr alloc_HpLim = NULL;      /* end of block at small_alloc_list   */
37
38 generation *generations;        /* all the generations */
39 generation *g0;                 /* generation 0, for convenience */
40 generation *oldest_gen;         /* oldest generation, for convenience */
41 step *g0s0;                     /* generation 0, step 0, for convenience */
42
43 lnat total_allocated = 0;       /* total memory allocated during run */
44
45 /*
46  * Storage manager mutex:  protects all the above state from
47  * simultaneous access by two STG threads.
48  */
49 #ifdef SMP
50 pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER;
51 #endif
52
53 /*
54  * Forward references
55  */
56 static void *stgAllocForGMP   (size_t size_in_bytes);
57 static void *stgReallocForGMP (void *ptr, size_t old_size, size_t new_size);
58 static void  stgDeallocForGMP (void *ptr, size_t size);
59
60 void
61 initStorage (void)
62 {
63   nat g, s;
64   step *step;
65   generation *gen;
66
67   /* If we're doing heap profiling, we want a two-space heap with a
68    * fixed-size allocation area so that we get roughly even-spaced
69    * samples.
70    */
71 #if defined(PROFILING) || defined(DEBUG)
72   if (RtsFlags.ProfFlags.doHeapProfile) {
73     RtsFlags.GcFlags.generations = 1;
74     RtsFlags.GcFlags.steps = 1;
75     RtsFlags.GcFlags.oldGenFactor = 0;
76     RtsFlags.GcFlags.heapSizeSuggestion = 0;
77   }
78 #endif
79
80   if (RtsFlags.GcFlags.heapSizeSuggestion > 
81       RtsFlags.GcFlags.maxHeapSize) {
82     RtsFlags.GcFlags.maxHeapSize = RtsFlags.GcFlags.heapSizeSuggestion;
83   }
84
85   initBlockAllocator();
86   
87   /* allocate generation info array */
88   generations = (generation *)stgMallocBytes(RtsFlags.GcFlags.generations 
89                                              * sizeof(struct _generation),
90                                              "initStorage: gens");
91
92   /* Initialise all generations */
93   for(g = 0; g < RtsFlags.GcFlags.generations; g++) {
94     gen = &generations[g];
95     gen->no = g;
96     gen->mut_list = END_MUT_LIST;
97     gen->mut_once_list = END_MUT_LIST;
98     gen->collections = 0;
99     gen->failed_promotions = 0;
100     gen->max_blocks = 0;
101   }
102
103   /* A couple of convenience pointers */
104   g0 = &generations[0];
105   oldest_gen = &generations[RtsFlags.GcFlags.generations-1];
106
107   /* Allocate step structures in each generation */
108   if (RtsFlags.GcFlags.generations > 1) {
109     /* Only for multiple-generations */
110
111     /* Oldest generation: one step */
112     oldest_gen->n_steps = 1;
113     oldest_gen->steps = 
114       stgMallocBytes(1 * sizeof(struct _step), "initStorage: last step");
115
116     /* set up all except the oldest generation with 2 steps */
117     for(g = 0; g < RtsFlags.GcFlags.generations-1; g++) {
118       generations[g].n_steps = RtsFlags.GcFlags.steps;
119       generations[g].steps  = 
120         stgMallocBytes (RtsFlags.GcFlags.steps * sizeof(struct _step),
121                         "initStorage: steps");
122     }
123     
124   } else {
125     /* single generation, i.e. a two-space collector */
126     g0->n_steps = 1;
127     g0->steps = stgMallocBytes (sizeof(struct _step), "initStorage: steps");
128   }
129
130   /* Initialise all steps */
131   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
132     for (s = 0; s < generations[g].n_steps; s++) {
133       step = &generations[g].steps[s];
134       step->no = s;
135       step->blocks = NULL;
136       step->n_blocks = 0;
137       step->gen = &generations[g];
138       step->hp = NULL;
139       step->hpLim = NULL;
140       step->hp_bd = NULL;
141       step->scan = NULL;
142       step->scan_bd = NULL;
143       step->large_objects = NULL;
144       step->new_large_objects = NULL;
145       step->scavenged_large_objects = NULL;
146     }
147   }
148   
149   /* Set up the destination pointers in each younger gen. step */
150   for (g = 0; g < RtsFlags.GcFlags.generations-1; g++) {
151     for (s = 0; s < generations[g].n_steps-1; s++) {
152       generations[g].steps[s].to = &generations[g].steps[s+1];
153     }
154     generations[g].steps[s].to = &generations[g+1].steps[0];
155   }
156   
157   /* The oldest generation has one step and its destination is the
158    * same step. */
159   oldest_gen->steps[0].to = &oldest_gen->steps[0];
160
161   /* generation 0 is special: that's the nursery */
162   generations[0].max_blocks = 0;
163
164   /* G0S0: the allocation area.  Policy: keep the allocation area
165    * small to begin with, even if we have a large suggested heap
166    * size.  Reason: we're going to do a major collection first, and we
167    * don't want it to be a big one.  This vague idea is borne out by 
168    * rigorous experimental evidence.
169    */
170   g0s0 = &generations[0].steps[0];
171
172   allocNurseries();
173
174   weak_ptr_list = NULL;
175   caf_list = NULL;
176    
177   /* initialise the allocate() interface */
178   small_alloc_list = NULL;
179   large_alloc_list = NULL;
180   alloc_blocks = 0;
181   alloc_blocks_lim = RtsFlags.GcFlags.minAllocAreaSize;
182
183   /* Tell GNU multi-precision pkg about our custom alloc functions */
184   mp_set_memory_functions(stgAllocForGMP, stgReallocForGMP, stgDeallocForGMP);
185
186 #ifdef SMP
187   pthread_mutex_init(&sm_mutex, NULL);
188 #endif
189
190   IF_DEBUG(gc, stat_describe_gens());
191 }
192
193 void
194 exitStorage (void)
195 {
196     stat_exit(calcAllocated());
197 }
198
199
200 /* -----------------------------------------------------------------------------
201    CAF management.
202    -------------------------------------------------------------------------- */
203
204 void
205 newCAF(StgClosure* caf)
206 {
207   /* Put this CAF on the mutable list for the old generation.
208    * This is a HACK - the IND_STATIC closure doesn't really have
209    * a mut_link field, but we pretend it has - in fact we re-use
210    * the STATIC_LINK field for the time being, because when we
211    * come to do a major GC we won't need the mut_link field
212    * any more and can use it as a STATIC_LINK.
213    */
214   ACQUIRE_LOCK(&sm_mutex);
215
216   ASSERT( ((StgMutClosure*)caf)->mut_link == NULL );
217   ((StgMutClosure *)caf)->mut_link = oldest_gen->mut_once_list;
218   oldest_gen->mut_once_list = (StgMutClosure *)caf;
219
220 #ifdef INTERPRETER
221   /* If we're Hugs, we also have to put it in the CAF table, so that
222      the CAF can be reverted.  When reverting, CAFs created by compiled
223      code are recorded in the CAF table, which lives outside the
224      heap, in mallocville.  CAFs created by interpreted code are
225      chained together via the link fields in StgCAFs, and are not
226      recorded in the CAF table.
227   */
228   ASSERT( get_itbl(caf)->type == THUNK_STATIC );
229   addToECafTable ( caf, get_itbl(caf) );
230 #endif
231
232   RELEASE_LOCK(&sm_mutex);
233 }
234
235 #ifdef INTERPRETER
236 void
237 newCAF_made_by_Hugs(StgCAF* caf)
238 {
239   ACQUIRE_LOCK(&sm_mutex);
240
241   ASSERT( get_itbl(caf)->type == CAF_ENTERED );
242   recordOldToNewPtrs((StgMutClosure*)caf);
243   caf->link = ecafList;
244   ecafList = caf->link;
245
246   RELEASE_LOCK(&sm_mutex);
247 }
248 #endif
249
250 #ifdef INTERPRETER
251 /* These initialisations are critical for correct operation
252    on the first call of addToECafTable. 
253 */
254 StgCAF*         ecafList      = END_ECAF_LIST;
255 StgCAFTabEntry* ecafTable     = NULL;
256 StgInt          usedECafTable = 0;
257 StgInt          sizeECafTable = 0;
258
259
260 void clearECafTable ( void )
261 {
262    usedECafTable = 0;
263 }
264
265 void addToECafTable ( StgClosure* closure, StgInfoTable* origItbl )
266 {
267    StgInt          i;
268    StgCAFTabEntry* et2;
269    if (usedECafTable == sizeECafTable) {
270       /* Make the initial table size be 8 */
271       sizeECafTable *= 2;
272       if (sizeECafTable == 0) sizeECafTable = 8;
273       et2 = stgMallocBytes ( 
274                sizeECafTable * sizeof(StgCAFTabEntry),
275                "addToECafTable" );
276       for (i = 0; i < usedECafTable; i++) 
277          et2[i] = ecafTable[i];
278       if (ecafTable) free(ecafTable);
279       ecafTable = et2;
280    }
281    ecafTable[usedECafTable].closure  = closure;
282    ecafTable[usedECafTable].origItbl = origItbl;
283    usedECafTable++;
284 }
285 #endif
286
287 /* -----------------------------------------------------------------------------
288    Nursery management.
289    -------------------------------------------------------------------------- */
290
291 void
292 allocNurseries( void )
293
294 #ifdef SMP
295   {
296     Capability *cap;
297     bdescr *bd;
298
299     g0s0->blocks = NULL;
300     g0s0->n_blocks = 0;
301     for (cap = free_capabilities; cap != NULL; cap = cap->link) {
302       cap->rNursery = allocNursery(NULL, RtsFlags.GcFlags.minAllocAreaSize);
303       cap->rCurrentNursery = cap->rNursery;
304       for (bd = cap->rNursery; bd != NULL; bd = bd->link) {
305         bd->back = (bdescr *)cap;
306       }
307     }
308     /* Set the back links to be equal to the Capability,
309      * so we can do slightly better informed locking.
310      */
311   }
312 #else /* SMP */
313   nursery_blocks  = RtsFlags.GcFlags.minAllocAreaSize;
314   g0s0->blocks    = allocNursery(NULL, nursery_blocks);
315   g0s0->n_blocks  = nursery_blocks;
316   g0s0->to_space  = NULL;
317   MainRegTable.rNursery        = g0s0->blocks;
318   MainRegTable.rCurrentNursery = g0s0->blocks;
319   /* hp, hpLim, hp_bd, to_space etc. aren't used in G0S0 */
320 #endif
321 }
322       
323 void
324 resetNurseries( void )
325 {
326   bdescr *bd;
327 #ifdef SMP
328   Capability *cap;
329   
330   /* All tasks must be stopped */
331   ASSERT(n_free_capabilities == RtsFlags.ParFlags.nNodes);
332
333   for (cap = free_capabilities; cap != NULL; cap = cap->link) {
334     for (bd = cap->rNursery; bd; bd = bd->link) {
335       bd->free = bd->start;
336       ASSERT(bd->gen == g0);
337       ASSERT(bd->step == g0s0);
338       IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE));
339     }
340     cap->rCurrentNursery = cap->rNursery;
341   }
342 #else
343   for (bd = g0s0->blocks; bd; bd = bd->link) {
344     bd->free = bd->start;
345     ASSERT(bd->gen == g0);
346     ASSERT(bd->step == g0s0);
347     IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE));
348   }
349   MainRegTable.rNursery = g0s0->blocks;
350   MainRegTable.rCurrentNursery = g0s0->blocks;
351 #endif
352 }
353
354 bdescr *
355 allocNursery (bdescr *last_bd, nat blocks)
356 {
357   bdescr *bd;
358   nat i;
359
360   /* Allocate a nursery */
361   for (i=0; i < blocks; i++) {
362     bd = allocBlock();
363     bd->link = last_bd;
364     bd->step = g0s0;
365     bd->gen = g0;
366     bd->evacuated = 0;
367     bd->free = bd->start;
368     last_bd = bd;
369   }
370   return last_bd;
371 }
372
373 void
374 resizeNursery ( nat blocks )
375 {
376   bdescr *bd;
377
378 #ifdef SMP
379   barf("resizeNursery: can't resize in SMP mode");
380 #endif
381
382   if (nursery_blocks == blocks) {
383     ASSERT(g0s0->n_blocks == blocks);
384     return;
385   }
386
387   else if (nursery_blocks < blocks) {
388     IF_DEBUG(gc, fprintf(stderr, "Increasing size of nursery to %d blocks\n", 
389                          blocks));
390     g0s0->blocks = allocNursery(g0s0->blocks, blocks-nursery_blocks);
391   } 
392
393   else {
394     bdescr *next_bd;
395     
396     IF_DEBUG(gc, fprintf(stderr, "Decreasing size of nursery to %d blocks\n", 
397                          blocks));
398     for (bd = g0s0->blocks; nursery_blocks > blocks; nursery_blocks--) {
399       next_bd = bd->link;
400       freeGroup(bd);
401       bd = next_bd;
402     }
403     g0s0->blocks = bd;
404   }
405   
406   g0s0->n_blocks = nursery_blocks = blocks;
407 }
408
409 /* -----------------------------------------------------------------------------
410    The allocate() interface
411
412    allocate(n) always succeeds, and returns a chunk of memory n words
413    long.  n can be larger than the size of a block if necessary, in
414    which case a contiguous block group will be allocated.
415    -------------------------------------------------------------------------- */
416
417 StgPtr
418 allocate(nat n)
419 {
420   bdescr *bd;
421   StgPtr p;
422
423   ACQUIRE_LOCK(&sm_mutex);
424
425   TICK_ALLOC_HEAP_NOCTR(n);
426   CCS_ALLOC(CCCS,n);
427
428   /* big allocation (>LARGE_OBJECT_THRESHOLD) */
429   /* ToDo: allocate directly into generation 1 */
430   if (n >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
431     nat req_blocks =  (lnat)BLOCK_ROUND_UP(n*sizeof(W_)) / BLOCK_SIZE;
432     bd = allocGroup(req_blocks);
433     dbl_link_onto(bd, &g0s0->large_objects);
434     bd->gen  = g0;
435     bd->step = g0s0;
436     bd->evacuated = 0;
437     bd->free = bd->start;
438     /* don't add these blocks to alloc_blocks, since we're assuming
439      * that large objects are likely to remain live for quite a while
440      * (eg. running threads), so garbage collecting early won't make
441      * much difference.
442      */
443     alloc_blocks += req_blocks;
444     RELEASE_LOCK(&sm_mutex);
445     return bd->start;
446
447   /* small allocation (<LARGE_OBJECT_THRESHOLD) */
448   } else if (small_alloc_list == NULL || alloc_Hp + n > alloc_HpLim) {
449     if (small_alloc_list) {
450       small_alloc_list->free = alloc_Hp;
451     }
452     bd = allocBlock();
453     bd->link = small_alloc_list;
454     small_alloc_list = bd;
455     bd->gen = g0;
456     bd->step = g0s0;
457     bd->evacuated = 0;
458     alloc_Hp = bd->start;
459     alloc_HpLim = bd->start + BLOCK_SIZE_W;
460     alloc_blocks++;
461   }
462   
463   p = alloc_Hp;
464   alloc_Hp += n;
465   RELEASE_LOCK(&sm_mutex);
466   return p;
467 }
468
469 lnat allocated_bytes(void)
470 {
471   return (alloc_blocks * BLOCK_SIZE_W - (alloc_HpLim - alloc_Hp));
472 }
473
474 /* -----------------------------------------------------------------------------
475    Allocation functions for GMP.
476
477    These all use the allocate() interface - we can't have any garbage
478    collection going on during a gmp operation, so we use allocate()
479    which always succeeds.  The gmp operations which might need to
480    allocate will ask the storage manager (via doYouWantToGC()) whether
481    a garbage collection is required, in case we get into a loop doing
482    only allocate() style allocation.
483    -------------------------------------------------------------------------- */
484
485 static void *
486 stgAllocForGMP (size_t size_in_bytes)
487 {
488   StgArrWords* arr;
489   nat data_size_in_words, total_size_in_words;
490   
491   /* should be a multiple of sizeof(StgWord) (whole no. of limbs) */
492   ASSERT(size_in_bytes % sizeof(W_) == 0);
493   
494   data_size_in_words  = size_in_bytes / sizeof(W_);
495   total_size_in_words = sizeofW(StgArrWords) + data_size_in_words;
496   
497   /* allocate and fill it in. */
498   arr = (StgArrWords *)allocate(total_size_in_words);
499   SET_ARR_HDR(arr, &stg_ARR_WORDS_info, CCCS, data_size_in_words);
500   
501   /* and return a ptr to the goods inside the array */
502   return(BYTE_ARR_CTS(arr));
503 }
504
505 static void *
506 stgReallocForGMP (void *ptr, size_t old_size, size_t new_size)
507 {
508     void *new_stuff_ptr = stgAllocForGMP(new_size);
509     nat i = 0;
510     char *p = (char *) ptr;
511     char *q = (char *) new_stuff_ptr;
512
513     for (; i < old_size; i++, p++, q++) {
514         *q = *p;
515     }
516
517     return(new_stuff_ptr);
518 }
519
520 static void
521 stgDeallocForGMP (void *ptr STG_UNUSED, 
522                   size_t size STG_UNUSED)
523 {
524     /* easy for us: the garbage collector does the dealloc'n */
525 }
526
527 /* -----------------------------------------------------------------------------
528  * Stats and stuff
529  * -------------------------------------------------------------------------- */
530
531 /* -----------------------------------------------------------------------------
532  * calcAllocated()
533  *
534  * Approximate how much we've allocated: number of blocks in the
535  * nursery + blocks allocated via allocate() - unused nusery blocks.
536  * This leaves a little slop at the end of each block, and doesn't
537  * take into account large objects (ToDo).
538  * -------------------------------------------------------------------------- */
539
540 lnat
541 calcAllocated( void )
542 {
543   nat allocated;
544   bdescr *bd;
545
546 #ifdef SMP
547   Capability *cap;
548
549   /* All tasks must be stopped.  Can't assert that all the
550      capabilities are owned by the scheduler, though: one or more
551      tasks might have been stopped while they were running (non-main)
552      threads. */
553   /*  ASSERT(n_free_capabilities == RtsFlags.ParFlags.nNodes); */
554
555   allocated = 
556     n_free_capabilities * RtsFlags.GcFlags.minAllocAreaSize * BLOCK_SIZE_W
557     + allocated_bytes();
558
559   for (cap = free_capabilities; cap != NULL; cap = cap->link) {
560     for ( bd = cap->rCurrentNursery->link; bd != NULL; bd = bd->link ) {
561       allocated -= BLOCK_SIZE_W;
562     }
563     if (cap->rCurrentNursery->free < cap->rCurrentNursery->start 
564         + BLOCK_SIZE_W) {
565       allocated -= (cap->rCurrentNursery->start + BLOCK_SIZE_W)
566         - cap->rCurrentNursery->free;
567     }
568   }
569
570 #else /* !SMP */
571   bdescr *current_nursery = MainRegTable.rCurrentNursery;
572
573   allocated = (nursery_blocks * BLOCK_SIZE_W) + allocated_bytes();
574   for ( bd = current_nursery->link; bd != NULL; bd = bd->link ) {
575     allocated -= BLOCK_SIZE_W;
576   }
577   if (current_nursery->free < current_nursery->start + BLOCK_SIZE_W) {
578     allocated -= (current_nursery->start + BLOCK_SIZE_W)
579       - current_nursery->free;
580   }
581 #endif
582
583   total_allocated += allocated;
584   return allocated;
585 }  
586
587 /* Approximate the amount of live data in the heap.  To be called just
588  * after garbage collection (see GarbageCollect()).
589  */
590 extern lnat 
591 calcLive(void)
592 {
593   nat g, s;
594   lnat live = 0;
595   step *step;
596
597   if (RtsFlags.GcFlags.generations == 1) {
598     live = (g0s0->to_blocks - 1) * BLOCK_SIZE_W + 
599       ((lnat)g0s0->hp_bd->free - (lnat)g0s0->hp_bd->start) / sizeof(W_);
600     return live;
601   }
602
603   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
604     for (s = 0; s < generations[g].n_steps; s++) {
605       /* approximate amount of live data (doesn't take into account slop
606        * at end of each block).
607        */
608       if (g == 0 && s == 0) { 
609           continue; 
610       }
611       step = &generations[g].steps[s];
612       live += (step->n_blocks - 1) * BLOCK_SIZE_W +
613         ((lnat)step->hp_bd->free - (lnat)step->hp_bd->start) / sizeof(W_);
614     }
615   }
616   return live;
617 }
618
619 /* Approximate the number of blocks that will be needed at the next
620  * garbage collection.
621  *
622  * Assume: all data currently live will remain live.  Steps that will
623  * be collected next time will therefore need twice as many blocks
624  * since all the data will be copied.
625  */
626 extern lnat 
627 calcNeeded(void)
628 {
629   lnat needed = 0;
630   nat g, s;
631   step *step;
632
633   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
634     for (s = 0; s < generations[g].n_steps; s++) {
635       if (g == 0 && s == 0) { continue; }
636       step = &generations[g].steps[s];
637       if (generations[g].steps[0].n_blocks > generations[g].max_blocks) {
638         needed += 2 * step->n_blocks;
639       } else {
640         needed += step->n_blocks;
641       }
642     }
643   }
644   return needed;
645 }
646
647 /* -----------------------------------------------------------------------------
648    Debugging
649
650    memInventory() checks for memory leaks by counting up all the
651    blocks we know about and comparing that to the number of blocks
652    allegedly floating around in the system.
653    -------------------------------------------------------------------------- */
654
655 #ifdef DEBUG
656
657 extern void
658 memInventory(void)
659 {
660   nat g, s;
661   step *step;
662   bdescr *bd;
663   lnat total_blocks = 0, free_blocks = 0;
664
665   /* count the blocks we current have */
666
667   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
668     for (s = 0; s < generations[g].n_steps; s++) {
669       step = &generations[g].steps[s];
670       total_blocks += step->n_blocks;
671       if (RtsFlags.GcFlags.generations == 1) {
672         /* two-space collector has a to-space too :-) */
673         total_blocks += g0s0->to_blocks;
674       }
675       for (bd = step->large_objects; bd; bd = bd->link) {
676         total_blocks += bd->blocks;
677         /* hack for megablock groups: they have an extra block or two in
678            the second and subsequent megablocks where the block
679            descriptors would normally go.
680         */
681         if (bd->blocks > BLOCKS_PER_MBLOCK) {
682           total_blocks -= (MBLOCK_SIZE / BLOCK_SIZE - BLOCKS_PER_MBLOCK)
683                           * (bd->blocks/(MBLOCK_SIZE/BLOCK_SIZE));
684         }
685       }
686     }
687   }
688
689   /* any blocks held by allocate() */
690   for (bd = small_alloc_list; bd; bd = bd->link) {
691     total_blocks += bd->blocks;
692   }
693   for (bd = large_alloc_list; bd; bd = bd->link) {
694     total_blocks += bd->blocks;
695   }
696   
697   /* count the blocks on the free list */
698   free_blocks = countFreeList();
699
700   ASSERT(total_blocks + free_blocks == mblocks_allocated * BLOCKS_PER_MBLOCK);
701
702 #if 0
703   if (total_blocks + free_blocks != mblocks_allocated *
704       BLOCKS_PER_MBLOCK) {
705     fprintf(stderr, "Blocks: %ld live + %ld free  = %ld total (%ld around)\n",
706             total_blocks, free_blocks, total_blocks + free_blocks,
707             mblocks_allocated * BLOCKS_PER_MBLOCK);
708   }
709 #endif
710 }
711
712 /* Full heap sanity check. */
713
714 extern void
715 checkSanity(nat N)
716 {
717   nat g, s;
718
719   if (RtsFlags.GcFlags.generations == 1) {
720     checkHeap(g0s0->to_space, NULL);
721     checkChain(g0s0->large_objects);
722   } else {
723     
724     for (g = 0; g <= N; g++) {
725       for (s = 0; s < generations[g].n_steps; s++) {
726         if (g == 0 && s == 0) { continue; }
727         checkHeap(generations[g].steps[s].blocks, NULL);
728       }
729     }
730     for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
731       for (s = 0; s < generations[g].n_steps; s++) {
732         checkHeap(generations[g].steps[s].blocks,
733                   generations[g].steps[s].blocks->start);
734         checkChain(generations[g].steps[s].large_objects);
735       }
736     }
737     checkFreeListSanity();
738   }
739 }
740
741 #endif