MCAS changes from Matt
[openafs.git] / src / mcas / gc.c
1 /******************************************************************************
2  * gc.c
3  *
4  * A fully recycling epoch-based garbage collector. Works by counting
5  * threads in and out of critical regions, to work out when
6  * garbage queues can be fully deleted.
7  *
8  * Copyright (c) 2001-2003, K A Fraser
9
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are
12 met:
13
14     * Redistributions of source code must retain the above copyright
15     * notice, this list of conditions and the following disclaimer.
16     * Redistributions in binary form must reproduce the above
17     * copyright notice, this list of conditions and the following
18     * disclaimer in the documentation and/or other materials provided
19     * with the distribution.  Neither the name of the Keir Fraser
20     * nor the names of its contributors may be used to endorse or
21     * promote products derived from this software without specific
22     * prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  */
36
37 #include <assert.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/mman.h>
42 #include <unistd.h>
43 #include "portable_defns.h"
44 #include "gc.h"
45 #include <afs/afsutil.h>
46
47 /*#define MINIMAL_GC*/
48 /*#define YIELD_TO_HELP_PROGRESS*/
49 #define PROFILE_GC
50
51 /* Recycled nodes are filled with this value if WEAK_MEM_ORDER. */
52 #define INVALID_BYTE 0
53 #define INITIALISE_NODES(_p,_c) memset((_p), INVALID_BYTE, (_c));
54
55 /* Number of unique block sizes we can deal with. Equivalently, the
56  * number of unique object caches which can be created. */
57 #define MAX_SIZES 30
58
59 #define MAX_HOOKS 4
60
61 /*
62  * The initial number of allocation chunks for each per-blocksize list.
63  * Popular allocation lists will steadily increase the allocation unit
64  * in line with demand.
65  */
66 #define ALLOC_CHUNKS_PER_LIST 10
67
68 /*
69  * How many times should a thread call gc_enter(), seeing the same epoch
70  * each time, before it makes a reclaim attempt?
71  */
72 #define ENTRIES_PER_RECLAIM_ATTEMPT 100
73
74 /*
75  *  0: current epoch -- threads are moving to this;
76  * -1: some threads may still throw garbage into this epoch;
77  * -2: no threads can see this epoch => we can zero garbage lists;
78  * -3: all threads see zeros in these garbage lists => move to alloc lists.
79  */
80 #ifdef WEAK_MEM_ORDER
81 #define NR_EPOCHS 4
82 #else
83 #define NR_EPOCHS 3
84 #endif
85
86 /*
87  * A chunk amortises the cost of allocation from shared lists. It also
88  * helps when zeroing nodes, as it increases per-cacheline pointer density
89  * and means that node locations don't need to be brought into the cache
90  * (most architectures have a non-temporal store instruction).
91  */
92 #define BLKS_PER_CHUNK 100
93 typedef struct chunk_st chunk_t;
94 struct chunk_st
95 {
96     chunk_t *next;             /* chunk chaining                 */
97     unsigned int i;            /* the next entry in blk[] to use */
98     void *blk[BLKS_PER_CHUNK];
99 };
100
101 static struct gc_global_st
102 {
103     CACHE_PAD(0);
104
105     /* The current epoch. */
106     VOLATILE unsigned int current;
107     CACHE_PAD(1);
108
109     /* Exclusive access to gc_reclaim(). */
110     VOLATILE unsigned int inreclaim;
111     CACHE_PAD(2);
112
113
114     /* Allocator caches currently defined */
115     long n_allocators;
116
117     /*
118      * RUN-TIME CONSTANTS (to first approximation)
119      */
120
121     /* Memory page size, in bytes. */
122     unsigned int page_size;
123
124     /* Node sizes (run-time constants). */
125     int nr_sizes;
126     int blk_sizes[MAX_SIZES];
127
128     /* Registered epoch hooks. */
129     int nr_hooks;
130     hook_fn_t hook_fns[MAX_HOOKS];
131     CACHE_PAD(3);
132
133     /*
134      * DATA WE MAY HIT HARD
135      */
136
137     /* Chain of free, empty chunks. */
138     chunk_t * VOLATILE free_chunks;
139
140     /* Main allocation lists. */
141     chunk_t * VOLATILE alloc[MAX_SIZES];
142     VOLATILE unsigned int alloc_size[MAX_SIZES];
143 #ifdef PROFILE_GC
144     VOLATILE unsigned int total_size;
145     VOLATILE unsigned int allocations;
146 #endif
147 } gc_global;
148
149
150 /* Per-thread state. */
151 struct gc_st
152 {
153     /* Epoch that this thread sees. */
154     unsigned int epoch;
155
156     /* Number of calls to gc_entry() since last gc_reclaim() attempt. */
157     unsigned int entries_since_reclaim;
158
159 #ifdef YIELD_TO_HELP_PROGRESS
160     /* Number of calls to gc_reclaim() since we last yielded. */
161     unsigned int reclaim_attempts_since_yield;
162 #endif
163
164     /* Used by gc_async_barrier(). */
165     void *async_page;
166     int   async_page_state;
167
168     /* Garbage lists. */
169     chunk_t *garbage[NR_EPOCHS][MAX_SIZES];
170     chunk_t *garbage_tail[NR_EPOCHS][MAX_SIZES];
171     chunk_t *chunk_cache;
172
173     /* Local allocation lists. */
174     chunk_t *alloc[MAX_SIZES];
175     unsigned int alloc_chunks[MAX_SIZES];
176
177     /* Hook pointer lists. */
178     chunk_t *hook[NR_EPOCHS][MAX_HOOKS];
179 };
180
181 /* XXX generalize */
182 #ifndef KERNEL
183
184 #define MEM_FAIL(_s) \
185 do { \
186     fprintf(stderr, "OUT OF MEMORY: %d bytes at line %d\n", (_s), __LINE__); \
187     abort(); \
188 } while ( 0 )
189 #endif
190
191 /* Allocate more empty chunks from the heap. */
192 #define CHUNKS_PER_ALLOC 1000
193 static chunk_t *alloc_more_chunks(void)
194 {
195     int i;
196     chunk_t *h, *p;
197
198     h = p = ALIGNED_ALLOC(CHUNKS_PER_ALLOC * sizeof(*h));
199     if ( h == NULL ) MEM_FAIL(CHUNKS_PER_ALLOC * sizeof(*h));
200
201     for ( i = 1; i < CHUNKS_PER_ALLOC; i++ )
202     {
203         p->next = p + 1;
204         p++;
205     }
206
207     p->next = h;
208
209     return(h);
210 }
211
212
213 /* Put a chain of chunks onto a list. */
214 static void add_chunks_to_list(chunk_t *ch, chunk_t *head)
215 {
216     chunk_t *h_next, *new_h_next, *ch_next;
217     ch_next    = ch->next;
218     new_h_next = head->next;
219     do { ch->next = h_next = new_h_next; WMB_NEAR_CAS(); }
220     while ( (new_h_next = CASPO(&head->next, h_next, ch_next)) != h_next );
221 }
222
223
224 /* Allocate a chain of @n empty chunks. Pointers may be garbage. */
225 static chunk_t *get_empty_chunks(int n)
226 {
227     int i;
228     chunk_t *new_rh, *rh, *rt, *head;
229
230  retry:
231     head = gc_global.free_chunks;
232     new_rh = head->next;
233     do {
234         rh = new_rh;
235         rt = head;
236         WEAK_DEP_ORDER_RMB();
237         for ( i = 0; i < n; i++ )
238         {
239             if ( (rt = rt->next) == head )
240             {
241                 /* Allocate some more chunks. */
242                 add_chunks_to_list(alloc_more_chunks(), head);
243                 goto retry;
244             }
245         }
246     }
247     while ( (new_rh = CASPO(&head->next, rh, rt->next)) != rh );
248
249     rt->next = rh;
250     return(rh);
251 }
252
253
254 /* Get @n filled chunks, pointing at blocks of @sz bytes each. */
255 static chunk_t *get_filled_chunks(int n, int sz)
256 {
257     chunk_t *h, *p;
258     char *node;
259     int i;
260
261 #ifdef PROFILE_GC
262     ADD_TO(gc_global.total_size, n * BLKS_PER_CHUNK * sz);
263     ADD_TO(gc_global.allocations, 1);
264 #endif
265
266     node = ALIGNED_ALLOC(n * BLKS_PER_CHUNK * sz);
267     if ( node == NULL ) MEM_FAIL(n * BLKS_PER_CHUNK * sz);
268 #ifdef WEAK_MEM_ORDER
269     INITIALISE_NODES(node, n * BLKS_PER_CHUNK * sz);
270 #endif
271
272     h = p = get_empty_chunks(n);
273     do {
274         p->i = BLKS_PER_CHUNK;
275         for ( i = 0; i < BLKS_PER_CHUNK; i++ )
276         {
277             p->blk[i] = node;
278             node += sz;
279         }
280     }
281     while ( (p = p->next) != h );
282
283     return(h);
284 }
285
286
287 /*
288  * gc_async_barrier: Cause an asynchronous barrier in all other threads. We do
289  * this by causing a TLB shootdown to be propagated to all other processors.
290  * Each time such an action is required, this function calls:
291  *   mprotect(async_page, <page size>, <new flags>)
292  * Each thread's state contains a memory page dedicated for this purpose.
293  */
294 #ifdef WEAK_MEM_ORDER
295 static void gc_async_barrier(gc_t *gc)
296 {
297     mprotect(gc->async_page, gc_global.page_size,
298              gc->async_page_state ? PROT_READ : PROT_NONE);
299     gc->async_page_state = !gc->async_page_state;
300 }
301 #else
302 #define gc_async_barrier(_g) ((void)0)
303 #endif
304
305
306 /* Grab a level @i allocation chunk from main chain. */
307 static chunk_t *get_alloc_chunk(gc_t *gc, int i)
308 {
309     chunk_t *alloc, *p, *new_p, *nh;
310     unsigned int sz;
311
312     alloc = gc_global.alloc[i];
313     new_p = alloc->next;
314
315     do {
316         p = new_p;
317         while ( p == alloc )
318         {
319             sz = gc_global.alloc_size[i];
320             nh = get_filled_chunks(sz, gc_global.blk_sizes[i]);
321             ADD_TO(gc_global.alloc_size[i], sz >> 3);
322             gc_async_barrier(gc);
323             add_chunks_to_list(nh, alloc);
324             p = alloc->next;
325         }
326         WEAK_DEP_ORDER_RMB();
327     }
328     while ( (new_p = CASPO(&alloc->next, p, p->next)) != p );
329
330     p->next = p;
331     assert(p->i == BLKS_PER_CHUNK);
332     return(p);
333 }
334
335
336 #ifndef MINIMAL_GC
337 /*
338  * gc_reclaim: Scans the list of struct gc_perthread looking for the lowest
339  * maximum epoch number seen by a thread that's in the list code. If it's the
340  * current epoch, the "nearly-free" lists from the previous epoch are
341  * reclaimed, and the epoch is incremented.
342  */
343 static void gc_reclaim(void)
344 {
345     ptst_t       *ptst, *first_ptst, *our_ptst = NULL;
346     gc_t         *gc = NULL;
347     unsigned long curr_epoch;
348     chunk_t      *ch, *t;
349     int           two_ago, three_ago, i, j;
350  
351     /* Barrier to entering the reclaim critical section. */
352     if ( gc_global.inreclaim || CASIO(&gc_global.inreclaim, 0, 1) ) return;
353
354     /*
355      * Grab first ptst structure *before* barrier -- prevent bugs
356      * on weak-ordered architectures.
357      */
358     first_ptst = ptst_first();
359     MB();
360     curr_epoch = gc_global.current;
361
362     /* Have all threads seen the current epoch, or not in mutator code? */
363     for ( ptst = first_ptst; ptst != NULL; ptst = ptst_next(ptst) )
364     {
365         if ( (ptst->count > 1) && (ptst->gc->epoch != curr_epoch) ) goto out;
366     }
367
368     /*
369      * Three-epoch-old garbage lists move to allocation lists.
370      * Two-epoch-old garbage lists are cleaned out.
371      */
372     two_ago   = (curr_epoch+2) % NR_EPOCHS;
373     three_ago = (curr_epoch+1) % NR_EPOCHS;
374     if ( gc_global.nr_hooks != 0 )
375         our_ptst = (ptst_t *)pthread_getspecific(ptst_key);
376     for ( ptst = first_ptst; ptst != NULL; ptst = ptst_next(ptst) )
377     {
378         gc = ptst->gc;
379
380         for ( i = 0; i < gc_global.nr_sizes; i++ )
381         {
382 #ifdef WEAK_MEM_ORDER
383             int sz = gc_global.blk_sizes[i];
384             if ( gc->garbage[two_ago][i] != NULL )
385             {
386                 chunk_t *head = gc->garbage[two_ago][i];
387                 ch = head;
388                 do {
389                     int j;
390                     for ( j = 0; j < ch->i; j++ )
391                         INITIALISE_NODES(ch->blk[j], sz);
392                 }
393                 while ( (ch = ch->next) != head );
394             }
395 #endif
396
397             /* NB. Leave one chunk behind, as it is probably not yet full. */
398             t = gc->garbage[three_ago][i];
399             if ( (t == NULL) || ((ch = t->next) == t) ) continue;
400             gc->garbage_tail[three_ago][i]->next = ch;
401             gc->garbage_tail[three_ago][i] = t;
402             t->next = t;
403             add_chunks_to_list(ch, gc_global.alloc[i]);
404         }
405
406         for ( i = 0; i < gc_global.nr_hooks; i++ )
407         {
408             hook_fn_t fn = gc_global.hook_fns[i];
409             ch = gc->hook[three_ago][i];
410             if ( ch == NULL ) continue;
411             gc->hook[three_ago][i] = NULL;
412
413             t = ch;
414             do { for ( j = 0; j < t->i; j++ ) fn(our_ptst, t->blk[j]); }
415             while ( (t = t->next) != ch );
416
417             add_chunks_to_list(ch, gc_global.free_chunks);
418         }
419     }
420
421     /* Update current epoch. */
422     WMB();
423     gc_global.current = (curr_epoch+1) % NR_EPOCHS;
424
425  out:
426     gc_global.inreclaim = 0;
427 }
428 #endif /* MINIMAL_GC */
429
430
431 void *gc_alloc(ptst_t *ptst, int alloc_id)
432 {
433     gc_t *gc = ptst->gc;
434     chunk_t *ch;
435
436     ch = gc->alloc[alloc_id];
437     if ( ch->i == 0 )
438     {
439         if ( gc->alloc_chunks[alloc_id]++ == 100 )
440         {
441             gc->alloc_chunks[alloc_id] = 0;
442             add_chunks_to_list(ch, gc_global.free_chunks);
443             gc->alloc[alloc_id] = ch = get_alloc_chunk(gc, alloc_id);
444         }
445         else
446         {
447             chunk_t *och = ch;
448             ch = get_alloc_chunk(gc, alloc_id);
449             ch->next  = och->next;
450             och->next = ch;
451             gc->alloc[alloc_id] = ch;
452         }
453     }
454
455     return ch->blk[--ch->i];
456 }
457
458 int
459 gc_get_blocksize(int alloc_id)
460 {
461     return (gc_global.blk_sizes[alloc_id]);
462 }
463
464 static chunk_t *chunk_from_cache(gc_t *gc)
465 {
466     chunk_t *ch = gc->chunk_cache, *p = ch->next;
467
468     if ( ch == p )
469     {
470         gc->chunk_cache = get_empty_chunks(100);
471     }
472     else
473     {
474         ch->next = p->next;
475         p->next  = p;
476     }
477
478     p->i = 0;
479     return(p);
480 }
481
482
483 void gc_free(ptst_t *ptst, void *p, int alloc_id)
484 {
485 #ifndef MINIMAL_GC
486     gc_t *gc = ptst->gc;
487     chunk_t *prev, *new, *ch = gc->garbage[gc->epoch][alloc_id];
488
489     if ( ch == NULL )
490     {
491         gc->garbage[gc->epoch][alloc_id] = ch = chunk_from_cache(gc);
492         gc->garbage_tail[gc->epoch][alloc_id] = ch;
493     }
494     else if ( ch->i == BLKS_PER_CHUNK )
495     {
496         prev = gc->garbage_tail[gc->epoch][alloc_id];
497         new  = chunk_from_cache(gc);
498         gc->garbage[gc->epoch][alloc_id] = new;
499         new->next  = ch;
500         prev->next = new;
501         ch = new;
502     }
503
504     ch->blk[ch->i++] = p;
505 #endif
506 }
507
508
509 void gc_add_ptr_to_hook_list(ptst_t *ptst, void *ptr, int hook_id)
510 {
511     gc_t *gc = ptst->gc;
512     chunk_t *och, *ch = gc->hook[gc->epoch][hook_id];
513
514     if ( ch == NULL )
515     {
516         gc->hook[gc->epoch][hook_id] = ch = chunk_from_cache(gc);
517     }
518     else
519     {
520         ch = ch->next;
521         if ( ch->i == BLKS_PER_CHUNK )
522         {
523             och       = gc->hook[gc->epoch][hook_id];
524             ch        = chunk_from_cache(gc);
525             ch->next  = och->next;
526             och->next = ch;
527         }
528     }
529
530     ch->blk[ch->i++] = ptr;
531 }
532
533
534 void gc_unsafe_free(ptst_t *ptst, void *p, int alloc_id)
535 {
536     gc_t *gc = ptst->gc;
537     chunk_t *ch;
538
539     ch = gc->alloc[alloc_id];
540     if ( ch->i < BLKS_PER_CHUNK )
541     {
542         ch->blk[ch->i++] = p;
543     }
544     else
545     {
546         gc_free(ptst, p, alloc_id);
547     }
548 }
549
550
551 void gc_enter(ptst_t *ptst)
552 {
553 #ifdef MINIMAL_GC
554     ptst->count++;
555     MB();
556 #else
557     gc_t *gc = ptst->gc;
558     int new_epoch, cnt;
559
560  retry:
561     cnt = ptst->count++;
562     MB();
563     if ( cnt == 1 )
564     {
565         new_epoch = gc_global.current;
566         if ( gc->epoch != new_epoch )
567         {
568             gc->epoch = new_epoch;
569             gc->entries_since_reclaim        = 0;
570 #ifdef YIELD_TO_HELP_PROGRESS
571             gc->reclaim_attempts_since_yield = 0;
572 #endif
573         }
574         else if ( gc->entries_since_reclaim++ == 100 )
575         {
576             ptst->count--;
577 #ifdef YIELD_TO_HELP_PROGRESS
578             if ( gc->reclaim_attempts_since_yield++ == 10000 )
579             {
580                 gc->reclaim_attempts_since_yield = 0;
581                 sched_yield();
582             }
583 #endif
584             gc->entries_since_reclaim = 0;
585             gc_reclaim();
586             goto retry;
587         }
588     }
589 #endif
590 }
591
592
593 void gc_exit(ptst_t *ptst)
594 {
595     MB();
596     ptst->count--;
597 }
598
599
600 gc_t *gc_init(void)
601 {
602     gc_t *gc;
603     int   i;
604
605     gc = ALIGNED_ALLOC(sizeof(*gc));
606     if ( gc == NULL ) MEM_FAIL(sizeof(*gc));
607     memset(gc, 0, sizeof(*gc));
608
609 #ifdef WEAK_MEM_ORDER
610     /* Initialise shootdown state. */
611     gc->async_page = mmap(NULL, gc_global.page_size, PROT_NONE,
612                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
613     if ( gc->async_page == (void *)MAP_FAILED ) MEM_FAIL(gc_global.page_size);
614     gc->async_page_state = 1;
615 #endif
616
617     gc->chunk_cache = get_empty_chunks(100);
618
619     /* Get ourselves a set of allocation chunks. */
620     for ( i = 0; i < gc_global.nr_sizes; i++ )
621     {
622         gc->alloc[i] = get_alloc_chunk(gc, i);
623     }
624     for ( ; i < MAX_SIZES; i++ )
625     {
626         gc->alloc[i] = chunk_from_cache(gc);
627     }
628
629     return(gc);
630 }
631
632
633 int
634 gc_add_allocator(int alloc_size)
635 {
636     int ni, i;
637
638     RMB();
639     FASPO(&gc_global.n_allocators, gc_global.n_allocators + 1);
640     if (gc_global.n_allocators > MAX_SIZES) {
641         /* critical error */
642 #if !defined(KERNEL)
643         printf("MCAS gc max allocators exceeded, aborting\n");
644 #endif
645         abort();
646     }
647
648     i = gc_global.nr_sizes;
649     while ((ni = CASIO(&gc_global.nr_sizes, i, i + 1)) != i)
650         i = ni;
651     gc_global.blk_sizes[i] = alloc_size;
652     gc_global.alloc_size[i] = ALLOC_CHUNKS_PER_LIST;
653     gc_global.alloc[i] = get_filled_chunks(ALLOC_CHUNKS_PER_LIST, alloc_size);
654     return i;
655 }
656
657
658 void gc_remove_allocator(int alloc_id)
659 {
660     /* This is a no-op for now. */
661 }
662
663
664 int gc_add_hook(hook_fn_t fn)
665 {
666     int ni, i = gc_global.nr_hooks;
667     while ( (ni = CASIO(&gc_global.nr_hooks, i, i+1)) != i ) i = ni;
668     gc_global.hook_fns[i] = fn;
669     return i;
670 }
671
672
673 void gc_remove_hook(int hook_id)
674 {
675     /* This is a no-op for now. */
676 }
677
678
679 void _destroy_gc_subsystem(void)
680 {
681 #ifdef PROFILE_GC
682     printf("Total heap: %u bytes (%.2fMB) in %u allocations\n",
683            gc_global.total_size, (double)gc_global.total_size / 1000000,
684            gc_global.allocations);
685 #endif
686 }
687
688
689 void _init_gc_subsystem(void)
690 {
691     memset(&gc_global, 0, sizeof(gc_global));
692
693     gc_global.page_size   = (unsigned int)sysconf(_SC_PAGESIZE);
694     gc_global.free_chunks = alloc_more_chunks();
695
696     gc_global.nr_hooks = 0;
697     gc_global.nr_sizes = 0;
698 }