[Loader] Change mono_trace level from info to debug (#19110)
[mono-project.git] / mono / metadata / sgen-tarjan-bridge.c
blobc5a7f876502d0ef7b005edff75abda284fdd8c29
1 /**
2 * \file
3 * Tarjan-based bridge implementation.
5 * Copyright 2011 Novell, Inc (http://www.novell.com)
6 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
9 * Copyright 2001-2003 Ximian, Inc
10 * Copyright 2003-2010 Novell, Inc.
12 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
15 #include "config.h"
17 #if defined (HAVE_SGEN_GC) && !defined (DISABLE_SGEN_GC_BRIDGE)
19 #include <stdlib.h>
21 #include "sgen/sgen-gc.h"
22 #include "sgen-bridge-internals.h"
23 #include "tabledefs.h"
24 #include "utils/mono-logger-internals.h"
26 #include "sgen-dynarray.h"
29 * See comments in sgen-bridge.h
31 * This bridge implementation is based on the tarjan algorithm for strongly
32 * connected components. It has two elements:
34 * - Standard tarjan SCC algorithm to convert graph to SCC forest
36 * - "Colors": We reduce the SCC forest to bridged-SCCs-only by using a
37 * "color" algorithm devised by Kumpera. Consider the set of bridged SCCs
38 * which is reachable from a given object. We call each such unique set a
39 * "color". We compute the set of colors and which colors contain links to
40 * which colors. The color graph then becomes the reduced SCC graph.
43 // Is this class bridged or not, and should its dependencies be scanned or not?
44 // The result of this callback will be cached for use by is_opaque_object later.
45 static MonoGCBridgeObjectKind
46 class_kind (MonoClass *klass)
48 MonoGCBridgeObjectKind res = mono_bridge_callbacks.bridge_class_kind (klass);
50 /* If it's a bridge, nothing we can do about it. */
51 if (res == GC_BRIDGE_TRANSPARENT_BRIDGE_CLASS || res == GC_BRIDGE_OPAQUE_BRIDGE_CLASS)
52 return res;
54 /* Non bridge classes with no pointers will never point to a bridge, so we can savely ignore them. */
55 if (!m_class_has_references (klass)) {
56 SGEN_LOG (6, "class %s is opaque\n", m_class_get_name (klass));
57 return GC_BRIDGE_OPAQUE_CLASS;
60 /* Some arrays can be ignored */
61 if (m_class_get_rank (klass) == 1) {
62 MonoClass *elem_class = m_class_get_element_class (klass);
64 /* FIXME the bridge check can be quite expensive, cache it at the class level. */
65 /* An array of a sealed type that is not a bridge will never get to a bridge */
66 if ((mono_class_get_flags (elem_class) & TYPE_ATTRIBUTE_SEALED) && !m_class_has_references (elem_class) && !mono_bridge_callbacks.bridge_class_kind (elem_class)) {
67 SGEN_LOG (6, "class %s is opaque\n", m_class_get_name (klass));
68 return GC_BRIDGE_OPAQUE_CLASS;
72 return GC_BRIDGE_TRANSPARENT_CLASS;
75 //enable usage logging
76 // #define DUMP_GRAPH 1
78 /* Used in bridgeless_color_is_heavy:
79 * The idea here is as long as the reference fanin and fanout on a node are both 2 or greater, then
80 * removing that node will result in a net increase in edge count. So the question is which node
81 * removals are counterproductive (i.e., how many edges saved balances out one node added).
82 * The number of edges saved by preserving a node is (fanin*fanout - fanin - fanout).
84 * With all that in mind:
86 * - HEAVY_REFS_MIN is the number that *both* fanin and fanout must meet to preserve the node.
87 * - HEAVY_COMBINED_REFS_MIN is the number (fanin*fanout) must meet to preserve the node.
89 * Note HEAVY_COMBINED_REFS_MIN must be <= 2*INCOMING_COLORS_MAX, or we won't know the true fanin.
92 #define HEAVY_REFS_MIN 2
93 #define HEAVY_COMBINED_REFS_MIN 60
95 /* Used in ColorData:
96 * The higher INCOMING_COLORS_BITS is the higher HEAVY_COMBINED_REFS_MIN can be (see above).
97 * However, API_INDEX_BITS + INCOMING_COLORS_BITS must be equal to 31, and if API_INDEX_BITS is too
98 * low then terrible things will happen if too many colors are generated. (The number of colors we
99 * will ever attempt to generate is currently naturally limited by the JNI GREF limit.)
102 #define API_INDEX_BITS 26
103 #define INCOMING_COLORS_BITS 5
105 #define API_INDEX_MAX ((1<<API_INDEX_BITS)-1)
106 #define INCOMING_COLORS_MAX ((1<<INCOMING_COLORS_BITS)-1)
108 // ScanData state
109 enum {
110 INITIAL,
111 SCANNED,
112 FINISHED_ON_STACK,
113 FINISHED_OFF_STACK
117 Optimizations:
118 We can split this data structure in two, those with bridges and those without
119 (and only bridgeless need to record incoming_colors)
121 typedef struct {
122 // Colors (ColorDatas) linked to by objects with this color
123 DynPtrArray other_colors;
124 // Bridge objects (GCObjects) held by objects with this color
125 DynPtrArray bridges;
126 // Index of this color's MonoGCBridgeSCC in the array passed to the client (or -1 for none)
127 signed api_index : API_INDEX_BITS;
128 // Count of colors that list this color in their other_colors
129 unsigned incoming_colors : INCOMING_COLORS_BITS;
130 unsigned visited : 1;
131 } ColorData;
133 // Represents one managed object. Equivalent of new/old bridge "HashEntry"
134 typedef struct _ScanData {
135 // FIXME this can be eliminated; if we have a ScanData we generally looked it up from its GCObject
136 GCObject *obj;
137 // We use the sgen lock_word in GCObject to store a pointer to the ScanData. Cache the original here to restore later:
138 mword lock_word;
140 ColorData *color;
141 // If this object isn't the scc root, we still need to store the xref colors on it, after computing
142 // low index, so we can add them to the scc that this object is part of.
143 DynPtrArray xrefs;
145 // Tarjan algorithm index (order visited)
146 int index;
147 // Tarjan index of lowest-index object known reachable from here
148 signed low_index : 27;
150 // See "ScanData state" enum above
151 unsigned state : 2;
152 unsigned is_bridge : 1;
153 // Similar to lock_word, we use these bits in the GCObject as scratch space then restore them when done
154 unsigned obj_state : 2;
155 } ScanData;
157 // If true, disable an optimization where sometimes SCC nodes don't contain any bridge objects, in order to reduce total xrefs.
158 static gboolean disable_non_bridge_scc;
160 /* Should color be made visible to client even though it has no bridges?
161 * True if we predict the number of reduced edges to be enough to justify the extra node.
163 static gboolean
164 bridgeless_color_is_heavy (ColorData *data) {
165 if (disable_non_bridge_scc)
166 return FALSE;
167 int fanin = data->incoming_colors;
168 int fanout = dyn_array_ptr_size (&data->other_colors);
169 return fanin > HEAVY_REFS_MIN && fanout > HEAVY_REFS_MIN
170 && fanin*fanout >= HEAVY_COMBINED_REFS_MIN;
173 // Should color be made visible to client?
174 static gboolean
175 color_visible_to_client (ColorData *data) {
176 return dyn_array_ptr_size (&data->bridges) || bridgeless_color_is_heavy (data);
179 // Stacks of ScanData objects used for tarjan algorithm.
180 // The Tarjan algorithm is normally defined recursively; here scan_stack simulates the call stack of a recursive algorithm,
181 // and loop_stack is the stack structure used by the algorithm itself.
182 static DynPtrArray scan_stack, loop_stack;
184 // GCObjects on which register_finalized_object has been called
185 static DynPtrArray registered_bridges;
187 // As we traverse the graph, which ColorData objects are accessible from our current position?
188 static DynPtrArray color_merge_array;
189 // Running hash of the contents of the color_merge_array.
190 static unsigned int color_merge_array_hash;
192 static void color_merge_array_empty (void)
194 dyn_array_ptr_empty (&color_merge_array);
195 color_merge_array_hash = 0;
198 static int ignored_objects;
199 static int object_index;
200 static int num_colors_with_bridges;
201 static int num_sccs;
202 static int xref_count;
204 static size_t setup_time, tarjan_time, scc_setup_time, gather_xref_time, xref_setup_time, cleanup_time;
205 static SgenBridgeProcessor *bridge_processor;
207 #define BUCKET_SIZE 8184
209 //ScanData buckets
210 #define NUM_SCAN_ENTRIES ((BUCKET_SIZE - SIZEOF_VOID_P * 2) / sizeof (ScanData))
212 typedef struct _ObjectBucket ObjectBucket;
213 struct _ObjectBucket {
214 ObjectBucket *next;
215 ScanData *next_data;
216 ScanData data [NUM_SCAN_ENTRIES];
219 static ObjectBucket *root_object_bucket;
220 static ObjectBucket *cur_object_bucket;
221 static int object_data_count;
223 // Arenas to allocate ScanData from
224 static ObjectBucket*
225 new_object_bucket (void)
227 ObjectBucket *res = (ObjectBucket *)sgen_alloc_internal (INTERNAL_MEM_TARJAN_OBJ_BUCKET);
228 res->next_data = &res->data [0];
229 return res;
232 static void
233 object_alloc_init (void)
235 root_object_bucket = cur_object_bucket = new_object_bucket ();
238 static ScanData*
239 alloc_object_data (void)
241 ScanData *res;
242 retry:
244 /* next_data points to the first free entry */
245 res = cur_object_bucket->next_data;
246 if (res >= &cur_object_bucket->data [NUM_SCAN_ENTRIES]) {
247 ObjectBucket *b = new_object_bucket ();
248 cur_object_bucket->next = b;
249 cur_object_bucket = b;
250 goto retry;
252 cur_object_bucket->next_data = res + 1;
253 object_data_count++;
254 return res;
257 static void
258 free_object_buckets (void)
260 ObjectBucket *cur = root_object_bucket;
262 object_data_count = 0;
264 while (cur) {
265 ObjectBucket *tmp = cur->next;
266 sgen_free_internal (cur, INTERNAL_MEM_TARJAN_OBJ_BUCKET);
267 cur = tmp;
270 root_object_bucket = cur_object_bucket = NULL;
273 //ColorData buckets
274 #define NUM_COLOR_ENTRIES ((BUCKET_SIZE - SIZEOF_VOID_P * 2) / sizeof (ColorData))
276 // Arenas for ColorDatas, same as ObjectBucket except items-per-bucket differs
277 typedef struct _ColorBucket ColorBucket;
278 struct _ColorBucket {
279 ColorBucket *next;
280 ColorData *next_data;
281 ColorData data [NUM_COLOR_ENTRIES];
284 static ColorBucket *root_color_bucket;
285 static ColorBucket *cur_color_bucket;
286 static int color_data_count;
289 static ColorBucket*
290 new_color_bucket (void)
292 ColorBucket *res = (ColorBucket *)sgen_alloc_internal (INTERNAL_MEM_TARJAN_OBJ_BUCKET);
293 res->next_data = &res->data [0];
294 return res;
297 static void
298 color_alloc_init (void)
300 root_color_bucket = cur_color_bucket = new_color_bucket ();
303 static ColorData*
304 alloc_color_data (void)
306 ColorData *res;
307 retry:
309 /* next_data points to the first free entry */
310 res = cur_color_bucket->next_data;
311 if (res >= &cur_color_bucket->data [NUM_COLOR_ENTRIES]) {
312 ColorBucket *b = new_color_bucket ();
313 cur_color_bucket->next = b;
314 cur_color_bucket = b;
315 goto retry;
317 cur_color_bucket->next_data = res + 1;
318 color_data_count++;
319 return res;
322 static void
323 free_color_buckets (void)
325 ColorBucket *cur, *tmp;
327 color_data_count = 0;
329 for (cur = root_color_bucket; cur; cur = tmp) {
330 ColorData *cd;
331 for (cd = &cur->data [0]; cd < cur->next_data; ++cd) {
332 dyn_array_ptr_uninit (&cd->other_colors);
333 dyn_array_ptr_uninit (&cd->bridges);
335 tmp = cur->next;
336 sgen_free_internal (cur, INTERNAL_MEM_TARJAN_OBJ_BUCKET);
338 root_color_bucket = cur_color_bucket = NULL;
342 static ScanData*
343 create_data (GCObject *obj)
345 mword *o = (mword*)obj;
346 ScanData *res = alloc_object_data ();
347 res->obj = obj;
348 res->color = NULL;
349 res->index = res->low_index = -1;
350 res->state = INITIAL;
351 res->is_bridge = FALSE;
352 res->obj_state = o [0] & SGEN_VTABLE_BITS_MASK;
353 res->lock_word = o [1];
355 o [0] |= SGEN_VTABLE_BITS_MASK;
356 o [1] = (mword)res;
357 return res;
360 static ScanData*
361 find_data (GCObject *obj)
363 ScanData *a = NULL;
364 mword *o = (mword*)obj;
365 if ((o [0] & SGEN_VTABLE_BITS_MASK) == SGEN_VTABLE_BITS_MASK)
366 a = (ScanData*)o [1];
367 return a;
370 static void
371 clear_after_processing (void)
373 ObjectBucket *cur;
375 for (cur = root_object_bucket; cur; cur = cur->next) {
376 ScanData *sd;
377 for (sd = &cur->data [0]; sd < cur->next_data; ++sd) {
378 mword *o = (mword*)sd->obj;
379 o [0] &= ~SGEN_VTABLE_BITS_MASK;
380 o [0] |= sd->obj_state;
381 o [1] = sd->lock_word;
386 static GCObject*
387 bridge_object_forward (GCObject *obj)
389 GCObject *fwd;
390 mword *o = (mword*)obj;
391 if ((o [0] & SGEN_VTABLE_BITS_MASK) == SGEN_VTABLE_BITS_MASK)
392 return obj;
394 fwd = SGEN_OBJECT_IS_FORWARDED (obj);
395 return fwd ? fwd : obj;
398 #ifdef DUMP_GRAPH
399 static const char*
400 safe_name_bridge (GCObject *obj)
402 GCVTable vt = SGEN_LOAD_VTABLE (obj);
403 return vt->klass->name;
406 static ScanData*
407 find_or_create_data (GCObject *obj)
409 ScanData *entry = find_data (obj);
410 if (!entry)
411 entry = create_data (obj);
412 return entry;
414 #endif
416 //----------
417 typedef struct {
418 ColorData *color;
419 unsigned int hash;
420 } HashEntry;
423 The merge cache maps an ordered list of ColorDatas [the color_merge_array] to a single ColorData.
425 About cache bucket tuning: We tried 2/32, 2/128, 4/32, 4/128, 6/128 and 8/128.
427 The performance cost between 4/128 and 8/128 is so small since cache movement happens completely in the same cacheline,
428 making the extra space pretty much free.
430 The cost between 32 and 128 itens is minimal too, it's mostly a fixed, setup cost.
432 Memory wise, 4/32 takes 512 and 8/128 takes 8k, so it's quite reasonable.
435 #define ELEMENTS_PER_BUCKET 8
436 #define COLOR_CACHE_SIZE 128
437 static HashEntry merge_cache [COLOR_CACHE_SIZE][ELEMENTS_PER_BUCKET];
438 static unsigned int hash_perturb;
440 // If true, disable an optimization where sometimes SCC nodes are merged without a perfect check
441 static gboolean scc_precise_merge;
443 static unsigned int
444 mix_hash (uintptr_t source)
446 unsigned int hash = source;
448 // The full hash determines whether two colors can be merged-- sometimes exclusively.
449 // This value changes every GC, so XORing it in before performing the hash will make the
450 // chance that two different colors will produce the same hash on successive GCs very low.
451 hash = hash ^ hash_perturb;
453 // Actual hash
454 hash = (((hash * 215497) >> 16) ^ ((hash * 1823231) + hash));
456 // Mix in highest bits on 64-bit systems only
457 if (sizeof (source) > 4)
458 hash = hash ^ ((source >> 31) >> 1);
460 return hash;
463 static void
464 reset_cache (void)
466 memset (merge_cache, 0, sizeof (merge_cache));
468 // When using the precise merge debug option, we do not want the inconsistency caused by hash_perturb.
469 if (!scc_precise_merge)
470 ++hash_perturb;
474 static gboolean
475 dyn_array_ptr_contains (DynPtrArray *da, void *x)
477 int i;
478 for (i = 0; i < dyn_array_ptr_size (da); ++i)
479 if (dyn_array_ptr_get (da, i) == x)
480 return TRUE;
481 return FALSE;
484 static gboolean
485 match_colors_estimate (DynPtrArray *a, DynPtrArray *b)
487 return dyn_array_ptr_size (a) == dyn_array_ptr_size (b);
491 static gboolean
492 match_colors (DynPtrArray *a, DynPtrArray *b)
494 int i;
495 if (dyn_array_ptr_size (a) != dyn_array_ptr_size (b))
496 return FALSE;
498 for (i = 0; i < dyn_array_ptr_size (a); ++i) {
499 if (!dyn_array_ptr_contains (b, dyn_array_ptr_get (a, i)))
500 return FALSE;
502 return TRUE;
505 // If scc_precise_merge, "semihits" refers to find_in_cache calls aborted because the merge array was too large.
506 // Otherwise "semihits" refers to cache hits where the match was only estimated.
507 static int cache_hits, cache_semihits, cache_misses;
509 // The cache contains only non-bridged colors.
510 static ColorData*
511 find_in_cache (int *insert_index)
513 HashEntry *bucket;
514 int i, size, index;
516 size = dyn_array_ptr_size (&color_merge_array);
518 /* Color equality checking is very expensive with a lot of elements, so when there are many
519 * elements we switch to a cheap comparison method which allows false positives. When false
520 * positives occur the worst that can happen is two items will be inappropriately merged
521 * and memory will be retained longer than it should be. We assume that will correct itself
522 * on the next GC (the hash_perturb mechanism increases the probability of this).
524 * Because this has *some* potential to create problems, if the user set the debug option
525 * 'enable-tarjan-precise-merge' we bail out early (and never merge SCCs with >3 colors).
527 gboolean color_merge_array_large = size > 3;
528 if (scc_precise_merge && color_merge_array_large) {
529 ++cache_semihits;
530 return NULL;
533 unsigned int hash = color_merge_array_hash;
534 if (!hash) // 0 is used to indicate an empty bucket entry
535 hash = 1;
537 index = hash & (COLOR_CACHE_SIZE - 1);
538 bucket = merge_cache [index];
539 for (i = 0; i < ELEMENTS_PER_BUCKET; ++i) {
540 if (bucket [i].hash != hash)
541 continue;
543 if (color_merge_array_large) {
544 if (match_colors_estimate (&bucket [i].color->other_colors, &color_merge_array)) {
545 ++cache_semihits;
546 return bucket [i].color;
548 } else {
549 if (match_colors (&bucket [i].color->other_colors, &color_merge_array)) {
550 ++cache_hits;
551 return bucket [i].color;
556 //move elements to the back
557 for (i = ELEMENTS_PER_BUCKET - 1; i > 0; --i)
558 bucket [i] = bucket [i - 1];
559 ++cache_misses;
560 *insert_index = index;
561 bucket [0].hash = hash;
562 return NULL;
565 // Populate other_colors for a give color (other_colors represent the xrefs for this color)
566 static void
567 add_other_colors (ColorData *color, DynPtrArray *other_colors)
569 for (int i = 0; i < dyn_array_ptr_size (other_colors); ++i) {
570 ColorData *points_to = (ColorData *)dyn_array_ptr_get (other_colors, i);
571 dyn_array_ptr_add (&color->other_colors, points_to);
572 // Inform targets
573 points_to->incoming_colors = MIN (points_to->incoming_colors + 1, INCOMING_COLORS_MAX);
577 // A color is needed for an SCC. If the SCC has bridges, the color MUST be newly allocated.
578 // If the SCC lacks bridges, the allocator MAY use the cache to merge it with an existing one.
579 static ColorData*
580 new_color (gboolean has_bridges)
582 int cacheSlot = -1;
583 ColorData *cd;
584 /* XXX Try to find an equal one and return it */
585 if (!has_bridges) {
586 cd = find_in_cache (&cacheSlot);
587 if (cd)
588 return cd;
591 cd = alloc_color_data ();
592 cd->api_index = -1;
594 add_other_colors (cd, &color_merge_array);
596 /* if cacheSlot >= 0, it means we prepared a given slot to receive the new color */
597 if (cacheSlot >= 0)
598 merge_cache [cacheSlot][0].color = cd;
600 return cd;
604 static void
605 register_bridge_object (GCObject *obj)
607 create_data (obj)->is_bridge = TRUE;
610 static gboolean
611 is_opaque_object (GCObject *obj)
613 MonoVTable *vt = SGEN_LOAD_VTABLE (obj);
614 if ((vt->gc_bits & SGEN_GC_BIT_BRIDGE_OPAQUE_OBJECT) == SGEN_GC_BIT_BRIDGE_OPAQUE_OBJECT) {
615 SGEN_LOG (6, "ignoring %s\n", m_class_get_name (vt->klass));
616 ++ignored_objects;
617 return TRUE;
619 return FALSE;
622 // Called during DFS; visits one child. If it is a candidate to be scanned, pushes it to the stacks.
623 static void
624 push_object (GCObject *obj)
626 ScanData *data;
627 obj = bridge_object_forward (obj);
629 #if DUMP_GRAPH
630 printf ("\t= pushing %p %s -> ", obj, safe_name_bridge (obj));
631 #endif
633 /* Object types we can ignore */
634 if (is_opaque_object (obj)) {
635 #if DUMP_GRAPH
636 printf ("opaque\n");
637 #endif
638 return;
641 data = find_data (obj);
643 /* Already marked - XXX must be done this way as the bridge themselves are alive. */
644 if (data && data->state != INITIAL) {
645 #if DUMP_GRAPH
646 printf ("already marked\n");
647 #endif
648 return;
651 /* We only care about dead objects */
652 if (!data && sgen_object_is_live (obj)) {
653 #if DUMP_GRAPH
654 printf ("alive\n");
655 #endif
656 return;
659 #if DUMP_GRAPH
660 printf ("pushed!\n");
661 #endif
663 if (!data)
664 data = create_data (obj);
665 g_assert (data->state == INITIAL);
666 g_assert (data->index == -1);
667 dyn_array_ptr_push (&scan_stack, data);
670 #undef HANDLE_PTR
671 #define HANDLE_PTR(ptr,obj) do { \
672 GCObject *dst = (GCObject*)*(ptr); \
673 if (dst) push_object (dst); \
674 } while (0)
676 // dfs () function's queue-children-of-object operation.
677 static void
678 push_all (ScanData *data)
680 GCObject *obj = data->obj;
681 char *start = (char*)obj;
682 mword desc = sgen_obj_get_descriptor_safe (obj);
684 #if DUMP_GRAPH
685 printf ("+scanning %s (%p) index %d color %p\n", safe_name_bridge (data->obj), data->obj, data->index, data->color);
686 #endif
688 #include "sgen/sgen-scan-object.h"
692 static void
693 compute_low_index (ScanData *data, GCObject *obj)
695 ScanData *other;
696 ColorData *cd;
698 obj = bridge_object_forward (obj);
699 other = find_data (obj);
701 #if DUMP_GRAPH
702 printf ("\tcompute low %p ->%p (%s) %p (%d / %d, color %p)\n", data->obj, obj, safe_name_bridge (obj), other, other ? other->index : -2, other ? other->low_index : -2, other->color);
703 #endif
704 if (!other)
705 return;
707 g_assert (other->state != INITIAL);
709 if ((other->state == SCANNED || other->state == FINISHED_ON_STACK) && data->low_index > other->low_index)
710 data->low_index = other->low_index;
712 /* Compute the low color */
713 if (other->color == NULL)
714 return;
716 cd = other->color;
718 // The scc for the referenced object was already created, meaning this is an xref.
719 // Add it to the color merge array so we can handle it later when creating the scc
720 // for the current object (data)
721 if (!cd->visited) {
722 color_merge_array_hash += mix_hash ((uintptr_t) other->color);
723 #if DUMP_GRAPH
724 printf ("\t\tadd color %p to color_merge_array\n", other->color);
725 #endif
726 dyn_array_ptr_add (&color_merge_array, other->color);
727 cd->visited = TRUE;
731 #undef HANDLE_PTR
732 #define HANDLE_PTR(ptr,obj) do { \
733 GCObject *dst = (GCObject*)*(ptr); \
734 if (dst) compute_low_index (data, dst); \
735 } while (0)
737 static void
738 compute_low (ScanData *data)
740 GCObject *obj = data->obj;
741 char *start = (char*)obj;
742 mword desc = sgen_obj_get_descriptor_safe (obj);
744 #include "sgen/sgen-scan-object.h"
747 // A non-bridged object needs a single color describing the current merge array.
748 static ColorData*
749 reduce_color (void)
751 ColorData *color = NULL;
752 int size = dyn_array_ptr_size (&color_merge_array);
754 // Merge array is empty-- this SCC points to no bridged colors.
755 // This SCC can be ignored completely.
756 if (size == 0)
757 color = NULL;
759 // Merge array has one item-- this SCC points to a single bridged color.
760 // This SCC can be forwarded to the pointed-to color.
761 else if (size == 1) {
762 color = (ColorData *)dyn_array_ptr_get (&color_merge_array, 0);
764 // This SCC gets to talk to the color allocator.
765 } else
766 color = new_color (FALSE);
768 return color;
771 static void
772 create_scc (ScanData *data)
774 int i;
775 gboolean found = FALSE;
776 gboolean found_bridge = FALSE;
777 ColorData *color_data = NULL;
779 for (i = dyn_array_ptr_size (&loop_stack) - 1; i >= 0; --i) {
780 ScanData *other = (ScanData *)dyn_array_ptr_get (&loop_stack, i);
781 found_bridge |= other->is_bridge;
782 if (found_bridge || other == data)
783 break;
786 if (found_bridge) {
787 color_data = new_color (TRUE);
788 ++num_colors_with_bridges;
789 } else {
790 color_data = reduce_color ();
792 #if DUMP_GRAPH
793 printf ("|SCC %p rooted in %s (%p) has bridge %d\n", color_data, safe_name_bridge (data->obj), data->obj, found_bridge);
794 printf ("\tloop stack: ");
795 for (int i = 0; i < dyn_array_ptr_size (&loop_stack); ++i) {
796 ScanData *other = dyn_array_ptr_get (&loop_stack, i);
797 printf ("(%d/%d)", other->index, other->low_index);
799 printf ("\n");
800 #endif
802 while (dyn_array_ptr_size (&loop_stack) > 0) {
803 ScanData *other = (ScanData *)dyn_array_ptr_pop (&loop_stack);
805 #if DUMP_GRAPH
806 printf ("\tmember %s (%p) index %d low-index %d color %p state %d\n", safe_name_bridge (other->obj), other->obj, other->index, other->low_index, other->color, other->state);
807 #endif
809 other->color = color_data;
810 switch (other->state) {
811 case FINISHED_ON_STACK:
812 other->state = FINISHED_OFF_STACK;
813 break;
814 case FINISHED_OFF_STACK:
815 break;
816 default:
817 g_error ("Invalid state when building SCC %d", other->state);
820 if (other->is_bridge)
821 dyn_array_ptr_add (&color_data->bridges, other->obj);
823 // Maybe we should make sure we are not adding duplicates here. It is not really a problem
824 // since we will get rid of duplicates before submitting the SCCs to the client in gather_xrefs
825 if (color_data)
826 add_other_colors (color_data, &other->xrefs);
827 dyn_array_ptr_uninit (&other->xrefs);
829 if (other == data) {
830 found = TRUE;
831 break;
834 g_assert (found);
836 #if DUMP_GRAPH
837 printf ("\tpoints-to-colors: ");
838 for (int i = 0; i < dyn_array_ptr_size (&color_data->other_colors); i++)
839 printf ("%p ", dyn_array_ptr_get (&color_data->other_colors, i));
840 printf ("\n");
841 #endif
844 static void
845 dfs (void)
847 g_assert (dyn_array_ptr_size (&scan_stack) == 1);
848 g_assert (dyn_array_ptr_size (&loop_stack) == 0);
850 color_merge_array_empty ();
852 while (dyn_array_ptr_size (&scan_stack) > 0) {
853 ScanData *data = (ScanData *)dyn_array_ptr_pop (&scan_stack);
856 * Ignore finished objects on stack, they happen due to loops. For example:
857 * A -> C
858 * A -> B
859 * B -> C
860 * C -> A
862 * We start scanning from A and push C before B. So, after the first iteration, the scan stack will have: A C B.
863 * We then visit B, which will find C in its initial state and push again.
864 * Finally after finish with C and B, the stack will be left with "A C" and at this point C should be ignored.
866 * The above explains FINISHED_ON_STACK, to explain FINISHED_OFF_STACK, consider if the root was D, which pointed
867 * to A and C. A is processed first, leaving C on stack after that in the mentioned state.
869 if (data->state == FINISHED_ON_STACK || data->state == FINISHED_OFF_STACK)
870 continue;
872 if (data->state == INITIAL) {
873 g_assert (data->index == -1);
874 g_assert (data->low_index == -1);
876 data->state = SCANNED;
877 data->low_index = data->index = object_index++;
878 dyn_array_ptr_push (&scan_stack, data);
879 dyn_array_ptr_push (&loop_stack, data);
881 /*push all refs */
882 push_all (data);
883 } else {
884 g_assert (data->state == SCANNED);
885 data->state = FINISHED_ON_STACK;
887 #if DUMP_GRAPH
888 printf ("-finishing %s (%p) index %d low-index %d color %p\n", safe_name_bridge (data->obj), data->obj, data->index, data->low_index, data->color);
889 #endif
891 /* Compute low index */
892 compute_low (data);
894 #if DUMP_GRAPH
895 printf ("-finished %s (%p) index %d low-index %d color %p\n", safe_name_bridge (data->obj), data->obj, data->index, data->low_index, data->color);
896 #endif
897 //SCC root
898 if (data->index == data->low_index) {
899 create_scc (data);
900 } else {
901 // We need to clear colo_merge_array from all xrefs. We flush them to the current color
902 // and will add them to the scc when we reach the root of the scc.
903 g_assert (dyn_array_ptr_size (&data->xrefs) == 0);
904 dyn_array_ptr_set_all (&data->xrefs, &color_merge_array);
906 // We populated color_merge_array while scanning the object with each neighbor color. Clear it now
907 for (int i = 0; i < dyn_array_ptr_size (&color_merge_array); i++) {
908 ColorData *cd = (ColorData *)dyn_array_ptr_get (&color_merge_array, i);
909 g_assert (cd->visited);
910 cd->visited = FALSE;
912 color_merge_array_empty ();
917 static void
918 register_finalized_object (GCObject *obj)
920 g_assert (sgen_need_bridge_processing ());
921 dyn_array_ptr_push (&registered_bridges, obj);
924 static void
925 reset_data (void)
927 dyn_array_ptr_empty (&registered_bridges);
930 static void
931 cleanup (void)
933 dyn_array_ptr_empty (&scan_stack);
934 dyn_array_ptr_empty (&loop_stack);
935 dyn_array_ptr_empty (&registered_bridges);
936 free_object_buckets ();
937 free_color_buckets ();
938 reset_cache ();
939 object_index = 0;
940 num_colors_with_bridges = 0;
943 #ifdef DUMP_GRAPH
944 static void
945 dump_color_table (const char *why, gboolean do_index)
947 ColorBucket *cur;
948 int i = 0, j;
949 printf ("colors%s:\n", why);
951 for (cur = root_color_bucket; cur; cur = cur->next, ++i) {
952 ColorData *cd;
953 for (cd = &cur->data [0]; cd < cur->next_data; ++cd) {
954 if (do_index)
955 printf ("\t%d(%d):", i, cd->api_index);
956 else
957 printf ("\t%d: ", i);
958 for (j = 0; j < dyn_array_ptr_size (&cd->other_colors); ++j) {
959 printf ("%p ", dyn_array_ptr_get (&cd->other_colors, j));
961 if (dyn_array_ptr_size (&cd->bridges)) {
962 printf (" bridges: ");
963 for (j = 0; j < dyn_array_ptr_size (&cd->bridges); ++j) {
964 GCObject *obj = dyn_array_ptr_get (&cd->bridges, j);
965 ScanData *data = find_or_create_data (obj);
966 printf ("%d ", data->index);
969 printf ("\n");
974 #endif
976 static gint64
977 step_timer (gint64 *timer)
979 gint64 curtime, diff;
981 SGEN_TV_GETTIME (curtime);
982 diff = SGEN_TV_ELAPSED (*timer, curtime);
983 *timer = curtime;
984 return diff;
986 static void
987 processing_stw_step (void)
989 int i;
990 int bridge_count;
991 gint64 curtime;
993 if (!dyn_array_ptr_size (&registered_bridges))
994 return;
996 #if defined (DUMP_GRAPH)
997 printf ("-----------------\n");
998 #endif
1000 SGEN_TV_GETTIME (curtime);
1002 object_alloc_init ();
1003 color_alloc_init ();
1005 bridge_count = dyn_array_ptr_size (&registered_bridges);
1006 for (i = 0; i < bridge_count ; ++i)
1007 register_bridge_object ((GCObject *)dyn_array_ptr_get (&registered_bridges, i));
1009 setup_time = step_timer (&curtime);
1011 for (i = 0; i < bridge_count; ++i) {
1012 ScanData *sd = find_data ((GCObject *)dyn_array_ptr_get (&registered_bridges, i));
1013 if (sd->state == INITIAL) {
1014 dyn_array_ptr_push (&scan_stack, sd);
1015 dfs ();
1016 } else {
1017 g_assert (sd->state == FINISHED_OFF_STACK);
1021 tarjan_time = step_timer (&curtime);
1023 #if defined (DUMP_GRAPH)
1024 printf ("----summary----\n");
1025 printf ("bridges:\n");
1026 for (int i = 0; i < bridge_count; ++i) {
1027 ScanData *sd = find_data (dyn_array_ptr_get (&registered_bridges, i));
1028 printf ("\t%s (%p) index %d color %p\n", safe_name_bridge (sd->obj), sd->obj, sd->index, sd->color);
1031 dump_color_table (" after tarjan", FALSE);
1032 #endif
1034 clear_after_processing ();
1038 static void
1039 gather_xrefs (ColorData *color)
1041 int i;
1042 for (i = 0; i < dyn_array_ptr_size (&color->other_colors); ++i) {
1043 ColorData *src = (ColorData *)dyn_array_ptr_get (&color->other_colors, i);
1044 if (src->visited)
1045 continue;
1046 src->visited = TRUE;
1047 if (color_visible_to_client (src))
1048 dyn_array_ptr_add (&color_merge_array, src);
1049 else
1050 gather_xrefs (src);
1054 static void
1055 reset_xrefs (ColorData *color)
1057 int i;
1058 for (i = 0; i < dyn_array_ptr_size (&color->other_colors); ++i) {
1059 ColorData *src = (ColorData *)dyn_array_ptr_get (&color->other_colors, i);
1060 if (!src->visited)
1061 continue;
1062 src->visited = FALSE;
1063 if (!color_visible_to_client (src))
1064 reset_xrefs (src);
1068 static void
1069 processing_build_callback_data (int generation)
1071 int j;
1072 gint64 curtime;
1073 ColorBucket *cur;
1075 g_assert (bridge_processor->num_sccs == 0 && bridge_processor->num_xrefs == 0);
1076 g_assert (!bridge_processor->api_sccs && !bridge_processor->api_xrefs);
1078 if (!dyn_array_ptr_size (&registered_bridges))
1079 return;
1081 SGEN_TV_GETTIME (curtime);
1083 /*create API objects */
1085 #if defined (DUMP_GRAPH)
1086 printf ("***** API *****\n");
1087 printf ("number of SCCs %d\n", num_colors_with_bridges);
1088 #endif
1090 // Count the number of SCCs visible to the client
1091 num_sccs = 0;
1092 for (cur = root_color_bucket; cur; cur = cur->next) {
1093 ColorData *cd;
1094 for (cd = &cur->data [0]; cd < cur->next_data; ++cd) {
1095 if (color_visible_to_client (cd))
1096 num_sccs++;
1100 /* This is a straightforward translation from colors to the bridge callback format. */
1101 MonoGCBridgeSCC **api_sccs = (MonoGCBridgeSCC **)sgen_alloc_internal_dynamic (sizeof (MonoGCBridgeSCC*) * num_sccs, INTERNAL_MEM_BRIDGE_DATA, TRUE);
1102 int api_index = 0;
1103 xref_count = 0;
1105 // Convert visible SCCs, along with their bridged object list, to MonoGCBridgeSCCs in the client's SCC list
1106 for (cur = root_color_bucket; cur; cur = cur->next) {
1107 ColorData *cd;
1108 for (cd = &cur->data [0]; cd < cur->next_data; ++cd) {
1109 int bridges = dyn_array_ptr_size (&cd->bridges);
1110 if (!(bridges || bridgeless_color_is_heavy (cd)))
1111 continue;
1113 api_sccs [api_index] = (MonoGCBridgeSCC *)sgen_alloc_internal_dynamic (sizeof (MonoGCBridgeSCC) + sizeof (MonoObject*) * bridges, INTERNAL_MEM_BRIDGE_DATA, TRUE);
1114 api_sccs [api_index]->is_alive = FALSE;
1115 api_sccs [api_index]->num_objs = bridges;
1117 cd->api_index = api_index;
1119 for (j = 0; j < bridges; ++j)
1120 api_sccs [api_index]->objs [j] = (MonoObject *)dyn_array_ptr_get (&cd->bridges, j);
1122 g_assert(api_index < API_INDEX_MAX);
1123 api_index++;
1127 scc_setup_time = step_timer (&curtime);
1129 // Eliminate non-visible SCCs from the SCC list and redistribute xrefs
1130 for (cur = root_color_bucket; cur; cur = cur->next) {
1131 ColorData *cd;
1132 for (cd = &cur->data [0]; cd < cur->next_data; ++cd) {
1133 if (!color_visible_to_client (cd))
1134 continue;
1136 color_merge_array_empty ();
1137 gather_xrefs (cd);
1138 reset_xrefs (cd);
1139 dyn_array_ptr_set_all (&cd->other_colors, &color_merge_array);
1140 xref_count += dyn_array_ptr_size (&cd->other_colors);
1144 gather_xref_time = step_timer (&curtime);
1146 #if defined (DUMP_GRAPH)
1147 printf ("TOTAL XREFS %d\n", xref_count);
1148 dump_color_table (" after xref pass", TRUE);
1149 #endif
1151 // Write out xrefs array
1152 MonoGCBridgeXRef *api_xrefs = (MonoGCBridgeXRef *)sgen_alloc_internal_dynamic (sizeof (MonoGCBridgeXRef) * xref_count, INTERNAL_MEM_BRIDGE_DATA, TRUE);
1153 int xref_index = 0;
1154 for (cur = root_color_bucket; cur; cur = cur->next) {
1155 ColorData *src;
1156 for (src = &cur->data [0]; src < cur->next_data; ++src) {
1157 if (!color_visible_to_client (src))
1158 continue;
1160 for (j = 0; j < dyn_array_ptr_size (&src->other_colors); ++j) {
1161 ColorData *dest = (ColorData *)dyn_array_ptr_get (&src->other_colors, j);
1162 g_assert (color_visible_to_client (dest)); /* Supposedly we already eliminated all xrefs to non-visible objects. */
1164 api_xrefs [xref_index].src_scc_index = src->api_index;
1165 api_xrefs [xref_index].dst_scc_index = dest->api_index;
1167 ++xref_index;
1172 g_assertf (xref_count == xref_index, "xref_count is %d but we added %d xrefs", xref_count, xref_index);
1173 xref_setup_time = step_timer (&curtime);
1175 #if defined (DUMP_GRAPH)
1176 printf ("---xrefs:\n");
1177 for (int i = 0; i < xref_count; ++i)
1178 printf ("\t%d -> %d\n", api_xrefs [i].src_scc_index, api_xrefs [i].dst_scc_index);
1179 #endif
1181 //FIXME move half of the cleanup to before the bridge callback?
1182 bridge_processor->num_sccs = num_sccs;
1183 bridge_processor->api_sccs = api_sccs;
1184 bridge_processor->num_xrefs = xref_count;
1185 bridge_processor->api_xrefs = api_xrefs;
1188 static void
1189 processing_after_callback (int generation)
1191 gint64 curtime;
1192 int bridge_count = dyn_array_ptr_size (&registered_bridges);
1193 int object_count = object_data_count;
1194 int color_count = color_data_count;
1195 int colors_with_bridges_count = num_colors_with_bridges;
1197 SGEN_TV_GETTIME (curtime);
1199 /* cleanup */
1200 cleanup ();
1202 cleanup_time = step_timer (&curtime);
1204 mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_GC, "GC_TAR_BRIDGE bridges %d objects %d opaque %d colors %d colors-bridged %d colors-visible %d xref %d cache-hit %d cache-%s %d cache-miss %d setup %.2fms tarjan %.2fms scc-setup %.2fms gather-xref %.2fms xref-setup %.2fms cleanup %.2fms",
1205 bridge_count, object_count, ignored_objects,
1206 color_count, colors_with_bridges_count, num_sccs, xref_count,
1207 cache_hits, (scc_precise_merge ? "abstain" : "semihit"), cache_semihits, cache_misses,
1208 setup_time / 10000.0f,
1209 tarjan_time / 10000.0f,
1210 scc_setup_time / 10000.0f,
1211 gather_xref_time / 10000.0f,
1212 xref_setup_time / 10000.0f,
1213 cleanup_time / 10000.0f);
1215 cache_hits = cache_semihits = cache_misses = 0;
1216 ignored_objects = 0;
1219 static void
1220 describe_pointer (GCObject *obj)
1222 // HashEntry *entry;
1223 int i;
1225 for (i = 0; i < dyn_array_ptr_size (&registered_bridges); ++i) {
1226 if (obj == dyn_array_ptr_get (&registered_bridges, i)) {
1227 printf ("Pointer is a registered bridge object.\n");
1228 break;
1232 // entry = sgen_hash_table_lookup (&hash_table, obj);
1233 // if (!entry)
1234 // return;
1236 // printf ("Bridge hash table entry %p:\n", entry);
1237 // printf (" is bridge: %d\n", (int)entry->is_bridge);
1238 // printf (" is visited: %d\n", (int)entry->is_visited);
1241 static void
1242 set_config (const SgenBridgeProcessorConfig *config)
1244 if (config->scc_precise_merge) {
1245 hash_perturb = 0;
1246 scc_precise_merge = TRUE;
1248 if (config->disable_non_bridge_scc)
1249 disable_non_bridge_scc = TRUE;
1252 void
1253 sgen_tarjan_bridge_init (SgenBridgeProcessor *collector)
1255 collector->reset_data = reset_data;
1256 collector->processing_stw_step = processing_stw_step;
1257 collector->processing_build_callback_data = processing_build_callback_data;
1258 collector->processing_after_callback = processing_after_callback;
1259 collector->class_kind = class_kind;
1260 collector->register_finalized_object = register_finalized_object;
1261 collector->describe_pointer = describe_pointer;
1262 collector->set_config = set_config;
1264 sgen_register_fixed_internal_mem_type (INTERNAL_MEM_TARJAN_OBJ_BUCKET, BUCKET_SIZE);
1265 g_assert (sizeof (ObjectBucket) <= BUCKET_SIZE);
1266 g_assert (sizeof (ColorBucket) <= BUCKET_SIZE);
1267 g_assert (API_INDEX_BITS + INCOMING_COLORS_BITS <= 31);
1268 bridge_processor = collector;
1271 #else
1273 #include <mono/utils/mono-compiler.h>
1275 MONO_EMPTY_SOURCE_FILE (sgen_tarjan_bridge);
1277 #endif