[sgen] Remove obsolete check in #ifdef.
[mono.git] / mono / metadata / sgen-debug.c
index a04e1a7c1f560aa67041e1fd8b73fe16bd1d4b62..1387676c75858cb57b6c6a403fb0f00d78705c1e 100644 (file)
@@ -32,6 +32,7 @@
 #include "metadata/sgen-protocol.h"
 #include "metadata/sgen-memory-governor.h"
 #include "metadata/sgen-pinning.h"
+#include "metadata/threadpool-internals.h"
 
 #define LOAD_VTABLE    SGEN_LOAD_VTABLE
 
@@ -157,9 +158,9 @@ static gboolean missing_remsets;
 #undef HANDLE_PTR
 #define HANDLE_PTR(ptr,obj)    do {    \
        if (*(ptr) && sgen_ptr_in_nursery ((char*)*(ptr))) { \
-               if (!sgen_get_remset ()->find_address ((char*)(ptr))) { \
+               if (!sgen_get_remset ()->find_address ((char*)(ptr)) && !sgen_cement_lookup (*(ptr))) { \
                        SGEN_LOG (0, "Oldspace->newspace reference %p at offset %td in object %p (%s.%s) not found in remsets.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), ((MonoObject*)(obj))->vtable->klass->name_space, ((MonoObject*)(obj))->vtable->klass->name); \
-                       binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
+                       binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (int) ((char*)(ptr) - (char*)(obj)), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
                        if (!object_is_pinned (*(ptr)))                                                         \
                                missing_remsets = TRUE;                                                                 \
                }                                                                                                                               \
@@ -194,7 +195,7 @@ sgen_check_consistency (void)
        SGEN_LOG (1, "Begin heap consistency check...");
 
        // Check that oldspace->newspace pointers are registered with the collector
-       major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
 
        sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_consistency_callback, NULL);
 
@@ -219,7 +220,8 @@ is_major_or_los_object_marked (char *obj)
        if (*(ptr) && !sgen_ptr_in_nursery ((char*)*(ptr)) && !is_major_or_los_object_marked ((char*)*(ptr))) { \
                if (!sgen_get_remset ()->find_address_with_cards (start, cards, (char*)(ptr))) { \
                        SGEN_LOG (0, "major->major reference %p at offset %td in object %p (%s.%s) not found in remsets.", *(ptr), (char*)(ptr) - (char*)(obj), (obj), ((MonoObject*)(obj))->vtable->klass->name_space, ((MonoObject*)(obj))->vtable->klass->name); \
-                       binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
+                       binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (int) ((char*)(ptr) - (char*)(obj)), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
+                       missing_remsets = TRUE;                         \
                }                                                                                                                               \
        }                                                                                                                                       \
        } while (0)
@@ -250,7 +252,7 @@ sgen_check_mod_union_consistency (void)
 {
        missing_remsets = FALSE;
 
-       major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_mod_union_callback, (void*)FALSE);
+       major_collector.iterate_objects (ITERATE_OBJECTS_ALL, (IterateObjectCallbackFunc)check_mod_union_callback, (void*)FALSE);
 
        sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_mod_union_callback, (void*)TRUE);
 
@@ -273,7 +275,7 @@ check_major_refs_callback (char *start, size_t size, void *dummy)
 void
 sgen_check_major_refs (void)
 {
-       major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
        sgen_los_iterate_objects ((IterateObjectCallbackFunc)check_major_refs_callback, NULL);
 }
 
@@ -439,7 +441,7 @@ sgen_check_whole_heap (gboolean allow_missing_pinned)
 
        broken_heap = FALSE;
        sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned, FALSE);
-       major_collector.iterate_objects (TRUE, TRUE, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
        sgen_los_iterate_objects (verify_object_pointers_callback, (void*) (size_t) allow_missing_pinned);
 
        g_assert (!broken_heap);
@@ -563,7 +565,7 @@ sgen_check_major_heap_marked (void)
 {
        setup_valid_nursery_objects ();
 
-       major_collector.iterate_objects (TRUE, TRUE, check_marked_callback, (void*)FALSE);
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, check_marked_callback, (void*)FALSE);
        sgen_los_iterate_objects (check_marked_callback, (void*)TRUE);
 }
 
@@ -587,4 +589,491 @@ sgen_check_nursery_objects_pinned (gboolean pinned)
                        (IterateObjectCallbackFunc)check_nursery_objects_pinned_callback, (void*) (size_t) pinned /* (void*)&ctx */, FALSE);
 }
 
+static gboolean scan_object_for_specific_ref_precise = TRUE;
+
+#undef HANDLE_PTR
+#define HANDLE_PTR(ptr,obj) do {               \
+       if ((MonoObject*)*(ptr) == key) {       \
+       g_print ("found ref to %p in object %p (%s) at offset %td\n",   \
+                       key, (obj), sgen_safe_name ((obj)), ((char*)(ptr) - (char*)(obj))); \
+       }                                                               \
+       } while (0)
+
+static void
+scan_object_for_specific_ref (char *start, MonoObject *key)
+{
+       char *forwarded;
+
+       if ((forwarded = SGEN_OBJECT_IS_FORWARDED (start)))
+               start = forwarded;
+
+       if (scan_object_for_specific_ref_precise) {
+               #include "sgen-scan-object.h"
+       } else {
+               mword *words = (mword*)start;
+               size_t size = safe_object_get_size ((MonoObject*)start);
+               int i;
+               for (i = 0; i < size / sizeof (mword); ++i) {
+                       if (words [i] == (mword)key) {
+                               g_print ("found possible ref to %p in object %p (%s) at offset %td\n",
+                                               key, start, sgen_safe_name (start), i * sizeof (mword));
+                       }
+               }
+       }
+}
+
+static void
+scan_object_for_specific_ref_callback (char *obj, size_t size, MonoObject *key)
+{
+       scan_object_for_specific_ref (obj, key);
+}
+
+static void
+check_root_obj_specific_ref (RootRecord *root, MonoObject *key, MonoObject *obj)
+{
+       if (key != obj)
+               return;
+       g_print ("found ref to %p in root record %p\n", key, root);
+}
+
+static MonoObject *check_key = NULL;
+static RootRecord *check_root = NULL;
+
+static void
+check_root_obj_specific_ref_from_marker (void **obj, void *gc_data)
+{
+       check_root_obj_specific_ref (check_root, check_key, *obj);
+}
+
+static void
+scan_roots_for_specific_ref (MonoObject *key, int root_type)
+{
+       void **start_root;
+       RootRecord *root;
+       check_key = key;
+
+       SGEN_HASH_TABLE_FOREACH (&roots_hash [root_type], start_root, root) {
+               mword desc = root->root_desc;
+
+               check_root = root;
+
+               switch (desc & ROOT_DESC_TYPE_MASK) {
+               case ROOT_DESC_BITMAP:
+                       desc >>= ROOT_DESC_TYPE_SHIFT;
+                       while (desc) {
+                               if (desc & 1)
+                                       check_root_obj_specific_ref (root, key, *start_root);
+                               desc >>= 1;
+                               start_root++;
+                       }
+                       return;
+               case ROOT_DESC_COMPLEX: {
+                       gsize *bitmap_data = sgen_get_complex_descriptor_bitmap (desc);
+                       int bwords = (int) ((*bitmap_data) - 1);
+                       void **start_run = start_root;
+                       bitmap_data++;
+                       while (bwords-- > 0) {
+                               gsize bmap = *bitmap_data++;
+                               void **objptr = start_run;
+                               while (bmap) {
+                                       if (bmap & 1)
+                                               check_root_obj_specific_ref (root, key, *objptr);
+                                       bmap >>= 1;
+                                       ++objptr;
+                               }
+                               start_run += GC_BITS_PER_WORD;
+                       }
+                       break;
+               }
+               case ROOT_DESC_USER: {
+                       MonoGCRootMarkFunc marker = sgen_get_user_descriptor_func (desc);
+                       marker (start_root, check_root_obj_specific_ref_from_marker, NULL);
+                       break;
+               }
+               case ROOT_DESC_RUN_LEN:
+                       g_assert_not_reached ();
+               default:
+                       g_assert_not_reached ();
+               }
+       } SGEN_HASH_TABLE_FOREACH_END;
+
+       check_key = NULL;
+       check_root = NULL;
+}
+
+void
+mono_gc_scan_for_specific_ref (MonoObject *key, gboolean precise)
+{
+       void **ptr;
+       RootRecord *root;
+
+       scan_object_for_specific_ref_precise = precise;
+
+       sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
+                       (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key, TRUE);
+
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
+
+       sgen_los_iterate_objects ((IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
+
+       scan_roots_for_specific_ref (key, ROOT_TYPE_NORMAL);
+       scan_roots_for_specific_ref (key, ROOT_TYPE_WBARRIER);
+
+       SGEN_HASH_TABLE_FOREACH (&roots_hash [ROOT_TYPE_PINNED], ptr, root) {
+               while (ptr < (void**)root->end_root) {
+                       check_root_obj_specific_ref (root, *ptr, key);
+                       ++ptr;
+               }
+       } SGEN_HASH_TABLE_FOREACH_END;
+}
+
+static MonoDomain *check_domain = NULL;
+
+static void
+check_obj_not_in_domain (void **o)
+{
+       g_assert (((MonoObject*)(*o))->vtable->domain != check_domain);
+}
+
+
+static void
+check_obj_not_in_domain_callback (void **o, void *gc_data)
+{
+       g_assert (((MonoObject*)(*o))->vtable->domain != check_domain);
+}
+
+void
+sgen_scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type)
+{
+       void **start_root;
+       RootRecord *root;
+       check_domain = domain;
+       SGEN_HASH_TABLE_FOREACH (&roots_hash [root_type], start_root, root) {
+               mword desc = root->root_desc;
+
+               /* The MonoDomain struct is allowed to hold
+                  references to objects in its own domain. */
+               if (start_root == (void**)domain)
+                       continue;
+
+               switch (desc & ROOT_DESC_TYPE_MASK) {
+               case ROOT_DESC_BITMAP:
+                       desc >>= ROOT_DESC_TYPE_SHIFT;
+                       while (desc) {
+                               if ((desc & 1) && *start_root)
+                                       check_obj_not_in_domain (*start_root);
+                               desc >>= 1;
+                               start_root++;
+                       }
+                       break;
+               case ROOT_DESC_COMPLEX: {
+                       gsize *bitmap_data = sgen_get_complex_descriptor_bitmap (desc);
+                       int bwords = (int)((*bitmap_data) - 1);
+                       void **start_run = start_root;
+                       bitmap_data++;
+                       while (bwords-- > 0) {
+                               gsize bmap = *bitmap_data++;
+                               void **objptr = start_run;
+                               while (bmap) {
+                                       if ((bmap & 1) && *objptr)
+                                               check_obj_not_in_domain (*objptr);
+                                       bmap >>= 1;
+                                       ++objptr;
+                               }
+                               start_run += GC_BITS_PER_WORD;
+                       }
+                       break;
+               }
+               case ROOT_DESC_USER: {
+                       MonoGCRootMarkFunc marker = sgen_get_user_descriptor_func (desc);
+                       marker (start_root, check_obj_not_in_domain_callback, NULL);
+                       break;
+               }
+               case ROOT_DESC_RUN_LEN:
+                       g_assert_not_reached ();
+               default:
+                       g_assert_not_reached ();
+               }
+       } SGEN_HASH_TABLE_FOREACH_END;
+
+       check_domain = NULL;
+}
+
+static gboolean
+is_xdomain_ref_allowed (gpointer *ptr, char *obj, MonoDomain *domain)
+{
+       MonoObject *o = (MonoObject*)(obj);
+       MonoObject *ref = (MonoObject*)*(ptr);
+       size_t offset = (char*)(ptr) - (char*)o;
+
+       if (o->vtable->klass == mono_defaults.thread_class && offset == G_STRUCT_OFFSET (MonoThread, internal_thread))
+               return TRUE;
+       if (o->vtable->klass == mono_defaults.internal_thread_class && offset == G_STRUCT_OFFSET (MonoInternalThread, current_appcontext))
+               return TRUE;
+
+#ifndef DISABLE_REMOTING
+       if (mono_defaults.real_proxy_class->supertypes && mono_class_has_parent_fast (o->vtable->klass, mono_defaults.real_proxy_class) &&
+                       offset == G_STRUCT_OFFSET (MonoRealProxy, unwrapped_server))
+               return TRUE;
+#endif
+       /* Thread.cached_culture_info */
+       if (!strcmp (ref->vtable->klass->name_space, "System.Globalization") &&
+                       !strcmp (ref->vtable->klass->name, "CultureInfo") &&
+                       !strcmp(o->vtable->klass->name_space, "System") &&
+                       !strcmp(o->vtable->klass->name, "Object[]"))
+               return TRUE;
+       /*
+        *  at System.IO.MemoryStream.InternalConstructor (byte[],int,int,bool,bool) [0x0004d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:121
+        * at System.IO.MemoryStream..ctor (byte[]) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:81
+        * at (wrapper remoting-invoke-with-check) System.IO.MemoryStream..ctor (byte[]) <IL 0x00020, 0xffffffff>
+        * at System.Runtime.Remoting.Messaging.CADMethodCallMessage.GetArguments () [0x0000d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/CADMessages.cs:327
+        * at System.Runtime.Remoting.Messaging.MethodCall..ctor (System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/MethodCall.cs:87
+        * at System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) [0x00018] in /home/schani/Work/novell/trunk/mcs/class/corlib/System/AppDomain.cs:1213
+        * at (wrapper remoting-invoke-with-check) System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) <IL 0x0003d, 0xffffffff>
+        * at System.Runtime.Remoting.Channels.CrossAppDomainSink.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00008] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Channels/CrossAppDomainChannel.cs:198
+        * at (wrapper runtime-invoke) object.runtime_invoke_CrossAppDomainSink/ProcessMessageRes_object_object (object,intptr,intptr,intptr) <IL 0x0004c, 0xffffffff>
+        */
+       if (!strcmp (ref->vtable->klass->name_space, "System") &&
+                       !strcmp (ref->vtable->klass->name, "Byte[]") &&
+                       !strcmp (o->vtable->klass->name_space, "System.IO") &&
+                       !strcmp (o->vtable->klass->name, "MemoryStream"))
+               return TRUE;
+       /* append_job() in threadpool.c */
+       if (!strcmp (ref->vtable->klass->name_space, "System.Runtime.Remoting.Messaging") &&
+                       !strcmp (ref->vtable->klass->name, "AsyncResult") &&
+                       !strcmp (o->vtable->klass->name_space, "System") &&
+                       !strcmp (o->vtable->klass->name, "Object[]") &&
+                       mono_thread_pool_is_queue_array ((MonoArray*) o))
+               return TRUE;
+       return FALSE;
+}
+
+static void
+check_reference_for_xdomain (gpointer *ptr, char *obj, MonoDomain *domain)
+{
+       MonoObject *o = (MonoObject*)(obj);
+       MonoObject *ref = (MonoObject*)*(ptr);
+       size_t offset = (char*)(ptr) - (char*)o;
+       MonoClass *class;
+       MonoClassField *field;
+       char *str;
+
+       if (!ref || ref->vtable->domain == domain)
+               return;
+       if (is_xdomain_ref_allowed (ptr, obj, domain))
+               return;
+
+       field = NULL;
+       for (class = o->vtable->klass; class; class = class->parent) {
+               int i;
+
+               for (i = 0; i < class->field.count; ++i) {
+                       if (class->fields[i].offset == offset) {
+                               field = &class->fields[i];
+                               break;
+                       }
+               }
+               if (field)
+                       break;
+       }
+
+       if (ref->vtable->klass == mono_defaults.string_class)
+               str = mono_string_to_utf8 ((MonoString*)ref);
+       else
+               str = NULL;
+       g_print ("xdomain reference in %p (%s.%s) at offset %d (%s) to %p (%s.%s) (%s)  -  pointed to by:\n",
+                       o, o->vtable->klass->name_space, o->vtable->klass->name,
+                       offset, field ? field->name : "",
+                       ref, ref->vtable->klass->name_space, ref->vtable->klass->name, str ? str : "");
+       mono_gc_scan_for_specific_ref (o, TRUE);
+       if (str)
+               g_free (str);
+}
+
+#undef HANDLE_PTR
+#define HANDLE_PTR(ptr,obj)    check_reference_for_xdomain ((ptr), (obj), domain)
+
+static void
+scan_object_for_xdomain_refs (char *start, mword size, void *data)
+{
+       MonoDomain *domain = ((MonoObject*)start)->vtable->domain;
+
+       #include "sgen-scan-object.h"
+}
+
+void
+sgen_check_for_xdomain_refs (void)
+{
+       LOSObject *bigobj;
+
+       sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
+                       (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL, FALSE);
+
+       major_collector.iterate_objects (ITERATE_OBJECTS_SWEEP_ALL, (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
+
+       for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
+               scan_object_for_xdomain_refs (bigobj->data, sgen_los_object_size (bigobj), NULL);
+}
+
+static int
+compare_xrefs (const void *a_ptr, const void *b_ptr)
+{
+       const MonoGCBridgeXRef *a = a_ptr;
+       const MonoGCBridgeXRef *b = 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");
+}
+*/
+
+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) {
+                       MonoObject *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 = 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 = 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 = sgen_alloc_internal_dynamic (xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG, TRUE);
+       b_xrefs = 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 = 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 = 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;
+}
+
 #endif /*HAVE_SGEN_GC*/