/** * \file * Simple generational GC. * * Copyright 2011 Novell, Inc (http://www.novell.com) * Copyright 2011 Xamarin Inc (http://www.xamarin.com) * Copyright 2001-2003 Ximian, Inc * Copyright 2003-2010 Novell, Inc. * * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ #include "config.h" #ifdef HAVE_SGEN_GC #include #include "sgen/sgen-gc.h" #include "sgen-bridge-internals.h" #include "sgen/sgen-hash-table.h" #include "sgen/sgen-qsort.h" #include "utils/mono-logger-internals.h" typedef enum { BRIDGE_PROCESSOR_INVALID, BRIDGE_PROCESSOR_OLD, BRIDGE_PROCESSOR_NEW, BRIDGE_PROCESSOR_TARJAN, BRIDGE_PROCESSOR_DEFAULT = BRIDGE_PROCESSOR_TARJAN } BridgeProcessorSelection; // Bridge processor type pending / in use static BridgeProcessorSelection bridge_processor_selection = BRIDGE_PROCESSOR_DEFAULT; // Most recently requested callbacks static MonoGCBridgeCallbacks pending_bridge_callbacks; // Configuration to be passed to bridge processor after init static SgenBridgeProcessorConfig bridge_processor_config; // Currently-in-use callbacks MonoGCBridgeCallbacks bridge_callbacks; // Bridge processor state static SgenBridgeProcessor bridge_processor; // This is used for a special debug feature static SgenBridgeProcessor compare_to_bridge_processor; volatile gboolean bridge_processing_in_progress = FALSE; // FIXME: The current usage pattern for this function is unsafe. Bridge processing could start immediately after unlock /** * mono_gc_wait_for_bridge_processing: */ void mono_gc_wait_for_bridge_processing (void) { if (!bridge_processing_in_progress) return; mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_BRIDGE waiting for bridge processing to finish"); sgen_gc_lock (); sgen_gc_unlock (); } /** * mono_gc_register_bridge_callbacks: */ void mono_gc_register_bridge_callbacks (MonoGCBridgeCallbacks *callbacks) { if (callbacks->bridge_version != SGEN_BRIDGE_VERSION) g_error ("Invalid bridge callback version. Expected %d but got %d\n", SGEN_BRIDGE_VERSION, callbacks->bridge_version); // Defer assigning to bridge_callbacks until we have the gc lock. // Note: This line is unsafe if we are on a separate thread from the one the runtime was initialized on. pending_bridge_callbacks = *callbacks; // If sgen has started, will assign bridge callbacks and init bridge sgen_init_bridge (); } static BridgeProcessorSelection bridge_processor_name (const char *name) { if (!strcmp ("old", name)) { return BRIDGE_PROCESSOR_OLD; } else if (!strcmp ("new", name)) { return BRIDGE_PROCESSOR_NEW; } else if (!strcmp ("tarjan", name)) { return BRIDGE_PROCESSOR_TARJAN; } else { return BRIDGE_PROCESSOR_INVALID; } } static gboolean bridge_processor_started (void) { return bridge_processor.reset_data != NULL; } // Initialize a single bridge processor static void init_bridge_processor (SgenBridgeProcessor *processor, BridgeProcessorSelection selection) { memset (processor, 0, sizeof (SgenBridgeProcessor)); switch (selection) { case BRIDGE_PROCESSOR_OLD: sgen_old_bridge_init (processor); break; case BRIDGE_PROCESSOR_NEW: sgen_new_bridge_init (processor); break; case BRIDGE_PROCESSOR_TARJAN: sgen_tarjan_bridge_init (processor); break; default: g_assert_not_reached (); } } /* * Initializing the sgen bridge consists of setting the bridge callbacks, * and initializing the bridge processor. Init should follow these rules: * * - Init happens only after sgen is initialized (because we don't * know which bridge processor to initialize until then, and also * to allow bridge processor init to interact with sgen if it wants) * * - Init happens only after mono_gc_register_bridge_callbacks is called * * - Init should not happen concurrently with a GC (because a GC will * call sgen_need_bridge_processing at various times) * * - Initializing the bridge processor should happen only once * * We call sgen_init_bridge when the callbacks are set, and also when sgen * is done initing. Actual initialization then only occurs if it is ready. */ void sgen_init_bridge (void) { if (sgen_gc_initialized ()) { // This lock is not initialized until the GC is sgen_gc_lock (); bridge_callbacks = pending_bridge_callbacks; // If a bridge was registered but there is no bridge processor yet if (bridge_callbacks.cross_references && !bridge_processor_started ()) { init_bridge_processor (&bridge_processor, bridge_processor_selection); if (bridge_processor.set_config) bridge_processor.set_config (&bridge_processor_config); // Config is no longer needed so free its memory free (bridge_processor_config.dump_prefix); bridge_processor_config.dump_prefix = NULL; } sgen_gc_unlock (); } } void sgen_set_bridge_implementation (const char *name) { BridgeProcessorSelection selection = bridge_processor_name (name); if (selection == BRIDGE_PROCESSOR_INVALID) g_warning ("Invalid value for bridge processor implementation, valid values are: 'new', 'old' and 'tarjan'."); else if (bridge_processor_started ()) g_warning ("Cannot set bridge processor implementation once bridge has already started"); else bridge_processor_selection = selection; } gboolean sgen_is_bridge_object (GCObject *obj) { if ((obj->vtable->gc_bits & SGEN_GC_BIT_BRIDGE_OBJECT) != SGEN_GC_BIT_BRIDGE_OBJECT) return FALSE; return bridge_callbacks.is_bridge_object (obj); } gboolean sgen_need_bridge_processing (void) { return bridge_callbacks.cross_references != NULL; } static gboolean compare_bridge_processors (void) { return compare_to_bridge_processor.reset_data != NULL; } /* Dispatch wrappers */ void sgen_bridge_reset_data (void) { bridge_processor.reset_data (); if (compare_bridge_processors ()) compare_to_bridge_processor.reset_data (); } void sgen_bridge_processing_stw_step (void) { /* * bridge_processing_in_progress must be set with the world * stopped. If not there would be race conditions. */ bridge_processing_in_progress = TRUE; bridge_processor.processing_stw_step (); if (compare_bridge_processors ()) compare_to_bridge_processor.processing_stw_step (); } static gboolean is_bridge_object_dead (GCObject *obj, void *data) { SgenHashTable *table = (SgenHashTable *)data; unsigned char *value = (unsigned char *)sgen_hash_table_lookup (table, obj); if (!value) return FALSE; return !*value; } static void null_weak_links_to_dead_objects (SgenBridgeProcessor *processor, int generation) { int i, j; int num_sccs = processor->num_sccs; MonoGCBridgeSCC **api_sccs = processor->api_sccs; SgenHashTable alive_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_BRIDGE_ALIVE_HASH_TABLE, INTERNAL_MEM_BRIDGE_ALIVE_HASH_TABLE_ENTRY, 1, mono_aligned_addr_hash, NULL); for (i = 0; i < num_sccs; ++i) { unsigned char alive = api_sccs [i]->is_alive ? 1 : 0; for (j = 0; j < api_sccs [i]->num_objs; ++j) { /* Build hash table for nulling weak links. */ sgen_hash_table_replace (&alive_hash, api_sccs [i]->objs [j], &alive, NULL); /* Release for finalization those objects we no longer care. */ if (!api_sccs [i]->is_alive) sgen_mark_bridge_object (api_sccs [i]->objs [j]); } } /* Null weak links to dead objects. */ sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_NURSERY, FALSE); sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_NURSERY, TRUE); if (generation == GENERATION_OLD) { sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_OLD, FALSE); sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_OLD, TRUE); } sgen_hash_table_clean (&alive_hash); } static void free_callback_data (SgenBridgeProcessor *processor) { int i; int num_sccs = processor->num_sccs; int num_xrefs = processor->num_xrefs; MonoGCBridgeSCC **api_sccs = processor->api_sccs; MonoGCBridgeXRef *api_xrefs = processor->api_xrefs; for (i = 0; i < num_sccs; ++i) { sgen_free_internal_dynamic (api_sccs [i], sizeof (MonoGCBridgeSCC) + sizeof (MonoObject*) * api_sccs [i]->num_objs, INTERNAL_MEM_BRIDGE_DATA); } sgen_free_internal_dynamic (api_sccs, sizeof (MonoGCBridgeSCC*) * num_sccs, INTERNAL_MEM_BRIDGE_DATA); sgen_free_internal_dynamic (api_xrefs, sizeof (MonoGCBridgeXRef) * num_xrefs, INTERNAL_MEM_BRIDGE_DATA); processor->num_sccs = 0; processor->api_sccs = NULL; processor->num_xrefs = 0; processor->api_xrefs = NULL; } static int compare_xrefs (const void *a_ptr, const void *b_ptr) { const MonoGCBridgeXRef *a = (const MonoGCBridgeXRef *)a_ptr; const MonoGCBridgeXRef *b = (const MonoGCBridgeXRef *)b_ptr; if (a->src_scc_index < b->src_scc_index) return -1; if (a->src_scc_index > b->src_scc_index) return 1; if (a->dst_scc_index < b->dst_scc_index) return -1; if (a->dst_scc_index > b->dst_scc_index) return 1; return 0; } /* static void dump_processor_state (SgenBridgeProcessor *p) { int i; printf ("------\n"); printf ("SCCS %d\n", p->num_sccs); for (i = 0; i < p->num_sccs; ++i) { int j; MonoGCBridgeSCC *scc = p->api_sccs [i]; printf ("\tSCC %d:", i); for (j = 0; j < scc->num_objs; ++j) { MonoObject *obj = scc->objs [j]; printf (" %p", obj); } printf ("\n"); } printf ("XREFS %d\n", p->num_xrefs); for (i = 0; i < p->num_xrefs; ++i) printf ("\t%d -> %d\n", p->api_xrefs [i].src_scc_index, p->api_xrefs [i].dst_scc_index); printf ("-------\n"); } */ static gboolean sgen_compare_bridge_processor_results (SgenBridgeProcessor *a, SgenBridgeProcessor *b) { int i; SgenHashTable obj_to_a_scc = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_BRIDGE_DEBUG, INTERNAL_MEM_BRIDGE_DEBUG, sizeof (int), mono_aligned_addr_hash, NULL); SgenHashTable b_scc_to_a_scc = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_BRIDGE_DEBUG, INTERNAL_MEM_BRIDGE_DEBUG, sizeof (int), g_direct_hash, NULL); MonoGCBridgeXRef *a_xrefs, *b_xrefs; size_t xrefs_alloc_size; // dump_processor_state (a); // dump_processor_state (b); if (a->num_sccs != b->num_sccs) g_error ("SCCS count expected %d but got %d", a->num_sccs, b->num_sccs); if (a->num_xrefs != b->num_xrefs) g_error ("SCCS count expected %d but got %d", a->num_xrefs, b->num_xrefs); /* * First we build a hash of each object in `a` to its respective SCC index within * `a`. Along the way we also assert that no object is more than one SCC. */ for (i = 0; i < a->num_sccs; ++i) { int j; MonoGCBridgeSCC *scc = a->api_sccs [i]; g_assert (scc->num_objs > 0); for (j = 0; j < scc->num_objs; ++j) { GCObject *obj = scc->objs [j]; gboolean new_entry = sgen_hash_table_replace (&obj_to_a_scc, obj, &i, NULL); g_assert (new_entry); } } /* * Now we check whether each of the objects in `b` are in `a`, and whether the SCCs * of `b` contain the same sets of objects as those of `a`. * * While we're doing this, build a hash table to map from `b` SCC indexes to `a` SCC * indexes. */ for (i = 0; i < b->num_sccs; ++i) { MonoGCBridgeSCC *scc = b->api_sccs [i]; MonoGCBridgeSCC *a_scc; int *a_scc_index_ptr; int a_scc_index; int j; gboolean new_entry; g_assert (scc->num_objs > 0); a_scc_index_ptr = (int *)sgen_hash_table_lookup (&obj_to_a_scc, scc->objs [0]); g_assert (a_scc_index_ptr); a_scc_index = *a_scc_index_ptr; //g_print ("A SCC %d -> B SCC %d\n", a_scc_index, i); a_scc = a->api_sccs [a_scc_index]; g_assert (a_scc->num_objs == scc->num_objs); for (j = 1; j < scc->num_objs; ++j) { a_scc_index_ptr = (int *)sgen_hash_table_lookup (&obj_to_a_scc, scc->objs [j]); g_assert (a_scc_index_ptr); g_assert (*a_scc_index_ptr == a_scc_index); } new_entry = sgen_hash_table_replace (&b_scc_to_a_scc, GINT_TO_POINTER (i), &a_scc_index, NULL); g_assert (new_entry); } /* * Finally, check that we have the same xrefs. We do this by making copies of both * xref arrays, and replacing the SCC indexes in the copy for `b` with the * corresponding indexes in `a`. Then we sort both arrays and assert that they're * the same. * * At the same time, check that no xref is self-referential and that there are no * duplicate ones. */ xrefs_alloc_size = a->num_xrefs * sizeof (MonoGCBridgeXRef); a_xrefs = (MonoGCBridgeXRef *)sgen_alloc_internal_dynamic (xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG, TRUE); b_xrefs = (MonoGCBridgeXRef *)sgen_alloc_internal_dynamic (xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG, TRUE); memcpy (a_xrefs, a->api_xrefs, xrefs_alloc_size); for (i = 0; i < b->num_xrefs; ++i) { MonoGCBridgeXRef *xref = &b->api_xrefs [i]; int *scc_index_ptr; g_assert (xref->src_scc_index != xref->dst_scc_index); scc_index_ptr = (int *)sgen_hash_table_lookup (&b_scc_to_a_scc, GINT_TO_POINTER (xref->src_scc_index)); g_assert (scc_index_ptr); b_xrefs [i].src_scc_index = *scc_index_ptr; scc_index_ptr = (int *)sgen_hash_table_lookup (&b_scc_to_a_scc, GINT_TO_POINTER (xref->dst_scc_index)); g_assert (scc_index_ptr); b_xrefs [i].dst_scc_index = *scc_index_ptr; } qsort (a_xrefs, a->num_xrefs, sizeof (MonoGCBridgeXRef), compare_xrefs); qsort (b_xrefs, a->num_xrefs, sizeof (MonoGCBridgeXRef), compare_xrefs); for (i = 0; i < a->num_xrefs; ++i) { g_assert (a_xrefs [i].src_scc_index == b_xrefs [i].src_scc_index); g_assert (a_xrefs [i].dst_scc_index == b_xrefs [i].dst_scc_index); } sgen_hash_table_clean (&obj_to_a_scc); sgen_hash_table_clean (&b_scc_to_a_scc); sgen_free_internal_dynamic (a_xrefs, xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG); sgen_free_internal_dynamic (b_xrefs, xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG); return TRUE; } void sgen_bridge_processing_finish (int generation) { bridge_processor.processing_build_callback_data (generation); if (compare_bridge_processors ()) compare_to_bridge_processor.processing_build_callback_data (generation); if (bridge_processor.num_sccs == 0) { g_assert (bridge_processor.num_xrefs == 0); goto after_callback; } bridge_callbacks.cross_references (bridge_processor.num_sccs, bridge_processor.api_sccs, bridge_processor.num_xrefs, bridge_processor.api_xrefs); if (compare_bridge_processors ()) sgen_compare_bridge_processor_results (&bridge_processor, &compare_to_bridge_processor); null_weak_links_to_dead_objects (&bridge_processor, generation); free_callback_data (&bridge_processor); if (compare_bridge_processors ()) free_callback_data (&compare_to_bridge_processor); after_callback: bridge_processor.processing_after_callback (generation); if (compare_bridge_processors ()) compare_to_bridge_processor.processing_after_callback (generation); mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_BRIDGE: Complete, was running for %.2fms", mono_time_since_last_stw () / 10000.0f); bridge_processing_in_progress = FALSE; } MonoGCBridgeObjectKind sgen_bridge_class_kind (MonoClass *klass) { return bridge_processor.class_kind (klass); } void sgen_bridge_register_finalized_object (GCObject *obj) { bridge_processor.register_finalized_object (obj); if (compare_bridge_processors ()) compare_to_bridge_processor.register_finalized_object (obj); } void sgen_bridge_describe_pointer (GCObject *obj) { if (bridge_processor.describe_pointer) bridge_processor.describe_pointer (obj); } static void set_dump_prefix (const char *prefix) { if (bridge_processor_config.dump_prefix) free (bridge_processor_config.dump_prefix); bridge_processor_config.dump_prefix = strdup (prefix); } /* Test support code */ static const char *bridge_class; static MonoGCBridgeObjectKind bridge_test_bridge_class_kind (MonoClass *klass) { if (!strcmp (bridge_class, klass->name)) return GC_BRIDGE_TRANSPARENT_BRIDGE_CLASS; return GC_BRIDGE_TRANSPARENT_CLASS; } static gboolean bridge_test_is_bridge_object (MonoObject *object) { return TRUE; } static void bridge_test_cross_reference (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs) { int i; for (i = 0; i < num_sccs; ++i) { int j; // g_print ("--- SCC %d\n", i); for (j = 0; j < sccs [i]->num_objs; ++j) { // g_print (" %s\n", sgen_safe_name (sccs [i]->objs [j])); if (i & 1) /*retain half of the bridged objects */ sccs [i]->is_alive = TRUE; } } for (i = 0; i < num_xrefs; ++i) { g_assert (xrefs [i].src_scc_index >= 0 && xrefs [i].src_scc_index < num_sccs); g_assert (xrefs [i].dst_scc_index >= 0 && xrefs [i].dst_scc_index < num_sccs); // g_print ("%d -> %d\n", xrefs [i].src_scc_index, xrefs [i].dst_scc_index); } } static MonoClassField *mono_bridge_test_field; enum { BRIDGE_DEAD, BRIDGE_ROOT, BRIDGE_SAME_SCC, BRIDGE_XREF, }; static gboolean test_scc (MonoGCBridgeSCC *scc, int i) { int status = BRIDGE_DEAD; mono_field_get_value (scc->objs [i], mono_bridge_test_field, &status); return status > 0; } static void mark_scc (MonoGCBridgeSCC *scc, int value) { int i; for (i = 0; i < scc->num_objs; ++i) { if (!test_scc (scc, i)) { int status = value; mono_field_set_value (scc->objs [i], mono_bridge_test_field, &status); } } } static void bridge_test_cross_reference2 (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs) { int i; gboolean modified; if (!mono_bridge_test_field) { mono_bridge_test_field = mono_class_get_field_from_name (mono_object_get_class (sccs[0]->objs [0]), "__test"); g_assert (mono_bridge_test_field); } /*We mark all objects in a scc with live objects as reachable by scc*/ for (i = 0; i < num_sccs; ++i) { int j; gboolean live = FALSE; for (j = 0; j < sccs [i]->num_objs; ++j) { if (test_scc (sccs [i], j)) { live = TRUE; break; } } if (!live) continue; for (j = 0; j < sccs [i]->num_objs; ++j) { if (!test_scc (sccs [i], j)) { int status = BRIDGE_SAME_SCC; mono_field_set_value (sccs [i]->objs [j], mono_bridge_test_field, &status); } } } /*Now we mark the transitive closure of reachable objects from the xrefs*/ modified = TRUE; while (modified) { modified = FALSE; /* Mark all objects that are brought to life due to xrefs*/ for (i = 0; i < num_xrefs; ++i) { MonoGCBridgeXRef ref = xrefs [i]; if (test_scc (sccs [ref.src_scc_index], 0) && !test_scc (sccs [ref.dst_scc_index], 0)) { modified = TRUE; mark_scc (sccs [ref.dst_scc_index], BRIDGE_XREF); } } } /* keep everything in memory, all we want to do is test persistence */ for (i = 0; i < num_sccs; ++i) sccs [i]->is_alive = TRUE; } /* This bridge keeps all peers with __test > 0 */ static void bridge_test_positive_status (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs) { int i; if (!mono_bridge_test_field) { mono_bridge_test_field = mono_class_get_field_from_name (mono_object_get_class (sccs[0]->objs [0]), "__test"); g_assert (mono_bridge_test_field); } /*We mark all objects in a scc with live objects as reachable by scc*/ for (i = 0; i < num_sccs; ++i) { int j; for (j = 0; j < sccs [i]->num_objs; ++j) { if (test_scc (sccs [i], j)) { sccs [i]->is_alive = TRUE; break; } } } } static void register_test_bridge_callbacks (const char *bridge_class_name) { MonoGCBridgeCallbacks callbacks; callbacks.bridge_version = SGEN_BRIDGE_VERSION; callbacks.bridge_class_kind = bridge_test_bridge_class_kind; callbacks.is_bridge_object = bridge_test_is_bridge_object; switch (bridge_class_name [0]) { case '2': bridge_class = bridge_class_name + 1; callbacks.cross_references = bridge_test_cross_reference2; break; case '3': bridge_class = bridge_class_name + 1; callbacks.cross_references = bridge_test_positive_status; break; default: bridge_class = bridge_class_name; callbacks.cross_references = bridge_test_cross_reference; } mono_gc_register_bridge_callbacks (&callbacks); } gboolean sgen_bridge_handle_gc_param (const char *opt) { g_assert (!bridge_processor_started ()); if (!strcmp (opt, "bridge-require-precise-merge")) { bridge_processor_config.scc_precise_merge = TRUE; } else { return FALSE; } return TRUE; } gboolean sgen_bridge_handle_gc_debug (const char *opt) { g_assert (!bridge_processor_started ()); if (g_str_has_prefix (opt, "bridge=")) { opt = strchr (opt, '=') + 1; register_test_bridge_callbacks (g_strdup (opt)); } else if (!strcmp (opt, "enable-bridge-accounting")) { bridge_processor_config.accounting = TRUE; } else if (g_str_has_prefix (opt, "bridge-dump=")) { char *prefix = strchr (opt, '=') + 1; set_dump_prefix(prefix); } else if (g_str_has_prefix (opt, "bridge-compare-to=")) { const char *name = strchr (opt, '=') + 1; BridgeProcessorSelection selection = bridge_processor_name (name); if (selection != BRIDGE_PROCESSOR_INVALID) { // Compare processor doesn't get config init_bridge_processor (&compare_to_bridge_processor, selection); } else { g_warning ("Invalid bridge implementation to compare against - ignoring."); } } else { return FALSE; } return TRUE; } void sgen_bridge_print_gc_debug_usage (void) { fprintf (stderr, " bridge=\n"); fprintf (stderr, " enable-bridge-accounting\n"); fprintf (stderr, " bridge-dump=\n"); fprintf (stderr, " bridge-compare-to=\n"); } #endif