b18a65afa82ddb7c436bcc3b69553e71cce526dd
[mono.git] / mono / sgen / sgen-gchandles.c
1 /*
2  * sgen-gchandles.c: SGen GC handles.
3  *
4  * Copyright (C) 2015 Xamarin Inc
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License 2.0 as published by the Free Software Foundation;
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License 2.0 along with this library; if not, write to the Free
17  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #include "config.h"
21 #ifdef HAVE_SGEN_GC
22
23 #include "mono/sgen/sgen-gc.h"
24 #include "mono/sgen/sgen-client.h"
25 #include "mono/utils/mono-membar.h"
26
27 #ifdef HEAVY_STATISTICS
28 static volatile guint64 stat_gc_handles_allocated = 0;
29 static volatile guint64 stat_gc_handles_max_allocated = 0;
30 #endif
31
32 #define BUCKETS (32 - MONO_GC_HANDLE_TYPE_SHIFT)
33 #define MIN_BUCKET_BITS (5)
34 #define MIN_BUCKET_SIZE (1 << MIN_BUCKET_BITS)
35
36 /*
37  * A table of GC handle data, implementing a simple lock-free bitmap allocator.
38  *
39  * 'entries' is an array of pointers to buckets of increasing size. The first
40  * bucket has size 'MIN_BUCKET_SIZE', and each bucket is twice the size of the
41  * previous, i.e.:
42  *
43  *           |-------|-- MIN_BUCKET_SIZE
44  *    [0] -> xxxxxxxx
45  *    [1] -> xxxxxxxxxxxxxxxx
46  *    [2] -> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
47  *    ...
48  *
49  * The size of the spine, 'BUCKETS', is chosen so that the maximum number of
50  * entries is no less than the maximum index value of a GC handle.
51  *
52  * Each entry in a bucket is a pointer with two tag bits: if
53  * 'GC_HANDLE_OCCUPIED' returns true for a slot, then the slot is occupied; if
54  * so, then 'GC_HANDLE_VALID' gives whether the entry refers to a valid (1) or
55  * NULL (0) object reference. If the reference is valid, then the pointer is an
56  * object pointer. If the reference is NULL, and 'GC_HANDLE_TYPE_IS_WEAK' is
57  * true for 'type', then the pointer is a metadata pointer--this allows us to
58  * retrieve the domain ID of an expired weak reference in Mono.
59  *
60  * Finally, 'slot_hint' denotes the position of the last allocation, so that the
61  * whole array needn't be searched on every allocation.
62  */
63
64 typedef struct {
65         volatile gpointer *volatile entries [BUCKETS];
66         volatile guint32 capacity;
67         volatile guint32 slot_hint;
68         volatile guint32 max_index;
69         guint8 type;
70 } HandleData;
71
72 static inline guint
73 bucket_size (guint index)
74 {
75         return 1 << (index + MIN_BUCKET_BITS);
76 }
77
78 /* Computes floor(log2(index + MIN_BUCKET_SIZE)) - 1, giving the index
79  * of the bucket containing a slot.
80  */
81 static inline guint
82 index_bucket (guint index)
83 {
84 #ifdef __GNUC__
85         return CHAR_BIT * sizeof (index) - __builtin_clz (index + MIN_BUCKET_SIZE) - 1 - MIN_BUCKET_BITS;
86 #else
87         guint count = 0;
88         index += MIN_BUCKET_SIZE;
89         while (index) {
90                 ++count;
91                 index >>= 1;
92         }
93         return count - 1 - MIN_BUCKET_BITS;
94 #endif
95 }
96
97 static inline void
98 bucketize (guint index, guint *bucket, guint *offset)
99 {
100         *bucket = index_bucket (index);
101         *offset = index - bucket_size (*bucket) + MIN_BUCKET_SIZE;
102 }
103
104 static void
105 protocol_gchandle_update (int handle_type, gpointer link, gpointer old_value, gpointer new_value)
106 {
107         gboolean old = MONO_GC_HANDLE_IS_OBJECT_POINTER (old_value);
108         gboolean new = MONO_GC_HANDLE_IS_OBJECT_POINTER (new_value);
109         gboolean track = handle_type == HANDLE_WEAK_TRACK;
110
111         if (!MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type))
112                 return;
113
114         if (!old && new)
115                 binary_protocol_dislink_add (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
116         else if (old && !new)
117                 binary_protocol_dislink_remove (link, track);
118         else if (old && new && old_value != new_value)
119                 binary_protocol_dislink_update (link, MONO_GC_REVEAL_POINTER (new_value, TRUE), track);
120 }
121
122 /* Returns the new value in the slot, or NULL if the CAS failed. */
123 static inline gpointer
124 try_set_slot (volatile gpointer *slot, GCObject *obj, gpointer old, GCHandleType type)
125 {
126         gpointer new;
127         if (obj)
128                 new = MONO_GC_HANDLE_OBJECT_POINTER (obj, GC_HANDLE_TYPE_IS_WEAK (type));
129         else
130                 new = MONO_GC_HANDLE_METADATA_POINTER (sgen_client_default_metadata (), GC_HANDLE_TYPE_IS_WEAK (type));
131         SGEN_ASSERT (0, new, "Why is the occupied bit not set?");
132         if (InterlockedCompareExchangePointer (slot, new, old) == old) {
133                 protocol_gchandle_update (type, (gpointer)slot, old, new);
134                 return new;
135         }
136         return NULL;
137 }
138
139 /* Try to claim a slot by setting its occupied bit. */
140 static inline gboolean
141 try_occupy_slot (HandleData *handles, guint bucket, guint offset, GCObject *obj, gboolean track)
142 {
143         volatile gpointer *link_addr = &(handles->entries [bucket] [offset]);
144         if (MONO_GC_HANDLE_OCCUPIED (*link_addr))
145                 return FALSE;
146         return try_set_slot (link_addr, obj, NULL, handles->type) != NULL;
147 }
148
149 #define EMPTY_HANDLE_DATA(t) \
150         (HandleData) { \
151                 .entries = { NULL }, \
152                 .capacity = 0, \
153                 .slot_hint = 0, \
154                 .max_index = 0, \
155                 .type = (t) \
156         }
157
158 /* weak and weak-track arrays will be allocated in malloc memory 
159  */
160 static HandleData gc_handles [] = {
161         EMPTY_HANDLE_DATA (HANDLE_WEAK),
162         EMPTY_HANDLE_DATA (HANDLE_WEAK_TRACK),
163         EMPTY_HANDLE_DATA (HANDLE_NORMAL),
164         EMPTY_HANDLE_DATA (HANDLE_PINNED)
165 };
166
167 static HandleData *
168 gc_handles_for_type (GCHandleType type)
169 {
170         g_assert (type < HANDLE_TYPE_MAX);
171         return &gc_handles [type];
172 }
173
174 /* This assumes that the world is stopped. */
175 void
176 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
177 {
178         HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
179         size_t bucket, offset;
180         const guint max_bucket = index_bucket (handles->capacity);
181         guint32 index = 0;
182         const guint32 max_index = handles->max_index;
183         for (bucket = 0; bucket < max_bucket; ++bucket) {
184                 volatile gpointer *entries = handles->entries [bucket];
185                 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
186                         volatile gpointer *entry;
187                         gpointer hidden, revealed;
188                         /* No need to iterate beyond the largest index ever allocated. */
189                         if (index > max_index)
190                                 return;
191                         entry = &entries [offset];
192                         hidden = *entry;
193                         revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
194                         if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
195                                 continue;
196                         mark_func ((MonoObject **)&revealed, gc_data);
197                         g_assert (revealed);
198                         *entry = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
199                 }
200         }
201 }
202
203 static guint
204 handle_data_find_unset (HandleData *handles, guint32 begin, guint32 end)
205 {
206         guint index;
207         gint delta = begin < end ? +1 : -1;
208         for (index = begin; index < end; index += delta) {
209                 guint bucket, offset;
210                 volatile gpointer *entries;
211                 bucketize (index, &bucket, &offset);
212                 entries = handles->entries [bucket];
213                 g_assert (entries);
214                 if (!MONO_GC_HANDLE_OCCUPIED (entries [offset]))
215                         return index;
216         }
217         return -1;
218 }
219
220 /* Adds a bucket if necessary and possible. */
221 static void
222 handle_data_grow (HandleData *handles, guint32 old_capacity)
223 {
224         const guint new_bucket = index_bucket (old_capacity);
225         const guint32 growth = bucket_size (new_bucket);
226         const guint32 new_capacity = old_capacity + growth;
227         gpointer *entries;
228         const size_t new_bucket_size = sizeof (**handles->entries) * growth;
229         if (handles->capacity >= new_capacity)
230                 return;
231         entries = g_malloc0 (new_bucket_size);
232         if (handles->type == HANDLE_PINNED)
233                 sgen_register_root ((char *)entries, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
234         if (InterlockedCompareExchangePointer ((volatile gpointer *)&handles->entries [new_bucket], entries, NULL) == NULL) {
235                 if (InterlockedCompareExchange ((volatile gint32 *)&handles->capacity, new_capacity, old_capacity) != old_capacity)
236                         g_assert_not_reached ();
237                 handles->slot_hint = old_capacity;
238                 mono_memory_write_barrier ();
239                 return;
240         }
241         /* Someone beat us to the allocation. */
242         if (handles->type == HANDLE_PINNED)
243                 sgen_deregister_root ((char *)entries);
244         g_free (entries);
245 }
246
247 static guint32
248 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
249 {
250         guint index;
251         guint32 res;
252         guint bucket, offset;
253         guint32 capacity;
254         guint32 slot_hint;
255         guint32 max_index;
256         if (!handles->capacity)
257                 handle_data_grow (handles, 0);
258 retry:
259         capacity = handles->capacity;
260         slot_hint = handles->slot_hint;
261         index = handle_data_find_unset (handles, slot_hint, capacity);
262         if (index == -1)
263                 index = handle_data_find_unset (handles, 0, slot_hint);
264         if (index == -1) {
265                 handle_data_grow (handles, capacity);
266                 goto retry;
267         }
268         handles->slot_hint = index;
269         bucketize (index, &bucket, &offset);
270         if (!try_occupy_slot (handles, bucket, offset, obj, track))
271                 goto retry;
272         /* If a GC happens shortly after a new bucket is allocated, the entire
273          * bucket could be scanned even though it's mostly empty. To avoid this, we
274          * track the maximum index seen so far, so that we can skip the empty slots.
275          */
276         do {
277                 max_index = handles->max_index;
278                 if (index <= max_index)
279                         break;
280         } while (!InterlockedCompareExchange ((volatile gint32 *)&handles->max_index, index, max_index));
281 #ifdef HEAVY_STATISTICS
282         InterlockedIncrement64 ((volatile gint64 *)&stat_gc_handles_allocated);
283         if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
284                 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
285 #endif
286         /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
287         mono_memory_write_barrier ();
288         res = MONO_GC_HANDLE (index, handles->type);
289         sgen_client_gchandle_created (handles->type, obj, res);
290         return res;
291 }
292
293 static gboolean
294 object_older_than (GCObject *object, int generation)
295 {
296         return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
297 }
298
299 /*
300  * Maps a function over all GC handles.
301  * This assumes that the world is stopped!
302  */
303 void
304 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, gpointer callback(gpointer, GCHandleType, int, gpointer), gpointer user)
305 {
306         HandleData *handle_data = gc_handles_for_type (handle_type);
307         size_t bucket, offset;
308         guint max_bucket = index_bucket (handle_data->capacity);
309         guint32 index = 0;
310         guint32 max_index = handle_data->max_index;
311         /* If a new bucket has been allocated, but the capacity has not yet been
312          * increased, nothing can yet have been allocated in the bucket because the
313          * world is stopped, so we shouldn't miss any handles during iteration.
314          */
315         for (bucket = 0; bucket < max_bucket; ++bucket) {
316                 volatile gpointer *entries = handle_data->entries [bucket];
317                 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
318                         gpointer hidden;
319                         gpointer result;
320                         /* Table must contain no garbage pointers. */
321                         gboolean occupied;
322                         /* No need to iterate beyond the largest index ever allocated. */
323                         if (index > max_index)
324                                         return;
325                         hidden = entries [offset];
326                         occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
327                         g_assert (hidden ? occupied : !occupied);
328                         if (!occupied)
329                                 continue;
330                         result = callback (hidden, handle_type, max_generation, user);
331                         if (result)
332                                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
333                         else
334                                 HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
335                         protocol_gchandle_update (handle_type, (gpointer)&entries [offset], hidden, result);
336                         entries [offset] = result;
337                 }
338         }
339 }
340
341 /**
342  * mono_gchandle_new:
343  * @obj: managed object to get a handle for
344  * @pinned: whether the object should be pinned
345  *
346  * This returns a handle that wraps the object, this is used to keep a
347  * reference to a managed object from the unmanaged world and preventing the
348  * object from being disposed.
349  * 
350  * If @pinned is false the address of the object can not be obtained, if it is
351  * true the address of the object can be obtained.  This will also pin the
352  * object so it will not be possible by a moving garbage collector to move the
353  * object. 
354  * 
355  * Returns: a handle that can be used to access the object from
356  * unmanaged code.
357  */
358 guint32
359 mono_gchandle_new (GCObject *obj, gboolean pinned)
360 {
361         return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
362 }
363
364 /**
365  * mono_gchandle_new_weakref:
366  * @obj: managed object to get a handle for
367  * @pinned: whether the object should be pinned
368  *
369  * This returns a weak handle that wraps the object, this is used to
370  * keep a reference to a managed object from the unmanaged world.
371  * Unlike the mono_gchandle_new the object can be reclaimed by the
372  * garbage collector.  In this case the value of the GCHandle will be
373  * set to zero.
374  * 
375  * If @pinned is false the address of the object can not be obtained, if it is
376  * true the address of the object can be obtained.  This will also pin the
377  * object so it will not be possible by a moving garbage collector to move the
378  * object. 
379  * 
380  * Returns: a handle that can be used to access the object from
381  * unmanaged code.
382  */
383 guint32
384 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
385 {
386         return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
387 }
388
389 static GCObject *
390 link_get (volatile gpointer *link_addr, gboolean is_weak)
391 {
392         void *volatile *link_addr_volatile;
393         void *ptr;
394         GCObject *obj;
395 retry:
396         link_addr_volatile = link_addr;
397         ptr = (void*)*link_addr_volatile;
398         /*
399          * At this point we have a hidden pointer.  If the GC runs
400          * here, it will not recognize the hidden pointer as a
401          * reference, and if the object behind it is not referenced
402          * elsewhere, it will be freed.  Once the world is restarted
403          * we reveal the pointer, giving us a pointer to a freed
404          * object.  To make sure we don't return it, we load the
405          * hidden pointer again.  If it's still the same, we can be
406          * sure the object reference is valid.
407          */
408         if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
409                 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
410         else
411                 return NULL;
412
413         /* Note [dummy use]:
414          *
415          * If a GC happens here, obj needs to be on the stack or in a
416          * register, so we need to prevent this from being reordered
417          * wrt the check.
418          */
419         sgen_dummy_use (obj);
420         mono_memory_barrier ();
421
422         if (is_weak)
423                 sgen_client_ensure_weak_gchandles_accessible ();
424
425         if ((void*)*link_addr_volatile != ptr)
426                 goto retry;
427
428         return obj;
429 }
430
431 /**
432  * mono_gchandle_get_target:
433  * @gchandle: a GCHandle's handle.
434  *
435  * The handle was previously created by calling mono_gchandle_new or
436  * mono_gchandle_new_weakref. 
437  *
438  * Returns a pointer to the MonoObject represented by the handle or
439  * NULL for a collected object if using a weakref handle.
440  */
441 GCObject*
442 mono_gchandle_get_target (guint32 gchandle)
443 {
444         guint index = MONO_GC_HANDLE_SLOT (gchandle);
445         guint type = MONO_GC_HANDLE_TYPE (gchandle);
446         HandleData *handles = gc_handles_for_type (type);
447         guint bucket, offset;
448         g_assert (index < handles->capacity);
449         bucketize (index, &bucket, &offset);
450         return link_get (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
451 }
452
453 void
454 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
455 {
456         guint index = MONO_GC_HANDLE_SLOT (gchandle);
457         guint type = MONO_GC_HANDLE_TYPE (gchandle);
458         HandleData *handles = gc_handles_for_type (type);
459         guint bucket, offset;
460         gpointer slot;
461
462         g_assert (index < handles->capacity);
463         bucketize (index, &bucket, &offset);
464
465         do {
466                 slot = handles->entries [bucket] [offset];
467                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (slot), "Why are we setting the target on an unoccupied slot?");
468         } while (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, handles->type));
469 }
470
471 static gpointer
472 mono_gchandle_slot_metadata (volatile gpointer *slot_addr, gboolean is_weak)
473 {
474         gpointer slot;
475         gpointer metadata;
476 retry:
477         slot = *slot_addr;
478         if (!MONO_GC_HANDLE_OCCUPIED (slot))
479                 return NULL;
480         if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot)) {
481                 GCObject *obj = MONO_GC_REVEAL_POINTER (slot, is_weak);
482                 /* See note [dummy use]. */
483                 sgen_dummy_use (obj);
484                 /*
485                  * FIXME: The compiler could technically not carry a reference to obj around
486                  * at this point and recompute it later, in which case we would still use
487                  * it.
488                  */
489                 if (*slot_addr != slot)
490                         goto retry;
491                 return sgen_client_metadata_for_object (obj);
492         }
493         metadata = MONO_GC_REVEAL_POINTER (slot, is_weak);
494         /* See note [dummy use]. */
495         sgen_dummy_use (metadata);
496         if (*slot_addr != slot)
497                 goto retry;
498         return metadata;
499 }
500
501 gpointer
502 sgen_gchandle_get_metadata (guint32 gchandle)
503 {
504         guint index = MONO_GC_HANDLE_SLOT (gchandle);
505         guint type = MONO_GC_HANDLE_TYPE (gchandle);
506         HandleData *handles = gc_handles_for_type (type);
507         guint bucket, offset;
508         if (index >= handles->capacity)
509                 return NULL;
510         bucketize (index, &bucket, &offset);
511         return mono_gchandle_slot_metadata (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
512 }
513
514 /**
515  * mono_gchandle_free:
516  * @gchandle: a GCHandle's handle.
517  *
518  * Frees the @gchandle handle.  If there are no outstanding
519  * references, the garbage collector can reclaim the memory of the
520  * object wrapped. 
521  */
522 void
523 mono_gchandle_free (guint32 gchandle)
524 {
525         guint index = MONO_GC_HANDLE_SLOT (gchandle);
526         guint type = MONO_GC_HANDLE_TYPE (gchandle);
527         HandleData *handles = gc_handles_for_type (type);
528         guint bucket, offset;
529         gpointer slot;
530         bucketize (index, &bucket, &offset);
531         slot = handles->entries [bucket] [offset];
532         if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (slot)) {
533                 handles->entries [bucket] [offset] = NULL;
534                 protocol_gchandle_update (handles->type, (gpointer)&handles->entries [bucket] [offset], slot, NULL);
535                 HEAVY_STAT (InterlockedDecrement64 ((volatile gint64 *)&stat_gc_handles_allocated));
536         } else {
537                 /* print a warning? */
538         }
539         sgen_client_gchandle_destroyed (handles->type, gchandle);
540 }
541
542 /*
543  * Returns whether to remove the link from its hash.
544  */
545 static gpointer
546 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
547 {
548         const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
549         ScanCopyContext *ctx = (ScanCopyContext *)user;
550         GCObject *obj;
551         GCObject *copy;
552
553         if (!MONO_GC_HANDLE_VALID (hidden))
554                 return hidden;
555
556         obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
557         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
558
559         if (object_older_than (obj, max_generation))
560                 return hidden;
561
562         if (major_collector.is_object_live (obj))
563                 return hidden;
564
565         /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
566         if (sgen_gc_is_object_ready_for_finalization (obj))
567                 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
568
569         copy = obj;
570         ctx->ops->copy_or_mark_object (&copy, ctx->queue);
571         SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
572         /* Update link if object was moved. */
573         return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
574 }
575
576 /* LOCKING: requires that the GC lock is held */
577 void
578 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
579 {
580         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
581 }
582
583 typedef struct {
584         SgenObjectPredicateFunc predicate;
585         gpointer data;
586 } WeakLinkAlivePredicateClosure;
587
588 static gpointer
589 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
590 {
591         /* Strictly speaking, function pointers are not guaranteed to have the same size as data pointers. */
592         WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
593         GCObject *obj;
594
595         if (!MONO_GC_HANDLE_VALID (hidden))
596                 return hidden;
597
598         obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
599         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
600
601         if (object_older_than (obj, max_generation))
602                 return hidden;
603
604         if (closure->predicate (obj, closure->data))
605                 return NULL;
606
607         return hidden;
608 }
609
610 /* LOCKING: requires that the GC lock is held */
611 void
612 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
613 {
614         WeakLinkAlivePredicateClosure closure = { predicate, data };
615         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
616 }
617
618 void
619 sgen_init_gchandles (void)
620 {
621 #ifdef HEAVY_STATISTICS
622         mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_allocated);
623         mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_gc_handles_max_allocated);
624 #endif
625 }
626
627 #endif