20cff63039a77c4293e249e340f461435492a2ec
[mono.git] / mono / metadata / 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  * 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;
16  *
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.
21  *
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.
25  */
26
27 #include "config.h"
28 #ifdef HAVE_SGEN_GC
29
30 #include "metadata/sgen-gc.h"
31 #include "metadata/sgen-gray.h"
32
33 #define ptr_in_nursery sgen_ptr_in_nursery
34
35 typedef SgenGrayQueue GrayQueue;
36
37 int num_ready_finalizers = 0;
38 static int no_finalize = 0;
39
40 #define DISLINK_OBJECT(l)       (REVEAL_POINTER (*(void**)(l)))
41 #define DISLINK_TRACK(l)        ((~(gulong)(*(void**)(l))) & 1)
42
43 /*
44  * The finalizable hash has the object as the key, the 
45  * disappearing_link hash, has the link address as key.
46  *
47  * Copyright 2011 Xamarin Inc.
48  */
49
50 #define TAG_MASK ((mword)0x1)
51
52 static inline MonoObject*
53 tagged_object_get_object (MonoObject *object)
54 {
55         return (MonoObject*)(((mword)object) & ~TAG_MASK);
56 }
57
58 static inline int
59 tagged_object_get_tag (MonoObject *object)
60 {
61         return ((mword)object) & TAG_MASK;
62 }
63
64 static inline MonoObject*
65 tagged_object_apply (void *object, int tag_bits)
66 {
67        return (MonoObject*)((mword)object | (mword)tag_bits);
68 }
69
70 static int
71 tagged_object_hash (MonoObject *o)
72 {
73         return mono_object_hash (tagged_object_get_object (o));
74 }
75
76 static gboolean
77 tagged_object_equals (MonoObject *a, MonoObject *b)
78 {
79         return tagged_object_get_object (a) == tagged_object_get_object (b);
80 }
81
82 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);
83 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);
84
85 static SgenHashTable*
86 get_finalize_entry_hash_table (int generation)
87 {
88         switch (generation) {
89         case GENERATION_NURSERY: return &minor_finalizable_hash;
90         case GENERATION_OLD: return &major_finalizable_hash;
91         default: g_assert_not_reached ();
92         }
93 }
94
95 #define BRIDGE_OBJECT_MARKED 0x1
96
97 /* LOCKING: requires that the GC lock is held */
98 void
99 sgen_mark_bridge_object (MonoObject *obj)
100 {
101         SgenHashTable *hash_table = get_finalize_entry_hash_table (ptr_in_nursery (obj) ? GENERATION_NURSERY : GENERATION_OLD);
102
103         sgen_hash_table_set_key (hash_table, obj, tagged_object_apply (obj, BRIDGE_OBJECT_MARKED));
104 }
105
106 /* LOCKING: requires that the GC lock is held */
107 void
108 sgen_collect_bridge_objects (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
109 {
110         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
111         MonoObject *object;
112         gpointer dummy;
113         char *copy;
114
115         if (no_finalize)
116                 return;
117
118         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
119                 int tag = tagged_object_get_tag (object);
120                 object = tagged_object_get_object (object);
121
122                 /* Bridge code told us to ignore this one */
123                 if (tag == BRIDGE_OBJECT_MARKED)
124                         continue;
125
126                 /* Object is a bridge object and major heap says it's dead  */
127                 if (!((char*)object >= start && (char*)object < end && !major_collector.is_object_live ((char*)object)))
128                         continue;
129
130                 /* Nursery says the object is dead. */
131                 if (!sgen_gc_is_object_ready_for_finalization (object))
132                         continue;
133
134                 if (!sgen_is_bridge_object (object))
135                         continue;
136
137                 copy = (char*)object;
138                 copy_func ((void**)&copy, queue);
139
140                 sgen_bridge_register_finalized_object ((MonoObject*)copy);
141                 
142                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
143                         /* remove from the list */
144                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
145
146                         /* insert it into the major hash */
147                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
148
149                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
150
151                         continue;
152                 } else {
153                         /* update pointer */
154                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
155                         SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
156                 }
157         } SGEN_HASH_TABLE_FOREACH_END;
158 }
159
160
161 /* LOCKING: requires that the GC lock is held */
162 void
163 sgen_finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
164 {
165         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
166         MonoObject *object;
167         gpointer dummy;
168
169         if (no_finalize)
170                 return;
171         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
172                 int tag = tagged_object_get_tag (object);
173                 object = tagged_object_get_object (object);
174                 if ((char*)object >= start && (char*)object < end && !major_collector.is_object_live ((char*)object)) {
175                         gboolean is_fin_ready = sgen_gc_is_object_ready_for_finalization (object);
176                         MonoObject *copy = object;
177                         copy_func ((void**)&copy, queue);
178                         if (is_fin_ready) {
179                                 /* remove and put in fin_ready_list */
180                                 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
181                                 num_ready_finalizers++;
182                                 sgen_queue_finalization_entry (copy);
183                                 /* Make it survive */
184                                 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));
185                                 continue;
186                         } else {
187                                 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
188                                         /* remove from the list */
189                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
190
191                                         /* insert it into the major hash */
192                                         sgen_hash_table_replace (&major_finalizable_hash, tagged_object_apply (copy, tag), NULL, NULL);
193
194                                         SGEN_LOG (5, "Promoting finalization of object %p (%s) (was at %p) to major table", copy, sgen_safe_name (copy), object);
195
196                                         continue;
197                                 } else {
198                                         /* update pointer */
199                                         SGEN_LOG (5, "Updating object for finalization: %p (%s) (was at %p)", copy, sgen_safe_name (copy), object);
200                                         SGEN_HASH_TABLE_FOREACH_SET_KEY (tagged_object_apply (copy, tag));
201                                 }
202                         }
203                 }
204         } SGEN_HASH_TABLE_FOREACH_END;
205 }
206
207 /* LOCKING: requires that the GC lock is held */
208 static void
209 register_for_finalization (MonoObject *obj, void *user_data, int generation)
210 {
211         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
212
213         if (no_finalize)
214                 return;
215
216         g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
217
218         if (user_data) {
219                 if (sgen_hash_table_replace (hash_table, obj, NULL, NULL))
220                         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));
221         } else {
222                 if (sgen_hash_table_remove (hash_table, obj, NULL))
223                         SGEN_LOG (5, "Removed finalizer for object: %p (%s) (%d)", obj, obj->vtable->klass->name, hash_table->num_entries);
224         }
225 }
226
227 #define STAGE_ENTRY_FREE        0
228 #define STAGE_ENTRY_BUSY        1
229 #define STAGE_ENTRY_USED        2
230
231 typedef struct {
232         gint32 state;
233         MonoObject *obj;
234         void *user_data;
235 } StageEntry;
236
237 #define NUM_FIN_STAGE_ENTRIES   1024
238
239 static volatile gint32 next_fin_stage_entry = 0;
240 static StageEntry fin_stage_entries [NUM_FIN_STAGE_ENTRIES];
241
242 /* LOCKING: requires that the GC lock is held */
243 static void
244 process_stage_entries (int num_entries, volatile gint32 *next_entry, StageEntry *entries, void (*process_func) (MonoObject*, void*))
245 {
246         int i;
247         int num_registered = 0;
248         int num_busy = 0;
249
250         for (i = 0; i < num_entries; ++i) {
251                 gint32 state = entries [i].state;
252
253                 if (state == STAGE_ENTRY_BUSY)
254                         ++num_busy;
255
256                 if (state != STAGE_ENTRY_USED ||
257                                 InterlockedCompareExchange (&entries [i].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_USED) != STAGE_ENTRY_USED) {
258                         continue;
259                 }
260
261                 process_func (entries [i].obj, entries [i].user_data);
262
263                 entries [i].obj = NULL;
264                 entries [i].user_data = NULL;
265
266                 mono_memory_write_barrier ();
267
268                 entries [i].state = STAGE_ENTRY_FREE;
269
270                 ++num_registered;
271         }
272
273         *next_entry = 0;
274
275         /* g_print ("stage busy %d reg %d\n", num_busy, num_registered); */
276 }
277
278 static gboolean
279 add_stage_entry (int num_entries, volatile gint32 *next_entry, StageEntry *entries, MonoObject *obj, void *user_data)
280 {
281         gint32 index;
282
283         do {
284                 do {
285                         index = *next_entry;
286                         if (index >= num_entries)
287                                 return FALSE;
288                 } while (InterlockedCompareExchange (next_entry, index + 1, index) != index);
289
290                 /*
291                  * We don't need a write barrier here.  *next_entry is just a
292                  * help for finding an index, its value is irrelevant for
293                  * correctness.
294                  */
295         } while (entries [index].state != STAGE_ENTRY_FREE ||
296                         InterlockedCompareExchange (&entries [index].state, STAGE_ENTRY_BUSY, STAGE_ENTRY_FREE) != STAGE_ENTRY_FREE);
297
298         entries [index].obj = obj;
299         entries [index].user_data = user_data;
300
301         mono_memory_write_barrier ();
302
303         entries [index].state = STAGE_ENTRY_USED;
304
305         return TRUE;
306 }
307
308 /* LOCKING: requires that the GC lock is held */
309 static void
310 process_fin_stage_entry (MonoObject *obj, void *user_data)
311 {
312         if (ptr_in_nursery (obj))
313                 register_for_finalization (obj, user_data, GENERATION_NURSERY);
314         else
315                 register_for_finalization (obj, user_data, GENERATION_OLD);
316 }
317
318 /* LOCKING: requires that the GC lock is held */
319 void
320 sgen_process_fin_stage_entries (void)
321 {
322         process_stage_entries (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, process_fin_stage_entry);
323 }
324
325 void
326 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
327 {
328         while (!add_stage_entry (NUM_FIN_STAGE_ENTRIES, &next_fin_stage_entry, fin_stage_entries, obj, user_data)) {
329                 LOCK_GC;
330                 sgen_process_fin_stage_entries ();
331                 UNLOCK_GC;
332         }
333 }
334
335 /* LOCKING: requires that the GC lock is held */
336 static int
337 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
338         SgenHashTable *hash_table)
339 {
340         MonoObject *object;
341         gpointer dummy;
342         int count;
343
344         if (no_finalize || !out_size || !out_array)
345                 return 0;
346         count = 0;
347         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
348                 object = tagged_object_get_object (object);
349
350                 if (mono_object_domain (object) == domain) {
351                         /* remove and put in out_array */
352                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
353                         out_array [count ++] = object;
354                         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));
355                         if (count == out_size)
356                                 return count;
357                         continue;
358                 }
359         } SGEN_HASH_TABLE_FOREACH_END;
360         return count;
361 }
362
363 /**
364  * mono_gc_finalizers_for_domain:
365  * @domain: the unloading appdomain
366  * @out_array: output array
367  * @out_size: size of output array
368  *
369  * Store inside @out_array up to @out_size objects that belong to the unloading
370  * appdomain @domain. Returns the number of stored items. Can be called repeteadly
371  * until it returns 0.
372  * The items are removed from the finalizer data structure, so the caller is supposed
373  * to finalize them.
374  * @out_array should be on the stack to allow the GC to know the objects are still alive.
375  */
376 int
377 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
378 {
379         int result;
380
381         LOCK_GC;
382         sgen_process_fin_stage_entries ();
383         result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
384         if (result < out_size) {
385                 result += finalizers_for_domain (domain, out_array + result, out_size - result,
386                         &major_finalizable_hash);
387         }
388         UNLOCK_GC;
389
390         return result;
391 }
392
393 static SgenHashTable minor_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
394 static SgenHashTable major_disappearing_link_hash = SGEN_HASH_TABLE_INIT (INTERNAL_MEM_DISLINK_TABLE, INTERNAL_MEM_DISLINK, 0, mono_aligned_addr_hash, NULL);
395
396 static SgenHashTable*
397 get_dislink_hash_table (int generation)
398 {
399         switch (generation) {
400         case GENERATION_NURSERY: return &minor_disappearing_link_hash;
401         case GENERATION_OLD: return &major_disappearing_link_hash;
402         default: g_assert_not_reached ();
403         }
404 }
405
406 /* LOCKING: assumes the GC lock is held */
407 static void
408 add_or_remove_disappearing_link (MonoObject *obj, void **link, int generation)
409 {
410         SgenHashTable *hash_table = get_dislink_hash_table (generation);
411
412         if (!obj) {
413                 if (sgen_hash_table_remove (hash_table, link, NULL)) {
414                         SGEN_LOG (5, "Removed dislink %p (%d) from %s table",
415                                         link, hash_table->num_entries, sgen_generation_name (generation));
416                 }
417                 return;
418         }
419
420         sgen_hash_table_replace (hash_table, link, NULL, NULL);
421         SGEN_LOG (5, "Added dislink for object: %p (%s) at %p to %s table",
422                         obj, obj->vtable->klass->name, link, sgen_generation_name (generation));
423 }
424
425 /* LOCKING: requires that the GC lock is held */
426 void
427 sgen_null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, gboolean before_finalization, GrayQueue *queue)
428 {
429         void **link;
430         gpointer dummy;
431         SgenHashTable *hash = get_dislink_hash_table (generation);
432
433         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
434                 char *object;
435                 gboolean track = DISLINK_TRACK (link);
436
437                 /*
438                  * Tracked references are processed after
439                  * finalization handling whereas standard weak
440                  * references are processed before.  If an
441                  * object is still not marked after finalization
442                  * handling it means that it either doesn't have
443                  * a finalizer or the finalizer has already run,
444                  * so we must null a tracking reference.
445                  */
446                 if (track != before_finalization) {
447                         object = DISLINK_OBJECT (link);
448
449                         if (object >= start && object < end && !major_collector.is_object_live (object)) {
450                                 if (sgen_gc_is_object_ready_for_finalization (object)) {
451                                         *link = NULL;
452                                         SGEN_LOG (5, "Dislink nullified at %p to GCed object %p", link, object);
453                                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
454                                         continue;
455                                 } else {
456                                         char *copy = object;
457                                         copy_func ((void**)&copy, queue);
458
459                                         /* Update pointer if it's moved.  If the object
460                                          * has been moved out of the nursery, we need to
461                                          * remove the link from the minor hash table to
462                                          * the major one.
463                                          *
464                                          * FIXME: what if an object is moved earlier?
465                                          */
466
467                                         if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
468                                                 SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
469
470                                                 g_assert (copy);
471                                                 *link = HIDE_POINTER (copy, track);
472                                                 add_or_remove_disappearing_link ((MonoObject*)copy, link, GENERATION_OLD);
473
474                                                 SGEN_LOG (5, "Upgraded dislink at %p to major because object %p moved to %p", link, object, copy);
475
476                                                 continue;
477                                         } else {
478                                                 *link = HIDE_POINTER (copy, track);
479                                                 SGEN_LOG (5, "Updated dislink at %p to %p", link, DISLINK_OBJECT (link));
480                                         }
481                                 }
482                         }
483                 }
484         } SGEN_HASH_TABLE_FOREACH_END;
485 }
486
487 /* LOCKING: requires that the GC lock is held */
488 void
489 sgen_null_links_for_domain (MonoDomain *domain, int generation)
490 {
491         void **link;
492         gpointer dummy;
493         SgenHashTable *hash = get_dislink_hash_table (generation);
494         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
495                 char *object = DISLINK_OBJECT (link);
496                 if (object && !((MonoObject*)object)->vtable) {
497                         gboolean free = TRUE;
498
499                         if (*link) {
500                                 *link = NULL;
501                                 free = FALSE;
502                                 /*
503                                  * This can happen if finalizers are not ran, i.e. Environment.Exit ()
504                                  * is called from finalizer like in finalizer-abort.cs.
505                                  */
506                                 SGEN_LOG (5, "Disappearing link %p not freed", link);
507                         }
508
509                         SGEN_HASH_TABLE_FOREACH_REMOVE (free);
510
511                         continue;
512                 }
513         } SGEN_HASH_TABLE_FOREACH_END;
514 }
515
516 /* LOCKING: requires that the GC lock is held */
517 void
518 sgen_null_links_with_predicate (int generation, WeakLinkAlivePredicateFunc predicate, void *data)
519 {
520         void **link;
521         gpointer dummy;
522         SgenHashTable *hash = get_dislink_hash_table (generation);
523         SGEN_HASH_TABLE_FOREACH (hash, link, dummy) {
524                 char *object = DISLINK_OBJECT (link);
525                 mono_bool is_alive = predicate ((MonoObject*)object, data);
526
527                 if (!is_alive) {
528                         *link = NULL;
529                         SGEN_LOG (5, "Dislink nullified by predicate at %p to GCed object %p", link, object);
530                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
531                         continue;
532                 }
533         } SGEN_HASH_TABLE_FOREACH_END;
534 }
535
536 void
537 sgen_remove_finalizers_for_domain (MonoDomain *domain, int generation)
538 {
539         SgenHashTable *hash_table = get_finalize_entry_hash_table (generation);
540         MonoObject *object;
541         gpointer dummy;
542
543         SGEN_HASH_TABLE_FOREACH (hash_table, object, dummy) {
544                 object = tagged_object_get_object (object);
545
546                 if (mono_object_domain (object) == domain) {
547                         SGEN_LOG (5, "Unregistering finalizer for object: %p (%s)", object, sgen_safe_name (object));
548
549                         SGEN_HASH_TABLE_FOREACH_REMOVE (TRUE);
550                         continue;
551                 }
552         } SGEN_HASH_TABLE_FOREACH_END;  
553 }
554
555 /* LOCKING: requires that the GC lock is held */
556 static void
557 process_dislink_stage_entry (MonoObject *obj, void *_link)
558 {
559         void **link = _link;
560
561         add_or_remove_disappearing_link (NULL, link, GENERATION_NURSERY);
562         add_or_remove_disappearing_link (NULL, link, GENERATION_OLD);
563         if (obj) {
564                 if (ptr_in_nursery (obj))
565                         add_or_remove_disappearing_link (obj, link, GENERATION_NURSERY);
566                 else
567                         add_or_remove_disappearing_link (obj, link, GENERATION_OLD);
568         }
569 }
570
571 #define NUM_DISLINK_STAGE_ENTRIES       1024
572
573 static volatile gint32 next_dislink_stage_entry = 0;
574 static StageEntry dislink_stage_entries [NUM_DISLINK_STAGE_ENTRIES];
575
576 /* LOCKING: requires that the GC lock is held */
577 void
578 sgen_process_dislink_stage_entries (void)
579 {
580         process_stage_entries (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, process_dislink_stage_entry);
581 }
582
583 void
584 sgen_register_disappearing_link (MonoObject *obj, void **link, gboolean track, gboolean in_gc)
585 {
586         if (obj)
587                 *link = HIDE_POINTER (obj, track);
588         else
589                 *link = NULL;
590
591 #if 1
592         if (in_gc) {
593                 process_dislink_stage_entry (obj, link);
594         } else {
595                 while (!add_stage_entry (NUM_DISLINK_STAGE_ENTRIES, &next_dislink_stage_entry, dislink_stage_entries, obj, link)) {
596                         LOCK_GC;
597                         sgen_process_dislink_stage_entries ();
598                         UNLOCK_GC;
599                 }
600         }
601 #else
602         if (!in_gc)
603                 LOCK_GC;
604         process_dislink_stage_entry (obj, link);
605         if (!in_gc)
606                 UNLOCK_GC;
607 #endif
608 }
609
610 #endif /* HAVE_SGEN_GC */