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
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 _DisappearingLink DisappearingLink;
+struct _DisappearingLink {
+ DisappearingLink *next;
+ void **link;
+};
+
+/*
+ * 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;
/* 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 FinalizeEntry *critical_fin_list = NULL;
+static DisappearingLink **disappearing_link_hash = NULL;
static mword disappearing_link_hash_size = 0;
static mword finalizable_hash_size = 0;
/* 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);
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); \
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;
}
* *) 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 */
/* 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;
}
/* 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);
/* 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);
/* 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)));
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 ();
}
*/
#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;
+ }
+}
+
static void
finalize_in_range (char *start, char *end)
{
next = entry->next;
num_ready_finalizers++;
num_registered_finalizers--;
- entry->next = fin_ready_list;
- fin_ready_list = entry;
+ queue_finalization_entry (entry);
/* Make it survive */
from = entry->object;
entry->object = copy_object (entry->object, start, end);
static void
null_link_in_range (char *start, char *end)
{
- FinalizeEntry *entry, *prev;
+ DisappearingLink *entry, *prev;
int i;
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)) {
+ if (!DISLINK_TRACK (entry) && 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--;
continue;
} else {
- void **link;
/* update pointer if it's moved
* 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;
+ /* We set the track
+ * resurrection bit to FALSE
+ * here so that the object can
+ * be collected in the next
+ * cycle (i.e. after it was
+ * finalized).
+ */
+ *entry->link = HIDE_POINTER (copy_object (object, start, end), FALSE);
+ DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
}
}
prev = entry;
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)
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;
}
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++;
{
int i;
unsigned int hash;
- FinalizeEntry **new_hash;
- FinalizeEntry *entry, *next;
+ DisappearingLink **new_hash;
+ DisappearingLink *entry, *next;
int new_size = g_spaced_primes_closest (num_disappearing_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;
}
static void
-mono_gc_register_disappearing_link (MonoObject *obj, void *link)
+mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
{
- FinalizeEntry *entry, *prev;
+ DisappearingLink *entry, *prev;
unsigned int hash;
LOCK_GC;
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)
num_disappearing_links--;
DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d)\n", entry, num_disappearing_links));
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;
+ 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++;
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) {
- fin_ready_list = entry->next;
+ entry_is_critical = FALSE;
+ } else {
+ entry_is_critical = TRUE;
+ for (entry = critical_fin_list; entry && !entry->object; entry = entry->next)
+ ;
+ }
+
+ if (entry) {
+ 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;
}
}
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;
+ mono_gc_register_disappearing_link (obj, link_addr, track);
}
void
mono_gc_weak_link_remove (void **link_addr)
{
- mono_gc_register_disappearing_link (NULL, link_addr);
- *link_addr = NULL;
+ mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
}
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");
}