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