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