2 * sgen-fin-weak-hash.c: Finalizers and weak links.
5 * Paolo Molaro (lupus@ximian.com)
6 * Rodrigo Kumpera (kumpera@gmail.com)
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
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License 2.0 as published by the Free Software Foundation;
17 * This library is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Library General Public License for more details.
22 * You should have received a copy of the GNU Library General Public
23 * License 2.0 along with this library; if not, write to the Free
24 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
30 #include "metadata/sgen-gc.h"
31 #include "metadata/sgen-gray.h"
32 #include "metadata/sgen-protocol.h"
33 #include "utils/dtrace.h"
35 #define ptr_in_nursery sgen_ptr_in_nursery
37 typedef SgenGrayQueue GrayQueue;
39 int num_ready_finalizers = 0;
40 static int no_finalize = 0;
42 #define DISLINK_OBJECT(l) (REVEAL_POINTER (*(void**)(l)))
43 #define DISLINK_TRACK(l) ((~(gulong)(*(void**)(l))) & 1)
46 * The finalizable hash has the object as the key, the
47 * disappearing_link hash, has the link address as key.
49 * Copyright 2011 Xamarin Inc.
52 #define TAG_MASK ((mword)0x1)
54 static inline MonoObject*
55 tagged_object_get_object (MonoObject *object)
57 return (MonoObject*)(((mword)object) & ~TAG_MASK);
61 tagged_object_get_tag (MonoObject *object)
63 return ((mword)object) & TAG_MASK;
66 static inline MonoObject*
67 tagged_object_apply (void *object, int tag_bits)
69 return (MonoObject*)((mword)object | (mword)tag_bits);
73 tagged_object_hash (MonoObject *o)
75 return mono_object_hash (tagged_object_get_object (o));
79 tagged_object_equals (MonoObject *a, MonoObject *b)
81 return tagged_object_get_object (a) == tagged_object_get_object (b);
84 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);
85 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);
88 get_finalize_entry_hash_table (int generation)
91 case GENERATION_NURSERY: return &minor_finalizable_hash;
92 case GENERATION_OLD: return &major_finalizable_hash;
93 default: g_assert_not_reached ();
97 #define BRIDGE_OBJECT_MARKED 0x1
99 /* LOCKING: requires that the GC lock is held */
101 sgen_mark_bridge_object (MonoObject *obj)
103 SgenHashTable *hash_table = get_finalize_entry_hash_table (ptr_in_nursery (obj) ? GENERATION_NURSERY : GENERATION_OLD);
105 sgen_hash_table_set_key (hash_table, obj, tagged_object_apply (obj, BRIDGE_OBJECT_MARKED));
108 /* LOCKING: requires that the GC lock is held */
110 sgen_collect_bridge_objects (int generation, ScanCopyContext ctx)
112 CopyOrMarkObjectFunc copy_func = ctx.copy_func;
113 GrayQueue *queue = ctx.queue;
114 SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
122 SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
123 int tag = tagged_object_get_tag (object);
124 object = tagged_object_get_object (object);
126 /* Bridge code told us to ignore this one */
127 if (tag == BRIDGE_OBJECT_MARKED)
130 /* Object is a bridge object and major heap says it's dead */
131 if (major_collector.is_object_live ((char*)object))
134 /* Nursery says the object is dead. */
135 if (!sgen_gc_is_object_ready_for_finalization (object))
138 if (!sgen_is_bridge_object (object))
141 copy = (char*)object;
142 copy_func ((void**)©, queue);
144 sgen_bridge_register_finalized_object ((MonoObject*)copy);
146 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
147 /* remove from the list */
148 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
150 /* insert it into the major hash */
151 sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
153 SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
158 SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
159 SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
161 } SGEN_HASH_TABLE_FOREACH_END;
165 /* LOCKING: requires that the GC lock is held */
167 sgen_finalize_in_range (int generation, ScanCopyContext ctx)
169 CopyOrMarkObjectFunc copy_func = ctx.copy_func;
170 GrayQueue *queue = ctx.queue;
171 SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
177 SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
178 int tag = tagged_object_get_tag (object);
179 object = tagged_object_get_object (object);
180 if (!major_collector.is_object_live ((char*)object)) {
181 gboolean is_fin_ready = sgen_gc_is_object_ready_for_finalization (object);
182 MonoObject *copy = object;
183 copy_func ((void**)©, queue);
185 /* remove and put in fin_ready_list */
186 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
187 num_ready_finalizers++;
188 sgen_queue_finalization_entry (copy);
189 /* Make it survive */
190 SGEN_LOG (5, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)", copy, sgen_safe_name (copy), object, num_ready_finalizers, sgen_hash_table_num_entries (hash_table));
193 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
194 /* remove from the list */
195 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
197 /* insert it into the major hash */
198 sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
200 SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
205 SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
206 SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
210 } SGEN_HASH_TABLE_FOREACH_END;
213 /* LOCKING: requires that the GC lock is held */
215 register_for_finalization (MonoObject *obj, void *user_data, int generation)
217 SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
222 g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
225 if (sgen_hash_table_replace (hash_table, obj, NULL, NULL))
226 SGEN_LOG (5, "Added finalizer for object: %p (%s) (%d) to %s table", obj, obj->vtable->klass->name, hash_table->num_entries, sgen_generation_name (generation));
228 if (sgen_hash_table_remove (hash_table, obj, NULL))
229 SGEN_LOG (5, "Removed finalizer for object: %p (%s) (%d)", obj, obj->vtable->klass->name, hash_table->num_entries);
233 #define STAGE_ENTRY_FREE 0
234 #define STAGE_ENTRY_BUSY 1
235 #define STAGE_ENTRY_USED 2
243 #define NUM_FIN_STAGE_ENTRIES 1024
245 static volatile gint32 next_fin_stage_entry = 0;
246 static StageEntry fin_stage_entries [NUM_FIN_STAGE_ENTRIES];
248 /* LOCKING: requires that the GC lock is held */
250 process_stage_entries (int num_entries, volatile gint32 *next_entry, StageEntry *entries, void (*process_func) (MonoObject*, void*))
253 int num_registered = 0;
256 for (i = 0; i < num_entries; ++i) {
257 gint32 state = entries [i].state;
259 if (state == STAGE_ENTRY_BUSY)
262 if (state != STAGE_ENTRY_USED ||
263 InterlockedCompareExchange (&entries [i].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_USED) != STAGE_ENTRY_USED) {
267 process_func (entries [i].obj, entries [i].user_data);
269 entries [i].obj = NULL;
270 entries [i].user_data = NULL;
272 mono_memory_write_barrier ();
274 entries [i].state = STAGE_ENTRY_FREE;
281 /* g_print ("stage busy %d reg %d\n", num_busy, num_registered); */
285 add_stage_entry (int num_entries, volatile gint32 *next_entry, StageEntry *entries, MonoObject *obj, void *user_data)
292 if (index >= num_entries)
294 } while (InterlockedCompareExchange (next_entry, index + 1, index) != index);
297 * We don't need a write barrier here. *next_entry is just a
298 * help for finding an index, its value is irrelevant for
301 } while (entries [index].state != STAGE_ENTRY_FREE ||
302 InterlockedCompareExchange (&entries [index].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_FREE) != STAGE_ENTRY_FREE);
304 entries [index].obj = obj;
305 entries [index].user_data = user_data;
307 mono_memory_write_barrier ();
309 entries [index].state = STAGE_ENTRY_USED;
314 /* LOCKING: requires that the GC lock is held */
316 process_fin_stage_entry (MonoObject *obj, void *user_data)
318 if (ptr_in_nursery (obj))
319 register_for_finalization (obj, user_data, GENERATION_NURSERY);
321 register_for_finalization (obj, user_data, GENERATION_OLD);
324 /* LOCKING: requires that the GC lock is held */
326 sgen_process_fin_stage_entries (void)
328 process_stage_entries (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, process_fin_stage_entry);
332 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
334 while (!add_stage_entry (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, obj, user_data)) {
336 sgen_process_fin_stage_entries ();
341 /* LOCKING: requires that the GC lock is held */
343 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
344 SgenHashTable *hash_table)
350 if (no_finalize || !out_size || !out_array)
353 SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
354 object = tagged_object_get_object (object);
356 if (mono_object_domain (object) == domain) {
357 /* remove and put in out_array */
358 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
359 out_array [count ++] = object;
360 SGEN_LOG (5, "Collecting object for finalization: %p (%s) (%d/%d)", object, sgen_safe_name (object), num_ready_finalizers, sgen_hash_table_num_entries (hash_table));
361 if (count == out_size)
365 } SGEN_HASH_TABLE_FOREACH_END;
370 * mono_gc_finalizers_for_domain:
371 * @domain: the unloading appdomain
372 * @out_array: output array
373 * @out_size: size of output array
375 * Store inside @out_array up to @out_size objects that belong to the unloading
376 * appdomain @domain. Returns the number of stored items. Can be called repeteadly
377 * until it returns 0.
378 * The items are removed from the finalizer data structure, so the caller is supposed
380 * @out_array should be on the stack to allow the GC to know the objects are still alive.
383 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
388 sgen_process_fin_stage_entries ();
389 result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
390 if (result < out_size) {
391 result += finalizers_for_domain (domain, out_array + result, out_size - result,
392 &major_finalizable_hash);
399 static SgenHashTable minor_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
400 static SgenHashTable major_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
402 static SgenHashTable*
403 get_dislink_hash_table (int generation)
405 switch (generation) {
406 case GENERATION_NURSERY: return &minor_disappearing_link_hash;
407 case GENERATION_OLD: return &major_disappearing_link_hash;
408 default: g_assert_not_reached ();
412 /* LOCKING: assumes the GC lock is held */
414 add_or_remove_disappearing_link (MonoObject *obj, void **link, int generation)
416 SgenHashTable *hash_table = get_dislink_hash_table (generation);
419 if (sgen_hash_table_remove (hash_table, link, NULL)) {
420 SGEN_LOG (5, "Removed dislink %p (%d) from %s table",
421 link, hash_table->num_entries, sgen_generation_name (generation));
426 sgen_hash_table_replace (hash_table, link, NULL, NULL);
427 SGEN_LOG (5, "Added dislink for object: %p (%s) at %p to %s table",
428 obj, obj->vtable->klass->name, link, sgen_generation_name (generation));
431 /* LOCKING: requires that the GC lock is held */
433 sgen_null_link_in_range (int generation, gboolean before_finalization, ScanCopyContext ctx)
435 CopyOrMarkObjectFunc copy_func = ctx.copy_func;
436 GrayQueue *queue = ctx.queue;
439 SgenHashTable *hash = get_dislink_hash_table (generation);
441 SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
446 We null a weak link before unregistering it, so it's possible that a thread is
447 suspended right in between setting the content to null and staging the unregister.
449 The rest of this code cannot handle null links as DISLINK_OBJECT (NULL) produces an invalid address.
451 We should simply skip the entry as the staged removal will take place during the next GC.
454 SGEN_LOG (5, "Dislink %p was externally nullified", link);
458 track = DISLINK_TRACK (link);
460 * Tracked references are processed after
461 * finalization handling whereas standard weak
462 * references are processed before. If an
463 * object is still not marked after finalization
464 * handling it means that it either doesn't have
465 * a finalizer or the finalizer has already run,
466 * so we must null a tracking reference.
468 if (track != before_finalization) {
469 object = DISLINK_OBJECT (link);
471 We should guard against a null object been hidden. This can sometimes happen.
474 SGEN_LOG (5, "Dislink %p with a hidden null object", link);
478 if (!major_collector.is_object_live (object)) {
479 if (sgen_gc_is_object_ready_for_finalization (object)) {
481 binary_protocol_dislink_update (link, NULL, 0);
482 SGEN_LOG (5, "Dislink nullified at %p to GCed object %p", link, object);
483 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
487 copy_func ((void**)©, queue);
489 /* Update pointer if it's moved. If the object
490 * has been moved out of the nursery, we need to
491 * remove the link from the minor hash table to
494 * FIXME: what if an object is moved earlier?
497 if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
498 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
501 *link = HIDE_POINTER (copy, track);
502 add_or_remove_disappearing_link ((MonoObject*)copy, link, GENERATION_OLD);
503 binary_protocol_dislink_update (link, copy, track);
505 SGEN_LOG (5, "Upgraded dislink at %p to major because object %p moved to %p", link, object, copy);
509 *link = HIDE_POINTER (copy, track);
510 binary_protocol_dislink_update (link, copy, track);
511 SGEN_LOG (5, "Updated dislink at %p to %p", link, DISLINK_OBJECT (link));
516 } SGEN_HASH_TABLE_FOREACH_END;
519 /* LOCKING: requires that the GC lock is held */
521 sgen_null_links_for_domain (MonoDomain *domain, int generation)
525 SgenHashTable *hash = get_dislink_hash_table (generation);
526 SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
527 char *object = DISLINK_OBJECT (link);
528 if (*link && object && !((MonoObject*)object)->vtable) {
529 gboolean free = TRUE;
533 binary_protocol_dislink_update (link, NULL, 0);
536 * This can happen if finalizers are not ran, i.e. Environment.Exit ()
537 * is called from finalizer like in finalizer-abort.cs.
539 SGEN_LOG (5, "Disappearing link %p not freed", link);
542 SGEN_HASH_TABLE_FOREACH_REMOVE (free);
546 } SGEN_HASH_TABLE_FOREACH_END;
549 /* LOCKING: requires that the GC lock is held */
551 sgen_null_links_with_predicate (int generation, WeakLinkAlivePredicateFunc predicate, void *data)
555 SgenHashTable *hash = get_dislink_hash_table (generation);
556 SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
557 char *object = DISLINK_OBJECT (link);
562 is_alive = predicate ((MonoObject*)object, data);
566 binary_protocol_dislink_update (link, NULL, 0);
567 SGEN_LOG (5, "Dislink nullified by predicate at %p to GCed object %p", link, object);
568 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
571 } SGEN_HASH_TABLE_FOREACH_END;
575 sgen_remove_finalizers_for_domain (MonoDomain *domain, int generation)
577 SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
581 SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
582 object = tagged_object_get_object (object);
584 if (mono_object_domain (object) == domain) {
585 SGEN_LOG (5, "Unregistering finalizer for object: %p (%s)", object, sgen_safe_name (object));
587 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
590 } SGEN_HASH_TABLE_FOREACH_END;
593 /* LOCKING: requires that the GC lock is held */
595 process_dislink_stage_entry (MonoObject *obj, void *_link)
599 add_or_remove_disappearing_link (NULL, link, GENERATION_NURSERY);
600 add_or_remove_disappearing_link (NULL, link, GENERATION_OLD);
602 if (ptr_in_nursery (obj))
603 add_or_remove_disappearing_link (obj, link, GENERATION_NURSERY);
605 add_or_remove_disappearing_link (obj, link, GENERATION_OLD);
609 #define NUM_DISLINK_STAGE_ENTRIES 1024
611 static volatile gint32 next_dislink_stage_entry = 0;
612 static StageEntry dislink_stage_entries [NUM_DISLINK_STAGE_ENTRIES];
614 /* LOCKING: requires that the GC lock is held */
616 sgen_process_dislink_stage_entries (void)
618 process_stage_entries (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, process_dislink_stage_entry);
622 sgen_register_disappearing_link (MonoObject *obj, void **link, gboolean track, gboolean in_gc)
626 if (MONO_GC_WEAK_UPDATE_ENABLED ()) {
627 MonoVTable *vt = obj ? (MonoVTable*)SGEN_LOAD_VTABLE (obj) : NULL;
628 MONO_GC_WEAK_UPDATE ((mword)link,
629 *link ? (mword)DISLINK_OBJECT (link) : (mword)0,
631 obj ? (mword)sgen_safe_object_get_size (obj) : (mword)0,
632 obj ? vt->klass->name_space : NULL,
633 obj ? vt->klass->name : NULL,
639 *link = HIDE_POINTER (obj, track);
643 binary_protocol_dislink_update (link, obj, track);
647 process_dislink_stage_entry (obj, link);
649 while (!add_stage_entry (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, obj, link)) {
651 sgen_process_dislink_stage_entries ();
658 process_dislink_stage_entry (obj, link);
664 #endif /* HAVE_SGEN_GC */