[sgen] DTrace probes for finalisers and weak links.
[mono.git] / mono / metadata / sgen-fin-weak-hash.c
1 /*
2  * sgen-fin-weak-hash.c: Finalizers and weak links.
3  *
4  * Author:
5  *      Paolo Molaro (lupus@ximian.com)
6  *  Rodrigo Kumpera (kumpera@gmail.com)
7  *
8  * Copyright 2005-2011 Novell, Inc (http://www.novell.com)
9  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
10  * Copyright 2011 Xamarin, Inc.
11  * Copyright (C) 2012 Xamarin Inc
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Library General Public
15  * License 2.0 as published by the Free Software Foundation;
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License 2.0 along with this library; if not, write to the Free
24  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25  */
26
27 #include "config.h"
28 #ifdef HAVE_SGEN_GC
29
30 #include "metadata/sgen-gc.h"
31 #include "metadata/sgen-gray.h"
32 #include "utils/dtrace.h"
33
34 #define ptr_in_nursery sgen_ptr_in_nursery
35
36 typedef SgenGrayQueue GrayQueue;
37
38 int num_ready_finalizers = 0;
39 static int no_finalize = 0;
40
41 #define DISLINK_OBJECT(l)       (REVEAL_POINTER (*(void**)(l)))
42 #define DISLINK_TRACK(l)        ((~(gulong)(*(void**)(l))) & 1)
43
44 /*
45  * The finalizable hash has the object as the key, the 
46  * disappearing_link hash, has the link address as key.
47  *
48  * Copyright 2011 Xamarin Inc.
49  */
50
51 #define TAG_MASK ((mword)0x1)
52
53 static inline MonoObject*
54 tagged_object_get_object (MonoObject *object)
55 {
56         return (MonoObject*)(((mword)object) & ~TAG_MASK);
57 }
58
59 static inline int
60 tagged_object_get_tag (MonoObject *object)
61 {
62         return ((mword)object) & TAG_MASK;
63 }
64
65 static inline MonoObject*
66 tagged_object_apply (void *object, int tag_bits)
67 {
68        return (MonoObject*)((mword)object | (mword)tag_bits);
69 }
70
71 static int
72 tagged_object_hash (MonoObject *o)
73 {
74         return mono_object_hash (tagged_object_get_object (o));
75 }
76
77 static gboolean
78 tagged_object_equals (MonoObject *a, MonoObject *b)
79 {
80         return tagged_object_get_object (a) == tagged_object_get_object (b);
81 }
82
83 static SgenHashTable minor_finalizable_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_FIN_TABLE, INTERNAL_MEM_FINALIZE_ENTRY, 0, (GHashFunc)tagged_object_hash, (GEqualFunc)tagged_object_equals);
84 static SgenHashTable major_finalizable_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_FIN_TABLE, INTERNAL_MEM_FINALIZE_ENTRY, 0, (GHashFunc)tagged_object_hash, (GEqualFunc)tagged_object_equals);
85
86 static SgenHashTable*
87 get_finalize_entry_hash_table (int generation)
88 {
89         switch (generation) {
90         case GENERATION_NURSERY: return &minor_finalizable_hash;
91         case GENERATION_OLD: return &major_finalizable_hash;
92         default: g_assert_not_reached ();
93         }
94 }
95
96 #define BRIDGE_OBJECT_MARKED 0x1
97
98 /* LOCKING: requires that the GC lock is held */
99 void
100 sgen_mark_bridge_object (MonoObject *obj)
101 {
102         SgenHashTable *hash_table = get_finalize_entry_hash_table (ptr_in_nursery (obj) ? GENERATION_NURSERY : GENERATION_OLD);
103
104         sgen_hash_table_set_key (hash_table, obj, tagged_object_apply (obj, BRIDGE_OBJECT_MARKED));
105 }
106
107 /* LOCKING: requires that the GC lock is held */
108 void
109 sgen_collect_bridge_objects (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
110 {
111         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
112         MonoObject *object;
113         gpointer dummy;
114         char *copy;
115
116         if (no_finalize)
117                 return;
118
119         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
120                 int tag = tagged_object_get_tag (object);
121                 object = tagged_object_get_object (object);
122
123                 /* Bridge code told us to ignore this one */
124                 if (tag == BRIDGE_OBJECT_MARKED)
125                         continue;
126
127                 /* Object is a bridge object and major heap says it's dead  */
128                 if (!((char*)object >= start && (char*)object < end && !major_collector.is_object_live ((char*)object)))
129                         continue;
130
131                 /* Nursery says the object is dead. */
132                 if (!sgen_gc_is_object_ready_for_finalization (object))
133                         continue;
134
135                 if (!sgen_is_bridge_object (object))
136                         continue;
137
138                 copy = (char*)object;
139                 copy_func ((void**)&copy, queue);
140
141                 sgen_bridge_register_finalized_object ((MonoObject*)copy);
142                 
143                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
144                         /* remove from the list */
145                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
146
147                         /* insert it into the major hash */
148                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
149
150                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
151
152                         continue;
153                 } else {
154                         /* update pointer */
155                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
156                         SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
157                 }
158         } SGEN_HASH_TABLE_FOREACH_END;
159 }
160
161
162 /* LOCKING: requires that the GC lock is held */
163 void
164 sgen_finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
165 {
166         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
167         MonoObject *object;
168         gpointer dummy;
169
170         if (no_finalize)
171                 return;
172         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
173                 int tag = tagged_object_get_tag (object);
174                 object = tagged_object_get_object (object);
175                 if ((char*)object >= start && (char*)object < end && !major_collector.is_object_live ((char*)object)) {
176                         gboolean is_fin_ready = sgen_gc_is_object_ready_for_finalization (object);
177                         MonoObject *copy = object;
178                         copy_func ((void**)&copy, queue);
179                         if (is_fin_ready) {
180                                 /* remove and put in fin_ready_list */
181                                 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
182                                 num_ready_finalizers++;
183                                 sgen_queue_finalization_entry (copy);
184                                 /* Make it survive */
185                                 SGEN_LOG (5, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)", copy, sgen_safe_name (copy), object, num_ready_finalizers, sgen_hash_table_num_entries (hash_table));
186                                 continue;
187                         } else {
188                                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
189                                         /* remove from the list */
190                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
191
192                                         /* insert it into the major hash */
193                                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
194
195                                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
196
197                                         continue;
198                                 } else {
199                                         /* update pointer */
200                                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
201                                         SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
202                                 }
203                         }
204                 }
205         } SGEN_HASH_TABLE_FOREACH_END;
206 }
207
208 /* LOCKING: requires that the GC lock is held */
209 static void
210 register_for_finalization (MonoObject *obj, void *user_data, int generation)
211 {
212         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
213
214         if (no_finalize)
215                 return;
216
217         g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
218
219         if (user_data) {
220                 if (sgen_hash_table_replace (hash_table, obj, NULL, NULL))
221                         SGEN_LOG (5, "Added finalizer for object: %p (%s) (%d) to %s table", obj, obj->vtable->klass->name, hash_table->num_entries, sgen_generation_name (generation));
222         } else {
223                 if (sgen_hash_table_remove (hash_table, obj, NULL))
224                         SGEN_LOG (5, "Removed finalizer for object: %p (%s) (%d)", obj, obj->vtable->klass->name, hash_table->num_entries);
225         }
226 }
227
228 #define STAGE_ENTRY_FREE        0
229 #define STAGE_ENTRY_BUSY        1
230 #define STAGE_ENTRY_USED        2
231
232 typedef struct {
233         gint32 state;
234         MonoObject *obj;
235         void *user_data;
236 } StageEntry;
237
238 #define NUM_FIN_STAGE_ENTRIES   1024
239
240 static volatile gint32 next_fin_stage_entry = 0;
241 static StageEntry fin_stage_entries [NUM_FIN_STAGE_ENTRIES];
242
243 /* LOCKING: requires that the GC lock is held */
244 static void
245 process_stage_entries (int num_entries, volatile gint32 *next_entry, StageEntry *entries, void (*process_func) (MonoObject*, void*))
246 {
247         int i;
248         int num_registered = 0;
249         int num_busy = 0;
250
251         for (i = 0; i < num_entries; ++i) {
252                 gint32 state = entries [i].state;
253
254                 if (state == STAGE_ENTRY_BUSY)
255                         ++num_busy;
256
257                 if (state != STAGE_ENTRY_USED ||
258                                 InterlockedCompareExchange (&entries [i].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_USED) != STAGE_ENTRY_USED) {
259                         continue;
260                 }
261
262                 process_func (entries [i].obj, entries [i].user_data);
263
264                 entries [i].obj = NULL;
265                 entries [i].user_data = NULL;
266
267                 mono_memory_write_barrier ();
268
269                 entries [i].state = STAGE_ENTRY_FREE;
270
271                 ++num_registered;
272         }
273
274         *next_entry = 0;
275
276         /* g_print ("stage busy %d reg %d\n", num_busy, num_registered); */
277 }
278
279 static gboolean
280 add_stage_entry (int num_entries, volatile gint32 *next_entry, StageEntry *entries, MonoObject *obj, void *user_data)
281 {
282         gint32 index;
283
284         do {
285                 do {
286                         index = *next_entry;
287                         if (index >= num_entries)
288                                 return FALSE;
289                 } while (InterlockedCompareExchange (next_entry, index + 1, index) != index);
290
291                 /*
292                  * We don't need a write barrier here.  *next_entry is just a
293                  * help for finding an index, its value is irrelevant for
294                  * correctness.
295                  */
296         } while (entries [index].state != STAGE_ENTRY_FREE ||
297                         InterlockedCompareExchange (&entries [index].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_FREE) != STAGE_ENTRY_FREE);
298
299         entries [index].obj = obj;
300         entries [index].user_data = user_data;
301
302         mono_memory_write_barrier ();
303
304         entries [index].state = STAGE_ENTRY_USED;
305
306         return TRUE;
307 }
308
309 /* LOCKING: requires that the GC lock is held */
310 static void
311 process_fin_stage_entry (MonoObject *obj, void *user_data)
312 {
313         if (ptr_in_nursery (obj))
314                 register_for_finalization (obj, user_data, GENERATION_NURSERY);
315         else
316                 register_for_finalization (obj, user_data, GENERATION_OLD);
317 }
318
319 /* LOCKING: requires that the GC lock is held */
320 void
321 sgen_process_fin_stage_entries (void)
322 {
323         process_stage_entries (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, process_fin_stage_entry);
324 }
325
326 void
327 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
328 {
329         while (!add_stage_entry (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, obj, user_data)) {
330                 LOCK_GC;
331                 sgen_process_fin_stage_entries ();
332                 UNLOCK_GC;
333         }
334 }
335
336 /* LOCKING: requires that the GC lock is held */
337 static int
338 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
339         SgenHashTable *hash_table)
340 {
341         MonoObject *object;
342         gpointer dummy;
343         int count;
344
345         if (no_finalize || !out_size || !out_array)
346                 return 0;
347         count = 0;
348         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
349                 object = tagged_object_get_object (object);
350
351                 if (mono_object_domain (object) == domain) {
352                         /* remove and put in out_array */
353                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
354                         out_array [count ++] = object;
355                         SGEN_LOG (5, "Collecting object for finalization: %p (%s) (%d/%d)", object, sgen_safe_name (object), num_ready_finalizers, sgen_hash_table_num_entries (hash_table));
356                         if (count == out_size)
357                                 return count;
358                         continue;
359                 }
360         } SGEN_HASH_TABLE_FOREACH_END;
361         return count;
362 }
363
364 /**
365  * mono_gc_finalizers_for_domain:
366  * @domain: the unloading appdomain
367  * @out_array: output array
368  * @out_size: size of output array
369  *
370  * Store inside @out_array up to @out_size objects that belong to the unloading
371  * appdomain @domain. Returns the number of stored items. Can be called repeteadly
372  * until it returns 0.
373  * The items are removed from the finalizer data structure, so the caller is supposed
374  * to finalize them.
375  * @out_array should be on the stack to allow the GC to know the objects are still alive.
376  */
377 int
378 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
379 {
380         int result;
381
382         LOCK_GC;
383         sgen_process_fin_stage_entries ();
384         result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
385         if (result < out_size) {
386                 result += finalizers_for_domain (domain, out_array + result, out_size - result,
387                         &major_finalizable_hash);
388         }
389         UNLOCK_GC;
390
391         return result;
392 }
393
394 static SgenHashTable minor_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
395 static SgenHashTable major_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
396
397 static SgenHashTable*
398 get_dislink_hash_table (int generation)
399 {
400         switch (generation) {
401         case GENERATION_NURSERY: return &minor_disappearing_link_hash;
402         case GENERATION_OLD: return &major_disappearing_link_hash;
403         default: g_assert_not_reached ();
404         }
405 }
406
407 /* LOCKING: assumes the GC lock is held */
408 static void
409 add_or_remove_disappearing_link (MonoObject *obj, void **link, int generation)
410 {
411         SgenHashTable *hash_table = get_dislink_hash_table (generation);
412
413         if (!obj) {
414                 if (sgen_hash_table_remove (hash_table, link, NULL)) {
415                         SGEN_LOG (5, "Removed dislink %p (%d) from %s table",
416                                         link, hash_table->num_entries, sgen_generation_name (generation));
417                 }
418                 return;
419         }
420
421         sgen_hash_table_replace (hash_table, link, NULL, NULL);
422         SGEN_LOG (5, "Added dislink for object: %p (%s) at %p to %s table",
423                         obj, obj->vtable->klass->name, link, sgen_generation_name (generation));
424 }
425
426 /* LOCKING: requires that the GC lock is held */
427 void
428 sgen_null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, gboolean before_finalization, GrayQueue *queue)
429 {
430         void **link;
431         gpointer dummy;
432         SgenHashTable *hash = get_dislink_hash_table (generation);
433
434         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
435                 char *object;
436                 gboolean track = DISLINK_TRACK (link);
437
438                 /*
439                  * Tracked references are processed after
440                  * finalization handling whereas standard weak
441                  * references are processed before.  If an
442                  * object is still not marked after finalization
443                  * handling it means that it either doesn't have
444                  * a finalizer or the finalizer has already run,
445                  * so we must null a tracking reference.
446                  */
447                 if (track != before_finalization) {
448                         object = DISLINK_OBJECT (link);
449
450                         if (object >= start && object < end && !major_collector.is_object_live (object)) {
451                                 if (sgen_gc_is_object_ready_for_finalization (object)) {
452                                         *link = NULL;
453                                         SGEN_LOG (5, "Dislink nullified at %p to GCed object %p", link, object);
454                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
455                                         continue;
456                                 } else {
457                                         char *copy = object;
458                                         copy_func ((void**)&copy, queue);
459
460                                         /* Update pointer if it's moved.  If the object
461                                          * has been moved out of the nursery, we need to
462                                          * remove the link from the minor hash table to
463                                          * the major one.
464                                          *
465                                          * FIXME: what if an object is moved earlier?
466                                          */
467
468                                         if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
469                                                 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
470
471                                                 g_assert (copy);
472                                                 *link = HIDE_POINTER (copy, track);
473                                                 add_or_remove_disappearing_link ((MonoObject*)copy, link, GENERATION_OLD);
474
475                                                 SGEN_LOG (5, "Upgraded dislink at %p to major because object %p moved to %p", link, object, copy);
476
477                                                 continue;
478                                         } else {
479                                                 *link = HIDE_POINTER (copy, track);
480                                                 SGEN_LOG (5, "Updated dislink at %p to %p", link, DISLINK_OBJECT (link));
481                                         }
482                                 }
483                         }
484                 }
485         } SGEN_HASH_TABLE_FOREACH_END;
486 }
487
488 /* LOCKING: requires that the GC lock is held */
489 void
490 sgen_null_links_for_domain (MonoDomain *domain, int generation)
491 {
492         void **link;
493         gpointer dummy;
494         SgenHashTable *hash = get_dislink_hash_table (generation);
495         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
496                 char *object = DISLINK_OBJECT (link);
497                 if (object && !((MonoObject*)object)->vtable) {
498                         gboolean free = TRUE;
499
500                         if (*link) {
501                                 *link = NULL;
502                                 free = FALSE;
503                                 /*
504                                  * This can happen if finalizers are not ran, i.e. Environment.Exit ()
505                                  * is called from finalizer like in finalizer-abort.cs.
506                                  */
507                                 SGEN_LOG (5, "Disappearing link %p not freed", link);
508                         }
509
510                         SGEN_HASH_TABLE_FOREACH_REMOVE (free);
511
512                         continue;
513                 }
514         } SGEN_HASH_TABLE_FOREACH_END;
515 }
516
517 /* LOCKING: requires that the GC lock is held */
518 void
519 sgen_null_links_with_predicate (int generation, WeakLinkAlivePredicateFunc predicate, void *data)
520 {
521         void **link;
522         gpointer dummy;
523         SgenHashTable *hash = get_dislink_hash_table (generation);
524         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
525                 char *object = DISLINK_OBJECT (link);
526                 mono_bool is_alive = predicate ((MonoObject*)object, data);
527
528                 if (!is_alive) {
529                         *link = NULL;
530                         SGEN_LOG (5, "Dislink nullified by predicate at %p to GCed object %p", link, object);
531                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
532                         continue;
533                 }
534         } SGEN_HASH_TABLE_FOREACH_END;
535 }
536
537 void
538 sgen_remove_finalizers_for_domain (MonoDomain *domain, int generation)
539 {
540         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
541         MonoObject *object;
542         gpointer dummy;
543
544         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
545                 object = tagged_object_get_object (object);
546
547                 if (mono_object_domain (object) == domain) {
548                         SGEN_LOG (5, "Unregistering finalizer for object: %p (%s)", object, sgen_safe_name (object));
549
550                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
551                         continue;
552                 }
553         } SGEN_HASH_TABLE_FOREACH_END;  
554 }
555
556 /* LOCKING: requires that the GC lock is held */
557 static void
558 process_dislink_stage_entry (MonoObject *obj, void *_link)
559 {
560         void **link = _link;
561
562         add_or_remove_disappearing_link (NULL, link, GENERATION_NURSERY);
563         add_or_remove_disappearing_link (NULL, link, GENERATION_OLD);
564         if (obj) {
565                 if (ptr_in_nursery (obj))
566                         add_or_remove_disappearing_link (obj, link, GENERATION_NURSERY);
567                 else
568                         add_or_remove_disappearing_link (obj, link, GENERATION_OLD);
569         }
570 }
571
572 #define NUM_DISLINK_STAGE_ENTRIES       1024
573
574 static volatile gint32 next_dislink_stage_entry = 0;
575 static StageEntry dislink_stage_entries [NUM_DISLINK_STAGE_ENTRIES];
576
577 /* LOCKING: requires that the GC lock is held */
578 void
579 sgen_process_dislink_stage_entries (void)
580 {
581         process_stage_entries (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, process_dislink_stage_entry);
582 }
583
584 void
585 sgen_register_disappearing_link (MonoObject *obj, void **link, gboolean track, gboolean in_gc)
586 {
587         if (MONO_GC_WEAK_UPDATE_ENABLED ()) {
588                 MonoVTable *vt = obj ? (MonoVTable*)SGEN_LOAD_VTABLE (obj) : NULL;
589                 MONO_GC_WEAK_UPDATE ((mword)link,
590                                 *link ? (mword)DISLINK_OBJECT (link) : (mword)0,
591                                 (mword)obj,
592                                 obj ? (mword)sgen_safe_object_get_size (obj) : (mword)0,
593                                 obj ? vt->klass->name_space : NULL,
594                                 obj ? vt->klass->name : NULL,
595                                 track ? 1 : 0);
596         }
597
598         if (obj)
599                 *link = HIDE_POINTER (obj, track);
600         else
601                 *link = NULL;
602
603 #if 1
604         if (in_gc) {
605                 process_dislink_stage_entry (obj, link);
606         } else {
607                 while (!add_stage_entry (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, obj, link)) {
608                         LOCK_GC;
609                         sgen_process_dislink_stage_entries ();
610                         UNLOCK_GC;
611                 }
612         }
613 #else
614         if (!in_gc)
615                 LOCK_GC;
616         process_dislink_stage_entry (obj, link);
617         if (!in_gc)
618                 UNLOCK_GC;
619 #endif
620 }
621
622 #endif /* HAVE_SGEN_GC */