Merge pull request #1457 from evincarofautumn/weak-refs
[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 guint32 stat_gc_handles_allocated = 0;
29 static volatile guint32 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 static HandleData gc_handles [] = {
150         { { NULL }, 0, 0, 0, (HANDLE_WEAK) },
151         { { NULL }, 0, 0, 0, (HANDLE_WEAK_TRACK) },
152         { { NULL }, 0, 0, 0, (HANDLE_NORMAL) },
153         { { NULL }, 0, 0, 0, (HANDLE_PINNED) }
154 };
155
156 static HandleData *
157 gc_handles_for_type (GCHandleType type)
158 {
159         g_assert (type < HANDLE_TYPE_MAX);
160         return &gc_handles [type];
161 }
162
163 /* This assumes that the world is stopped. */
164 void
165 sgen_mark_normal_gc_handles (void *addr, SgenUserMarkFunc mark_func, void *gc_data)
166 {
167         HandleData *handles = gc_handles_for_type (HANDLE_NORMAL);
168         size_t bucket, offset;
169         const guint max_bucket = index_bucket (handles->capacity);
170         guint32 index = 0;
171         const guint32 max_index = handles->max_index;
172         for (bucket = 0; bucket < max_bucket; ++bucket) {
173                 volatile gpointer *entries = handles->entries [bucket];
174                 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
175                         volatile gpointer *entry;
176                         gpointer hidden, revealed;
177                         /* No need to iterate beyond the largest index ever allocated. */
178                         if (index > max_index)
179                                 return;
180                         entry = &entries [offset];
181                         hidden = *entry;
182                         revealed = MONO_GC_REVEAL_POINTER (hidden, FALSE);
183                         if (!MONO_GC_HANDLE_IS_OBJECT_POINTER (hidden))
184                                 continue;
185                         mark_func ((MonoObject **)&revealed, gc_data);
186                         g_assert (revealed);
187                         *entry = MONO_GC_HANDLE_OBJECT_POINTER (revealed, FALSE);
188                 }
189         }
190 }
191
192 static guint
193 handle_data_find_unset (HandleData *handles, guint32 begin, guint32 end)
194 {
195         guint index;
196         gint delta = begin < end ? +1 : -1;
197         for (index = begin; index < end; index += delta) {
198                 guint bucket, offset;
199                 volatile gpointer *entries;
200                 bucketize (index, &bucket, &offset);
201                 entries = handles->entries [bucket];
202                 g_assert (entries);
203                 if (!MONO_GC_HANDLE_OCCUPIED (entries [offset]))
204                         return index;
205         }
206         return -1;
207 }
208
209 /* Adds a bucket if necessary and possible. */
210 static void
211 handle_data_grow (HandleData *handles, guint32 old_capacity)
212 {
213         const guint new_bucket = index_bucket (old_capacity);
214         const guint32 growth = bucket_size (new_bucket);
215         const guint32 new_capacity = old_capacity + growth;
216         gpointer *entries;
217         const size_t new_bucket_size = sizeof (**handles->entries) * growth;
218         if (handles->capacity >= new_capacity)
219                 return;
220         entries = g_malloc0 (new_bucket_size);
221         if (handles->type == HANDLE_PINNED)
222                 sgen_register_root ((char *)entries, new_bucket_size, SGEN_DESCRIPTOR_NULL, ROOT_TYPE_PINNED, MONO_ROOT_SOURCE_GC_HANDLE, "pinned gc handles");
223         /* The zeroing of the newly allocated bucket must be complete before storing
224          * the new bucket pointer.
225          */
226         mono_memory_write_barrier ();
227         if (InterlockedCompareExchangePointer ((volatile gpointer *)&handles->entries [new_bucket], entries, NULL) == NULL) {
228                 /* It must not be the case that we succeeded in setting the bucket
229                  * pointer, while someone else succeeded in changing the capacity.
230                  */
231                 if (InterlockedCompareExchange ((volatile gint32 *)&handles->capacity, new_capacity, old_capacity) != old_capacity)
232                         g_assert_not_reached ();
233                 handles->slot_hint = old_capacity;
234                 return;
235         }
236         /* Someone beat us to the allocation. */
237         if (handles->type == HANDLE_PINNED)
238                 sgen_deregister_root ((char *)entries);
239         g_free (entries);
240 }
241
242 static guint32
243 alloc_handle (HandleData *handles, GCObject *obj, gboolean track)
244 {
245         guint index;
246         guint32 res;
247         guint bucket, offset;
248         guint32 capacity;
249         guint32 slot_hint;
250         guint32 max_index;
251         if (!handles->capacity)
252                 handle_data_grow (handles, 0);
253 retry:
254         capacity = handles->capacity;
255         slot_hint = handles->slot_hint;
256         index = handle_data_find_unset (handles, slot_hint, capacity);
257         if (index == -1)
258                 index = handle_data_find_unset (handles, 0, slot_hint);
259         if (index == -1) {
260                 handle_data_grow (handles, capacity);
261                 goto retry;
262         }
263         handles->slot_hint = index;
264         bucketize (index, &bucket, &offset);
265         if (!try_occupy_slot (handles, bucket, offset, obj, track))
266                 goto retry;
267         /* If a GC happens shortly after a new bucket is allocated, the entire
268          * bucket could be scanned even though it's mostly empty. To avoid this, we
269          * track the maximum index seen so far, so that we can skip the empty slots.
270          */
271         do {
272                 max_index = handles->max_index;
273                 if (index <= max_index)
274                         break;
275         } while (!InterlockedCompareExchange ((volatile gint32 *)&handles->max_index, index, max_index));
276 #ifdef HEAVY_STATISTICS
277         InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
278         if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
279                 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
280 #endif
281         /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
282         mono_memory_write_barrier ();
283         res = MONO_GC_HANDLE (index, handles->type);
284         sgen_client_gchandle_created (handles->type, obj, res);
285         return res;
286 }
287
288 static gboolean
289 object_older_than (GCObject *object, int generation)
290 {
291         return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
292 }
293
294 /*
295  * Maps a function over all GC handles.
296  * This assumes that the world is stopped!
297  */
298 void
299 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
300 {
301         HandleData *handle_data = gc_handles_for_type (handle_type);
302         size_t bucket, offset;
303         guint max_bucket = index_bucket (handle_data->capacity);
304         guint32 index = 0;
305         guint32 max_index = handle_data->max_index;
306         /* If a new bucket has been allocated, but the capacity has not yet been
307          * increased, nothing can yet have been allocated in the bucket because the
308          * world is stopped, so we shouldn't miss any handles during iteration.
309          */
310         for (bucket = 0; bucket < max_bucket; ++bucket) {
311                 volatile gpointer *entries = handle_data->entries [bucket];
312                 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
313                         gpointer hidden;
314                         gpointer result;
315                         /* Table must contain no garbage pointers. */
316                         gboolean occupied;
317                         /* No need to iterate beyond the largest index ever allocated. */
318                         if (index > max_index)
319                                         return;
320                         hidden = entries [offset];
321                         occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
322                         g_assert (hidden ? occupied : !occupied);
323                         if (!occupied)
324                                 continue;
325                         result = callback (hidden, handle_type, max_generation, user);
326                         if (result)
327                                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
328                         else
329                                 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
330                         protocol_gchandle_update (handle_type, (gpointer)&entries [offset], hidden, result);
331                         entries [offset] = result;
332                 }
333         }
334 }
335
336 /**
337  * mono_gchandle_new:
338  * @obj: managed object to get a handle for
339  * @pinned: whether the object should be pinned
340  *
341  * This returns a handle that wraps the object, this is used to keep a
342  * reference to a managed object from the unmanaged world and preventing the
343  * object from being disposed.
344  * 
345  * If @pinned is false the address of the object can not be obtained, if it is
346  * true the address of the object can be obtained.  This will also pin the
347  * object so it will not be possible by a moving garbage collector to move the
348  * object. 
349  * 
350  * Returns: a handle that can be used to access the object from
351  * unmanaged code.
352  */
353 guint32
354 mono_gchandle_new (GCObject *obj, gboolean pinned)
355 {
356         return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
357 }
358
359 /**
360  * mono_gchandle_new_weakref:
361  * @obj: managed object to get a handle for
362  * @pinned: whether the object should be pinned
363  *
364  * This returns a weak handle that wraps the object, this is used to
365  * keep a reference to a managed object from the unmanaged world.
366  * Unlike the mono_gchandle_new the object can be reclaimed by the
367  * garbage collector.  In this case the value of the GCHandle will be
368  * set to zero.
369  * 
370  * If @pinned is false the address of the object can not be obtained, if it is
371  * true the address of the object can be obtained.  This will also pin the
372  * object so it will not be possible by a moving garbage collector to move the
373  * object. 
374  * 
375  * Returns: a handle that can be used to access the object from
376  * unmanaged code.
377  */
378 guint32
379 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
380 {
381         return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
382 }
383
384 static GCObject *
385 link_get (volatile gpointer *link_addr, gboolean is_weak)
386 {
387         void *volatile *link_addr_volatile;
388         void *ptr;
389         GCObject *obj;
390 retry:
391         link_addr_volatile = link_addr;
392         ptr = (void*)*link_addr_volatile;
393         /*
394          * At this point we have a hidden pointer.  If the GC runs
395          * here, it will not recognize the hidden pointer as a
396          * reference, and if the object behind it is not referenced
397          * elsewhere, it will be freed.  Once the world is restarted
398          * we reveal the pointer, giving us a pointer to a freed
399          * object.  To make sure we don't return it, we load the
400          * hidden pointer again.  If it's still the same, we can be
401          * sure the object reference is valid.
402          */
403         if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
404                 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
405         else
406                 return NULL;
407
408         /* Note [dummy use]:
409          *
410          * If a GC happens here, obj needs to be on the stack or in a
411          * register, so we need to prevent this from being reordered
412          * wrt the check.
413          */
414         sgen_dummy_use (obj);
415         mono_memory_barrier ();
416
417         if (is_weak)
418                 sgen_client_ensure_weak_gchandles_accessible ();
419
420         if ((void*)*link_addr_volatile != ptr)
421                 goto retry;
422
423         return obj;
424 }
425
426 /**
427  * mono_gchandle_get_target:
428  * @gchandle: a GCHandle's handle.
429  *
430  * The handle was previously created by calling mono_gchandle_new or
431  * mono_gchandle_new_weakref. 
432  *
433  * Returns a pointer to the MonoObject represented by the handle or
434  * NULL for a collected object if using a weakref handle.
435  */
436 GCObject*
437 mono_gchandle_get_target (guint32 gchandle)
438 {
439         guint index = MONO_GC_HANDLE_SLOT (gchandle);
440         guint type = MONO_GC_HANDLE_TYPE (gchandle);
441         HandleData *handles = gc_handles_for_type (type);
442         guint bucket, offset;
443         g_assert (index < handles->capacity);
444         bucketize (index, &bucket, &offset);
445         return link_get (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
446 }
447
448 void
449 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
450 {
451         guint index = MONO_GC_HANDLE_SLOT (gchandle);
452         guint type = MONO_GC_HANDLE_TYPE (gchandle);
453         HandleData *handles = gc_handles_for_type (type);
454         guint bucket, offset;
455         gpointer slot;
456
457         g_assert (index < handles->capacity);
458         bucketize (index, &bucket, &offset);
459
460         do {
461                 slot = handles->entries [bucket] [offset];
462                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (slot), "Why are we setting the target on an unoccupied slot?");
463         } while (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, handles->type));
464 }
465
466 static gpointer
467 mono_gchandle_slot_metadata (volatile gpointer *slot_addr, gboolean is_weak)
468 {
469         gpointer slot;
470         gpointer metadata;
471 retry:
472         slot = *slot_addr;
473         if (!MONO_GC_HANDLE_OCCUPIED (slot))
474                 return NULL;
475         if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot)) {
476                 GCObject *obj = MONO_GC_REVEAL_POINTER (slot, is_weak);
477                 /* See note [dummy use]. */
478                 sgen_dummy_use (obj);
479                 /*
480                  * FIXME: The compiler could technically not carry a reference to obj around
481                  * at this point and recompute it later, in which case we would still use
482                  * it.
483                  */
484                 if (*slot_addr != slot)
485                         goto retry;
486                 return sgen_client_metadata_for_object (obj);
487         }
488         metadata = MONO_GC_REVEAL_POINTER (slot, is_weak);
489         /* See note [dummy use]. */
490         sgen_dummy_use (metadata);
491         if (*slot_addr != slot)
492                 goto retry;
493         return metadata;
494 }
495
496 gpointer
497 sgen_gchandle_get_metadata (guint32 gchandle)
498 {
499         guint index = MONO_GC_HANDLE_SLOT (gchandle);
500         guint type = MONO_GC_HANDLE_TYPE (gchandle);
501         HandleData *handles = gc_handles_for_type (type);
502         guint bucket, offset;
503         if (index >= handles->capacity)
504                 return NULL;
505         bucketize (index, &bucket, &offset);
506         return mono_gchandle_slot_metadata (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
507 }
508
509 /**
510  * mono_gchandle_free:
511  * @gchandle: a GCHandle's handle.
512  *
513  * Frees the @gchandle handle.  If there are no outstanding
514  * references, the garbage collector can reclaim the memory of the
515  * object wrapped. 
516  */
517 void
518 mono_gchandle_free (guint32 gchandle)
519 {
520         guint index = MONO_GC_HANDLE_SLOT (gchandle);
521         guint type = MONO_GC_HANDLE_TYPE (gchandle);
522         HandleData *handles = gc_handles_for_type (type);
523         guint bucket, offset;
524         gpointer slot;
525         bucketize (index, &bucket, &offset);
526         slot = handles->entries [bucket] [offset];
527         if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (slot)) {
528                 handles->entries [bucket] [offset] = NULL;
529                 protocol_gchandle_update (handles->type, (gpointer)&handles->entries [bucket] [offset], slot, NULL);
530                 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
531         } else {
532                 /* print a warning? */
533         }
534         sgen_client_gchandle_destroyed (handles->type, gchandle);
535 }
536
537 /*
538  * Returns whether to remove the link from its hash.
539  */
540 static gpointer
541 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
542 {
543         const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
544         ScanCopyContext *ctx = (ScanCopyContext *)user;
545         GCObject *obj;
546         GCObject *copy;
547
548         if (!MONO_GC_HANDLE_VALID (hidden))
549                 return hidden;
550
551         obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
552         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
553
554         if (object_older_than (obj, max_generation))
555                 return hidden;
556
557         if (major_collector.is_object_live (obj))
558                 return hidden;
559
560         /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
561         if (sgen_gc_is_object_ready_for_finalization (obj))
562                 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
563
564         copy = obj;
565         ctx->ops->copy_or_mark_object (&copy, ctx->queue);
566         SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
567         /* Update link if object was moved. */
568         return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
569 }
570
571 /* LOCKING: requires that the GC lock is held */
572 void
573 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
574 {
575         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
576 }
577
578 typedef struct {
579         SgenObjectPredicateFunc predicate;
580         gpointer data;
581 } WeakLinkAlivePredicateClosure;
582
583 static gpointer
584 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
585 {
586         WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
587         GCObject *obj;
588
589         if (!MONO_GC_HANDLE_VALID (hidden))
590                 return hidden;
591
592         obj = MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
593         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
594
595         if (object_older_than (obj, max_generation))
596                 return hidden;
597
598         if (closure->predicate (obj, closure->data))
599                 return NULL;
600
601         return hidden;
602 }
603
604 /* LOCKING: requires that the GC lock is held */
605 void
606 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
607 {
608         WeakLinkAlivePredicateClosure closure = { predicate, data };
609         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
610 }
611
612 void
613 sgen_init_gchandles (void)
614 {
615 #ifdef HEAVY_STATISTICS
616         mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
617         mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);
618 #endif
619 }
620
621 #endif