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