#include "moar.h" /* Creates a new second generation allocator. */ MVMGen2Allocator * MVM_gc_gen2_create(MVMInstance *i) { /* Create allocator data structure. */ MVMGen2Allocator *al = MVM_malloc(sizeof(MVMGen2Allocator)); /* Create empty size classes array data structure. */ al->size_classes = (MVMGen2SizeClass *)MVM_calloc(MVM_GEN2_BINS, sizeof(MVMGen2SizeClass)); /* Set up overflows area. */ al->alloc_overflows = MVM_GEN2_OVERFLOWS; al->num_overflows = 0; al->overflows = MVM_malloc(al->alloc_overflows * sizeof(MVMCollectable *)); return al; } /* Sets up a size class bin in the second generation. */ static void setup_bin(MVMGen2Allocator *al, MVMuint32 bin) { /* Work out page size we want. */ MVMuint32 page_size = MVM_GEN2_PAGE_ITEMS * ((bin + 1) << MVM_GEN2_BIN_BITS); /* We'll just allocate a single page to start off with. */ al->size_classes[bin].num_pages = 1; al->size_classes[bin].pages = MVM_malloc(sizeof(void *) * al->size_classes[bin].num_pages); al->size_classes[bin].pages[0] = MVM_malloc(page_size); /* Set up allocation position and limit. */ al->size_classes[bin].alloc_pos = al->size_classes[bin].pages[0]; al->size_classes[bin].alloc_limit = al->size_classes[bin].alloc_pos + page_size; /* Free list is empty until GC run (and we just do page by page allocation). */ al->size_classes[bin].free_list = NULL; } /* Adds a new page to a size class bin. */ static void add_page(MVMGen2Allocator *al, MVMuint32 bin) { /* Work out page size. */ MVMuint32 page_size = MVM_GEN2_PAGE_ITEMS * ((bin + 1) << MVM_GEN2_BIN_BITS); /* Add the extra page. */ MVMuint32 cur_page = al->size_classes[bin].num_pages; al->size_classes[bin].num_pages++; al->size_classes[bin].pages = MVM_realloc(al->size_classes[bin].pages, sizeof(void *) * al->size_classes[bin].num_pages); al->size_classes[bin].pages[cur_page] = MVM_malloc(page_size); /* Set up allocation position and limit. */ al->size_classes[bin].alloc_pos = al->size_classes[bin].pages[cur_page]; al->size_classes[bin].alloc_limit = al->size_classes[bin].alloc_pos + page_size; /* set the cur_page to a proper value */ al->size_classes[bin].cur_page = cur_page; } /* Allocates space using the second generation allocator and returns * a pointer to the allocated space. Does not zero the space or set * it up in any way. */ void * MVM_gc_gen2_allocate(MVMGen2Allocator *al, MVMuint32 size) { void *result; /* Determine the bin. If we hit a bin exactly then it's off-by-one, * since the bins list is base-0. Otherwise we've some extra bits, * which round us up to the next bin, but that's a no-op. */ MVMuint32 bin = (size >> MVM_GEN2_BIN_BITS); if ((size & MVM_GEN2_BIN_MASK) == 0) bin--; /* If the selected bin is in range... */ if (bin < MVM_GEN2_BINS) { /* If we've no pages yet, never encountered this bin; set it up. */ if (al->size_classes[bin].pages == NULL) setup_bin(al, bin); /* If there's a free list entry, use that. */ if (al->size_classes[bin].free_list) { result = (void *)al->size_classes[bin].free_list; al->size_classes[bin].free_list = (char **)*(al->size_classes[bin].free_list); } else { /* If we're at the page limit, add a new page. */ if (al->size_classes[bin].alloc_pos == al->size_classes[bin].alloc_limit) add_page(al, bin); /* Now we can allocate. */ result = al->size_classes[bin].alloc_pos; al->size_classes[bin].alloc_pos += (bin + 1) << MVM_GEN2_BIN_BITS; } } else { /* We're beyond the size class bins, so resort to malloc. */ result = MVM_malloc(size); /* Add to overflows list. */ if (al->num_overflows == al->alloc_overflows) { al->alloc_overflows *= 2; al->overflows = MVM_realloc(al->overflows, al->alloc_overflows * sizeof(MVMCollectable *)); } al->overflows[al->num_overflows++] = result; } return result; } /* Allocates space using the second generation allocator and returns * a pointer to the allocated space. Promises the memory will be * zeroed, except that the MVMCollectable gen 2 flag will get set. */ void * MVM_gc_gen2_allocate_zeroed(MVMGen2Allocator *al, MVMuint32 size) { void *a = MVM_gc_gen2_allocate(al, size); memset(a, 0, size); ((MVMCollectable *)a)->flags2 = MVM_CF_SECOND_GEN; return a; } /* Frees all memory associated with the second generation. */ void MVM_gc_gen2_destroy(MVMInstance *i, MVMGen2Allocator *al) { MVMuint32 j, k; /* Remove all pages. */ for (j = 0; j < MVM_GEN2_BINS; j++) { for (k = 0; k < al->size_classes[j].num_pages; k++) MVM_free(al->size_classes[j].pages[k]); MVM_free(al->size_classes[j].pages); } /* Free any allocated overflows. */ for (j = 0; j < al->num_overflows; j++) if (al->overflows[j]) MVM_free(al->overflows[j]); /* Clean up allocator data structure. */ MVM_free(al->size_classes); al->size_classes = NULL; MVM_free(al->overflows); al->overflows = NULL; MVM_free(al); } /* blindly move pages from one gen2 to another */ void MVM_gc_gen2_transfer(MVMThreadContext *src, MVMThreadContext *dest) { MVMGen2Allocator *gen2 = src->gen2, *dest_gen2 = dest->gen2; MVMuint32 bin, obj_size, page; char ***freelist_insert_pos; for (bin = 0; bin < MVM_GEN2_BINS; bin++) { MVMuint32 orig_dest_num_pages = dest_gen2->size_classes[bin].num_pages; char *cur_ptr, *end_ptr; /* If we've nothing allocated in this size class, skip it. */ if (gen2->size_classes[bin].pages == NULL) continue; if (dest_gen2->size_classes[bin].pages == NULL) dest_gen2->size_classes[bin].free_list = NULL; /* Calculate object size for this bin. */ obj_size = (bin + 1) << MVM_GEN2_BIN_BITS; /* freelist_insert_pos is a pointer to a memory location that * stores the address of the last traversed free list node (char **). */ /* Initialize freelist insertion position to free list head. */ freelist_insert_pos = &gen2->size_classes[bin].free_list; if (dest_gen2->size_classes[bin].pages == NULL) { dest_gen2->size_classes[bin].pages = MVM_malloc(sizeof(void *) * gen2->size_classes[bin].num_pages); dest_gen2->size_classes[bin].num_pages = gen2->size_classes[bin].num_pages; } else { dest_gen2->size_classes[bin].num_pages += gen2->size_classes[bin].num_pages; dest_gen2->size_classes[bin].pages = MVM_realloc(dest_gen2->size_classes[bin].pages, sizeof(void *) * dest_gen2->size_classes[bin].num_pages); } /* Visit each page in the source. */ for (page = 0; page < gen2->size_classes[bin].num_pages; page++) { /* Visit all the objects, looking for dead ones and swap the * owner for each of them. */ cur_ptr = gen2->size_classes[bin].pages[page]; end_ptr = page + 1 == gen2->size_classes[bin].num_pages ? gen2->size_classes[bin].alloc_pos : cur_ptr + obj_size * MVM_GEN2_PAGE_ITEMS; while (cur_ptr < end_ptr) { if (cur_ptr == (char *)freelist_insert_pos) { /* skip */ } else if (cur_ptr == (char *)*freelist_insert_pos) { /* printf("found a free list slot in bin %d page %d: %d with value %d and start %d and limit %d\n", bin, page, cur_ptr, *(void **)cur_ptr, gen2->size_classes[bin].pages[page], dest_gen2->size_classes[bin].alloc_limit);*/ freelist_insert_pos = (char ***)cur_ptr; } else { /* note: we don't have tests that exercise this path yet. */ /* printf("updating an owner from %d to %d\n", ((MVMCollectable *)cur_ptr)->owner, dest->thread_id);*/ ((MVMCollectable *)cur_ptr)->owner = dest->thread_id; } /* Move to the next object. */ cur_ptr += obj_size; } dest_gen2->size_classes[bin].pages[page + orig_dest_num_pages] = gen2->size_classes[bin].pages[page]; } freelist_insert_pos = &dest_gen2->size_classes[bin].free_list; while (*freelist_insert_pos) { freelist_insert_pos = (char ***)*freelist_insert_pos; } /* chain the destination's freelist through any remaining unallocated area */ if (dest_gen2->size_classes[bin].alloc_pos) { cur_ptr = dest_gen2->size_classes[bin].alloc_pos; end_ptr = dest_gen2->size_classes[bin].alloc_limit; while (cur_ptr < end_ptr) { *freelist_insert_pos = (char **)cur_ptr; freelist_insert_pos = (char ***)cur_ptr; cur_ptr += obj_size; } } /* link to the new pages, if any */ *freelist_insert_pos = gen2->size_classes[bin].free_list; dest_gen2->size_classes[bin].alloc_pos = gen2->size_classes[bin].alloc_pos; dest_gen2->size_classes[bin].alloc_limit = gen2->size_classes[bin].alloc_limit; MVM_free(gen2->size_classes[bin].pages); gen2->size_classes[bin].pages = NULL; gen2->size_classes[bin].num_pages = 0; } { /* transfer the overflows */ MVMuint32 i; if (gen2->num_overflows + dest_gen2->num_overflows > dest_gen2->alloc_overflows) { dest_gen2->alloc_overflows = ( gen2->alloc_overflows > dest_gen2->alloc_overflows ? gen2->alloc_overflows : dest_gen2->alloc_overflows ) * 2; /* double the larger of the 2 sizes */ dest_gen2->overflows = MVM_realloc(dest_gen2->overflows, dest_gen2->alloc_overflows * sizeof(MVMCollectable *)); } for (i = 0; i < gen2->num_overflows; i++) gen2->overflows[i]->owner = dest->thread_id; memcpy( &dest_gen2->overflows[dest_gen2->num_overflows], gen2->overflows, gen2->num_overflows * sizeof(MVMCollectable *) ); dest_gen2->num_overflows += gen2->num_overflows; gen2->num_overflows = 0; gen2->alloc_overflows = 0; MVM_free(gen2->overflows); gen2->overflows = NULL; } { /* copy the roots... */ MVMuint32 i, n = src->num_gen2roots; for ( i = 0; i < n; i++) { MVM_gc_root_gen2_add(dest, src->gen2roots[i]); } src->num_gen2roots = 0; src->alloc_gen2roots = 0; MVM_free(src->gen2roots); src->gen2roots = NULL; } } void MVM_gc_gen2_compact_overflows(MVMGen2Allocator *al) { /* compact the overflow list to prevent it from growing without bounds */ MVMCollectable **overflows = al->overflows; const MVMuint32 num_overflows = al->num_overflows; MVMuint32 cursor = 0; MVMuint32 live; /* Find the first NULL object. */ while (cursor < num_overflows && overflows[cursor]) cursor++; live = cursor; /* Slide others back so the alive ones are at the start of the list. */ while (cursor < num_overflows) { if (overflows[cursor]) { overflows[live++] = overflows[cursor]; } cursor++; } al->num_overflows = live; }