First set of licensing changes
[mono.git] / mono / sgen / sgen-fin-weak-hash.c
1 /*
2  * sgen-fin-weak-hash.c: Finalizers and weak links.
3  *
4  * Author:
5  *      Paolo Molaro (lupus@ximian.com)
6  *  Rodrigo Kumpera (kumpera@gmail.com)
7  *
8  * Copyright 2005-2011 Novell, Inc (http://www.novell.com)
9  * Copyright 2011 Xamarin Inc (http://www.xamarin.com)
10  * Copyright 2011 Xamarin, Inc.
11  * Copyright (C) 2012 Xamarin Inc
12  *
13  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
14  */
15
16 #include "config.h"
17 #ifdef HAVE_SGEN_GC
18
19 #include "mono/sgen/sgen-gc.h"
20 #include "mono/sgen/sgen-gray.h"
21 #include "mono/sgen/sgen-protocol.h"
22 #include "mono/sgen/sgen-pointer-queue.h"
23 #include "mono/sgen/sgen-client.h"
24 #include "mono/sgen/gc-internal-agnostic.h"
25 #include "mono/utils/mono-membar.h"
26
27 #define ptr_in_nursery sgen_ptr_in_nursery
28
29 typedef SgenGrayQueue GrayQueue;
30
31 static int no_finalize = 0;
32
33 /*
34  * The finalizable hash has the object as the key, the 
35  * disappearing_link hash, has the link address as key.
36  *
37  * Copyright 2011 Xamarin Inc.
38  */
39
40 #define TAG_MASK ((mword)0x1)
41
42 static inline GCObject*
43 tagged_object_get_object (GCObject *object)
44 {
45         return (GCObject*)(((mword)object) & ~TAG_MASK);
46 }
47
48 static inline int
49 tagged_object_get_tag (GCObject *object)
50 {
51         return ((mword)object) & TAG_MASK;
52 }
53
54 static inline GCObject*
55 tagged_object_apply (void *object, int tag_bits)
56 {
57        return (GCObject*)((mword)object | (mword)tag_bits);
58 }
59
60 static int
61 tagged_object_hash (GCObject *o)
62 {
63         return sgen_aligned_addr_hash (tagged_object_get_object (o));
64 }
65
66 static gboolean
67 tagged_object_equals (GCObject *a, GCObject *b)
68 {
69         return tagged_object_get_object (a) == tagged_object_get_object (b);
70 }
71
72 static SgenHashTable minor_finalizable_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_FIN_TABLE, INTERNAL_MEM_FINALIZE_ENTRY, 0, (GHashFunc)tagged_object_hash, (GEqualFunc)tagged_object_equals);
73 static SgenHashTable major_finalizable_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_FIN_TABLE, INTERNAL_MEM_FINALIZE_ENTRY, 0, (GHashFunc)tagged_object_hash, (GEqualFunc)tagged_object_equals);
74
75 static SgenHashTable*
76 get_finalize_entry_hash_table (int generation)
77 {
78         switch (generation) {
79         case GENERATION_NURSERY: return &minor_finalizable_hash;
80         case GENERATION_OLD: return &major_finalizable_hash;
81         default: g_assert_not_reached ();
82         }
83 }
84
85 #define BRIDGE_OBJECT_MARKED 0x1
86
87 /* LOCKING: requires that the GC lock is held */
88 void
89 sgen_mark_bridge_object (GCObject *obj)
90 {
91         SgenHashTable *hash_table = get_finalize_entry_hash_table (ptr_in_nursery (obj) ? GENERATION_NURSERY : GENERATION_OLD);
92
93         sgen_hash_table_set_key (hash_table, obj, tagged_object_apply (obj, BRIDGE_OBJECT_MARKED));
94 }
95
96 /* LOCKING: requires that the GC lock is held */
97 void
98 sgen_collect_bridge_objects (int generation, ScanCopyContext ctx)
99 {
100         CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object;
101         GrayQueue *queue = ctx.queue;
102         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
103         GCObject *object;
104         gpointer dummy G_GNUC_UNUSED;
105         GCObject *copy;
106         SgenPointerQueue moved_fin_objects;
107
108         sgen_pointer_queue_init (&moved_fin_objects, INTERNAL_MEM_TEMPORARY);
109
110         if (no_finalize)
111                 return;
112
113         SGEN_HASH_TABLE_FOREACH (hash_table, GCObject *, object, gpointer, dummy) {
114                 int tag = tagged_object_get_tag (object);
115                 object = tagged_object_get_object (object);
116
117                 /* Bridge code told us to ignore this one */
118                 if (tag == BRIDGE_OBJECT_MARKED)
119                         continue;
120
121                 /* Object is a bridge object and major heap says it's dead  */
122                 if (major_collector.is_object_live (object))
123                         continue;
124
125                 /* Nursery says the object is dead. */
126                 if (!sgen_gc_is_object_ready_for_finalization (object))
127                         continue;
128
129                 if (!sgen_client_bridge_is_bridge_object (object))
130                         continue;
131
132                 copy = object;
133                 copy_func (&copy, queue);
134
135                 sgen_client_bridge_register_finalized_object (copy);
136                 
137                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
138                         /* remove from the list */
139                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
140
141                         /* insert it into the major hash */
142                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
143
144                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object);
145
146                         continue;
147                 } else if (copy != object) {
148                         /* update pointer */
149                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
150
151                         /* register for reinsertion */
152                         sgen_pointer_queue_add (&moved_fin_objects, tagged_object_apply (copy, tag));
153
154                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object);
155
156                         continue;
157                 }
158         } SGEN_HASH_TABLE_FOREACH_END;
159
160         while (!sgen_pointer_queue_is_empty (&moved_fin_objects)) {
161                 sgen_hash_table_replace (hash_table, sgen_pointer_queue_pop (&moved_fin_objects), NULL, NULL);
162         }
163
164         sgen_pointer_queue_free (&moved_fin_objects);
165 }
166
167
168 /* LOCKING: requires that the GC lock is held */
169 void
170 sgen_finalize_in_range (int generation, ScanCopyContext ctx)
171 {
172         CopyOrMarkObjectFunc copy_func = ctx.ops->copy_or_mark_object;
173         GrayQueue *queue = ctx.queue;
174         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
175         GCObject *object;
176         gpointer dummy G_GNUC_UNUSED;
177         SgenPointerQueue moved_fin_objects;
178
179         sgen_pointer_queue_init (&moved_fin_objects, INTERNAL_MEM_TEMPORARY);
180
181         if (no_finalize)
182                 return;
183         SGEN_HASH_TABLE_FOREACH (hash_table, GCObject *, object, gpointer, dummy) {
184                 int tag = tagged_object_get_tag (object);
185                 object = tagged_object_get_object (object);
186                 if (!major_collector.is_object_live (object)) {
187                         gboolean is_fin_ready = sgen_gc_is_object_ready_for_finalization (object);
188                         GCObject *copy = object;
189                         copy_func (&copy, queue);
190                         if (is_fin_ready) {
191                                 /* remove and put in fin_ready_list */
192                                 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
193                                 sgen_queue_finalization_entry (copy);
194                                 /* Make it survive */
195                                 SGEN_LOG (5, "Queueing object for finalization: %p (%s) (was at %p) (%d)", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object, sgen_hash_table_num_entries (hash_table));
196                                 continue;
197                         } else {
198                                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
199                                         /* remove from the list */
200                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
201
202                                         /* insert it into the major hash */
203                                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
204
205                                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object);
206
207                                         continue;
208                                 } else if (copy != object) {
209                                         /* update pointer */
210                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
211
212                                         /* register for reinsertion */
213                                         sgen_pointer_queue_add (&moved_fin_objects, tagged_object_apply (copy, tag));
214
215                                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (copy)), object);
216
217                                         continue;
218                                 }
219                         }
220                 }
221         } SGEN_HASH_TABLE_FOREACH_END;
222
223         while (!sgen_pointer_queue_is_empty (&moved_fin_objects)) {
224                 sgen_hash_table_replace (hash_table, sgen_pointer_queue_pop (&moved_fin_objects), NULL, NULL);
225         }
226
227         sgen_pointer_queue_free (&moved_fin_objects);
228 }
229
230 /* LOCKING: requires that the GC lock is held */
231 static void
232 register_for_finalization (GCObject *obj, void *user_data, int generation)
233 {
234         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
235
236         if (no_finalize)
237                 return;
238
239         if (user_data) {
240                 if (sgen_hash_table_replace (hash_table, obj, NULL, NULL)) {
241                         GCVTable vt = SGEN_LOAD_VTABLE_UNCHECKED (obj);
242                         SGEN_LOG (5, "Added finalizer for object: %p (%s) (%d) to %s table", obj, sgen_client_vtable_get_name (vt), hash_table->num_entries, sgen_generation_name (generation));
243                 }
244         } else {
245                 if (sgen_hash_table_remove (hash_table, obj, NULL)) {
246                         GCVTable vt = SGEN_LOAD_VTABLE_UNCHECKED (obj);
247                         SGEN_LOG (5, "Removed finalizer for object: %p (%s) (%d)", obj, sgen_client_vtable_get_name (vt), hash_table->num_entries);
248                 }
249         }
250 }
251
252 /*
253  * We're using (mostly) non-locking staging queues for finalizers and weak links to speed
254  * up registering them.  Otherwise we'd have to take the GC lock.
255  *
256  * The queues are arrays of `StageEntry`, plus a `next_entry` index.  Threads add entries to
257  * the queue via `add_stage_entry()` in a linear fashion until it fills up, in which case
258  * `process_stage_entries()` is called to drain it.  A garbage collection will also drain
259  * the queues via the same function.  That implies that `add_stage_entry()`, since it
260  * doesn't take a lock, must be able to run concurrently with `process_stage_entries()`,
261  * though it doesn't have to make progress while the queue is drained.  In fact, once it
262  * detects that the queue is being drained, it blocks until the draining is done.
263  *
264  * The protocol must guarantee that entries in the queue are causally ordered, otherwise two
265  * entries for the same location might get switched, resulting in the earlier one being
266  * committed and the later one ignored.
267  *
268  * `next_entry` is the index of the next entry to be filled, or `-1` if the queue is
269  * currently being drained.  Each entry has a state:
270  *
271  * `STAGE_ENTRY_FREE`: The entry is free.  Its data fields must be `NULL`.
272  *
273  * `STAGE_ENTRY_BUSY`: The entry is currently being filled in.
274  *
275  * `STAGE_ENTRY_USED`: The entry is completely filled in and must be processed in the next
276  * draining round.
277  *
278  * `STAGE_ENTRY_INVALID`: The entry was busy during queue draining and therefore
279  * invalidated.  Entries that are `BUSY` can obviously not be processed during a drain, but
280  * we can't leave them in place because new entries might be inserted before them, including
281  * from the same thread, violating causality.  An alternative would be not to reset
282  * `next_entry` to `0` after a drain, but to the index of the last `BUSY` entry plus one,
283  * but that can potentially waste the whole queue.
284  *
285  * State transitions:
286  *
287  * | from    | to      | filler? | drainer? |
288  * +---------+---------+---------+----------+
289  * | FREE    | BUSY    | X       |          |
290  * | BUSY    | FREE    | X       |          |
291  * | BUSY    | USED    | X       |          |
292  * | BUSY    | INVALID |         | X        |
293  * | USED    | FREE    |         | X        |
294  * | INVALID | FREE    | X       |          |
295  *
296  * `next_entry` can be incremented either by the filler thread that set the corresponding
297  * entry to `BUSY`, or by another filler thread that's trying to get a `FREE` slot.  If that
298  * other thread wasn't allowed to increment, it would block on the first filler thread.
299  *
300  * An entry's state, once it's set from `FREE` to `BUSY` by a filler thread, can only be
301  * changed by that same thread or by the drained.  The drainer can only set a `BUSY` thread
302  * to `INVALID`, so it needs to be set to `FREE` again by the original filler thread.
303  */
304
305 #define STAGE_ENTRY_FREE        0
306 #define STAGE_ENTRY_BUSY        1
307 #define STAGE_ENTRY_USED        2
308 #define STAGE_ENTRY_INVALID     3
309
310 typedef struct {
311         volatile gint32 state;
312         GCObject *obj;
313         void *user_data;
314 } StageEntry;
315
316 #define NUM_FIN_STAGE_ENTRIES   1024
317
318 static volatile gint32 next_fin_stage_entry = 0;
319 static StageEntry fin_stage_entries [NUM_FIN_STAGE_ENTRIES];
320
321 /*
322  * This is used to lock the stage when processing is forced, i.e. when it's triggered by a
323  * garbage collection.  In that case, the world is already stopped and there's only one
324  * thread operating on the queue.
325  */
326 static void
327 lock_stage_for_processing (volatile gint32 *next_entry)
328 {
329         *next_entry = -1;
330 }
331
332 /*
333  * When processing is triggered by an overflow, we don't want to take the GC lock
334  * immediately, and then set `next_index` to `-1`, because another thread might have drained
335  * the queue in the mean time.  Instead, we make sure the overflow is still there, we
336  * atomically set `next_index`, and only once that happened do we take the GC lock.
337  */
338 static gboolean
339 try_lock_stage_for_processing (int num_entries, volatile gint32 *next_entry)
340 {
341         gint32 old = *next_entry;
342         if (old < num_entries)
343                 return FALSE;
344         return InterlockedCompareExchange (next_entry, -1, old) == old;
345 }
346
347 /* LOCKING: requires that the GC lock is held */
348 static void
349 process_stage_entries (int num_entries, volatile gint32 *next_entry, StageEntry *entries, void (*process_func) (GCObject*, void*, int))
350 {
351         int i;
352
353         /*
354          * This can happen if after setting `next_index` to `-1` in
355          * `try_lock_stage_for_processing()`, a GC was triggered, which then drained the
356          * queue and reset `next_entry`.
357          *
358          * We have the GC lock now, so if it's still `-1`, we can't be interrupted by a GC.
359          */
360         if (*next_entry != -1)
361                 return;
362
363         for (i = 0; i < num_entries; ++i) {
364                 gint32 state;
365
366         retry:
367                 state = entries [i].state;
368
369                 switch (state) {
370                 case STAGE_ENTRY_FREE:
371                 case STAGE_ENTRY_INVALID:
372                         continue;
373                 case STAGE_ENTRY_BUSY:
374                         /* BUSY -> INVALID */
375                         /*
376                          * This must be done atomically, because the filler thread can set
377                          * the entry to `USED`, in which case we must process it, so we must
378                          * detect that eventuality.
379                          */
380                         if (InterlockedCompareExchange (&entries [i].state, STAGE_ENTRY_INVALID, STAGE_ENTRY_BUSY) != STAGE_ENTRY_BUSY)
381                                 goto retry;
382                         continue;
383                 case STAGE_ENTRY_USED:
384                         break;
385                 default:
386                         SGEN_ASSERT (0, FALSE, "Invalid stage entry state");
387                         break;
388                 }
389
390                 /* state is USED */
391
392                 process_func (entries [i].obj, entries [i].user_data, i);
393
394                 entries [i].obj = NULL;
395                 entries [i].user_data = NULL;
396
397                 mono_memory_write_barrier ();
398
399                 /* USED -> FREE */
400                 /*
401                  * This transition only happens here, so we don't have to do it atomically.
402                  */
403                 entries [i].state = STAGE_ENTRY_FREE;
404         }
405
406         mono_memory_write_barrier ();
407
408         *next_entry = 0;
409 }
410
411 #ifdef HEAVY_STATISTICS
412 static guint64 stat_overflow_abort = 0;
413 static guint64 stat_wait_for_processing = 0;
414 static guint64 stat_increment_other_thread = 0;
415 static guint64 stat_index_decremented = 0;
416 static guint64 stat_entry_invalidated = 0;
417 static guint64 stat_success = 0;
418 #endif
419
420 static int
421 add_stage_entry (int num_entries, volatile gint32 *next_entry, StageEntry *entries, GCObject *obj, void *user_data)
422 {
423         gint32 index, new_next_entry, old_next_entry;
424         gint32 previous_state;
425
426  retry:
427         for (;;) {
428                 index = *next_entry;
429                 if (index >= num_entries) {
430                         HEAVY_STAT (++stat_overflow_abort);
431                         return -1;
432                 }
433                 if (index < 0) {
434                         /*
435                          * Backed-off waiting is way more efficient than even using a
436                          * dedicated lock for this.
437                          */
438                         while ((index = *next_entry) < 0) {
439                                 /*
440                                  * This seems like a good value.  Determined by timing
441                                  * sgen-weakref-stress.exe.
442                                  */
443                                 mono_thread_info_usleep (200);
444                                 HEAVY_STAT (++stat_wait_for_processing);
445                         }
446                         continue;
447                 }
448                 /* FREE -> BUSY */
449                 if (entries [index].state != STAGE_ENTRY_FREE ||
450                                 InterlockedCompareExchange (&entries [index].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_FREE) != STAGE_ENTRY_FREE) {
451                         /*
452                          * If we can't get the entry it must be because another thread got
453                          * it first.  We don't want to wait for that thread to increment
454                          * `next_entry`, so we try to do it ourselves.  Whether we succeed
455                          * or not, we start over.
456                          */
457                         if (*next_entry == index) {
458                                 InterlockedCompareExchange (next_entry, index + 1, index);
459                                 //g_print ("tried increment for other thread\n");
460                                 HEAVY_STAT (++stat_increment_other_thread);
461                         }
462                         continue;
463                 }
464                 /* state is BUSY now */
465                 mono_memory_write_barrier ();
466                 /*
467                  * Incrementing `next_entry` must happen after setting the state to `BUSY`.
468                  * If it were the other way around, it would be possible that after a filler
469                  * incremented the index, other threads fill up the queue, the queue is
470                  * drained, the original filler finally fills in the slot, but `next_entry`
471                  * ends up at the start of the queue, and new entries are written in the
472                  * queue in front of, not behind, the original filler's entry.
473                  *
474                  * We don't actually require that the CAS succeeds, but we do require that
475                  * the value of `next_entry` is not lower than our index.  Since the drainer
476                  * sets it to `-1`, that also takes care of the case that the drainer is
477                  * currently running.
478                  */
479                 old_next_entry = InterlockedCompareExchange (next_entry, index + 1, index);
480                 if (old_next_entry < index) {
481                         /* BUSY -> FREE */
482                         /* INVALID -> FREE */
483                         /*
484                          * The state might still be `BUSY`, or the drainer could have set it
485                          * to `INVALID`.  In either case, there's no point in CASing.  Set
486                          * it to `FREE` and start over.
487                          */
488                         entries [index].state = STAGE_ENTRY_FREE;
489                         HEAVY_STAT (++stat_index_decremented);
490                         continue;
491                 }
492                 break;
493         }
494
495         SGEN_ASSERT (0, index >= 0 && index < num_entries, "Invalid index");
496
497         entries [index].obj = obj;
498         entries [index].user_data = user_data;
499
500         mono_memory_write_barrier ();
501
502         new_next_entry = *next_entry;
503         mono_memory_read_barrier ();
504         /* BUSY -> USED */
505         /*
506          * A `BUSY` entry will either still be `BUSY` or the drainer will have set it to
507          * `INVALID`.  In the former case, we set it to `USED` and we're finished.  In the
508          * latter case, we reset it to `FREE` and start over.
509          */
510         previous_state = InterlockedCompareExchange (&entries [index].state, STAGE_ENTRY_USED, STAGE_ENTRY_BUSY);
511         if (previous_state == STAGE_ENTRY_BUSY) {
512                 SGEN_ASSERT (0, new_next_entry >= index || new_next_entry < 0, "Invalid next entry index - as long as we're busy, other thread can only increment or invalidate it");
513                 HEAVY_STAT (++stat_success);
514                 return index;
515         }
516
517         SGEN_ASSERT (0, previous_state == STAGE_ENTRY_INVALID, "Invalid state transition - other thread can only make busy state invalid");
518         entries [index].obj = NULL;
519         entries [index].user_data = NULL;
520         mono_memory_write_barrier ();
521         /* INVALID -> FREE */
522         entries [index].state = STAGE_ENTRY_FREE;
523
524         HEAVY_STAT (++stat_entry_invalidated);
525
526         goto retry;
527 }
528
529 /* LOCKING: requires that the GC lock is held */
530 static void
531 process_fin_stage_entry (GCObject *obj, void *user_data, int index)
532 {
533         if (ptr_in_nursery (obj))
534                 register_for_finalization (obj, user_data, GENERATION_NURSERY);
535         else
536                 register_for_finalization (obj, user_data, GENERATION_OLD);
537 }
538
539 /* LOCKING: requires that the GC lock is held */
540 void
541 sgen_process_fin_stage_entries (void)
542 {
543         lock_stage_for_processing (&next_fin_stage_entry);
544         process_stage_entries (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, process_fin_stage_entry);
545 }
546
547 void
548 sgen_object_register_for_finalization (GCObject *obj, void *user_data)
549 {
550         while (add_stage_entry (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, obj, user_data) == -1) {
551                 if (try_lock_stage_for_processing (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry)) {
552                         LOCK_GC;
553                         process_stage_entries (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, process_fin_stage_entry);
554                         UNLOCK_GC;
555                 }
556         }
557 }
558
559 /* LOCKING: requires that the GC lock is held */
560 static int
561 finalizers_with_predicate (SgenObjectPredicateFunc predicate, void *user_data, GCObject **out_array, int out_size, SgenHashTable *hash_table)
562 {
563         GCObject *object;
564         gpointer dummy G_GNUC_UNUSED;
565         int count;
566
567         if (no_finalize || !out_size || !out_array)
568                 return 0;
569         count = 0;
570         SGEN_HASH_TABLE_FOREACH (hash_table, GCObject *, object, gpointer, dummy) {
571                 object = tagged_object_get_object (object);
572
573                 if (predicate (object, user_data)) {
574                         /* remove and put in out_array */
575                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
576                         out_array [count ++] = object;
577                         SGEN_LOG (5, "Collecting object for finalization: %p (%s) (%d)", object, sgen_client_vtable_get_name (SGEN_LOAD_VTABLE (object)), sgen_hash_table_num_entries (hash_table));
578                         if (count == out_size)
579                                 return count;
580                         continue;
581                 }
582         } SGEN_HASH_TABLE_FOREACH_END;
583         return count;
584 }
585
586 /**
587  * sgen_gather_finalizers_if:
588  * @predicate: predicate function
589  * @user_data: predicate function data argument
590  * @out_array: output array
591  * @out_size: size of output array
592  *
593  * Store inside @out_array up to @out_size objects that match @predicate. Returns the number
594  * of stored items. Can be called repeteadly until it returns 0.
595  *
596  * The items are removed from the finalizer data structure, so the caller is supposed
597  * to finalize them.
598  *
599  * @out_array me be on the stack, or registered as a root, to allow the GC to know the
600  * objects are still alive.
601  */
602 int
603 sgen_gather_finalizers_if (SgenObjectPredicateFunc predicate, void *user_data, GCObject **out_array, int out_size)
604 {
605         int result;
606
607         LOCK_GC;
608         sgen_process_fin_stage_entries ();
609         result = finalizers_with_predicate (predicate, user_data, (GCObject**)out_array, out_size, &minor_finalizable_hash);
610         if (result < out_size) {
611                 result += finalizers_with_predicate (predicate, user_data, (GCObject**)out_array + result, out_size - result,
612                         &major_finalizable_hash);
613         }
614         UNLOCK_GC;
615
616         return result;
617 }
618
619 void
620 sgen_remove_finalizers_if (SgenObjectPredicateFunc predicate, void *user_data, int generation)
621 {
622         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
623         GCObject *object;
624         gpointer dummy G_GNUC_UNUSED;
625
626         SGEN_HASH_TABLE_FOREACH (hash_table, GCObject *, object, gpointer, dummy) {
627                 object = tagged_object_get_object (object);
628
629                 if (predicate (object, user_data)) {
630                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
631                         continue;
632                 }
633         } SGEN_HASH_TABLE_FOREACH_END;  
634 }
635
636 void
637 sgen_init_fin_weak_hash (void)
638 {
639 #ifdef HEAVY_STATISTICS
640         mono_counters_register ("FinWeak Successes", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_success);
641         mono_counters_register ("FinWeak Overflow aborts", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_overflow_abort);
642         mono_counters_register ("FinWeak Wait for processing", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_wait_for_processing);
643         mono_counters_register ("FinWeak Increment other thread", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_increment_other_thread);
644         mono_counters_register ("FinWeak Index decremented", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_index_decremented);
645         mono_counters_register ("FinWeak Entry invalidated", MONO_COUNTER_GC | MONO_COUNTER_ULONG, &stat_entry_invalidated);
646 #endif
647 }
648
649 #endif /* HAVE_SGEN_GC */