Merge pull request #2338 from BogdanovKirill/httpwritefix3
[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, (GCHandleType)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 = (gpointer *)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
264         /*
265          * If a GC happens shortly after a new bucket is allocated, the entire
266          * bucket could be scanned even though it's mostly empty. To avoid this,
267          * we track the maximum index seen so far, so that we can skip the empty
268          * slots.
269          *
270          * Note that we update `max_index` before we even try occupying the
271          * slot.  If we did it the other way around and a GC happened in
272          * between, the GC wouldn't know that the slot was occupied.  This is
273          * not a huge deal since `obj` is on the stack and thus pinned anyway,
274          * but hopefully some day it won't be anymore.
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) != max_index);
281
282         bucketize (index, &bucket, &offset);
283         if (!try_occupy_slot (handles, bucket, offset, obj, track))
284                 goto retry;
285 #ifdef HEAVY_STATISTICS
286         InterlockedIncrement ((volatile gint32 *)&stat_gc_handles_allocated);
287         if (stat_gc_handles_allocated > stat_gc_handles_max_allocated)
288                 stat_gc_handles_max_allocated = stat_gc_handles_allocated;
289 #endif
290         /* Ensure that a GC handle cannot be given to another thread without the slot having been set. */
291         mono_memory_write_barrier ();
292         res = MONO_GC_HANDLE (index, handles->type);
293         sgen_client_gchandle_created (handles->type, obj, res);
294         return res;
295 }
296
297 static gboolean
298 object_older_than (GCObject *object, int generation)
299 {
300         return generation == GENERATION_NURSERY && !sgen_ptr_in_nursery (object);
301 }
302
303 /*
304  * Maps a function over all GC handles.
305  * This assumes that the world is stopped!
306  */
307 void
308 sgen_gchandle_iterate (GCHandleType handle_type, int max_generation, SgenGCHandleIterateCallback callback, gpointer user)
309 {
310         HandleData *handle_data = gc_handles_for_type (handle_type);
311         size_t bucket, offset;
312         guint max_bucket = index_bucket (handle_data->capacity);
313         guint32 index = 0;
314         guint32 max_index = handle_data->max_index;
315         /* If a new bucket has been allocated, but the capacity has not yet been
316          * increased, nothing can yet have been allocated in the bucket because the
317          * world is stopped, so we shouldn't miss any handles during iteration.
318          */
319         for (bucket = 0; bucket < max_bucket; ++bucket) {
320                 volatile gpointer *entries = handle_data->entries [bucket];
321                 for (offset = 0; offset < bucket_size (bucket); ++offset, ++index) {
322                         gpointer hidden;
323                         gpointer result;
324                         /* Table must contain no garbage pointers. */
325                         gboolean occupied;
326                         /* No need to iterate beyond the largest index ever allocated. */
327                         if (index > max_index)
328                                         return;
329                         hidden = entries [offset];
330                         occupied = MONO_GC_HANDLE_OCCUPIED (hidden);
331                         g_assert (hidden ? occupied : !occupied);
332                         if (!occupied)
333                                 continue;
334                         result = callback (hidden, handle_type, max_generation, user);
335                         if (result)
336                                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (result), "Why did the callback return an unoccupied entry?");
337                         else
338                                 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
339                         protocol_gchandle_update (handle_type, (gpointer)&entries [offset], hidden, result);
340                         entries [offset] = result;
341                 }
342         }
343 }
344
345 /**
346  * mono_gchandle_new:
347  * @obj: managed object to get a handle for
348  * @pinned: whether the object should be pinned
349  *
350  * This returns a handle that wraps the object, this is used to keep a
351  * reference to a managed object from the unmanaged world and preventing the
352  * object from being disposed.
353  * 
354  * If @pinned is false the address of the object can not be obtained, if it is
355  * true the address of the object can be obtained.  This will also pin the
356  * object so it will not be possible by a moving garbage collector to move the
357  * object. 
358  * 
359  * Returns: a handle that can be used to access the object from
360  * unmanaged code.
361  */
362 guint32
363 mono_gchandle_new (GCObject *obj, gboolean pinned)
364 {
365         return alloc_handle (gc_handles_for_type (pinned ? HANDLE_PINNED : HANDLE_NORMAL), obj, FALSE);
366 }
367
368 /**
369  * mono_gchandle_new_weakref:
370  * @obj: managed object to get a handle for
371  * @pinned: whether the object should be pinned
372  *
373  * This returns a weak handle that wraps the object, this is used to
374  * keep a reference to a managed object from the unmanaged world.
375  * Unlike the mono_gchandle_new the object can be reclaimed by the
376  * garbage collector.  In this case the value of the GCHandle will be
377  * set to zero.
378  * 
379  * If @pinned is false the address of the object can not be obtained, if it is
380  * true the address of the object can be obtained.  This will also pin the
381  * object so it will not be possible by a moving garbage collector to move the
382  * object. 
383  * 
384  * Returns: a handle that can be used to access the object from
385  * unmanaged code.
386  */
387 guint32
388 mono_gchandle_new_weakref (GCObject *obj, gboolean track_resurrection)
389 {
390         return alloc_handle (gc_handles_for_type (track_resurrection ? HANDLE_WEAK_TRACK : HANDLE_WEAK), obj, track_resurrection);
391 }
392
393 static GCObject *
394 link_get (volatile gpointer *link_addr, gboolean is_weak)
395 {
396         void *volatile *link_addr_volatile;
397         void *ptr;
398         GCObject *obj;
399 retry:
400         link_addr_volatile = link_addr;
401         ptr = (void*)*link_addr_volatile;
402         /*
403          * At this point we have a hidden pointer.  If the GC runs
404          * here, it will not recognize the hidden pointer as a
405          * reference, and if the object behind it is not referenced
406          * elsewhere, it will be freed.  Once the world is restarted
407          * we reveal the pointer, giving us a pointer to a freed
408          * object.  To make sure we don't return it, we load the
409          * hidden pointer again.  If it's still the same, we can be
410          * sure the object reference is valid.
411          */
412         if (ptr && MONO_GC_HANDLE_IS_OBJECT_POINTER (ptr))
413                 obj = (GCObject *)MONO_GC_REVEAL_POINTER (ptr, is_weak);
414         else
415                 return NULL;
416
417         /* Note [dummy use]:
418          *
419          * If a GC happens here, obj needs to be on the stack or in a
420          * register, so we need to prevent this from being reordered
421          * wrt the check.
422          */
423         sgen_dummy_use (obj);
424         mono_memory_barrier ();
425
426         if (is_weak)
427                 sgen_client_ensure_weak_gchandles_accessible ();
428
429         if ((void*)*link_addr_volatile != ptr)
430                 goto retry;
431
432         return obj;
433 }
434
435 /**
436  * mono_gchandle_get_target:
437  * @gchandle: a GCHandle's handle.
438  *
439  * The handle was previously created by calling mono_gchandle_new or
440  * mono_gchandle_new_weakref. 
441  *
442  * Returns a pointer to the MonoObject represented by the handle or
443  * NULL for a collected object if using a weakref handle.
444  */
445 GCObject*
446 mono_gchandle_get_target (guint32 gchandle)
447 {
448         guint index = MONO_GC_HANDLE_SLOT (gchandle);
449         GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
450         HandleData *handles = gc_handles_for_type (type);
451         /* Invalid handles are possible; accessing one should produce NULL. (#34276) */
452         if (!handles)
453                 return NULL;
454         guint bucket, offset;
455         g_assert (index < handles->capacity);
456         bucketize (index, &bucket, &offset);
457         return link_get (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
458 }
459
460 void
461 sgen_gchandle_set_target (guint32 gchandle, GCObject *obj)
462 {
463         guint index = MONO_GC_HANDLE_SLOT (gchandle);
464         GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
465         HandleData *handles = gc_handles_for_type (type);
466         if (!handles)
467                 return;
468         guint bucket, offset;
469         gpointer slot;
470
471         g_assert (index < handles->capacity);
472         bucketize (index, &bucket, &offset);
473
474         do {
475                 slot = handles->entries [bucket] [offset];
476                 SGEN_ASSERT (0, MONO_GC_HANDLE_OCCUPIED (slot), "Why are we setting the target on an unoccupied slot?");
477         } while (!try_set_slot (&handles->entries [bucket] [offset], obj, slot, (GCHandleType)handles->type));
478 }
479
480 static gpointer
481 mono_gchandle_slot_metadata (volatile gpointer *slot_addr, gboolean is_weak)
482 {
483         gpointer slot;
484         gpointer metadata;
485 retry:
486         slot = *slot_addr;
487         if (!MONO_GC_HANDLE_OCCUPIED (slot))
488                 return NULL;
489         if (MONO_GC_HANDLE_IS_OBJECT_POINTER (slot)) {
490                 GCObject *obj = (GCObject *)MONO_GC_REVEAL_POINTER (slot, is_weak);
491                 /* See note [dummy use]. */
492                 sgen_dummy_use (obj);
493                 /*
494                  * FIXME: The compiler could technically not carry a reference to obj around
495                  * at this point and recompute it later, in which case we would still use
496                  * it.
497                  */
498                 if (*slot_addr != slot)
499                         goto retry;
500                 return sgen_client_metadata_for_object (obj);
501         }
502         metadata = MONO_GC_REVEAL_POINTER (slot, is_weak);
503         /* See note [dummy use]. */
504         sgen_dummy_use (metadata);
505         if (*slot_addr != slot)
506                 goto retry;
507         return metadata;
508 }
509
510 gpointer
511 sgen_gchandle_get_metadata (guint32 gchandle)
512 {
513         guint index = MONO_GC_HANDLE_SLOT (gchandle);
514         GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
515         HandleData *handles = gc_handles_for_type (type);
516         if (!handles)
517                 return NULL;
518         guint bucket, offset;
519         if (index >= handles->capacity)
520                 return NULL;
521         bucketize (index, &bucket, &offset);
522         return mono_gchandle_slot_metadata (&handles->entries [bucket] [offset], MONO_GC_HANDLE_TYPE_IS_WEAK (type));
523 }
524
525 /**
526  * mono_gchandle_free:
527  * @gchandle: a GCHandle's handle.
528  *
529  * Frees the @gchandle handle.  If there are no outstanding
530  * references, the garbage collector can reclaim the memory of the
531  * object wrapped. 
532  */
533 void
534 mono_gchandle_free (guint32 gchandle)
535 {
536         guint index = MONO_GC_HANDLE_SLOT (gchandle);
537         GCHandleType type = MONO_GC_HANDLE_TYPE (gchandle);
538         HandleData *handles = gc_handles_for_type (type);
539         if (!handles)
540                 return;
541         guint bucket, offset;
542         gpointer slot;
543         bucketize (index, &bucket, &offset);
544         slot = handles->entries [bucket] [offset];
545         if (index < handles->capacity && MONO_GC_HANDLE_OCCUPIED (slot)) {
546                 handles->entries [bucket] [offset] = NULL;
547                 protocol_gchandle_update (handles->type, (gpointer)&handles->entries [bucket] [offset], slot, NULL);
548                 HEAVY_STAT (InterlockedDecrement ((volatile gint32 *)&stat_gc_handles_allocated));
549         } else {
550                 /* print a warning? */
551         }
552         sgen_client_gchandle_destroyed (handles->type, gchandle);
553 }
554
555 /*
556  * Returns whether to remove the link from its hash.
557  */
558 static gpointer
559 null_link_if_necessary (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
560 {
561         const gboolean is_weak = GC_HANDLE_TYPE_IS_WEAK (handle_type);
562         ScanCopyContext *ctx = (ScanCopyContext *)user;
563         GCObject *obj;
564         GCObject *copy;
565
566         if (!MONO_GC_HANDLE_VALID (hidden))
567                 return hidden;
568
569         obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
570         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
571
572         if (object_older_than (obj, max_generation))
573                 return hidden;
574
575         if (major_collector.is_object_live (obj))
576                 return hidden;
577
578         /* Clear link if object is ready for finalization. This check may be redundant wrt is_object_live(). */
579         if (sgen_gc_is_object_ready_for_finalization (obj))
580                 return MONO_GC_HANDLE_METADATA_POINTER (sgen_client_metadata_for_object (obj), is_weak);
581
582         copy = obj;
583         ctx->ops->copy_or_mark_object (&copy, ctx->queue);
584         SGEN_ASSERT (0, copy, "Why couldn't we copy the object?");
585         /* Update link if object was moved. */
586         return MONO_GC_HANDLE_OBJECT_POINTER (copy, is_weak);
587 }
588
589 /* LOCKING: requires that the GC lock is held */
590 void
591 sgen_null_link_in_range (int generation, ScanCopyContext ctx, gboolean track)
592 {
593         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if_necessary, &ctx);
594 }
595
596 typedef struct {
597         SgenObjectPredicateFunc predicate;
598         gpointer data;
599 } WeakLinkAlivePredicateClosure;
600
601 static gpointer
602 null_link_if (gpointer hidden, GCHandleType handle_type, int max_generation, gpointer user)
603 {
604         WeakLinkAlivePredicateClosure *closure = (WeakLinkAlivePredicateClosure *)user;
605         GCObject *obj;
606
607         if (!MONO_GC_HANDLE_VALID (hidden))
608                 return hidden;
609
610         obj = (GCObject *)MONO_GC_REVEAL_POINTER (hidden, MONO_GC_HANDLE_TYPE_IS_WEAK (handle_type));
611         SGEN_ASSERT (0, obj, "Why is the hidden pointer NULL?");
612
613         if (object_older_than (obj, max_generation))
614                 return hidden;
615
616         if (closure->predicate (obj, closure->data))
617                 return NULL;
618
619         return hidden;
620 }
621
622 /* LOCKING: requires that the GC lock is held */
623 void
624 sgen_null_links_if (SgenObjectPredicateFunc predicate, void *data, int generation, gboolean track)
625 {
626         WeakLinkAlivePredicateClosure closure = { predicate, data };
627         sgen_gchandle_iterate (track ? HANDLE_WEAK_TRACK : HANDLE_WEAK, generation, null_link_if, &closure);
628 }
629
630 void
631 sgen_init_gchandles (void)
632 {
633 #ifdef HEAVY_STATISTICS
634         mono_counters_register ("GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_allocated);
635         mono_counters_register ("max GC handles allocated", MONO_COUNTER_GC | MONO_COUNTER_UINT, (void *)&stat_gc_handles_max_allocated);
636 #endif
637 }
638
639 #endif