factor/vm/data_gc.h

319 lines
8.5 KiB
C

/* Set by the -S command line argument */
bool secure_gc;
/* set up guard pages to check for under/overflow.
size must be a multiple of the page size */
F_SEGMENT *alloc_segment(CELL size);
void dealloc_segment(F_SEGMENT *block);
CELL untagged_object_size(CELL pointer);
CELL unaligned_object_size(CELL pointer);
CELL object_size(CELL pointer);
CELL binary_payload_start(CELL pointer);
void begin_scan(void);
CELL next_object(void);
DECLARE_PRIMITIVE(data_room);
DECLARE_PRIMITIVE(size);
DECLARE_PRIMITIVE(begin_scan);
DECLARE_PRIMITIVE(next_object);
DECLARE_PRIMITIVE(end_scan);
/* generational copying GC divides memory into zones */
typedef struct {
/* allocation pointer is 'here'; its offset is hardcoded in the
compiler backends, see core/compiler/.../allot.factor */
CELL start;
CELL here;
CELL size;
CELL end;
} F_ZONE;
typedef struct {
F_SEGMENT *segment;
CELL young_size;
CELL aging_size;
CELL gen_count;
F_ZONE *generations;
F_ZONE* semispaces;
CELL *cards;
CELL *cards_end;
} F_DATA_HEAP;
F_DATA_HEAP *data_heap;
/* card marking write barrier. a card is a byte storing a mark flag,
and the offset (in cells) of the first object in the card.
the mark flag is set by the write barrier when an object in the
card has a slot written to.
the offset of the first object is set by the allocator. */
/* if CARD_POINTS_TO_NURSERY is set, CARD_POINTS_TO_AGING must also be set. */
#define CARD_POINTS_TO_NURSERY 0x80
#define CARD_POINTS_TO_AGING 0x40
#define CARD_MARK_MASK (CARD_POINTS_TO_NURSERY | CARD_POINTS_TO_AGING)
#define CARD_BASE_MASK 0x3f
typedef u8 F_CARD;
/* A card is 64 bytes. 6 bits is sufficient to represent every
offset within the card */
#define CARD_SIZE 64
#define CARD_BITS 6
#define ADDR_CARD_MASK (CARD_SIZE-1)
INLINE void clear_card(F_CARD *c)
{
*c = CARD_BASE_MASK; /* invalid value */
}
DLLEXPORT CELL cards_offset;
void init_cards_offset(void);
#define ADDR_TO_CARD(a) (F_CARD*)(((CELL)(a) >> CARD_BITS) + cards_offset)
#define CARD_TO_ADDR(c) (CELL*)(((CELL)(c) - cards_offset)<<CARD_BITS)
/* this is an inefficient write barrier. compiled definitions use a more
efficient one hand-coded in assembly. the write barrier must be called
any time we are potentially storing a pointer from an older generation
to a younger one */
INLINE void write_barrier(CELL address)
{
F_CARD *c = ADDR_TO_CARD(address);
*c |= (CARD_POINTS_TO_NURSERY | CARD_POINTS_TO_AGING);
}
#define SLOT(obj,slot) (UNTAG(obj) + (slot) * CELLS)
INLINE void set_slot(CELL obj, CELL slot, CELL value)
{
put(SLOT(obj,slot),value);
write_barrier(obj);
}
/* we need to remember the first object allocated in the card */
INLINE void allot_barrier(CELL address)
{
F_CARD *ptr = ADDR_TO_CARD(address);
F_CARD c = *ptr;
CELL b = (c & CARD_BASE_MASK);
CELL a = (address & ADDR_CARD_MASK);
*ptr = ((c & CARD_MARK_MASK) | ((b < a) ? b : a));
}
void clear_cards(CELL from, CELL to);
void collect_cards(void);
/* the 0th generation is where new objects are allocated. */
#define NURSERY 0
#define HAVE_NURSERY_P (data_heap->gen_count>1)
/* where objects hang around */
#define AGING (data_heap->gen_count-2)
#define HAVE_AGING_P (data_heap->gen_count>2)
/* the oldest generation */
#define TENURED (data_heap->gen_count-1)
/* used during garbage collection only */
F_ZONE *newspace;
/* new objects are allocated here */
DLLEXPORT F_ZONE *nursery;
INLINE bool in_zone(F_ZONE *z, CELL pointer)
{
return pointer >= z->start && pointer < z->end;
}
CELL init_zone(F_ZONE *z, CELL size, CELL base);
void init_data_heap(CELL gens,
CELL young_size,
CELL aging_size,
bool secure_gc_);
/* statistics */
s64 gc_time;
CELL minor_collections;
CELL cards_scanned;
/* only meaningful during a GC */
bool performing_gc;
CELL collecting_gen;
bool collecting_code;
/* if true, we collecting AGING space for the second time, so if it is still
full, we go on to collect TENURED */
bool collecting_aging_again;
INLINE bool collecting_accumulation_gen_p(void)
{
return ((HAVE_AGING_P
&& collecting_gen == AGING
&& !collecting_aging_again)
|| collecting_gen == TENURED);
}
/* What generation was being collected when collect_literals() was last
called? Until the next call to primitive_add_compiled_block(), future
collections of younger generations don't have to touch the code
heap. */
CELL last_code_heap_scan;
/* sometimes we grow the heap */
bool growing_data_heap;
F_DATA_HEAP *old_data_heap;
/* Every object has a regular representation in the runtime, which makes GC
much simpler. Every slot of the object until binary_payload_start is a pointer
to some other object. */
INLINE void do_slots(CELL obj, void (* iter)(CELL *))
{
CELL scan = obj;
CELL payload_start = binary_payload_start(obj);
CELL end = obj + payload_start;
scan += CELLS;
while(scan < end)
{
iter((CELL *)scan);
scan += CELLS;
}
}
/* test if the pointer is in generation being collected, or a younger one.
init_data_heap() arranges things so that the older generations are first,
so we have to check that the pointer occurs after the beginning of
the requested generation. */
INLINE bool should_copy(CELL untagged)
{
if(in_zone(newspace,untagged))
return false;
if(collecting_gen == TENURED)
return true;
else if(HAVE_AGING_P && collecting_gen == AGING)
return !in_zone(&data_heap->generations[TENURED],untagged);
else if(HAVE_NURSERY_P && collecting_gen == NURSERY)
return in_zone(&data_heap->generations[NURSERY],untagged);
else
{
critical_error("Bug in should_copy",untagged);
return false;
}
}
void copy_handle(CELL *handle);
/* in case a generation fills up in the middle of a gc, we jump back
up to try collecting the next generation. */
jmp_buf gc_jmp;
/* A heap walk allows useful things to be done, like finding all
references to an object for debugging purposes. */
CELL heap_scan_ptr;
/* GC is off during heap walking */
bool gc_off;
void garbage_collection(volatile CELL gen,
bool code_gc,
bool growing_data_heap_,
CELL requested_bytes);
/* If a runtime function needs to call another function which potentially
allocates memory, it must store any local variable references to Factor
objects on the root stack */
F_SEGMENT *extra_roots_region;
CELL extra_roots;
DEFPUSHPOP(root_,extra_roots)
#define REGISTER_ROOT(obj) root_push(obj)
#define UNREGISTER_ROOT(obj) obj = root_pop()
#define REGISTER_UNTAGGED(obj) root_push(obj ? tag_object(obj) : 0)
#define UNREGISTER_UNTAGGED(obj) obj = untag_object(root_pop())
#define REGISTER_STRING(obj) REGISTER_UNTAGGED(obj)
#define UNREGISTER_STRING(obj) UNREGISTER_UNTAGGED(obj)
/* We ignore strings which point outside the data heap, but we might be given
a char* which points inside the data heap, in which case it is a root, for
example if we call unbox_char_string() the result is placed in a byte array */
INLINE bool root_push_alien(const void *ptr)
{
if((CELL)ptr > data_heap->segment->start
&& (CELL)ptr < data_heap->segment->end)
{
F_BYTE_ARRAY *objptr = ((F_BYTE_ARRAY *)ptr) - 1;
if(objptr->header == tag_header(BYTE_ARRAY_TYPE))
{
root_push(tag_object(objptr));
return true;
}
}
return false;
}
#define REGISTER_C_STRING(obj) \
bool obj##_root = root_push_alien(obj)
#define UNREGISTER_C_STRING(obj) \
if(obj##_root) obj = alien_offset(root_pop())
#define REGISTER_BIGNUM(obj) if(obj) root_push(tag_bignum(obj))
#define UNREGISTER_BIGNUM(obj) if(obj) obj = (untag_object(root_pop()))
INLINE void *allot_zone(F_ZONE *z, CELL a)
{
CELL h = z->here;
z->here = h + align8(a);
return (void*)h;
}
/* We leave this many bytes free at the top of the nursery so that inline
allocation (which does not call GC because of possible roots in volatile
registers) does not run out of memory */
#define ALLOT_BUFFER_ZONE 1024
INLINE void maybe_gc(CELL a)
{
/* If we are requesting a huge object, grow immediately */
if(nursery->size - ALLOT_BUFFER_ZONE <= a)
garbage_collection(TENURED,false,true,a);
/* If we have enough space in the nursery, just return.
Otherwise, perform a GC - this may grow the heap if
tenured space cannot hold all live objects from the nursery
even after a full GC */
else if(a + ALLOT_BUFFER_ZONE + nursery->here > nursery->end)
garbage_collection(NURSERY,false,false,0);
/* There is now sufficient room in the nursery for 'a' */
}
/*
* It is up to the caller to fill in the object's fields in a meaningful
* fashion!
*/
INLINE void* allot_object(CELL type, CELL length)
{
maybe_gc(length);
CELL* object = allot_zone(nursery,length);
*object = tag_header(type);
return object;
}
CELL collect_next(CELL scan);
DLLEXPORT void simple_gc(void);
void data_gc(void);
DECLARE_PRIMITIVE(data_gc);
DECLARE_PRIMITIVE(gc_time);
DECLARE_PRIMITIVE(become);