[project @ 1999-11-02 15:05:38 by simonmar]
[ghc-hetmet.git] / ghc / rts / Storage.c
1 /* -----------------------------------------------------------------------------
2  * $Id: Storage.c,v 1.20 1999/11/02 15:06:04 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 "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 #ifdef COMPILER
183   /* Tell GNU multi-precision pkg about our custom alloc functions */
184   mp_set_memory_functions(stgAllocForGMP, stgReallocForGMP, stgDeallocForGMP);
185 #endif
186
187 #ifdef SMP
188   pthread_mutex_init(&sm_mutex, NULL);
189 #endif
190
191   IF_DEBUG(gc, stat_describe_gens());
192 }
193
194 void
195 exitStorage (void)
196 {
197   stat_exit(calcAllocated());
198 }
199
200 void
201 newCAF(StgClosure* caf)
202 {
203   /* Put this CAF on the mutable list for the old generation.
204    * This is a HACK - the IND_STATIC closure doesn't really have
205    * a mut_link field, but we pretend it has - in fact we re-use
206    * the STATIC_LINK field for the time being, because when we
207    * come to do a major GC we won't need the mut_link field
208    * any more and can use it as a STATIC_LINK.
209    */
210   ACQUIRE_LOCK(&sm_mutex);
211   ((StgMutClosure *)caf)->mut_link = oldest_gen->mut_once_list;
212   oldest_gen->mut_once_list = (StgMutClosure *)caf;
213
214 #ifdef DEBUG
215   { 
216     const StgInfoTable *info;
217     
218     info = get_itbl(caf);
219     ASSERT(info->type == IND_STATIC);
220 #if 0
221     STATIC_LINK2(info,caf) = caf_list;
222     caf_list = caf;
223 #endif
224   }
225 #endif
226   RELEASE_LOCK(&sm_mutex);
227 }
228
229 /* -----------------------------------------------------------------------------
230    Nursery management.
231    -------------------------------------------------------------------------- */
232
233 void
234 allocNurseries( void )
235
236 #ifdef SMP
237   {
238     Capability *cap;
239     
240     g0s0->blocks = NULL;
241     g0s0->n_blocks = 0;
242     for (cap = free_capabilities; cap != NULL; cap = cap->link) {
243       cap->rNursery = allocNursery(NULL, RtsFlags.GcFlags.minAllocAreaSize);
244       cap->rCurrentNursery = cap->rNursery;
245     }
246   }
247 #else /* SMP */
248   nursery_blocks  = RtsFlags.GcFlags.minAllocAreaSize;
249   g0s0->blocks    = allocNursery(NULL, nursery_blocks);
250   g0s0->n_blocks  = nursery_blocks;
251   g0s0->to_space  = NULL;
252   MainRegTable.rNursery        = g0s0->blocks;
253   MainRegTable.rCurrentNursery = g0s0->blocks;
254   /* hp, hpLim, hp_bd, to_space etc. aren't used in G0S0 */
255 #endif
256 }
257       
258 void
259 resetNurseries( void )
260 {
261   bdescr *bd;
262 #ifdef SMP
263   Capability *cap;
264   
265   /* All tasks must be stopped */
266   ASSERT(n_free_capabilities == RtsFlags.ConcFlags.nNodes);
267
268   for (cap = free_capabilities; cap != NULL; cap = cap->link) {
269     for (bd = cap->rNursery; bd; bd = bd->link) {
270       bd->free = bd->start;
271       ASSERT(bd->gen == g0);
272       ASSERT(bd->step == g0s0);
273       IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE));
274     }
275     cap->rCurrentNursery = cap->rNursery;
276   }
277 #else
278   for (bd = g0s0->blocks; bd; bd = bd->link) {
279     bd->free = bd->start;
280     ASSERT(bd->gen == g0);
281     ASSERT(bd->step == g0s0);
282     IF_DEBUG(sanity,memset(bd->start, 0xaa, BLOCK_SIZE));
283   }
284   MainRegTable.rNursery = g0s0->blocks;
285   MainRegTable.rCurrentNursery = g0s0->blocks;
286 #endif
287 }
288
289 bdescr *
290 allocNursery (bdescr *last_bd, nat blocks)
291 {
292   bdescr *bd;
293   nat i;
294
295   /* Allocate a nursery */
296   for (i=0; i < blocks; i++) {
297     bd = allocBlock();
298     bd->link = last_bd;
299     bd->step = g0s0;
300     bd->gen = g0;
301     bd->evacuated = 0;
302     bd->free = bd->start;
303     last_bd = bd;
304   }
305   return last_bd;
306 }
307
308 void
309 resizeNursery ( nat blocks )
310 {
311   bdescr *bd;
312
313 #ifdef SMP
314   barf("resizeNursery: can't resize in SMP mode");
315 #endif
316
317   if (nursery_blocks == blocks) {
318     ASSERT(g0s0->n_blocks == blocks);
319     return;
320   }
321
322   else if (nursery_blocks < blocks) {
323     IF_DEBUG(gc, fprintf(stderr, "Increasing size of nursery to %d blocks\n", 
324                          blocks));
325     g0s0->blocks = allocNursery(g0s0->blocks, blocks-nursery_blocks);
326   } 
327
328   else {
329     bdescr *next_bd;
330     
331     IF_DEBUG(gc, fprintf(stderr, "Decreasing size of nursery to %d blocks\n", 
332                          blocks));
333     for (bd = g0s0->blocks; nursery_blocks > blocks; nursery_blocks--) {
334       next_bd = bd->link;
335       freeGroup(bd);
336       bd = next_bd;
337     }
338     g0s0->blocks = bd;
339   }
340   
341   g0s0->n_blocks = nursery_blocks = blocks;
342 }
343
344 /* -----------------------------------------------------------------------------
345    The allocate() interface
346
347    allocate(n) always succeeds, and returns a chunk of memory n words
348    long.  n can be larger than the size of a block if necessary, in
349    which case a contiguous block group will be allocated.
350    -------------------------------------------------------------------------- */
351
352 StgPtr
353 allocate(nat n)
354 {
355   bdescr *bd;
356   StgPtr p;
357
358   ACQUIRE_LOCK(&sm_mutex);
359
360   TICK_ALLOC_HEAP_NOCTR(n);
361   CCS_ALLOC(CCCS,n);
362
363   /* big allocation (>LARGE_OBJECT_THRESHOLD) */
364   /* ToDo: allocate directly into generation 1 */
365   if (n >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
366     nat req_blocks =  (lnat)BLOCK_ROUND_UP(n*sizeof(W_)) / BLOCK_SIZE;
367     bd = allocGroup(req_blocks);
368     dbl_link_onto(bd, &g0s0->large_objects);
369     bd->gen  = g0;
370     bd->step = g0s0;
371     bd->evacuated = 0;
372     bd->free = bd->start;
373     /* don't add these blocks to alloc_blocks, since we're assuming
374      * that large objects are likely to remain live for quite a while
375      * (eg. running threads), so garbage collecting early won't make
376      * much difference.
377      */
378     RELEASE_LOCK(&sm_mutex);
379     return bd->start;
380
381   /* small allocation (<LARGE_OBJECT_THRESHOLD) */
382   } else if (small_alloc_list == NULL || alloc_Hp + n > alloc_HpLim) {
383     if (small_alloc_list) {
384       small_alloc_list->free = alloc_Hp;
385     }
386     bd = allocBlock();
387     bd->link = small_alloc_list;
388     small_alloc_list = bd;
389     bd->gen = g0;
390     bd->step = g0s0;
391     bd->evacuated = 0;
392     alloc_Hp = bd->start;
393     alloc_HpLim = bd->start + BLOCK_SIZE_W;
394     alloc_blocks++;
395   }
396   
397   p = alloc_Hp;
398   alloc_Hp += n;
399   RELEASE_LOCK(&sm_mutex);
400   return p;
401 }
402
403 lnat allocated_bytes(void)
404 {
405   return (alloc_blocks * BLOCK_SIZE_W - (alloc_HpLim - alloc_Hp));
406 }
407
408 /* -----------------------------------------------------------------------------
409    Allocation functions for GMP.
410
411    These all use the allocate() interface - we can't have any garbage
412    collection going on during a gmp operation, so we use allocate()
413    which always succeeds.  The gmp operations which might need to
414    allocate will ask the storage manager (via doYouWantToGC()) whether
415    a garbage collection is required, in case we get into a loop doing
416    only allocate() style allocation.
417    -------------------------------------------------------------------------- */
418
419 static void *
420 stgAllocForGMP (size_t size_in_bytes)
421 {
422   StgArrWords* arr;
423   nat data_size_in_words, total_size_in_words;
424   
425   /* should be a multiple of sizeof(StgWord) (whole no. of limbs) */
426   ASSERT(size_in_bytes % sizeof(W_) == 0);
427   
428   data_size_in_words  = size_in_bytes / sizeof(W_);
429   total_size_in_words = sizeofW(StgArrWords) + data_size_in_words;
430   
431   /* allocate and fill it in. */
432   arr = (StgArrWords *)allocate(total_size_in_words);
433   SET_ARR_HDR(arr, &ARR_WORDS_info, CCCS, data_size_in_words);
434   
435   /* and return a ptr to the goods inside the array */
436   return(BYTE_ARR_CTS(arr));
437 }
438
439 static void *
440 stgReallocForGMP (void *ptr, size_t old_size, size_t new_size)
441 {
442     void *new_stuff_ptr = stgAllocForGMP(new_size);
443     nat i = 0;
444     char *p = (char *) ptr;
445     char *q = (char *) new_stuff_ptr;
446
447     for (; i < old_size; i++, p++, q++) {
448         *q = *p;
449     }
450
451     return(new_stuff_ptr);
452 }
453
454 static void
455 stgDeallocForGMP (void *ptr STG_UNUSED, 
456                   size_t size STG_UNUSED)
457 {
458     /* easy for us: the garbage collector does the dealloc'n */
459 }
460
461 /* -----------------------------------------------------------------------------
462  * Stats and stuff
463  * -------------------------------------------------------------------------- */
464
465 /* -----------------------------------------------------------------------------
466  * calcAllocated()
467  *
468  * Approximate how much we've allocated: number of blocks in the
469  * nursery + blocks allocated via allocate() - unused nusery blocks.
470  * This leaves a little slop at the end of each block, and doesn't
471  * take into account large objects (ToDo).
472  * -------------------------------------------------------------------------- */
473
474 lnat
475 calcAllocated( void )
476 {
477   nat allocated;
478   bdescr *bd;
479
480 #ifdef SMP
481   Capability *cap;
482
483   /* All tasks must be stopped */
484   ASSERT(n_free_capabilities == RtsFlags.ConcFlags.nNodes);
485
486   allocated = 
487     n_free_capabilities * RtsFlags.GcFlags.minAllocAreaSize * BLOCK_SIZE_W
488     + allocated_bytes();
489
490   for (cap = free_capabilities; cap != NULL; cap = cap->link) {
491     for ( bd = cap->rCurrentNursery->link; bd != NULL; bd = bd->link ) {
492       allocated -= BLOCK_SIZE_W;
493     }
494     if (cap->rCurrentNursery->free < cap->rCurrentNursery->start 
495         + BLOCK_SIZE_W) {
496       allocated -= (cap->rCurrentNursery->start + BLOCK_SIZE_W)
497         - cap->rCurrentNursery->free;
498     }
499   }
500
501 #else /* !SMP */
502   bdescr *current_nursery = MainRegTable.rCurrentNursery;
503
504   allocated = (nursery_blocks * BLOCK_SIZE_W) + allocated_bytes();
505   for ( bd = current_nursery->link; bd != NULL; bd = bd->link ) {
506     allocated -= BLOCK_SIZE_W;
507   }
508   if (current_nursery->free < current_nursery->start + BLOCK_SIZE_W) {
509     allocated -= (current_nursery->start + BLOCK_SIZE_W)
510       - current_nursery->free;
511   }
512 #endif
513
514   return allocated;
515 }  
516
517 /* Approximate the amount of live data in the heap.  To be called just
518  * after garbage collection (see GarbageCollect()).
519  */
520 extern lnat 
521 calcLive(void)
522 {
523   nat g, s;
524   lnat live = 0;
525   step *step;
526
527   if (RtsFlags.GcFlags.generations == 1) {
528     live = (g0s0->to_blocks - 1) * BLOCK_SIZE_W + 
529       ((lnat)g0s0->hp_bd->free - (lnat)g0s0->hp_bd->start) / sizeof(W_);
530     return live;
531   }
532
533   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
534     for (s = 0; s < generations[g].n_steps; s++) {
535       /* approximate amount of live data (doesn't take into account slop
536        * at end of each block).
537        */
538       if (g == 0 && s == 0) { 
539           continue; 
540       }
541       step = &generations[g].steps[s];
542       live += (step->n_blocks - 1) * BLOCK_SIZE_W +
543         ((lnat)step->hp_bd->free - (lnat)step->hp_bd->start) / sizeof(W_);
544     }
545   }
546   return live;
547 }
548
549 /* Approximate the number of blocks that will be needed at the next
550  * garbage collection.
551  *
552  * Assume: all data currently live will remain live.  Steps that will
553  * be collected next time will therefore need twice as many blocks
554  * since all the data will be copied.
555  */
556 extern lnat 
557 calcNeeded(void)
558 {
559   lnat needed = 0;
560   nat g, s;
561   step *step;
562
563   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
564     for (s = 0; s < generations[g].n_steps; s++) {
565       if (g == 0 && s == 0) { continue; }
566       step = &generations[g].steps[s];
567       if (generations[g].steps[0].n_blocks > generations[g].max_blocks) {
568         needed += 2 * step->n_blocks;
569       } else {
570         needed += step->n_blocks;
571       }
572     }
573   }
574   return needed;
575 }
576
577 /* -----------------------------------------------------------------------------
578    Debugging
579
580    memInventory() checks for memory leaks by counting up all the
581    blocks we know about and comparing that to the number of blocks
582    allegedly floating around in the system.
583    -------------------------------------------------------------------------- */
584
585 #ifdef DEBUG
586
587 extern void
588 memInventory(void)
589 {
590   nat g, s;
591   step *step;
592   bdescr *bd;
593   lnat total_blocks = 0, free_blocks = 0;
594
595   /* count the blocks we current have */
596
597   for (g = 0; g < RtsFlags.GcFlags.generations; g++) {
598     for (s = 0; s < generations[g].n_steps; s++) {
599       step = &generations[g].steps[s];
600       total_blocks += step->n_blocks;
601       if (RtsFlags.GcFlags.generations == 1) {
602         /* two-space collector has a to-space too :-) */
603         total_blocks += g0s0->to_blocks;
604       }
605       for (bd = step->large_objects; bd; bd = bd->link) {
606         total_blocks += bd->blocks;
607         /* hack for megablock groups: they have an extra block or two in
608            the second and subsequent megablocks where the block
609            descriptors would normally go.
610         */
611         if (bd->blocks > BLOCKS_PER_MBLOCK) {
612           total_blocks -= (MBLOCK_SIZE / BLOCK_SIZE - BLOCKS_PER_MBLOCK)
613                           * (bd->blocks/(MBLOCK_SIZE/BLOCK_SIZE));
614         }
615       }
616     }
617   }
618
619   /* any blocks held by allocate() */
620   for (bd = small_alloc_list; bd; bd = bd->link) {
621     total_blocks += bd->blocks;
622   }
623   for (bd = large_alloc_list; bd; bd = bd->link) {
624     total_blocks += bd->blocks;
625   }
626   
627   /* count the blocks on the free list */
628   free_blocks = countFreeList();
629
630   ASSERT(total_blocks + free_blocks == mblocks_allocated * BLOCKS_PER_MBLOCK);
631
632 #if 0
633   if (total_blocks + free_blocks != mblocks_allocated *
634       BLOCKS_PER_MBLOCK) {
635     fprintf(stderr, "Blocks: %ld live + %ld free  = %ld total (%ld around)\n",
636             total_blocks, free_blocks, total_blocks + free_blocks,
637             mblocks_allocated * BLOCKS_PER_MBLOCK);
638   }
639 #endif
640 }
641
642 /* Full heap sanity check. */
643
644 extern void
645 checkSanity(nat N)
646 {
647   nat g, s;
648
649   if (RtsFlags.GcFlags.generations == 1) {
650     checkHeap(g0s0->to_space, NULL);
651     checkChain(g0s0->large_objects);
652   } else {
653     
654     for (g = 0; g <= N; g++) {
655       for (s = 0; s < generations[g].n_steps; s++) {
656         if (g == 0 && s == 0) { continue; }
657         checkHeap(generations[g].steps[s].blocks, NULL);
658       }
659     }
660     for (g = N+1; g < RtsFlags.GcFlags.generations; g++) {
661       for (s = 0; s < generations[g].n_steps; s++) {
662         checkHeap(generations[g].steps[s].blocks,
663                   generations[g].steps[s].blocks->start);
664         checkChain(generations[g].steps[s].large_objects);
665       }
666     }
667     checkFreeListSanity();
668   }
669 }
670
671 #endif