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