* Author:
* Paolo Molaro (lupus@ximian.com)
*
- * Copyright (C) 2005-2006 Novell, Inc
+ * Copyright 2005-2009 Novell, Inc (http://www.novell.com)
*
* Thread start/stop adapted from Boehm's GC:
* Copyright (c) 1994 by Xerox Corporation. All rights reserved.
Multi-dim arrays have the same issue for rank == 1 for the bounds data.
*) implement a card table as the write barrier instead of remembered sets?
*) some sort of blacklist support?
- *) fin_ready_list is part of the root set, too
+ *) fin_ready_list and critical_fin_list are part of the root set, too
*) consider lowering the large object min size to 16/32KB or so and benchmark
*) once mark-compact is implemented we could still keep the
copying collector for the old generation and use it if we think
#include "metadata/mono-gc.h"
#include "metadata/method-builder.h"
#include "metadata/profiler-private.h"
+#include "metadata/monitor.h"
#include "utils/mono-mmap.h"
#ifdef HAVE_VALGRIND_MEMCHECK_H
}
*/
-#define MAX_DEBUG_LEVEL 9
+#define MAX_DEBUG_LEVEL 8
#define DEBUG(level,a) do {if (G_UNLIKELY ((level) <= MAX_DEBUG_LEVEL && (level) <= gc_debug_level)) a;} while (0)
#define TV_DECLARE(name) struct timeval name
struct _FinalizeEntry {
FinalizeEntry *next;
void *object;
- void *data; /* can be a disappearing link or the data for the finalizer */
- /* Note we could use just one pointer if we don't support multiple callbacks
- * for finalizers and per-finalizer data and if we store the obj pointers
- * in the link like libgc does
- */
};
+typedef struct _FinalizeEntryHashTable FinalizeEntryHashTable;
+struct _FinalizeEntryHashTable {
+ FinalizeEntry **table;
+ mword size;
+ int num_registered;
+};
+
+typedef struct _DisappearingLink DisappearingLink;
+struct _DisappearingLink {
+ DisappearingLink *next;
+ void **link;
+};
+
+typedef struct _DisappearingLinkHashTable DisappearingLinkHashTable;
+struct _DisappearingLinkHashTable {
+ DisappearingLink **table;
+ mword size;
+ int num_links;
+};
+
+enum {
+ GENERATION_NURSERY,
+ GENERATION_OLD,
+ GENERATION_MAX
+};
+
+/*
+ * The link pointer is hidden by negating each bit. We use the lowest
+ * bit of the link (before negation) to store whether it needs
+ * resurrection tracking.
+ */
+#define HIDE_POINTER(p,t) ((gpointer)(~((gulong)(p)|((t)?1:0))))
+#define REVEAL_POINTER(p) ((gpointer)((~(gulong)(p))&~3L))
+
+#define DISLINK_OBJECT(d) (REVEAL_POINTER (*(d)->link))
+#define DISLINK_TRACK(d) ((~(gulong)(*(d)->link)) & 1)
+
/*
* The finalizable hash has the object as the key, the
* disappearing_link hash, has the link address as key.
*/
-static FinalizeEntry **finalizable_hash = NULL;
+static FinalizeEntryHashTable minor_finalizable_hash;
+static FinalizeEntryHashTable major_finalizable_hash;
/* objects that are ready to be finalized */
static FinalizeEntry *fin_ready_list = NULL;
-/* disappearing links use the same structure but a different list */
-static FinalizeEntry **disappearing_link_hash = NULL;
-static mword disappearing_link_hash_size = 0;
-static mword finalizable_hash_size = 0;
+static FinalizeEntry *critical_fin_list = NULL;
+
+static DisappearingLinkHashTable minor_disappearing_link_hash;
+static DisappearingLinkHashTable major_disappearing_link_hash;
-static int num_registered_finalizers = 0;
static int num_ready_finalizers = 0;
-static int num_disappearing_links = 0;
static int no_finalize = 0;
/* keep each size a multiple of ALLOC_ALIGN */
return FALSE;
}
+static int slot_for_size (size_t size);
+
+static void
+free_pinned_object (PinnedChunk *chunk, char *obj, size_t size)
+{
+ void **p = (void**)obj;
+ int slot = slot_for_size (size);
+
+ g_assert (obj >= (char*)chunk->start_data && obj < ((char*)chunk + chunk->num_pages * FREELIST_PAGESIZE));
+ *p = chunk->free_list [slot];
+ chunk->free_list [slot] = p;
+}
+
enum {
ROOT_TYPE_NORMAL = 0, /* "normal" roots */
ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */
/*
* used when moving the objects
* When the nursery is collected, objects are copied to to_space.
- * The area between to_space and gray_objects is used as a stack
+ * The area between gray_first and gray_objects is used as a stack
* of objects that need their fields checked for more references
* to be copied.
* We should optimize somehow this mechanism to avoid rescanning
* test cache misses and other graph traversal orders.
*/
static char *to_space = NULL;
+static char *gray_first = NULL;
static char *gray_objects = NULL;
static char *to_space_end = NULL;
static GCMemSection *to_space_section = NULL;
/* objects bigger then this go into the large object space */
#define MAX_SMALL_OBJ_SIZE 0xffff
+/* Functions supplied by the runtime to be called by the GC */
+static MonoGCCallbacks gc_callbacks;
+
/*
* ######################################################################
* ######## Macros and function declarations.
static int stop_world (void);
static int restart_world (void);
-static void pin_thread_data (void *start_nursery, void *end_nursery);
+static void scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise);
static void scan_from_remsets (void *start_nursery, void *end_nursery);
static void find_pinning_ref_from_thread (char *obj, size_t size);
static void update_current_thread_stack (void *start);
static GCMemSection* alloc_section (size_t size);
-static void finalize_in_range (char *start, char *end);
-static void null_link_in_range (char *start, char *end);
+static void finalize_in_range (char *start, char *end, int generation);
+static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track,
+ DisappearingLinkHashTable *hash);
+static void null_link_in_range (char *start, char *end, int generation);
+static void null_links_for_domain (MonoDomain *domain, int generation);
static gboolean search_fragment_for_size (size_t size);
static void mark_pinned_from_addresses (PinnedChunk *chunk, void **start, void **end);
static void clear_remsets (void);
static void clear_tlabs (void);
static char *find_tlab_next_from_address (char *addr);
+static void scan_pinned_objects (void (*callback) (PinnedChunk*, char*, size_t, void*), void *callback_data);
static void sweep_pinned_objects (void);
static void scan_from_pinned_objects (char *addr_start, char *addr_end);
static void free_large_object (LOSObject *obj);
static void free_mem_section (GCMemSection *section);
+static void mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track);
+
void describe_ptr (char *ptr);
void check_consistency (void);
char* check_object (char *start);
stored_size += ALLOC_ALIGN - 1;
stored_size &= ~(ALLOC_ALIGN - 1);
for (i = 0; i < numbits; ++i) {
- if (bitmap [i / GC_BITS_PER_WORD] & (1 << (i % GC_BITS_PER_WORD))) {
+ if (bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
if (first_set < 0)
first_set = i;
last_set = i;
int first_set = -1, num_set = 0, last_set = -1, i;
mword desc = vector? DESC_TYPE_VECTOR: DESC_TYPE_ARRAY;
for (i = 0; i < numbits; ++i) {
- if (elem_bitmap [i / GC_BITS_PER_WORD] & (1 << (i % GC_BITS_PER_WORD))) {
+ if (elem_bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
if (first_set < 0)
first_set = i;
last_set = i;
return (void*) desc;
}
+/* Return the bitmap encoded by a descriptor */
+gsize*
+mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
+{
+ mword d = (mword)descr;
+ gsize *bitmap;
+
+ switch (d & 0x7) {
+ case DESC_TYPE_RUN_LENGTH: {
+ int first_set = (d >> 16) & 0xff;
+ int num_set = (d >> 16) & 0xff;
+ int i;
+
+ bitmap = g_new0 (gsize, (first_set + num_set + 7) / 8);
+
+ for (i = first_set; i < first_set + num_set; ++i)
+ bitmap [i / GC_BITS_PER_WORD] |= ((gsize)1 << (i % GC_BITS_PER_WORD));
+
+ *numbits = first_set + num_set;
+
+ return bitmap;
+ }
+ case DESC_TYPE_SMALL_BITMAP:
+ bitmap = g_new0 (gsize, 1);
+
+ bitmap [0] = (d >> SMALL_BITMAP_SHIFT) << OBJECT_HEADER_WORDS;
+
+ *numbits = GC_BITS_PER_WORD;
+
+ return bitmap;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
/* helper macros to scan and traverse objects, macros because we resue them in many functions */
#define STRING_SIZE(size,str) do { \
(size) = sizeof (MonoString) + 2 * (mono_string_length ((MonoString*)(str)) + 1); \
* This section of code deals with detecting the objects no longer in use
* and reclaiming the memory.
*/
-#if 0
static void __attribute__((noinline))
scan_area (char *start, char *end)
{
type_rlen++;
continue;
} else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
- skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
- skip_size *= mono_array_length ((MonoArray*)start);
- skip_size += sizeof (MonoArray);
+ skip_size = safe_object_get_size ((MonoObject*)start);
skip_size += (ALLOC_ALIGN - 1);
skip_size &= ~(ALLOC_ALIGN - 1);
OBJ_VECTOR_FOREACH_PTR (vt, start);
type_str, type_rlen, type_vector, type_bitmap, type_lbit, type_complex);*/
}
+static gboolean
+need_remove_object_for_domain (char *start, MonoDomain *domain)
+{
+ GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
+ if (mono_object_domain (start) == domain) {
+ DEBUG (1, fprintf (gc_debug_file, "Need to cleanup object %p, (%s)\n", start, safe_name (start)));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+process_object_for_domain_clearing (char *start, MonoDomain *domain)
+{
+ GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
+ /* The object could be a proxy for an object in the domain
+ we're deleting. */
+ if (mono_class_has_parent (vt->klass, mono_defaults.real_proxy_class)) {
+ MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server;
+
+ /* The server could already have been zeroed out, so
+ we need to check for that, too. */
+ if (server && (!LOAD_VTABLE (server) || mono_object_domain (server) == domain)) {
+ DEBUG (1, fprintf (gc_debug_file, "Cleaning up remote pointer in %p to object %p (%s)\n",
+ start, server, LOAD_VTABLE (server) ? safe_name (server) : "null"));
+ ((MonoRealProxy*)start)->unwrapped_server = NULL;
+ }
+ }
+}
+
static void __attribute__((noinline))
scan_area_for_domain (MonoDomain *domain, char *start, char *end)
{
GCVTable *vt;
size_t skip_size;
- int type, remove;
+ int type;
+ gboolean remove;
mword desc;
while (start < end) {
continue;
}
vt = (GCVTable*)LOAD_VTABLE (start);
- /* handle threads someway (maybe insert the root domain vtable?) */
- if (mono_object_domain (start) == domain && vt->klass != mono_defaults.thread_class) {
- DEBUG (1, fprintf (gc_debug_file, "Need to cleanup object %p, (%s)\n", start, safe_name (start)));
- remove = 1;
- } else {
- remove = 0;
+ process_object_for_domain_clearing (start, domain);
+ remove = need_remove_object_for_domain (start, domain);
+ if (remove && ((MonoObject*)start)->synchronisation) {
+ void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)start);
+ if (dislink)
+ mono_gc_register_disappearing_link (NULL, dislink, FALSE);
}
desc = vt->desc;
type = desc & 0x7;
start += skip_size;
continue;
} else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
- skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
- skip_size *= mono_array_length ((MonoArray*)start);
- skip_size += sizeof (MonoArray);
+ skip_size = safe_object_get_size ((MonoObject*)start);
skip_size += (ALLOC_ALIGN - 1);
skip_size &= ~(ALLOC_ALIGN - 1);
if (type == DESC_TYPE_ARRAY) {
}
}
+static void
+clear_domain_process_pinned_object_callback (PinnedChunk *chunk, char *obj, size_t size, MonoDomain *domain)
+{
+ process_object_for_domain_clearing (obj, domain);
+}
+
+static void
+clear_domain_free_pinned_object_callback (PinnedChunk *chunk, char *obj, size_t size, MonoDomain *domain)
+{
+ if (need_remove_object_for_domain (obj, domain))
+ free_pinned_object (chunk, obj, size);
+}
+
/*
* When appdomains are unloaded we can easily remove objects that have finalizers,
* but all the others could still be present in random places on the heap.
mono_gc_clear_domain (MonoDomain * domain)
{
GCMemSection *section;
+ LOSObject *bigobj, *prev;
+ Fragment *frag;
+ int i;
+
LOCK_GC;
+ /* Clear all remaining nursery fragments */
+ if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
+ g_assert (nursery_next <= nursery_frag_real_end);
+ memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
+ for (frag = nursery_fragments; frag; frag = frag->next) {
+ memset (frag->fragment_start, 0, frag->fragment_end - frag->fragment_start);
+ }
+ }
+
for (section = section_list; section; section = section->next) {
scan_area_for_domain (domain, section->data, section->end_data);
}
- /* FIXME: handle big and fixed objects (we remove, don't clear in this case) */
+
+ /* We need two passes over pinned and large objects because
+ freeing such an object gives its memory back to the OS (in
+ the case of large objects) or obliterates its vtable
+ (pinned objects), but we might need to dereference a
+ pointer from an object to another object if the first
+ object is a proxy. */
+ scan_pinned_objects (clear_domain_process_pinned_object_callback, domain);
+ for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
+ process_object_for_domain_clearing (bigobj->data, domain);
+
+ prev = NULL;
+ for (bigobj = los_object_list; bigobj;) {
+ if (need_remove_object_for_domain (bigobj->data, domain)) {
+ LOSObject *to_free = bigobj;
+ if (prev)
+ prev->next = bigobj->next;
+ else
+ los_object_list = bigobj->next;
+ bigobj = bigobj->next;
+ DEBUG (1, fprintf (gc_debug_file, "Freeing large object %p (%s)\n",
+ bigobj->data, safe_name (bigobj->data)));
+ free_large_object (to_free);
+ continue;
+ }
+ prev = bigobj;
+ bigobj = bigobj->next;
+ }
+ scan_pinned_objects (clear_domain_free_pinned_object_callback, domain);
+
+ for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
+ null_links_for_domain (domain, i);
+
UNLOCK_GC;
}
-#endif
/*
* add_to_global_remset:
global_remset = rs;
if (root) {
*(global_remset->store_next++) = (mword)ptr | REMSET_OTHER;
- *(global_remset->store_next++) = (mword)REMSET_LOCATION;
+ *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION;
} else {
*(global_remset->store_next++) = (mword)ptr;
}
return NULL;
}
+/*
+ * drain_gray_stack:
+ *
+ * Scan objects in the gray stack until the stack is empty. This should be called
+ * frequently after each object is copied, to achieve better locality and cache
+ * usage.
+ */
+static void inline
+drain_gray_stack (char *start_addr, char *end_addr)
+{
+ char *gray_start = gray_first;
+
+ while (gray_start < gray_objects) {
+ DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", gray_start, safe_name (gray_start)));
+ gray_start = scan_object (gray_start, start_addr, end_addr);
+ }
+
+ gray_first = gray_start;
+}
+
/*
* scan_vtype:
*
* *) the _last_ managed stack frame
* *) pointers slots in managed frames
*/
- pin_thread_data (start_nursery, end_nursery);
+ scan_thread_data (start_nursery, end_nursery, FALSE);
}
/* Copy function called from user defined mark functions */
while (desc) {
if ((desc & 1) && *start_root) {
*start_root = copy_object (*start_root, n_start, n_end);
- DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", start_root, *start_root)); \
+ DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", start_root, *start_root));
+ drain_gray_stack (n_start, n_end);
}
desc >>= 1;
start_root++;
if ((bmap & 1) && *objptr) {
*objptr = copy_object (*objptr, n_start, n_end);
DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", objptr, *objptr));
+ drain_gray_stack (n_start, n_end);
}
bmap >>= 1;
++objptr;
#ifdef ALIGN_NURSERY
/* Allocate twice the memory to be able to put the nursery at an aligned address */
- g_assert (nursery_size = DEFAULT_NURSERY_SIZE);
+ g_assert (nursery_size == DEFAULT_NURSERY_SIZE);
alloc_size = nursery_size * 2;
data = get_os_memory (alloc_size, TRUE);
/* FIXME: frag here is lost */
}
+static void
+scan_finalizer_entries (FinalizeEntry *list, char *start, char *end) {
+ FinalizeEntry *fin;
+
+ for (fin = list; fin; fin = fin->next) {
+ if (!fin->object)
+ continue;
+ DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
+ fin->object = copy_object (fin->object, start, end);
+ }
+}
+
/*
* Update roots in the old generation. Since we currently don't have the
* info from the write barriers, we just scan all the objects.
scan_old_generation (char *start, char* end)
{
GCMemSection *section;
- FinalizeEntry *fin;
LOSObject *big_object;
char *p;
scan_object (big_object->data, start, end);
}
/* scan the list of objects ready for finalization */
- for (fin = fin_ready_list; fin; fin = fin->next) {
- DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
- fin->object = copy_object (fin->object, start, end);
- }
+ scan_finalizer_entries (fin_ready_list, start, end);
+ scan_finalizer_entries (critical_fin_list, start, end);
}
static mword fragment_total = 0;
return count;
}
+static DisappearingLinkHashTable*
+get_dislink_hash_table (int generation)
+{
+ switch (generation) {
+ case GENERATION_NURSERY: return &minor_disappearing_link_hash;
+ case GENERATION_OLD: return &major_disappearing_link_hash;
+ default: g_assert_not_reached ();
+ }
+}
+
+static FinalizeEntryHashTable*
+get_finalize_entry_hash_table (int generation)
+{
+ switch (generation) {
+ case GENERATION_NURSERY: return &minor_finalizable_hash;
+ case GENERATION_OLD: return &major_finalizable_hash;
+ default: g_assert_not_reached ();
+ }
+}
+
static void
-drain_gray_stack (char *start_addr, char *end_addr)
+finish_gray_stack (char *start_addr, char *end_addr, int generation)
{
TV_DECLARE (atv);
TV_DECLARE (btv);
* We need to walk the LO list as well in search of marked big objects
* (use a flag since this is needed only on major collections). We need to loop
* here as well, so keep a counter of marked LO (increasing it in copy_object).
+ * To achieve better cache locality and cache usage, we drain the gray stack
+ * frequently, after each object is copied, and just finish the work here.
*/
- TV_GETTIME (btv);
- gray_start = to_space;
- DEBUG (6, fprintf (gc_debug_file, "Precise scan of gray area: %p-%p, size: %d\n", gray_start, gray_objects, (int)(gray_objects - gray_start)));
+ gray_start = gray_first;
while (gray_start < gray_objects) {
DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", gray_start, safe_name (gray_start)));
gray_start = scan_object (gray_start, start_addr, end_addr);
}
TV_GETTIME (atv);
- DEBUG (2, fprintf (gc_debug_file, "Gray stack scan: %d usecs\n", TV_ELAPSED (btv, atv)));
//scan_old_generation (start_addr, end_addr);
DEBUG (2, fprintf (gc_debug_file, "Old generation done\n"));
/* walk the finalization queue and move also the objects that need to be
*/
do {
fin_ready = num_ready_finalizers;
- finalize_in_range (start_addr, end_addr);
+ finalize_in_range (start_addr, end_addr, generation);
+ if (generation == GENERATION_OLD)
+ finalize_in_range (nursery_start, nursery_real_end, GENERATION_NURSERY);
bigo_scanned_num = scan_needed_big_objects (start_addr, end_addr);
/* drain the new stack that might have been created */
* GC a finalized object my lose the monitor because it is cleared before the finalizer is
* called.
*/
- null_link_in_range (start_addr, end_addr);
+ null_link_in_range (start_addr, end_addr, generation);
TV_GETTIME (btv);
DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan: %d usecs\n", TV_ELAPSED (atv, btv)));
}
* We reset to_space if we allocated objects in degraded mode.
*/
if (to_space_section)
- to_space = gray_objects = to_space_section->next_data;
+ to_space = gray_objects = gray_first = to_space_section->next_data;
if ((to_space_end - to_space) < max_garbage_amount) {
section = alloc_section (nursery_section->size * 4);
g_assert (nursery_section->size >= max_garbage_amount);
- to_space = gray_objects = section->next_data;
+ to_space = gray_objects = gray_first = section->next_data;
to_space_end = section->end_data;
to_space_section = section;
}
}
/* registered roots, this includes static fields */
scan_from_registered_roots (nursery_start, nursery_next, ROOT_TYPE_NORMAL);
+ scan_thread_data (nursery_start, nursery_next, TRUE);
/* alloc_pinned objects */
scan_from_pinned_objects (nursery_start, nursery_next);
TV_GETTIME (btv);
DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (atv, btv)));
- drain_gray_stack (nursery_start, nursery_next);
+ finish_gray_stack (nursery_start, nursery_next, GENERATION_NURSERY);
/* walk the pin_queue, build up the fragment list of free memory, unmark
* pinned objects as we go, memzero() the empty fragments so they are ready for the
/* prepare the pin queue for the next collection */
last_num_pinned = next_pin_slot;
next_pin_slot = 0;
- if (fin_ready_list) {
+ if (fin_ready_list || critical_fin_list) {
DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
mono_gc_finalize_notify ();
}
LOSObject *bigobj, *prevbo;
int i;
PinnedChunk *chunk;
- FinalizeEntry *fin;
Fragment *frag;
int count;
TV_DECLARE (all_atv);
/* allocate the big to space */
DEBUG (4, fprintf (gc_debug_file, "Allocate tospace for size: %zd\n", copy_space_required));
section = alloc_section (copy_space_required);
- to_space = gray_objects = section->next_data;
+ to_space = gray_objects = gray_first = section->next_data;
to_space_end = section->end_data;
to_space_section = section;
/* registered roots, this includes static fields */
scan_from_registered_roots (heap_start, heap_end, ROOT_TYPE_NORMAL);
scan_from_registered_roots (heap_start, heap_end, ROOT_TYPE_WBARRIER);
+ /* Threads */
+ scan_thread_data (heap_start, heap_end, TRUE);
/* alloc_pinned objects */
scan_from_pinned_objects (heap_start, heap_end);
/* scan the list of objects ready for finalization */
- for (fin = fin_ready_list; fin; fin = fin->next) {
- DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
- fin->object = copy_object (fin->object, heap_start, heap_end);
- }
+ scan_finalizer_entries (fin_ready_list, heap_start, heap_end);
+ scan_finalizer_entries (critical_fin_list, heap_start, heap_end);
TV_GETTIME (atv);
DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (btv, atv)));
/* we need to go over the big object list to see if any was marked and scan it
* And we need to make this in a loop, considering that objects referenced by finalizable
- * objects could reference big objects (this happens in drain_gray_stack ())
+ * objects could reference big objects (this happens in finish_gray_stack ())
*/
scan_needed_big_objects (heap_start, heap_end);
/* all the objects in the heap */
- drain_gray_stack (heap_start, heap_end);
+ finish_gray_stack (heap_start, heap_end, GENERATION_OLD);
+ null_link_in_range (nursery_start, nursery_real_end, GENERATION_NURSERY);
/* sweep the big objects list */
prevbo = NULL;
mono_stats.major_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
/* prepare the pin queue for the next collection */
next_pin_slot = 0;
- if (fin_ready_list) {
+ if (fin_ready_list || critical_fin_list) {
DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
mono_gc_finalize_notify ();
}
}
static void
-sweep_pinned_objects (void)
+scan_pinned_objects (void (*callback) (PinnedChunk*, char*, size_t, void*), void *callback_data)
{
PinnedChunk *chunk;
int i, obj_size;
void *end_chunk;
for (chunk = pinned_chunk_list; chunk; chunk = chunk->next) {
end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
- DEBUG (6, fprintf (gc_debug_file, "Sweeping pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
+ DEBUG (6, fprintf (gc_debug_file, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
for (i = 0; i < chunk->num_pages; ++i) {
obj_size = chunk->page_sizes [i];
if (!obj_size)
ptr = (void**)p;
DEBUG (9, fprintf (gc_debug_file, "Considering %p (vtable: %p)\n", ptr, *ptr));
/* if the first word (the vtable) is outside the chunk we have an object */
- if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk)) {
- if (object_is_pinned (ptr)) {
- unpin_object (ptr);
- DEBUG (6, fprintf (gc_debug_file, "Unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
- } else {
- /* FIXME: add to freelist */
- DEBUG (6, fprintf (gc_debug_file, "Going to free unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
- }
- }
+ if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk))
+ callback (chunk, (char*)ptr, obj_size, callback_data);
p += obj_size;
}
}
}
static void
-scan_from_pinned_objects (char *addr_start, char *addr_end)
+sweep_pinned_objects_callback (PinnedChunk *chunk, char *ptr, size_t size, void *data)
{
- PinnedChunk *chunk;
- int i, obj_size;
- char *p, *endp;
- void **ptr;
- void *end_chunk;
- for (chunk = pinned_chunk_list; chunk; chunk = chunk->next) {
- end_chunk = (char*)chunk + chunk->num_pages * FREELIST_PAGESIZE;
- DEBUG (6, fprintf (gc_debug_file, "Scanning pinned chunk %p (range: %p-%p)\n", chunk, chunk->start_data, end_chunk));
- for (i = 0; i < chunk->num_pages; ++i) {
- obj_size = chunk->page_sizes [i];
- if (!obj_size)
- continue;
- p = i? (char*)chunk + i * FREELIST_PAGESIZE: chunk->start_data;
- endp = i? p + FREELIST_PAGESIZE: (char*)chunk + FREELIST_PAGESIZE;
- DEBUG (6, fprintf (gc_debug_file, "Page %d (size: %d, range: %p-%p)\n", i, obj_size, p, endp));
- while (p + obj_size <= endp) {
- ptr = (void**)p;
- DEBUG (9, fprintf (gc_debug_file, "Considering %p (vtable: %p)\n", ptr, *ptr));
- /* if the first word (the vtable) is outside the chunk we have an object */
- if (*ptr && (*ptr < (void*)chunk || *ptr >= end_chunk)) {
- DEBUG (6, fprintf (gc_debug_file, "Precise object scan %d of alloc_pinned %p (%s)\n", i, ptr, safe_name (ptr)));
- // FIXME: Put objects without references into separate chunks
- // which do not need to be scanned
- scan_object ((char*)ptr, addr_start, addr_end);
- }
- p += obj_size;
- }
- }
+ if (object_is_pinned (ptr)) {
+ unpin_object (ptr);
+ DEBUG (6, fprintf (gc_debug_file, "Unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
+ } else {
+ DEBUG (6, fprintf (gc_debug_file, "Freeing unmarked pinned object %p (%s)\n", ptr, safe_name (ptr)));
+ free_pinned_object (chunk, ptr, size);
}
}
+static void
+sweep_pinned_objects (void)
+{
+ scan_pinned_objects (sweep_pinned_objects_callback, NULL);
+}
+
+static void
+scan_object_callback (PinnedChunk *chunk, char *ptr, size_t size, char **data)
+{
+ DEBUG (6, fprintf (gc_debug_file, "Precise object scan of alloc_pinned %p (%s)\n", ptr, safe_name (ptr)));
+ /* FIXME: Put objects without references into separate chunks
+ which do not need to be scanned */
+ scan_object (ptr, data [0], data [1]);
+}
+
+static void
+scan_from_pinned_objects (char *addr_start, char *addr_end)
+{
+ char *data [2] = { addr_start, addr_end };
+ scan_pinned_objects (scan_object_callback, data);
+}
+
/*
* Find the slot number in the freelist for memory chunks that
* can contain @size objects.
*/
#define object_is_fin_ready(obj) (!object_is_pinned (obj) && !object_is_forwarded (obj))
+static gboolean
+is_critical_finalizer (FinalizeEntry *entry)
+{
+ MonoObject *obj;
+ MonoClass *class;
+
+ if (!mono_defaults.critical_finalizer_object)
+ return FALSE;
+
+ obj = entry->object;
+ class = ((MonoVTable*)LOAD_VTABLE (obj))->klass;
+
+ return mono_class_has_parent (class, mono_defaults.critical_finalizer_object);
+}
+
+static void
+queue_finalization_entry (FinalizeEntry *entry) {
+ if (is_critical_finalizer (entry)) {
+ entry->next = critical_fin_list;
+ critical_fin_list = entry;
+ } else {
+ entry->next = fin_ready_list;
+ fin_ready_list = entry;
+ }
+}
+
+/* LOCKING: requires that the GC lock is held */
static void
-finalize_in_range (char *start, char *end)
+rehash_fin_table (FinalizeEntryHashTable *hash_table)
{
+ FinalizeEntry **finalizable_hash = hash_table->table;
+ mword finalizable_hash_size = hash_table->size;
+ int i;
+ unsigned int hash;
+ FinalizeEntry **new_hash;
+ FinalizeEntry *entry, *next;
+ int new_size = g_spaced_primes_closest (hash_table->num_registered);
+
+ new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*));
+ for (i = 0; i < finalizable_hash_size; ++i) {
+ for (entry = finalizable_hash [i]; entry; entry = next) {
+ hash = mono_object_hash (entry->object) % new_size;
+ next = entry->next;
+ entry->next = new_hash [hash];
+ new_hash [hash] = entry;
+ }
+ }
+ free_internal_mem (finalizable_hash);
+ hash_table->table = new_hash;
+ hash_table->size = new_size;
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+rehash_fin_table_if_necessary (FinalizeEntryHashTable *hash_table)
+{
+ if (hash_table->num_registered >= hash_table->size * 2)
+ rehash_fin_table (hash_table);
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+finalize_in_range (char *start, char *end, int generation)
+{
+ FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
FinalizeEntry *entry, *prev;
int i;
+ FinalizeEntry **finalizable_hash = hash_table->table;
+ mword finalizable_hash_size = hash_table->size;
+
if (no_finalize)
return;
for (i = 0; i < finalizable_hash_size; ++i) {
prev = NULL;
for (entry = finalizable_hash [i]; entry;) {
if ((char*)entry->object >= start && (char*)entry->object < end && ((char*)entry->object < to_space || (char*)entry->object >= to_space_end)) {
- if (object_is_fin_ready (entry->object)) {
+ gboolean is_fin_ready = object_is_fin_ready (entry->object);
+ char *copy = copy_object (entry->object, start, end);
+ if (is_fin_ready) {
char *from;
FinalizeEntry *next;
/* remove and put in fin_ready_list */
finalizable_hash [i] = entry->next;
next = entry->next;
num_ready_finalizers++;
- num_registered_finalizers--;
- entry->next = fin_ready_list;
- fin_ready_list = entry;
+ hash_table->num_registered--;
+ queue_finalization_entry (entry);
/* Make it survive */
from = entry->object;
- entry->object = copy_object (entry->object, start, end);
- DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, num_registered_finalizers));
+ entry->object = copy;
+ DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, hash_table->num_registered));
entry = next;
continue;
} else {
- /* update pointer */
- DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s)\n", entry->object, safe_name (entry->object)));
- entry->object = copy_object (entry->object, start, end);
+ char *from = entry->object;
+ if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
+ FinalizeEntry *next = entry->next;
+ unsigned int major_hash;
+ /* remove from the list */
+ if (prev)
+ prev->next = entry->next;
+ else
+ finalizable_hash [i] = entry->next;
+ hash_table->num_registered--;
+
+ entry->object = copy;
+
+ /* insert it into the major hash */
+ rehash_fin_table_if_necessary (&major_finalizable_hash);
+ major_hash = mono_object_hash ((MonoObject*) copy) %
+ major_finalizable_hash.size;
+ entry->next = major_finalizable_hash.table [major_hash];
+ major_finalizable_hash.table [major_hash] = entry;
+ major_finalizable_hash.num_registered++;
+
+ DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from));
+
+ entry = next;
+ continue;
+ } else {
+ /* update pointer */
+ DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from));
+ entry->object = copy;
+ }
}
}
prev = entry;
}
}
+/* LOCKING: requires that the GC lock is held */
static void
-null_link_in_range (char *start, char *end)
+null_link_in_range (char *start, char *end, int generation)
{
- FinalizeEntry *entry, *prev;
+ DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
+ DisappearingLink **disappearing_link_hash = hash->table;
+ int disappearing_link_hash_size = hash->size;
+ DisappearingLink *entry, *prev;
int i;
+ if (!hash->num_links)
+ return;
for (i = 0; i < disappearing_link_hash_size; ++i) {
prev = NULL;
for (entry = disappearing_link_hash [i]; entry;) {
- if ((char*)entry->object >= start && (char*)entry->object < end && ((char*)entry->object < to_space || (char*)entry->object >= to_space_end)) {
- if (object_is_fin_ready (entry->object)) {
- void **p = entry->data;
- FinalizeEntry *old;
+ char *object = DISLINK_OBJECT (entry);
+ if (object >= start && object < end && (object < to_space || object >= to_space_end)) {
+ gboolean track = DISLINK_TRACK (entry);
+ if (!track && object_is_fin_ready (object)) {
+ void **p = entry->link;
+ DisappearingLink *old;
*p = NULL;
/* remove from list */
if (prev)
prev->next = entry->next;
else
disappearing_link_hash [i] = entry->next;
- DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, entry->object));
+ DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object));
old = entry->next;
free_internal_mem (entry);
entry = old;
- num_disappearing_links--;
+ hash->num_links--;
continue;
} else {
- void **link;
- /* update pointer if it's moved
+ char *copy = copy_object (object, start, end);
+
+ /* Update pointer if it's moved. If the object
+ * has been moved out of the nursery, we need to
+ * remove the link from the minor hash table to
+ * the major one.
+ *
* FIXME: what if an object is moved earlier?
*/
- entry->object = copy_object (entry->object, start, end);
- DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->data, entry->object));
- link = entry->data;
- *link = entry->object;
+
+ if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
+ void **link = entry->link;
+ DisappearingLink *old;
+ /* remove from list */
+ if (prev)
+ prev->next = entry->next;
+ else
+ disappearing_link_hash [i] = entry->next;
+ old = entry->next;
+ free_internal_mem (entry);
+ entry = old;
+ hash->num_links--;
+
+ add_or_remove_disappearing_link ((MonoObject*)copy, link, track,
+ &major_disappearing_link_hash);
+
+ DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy));
+
+ continue;
+ } else {
+ /* We set the track resurrection bit to
+ * FALSE if the object is to be finalized
+ * so that the object can be collected in
+ * the next cycle (i.e. after it was
+ * finalized).
+ */
+ *entry->link = HIDE_POINTER (copy,
+ object_is_fin_ready (object) ? FALSE : track);
+ DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
+ }
}
}
prev = entry;
}
}
-/**
- * mono_gc_finalizers_for_domain:
- * @domain: the unloading appdomain
- * @out_array: output array
- * @out_size: size of output array
- *
- * Store inside @out_array up to @out_size objects that belong to the unloading
- * appdomain @domain. Returns the number of stored items. Can be called repeteadly
- * until it returns 0.
- * The items are removed from the finalizer data structure, so the caller is supposed
- * to finalize them.
- * @out_array should be on the stack to allow the GC to know the objects are still alive.
- */
-int
-mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
+static const char*
+dislink_table_name (DisappearingLinkHashTable *table)
+{
+ if (table == &minor_disappearing_link_hash)
+ return "minor table";
+ return "major table";
+}
+
+/* LOCKING: requires that the GC lock is held */
+static void
+null_links_for_domain (MonoDomain *domain, int generation)
+{
+ DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
+ DisappearingLink **disappearing_link_hash = hash->table;
+ int disappearing_link_hash_size = hash->size;
+ DisappearingLink *entry, *prev;
+ int i;
+ for (i = 0; i < disappearing_link_hash_size; ++i) {
+ prev = NULL;
+ for (entry = disappearing_link_hash [i]; entry; ) {
+ char *object = DISLINK_OBJECT (entry);
+ /* FIXME: actually there should be no object
+ left in the domain with a non-null vtable
+ (provided we remove the Thread special
+ case) */
+ if (object && (!((MonoObject*)object)->vtable || mono_object_domain (object) == domain)) {
+ DisappearingLink *next = entry->next;
+
+ if (prev)
+ prev->next = next;
+ else
+ disappearing_link_hash [i] = next;
+
+ if (*(entry->link)) {
+ *(entry->link) = NULL;
+ g_warning ("Disappearing link %p not freed", entry->link);
+ } else {
+ free_internal_mem (entry);
+ }
+
+ entry = next;
+ continue;
+ }
+ prev = entry;
+ entry = entry->next;
+ }
+ }
+}
+
+/* LOCKING: requires that the GC lock is held */
+static int
+finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
+ FinalizeEntryHashTable *hash_table)
{
+ FinalizeEntry **finalizable_hash = hash_table->table;
+ mword finalizable_hash_size = hash_table->size;
FinalizeEntry *entry, *prev;
int i, count;
+
if (no_finalize || !out_size || !out_array)
return 0;
count = 0;
- LOCK_GC;
for (i = 0; i < finalizable_hash_size; ++i) {
prev = NULL;
for (entry = finalizable_hash [i]; entry;) {
else
finalizable_hash [i] = entry->next;
next = entry->next;
- num_registered_finalizers--;
+ hash_table->num_registered--;
out_array [count ++] = entry->object;
- DEBUG (5, fprintf (gc_debug_file, "Collecting object for finalization: %p (%s) (%d/%d)\n", entry->object, safe_name (entry->object), num_ready_finalizers, num_registered_finalizers));
+ DEBUG (5, fprintf (gc_debug_file, "Collecting object for finalization: %p (%s) (%d/%d)\n", entry->object, safe_name (entry->object), num_ready_finalizers, hash_table->num_registered));
entry = next;
- if (count == out_size) {
- UNLOCK_GC;
+ if (count == out_size)
return count;
- }
continue;
}
prev = entry;
entry = entry->next;
}
}
- UNLOCK_GC;
return count;
}
-static void
-rehash_fin_table (void)
+/**
+ * mono_gc_finalizers_for_domain:
+ * @domain: the unloading appdomain
+ * @out_array: output array
+ * @out_size: size of output array
+ *
+ * Store inside @out_array up to @out_size objects that belong to the unloading
+ * appdomain @domain. Returns the number of stored items. Can be called repeteadly
+ * until it returns 0.
+ * The items are removed from the finalizer data structure, so the caller is supposed
+ * to finalize them.
+ * @out_array should be on the stack to allow the GC to know the objects are still alive.
+ */
+int
+mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
{
- int i;
- unsigned int hash;
- FinalizeEntry **new_hash;
- FinalizeEntry *entry, *next;
- int new_size = g_spaced_primes_closest (num_registered_finalizers);
+ int result;
- new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*));
- for (i = 0; i < finalizable_hash_size; ++i) {
- for (entry = finalizable_hash [i]; entry; entry = next) {
- hash = mono_object_hash (entry->object) % new_size;
- next = entry->next;
- entry->next = new_hash [hash];
- new_hash [hash] = entry;
- }
+ LOCK_GC;
+ result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
+ if (result < out_size) {
+ result += finalizers_for_domain (domain, out_array + result, out_size - result,
+ &major_finalizable_hash);
}
- free_internal_mem (finalizable_hash);
- finalizable_hash = new_hash;
- finalizable_hash_size = new_size;
+ UNLOCK_GC;
+
+ return result;
}
-void
-mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
+static void
+register_for_finalization (MonoObject *obj, void *user_data, FinalizeEntryHashTable *hash_table)
{
+ FinalizeEntry **finalizable_hash;
+ mword finalizable_hash_size;
FinalizeEntry *entry, *prev;
unsigned int hash;
if (no_finalize)
return;
+ g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
hash = mono_object_hash (obj);
LOCK_GC;
- if (num_registered_finalizers >= finalizable_hash_size * 2)
- rehash_fin_table ();
+ rehash_fin_table_if_necessary (hash_table);
+ finalizable_hash = hash_table->table;
+ finalizable_hash_size = hash_table->size;
hash %= finalizable_hash_size;
prev = NULL;
for (entry = finalizable_hash [hash]; entry; entry = entry->next) {
if (entry->object == obj) {
- if (user_data) {
- entry->data = user_data;
- } else {
+ if (!user_data) {
/* remove from the list */
if (prev)
prev->next = entry->next;
else
finalizable_hash [hash] = entry->next;
- num_registered_finalizers--;
- DEBUG (5, fprintf (gc_debug_file, "Removed finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, num_registered_finalizers));
+ hash_table->num_registered--;
+ DEBUG (5, fprintf (gc_debug_file, "Removed finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered));
free_internal_mem (entry);
}
UNLOCK_GC;
}
entry = get_internal_mem (sizeof (FinalizeEntry));
entry->object = obj;
- entry->data = user_data;
entry->next = finalizable_hash [hash];
finalizable_hash [hash] = entry;
- num_registered_finalizers++;
- DEBUG (5, fprintf (gc_debug_file, "Added finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, num_registered_finalizers));
+ hash_table->num_registered++;
+ DEBUG (5, fprintf (gc_debug_file, "Added finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered));
UNLOCK_GC;
}
+void
+mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
+{
+ if (ptr_in_nursery (obj))
+ register_for_finalization (obj, user_data, &minor_finalizable_hash);
+ else
+ register_for_finalization (obj, user_data, &minor_finalizable_hash);
+}
+
static void
-rehash_dislink (void)
+rehash_dislink (DisappearingLinkHashTable *hash_table)
{
+ DisappearingLink **disappearing_link_hash = hash_table->table;
+ int disappearing_link_hash_size = hash_table->size;
int i;
unsigned int hash;
- FinalizeEntry **new_hash;
- FinalizeEntry *entry, *next;
- int new_size = g_spaced_primes_closest (num_disappearing_links);
+ DisappearingLink **new_hash;
+ DisappearingLink *entry, *next;
+ int new_size = g_spaced_primes_closest (hash_table->num_links);
- new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*));
+ new_hash = get_internal_mem (new_size * sizeof (DisappearingLink*));
for (i = 0; i < disappearing_link_hash_size; ++i) {
for (entry = disappearing_link_hash [i]; entry; entry = next) {
- hash = mono_aligned_addr_hash (entry->data) % new_size;
+ hash = mono_aligned_addr_hash (entry->link) % new_size;
next = entry->next;
entry->next = new_hash [hash];
new_hash [hash] = entry;
}
}
free_internal_mem (disappearing_link_hash);
- disappearing_link_hash = new_hash;
- disappearing_link_hash_size = new_size;
+ hash_table->table = new_hash;
+ hash_table->size = new_size;
}
+/* LOCKING: assumes the GC lock is held */
static void
-mono_gc_register_disappearing_link (MonoObject *obj, void *link)
+add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track,
+ DisappearingLinkHashTable *hash_table)
{
- FinalizeEntry *entry, *prev;
+ DisappearingLink *entry, *prev;
unsigned int hash;
- LOCK_GC;
+ DisappearingLink **disappearing_link_hash = hash_table->table;
+ int disappearing_link_hash_size = hash_table->size;
- if (num_disappearing_links >= disappearing_link_hash_size * 2)
- rehash_dislink ();
+ if (hash_table->num_links >= disappearing_link_hash_size * 2) {
+ rehash_dislink (hash_table);
+ disappearing_link_hash = hash_table->table;
+ disappearing_link_hash_size = hash_table->size;
+ }
/* FIXME: add check that link is not in the heap */
hash = mono_aligned_addr_hash (link) % disappearing_link_hash_size;
entry = disappearing_link_hash [hash];
prev = NULL;
for (; entry; entry = entry->next) {
/* link already added */
- if (link == entry->data) {
+ if (link == entry->link) {
/* NULL obj means remove */
if (obj == NULL) {
if (prev)
prev->next = entry->next;
else
disappearing_link_hash [hash] = entry->next;
- num_disappearing_links--;
- DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d)\n", entry, num_disappearing_links));
+ hash_table->num_links--;
+ DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d) from %s\n", entry, hash_table->num_links, dislink_table_name (hash_table)));
free_internal_mem (entry);
+ *link = NULL;
} else {
- entry->object = obj; /* we allow the change of object */
+ *link = HIDE_POINTER (obj, track); /* we allow the change of object */
}
- UNLOCK_GC;
return;
}
prev = entry;
}
- entry = get_internal_mem (sizeof (FinalizeEntry));
- entry->object = obj;
- entry->data = link;
+ if (obj == NULL)
+ return;
+ entry = get_internal_mem (sizeof (DisappearingLink));
+ *link = HIDE_POINTER (obj, track);
+ entry->link = link;
entry->next = disappearing_link_hash [hash];
disappearing_link_hash [hash] = entry;
- num_disappearing_links++;
- DEBUG (5, fprintf (gc_debug_file, "Added dislink %p for object: %p (%s) at %p\n", entry, obj, obj->vtable->klass->name, link));
- UNLOCK_GC;
+ hash_table->num_links++;
+ DEBUG (5, fprintf (gc_debug_file, "Added dislink %p for object: %p (%s) at %p to %s\n", entry, obj, obj->vtable->klass->name, link, dislink_table_name (hash_table)));
+}
+
+/* LOCKING: assumes the GC lock is held */
+static void
+mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
+{
+ add_or_remove_disappearing_link (NULL, link, FALSE, &minor_disappearing_link_hash);
+ add_or_remove_disappearing_link (NULL, link, FALSE, &major_disappearing_link_hash);
+ if (obj) {
+ if (ptr_in_nursery (obj))
+ add_or_remove_disappearing_link (obj, link, track, &minor_disappearing_link_hash);
+ else
+ add_or_remove_disappearing_link (obj, link, track, &major_disappearing_link_hash);
+ }
}
int
mono_gc_invoke_finalizers (void)
{
- FinalizeEntry *entry;
+ FinalizeEntry *entry = NULL;
+ gboolean entry_is_critical;
int count = 0;
void *obj;
/* FIXME: batch to reduce lock contention */
- while (fin_ready_list) {
+ while (fin_ready_list || critical_fin_list) {
LOCK_GC;
- entry = fin_ready_list;
+
+ if (entry) {
+ FinalizeEntry **list = entry_is_critical ? &critical_fin_list : &fin_ready_list;
+
+ /* We have finalized entry in the last
+ interation, now we need to remove it from
+ the list. */
+ if (*list == entry)
+ *list = entry->next;
+ else {
+ FinalizeEntry *e = *list;
+ while (e->next != entry)
+ e = e->next;
+ e->next = entry->next;
+ }
+ free_internal_mem (entry);
+ entry = NULL;
+ }
+
+ /* Now look for the first non-null entry. */
+ for (entry = fin_ready_list; entry && !entry->object; entry = entry->next)
+ ;
+ if (entry) {
+ entry_is_critical = FALSE;
+ } else {
+ entry_is_critical = TRUE;
+ for (entry = critical_fin_list; entry && !entry->object; entry = entry->next)
+ ;
+ }
+
if (entry) {
- fin_ready_list = entry->next;
+ g_assert (entry->object);
num_ready_finalizers--;
obj = entry->object;
+ entry->object = NULL;
DEBUG (7, fprintf (gc_debug_file, "Finalizing object %p (%s)\n", obj, safe_name (obj)));
}
+
UNLOCK_GC;
- if (entry) {
- void (*callback)(void *, void*) = entry->data;
- entry->next = NULL;
- obj = entry->object;
- count++;
- /* the object is on the stack so it is pinned */
- /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/
- callback (obj, NULL);
- free_internal_mem (entry);
- }
+
+ if (!entry)
+ break;
+
+ g_assert (entry->object == NULL);
+ count++;
+ /* the object is on the stack so it is pinned */
+ /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/
+ mono_gc_run_finalize (obj, NULL);
}
+ g_assert (!entry);
return count;
}
gboolean
mono_gc_pending_finalizers (void)
{
- return fin_ready_list != NULL;
+ return fin_ready_list || critical_fin_list;
}
/* Negative value to remove */
char **tlab_temp_end_addr;
char **tlab_real_end_addr;
RememberedSet *remset;
+ gpointer runtime_data;
};
/* FIXME: handle large/small config */
SgenThreadInfo *info = thread_info_lookup (ARCH_GET_THREAD ());
info->stack_start = align_pointer (&ptr);
ARCH_STORE_REGS (ptr);
+ if (gc_callbacks.thread_suspend_func)
+ gc_callbacks.thread_suspend_func (info->runtime_data, NULL);
}
static const char*
/* LOCKING: assumes the GC lock is held (by the stopping thread) */
static void
-suspend_handler (int sig)
+suspend_handler (int sig, siginfo_t *siginfo, void *context)
{
SgenThreadInfo *info;
pthread_t id;
*/
info->stack_start = align_pointer (&id);
+ /* Notify the JIT */
+ if (gc_callbacks.thread_suspend_func)
+ gc_callbacks.thread_suspend_func (info->runtime_data, context);
+
/* notify the waiting thread */
sem_post (&suspend_ack_semaphore);
info->stop_count = stop_count;
#endif /* USE_SIGNAL_BASED_START_STOP_WORLD */
+void
+mono_gc_set_gc_callbacks (MonoGCCallbacks *callbacks)
+{
+ gc_callbacks = *callbacks;
+}
+
+/* Variables holding start/end nursery so it won't have to be passed at every call */
+static void *scan_area_arg_start, *scan_area_arg_end;
+
+void
+mono_gc_conservatively_scan_area (void *start, void *end)
+{
+ conservatively_pin_objects_from (start, end, scan_area_arg_start, scan_area_arg_end);
+}
+
+void*
+mono_gc_scan_object (void *obj)
+{
+ return copy_object (obj, scan_area_arg_start, scan_area_arg_end);
+}
+
/*
- * Identify objects pinned in a thread stack and its registers.
+ * Mark from thread stacks and registers.
*/
static void
-pin_thread_data (void *start_nursery, void *end_nursery)
+scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise)
{
int i;
SgenThreadInfo *info;
+ scan_area_arg_start = start_nursery;
+ scan_area_arg_end = end_nursery;
+
for (i = 0; i < THREAD_HASH_SIZE; ++i) {
for (info = thread_table [i]; info; info = info->next) {
if (info->skip) {
continue;
}
DEBUG (2, fprintf (gc_debug_file, "Scanning thread %p, range: %p-%p, size: %zd, pinned=%d\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start, next_pin_slot));
- conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery);
+ if (gc_callbacks.thread_mark_func)
+ gc_callbacks.thread_mark_func (info->runtime_data, info->stack_start, info->stack_end, precise);
+ else if (!precise)
+ conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery);
}
}
DEBUG (2, fprintf (gc_debug_file, "Scanning current thread registers, pinned=%d\n", next_pin_slot));
- conservatively_pin_objects_from ((void*)cur_thread_regs, (void*)(cur_thread_regs + ARCH_NUM_REGS), start_nursery, end_nursery);
+ if (!precise)
+ conservatively_pin_objects_from ((void*)cur_thread_regs, (void*)(cur_thread_regs + ARCH_NUM_REGS), start_nursery, end_nursery);
}
static void
remembered_set = info->remset = alloc_remset (DEFAULT_REMSET_SIZE, info);
pthread_setspecific (remembered_set_key, remembered_set);
DEBUG (3, fprintf (gc_debug_file, "registered thread %p (%p) (hash: %d)\n", info, (gpointer)info->id, hash));
+
+ if (gc_callbacks.thread_attach_func)
+ info->runtime_data = gc_callbacks.thread_attach_func ();
+
return info;
}
}
return p + 3;
+ case REMSET_ROOT_LOCATION:
+ return p + 2;
+ default:
+ g_assert_not_reached ();
}
break;
}
type_rlen++;
continue;
} else if (type == DESC_TYPE_VECTOR) { // includes ARRAY, too
- skip_size = (vt->desc >> LOW_TYPE_BITS) & MAX_ELEMENT_SIZE;
- skip_size *= mono_array_length ((MonoArray*)start);
- skip_size += sizeof (MonoArray);
+ skip_size = safe_object_get_size ((MonoObject*)start);
skip_size += (ALLOC_ALIGN - 1);
skip_size &= ~(ALLOC_ALIGN - 1);
OBJ_VECTOR_FOREACH_PTR (vt, start);
}
void
-mono_gc_weak_link_add (void **link_addr, MonoObject *obj)
+mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
{
- mono_gc_register_disappearing_link (obj, link_addr);
- *link_addr = obj;
+ LOCK_GC;
+ mono_gc_register_disappearing_link (obj, link_addr, track);
+ UNLOCK_GC;
}
void
mono_gc_weak_link_remove (void **link_addr)
{
- mono_gc_register_disappearing_link (NULL, link_addr);
- *link_addr = NULL;
+ LOCK_GC;
+ mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
+ UNLOCK_GC;
}
MonoObject*
mono_gc_weak_link_get (void **link_addr)
{
- return *link_addr;
+ if (!*link_addr)
+ return NULL;
+ return (MonoObject*) REVEAL_POINTER (*link_addr);
}
void*
collect_before_allocs = TRUE;
} else if (!strcmp (opt, "check-at-minor-collections")) {
consistency_check_at_minor_collection = TRUE;
+ } else if (!strcmp (opt, "clear-at-gc")) {
+ nursery_clear_policy = CLEAR_AT_GC;
} else {
fprintf (stderr, "Invalid format for the MONO_GC_DEBUG env variable: '%s'\n", env);
fprintf (stderr, "The format is: MONO_GC_DEBUG=[l[:filename]|<option>]+ where l is a debug level 0-9.\n");
- fprintf (stderr, "Valid options are: collect-before-allocs, check-at-minor-collections.\n");
+ fprintf (stderr, "Valid options are: collect-before-allocs, check-at-minor-collections, clear-at-gc.\n");
exit (1);
}
}
sigfillset (&sinfo.sa_mask);
sinfo.sa_flags = SA_RESTART | SA_SIGINFO;
- sinfo.sa_handler = suspend_handler;
+ sinfo.sa_sigaction = suspend_handler;
if (sigaction (suspend_signal_num, &sinfo, NULL) != 0) {
g_error ("failed sigaction");
}
return ATYPE_NUM;
}
+static MonoMethod *write_barrier_method;
+
+MonoMethod*
+mono_gc_get_write_barrier (void)
+{
+ MonoMethod *res;
+ int remset_offset = -1;
+ int remset_var, next_var;
+ MonoMethodBuilder *mb;
+ MonoMethodSignature *sig;
+ int label1, label2;
+
+ MONO_THREAD_VAR_OFFSET (remembered_set, remset_offset);
+
+ // FIXME: Maybe create a separate version for ctors (the branch would be
+ // correctly predicted more times)
+ if (write_barrier_method)
+ return write_barrier_method;
+
+ /* Create the IL version of mono_gc_barrier_generic_store () */
+ sig = mono_metadata_signature_alloc (mono_defaults.corlib, 2);
+ sig->ret = &mono_defaults.void_class->byval_arg;
+ sig->params [0] = &mono_defaults.int_class->byval_arg;
+ sig->params [1] = &mono_defaults.object_class->byval_arg;
+
+ mb = mono_mb_new (mono_defaults.object_class, "wbarrier", MONO_WRAPPER_WRITE_BARRIER);
+
+ /* ptr_in_nursery () check */
+#ifdef ALIGN_NURSERY
+ /*
+ * Masking out the bits might be faster, but we would have to use 64 bit
+ * immediates, which might be slower.
+ */
+ mono_mb_emit_ldarg (mb, 0);
+ mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
+ mono_mb_emit_byte (mb, CEE_SHR_UN);
+ mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
+ label1 = mono_mb_emit_branch (mb, CEE_BNE_UN);
+#else
+ // FIXME:
+ g_assert_not_reached ();
+#endif
+
+ /* Don't need write barrier case */
+ /* do the assignment */
+ mono_mb_emit_ldarg (mb, 0);
+ mono_mb_emit_ldarg (mb, 1);
+ /* Don't use STIND_REF, as it would cause infinite recursion */
+ mono_mb_emit_byte (mb, CEE_STIND_I);
+ mono_mb_emit_byte (mb, CEE_RET);
+
+ /* Need write barrier case */
+ mono_mb_patch_branch (mb, label1);
+
+ if (remset_offset == -1)
+ // FIXME:
+ g_assert_not_reached ();
+
+ // remset_var = remembered_set;
+ remset_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+ mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
+ mono_mb_emit_byte (mb, CEE_MONO_TLS);
+ mono_mb_emit_i4 (mb, remset_offset);
+ mono_mb_emit_stloc (mb, remset_var);
+
+ // next_var = rs->store_next
+ next_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
+ mono_mb_emit_ldloc (mb, remset_var);
+ mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (RememberedSet, store_next));
+ mono_mb_emit_byte (mb, CEE_LDIND_I);
+ mono_mb_emit_stloc (mb, next_var);
+
+ // if (rs->store_next < rs->end_set) {
+ mono_mb_emit_ldloc (mb, next_var);
+ mono_mb_emit_ldloc (mb, remset_var);
+ mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (RememberedSet, end_set));
+ mono_mb_emit_byte (mb, CEE_LDIND_I);
+ label2 = mono_mb_emit_branch (mb, CEE_BGE);
+
+ /* write barrier fast path */
+ // *(rs->store_next++) = (mword)ptr;
+ mono_mb_emit_ldloc (mb, next_var);
+ mono_mb_emit_ldarg (mb, 0);
+ mono_mb_emit_byte (mb, CEE_STIND_I);
+
+ mono_mb_emit_ldloc (mb, next_var);
+ mono_mb_emit_icon (mb, sizeof (gpointer));
+ mono_mb_emit_byte (mb, CEE_ADD);
+ mono_mb_emit_stloc (mb, next_var);
+
+ mono_mb_emit_ldloc (mb, remset_var);
+ mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (RememberedSet, store_next));
+ mono_mb_emit_ldloc (mb, next_var);
+ mono_mb_emit_byte (mb, CEE_STIND_I);
+
+ // *(void**)ptr = value;
+ mono_mb_emit_ldarg (mb, 0);
+ mono_mb_emit_ldarg (mb, 1);
+ mono_mb_emit_byte (mb, CEE_STIND_I);
+ mono_mb_emit_byte (mb, CEE_RET);
+
+ /* write barrier slow path */
+ mono_mb_patch_branch (mb, label2);
+
+ mono_mb_emit_ldarg (mb, 0);
+ mono_mb_emit_ldarg (mb, 1);
+ mono_mb_emit_icall (mb, mono_gc_wbarrier_generic_store);
+ mono_mb_emit_byte (mb, CEE_RET);
+
+ res = mono_mb_create_method (mb, sig, 16);
+ mono_mb_free (mb);
+
+ mono_loader_lock ();
+ if (write_barrier_method) {
+ /* Already created */
+ mono_free_method (res);
+ } else {
+ /* double-checked locking */
+ mono_memory_barrier ();
+ write_barrier_method = res;
+ }
+ mono_loader_unlock ();
+
+ return write_barrier_method;
+}
+
#endif /* HAVE_SGEN_GC */