Merge pull request #3258 from lindenlab/mono4-continuations_fix
[mono.git] / mono / metadata / sgen-bridge.c
1 /*
2  * sgen-bridge.c: Simple generational GC.
3  *
4  * Copyright 2011 Novell, Inc (http://www.novell.com)
5  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
6  * Copyright 2001-2003 Ximian, Inc
7  * Copyright 2003-2010 Novell, Inc.
8  *
9  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
10  */
11
12 #include "config.h"
13
14 #ifdef HAVE_SGEN_GC
15
16 #include <stdlib.h>
17
18 #include "sgen/sgen-gc.h"
19 #include "sgen-bridge-internals.h"
20 #include "sgen/sgen-hash-table.h"
21 #include "sgen/sgen-qsort.h"
22 #include "utils/mono-logger-internals.h"
23
24 MonoGCBridgeCallbacks bridge_callbacks;
25 static SgenBridgeProcessor bridge_processor;
26 static SgenBridgeProcessor compare_to_bridge_processor;
27
28 volatile gboolean bridge_processing_in_progress = FALSE;
29
30 void
31 mono_gc_wait_for_bridge_processing (void)
32 {
33         if (!bridge_processing_in_progress)
34                 return;
35
36         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_BRIDGE waiting for bridge processing to finish");
37
38         sgen_gc_lock ();
39         sgen_gc_unlock ();
40 }
41
42
43 void
44 mono_gc_register_bridge_callbacks (MonoGCBridgeCallbacks *callbacks)
45 {
46         if (callbacks->bridge_version != SGEN_BRIDGE_VERSION)
47                 g_error ("Invalid bridge callback version. Expected %d but got %d\n", SGEN_BRIDGE_VERSION, callbacks->bridge_version);
48
49         bridge_callbacks = *callbacks;
50
51         // If callbacks are still uninitialized, initialize defaults
52         if (!bridge_processor.reset_data)
53                 sgen_tarjan_bridge_init (&bridge_processor);
54 }
55
56 static gboolean
57 init_bridge_processor (SgenBridgeProcessor *processor, const char *name)
58 {
59         if (!strcmp ("old", name)) {
60                 memset (processor, 0, sizeof (SgenBridgeProcessor));
61                 sgen_old_bridge_init (processor);
62         } else if (!strcmp ("new", name)) {
63                 memset (processor, 0, sizeof (SgenBridgeProcessor));
64                 sgen_new_bridge_init (processor);
65         } else if (!strcmp ("tarjan", name)) {
66                 memset (processor, 0, sizeof (SgenBridgeProcessor));
67                 sgen_tarjan_bridge_init (processor);
68         } else {
69                 return FALSE;
70         }
71         return TRUE;
72 }
73
74 void
75 sgen_set_bridge_implementation (const char *name)
76 {
77         if (!init_bridge_processor (&bridge_processor, name))
78                 g_warning ("Invalid value for bridge implementation, valid values are: 'new', 'old' and 'tarjan'.");
79 }
80
81 gboolean
82 sgen_is_bridge_object (GCObject *obj)
83 {
84         if ((obj->vtable->gc_bits & SGEN_GC_BIT_BRIDGE_OBJECT) != SGEN_GC_BIT_BRIDGE_OBJECT)
85                 return FALSE;
86         return bridge_callbacks.is_bridge_object (obj);
87 }
88
89 gboolean
90 sgen_need_bridge_processing (void)
91 {
92         return bridge_callbacks.cross_references != NULL;
93 }
94
95 static gboolean
96 compare_bridge_processors (void)
97 {
98         return compare_to_bridge_processor.reset_data != NULL;
99 }
100
101 /* Dispatch wrappers */
102 void
103 sgen_bridge_reset_data (void)
104 {
105         bridge_processor.reset_data ();
106         if (compare_bridge_processors ())
107                 compare_to_bridge_processor.reset_data ();
108 }
109
110 void
111 sgen_bridge_processing_stw_step (void)
112 {
113         /*
114          * bridge_processing_in_progress must be set with the world
115          * stopped.  If not there would be race conditions.
116          */
117         bridge_processing_in_progress = TRUE;
118
119         bridge_processor.processing_stw_step ();
120         if (compare_bridge_processors ())
121                 compare_to_bridge_processor.processing_stw_step ();
122 }
123
124 static gboolean
125 is_bridge_object_dead (GCObject *obj, void *data)
126 {
127         SgenHashTable *table = (SgenHashTable *)data;
128         unsigned char *value = (unsigned char *)sgen_hash_table_lookup (table, obj);
129         if (!value)
130                 return FALSE;
131         return !*value;
132 }
133
134 static void
135 null_weak_links_to_dead_objects (SgenBridgeProcessor *processor, int generation)
136 {
137         int i, j;
138         int num_sccs = processor->num_sccs;
139         MonoGCBridgeSCC **api_sccs = processor->api_sccs;
140         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);
141
142         for (i = 0; i < num_sccs; ++i) {
143                 unsigned char alive = api_sccs [i]->is_alive ? 1 : 0;
144                 for (j = 0; j < api_sccs [i]->num_objs; ++j) {
145                         /* Build hash table for nulling weak links. */
146                         sgen_hash_table_replace (&alive_hash, api_sccs [i]->objs [j], &alive, NULL);
147
148                         /* Release for finalization those objects we no longer care. */
149                         if (!api_sccs [i]->is_alive)
150                                 sgen_mark_bridge_object (api_sccs [i]->objs [j]);
151                 }
152         }
153
154         /* Null weak links to dead objects. */
155         sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_NURSERY, FALSE);
156         sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_NURSERY, TRUE);
157         if (generation == GENERATION_OLD) {
158                 sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_OLD, FALSE);
159                 sgen_null_links_if (is_bridge_object_dead, &alive_hash, GENERATION_OLD, TRUE);
160         }
161
162         sgen_hash_table_clean (&alive_hash);
163 }
164
165 static void
166 free_callback_data (SgenBridgeProcessor *processor)
167 {
168         int i;
169         int num_sccs = processor->num_sccs;
170         int num_xrefs = processor->num_xrefs;
171         MonoGCBridgeSCC **api_sccs = processor->api_sccs;
172         MonoGCBridgeXRef *api_xrefs = processor->api_xrefs;
173
174         for (i = 0; i < num_sccs; ++i) {
175                 sgen_free_internal_dynamic (api_sccs [i],
176                                 sizeof (MonoGCBridgeSCC) + sizeof (MonoObject*) * api_sccs [i]->num_objs,
177                                 INTERNAL_MEM_BRIDGE_DATA);
178         }
179         sgen_free_internal_dynamic (api_sccs, sizeof (MonoGCBridgeSCC*) * num_sccs, INTERNAL_MEM_BRIDGE_DATA);
180
181         sgen_free_internal_dynamic (api_xrefs, sizeof (MonoGCBridgeXRef) * num_xrefs, INTERNAL_MEM_BRIDGE_DATA);
182
183         processor->num_sccs = 0;
184         processor->api_sccs = NULL;
185         processor->num_xrefs = 0;
186         processor->api_xrefs = NULL;
187 }
188
189 static int
190 compare_xrefs (const void *a_ptr, const void *b_ptr)
191 {
192         const MonoGCBridgeXRef *a = (const MonoGCBridgeXRef *)a_ptr;
193         const MonoGCBridgeXRef *b = (const MonoGCBridgeXRef *)b_ptr;
194
195         if (a->src_scc_index < b->src_scc_index)
196                 return -1;
197         if (a->src_scc_index > b->src_scc_index)
198                 return 1;
199
200         if (a->dst_scc_index < b->dst_scc_index)
201                 return -1;
202         if (a->dst_scc_index > b->dst_scc_index)
203                 return 1;
204
205         return 0;
206 }
207
208 /*
209 static void
210 dump_processor_state (SgenBridgeProcessor *p)
211 {
212         int i;
213
214         printf ("------\n");
215         printf ("SCCS %d\n", p->num_sccs);
216         for (i = 0; i < p->num_sccs; ++i) {
217                 int j;
218                 MonoGCBridgeSCC *scc = p->api_sccs [i];
219                 printf ("\tSCC %d:", i);
220                 for (j = 0; j < scc->num_objs; ++j) {
221                         MonoObject *obj = scc->objs [j];
222                         printf (" %p", obj);
223                 }
224                 printf ("\n");
225         }
226
227         printf ("XREFS %d\n", p->num_xrefs);
228         for (i = 0; i < p->num_xrefs; ++i)
229                 printf ("\t%d -> %d\n", p->api_xrefs [i].src_scc_index, p->api_xrefs [i].dst_scc_index);
230
231         printf ("-------\n");
232 }
233 */
234
235 static gboolean
236 sgen_compare_bridge_processor_results (SgenBridgeProcessor *a, SgenBridgeProcessor *b)
237 {
238         int i;
239         SgenHashTable obj_to_a_scc = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_BRIDGE_DEBUG, INTERNAL_MEM_BRIDGE_DEBUG, sizeof (int), mono_aligned_addr_hash, NULL);
240         SgenHashTable b_scc_to_a_scc = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_BRIDGE_DEBUG, INTERNAL_MEM_BRIDGE_DEBUG, sizeof (int), g_direct_hash, NULL);
241         MonoGCBridgeXRef *a_xrefs, *b_xrefs;
242         size_t xrefs_alloc_size;
243
244         // dump_processor_state (a);
245         // dump_processor_state (b);
246
247         if (a->num_sccs != b->num_sccs)
248                 g_error ("SCCS count expected %d but got %d", a->num_sccs, b->num_sccs);
249         if (a->num_xrefs != b->num_xrefs)
250                 g_error ("SCCS count expected %d but got %d", a->num_xrefs, b->num_xrefs);
251
252         /*
253          * First we build a hash of each object in `a` to its respective SCC index within
254          * `a`.  Along the way we also assert that no object is more than one SCC.
255          */
256         for (i = 0; i < a->num_sccs; ++i) {
257                 int j;
258                 MonoGCBridgeSCC *scc = a->api_sccs [i];
259
260                 g_assert (scc->num_objs > 0);
261
262                 for (j = 0; j < scc->num_objs; ++j) {
263                         GCObject *obj = scc->objs [j];
264                         gboolean new_entry = sgen_hash_table_replace (&obj_to_a_scc, obj, &i, NULL);
265                         g_assert (new_entry);
266                 }
267         }
268
269         /*
270          * Now we check whether each of the objects in `b` are in `a`, and whether the SCCs
271          * of `b` contain the same sets of objects as those of `a`.
272          *
273          * While we're doing this, build a hash table to map from `b` SCC indexes to `a` SCC
274          * indexes.
275          */
276         for (i = 0; i < b->num_sccs; ++i) {
277                 MonoGCBridgeSCC *scc = b->api_sccs [i];
278                 MonoGCBridgeSCC *a_scc;
279                 int *a_scc_index_ptr;
280                 int a_scc_index;
281                 int j;
282                 gboolean new_entry;
283
284                 g_assert (scc->num_objs > 0);
285                 a_scc_index_ptr = (int *)sgen_hash_table_lookup (&obj_to_a_scc, scc->objs [0]);
286                 g_assert (a_scc_index_ptr);
287                 a_scc_index = *a_scc_index_ptr;
288
289                 //g_print ("A SCC %d -> B SCC %d\n", a_scc_index, i);
290
291                 a_scc = a->api_sccs [a_scc_index];
292                 g_assert (a_scc->num_objs == scc->num_objs);
293
294                 for (j = 1; j < scc->num_objs; ++j) {
295                         a_scc_index_ptr = (int *)sgen_hash_table_lookup (&obj_to_a_scc, scc->objs [j]);
296                         g_assert (a_scc_index_ptr);
297                         g_assert (*a_scc_index_ptr == a_scc_index);
298                 }
299
300                 new_entry = sgen_hash_table_replace (&b_scc_to_a_scc, GINT_TO_POINTER (i), &a_scc_index, NULL);
301                 g_assert (new_entry);
302         }
303
304         /*
305          * Finally, check that we have the same xrefs.  We do this by making copies of both
306          * xref arrays, and replacing the SCC indexes in the copy for `b` with the
307          * corresponding indexes in `a`.  Then we sort both arrays and assert that they're
308          * the same.
309          *
310          * At the same time, check that no xref is self-referential and that there are no
311          * duplicate ones.
312          */
313
314         xrefs_alloc_size = a->num_xrefs * sizeof (MonoGCBridgeXRef);
315         a_xrefs = (MonoGCBridgeXRef *)sgen_alloc_internal_dynamic (xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG, TRUE);
316         b_xrefs = (MonoGCBridgeXRef *)sgen_alloc_internal_dynamic (xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG, TRUE);
317
318         memcpy (a_xrefs, a->api_xrefs, xrefs_alloc_size);
319         for (i = 0; i < b->num_xrefs; ++i) {
320                 MonoGCBridgeXRef *xref = &b->api_xrefs [i];
321                 int *scc_index_ptr;
322
323                 g_assert (xref->src_scc_index != xref->dst_scc_index);
324
325                 scc_index_ptr = (int *)sgen_hash_table_lookup (&b_scc_to_a_scc, GINT_TO_POINTER (xref->src_scc_index));
326                 g_assert (scc_index_ptr);
327                 b_xrefs [i].src_scc_index = *scc_index_ptr;
328
329                 scc_index_ptr = (int *)sgen_hash_table_lookup (&b_scc_to_a_scc, GINT_TO_POINTER (xref->dst_scc_index));
330                 g_assert (scc_index_ptr);
331                 b_xrefs [i].dst_scc_index = *scc_index_ptr;
332         }
333
334         qsort (a_xrefs, a->num_xrefs, sizeof (MonoGCBridgeXRef), compare_xrefs);
335         qsort (b_xrefs, a->num_xrefs, sizeof (MonoGCBridgeXRef), compare_xrefs);
336
337         for (i = 0; i < a->num_xrefs; ++i) {
338                 g_assert (a_xrefs [i].src_scc_index == b_xrefs [i].src_scc_index);
339                 g_assert (a_xrefs [i].dst_scc_index == b_xrefs [i].dst_scc_index);
340         }
341
342         sgen_hash_table_clean (&obj_to_a_scc);
343         sgen_hash_table_clean (&b_scc_to_a_scc);
344         sgen_free_internal_dynamic (a_xrefs, xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG);
345         sgen_free_internal_dynamic (b_xrefs, xrefs_alloc_size, INTERNAL_MEM_BRIDGE_DEBUG);
346
347         return TRUE;
348 }
349
350 void
351 sgen_bridge_processing_finish (int generation)
352 {
353         bridge_processor.processing_build_callback_data (generation);
354         if (compare_bridge_processors ())
355                 compare_to_bridge_processor.processing_build_callback_data (generation);
356
357         if (bridge_processor.num_sccs == 0) {
358                 g_assert (bridge_processor.num_xrefs == 0);
359                 goto after_callback;
360         }
361
362         bridge_callbacks.cross_references (bridge_processor.num_sccs, bridge_processor.api_sccs,
363                         bridge_processor.num_xrefs, bridge_processor.api_xrefs);
364
365         if (compare_bridge_processors ())
366                 sgen_compare_bridge_processor_results (&bridge_processor, &compare_to_bridge_processor);
367
368         null_weak_links_to_dead_objects (&bridge_processor, generation);
369
370         free_callback_data (&bridge_processor);
371         if (compare_bridge_processors ())
372                 free_callback_data (&compare_to_bridge_processor);
373
374  after_callback:
375         bridge_processor.processing_after_callback (generation);
376         if (compare_bridge_processors ())
377                 compare_to_bridge_processor.processing_after_callback (generation);
378
379         mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_GC, "GC_BRIDGE: Complete, was running for %.2fms", mono_time_since_last_stw () / 10000.0f);
380
381         bridge_processing_in_progress = FALSE;
382 }
383
384 MonoGCBridgeObjectKind
385 sgen_bridge_class_kind (MonoClass *klass)
386 {
387         return bridge_processor.class_kind (klass);
388 }
389
390 void
391 sgen_bridge_register_finalized_object (GCObject *obj)
392 {
393         bridge_processor.register_finalized_object (obj);
394         if (compare_bridge_processors ())
395                 compare_to_bridge_processor.register_finalized_object (obj);
396 }
397
398 void
399 sgen_bridge_describe_pointer (GCObject *obj)
400 {
401         if (bridge_processor.describe_pointer)
402                 bridge_processor.describe_pointer (obj);
403 }
404
405 static void
406 set_dump_prefix (const char *prefix)
407 {
408         if (!bridge_processor.set_dump_prefix) {
409                 fprintf (stderr, "Warning: Bridge implementation does not support dumping - ignoring.\n");
410                 return;
411         }
412
413         bridge_processor.set_dump_prefix (prefix);
414 }
415
416 /* Test support code */
417 static const char *bridge_class;
418
419 static MonoGCBridgeObjectKind
420 bridge_test_bridge_class_kind (MonoClass *klass)
421 {
422         if (!strcmp (bridge_class, klass->name))
423                 return GC_BRIDGE_TRANSPARENT_BRIDGE_CLASS;
424         return GC_BRIDGE_TRANSPARENT_CLASS;
425 }
426
427 static gboolean
428 bridge_test_is_bridge_object (MonoObject *object)
429 {
430         return TRUE;
431 }
432
433 static void
434 bridge_test_cross_reference (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs)
435 {
436         int i;
437         for (i = 0; i < num_sccs; ++i) {
438                 int j;
439         //      g_print ("--- SCC %d\n", i);
440                 for (j = 0; j < sccs [i]->num_objs; ++j) {
441         //              g_print ("  %s\n", sgen_safe_name (sccs [i]->objs [j]));
442                         if (i & 1) /*retain half of the bridged objects */
443                                 sccs [i]->is_alive = TRUE;
444                 }
445         }
446         for (i = 0; i < num_xrefs; ++i) {
447                 g_assert (xrefs [i].src_scc_index >= 0 && xrefs [i].src_scc_index < num_sccs);
448                 g_assert (xrefs [i].dst_scc_index >= 0 && xrefs [i].dst_scc_index < num_sccs);
449         //      g_print ("%d -> %d\n", xrefs [i].src_scc_index, xrefs [i].dst_scc_index);
450         }
451 }
452
453 static MonoClassField *mono_bridge_test_field;
454
455 enum {
456         BRIDGE_DEAD,
457         BRIDGE_ROOT,
458         BRIDGE_SAME_SCC,
459         BRIDGE_XREF,
460 };
461
462 static gboolean
463 test_scc (MonoGCBridgeSCC *scc, int i)
464 {
465         int status = BRIDGE_DEAD;
466         mono_field_get_value (scc->objs [i], mono_bridge_test_field, &status);
467         return status > 0;
468 }
469
470 static void
471 mark_scc (MonoGCBridgeSCC *scc, int value)
472 {
473         int i;
474         for (i = 0; i < scc->num_objs; ++i) {
475                 if (!test_scc (scc, i)) {
476                         int status = value;
477                         mono_field_set_value (scc->objs [i], mono_bridge_test_field, &status);
478                 }
479         }
480 }
481
482 static void
483 bridge_test_cross_reference2 (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs)
484 {
485         int i;
486         gboolean modified;
487
488         if (!mono_bridge_test_field) {
489                 mono_bridge_test_field = mono_class_get_field_from_name (mono_object_get_class (sccs[0]->objs [0]), "__test");
490                 g_assert (mono_bridge_test_field);
491         }
492
493         /*We mark all objects in a scc with live objects as reachable by scc*/
494         for (i = 0; i < num_sccs; ++i) {
495                 int j;
496                 gboolean live = FALSE;
497                 for (j = 0; j < sccs [i]->num_objs; ++j) {
498                         if (test_scc (sccs [i], j)) {
499                                 live = TRUE;
500                                 break;
501                         }
502                 }
503                 if (!live)
504                         continue;
505                 for (j = 0; j < sccs [i]->num_objs; ++j) {
506                         if (!test_scc (sccs [i], j)) {
507                                 int status = BRIDGE_SAME_SCC;
508                                 mono_field_set_value (sccs [i]->objs [j], mono_bridge_test_field, &status);
509                         }
510                 }
511         }
512
513         /*Now we mark the transitive closure of reachable objects from the xrefs*/
514         modified = TRUE;
515         while (modified) {
516                 modified = FALSE;
517                 /* Mark all objects that are brought to life due to xrefs*/
518                 for (i = 0; i < num_xrefs; ++i) {
519                         MonoGCBridgeXRef ref = xrefs [i];
520                         if (test_scc (sccs [ref.src_scc_index], 0) && !test_scc (sccs [ref.dst_scc_index], 0)) {
521                                 modified = TRUE;
522                                 mark_scc (sccs [ref.dst_scc_index], BRIDGE_XREF);
523                         }
524                 }
525         }
526
527         /* keep everything in memory, all we want to do is test persistence */
528         for (i = 0; i < num_sccs; ++i)
529                 sccs [i]->is_alive = TRUE;
530 }
531
532 /* This bridge keeps all peers with __test > 0 */
533 static void
534 bridge_test_positive_status (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs)
535 {
536         int i;
537
538         if (!mono_bridge_test_field) {
539                 mono_bridge_test_field = mono_class_get_field_from_name (mono_object_get_class (sccs[0]->objs [0]), "__test");
540                 g_assert (mono_bridge_test_field);
541         }
542
543         /*We mark all objects in a scc with live objects as reachable by scc*/
544         for (i = 0; i < num_sccs; ++i) {
545                 int j;
546                 for (j = 0; j < sccs [i]->num_objs; ++j) {
547                         if (test_scc (sccs [i], j)) {
548                                 sccs [i]->is_alive = TRUE;
549                                 break;
550                         }
551                 }
552         }
553 }
554
555
556 static void
557 register_test_bridge_callbacks (const char *bridge_class_name)
558 {
559         MonoGCBridgeCallbacks callbacks;
560         callbacks.bridge_version = SGEN_BRIDGE_VERSION;
561         callbacks.bridge_class_kind = bridge_test_bridge_class_kind;
562         callbacks.is_bridge_object = bridge_test_is_bridge_object;
563
564         switch (bridge_class_name [0]) {
565         case '2':
566                 bridge_class = bridge_class_name + 1;
567                 callbacks.cross_references = bridge_test_cross_reference2;
568                 break;
569         case '3':
570                 bridge_class = bridge_class_name + 1;
571                 callbacks.cross_references = bridge_test_positive_status;
572                 break;
573         default:
574                 bridge_class = bridge_class_name;
575                 callbacks.cross_references = bridge_test_cross_reference;
576         }
577         mono_gc_register_bridge_callbacks (&callbacks);
578 }
579
580 gboolean
581 sgen_bridge_handle_gc_debug (const char *opt)
582 {
583         if (g_str_has_prefix (opt, "bridge=")) {
584                 opt = strchr (opt, '=') + 1;
585                 register_test_bridge_callbacks (g_strdup (opt));
586         } else if (!strcmp (opt, "enable-bridge-accounting")) {
587                 bridge_processor.enable_accounting ();
588         } else if (g_str_has_prefix (opt, "bridge-dump=")) {
589                 char *prefix = strchr (opt, '=') + 1;
590                 set_dump_prefix (prefix);
591         } else if (g_str_has_prefix (opt, "bridge-compare-to=")) {
592                 const char *name = strchr (opt, '=') + 1;
593                 if (init_bridge_processor (&compare_to_bridge_processor, name)) {
594                         if (compare_to_bridge_processor.reset_data == bridge_processor.reset_data) {
595                                 g_warning ("Cannot compare bridge implementation to itself - ignoring.");
596                                 memset (&compare_to_bridge_processor, 0, sizeof (SgenBridgeProcessor));
597                         }
598                 } else {
599                         g_warning ("Invalid bridge implementation to compare against - ignoring.");
600                 }
601         } else {
602                 return FALSE;
603         }
604         return TRUE;
605 }
606
607 void
608 sgen_bridge_print_gc_debug_usage (void)
609 {
610         fprintf (stderr, "  bridge=<class-name>\n");
611         fprintf (stderr, "  enable-bridge-accounting\n");
612         fprintf (stderr, "  bridge-dump=<filename-prefix>\n");
613         fprintf (stderr, "  bridge-compare-to=<implementation>\n");
614 }
615
616 #endif