Move function from sgen to gc so boehm can use it too.
[mono-project.git] / mono / metadata / sgen-gc.c
blobfed1dae919fa3825c3b55e9d54bfbe006a6dc8b4
1 /*
2 * sgen-gc.c: Simple generational GC.
4 * Author:
5 * Paolo Molaro (lupus@ximian.com)
7 * Copyright 2005-2010 Novell, Inc (http://www.novell.com)
9 * Thread start/stop adapted from Boehm's GC:
10 * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
11 * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
12 * Copyright (c) 1998 by Fergus Henderson. All rights reserved.
13 * Copyright (c) 2000-2004 by Hewlett-Packard Company. All rights reserved.
15 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
16 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
18 * Permission is hereby granted to use or copy this program
19 * for any purpose, provided the above notices are retained on all copies.
20 * Permission to modify the code and to distribute modified code is granted,
21 * provided the above notices are retained, and a notice that the code was
22 * modified is included with the above copyright notice.
25 * Copyright 2001-2003 Ximian, Inc
26 * Copyright 2003-2010 Novell, Inc.
28 * Permission is hereby granted, free of charge, to any person obtaining
29 * a copy of this software and associated documentation files (the
30 * "Software"), to deal in the Software without restriction, including
31 * without limitation the rights to use, copy, modify, merge, publish,
32 * distribute, sublicense, and/or sell copies of the Software, and to
33 * permit persons to whom the Software is furnished to do so, subject to
34 * the following conditions:
36 * The above copyright notice and this permission notice shall be
37 * included in all copies or substantial portions of the Software.
39 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
40 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
41 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
42 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
43 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
44 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
45 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
48 * Important: allocation provides always zeroed memory, having to do
49 * a memset after allocation is deadly for performance.
50 * Memory usage at startup is currently as follows:
51 * 64 KB pinned space
52 * 64 KB internal space
53 * size of nursery
54 * We should provide a small memory config with half the sizes
56 * We currently try to make as few mono assumptions as possible:
57 * 1) 2-word header with no GC pointers in it (first vtable, second to store the
58 * forwarding ptr)
59 * 2) gc descriptor is the second word in the vtable (first word in the class)
60 * 3) 8 byte alignment is the minimum and enough (not true for special structures (SIMD), FIXME)
61 * 4) there is a function to get an object's size and the number of
62 * elements in an array.
63 * 5) we know the special way bounds are allocated for complex arrays
64 * 6) we know about proxies and how to treat them when domains are unloaded
66 * Always try to keep stack usage to a minimum: no recursive behaviour
67 * and no large stack allocs.
69 * General description.
70 * Objects are initially allocated in a nursery using a fast bump-pointer technique.
71 * When the nursery is full we start a nursery collection: this is performed with a
72 * copying GC.
73 * When the old generation is full we start a copying GC of the old generation as well:
74 * this will be changed to mark&sweep with copying when fragmentation becomes to severe
75 * in the future. Maybe we'll even do both during the same collection like IMMIX.
77 * The things that complicate this description are:
78 * *) pinned objects: we can't move them so we need to keep track of them
79 * *) no precise info of the thread stacks and registers: we need to be able to
80 * quickly find the objects that may be referenced conservatively and pin them
81 * (this makes the first issues more important)
82 * *) large objects are too expensive to be dealt with using copying GC: we handle them
83 * with mark/sweep during major collections
84 * *) some objects need to not move even if they are small (interned strings, Type handles):
85 * we use mark/sweep for them, too: they are not allocated in the nursery, but inside
86 * PinnedChunks regions
90 * TODO:
92 *) we could have a function pointer in MonoClass to implement
93 customized write barriers for value types
95 *) investigate the stuff needed to advance a thread to a GC-safe
96 point (single-stepping, read from unmapped memory etc) and implement it.
97 This would enable us to inline allocations and write barriers, for example,
98 or at least parts of them, like the write barrier checks.
99 We may need this also for handling precise info on stacks, even simple things
100 as having uninitialized data on the stack and having to wait for the prolog
101 to zero it. Not an issue for the last frame that we scan conservatively.
102 We could always not trust the value in the slots anyway.
104 *) modify the jit to save info about references in stack locations:
105 this can be done just for locals as a start, so that at least
106 part of the stack is handled precisely.
108 *) test/fix endianess issues
110 *) Implement a card table as the write barrier instead of remembered
111 sets? Card tables are not easy to implement with our current
112 memory layout. We have several different kinds of major heap
113 objects: Small objects in regular blocks, small objects in pinned
114 chunks and LOS objects. If we just have a pointer we have no way
115 to tell which kind of object it points into, therefore we cannot
116 know where its card table is. The least we have to do to make
117 this happen is to get rid of write barriers for indirect stores.
118 (See next item)
120 *) Get rid of write barriers for indirect stores. We can do this by
121 telling the GC to wbarrier-register an object once we do an ldloca
122 or ldelema on it, and to unregister it once it's not used anymore
123 (it can only travel downwards on the stack). The problem with
124 unregistering is that it needs to happen eventually no matter
125 what, even if exceptions are thrown, the thread aborts, etc.
126 Rodrigo suggested that we could do only the registering part and
127 let the collector find out (pessimistically) when it's safe to
128 unregister, namely when the stack pointer of the thread that
129 registered the object is higher than it was when the registering
130 happened. This might make for a good first implementation to get
131 some data on performance.
133 *) Some sort of blacklist support? Blacklists is a concept from the
134 Boehm GC: if during a conservative scan we find pointers to an
135 area which we might use as heap, we mark that area as unusable, so
136 pointer retention by random pinning pointers is reduced.
138 *) experiment with max small object size (very small right now - 2kb,
139 because it's tied to the max freelist size)
141 *) add an option to mmap the whole heap in one chunk: it makes for many
142 simplifications in the checks (put the nursery at the top and just use a single
143 check for inclusion/exclusion): the issue this has is that on 32 bit systems it's
144 not flexible (too much of the address space may be used by default or we can't
145 increase the heap as needed) and we'd need a race-free mechanism to return memory
146 back to the system (mprotect(PROT_NONE) will still keep the memory allocated if it
147 was written to, munmap is needed, but the following mmap may not find the same segment
148 free...)
150 *) memzero the major fragments after restarting the world and optionally a smaller
151 chunk at a time
153 *) investigate having fragment zeroing threads
155 *) separate locks for finalization and other minor stuff to reduce
156 lock contention
158 *) try a different copying order to improve memory locality
160 *) a thread abort after a store but before the write barrier will
161 prevent the write barrier from executing
163 *) specialized dynamically generated markers/copiers
165 *) Dynamically adjust TLAB size to the number of threads. If we have
166 too many threads that do allocation, we might need smaller TLABs,
167 and we might get better performance with larger TLABs if we only
168 have a handful of threads. We could sum up the space left in all
169 assigned TLABs and if that's more than some percentage of the
170 nursery size, reduce the TLAB size.
172 *) Explore placing unreachable objects on unused nursery memory.
173 Instead of memset'ng a region to zero, place an int[] covering it.
174 A good place to start is add_nursery_frag. The tricky thing here is
175 placing those objects atomically outside of a collection.
179 #include "config.h"
180 #ifdef HAVE_SGEN_GC
182 #include <unistd.h>
183 #include <stdio.h>
184 #include <string.h>
185 #include <semaphore.h>
186 #include <signal.h>
187 #include <errno.h>
188 #include <assert.h>
189 #ifdef __MACH__
190 #undef _XOPEN_SOURCE
191 #endif
192 #include <pthread.h>
193 #ifdef __MACH__
194 #define _XOPEN_SOURCE
195 #endif
196 #include "metadata/metadata-internals.h"
197 #include "metadata/class-internals.h"
198 #include "metadata/gc-internal.h"
199 #include "metadata/object-internals.h"
200 #include "metadata/threads.h"
201 #include "metadata/sgen-gc.h"
202 #include "metadata/sgen-cardtable.h"
203 #include "metadata/sgen-archdep.h"
204 #include "metadata/mono-gc.h"
205 #include "metadata/method-builder.h"
206 #include "metadata/profiler-private.h"
207 #include "metadata/monitor.h"
208 #include "metadata/threadpool-internals.h"
209 #include "metadata/mempool-internals.h"
210 #include "metadata/marshal.h"
211 #include "utils/mono-mmap.h"
212 #include "utils/mono-time.h"
213 #include "utils/mono-semaphore.h"
214 #include "utils/mono-counters.h"
215 #include "utils/mono-proclib.h"
217 #include <mono/utils/memcheck.h>
219 #if defined(__MACH__)
220 #include "utils/mach-support.h"
221 #endif
223 #define OPDEF(a,b,c,d,e,f,g,h,i,j) \
224 a = i,
226 enum {
227 #include "mono/cil/opcode.def"
228 CEE_LAST
231 #undef OPDEF
233 #undef pthread_create
234 #undef pthread_join
235 #undef pthread_detach
238 * ######################################################################
239 * ######## Types and constants used by the GC.
240 * ######################################################################
243 static int gc_initialized = 0;
244 /* If set, do a minor collection before every allocation */
245 static gboolean collect_before_allocs = FALSE;
246 /* If set, do a heap consistency check before each minor collection */
247 static gboolean consistency_check_at_minor_collection = FALSE;
248 /* If set, check that there are no references to the domain left at domain unload */
249 static gboolean xdomain_checks = FALSE;
250 /* If not null, dump the heap after each collection into this file */
251 static FILE *heap_dump_file = NULL;
252 /* If set, mark stacks conservatively, even if precise marking is possible */
253 static gboolean conservative_stack_mark = TRUE;
254 /* If set, do a plausibility check on the scan_starts before and after
255 each collection */
256 static gboolean do_scan_starts_check = FALSE;
258 #ifdef HEAVY_STATISTICS
259 static long long stat_objects_alloced = 0;
260 static long long stat_bytes_alloced = 0;
261 long long stat_objects_alloced_degraded = 0;
262 long long stat_bytes_alloced_degraded = 0;
263 static long long stat_bytes_alloced_los = 0;
265 long long stat_copy_object_called_nursery = 0;
266 long long stat_objects_copied_nursery = 0;
267 long long stat_copy_object_called_major = 0;
268 long long stat_objects_copied_major = 0;
270 long long stat_scan_object_called_nursery = 0;
271 long long stat_scan_object_called_major = 0;
273 long long stat_nursery_copy_object_failed_from_space = 0;
274 long long stat_nursery_copy_object_failed_forwarded = 0;
275 long long stat_nursery_copy_object_failed_pinned = 0;
277 static long long stat_store_remsets = 0;
278 static long long stat_store_remsets_unique = 0;
279 static long long stat_saved_remsets_1 = 0;
280 static long long stat_saved_remsets_2 = 0;
281 static long long stat_local_remsets_processed = 0;
282 static long long stat_global_remsets_added = 0;
283 static long long stat_global_remsets_readded = 0;
284 static long long stat_global_remsets_processed = 0;
285 static long long stat_global_remsets_discarded = 0;
287 static long long stat_wasted_fragments_used = 0;
288 static long long stat_wasted_fragments_bytes = 0;
290 static int stat_wbarrier_set_field = 0;
291 static int stat_wbarrier_set_arrayref = 0;
292 static int stat_wbarrier_arrayref_copy = 0;
293 static int stat_wbarrier_generic_store = 0;
294 static int stat_wbarrier_generic_store_remset = 0;
295 static int stat_wbarrier_set_root = 0;
296 static int stat_wbarrier_value_copy = 0;
297 static int stat_wbarrier_object_copy = 0;
298 #endif
300 static long long time_minor_pre_collection_fragment_clear = 0;
301 static long long time_minor_pinning = 0;
302 static long long time_minor_scan_remsets = 0;
303 static long long time_minor_scan_card_table = 0;
304 static long long time_minor_scan_pinned = 0;
305 static long long time_minor_scan_registered_roots = 0;
306 static long long time_minor_scan_thread_data = 0;
307 static long long time_minor_finish_gray_stack = 0;
308 static long long time_minor_fragment_creation = 0;
310 static long long time_major_pre_collection_fragment_clear = 0;
311 static long long time_major_pinning = 0;
312 static long long time_major_scan_pinned = 0;
313 static long long time_major_scan_registered_roots = 0;
314 static long long time_major_scan_thread_data = 0;
315 static long long time_major_scan_alloc_pinned = 0;
316 static long long time_major_scan_finalized = 0;
317 static long long time_major_scan_big_objects = 0;
318 static long long time_major_finish_gray_stack = 0;
319 static long long time_major_free_bigobjs = 0;
320 static long long time_major_los_sweep = 0;
321 static long long time_major_sweep = 0;
322 static long long time_major_fragment_creation = 0;
324 #define DEBUG(level,a) do {if (G_UNLIKELY ((level) <= SGEN_MAX_DEBUG_LEVEL && (level) <= gc_debug_level)) a;} while (0)
326 static int gc_debug_level = 0;
327 static FILE* gc_debug_file;
330 void
331 mono_gc_flush_info (void)
333 fflush (gc_debug_file);
338 * Define this to allow the user to change the nursery size by
339 * specifying its value in the MONO_GC_PARAMS environmental
340 * variable. See mono_gc_base_init for details.
342 #define USER_CONFIG 1
344 #define TV_DECLARE(name) gint64 name
345 #define TV_GETTIME(tv) tv = mono_100ns_ticks ()
346 #define TV_ELAPSED(start,end) (int)((end-start) / 10)
347 #define TV_ELAPSED_MS(start,end) ((TV_ELAPSED((start),(end)) + 500) / 1000)
349 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
351 /* The method used to clear the nursery */
352 /* Clearing at nursery collections is the safest, but has bad interactions with caches.
353 * Clearing at TLAB creation is much faster, but more complex and it might expose hard
354 * to find bugs.
356 typedef enum {
357 CLEAR_AT_GC,
358 CLEAR_AT_TLAB_CREATION
359 } NurseryClearPolicy;
361 static NurseryClearPolicy nursery_clear_policy = CLEAR_AT_TLAB_CREATION;
364 * The young generation is divided into fragments. This is because
365 * we can hand one fragments to a thread for lock-less fast alloc and
366 * because the young generation ends up fragmented anyway by pinned objects.
367 * Once a collection is done, a list of fragments is created. When doing
368 * thread local alloc we use smallish nurseries so we allow new threads to
369 * allocate memory from gen0 without triggering a collection. Threads that
370 * are found to allocate lots of memory are given bigger fragments. This
371 * should make the finalizer thread use little nursery memory after a while.
372 * We should start assigning threads very small fragments: if there are many
373 * threads the nursery will be full of reserved space that the threads may not
374 * use at all, slowing down allocation speed.
375 * Thread local allocation is done from areas of memory Hotspot calls Thread Local
376 * Allocation Buffers (TLABs).
378 typedef struct _Fragment Fragment;
380 struct _Fragment {
381 Fragment *next;
382 char *fragment_start;
383 char *fragment_limit; /* the current soft limit for allocation */
384 char *fragment_end;
387 /* the runtime can register areas of memory as roots: we keep two lists of roots,
388 * a pinned root set for conservatively scanned roots and a normal one for
389 * precisely scanned roots (currently implemented as a single list).
391 typedef struct _RootRecord RootRecord;
392 struct _RootRecord {
393 RootRecord *next;
394 char *start_root;
395 char *end_root;
396 mword root_desc;
400 * We're never actually using the first element. It's always set to
401 * NULL to simplify the elimination of consecutive duplicate
402 * entries.
404 #define STORE_REMSET_BUFFER_SIZE 1024
406 typedef struct _GenericStoreRememberedSet GenericStoreRememberedSet;
407 struct _GenericStoreRememberedSet {
408 GenericStoreRememberedSet *next;
409 /* We need one entry less because the first entry of store
410 remset buffers is always a dummy and we don't copy it. */
411 gpointer data [STORE_REMSET_BUFFER_SIZE - 1];
414 /* we have 4 possible values in the low 2 bits */
415 enum {
416 REMSET_LOCATION, /* just a pointer to the exact location */
417 REMSET_RANGE, /* range of pointer fields */
418 REMSET_OBJECT, /* mark all the object for scanning */
419 REMSET_VTYPE, /* a valuetype array described by a gc descriptor and a count */
420 REMSET_TYPE_MASK = 0x3
423 #ifdef HAVE_KW_THREAD
424 static __thread RememberedSet *remembered_set MONO_TLS_FAST;
425 #endif
426 static pthread_key_t remembered_set_key;
427 static RememberedSet *global_remset;
428 static RememberedSet *freed_thread_remsets;
429 static GenericStoreRememberedSet *generic_store_remsets = NULL;
431 /*A two slots cache for recently inserted remsets */
432 static gpointer global_remset_cache [2];
434 /* FIXME: later choose a size that takes into account the RememberedSet struct
435 * and doesn't waste any alloc paddin space.
437 #define DEFAULT_REMSET_SIZE 1024
438 static RememberedSet* alloc_remset (int size, gpointer id);
440 #define object_is_forwarded SGEN_OBJECT_IS_FORWARDED
441 #define object_is_pinned SGEN_OBJECT_IS_PINNED
442 #define pin_object SGEN_PIN_OBJECT
443 #define unpin_object SGEN_UNPIN_OBJECT
445 #define ptr_in_nursery(p) (SGEN_PTR_IN_NURSERY ((p), DEFAULT_NURSERY_BITS, nursery_start, nursery_real_end))
447 #define LOAD_VTABLE SGEN_LOAD_VTABLE
449 static const char*
450 safe_name (void* obj)
452 MonoVTable *vt = (MonoVTable*)LOAD_VTABLE (obj);
453 return vt->klass->name;
456 #define safe_object_get_size mono_sgen_safe_object_get_size
459 * ######################################################################
460 * ######## Global data.
461 * ######################################################################
463 static LOCK_DECLARE (gc_mutex);
464 static int gc_disabled = 0;
465 static int num_minor_gcs = 0;
466 static int num_major_gcs = 0;
468 static gboolean use_cardtable;
470 #ifdef USER_CONFIG
472 /* good sizes are 512KB-1MB: larger ones increase a lot memzeroing time */
473 #define DEFAULT_NURSERY_SIZE (default_nursery_size)
474 static int default_nursery_size = (1 << 22);
475 #ifdef SGEN_ALIGN_NURSERY
476 /* The number of trailing 0 bits in DEFAULT_NURSERY_SIZE */
477 #define DEFAULT_NURSERY_BITS (default_nursery_bits)
478 static int default_nursery_bits = 22;
479 #endif
481 #else
483 #define DEFAULT_NURSERY_SIZE (4*1024*1024)
484 #ifdef SGEN_ALIGN_NURSERY
485 #define DEFAULT_NURSERY_BITS 22
486 #endif
488 #endif
490 #ifndef SGEN_ALIGN_NURSERY
491 #define DEFAULT_NURSERY_BITS -1
492 #endif
494 #define MIN_MINOR_COLLECTION_ALLOWANCE (DEFAULT_NURSERY_SIZE * 4)
496 #define SCAN_START_SIZE SGEN_SCAN_START_SIZE
498 /* the minimum size of a fragment that we consider useful for allocation */
499 #define FRAGMENT_MIN_SIZE (512)
501 static mword pagesize = 4096;
502 static mword nursery_size;
503 static int degraded_mode = 0;
505 static mword total_alloc = 0;
506 /* use this to tune when to do a major/minor collection */
507 static mword memory_pressure = 0;
508 static mword minor_collection_allowance;
509 static int minor_collection_sections_alloced = 0;
511 static GCMemSection *nursery_section = NULL;
512 static mword lowest_heap_address = ~(mword)0;
513 static mword highest_heap_address = 0;
515 static LOCK_DECLARE (interruption_mutex);
516 static LOCK_DECLARE (global_remset_mutex);
518 #define LOCK_GLOBAL_REMSET pthread_mutex_lock (&global_remset_mutex)
519 #define UNLOCK_GLOBAL_REMSET pthread_mutex_unlock (&global_remset_mutex)
521 typedef struct _FinalizeEntry FinalizeEntry;
522 struct _FinalizeEntry {
523 FinalizeEntry *next;
524 void *object;
527 typedef struct _FinalizeEntryHashTable FinalizeEntryHashTable;
528 struct _FinalizeEntryHashTable {
529 FinalizeEntry **table;
530 mword size;
531 int num_registered;
534 typedef struct _DisappearingLink DisappearingLink;
535 struct _DisappearingLink {
536 DisappearingLink *next;
537 void **link;
540 typedef struct _DisappearingLinkHashTable DisappearingLinkHashTable;
541 struct _DisappearingLinkHashTable {
542 DisappearingLink **table;
543 mword size;
544 int num_links;
547 typedef struct _EphemeronLinkNode EphemeronLinkNode;
549 struct _EphemeronLinkNode {
550 EphemeronLinkNode *next;
551 char *array;
554 typedef struct {
555 void *key;
556 void *value;
557 } Ephemeron;
559 int current_collection_generation = -1;
562 * The link pointer is hidden by negating each bit. We use the lowest
563 * bit of the link (before negation) to store whether it needs
564 * resurrection tracking.
566 #define HIDE_POINTER(p,t) ((gpointer)(~((gulong)(p)|((t)?1:0))))
567 #define REVEAL_POINTER(p) ((gpointer)((~(gulong)(p))&~3L))
569 #define DISLINK_OBJECT(d) (REVEAL_POINTER (*(d)->link))
570 #define DISLINK_TRACK(d) ((~(gulong)(*(d)->link)) & 1)
573 * The finalizable hash has the object as the key, the
574 * disappearing_link hash, has the link address as key.
576 static FinalizeEntryHashTable minor_finalizable_hash;
577 static FinalizeEntryHashTable major_finalizable_hash;
578 /* objects that are ready to be finalized */
579 static FinalizeEntry *fin_ready_list = NULL;
580 static FinalizeEntry *critical_fin_list = NULL;
582 static DisappearingLinkHashTable minor_disappearing_link_hash;
583 static DisappearingLinkHashTable major_disappearing_link_hash;
585 static EphemeronLinkNode *ephemeron_list;
587 static int num_ready_finalizers = 0;
588 static int no_finalize = 0;
590 enum {
591 ROOT_TYPE_NORMAL = 0, /* "normal" roots */
592 ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */
593 ROOT_TYPE_WBARRIER = 2, /* roots with a write barrier */
594 ROOT_TYPE_NUM
597 /* registered roots: the key to the hash is the root start address */
599 * Different kinds of roots are kept separate to speed up pin_from_roots () for example.
601 static RootRecord **roots_hash [ROOT_TYPE_NUM] = { NULL, NULL };
602 static int roots_hash_size [ROOT_TYPE_NUM] = { 0, 0, 0 };
603 static mword roots_size = 0; /* amount of memory in the root set */
604 static int num_roots_entries [ROOT_TYPE_NUM] = { 0, 0, 0 };
607 * The current allocation cursors
608 * We allocate objects in the nursery.
609 * The nursery is the area between nursery_start and nursery_real_end.
610 * Allocation is done from a Thread Local Allocation Buffer (TLAB). TLABs are allocated
611 * from nursery fragments.
612 * tlab_next is the pointer to the space inside the TLAB where the next object will
613 * be allocated.
614 * tlab_temp_end is the pointer to the end of the temporary space reserved for
615 * the allocation: it allows us to set the scan starts at reasonable intervals.
616 * tlab_real_end points to the end of the TLAB.
617 * nursery_frag_real_end points to the end of the currently used nursery fragment.
618 * nursery_first_pinned_start points to the start of the first pinned object in the nursery
619 * nursery_last_pinned_end points to the end of the last pinned object in the nursery
620 * At the next allocation, the area of the nursery where objects can be present is
621 * between MIN(nursery_first_pinned_start, first_fragment_start) and
622 * MAX(nursery_last_pinned_end, nursery_frag_real_end)
624 static char *nursery_start = NULL;
626 #ifdef HAVE_KW_THREAD
627 #define TLAB_ACCESS_INIT
628 #define TLAB_START tlab_start
629 #define TLAB_NEXT tlab_next
630 #define TLAB_TEMP_END tlab_temp_end
631 #define TLAB_REAL_END tlab_real_end
632 #define REMEMBERED_SET remembered_set
633 #define STORE_REMSET_BUFFER store_remset_buffer
634 #define STORE_REMSET_BUFFER_INDEX store_remset_buffer_index
635 #define IN_CRITICAL_REGION thread_info->in_critical_region
636 #else
637 static pthread_key_t thread_info_key;
638 #define TLAB_ACCESS_INIT SgenThreadInfo *__thread_info__ = pthread_getspecific (thread_info_key)
639 #define TLAB_START (__thread_info__->tlab_start)
640 #define TLAB_NEXT (__thread_info__->tlab_next)
641 #define TLAB_TEMP_END (__thread_info__->tlab_temp_end)
642 #define TLAB_REAL_END (__thread_info__->tlab_real_end)
643 #define REMEMBERED_SET (__thread_info__->remset)
644 #define STORE_REMSET_BUFFER (__thread_info__->store_remset_buffer)
645 #define STORE_REMSET_BUFFER_INDEX (__thread_info__->store_remset_buffer_index)
646 #define IN_CRITICAL_REGION (__thread_info__->in_critical_region)
647 #endif
649 /* we use the memory barrier only to prevent compiler reordering (a memory constraint may be enough) */
650 #define ENTER_CRITICAL_REGION do {IN_CRITICAL_REGION = 1;mono_memory_barrier ();} while (0)
651 #define EXIT_CRITICAL_REGION do {IN_CRITICAL_REGION = 0;mono_memory_barrier ();} while (0)
654 * FIXME: What is faster, a TLS variable pointing to a structure, or separate TLS
655 * variables for next+temp_end ?
657 #ifdef HAVE_KW_THREAD
658 static __thread SgenThreadInfo *thread_info;
659 static __thread char *tlab_start;
660 static __thread char *tlab_next;
661 static __thread char *tlab_temp_end;
662 static __thread char *tlab_real_end;
663 static __thread gpointer *store_remset_buffer;
664 static __thread long store_remset_buffer_index;
665 /* Used by the managed allocator/wbarrier */
666 static __thread char **tlab_next_addr;
667 static __thread char *stack_end;
668 static __thread long *store_remset_buffer_index_addr;
669 #endif
670 static char *nursery_next = NULL;
671 static char *nursery_frag_real_end = NULL;
672 static char *nursery_real_end = NULL;
673 static char *nursery_last_pinned_end = NULL;
675 /* The size of a TLAB */
676 /* The bigger the value, the less often we have to go to the slow path to allocate a new
677 * one, but the more space is wasted by threads not allocating much memory.
678 * FIXME: Tune this.
679 * FIXME: Make this self-tuning for each thread.
681 static guint32 tlab_size = (1024 * 4);
683 /*How much space is tolerable to be wasted from the current fragment when allocating a new TLAB*/
684 #define MAX_NURSERY_TLAB_WASTE 512
686 /* fragments that are free and ready to be used for allocation */
687 static Fragment *nursery_fragments = NULL;
688 /* freeelist of fragment structures */
689 static Fragment *fragment_freelist = NULL;
691 #define MAX_SMALL_OBJ_SIZE SGEN_MAX_SMALL_OBJ_SIZE
693 /* Functions supplied by the runtime to be called by the GC */
694 static MonoGCCallbacks gc_callbacks;
696 #define ALLOC_ALIGN SGEN_ALLOC_ALIGN
697 #define ALLOC_ALIGN_BITS SGEN_ALLOC_ALIGN_BITS
699 #define ALIGN_UP SGEN_ALIGN_UP
701 #define MOVED_OBJECTS_NUM 64
702 static void *moved_objects [MOVED_OBJECTS_NUM];
703 static int moved_objects_idx = 0;
705 /* Vtable of the objects used to fill out nursery fragments before a collection */
706 static MonoVTable *array_fill_vtable;
709 * ######################################################################
710 * ######## Macros and function declarations.
711 * ######################################################################
714 #define ADDR_IN_HEAP_BOUNDARIES(addr) ((p) >= lowest_heap_address && (p) < highest_heap_address)
716 inline static void*
717 align_pointer (void *ptr)
719 mword p = (mword)ptr;
720 p += sizeof (gpointer) - 1;
721 p &= ~ (sizeof (gpointer) - 1);
722 return (void*)p;
725 typedef SgenGrayQueue GrayQueue;
727 typedef void (*CopyOrMarkObjectFunc) (void**, GrayQueue*);
728 typedef char* (*ScanObjectFunc) (char*, GrayQueue*);
730 /* forward declarations */
731 static int stop_world (int generation);
732 static int restart_world (int generation);
733 static void scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise);
734 static void scan_from_remsets (void *start_nursery, void *end_nursery, GrayQueue *queue);
735 static void scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type, GrayQueue *queue);
736 static void scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list, GrayQueue *queue);
737 static void find_pinning_ref_from_thread (char *obj, size_t size);
738 static void update_current_thread_stack (void *start);
739 static void finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue);
740 static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation);
741 static void null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue);
742 static void null_links_for_domain (MonoDomain *domain, int generation);
743 static gboolean search_fragment_for_size (size_t size);
744 static int search_fragment_for_size_range (size_t desired_size, size_t minimum_size);
745 static void clear_nursery_fragments (char *next);
746 static void pin_from_roots (void *start_nursery, void *end_nursery);
747 static int pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery, GrayQueue *queue);
748 static void optimize_pin_queue (int start_slot);
749 static void clear_remsets (void);
750 static void clear_tlabs (void);
751 static void sort_addresses (void **array, int size);
752 static void drain_gray_stack (GrayQueue *queue);
753 static void finish_gray_stack (char *start_addr, char *end_addr, int generation, GrayQueue *queue);
754 static gboolean need_major_collection (void);
755 static void major_collection (const char *reason);
757 static void mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track);
759 void describe_ptr (char *ptr);
760 void check_object (char *start);
762 static void check_consistency (void);
763 static void check_major_refs (void);
764 static void check_scan_starts (void);
765 static void check_for_xdomain_refs (void);
766 static void dump_heap (const char *type, int num, const char *reason);
768 void mono_gc_scan_for_specific_ref (MonoObject *key);
770 static void init_stats (void);
772 static int mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, GrayQueue *queue);
773 static void clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end, GrayQueue *queue);
774 static void null_ephemerons_for_domain (MonoDomain *domain);
776 SgenMajorCollector major_collector;
778 #include "sgen-protocol.c"
779 #include "sgen-pinning.c"
780 #include "sgen-pinning-stats.c"
781 #include "sgen-gray.c"
782 #include "sgen-workers.c"
783 #include "sgen-los.c"
784 #include "sgen-cardtable.c"
786 /* Root bitmap descriptors are simpler: the lower three bits describe the type
787 * and we either have 30/62 bitmap bits or nibble-based run-length,
788 * or a complex descriptor, or a user defined marker function.
790 enum {
791 ROOT_DESC_CONSERVATIVE, /* 0, so matches NULL value */
792 ROOT_DESC_BITMAP,
793 ROOT_DESC_RUN_LEN,
794 ROOT_DESC_COMPLEX,
795 ROOT_DESC_USER,
796 ROOT_DESC_TYPE_MASK = 0x7,
797 ROOT_DESC_TYPE_SHIFT = 3,
800 #define MAKE_ROOT_DESC(type,val) ((type) | ((val) << ROOT_DESC_TYPE_SHIFT))
802 #define MAX_USER_DESCRIPTORS 16
804 static gsize* complex_descriptors = NULL;
805 static int complex_descriptors_size = 0;
806 static int complex_descriptors_next = 0;
807 static MonoGCRootMarkFunc user_descriptors [MAX_USER_DESCRIPTORS];
808 static int user_descriptors_next = 0;
810 static int
811 alloc_complex_descriptor (gsize *bitmap, int numbits)
813 int nwords, res, i;
815 numbits = ALIGN_TO (numbits, GC_BITS_PER_WORD);
816 nwords = numbits / GC_BITS_PER_WORD + 1;
818 LOCK_GC;
819 res = complex_descriptors_next;
820 /* linear search, so we don't have duplicates with domain load/unload
821 * this should not be performance critical or we'd have bigger issues
822 * (the number and size of complex descriptors should be small).
824 for (i = 0; i < complex_descriptors_next; ) {
825 if (complex_descriptors [i] == nwords) {
826 int j, found = TRUE;
827 for (j = 0; j < nwords - 1; ++j) {
828 if (complex_descriptors [i + 1 + j] != bitmap [j]) {
829 found = FALSE;
830 break;
833 if (found) {
834 UNLOCK_GC;
835 return i;
838 i += complex_descriptors [i];
840 if (complex_descriptors_next + nwords > complex_descriptors_size) {
841 int new_size = complex_descriptors_size * 2 + nwords;
842 complex_descriptors = g_realloc (complex_descriptors, new_size * sizeof (gsize));
843 complex_descriptors_size = new_size;
845 DEBUG (6, fprintf (gc_debug_file, "Complex descriptor %d, size: %d (total desc memory: %d)\n", res, nwords, complex_descriptors_size));
846 complex_descriptors_next += nwords;
847 complex_descriptors [res] = nwords;
848 for (i = 0; i < nwords - 1; ++i) {
849 complex_descriptors [res + 1 + i] = bitmap [i];
850 DEBUG (6, fprintf (gc_debug_file, "\tvalue: %p\n", (void*)complex_descriptors [res + 1 + i]));
852 UNLOCK_GC;
853 return res;
856 gsize*
857 mono_sgen_get_complex_descriptor (GCVTable *vt)
859 return complex_descriptors + (vt->desc >> LOW_TYPE_BITS);
863 * Descriptor builders.
865 void*
866 mono_gc_make_descr_for_string (gsize *bitmap, int numbits)
868 return (void*) DESC_TYPE_RUN_LENGTH;
871 void*
872 mono_gc_make_descr_for_object (gsize *bitmap, int numbits, size_t obj_size)
874 int first_set = -1, num_set = 0, last_set = -1, i;
875 mword desc = 0;
876 size_t stored_size = obj_size;
877 for (i = 0; i < numbits; ++i) {
878 if (bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
879 if (first_set < 0)
880 first_set = i;
881 last_set = i;
882 num_set++;
886 * We don't encode the size of types that don't contain
887 * references because they might not be aligned, i.e. the
888 * bottom two bits might be set, which would clash with the
889 * bits we need to encode the descriptor type. Since we don't
890 * use the encoded size to skip objects, other than for
891 * processing remsets, in which case only the positions of
892 * references are relevant, this is not a problem.
894 if (first_set < 0)
895 return (void*)DESC_TYPE_RUN_LENGTH;
896 g_assert (!(stored_size & 0x3));
897 if (stored_size <= MAX_SMALL_OBJ_SIZE) {
898 /* check run-length encoding first: one byte offset, one byte number of pointers
899 * on 64 bit archs, we can have 3 runs, just one on 32.
900 * It may be better to use nibbles.
902 if (first_set < 0) {
903 desc = DESC_TYPE_RUN_LENGTH | (stored_size << 1);
904 DEBUG (6, fprintf (gc_debug_file, "Ptrfree descriptor %p, size: %zd\n", (void*)desc, stored_size));
905 return (void*) desc;
906 } else if (first_set < 256 && num_set < 256 && (first_set + num_set == last_set + 1)) {
907 desc = DESC_TYPE_RUN_LENGTH | (stored_size << 1) | (first_set << 16) | (num_set << 24);
908 DEBUG (6, fprintf (gc_debug_file, "Runlen descriptor %p, size: %zd, first set: %d, num set: %d\n", (void*)desc, stored_size, first_set, num_set));
909 return (void*) desc;
911 /* we know the 2-word header is ptr-free */
912 if (last_set < SMALL_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
913 desc = DESC_TYPE_SMALL_BITMAP | (stored_size << 1) | ((*bitmap >> OBJECT_HEADER_WORDS) << SMALL_BITMAP_SHIFT);
914 DEBUG (6, fprintf (gc_debug_file, "Smallbitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
915 return (void*) desc;
918 /* we know the 2-word header is ptr-free */
919 if (last_set < LARGE_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
920 desc = DESC_TYPE_LARGE_BITMAP | ((*bitmap >> OBJECT_HEADER_WORDS) << LOW_TYPE_BITS);
921 DEBUG (6, fprintf (gc_debug_file, "Largebitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
922 return (void*) desc;
924 /* it's a complex object ... */
925 desc = DESC_TYPE_COMPLEX | (alloc_complex_descriptor (bitmap, last_set + 1) << LOW_TYPE_BITS);
926 return (void*) desc;
929 /* If the array holds references, numbits == 1 and the first bit is set in elem_bitmap */
930 void*
931 mono_gc_make_descr_for_array (int vector, gsize *elem_bitmap, int numbits, size_t elem_size)
933 int first_set = -1, num_set = 0, last_set = -1, i;
934 mword desc = vector? DESC_TYPE_VECTOR: DESC_TYPE_ARRAY;
935 for (i = 0; i < numbits; ++i) {
936 if (elem_bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
937 if (first_set < 0)
938 first_set = i;
939 last_set = i;
940 num_set++;
943 /* See comment at the definition of DESC_TYPE_RUN_LENGTH. */
944 if (first_set < 0)
945 return (void*)DESC_TYPE_RUN_LENGTH;
946 if (elem_size <= MAX_ELEMENT_SIZE) {
947 desc |= elem_size << VECTOR_ELSIZE_SHIFT;
948 if (!num_set) {
949 return (void*)(desc | VECTOR_SUBTYPE_PTRFREE);
951 /* Note: we also handle structs with just ref fields */
952 if (num_set * sizeof (gpointer) == elem_size) {
953 return (void*)(desc | VECTOR_SUBTYPE_REFS | ((gssize)(-1) << 16));
955 /* FIXME: try run-len first */
956 /* Note: we can't skip the object header here, because it's not present */
957 if (last_set <= SMALL_BITMAP_SIZE) {
958 return (void*)(desc | VECTOR_SUBTYPE_BITMAP | (*elem_bitmap << 16));
961 /* it's am array of complex structs ... */
962 desc = DESC_TYPE_COMPLEX_ARR;
963 desc |= alloc_complex_descriptor (elem_bitmap, last_set + 1) << LOW_TYPE_BITS;
964 return (void*) desc;
967 /* Return the bitmap encoded by a descriptor */
968 gsize*
969 mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
971 mword d = (mword)descr;
972 gsize *bitmap;
974 switch (d & 0x7) {
975 case DESC_TYPE_RUN_LENGTH: {
976 int first_set = (d >> 16) & 0xff;
977 int num_set = (d >> 24) & 0xff;
978 int i;
980 bitmap = g_new0 (gsize, (first_set + num_set + 7) / 8);
982 for (i = first_set; i < first_set + num_set; ++i)
983 bitmap [i / GC_BITS_PER_WORD] |= ((gsize)1 << (i % GC_BITS_PER_WORD));
985 *numbits = first_set + num_set;
987 return bitmap;
989 case DESC_TYPE_SMALL_BITMAP:
990 bitmap = g_new0 (gsize, 1);
992 bitmap [0] = (d >> SMALL_BITMAP_SHIFT) << OBJECT_HEADER_WORDS;
994 *numbits = GC_BITS_PER_WORD;
996 return bitmap;
997 default:
998 g_assert_not_reached ();
1002 static gboolean
1003 is_xdomain_ref_allowed (gpointer *ptr, char *obj, MonoDomain *domain)
1005 MonoObject *o = (MonoObject*)(obj);
1006 MonoObject *ref = (MonoObject*)*(ptr);
1007 int offset = (char*)(ptr) - (char*)o;
1009 if (o->vtable->klass == mono_defaults.thread_class && offset == G_STRUCT_OFFSET (MonoThread, internal_thread))
1010 return TRUE;
1011 if (o->vtable->klass == mono_defaults.internal_thread_class && offset == G_STRUCT_OFFSET (MonoInternalThread, current_appcontext))
1012 return TRUE;
1013 if (mono_class_has_parent (o->vtable->klass, mono_defaults.real_proxy_class) &&
1014 offset == G_STRUCT_OFFSET (MonoRealProxy, unwrapped_server))
1015 return TRUE;
1016 /* Thread.cached_culture_info */
1017 if (!strcmp (ref->vtable->klass->name_space, "System.Globalization") &&
1018 !strcmp (ref->vtable->klass->name, "CultureInfo") &&
1019 !strcmp(o->vtable->klass->name_space, "System") &&
1020 !strcmp(o->vtable->klass->name, "Object[]"))
1021 return TRUE;
1023 * at System.IO.MemoryStream.InternalConstructor (byte[],int,int,bool,bool) [0x0004d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:121
1024 * at System.IO.MemoryStream..ctor (byte[]) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:81
1025 * at (wrapper remoting-invoke-with-check) System.IO.MemoryStream..ctor (byte[]) <IL 0x00020, 0xffffffff>
1026 * at System.Runtime.Remoting.Messaging.CADMethodCallMessage.GetArguments () [0x0000d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/CADMessages.cs:327
1027 * at System.Runtime.Remoting.Messaging.MethodCall..ctor (System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/MethodCall.cs:87
1028 * at System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) [0x00018] in /home/schani/Work/novell/trunk/mcs/class/corlib/System/AppDomain.cs:1213
1029 * at (wrapper remoting-invoke-with-check) System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) <IL 0x0003d, 0xffffffff>
1030 * at System.Runtime.Remoting.Channels.CrossAppDomainSink.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage) [0x00008] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Channels/CrossAppDomainChannel.cs:198
1031 * at (wrapper runtime-invoke) object.runtime_invoke_CrossAppDomainSink/ProcessMessageRes_object_object (object,intptr,intptr,intptr) <IL 0x0004c, 0xffffffff>
1033 if (!strcmp (ref->vtable->klass->name_space, "System") &&
1034 !strcmp (ref->vtable->klass->name, "Byte[]") &&
1035 !strcmp (o->vtable->klass->name_space, "System.IO") &&
1036 !strcmp (o->vtable->klass->name, "MemoryStream"))
1037 return TRUE;
1038 /* append_job() in threadpool.c */
1039 if (!strcmp (ref->vtable->klass->name_space, "System.Runtime.Remoting.Messaging") &&
1040 !strcmp (ref->vtable->klass->name, "AsyncResult") &&
1041 !strcmp (o->vtable->klass->name_space, "System") &&
1042 !strcmp (o->vtable->klass->name, "Object[]") &&
1043 mono_thread_pool_is_queue_array ((MonoArray*) o))
1044 return TRUE;
1045 return FALSE;
1048 static void
1049 check_reference_for_xdomain (gpointer *ptr, char *obj, MonoDomain *domain)
1051 MonoObject *o = (MonoObject*)(obj);
1052 MonoObject *ref = (MonoObject*)*(ptr);
1053 int offset = (char*)(ptr) - (char*)o;
1054 MonoClass *class;
1055 MonoClassField *field;
1056 char *str;
1058 if (!ref || ref->vtable->domain == domain)
1059 return;
1060 if (is_xdomain_ref_allowed (ptr, obj, domain))
1061 return;
1063 field = NULL;
1064 for (class = o->vtable->klass; class; class = class->parent) {
1065 int i;
1067 for (i = 0; i < class->field.count; ++i) {
1068 if (class->fields[i].offset == offset) {
1069 field = &class->fields[i];
1070 break;
1073 if (field)
1074 break;
1077 if (ref->vtable->klass == mono_defaults.string_class)
1078 str = mono_string_to_utf8 ((MonoString*)ref);
1079 else
1080 str = NULL;
1081 g_print ("xdomain reference in %p (%s.%s) at offset %d (%s) to %p (%s.%s) (%s) - pointed to by:\n",
1082 o, o->vtable->klass->name_space, o->vtable->klass->name,
1083 offset, field ? field->name : "",
1084 ref, ref->vtable->klass->name_space, ref->vtable->klass->name, str ? str : "");
1085 mono_gc_scan_for_specific_ref (o);
1086 if (str)
1087 g_free (str);
1090 #undef HANDLE_PTR
1091 #define HANDLE_PTR(ptr,obj) check_reference_for_xdomain ((ptr), (obj), domain)
1093 static void
1094 scan_object_for_xdomain_refs (char *start, mword size, void *data)
1096 MonoDomain *domain = ((MonoObject*)start)->vtable->domain;
1098 #include "sgen-scan-object.h"
1101 #undef HANDLE_PTR
1102 #define HANDLE_PTR(ptr,obj) do { \
1103 if ((MonoObject*)*(ptr) == key) { \
1104 g_print ("found ref to %p in object %p (%s) at offset %td\n", \
1105 key, (obj), safe_name ((obj)), ((char*)(ptr) - (char*)(obj))); \
1107 } while (0)
1109 static void
1110 scan_object_for_specific_ref (char *start, MonoObject *key)
1112 #include "sgen-scan-object.h"
1115 void
1116 mono_sgen_scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data)
1118 while (start < end) {
1119 size_t size;
1120 if (!*(void**)start) {
1121 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
1122 continue;
1125 size = ALIGN_UP (safe_object_get_size ((MonoObject*) start));
1127 callback (start, size, data);
1129 start += size;
1133 static void
1134 scan_object_for_specific_ref_callback (char *obj, size_t size, MonoObject *key)
1136 scan_object_for_specific_ref (obj, key);
1139 static void
1140 check_root_obj_specific_ref (RootRecord *root, MonoObject *key, MonoObject *obj)
1142 if (key != obj)
1143 return;
1144 g_print ("found ref to %p in root record %p\n", key, root);
1147 static MonoObject *check_key = NULL;
1148 static RootRecord *check_root = NULL;
1150 static void
1151 check_root_obj_specific_ref_from_marker (void **obj)
1153 check_root_obj_specific_ref (check_root, check_key, *obj);
1156 static void
1157 scan_roots_for_specific_ref (MonoObject *key, int root_type)
1159 int i;
1160 RootRecord *root;
1161 check_key = key;
1162 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1163 for (root = roots_hash [root_type][i]; root; root = root->next) {
1164 void **start_root = (void**)root->start_root;
1165 mword desc = root->root_desc;
1167 check_root = root;
1169 switch (desc & ROOT_DESC_TYPE_MASK) {
1170 case ROOT_DESC_BITMAP:
1171 desc >>= ROOT_DESC_TYPE_SHIFT;
1172 while (desc) {
1173 if (desc & 1)
1174 check_root_obj_specific_ref (root, key, *start_root);
1175 desc >>= 1;
1176 start_root++;
1178 return;
1179 case ROOT_DESC_COMPLEX: {
1180 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1181 int bwords = (*bitmap_data) - 1;
1182 void **start_run = start_root;
1183 bitmap_data++;
1184 while (bwords-- > 0) {
1185 gsize bmap = *bitmap_data++;
1186 void **objptr = start_run;
1187 while (bmap) {
1188 if (bmap & 1)
1189 check_root_obj_specific_ref (root, key, *objptr);
1190 bmap >>= 1;
1191 ++objptr;
1193 start_run += GC_BITS_PER_WORD;
1195 break;
1197 case ROOT_DESC_USER: {
1198 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1199 marker (start_root, check_root_obj_specific_ref_from_marker);
1200 break;
1202 case ROOT_DESC_RUN_LEN:
1203 g_assert_not_reached ();
1204 default:
1205 g_assert_not_reached ();
1209 check_key = NULL;
1210 check_root = NULL;
1213 void
1214 mono_gc_scan_for_specific_ref (MonoObject *key)
1216 LOSObject *bigobj;
1217 RootRecord *root;
1218 int i;
1220 mono_sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1221 (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1223 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1225 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1226 scan_object_for_specific_ref (bigobj->data, key);
1228 scan_roots_for_specific_ref (key, ROOT_TYPE_NORMAL);
1229 scan_roots_for_specific_ref (key, ROOT_TYPE_WBARRIER);
1231 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
1232 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
1233 void **ptr = (void**)root->start_root;
1235 while (ptr < (void**)root->end_root) {
1236 check_root_obj_specific_ref (root, *ptr, key);
1237 ++ptr;
1243 static void
1244 clear_current_nursery_fragment (char *next)
1246 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
1247 g_assert (next <= nursery_frag_real_end);
1248 DEBUG (4, fprintf (gc_debug_file, "Clear nursery frag %p-%p\n", next, nursery_frag_real_end));
1249 memset (next, 0, nursery_frag_real_end - next);
1253 /* Clear all remaining nursery fragments */
1254 static void
1255 clear_nursery_fragments (char *next)
1257 Fragment *frag;
1258 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
1259 clear_current_nursery_fragment (next);
1260 for (frag = nursery_fragments; frag; frag = frag->next) {
1261 DEBUG (4, fprintf (gc_debug_file, "Clear nursery frag %p-%p\n", frag->fragment_start, frag->fragment_end));
1262 memset (frag->fragment_start, 0, frag->fragment_end - frag->fragment_start);
1267 static gboolean
1268 need_remove_object_for_domain (char *start, MonoDomain *domain)
1270 if (mono_object_domain (start) == domain) {
1271 DEBUG (4, fprintf (gc_debug_file, "Need to cleanup object %p\n", start));
1272 binary_protocol_cleanup (start, (gpointer)LOAD_VTABLE (start), safe_object_get_size ((MonoObject*)start));
1273 return TRUE;
1275 return FALSE;
1278 static void
1279 process_object_for_domain_clearing (char *start, MonoDomain *domain)
1281 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
1282 if (vt->klass == mono_defaults.internal_thread_class)
1283 g_assert (mono_object_domain (start) == mono_get_root_domain ());
1284 /* The object could be a proxy for an object in the domain
1285 we're deleting. */
1286 if (mono_class_has_parent (vt->klass, mono_defaults.real_proxy_class)) {
1287 MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server;
1289 /* The server could already have been zeroed out, so
1290 we need to check for that, too. */
1291 if (server && (!LOAD_VTABLE (server) || mono_object_domain (server) == domain)) {
1292 DEBUG (4, fprintf (gc_debug_file, "Cleaning up remote pointer in %p to object %p\n",
1293 start, server));
1294 ((MonoRealProxy*)start)->unwrapped_server = NULL;
1299 static MonoDomain *check_domain = NULL;
1301 static void
1302 check_obj_not_in_domain (void **o)
1304 g_assert (((MonoObject*)(*o))->vtable->domain != check_domain);
1307 static void
1308 scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type)
1310 int i;
1311 RootRecord *root;
1312 check_domain = domain;
1313 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1314 for (root = roots_hash [root_type][i]; root; root = root->next) {
1315 void **start_root = (void**)root->start_root;
1316 mword desc = root->root_desc;
1318 /* The MonoDomain struct is allowed to hold
1319 references to objects in its own domain. */
1320 if (start_root == (void**)domain)
1321 continue;
1323 switch (desc & ROOT_DESC_TYPE_MASK) {
1324 case ROOT_DESC_BITMAP:
1325 desc >>= ROOT_DESC_TYPE_SHIFT;
1326 while (desc) {
1327 if ((desc & 1) && *start_root)
1328 check_obj_not_in_domain (*start_root);
1329 desc >>= 1;
1330 start_root++;
1332 break;
1333 case ROOT_DESC_COMPLEX: {
1334 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1335 int bwords = (*bitmap_data) - 1;
1336 void **start_run = start_root;
1337 bitmap_data++;
1338 while (bwords-- > 0) {
1339 gsize bmap = *bitmap_data++;
1340 void **objptr = start_run;
1341 while (bmap) {
1342 if ((bmap & 1) && *objptr)
1343 check_obj_not_in_domain (*objptr);
1344 bmap >>= 1;
1345 ++objptr;
1347 start_run += GC_BITS_PER_WORD;
1349 break;
1351 case ROOT_DESC_USER: {
1352 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1353 marker (start_root, check_obj_not_in_domain);
1354 break;
1356 case ROOT_DESC_RUN_LEN:
1357 g_assert_not_reached ();
1358 default:
1359 g_assert_not_reached ();
1363 check_domain = NULL;
1366 static void
1367 check_for_xdomain_refs (void)
1369 LOSObject *bigobj;
1371 mono_sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1372 (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
1374 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
1376 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1377 scan_object_for_xdomain_refs (bigobj->data, bigobj->size, NULL);
1380 static gboolean
1381 clear_domain_process_object (char *obj, MonoDomain *domain)
1383 gboolean remove;
1385 process_object_for_domain_clearing (obj, domain);
1386 remove = need_remove_object_for_domain (obj, domain);
1388 if (remove && ((MonoObject*)obj)->synchronisation) {
1389 void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)obj);
1390 if (dislink)
1391 mono_gc_register_disappearing_link (NULL, dislink, FALSE);
1394 return remove;
1397 static void
1398 clear_domain_process_minor_object_callback (char *obj, size_t size, MonoDomain *domain)
1400 if (clear_domain_process_object (obj, domain))
1401 memset (obj, 0, size);
1404 static void
1405 clear_domain_process_major_object_callback (char *obj, size_t size, MonoDomain *domain)
1407 clear_domain_process_object (obj, domain);
1410 static void
1411 clear_domain_free_major_non_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1413 if (need_remove_object_for_domain (obj, domain))
1414 major_collector.free_non_pinned_object (obj, size);
1417 static void
1418 clear_domain_free_major_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1420 if (need_remove_object_for_domain (obj, domain))
1421 major_collector.free_pinned_object (obj, size);
1425 * When appdomains are unloaded we can easily remove objects that have finalizers,
1426 * but all the others could still be present in random places on the heap.
1427 * We need a sweep to get rid of them even though it's going to be costly
1428 * with big heaps.
1429 * The reason we need to remove them is because we access the vtable and class
1430 * structures to know the object size and the reference bitmap: once the domain is
1431 * unloaded the point to random memory.
1433 void
1434 mono_gc_clear_domain (MonoDomain * domain)
1436 LOSObject *bigobj, *prev;
1437 int i;
1439 LOCK_GC;
1441 clear_nursery_fragments (nursery_next);
1443 if (xdomain_checks && domain != mono_get_root_domain ()) {
1444 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_NORMAL);
1445 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_WBARRIER);
1446 check_for_xdomain_refs ();
1449 mono_sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1450 (IterateObjectCallbackFunc)clear_domain_process_minor_object_callback, domain);
1452 /*Ephemerons and dislinks must be processed before LOS since they might end up pointing
1453 to memory returned to the OS.*/
1454 null_ephemerons_for_domain (domain);
1456 for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
1457 null_links_for_domain (domain, i);
1459 /* We need two passes over major and large objects because
1460 freeing such objects might give their memory back to the OS
1461 (in the case of large objects) or obliterate its vtable
1462 (pinned objects with major-copying or pinned and non-pinned
1463 objects with major-mark&sweep), but we might need to
1464 dereference a pointer from an object to another object if
1465 the first object is a proxy. */
1466 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)clear_domain_process_major_object_callback, domain);
1467 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1468 clear_domain_process_object (bigobj->data, domain);
1470 prev = NULL;
1471 for (bigobj = los_object_list; bigobj;) {
1472 if (need_remove_object_for_domain (bigobj->data, domain)) {
1473 LOSObject *to_free = bigobj;
1474 if (prev)
1475 prev->next = bigobj->next;
1476 else
1477 los_object_list = bigobj->next;
1478 bigobj = bigobj->next;
1479 DEBUG (4, fprintf (gc_debug_file, "Freeing large object %p\n",
1480 bigobj->data));
1481 free_large_object (to_free);
1482 continue;
1484 prev = bigobj;
1485 bigobj = bigobj->next;
1487 major_collector.iterate_objects (TRUE, FALSE, (IterateObjectCallbackFunc)clear_domain_free_major_non_pinned_object_callback, domain);
1488 major_collector.iterate_objects (FALSE, TRUE, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain);
1490 UNLOCK_GC;
1493 static void
1494 global_remset_cache_clear (void)
1496 memset (global_remset_cache, 0, sizeof (global_remset_cache));
1500 * Tries to check if a given remset location was already added to the global remset.
1501 * It can
1503 * A 2 entry, LRU cache of recently saw location remsets.
1505 * It's hand-coded instead of done using loops to reduce the number of memory references on cache hit.
1507 * Returns TRUE is the element was added..
1509 static gboolean
1510 global_remset_location_was_not_added (gpointer ptr)
1513 gpointer first = global_remset_cache [0], second;
1514 if (first == ptr) {
1515 HEAVY_STAT (++stat_global_remsets_discarded);
1516 return FALSE;
1519 second = global_remset_cache [1];
1521 if (second == ptr) {
1522 /*Move the second to the front*/
1523 global_remset_cache [0] = second;
1524 global_remset_cache [1] = first;
1526 HEAVY_STAT (++stat_global_remsets_discarded);
1527 return FALSE;
1530 global_remset_cache [0] = second;
1531 global_remset_cache [1] = ptr;
1532 return TRUE;
1536 * mono_sgen_add_to_global_remset:
1538 * The global remset contains locations which point into newspace after
1539 * a minor collection. This can happen if the objects they point to are pinned.
1541 * LOCKING: If called from a parallel collector, the global remset
1542 * lock must be held. For serial collectors that is not necessary.
1544 void
1545 mono_sgen_add_to_global_remset (gpointer ptr)
1547 RememberedSet *rs;
1548 gboolean lock;
1550 if (use_cardtable) {
1551 sgen_card_table_mark_address ((mword)ptr);
1552 return;
1555 g_assert (!ptr_in_nursery (ptr) && ptr_in_nursery (*(gpointer*)ptr));
1557 lock = (current_collection_generation == GENERATION_OLD && major_collector.is_parallel);
1558 if (lock)
1559 LOCK_GLOBAL_REMSET;
1561 if (!global_remset_location_was_not_added (ptr))
1562 goto done;
1564 DEBUG (8, fprintf (gc_debug_file, "Adding global remset for %p\n", ptr));
1565 binary_protocol_global_remset (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
1567 HEAVY_STAT (++stat_global_remsets_added);
1570 * FIXME: If an object remains pinned, we need to add it at every minor collection.
1571 * To avoid uncontrolled growth of the global remset, only add each pointer once.
1573 if (global_remset->store_next + 3 < global_remset->end_set) {
1574 *(global_remset->store_next++) = (mword)ptr;
1575 goto done;
1577 rs = alloc_remset (global_remset->end_set - global_remset->data, NULL);
1578 rs->next = global_remset;
1579 global_remset = rs;
1580 *(global_remset->store_next++) = (mword)ptr;
1583 int global_rs_size = 0;
1585 for (rs = global_remset; rs; rs = rs->next) {
1586 global_rs_size += rs->store_next - rs->data;
1588 DEBUG (4, fprintf (gc_debug_file, "Global remset now has size %d\n", global_rs_size));
1591 done:
1592 if (lock)
1593 UNLOCK_GLOBAL_REMSET;
1597 * drain_gray_stack:
1599 * Scan objects in the gray stack until the stack is empty. This should be called
1600 * frequently after each object is copied, to achieve better locality and cache
1601 * usage.
1603 static void
1604 drain_gray_stack (GrayQueue *queue)
1606 char *obj;
1608 if (current_collection_generation == GENERATION_NURSERY) {
1609 for (;;) {
1610 GRAY_OBJECT_DEQUEUE (queue, obj);
1611 if (!obj)
1612 break;
1613 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
1614 major_collector.minor_scan_object (obj, queue);
1616 } else {
1617 if (major_collector.is_parallel && queue == &workers_distribute_gray_queue)
1618 return;
1620 for (;;) {
1621 GRAY_OBJECT_DEQUEUE (queue, obj);
1622 if (!obj)
1623 break;
1624 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
1625 major_collector.major_scan_object (obj, queue);
1631 * Addresses from start to end are already sorted. This function finds
1632 * the object header for each address and pins the object. The
1633 * addresses must be inside the passed section. The (start of the)
1634 * address array is overwritten with the addresses of the actually
1635 * pinned objects. Return the number of pinned objects.
1637 static int
1638 pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery, GrayQueue *queue)
1640 void *last = NULL;
1641 int count = 0;
1642 void *search_start;
1643 void *last_obj = NULL;
1644 size_t last_obj_size = 0;
1645 void *addr;
1646 int idx;
1647 void **definitely_pinned = start;
1648 Fragment *frag;
1651 * The code below starts the search from an entry in scan_starts, which might point into a nursery
1652 * fragment containing random data. Clearing the nursery fragments takes a lot of time, and searching
1653 * though them too, so lay arrays at each location inside a fragment where a search can start:
1654 * - scan_locations[i]
1655 * - start_nursery
1656 * - the start of each fragment (the last_obj + last_obj case)
1657 * The third encompasses the first two, since scan_locations [i] can't point inside a nursery fragment.
1659 for (frag = nursery_fragments; frag; frag = frag->next) {
1660 MonoArray *o;
1662 g_assert (frag->fragment_end - frag->fragment_start >= sizeof (MonoArray));
1663 o = (MonoArray*)frag->fragment_start;
1664 memset (o, 0, sizeof (MonoArray));
1665 g_assert (array_fill_vtable);
1666 o->obj.vtable = array_fill_vtable;
1667 /* Mark this as not a real object */
1668 o->obj.synchronisation = GINT_TO_POINTER (-1);
1669 o->max_length = (frag->fragment_end - frag->fragment_start) - sizeof (MonoArray);
1670 g_assert (frag->fragment_start + safe_object_get_size ((MonoObject*)o) == frag->fragment_end);
1673 while (start < end) {
1674 addr = *start;
1675 /* the range check should be reduntant */
1676 if (addr != last && addr >= start_nursery && addr < end_nursery) {
1677 DEBUG (5, fprintf (gc_debug_file, "Considering pinning addr %p\n", addr));
1678 /* multiple pointers to the same object */
1679 if (addr >= last_obj && (char*)addr < (char*)last_obj + last_obj_size) {
1680 start++;
1681 continue;
1683 idx = ((char*)addr - (char*)section->data) / SCAN_START_SIZE;
1684 g_assert (idx < section->num_scan_start);
1685 search_start = (void*)section->scan_starts [idx];
1686 if (!search_start || search_start > addr) {
1687 while (idx) {
1688 --idx;
1689 search_start = section->scan_starts [idx];
1690 if (search_start && search_start <= addr)
1691 break;
1693 if (!search_start || search_start > addr)
1694 search_start = start_nursery;
1696 if (search_start < last_obj)
1697 search_start = (char*)last_obj + last_obj_size;
1698 /* now addr should be in an object a short distance from search_start
1699 * Note that search_start must point to zeroed mem or point to an object.
1702 do {
1703 if (!*(void**)search_start) {
1704 /* Consistency check */
1706 for (frag = nursery_fragments; frag; frag = frag->next) {
1707 if (search_start >= frag->fragment_start && search_start < frag->fragment_end)
1708 g_assert_not_reached ();
1712 search_start = (void*)ALIGN_UP ((mword)search_start + sizeof (gpointer));
1713 continue;
1715 last_obj = search_start;
1716 last_obj_size = ALIGN_UP (safe_object_get_size ((MonoObject*)search_start));
1718 if (((MonoObject*)last_obj)->synchronisation == GINT_TO_POINTER (-1)) {
1719 /* Marks the beginning of a nursery fragment, skip */
1720 } else {
1721 DEBUG (8, fprintf (gc_debug_file, "Pinned try match %p (%s), size %zd\n", last_obj, safe_name (last_obj), last_obj_size));
1722 if (addr >= search_start && (char*)addr < (char*)last_obj + last_obj_size) {
1723 DEBUG (4, fprintf (gc_debug_file, "Pinned object %p, vtable %p (%s), count %d\n", search_start, *(void**)search_start, safe_name (search_start), count));
1724 binary_protocol_pin (search_start, (gpointer)LOAD_VTABLE (search_start), safe_object_get_size (search_start));
1725 pin_object (search_start);
1726 GRAY_OBJECT_ENQUEUE (queue, search_start);
1727 if (heap_dump_file)
1728 mono_sgen_pin_stats_register_object (search_start, last_obj_size);
1729 definitely_pinned [count] = search_start;
1730 count++;
1731 break;
1734 /* skip to the next object */
1735 search_start = (void*)((char*)search_start + last_obj_size);
1736 } while (search_start <= addr);
1737 /* we either pinned the correct object or we ignored the addr because
1738 * it points to unused zeroed memory.
1740 last = addr;
1742 start++;
1744 //printf ("effective pinned: %d (at the end: %d)\n", count, (char*)end_nursery - (char*)last);
1745 return count;
1748 void
1749 mono_sgen_pin_objects_in_section (GCMemSection *section, GrayQueue *queue)
1751 int num_entries = section->pin_queue_num_entries;
1752 if (num_entries) {
1753 void **start = section->pin_queue_start;
1754 int reduced_to;
1755 reduced_to = pin_objects_from_addresses (section, start, start + num_entries,
1756 section->data, section->next_data, queue);
1757 section->pin_queue_num_entries = reduced_to;
1758 if (!reduced_to)
1759 section->pin_queue_start = NULL;
1763 /* Sort the addresses in array in increasing order.
1764 * Done using a by-the book heap sort. Which has decent and stable performance, is pretty cache efficient.
1766 static void
1767 sort_addresses (void **array, int size)
1769 int i;
1770 void *tmp;
1772 for (i = 1; i < size; ++i) {
1773 int child = i;
1774 while (child > 0) {
1775 int parent = (child - 1) / 2;
1777 if (array [parent] >= array [child])
1778 break;
1780 tmp = array [parent];
1781 array [parent] = array [child];
1782 array [child] = tmp;
1784 child = parent;
1788 for (i = size - 1; i > 0; --i) {
1789 int end, root;
1790 tmp = array [i];
1791 array [i] = array [0];
1792 array [0] = tmp;
1794 end = i - 1;
1795 root = 0;
1797 while (root * 2 + 1 <= end) {
1798 int child = root * 2 + 1;
1800 if (child < end && array [child] < array [child + 1])
1801 ++child;
1802 if (array [root] >= array [child])
1803 break;
1805 tmp = array [root];
1806 array [root] = array [child];
1807 array [child] = tmp;
1809 root = child;
1814 static G_GNUC_UNUSED void
1815 print_nursery_gaps (void* start_nursery, void *end_nursery)
1817 int i;
1818 gpointer first = start_nursery;
1819 gpointer next;
1820 for (i = 0; i < next_pin_slot; ++i) {
1821 next = pin_queue [i];
1822 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %td\n", first, next, (char*)next-(char*)first);
1823 first = next;
1825 next = end_nursery;
1826 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %td\n", first, next, (char*)next-(char*)first);
1829 /* reduce the info in the pin queue, removing duplicate pointers and sorting them */
1830 static void
1831 optimize_pin_queue (int start_slot)
1833 void **start, **cur, **end;
1834 /* sort and uniq pin_queue: we just sort and we let the rest discard multiple values */
1835 /* it may be better to keep ranges of pinned memory instead of individually pinning objects */
1836 DEBUG (5, fprintf (gc_debug_file, "Sorting pin queue, size: %d\n", next_pin_slot));
1837 if ((next_pin_slot - start_slot) > 1)
1838 sort_addresses (pin_queue + start_slot, next_pin_slot - start_slot);
1839 start = cur = pin_queue + start_slot;
1840 end = pin_queue + next_pin_slot;
1841 while (cur < end) {
1842 *start = *cur++;
1843 while (*start == *cur && cur < end)
1844 cur++;
1845 start++;
1847 next_pin_slot = start - pin_queue;
1848 DEBUG (5, fprintf (gc_debug_file, "Pin queue reduced to size: %d\n", next_pin_slot));
1849 //DEBUG (6, print_nursery_gaps (start_nursery, end_nursery));
1854 * Scan the memory between start and end and queue values which could be pointers
1855 * to the area between start_nursery and end_nursery for later consideration.
1856 * Typically used for thread stacks.
1858 static void
1859 conservatively_pin_objects_from (void **start, void **end, void *start_nursery, void *end_nursery, int pin_type)
1861 int count = 0;
1862 while (start < end) {
1863 if (*start >= start_nursery && *start < end_nursery) {
1865 * *start can point to the middle of an object
1866 * note: should we handle pointing at the end of an object?
1867 * pinning in C# code disallows pointing at the end of an object
1868 * but there is some small chance that an optimizing C compiler
1869 * may keep the only reference to an object by pointing
1870 * at the end of it. We ignore this small chance for now.
1871 * Pointers to the end of an object are indistinguishable
1872 * from pointers to the start of the next object in memory
1873 * so if we allow that we'd need to pin two objects...
1874 * We queue the pointer in an array, the
1875 * array will then be sorted and uniqued. This way
1876 * we can coalesce several pinning pointers and it should
1877 * be faster since we'd do a memory scan with increasing
1878 * addresses. Note: we can align the address to the allocation
1879 * alignment, so the unique process is more effective.
1881 mword addr = (mword)*start;
1882 addr &= ~(ALLOC_ALIGN - 1);
1883 if (addr >= (mword)start_nursery && addr < (mword)end_nursery)
1884 pin_stage_ptr ((void*)addr);
1885 if (heap_dump_file)
1886 pin_stats_register_address ((char*)addr, pin_type);
1887 DEBUG (6, if (count) fprintf (gc_debug_file, "Pinning address %p\n", (void*)addr));
1888 count++;
1890 start++;
1892 DEBUG (7, if (count) fprintf (gc_debug_file, "found %d potential pinned heap pointers\n", count));
1896 * Debugging function: find in the conservative roots where @obj is being pinned.
1898 static G_GNUC_UNUSED void
1899 find_pinning_reference (char *obj, size_t size)
1901 RootRecord *root;
1902 int i;
1903 char *endobj = obj + size;
1904 for (i = 0; i < roots_hash_size [0]; ++i) {
1905 for (root = roots_hash [0][i]; root; root = root->next) {
1906 /* if desc is non-null it has precise info */
1907 if (!root->root_desc) {
1908 char ** start = (char**)root->start_root;
1909 while (start < (char**)root->end_root) {
1910 if (*start >= obj && *start < endobj) {
1911 DEBUG (0, fprintf (gc_debug_file, "Object %p referenced in pinned roots %p-%p (at %p in record %p)\n", obj, root->start_root, root->end_root, start, root));
1913 start++;
1918 find_pinning_ref_from_thread (obj, size);
1922 * The first thing we do in a collection is to identify pinned objects.
1923 * This function considers all the areas of memory that need to be
1924 * conservatively scanned.
1926 static void
1927 pin_from_roots (void *start_nursery, void *end_nursery)
1929 RootRecord *root;
1930 int i;
1931 DEBUG (2, fprintf (gc_debug_file, "Scanning pinned roots (%d bytes, %d/%d entries)\n", (int)roots_size, num_roots_entries [ROOT_TYPE_NORMAL], num_roots_entries [ROOT_TYPE_PINNED]));
1932 /* objects pinned from the API are inside these roots */
1933 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
1934 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
1935 DEBUG (6, fprintf (gc_debug_file, "Pinned roots %p-%p\n", root->start_root, root->end_root));
1936 conservatively_pin_objects_from ((void**)root->start_root, (void**)root->end_root, start_nursery, end_nursery, PIN_TYPE_OTHER);
1939 /* now deal with the thread stacks
1940 * in the future we should be able to conservatively scan only:
1941 * *) the cpu registers
1942 * *) the unmanaged stack frames
1943 * *) the _last_ managed stack frame
1944 * *) pointers slots in managed frames
1946 scan_thread_data (start_nursery, end_nursery, FALSE);
1948 evacuate_pin_staging_area ();
1951 static CopyOrMarkObjectFunc user_copy_or_mark_func;
1952 static GrayQueue *user_copy_or_mark_queue;
1954 static void
1955 single_arg_user_copy_or_mark (void **obj)
1957 user_copy_or_mark_func (obj, user_copy_or_mark_queue);
1961 * The memory area from start_root to end_root contains pointers to objects.
1962 * Their position is precisely described by @desc (this means that the pointer
1963 * can be either NULL or the pointer to the start of an object).
1964 * This functions copies them to to_space updates them.
1966 * This function is not thread-safe!
1968 static void
1969 precisely_scan_objects_from (CopyOrMarkObjectFunc copy_func, void** start_root, void** end_root, char* n_start, char *n_end, mword desc, GrayQueue *queue)
1971 switch (desc & ROOT_DESC_TYPE_MASK) {
1972 case ROOT_DESC_BITMAP:
1973 desc >>= ROOT_DESC_TYPE_SHIFT;
1974 while (desc) {
1975 if ((desc & 1) && *start_root) {
1976 copy_func (start_root, queue);
1977 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", start_root, *start_root));
1978 drain_gray_stack (queue);
1980 desc >>= 1;
1981 start_root++;
1983 return;
1984 case ROOT_DESC_COMPLEX: {
1985 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1986 int bwords = (*bitmap_data) - 1;
1987 void **start_run = start_root;
1988 bitmap_data++;
1989 while (bwords-- > 0) {
1990 gsize bmap = *bitmap_data++;
1991 void **objptr = start_run;
1992 while (bmap) {
1993 if ((bmap & 1) && *objptr) {
1994 copy_func (objptr, queue);
1995 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", objptr, *objptr));
1996 drain_gray_stack (queue);
1998 bmap >>= 1;
1999 ++objptr;
2001 start_run += GC_BITS_PER_WORD;
2003 break;
2005 case ROOT_DESC_USER: {
2006 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
2007 user_copy_or_mark_func = copy_func;
2008 user_copy_or_mark_queue = queue;
2009 marker (start_root, single_arg_user_copy_or_mark);
2010 user_copy_or_mark_func = NULL;
2011 user_copy_or_mark_queue = NULL;
2012 break;
2014 case ROOT_DESC_RUN_LEN:
2015 g_assert_not_reached ();
2016 default:
2017 g_assert_not_reached ();
2021 void
2022 mono_sgen_update_heap_boundaries (mword low, mword high)
2024 mword old;
2026 do {
2027 old = lowest_heap_address;
2028 if (low >= old)
2029 break;
2030 } while (SGEN_CAS_PTR ((gpointer*)&lowest_heap_address, (gpointer)low, (gpointer)old) != (gpointer)old);
2032 do {
2033 old = highest_heap_address;
2034 if (high <= old)
2035 break;
2036 } while (SGEN_CAS_PTR ((gpointer*)&highest_heap_address, (gpointer)high, (gpointer)old) != (gpointer)old);
2039 static Fragment*
2040 alloc_fragment (void)
2042 Fragment *frag = fragment_freelist;
2043 if (frag) {
2044 fragment_freelist = frag->next;
2045 frag->next = NULL;
2046 return frag;
2048 frag = mono_sgen_alloc_internal (INTERNAL_MEM_FRAGMENT);
2049 frag->next = NULL;
2050 return frag;
2053 /* size must be a power of 2 */
2054 void*
2055 mono_sgen_alloc_os_memory_aligned (mword size, mword alignment, gboolean activate)
2057 /* Allocate twice the memory to be able to put the block on an aligned address */
2058 char *mem = mono_sgen_alloc_os_memory (size + alignment, activate);
2059 char *aligned;
2061 g_assert (mem);
2063 aligned = (char*)((mword)(mem + (alignment - 1)) & ~(alignment - 1));
2064 g_assert (aligned >= mem && aligned + size <= mem + size + alignment && !((mword)aligned & (alignment - 1)));
2066 if (aligned > mem)
2067 mono_sgen_free_os_memory (mem, aligned - mem);
2068 if (aligned + size < mem + size + alignment)
2069 mono_sgen_free_os_memory (aligned + size, (mem + size + alignment) - (aligned + size));
2071 return aligned;
2075 * Allocate and setup the data structures needed to be able to allocate objects
2076 * in the nursery. The nursery is stored in nursery_section.
2078 static void
2079 alloc_nursery (void)
2081 GCMemSection *section;
2082 char *data;
2083 int scan_starts;
2084 Fragment *frag;
2085 int alloc_size;
2087 if (nursery_section)
2088 return;
2089 DEBUG (2, fprintf (gc_debug_file, "Allocating nursery size: %lu\n", (unsigned long)nursery_size));
2090 /* later we will alloc a larger area for the nursery but only activate
2091 * what we need. The rest will be used as expansion if we have too many pinned
2092 * objects in the existing nursery.
2094 /* FIXME: handle OOM */
2095 section = mono_sgen_alloc_internal (INTERNAL_MEM_SECTION);
2097 g_assert (nursery_size == DEFAULT_NURSERY_SIZE);
2098 alloc_size = nursery_size;
2099 #ifdef SGEN_ALIGN_NURSERY
2100 data = major_collector.alloc_heap (alloc_size, alloc_size, DEFAULT_NURSERY_BITS);
2101 #else
2102 data = major_collector.alloc_heap (alloc_size, 0, DEFAULT_NURSERY_BITS);
2103 #endif
2104 nursery_start = data;
2105 nursery_real_end = nursery_start + nursery_size;
2106 mono_sgen_update_heap_boundaries ((mword)nursery_start, (mword)nursery_real_end);
2107 nursery_next = nursery_start;
2108 DEBUG (4, fprintf (gc_debug_file, "Expanding nursery size (%p-%p): %lu, total: %lu\n", data, data + alloc_size, (unsigned long)nursery_size, (unsigned long)total_alloc));
2109 section->data = section->next_data = data;
2110 section->size = alloc_size;
2111 section->end_data = nursery_real_end;
2112 scan_starts = (alloc_size + SCAN_START_SIZE - 1) / SCAN_START_SIZE;
2113 section->scan_starts = mono_sgen_alloc_internal_dynamic (sizeof (char*) * scan_starts, INTERNAL_MEM_SCAN_STARTS);
2114 section->num_scan_start = scan_starts;
2115 section->block.role = MEMORY_ROLE_GEN0;
2116 section->block.next = NULL;
2118 nursery_section = section;
2120 /* Setup the single first large fragment */
2121 frag = alloc_fragment ();
2122 frag->fragment_start = nursery_start;
2123 frag->fragment_limit = nursery_start;
2124 frag->fragment_end = nursery_real_end;
2125 nursery_frag_real_end = nursery_real_end;
2126 /* FIXME: frag here is lost */
2129 void*
2130 mono_gc_get_nursery (int *shift_bits, size_t *size)
2132 *size = nursery_size;
2133 #ifdef SGEN_ALIGN_NURSERY
2134 *shift_bits = DEFAULT_NURSERY_BITS;
2135 #else
2136 *shift_bits = -1;
2137 #endif
2138 return nursery_start;
2141 static void
2142 scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list, GrayQueue *queue)
2144 FinalizeEntry *fin;
2146 for (fin = list; fin; fin = fin->next) {
2147 if (!fin->object)
2148 continue;
2149 DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
2150 copy_func (&fin->object, queue);
2154 static mword fragment_total = 0;
2156 * We found a fragment of free memory in the nursery: memzero it and if
2157 * it is big enough, add it to the list of fragments that can be used for
2158 * allocation.
2160 static void
2161 add_nursery_frag (size_t frag_size, char* frag_start, char* frag_end)
2163 Fragment *fragment;
2164 DEBUG (4, fprintf (gc_debug_file, "Found empty fragment: %p-%p, size: %zd\n", frag_start, frag_end, frag_size));
2165 binary_protocol_empty (frag_start, frag_size);
2166 /* Not worth dealing with smaller fragments: need to tune */
2167 if (frag_size >= FRAGMENT_MIN_SIZE) {
2168 /* memsetting just the first chunk start is bound to provide better cache locality */
2169 if (nursery_clear_policy == CLEAR_AT_GC)
2170 memset (frag_start, 0, frag_size);
2172 fragment = alloc_fragment ();
2173 fragment->fragment_start = frag_start;
2174 fragment->fragment_limit = frag_start;
2175 fragment->fragment_end = frag_end;
2176 fragment->next = nursery_fragments;
2177 nursery_fragments = fragment;
2178 fragment_total += frag_size;
2179 } else {
2180 /* Clear unused fragments, pinning depends on this */
2181 /*TODO place an int[] here instead of the memset if size justify it*/
2182 memset (frag_start, 0, frag_size);
2186 static const char*
2187 generation_name (int generation)
2189 switch (generation) {
2190 case GENERATION_NURSERY: return "nursery";
2191 case GENERATION_OLD: return "old";
2192 default: g_assert_not_reached ();
2196 static DisappearingLinkHashTable*
2197 get_dislink_hash_table (int generation)
2199 switch (generation) {
2200 case GENERATION_NURSERY: return &minor_disappearing_link_hash;
2201 case GENERATION_OLD: return &major_disappearing_link_hash;
2202 default: g_assert_not_reached ();
2206 static FinalizeEntryHashTable*
2207 get_finalize_entry_hash_table (int generation)
2209 switch (generation) {
2210 case GENERATION_NURSERY: return &minor_finalizable_hash;
2211 case GENERATION_OLD: return &major_finalizable_hash;
2212 default: g_assert_not_reached ();
2216 static void
2217 finish_gray_stack (char *start_addr, char *end_addr, int generation, GrayQueue *queue)
2219 TV_DECLARE (atv);
2220 TV_DECLARE (btv);
2221 int fin_ready;
2222 int ephemeron_rounds = 0;
2223 CopyOrMarkObjectFunc copy_func = current_collection_generation == GENERATION_NURSERY ? major_collector.copy_object : major_collector.copy_or_mark_object;
2226 * We copied all the reachable objects. Now it's the time to copy
2227 * the objects that were not referenced by the roots, but by the copied objects.
2228 * we built a stack of objects pointed to by gray_start: they are
2229 * additional roots and we may add more items as we go.
2230 * We loop until gray_start == gray_objects which means no more objects have
2231 * been added. Note this is iterative: no recursion is involved.
2232 * We need to walk the LO list as well in search of marked big objects
2233 * (use a flag since this is needed only on major collections). We need to loop
2234 * here as well, so keep a counter of marked LO (increasing it in copy_object).
2235 * To achieve better cache locality and cache usage, we drain the gray stack
2236 * frequently, after each object is copied, and just finish the work here.
2238 drain_gray_stack (queue);
2239 TV_GETTIME (atv);
2240 DEBUG (2, fprintf (gc_debug_file, "%s generation done\n", generation_name (generation)));
2241 /* walk the finalization queue and move also the objects that need to be
2242 * finalized: use the finalized objects as new roots so the objects they depend
2243 * on are also not reclaimed. As with the roots above, only objects in the nursery
2244 * are marked/copied.
2245 * We need a loop here, since objects ready for finalizers may reference other objects
2246 * that are fin-ready. Speedup with a flag?
2248 do {
2250 * Walk the ephemeron tables marking all values with reachable keys. This must be completely done
2251 * before processing finalizable objects to avoid finalizing reachable values.
2253 * It must be done inside the finalizaters loop since objects must not be removed from CWT tables
2254 * while they are been finalized.
2256 int done_with_ephemerons = 0;
2257 do {
2258 done_with_ephemerons = mark_ephemerons_in_range (copy_func, start_addr, end_addr, queue);
2259 drain_gray_stack (queue);
2260 ++ephemeron_rounds;
2261 } while (!done_with_ephemerons);
2263 fin_ready = num_ready_finalizers;
2264 finalize_in_range (copy_func, start_addr, end_addr, generation, queue);
2265 if (generation == GENERATION_OLD)
2266 finalize_in_range (copy_func, nursery_start, nursery_real_end, GENERATION_NURSERY, queue);
2268 /* drain the new stack that might have been created */
2269 DEBUG (6, fprintf (gc_debug_file, "Precise scan of gray area post fin\n"));
2270 drain_gray_stack (queue);
2271 } while (fin_ready != num_ready_finalizers);
2274 * Clear ephemeron pairs with unreachable keys.
2275 * We pass the copy func so we can figure out if an array was promoted or not.
2277 clear_unreachable_ephemerons (copy_func, start_addr, end_addr, queue);
2279 TV_GETTIME (btv);
2280 DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan for %s generation: %d usecs %d ephemeron roundss\n", generation_name (generation), TV_ELAPSED (atv, btv), ephemeron_rounds));
2283 * handle disappearing links
2284 * Note we do this after checking the finalization queue because if an object
2285 * survives (at least long enough to be finalized) we don't clear the link.
2286 * This also deals with a possible issue with the monitor reclamation: with the Boehm
2287 * GC a finalized object my lose the monitor because it is cleared before the finalizer is
2288 * called.
2290 g_assert (gray_object_queue_is_empty (queue));
2291 for (;;) {
2292 null_link_in_range (copy_func, start_addr, end_addr, generation, queue);
2293 if (generation == GENERATION_OLD)
2294 null_link_in_range (copy_func, start_addr, end_addr, GENERATION_NURSERY, queue);
2295 if (gray_object_queue_is_empty (queue))
2296 break;
2297 drain_gray_stack (queue);
2300 g_assert (gray_object_queue_is_empty (queue));
2303 void
2304 mono_sgen_check_section_scan_starts (GCMemSection *section)
2306 int i;
2307 for (i = 0; i < section->num_scan_start; ++i) {
2308 if (section->scan_starts [i]) {
2309 guint size = safe_object_get_size ((MonoObject*) section->scan_starts [i]);
2310 g_assert (size >= sizeof (MonoObject) && size <= MAX_SMALL_OBJ_SIZE);
2315 static void
2316 check_scan_starts (void)
2318 if (!do_scan_starts_check)
2319 return;
2320 mono_sgen_check_section_scan_starts (nursery_section);
2321 major_collector.check_scan_starts ();
2324 static int last_num_pinned = 0;
2326 static void
2327 build_nursery_fragments (void **start, int num_entries)
2329 char *frag_start, *frag_end;
2330 size_t frag_size;
2331 int i;
2333 while (nursery_fragments) {
2334 Fragment *next = nursery_fragments->next;
2335 nursery_fragments->next = fragment_freelist;
2336 fragment_freelist = nursery_fragments;
2337 nursery_fragments = next;
2339 frag_start = nursery_start;
2340 fragment_total = 0;
2341 /* clear scan starts */
2342 memset (nursery_section->scan_starts, 0, nursery_section->num_scan_start * sizeof (gpointer));
2343 for (i = 0; i < num_entries; ++i) {
2344 frag_end = start [i];
2345 /* remove the pin bit from pinned objects */
2346 unpin_object (frag_end);
2347 nursery_section->scan_starts [((char*)frag_end - (char*)nursery_section->data)/SCAN_START_SIZE] = frag_end;
2348 frag_size = frag_end - frag_start;
2349 if (frag_size)
2350 add_nursery_frag (frag_size, frag_start, frag_end);
2351 frag_size = ALIGN_UP (safe_object_get_size ((MonoObject*)start [i]));
2352 frag_start = (char*)start [i] + frag_size;
2354 nursery_last_pinned_end = frag_start;
2355 frag_end = nursery_real_end;
2356 frag_size = frag_end - frag_start;
2357 if (frag_size)
2358 add_nursery_frag (frag_size, frag_start, frag_end);
2359 if (!nursery_fragments) {
2360 DEBUG (1, fprintf (gc_debug_file, "Nursery fully pinned (%d)\n", num_entries));
2361 for (i = 0; i < num_entries; ++i) {
2362 DEBUG (3, fprintf (gc_debug_file, "Bastard pinning obj %p (%s), size: %d\n", start [i], safe_name (start [i]), safe_object_get_size (start [i])));
2364 degraded_mode = 1;
2367 nursery_next = nursery_frag_real_end = NULL;
2369 /* Clear TLABs for all threads */
2370 clear_tlabs ();
2373 static void
2374 scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type, GrayQueue *queue)
2376 int i;
2377 RootRecord *root;
2378 for (i = 0; i < roots_hash_size [root_type]; ++i) {
2379 for (root = roots_hash [root_type][i]; root; root = root->next) {
2380 DEBUG (6, fprintf (gc_debug_file, "Precise root scan %p-%p (desc: %p)\n", root->start_root, root->end_root, (void*)root->root_desc));
2381 precisely_scan_objects_from (copy_func, (void**)root->start_root, (void**)root->end_root, addr_start, addr_end, root->root_desc, queue);
2386 void
2387 mono_sgen_dump_occupied (char *start, char *end, char *section_start)
2389 fprintf (heap_dump_file, "<occupied offset=\"%td\" size=\"%td\"/>\n", start - section_start, end - start);
2392 void
2393 mono_sgen_dump_section (GCMemSection *section, const char *type)
2395 char *start = section->data;
2396 char *end = section->data + section->size;
2397 char *occ_start = NULL;
2398 GCVTable *vt;
2399 char *old_start = NULL; /* just for debugging */
2401 fprintf (heap_dump_file, "<section type=\"%s\" size=\"%lu\">\n", type, (unsigned long)section->size);
2403 while (start < end) {
2404 guint size;
2405 MonoClass *class;
2407 if (!*(void**)start) {
2408 if (occ_start) {
2409 mono_sgen_dump_occupied (occ_start, start, section->data);
2410 occ_start = NULL;
2412 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
2413 continue;
2415 g_assert (start < section->next_data);
2417 if (!occ_start)
2418 occ_start = start;
2420 vt = (GCVTable*)LOAD_VTABLE (start);
2421 class = vt->klass;
2423 size = ALIGN_UP (safe_object_get_size ((MonoObject*) start));
2426 fprintf (heap_dump_file, "<object offset=\"%d\" class=\"%s.%s\" size=\"%d\"/>\n",
2427 start - section->data,
2428 vt->klass->name_space, vt->klass->name,
2429 size);
2432 old_start = start;
2433 start += size;
2435 if (occ_start)
2436 mono_sgen_dump_occupied (occ_start, start, section->data);
2438 fprintf (heap_dump_file, "</section>\n");
2441 static void
2442 dump_object (MonoObject *obj, gboolean dump_location)
2444 static char class_name [1024];
2446 MonoClass *class = mono_object_class (obj);
2447 int i, j;
2450 * Python's XML parser is too stupid to parse angle brackets
2451 * in strings, so we just ignore them;
2453 i = j = 0;
2454 while (class->name [i] && j < sizeof (class_name) - 1) {
2455 if (!strchr ("<>\"", class->name [i]))
2456 class_name [j++] = class->name [i];
2457 ++i;
2459 g_assert (j < sizeof (class_name));
2460 class_name [j] = 0;
2462 fprintf (heap_dump_file, "<object class=\"%s.%s\" size=\"%d\"",
2463 class->name_space, class_name,
2464 safe_object_get_size (obj));
2465 if (dump_location) {
2466 const char *location;
2467 if (ptr_in_nursery (obj))
2468 location = "nursery";
2469 else if (safe_object_get_size (obj) <= MAX_SMALL_OBJ_SIZE)
2470 location = "major";
2471 else
2472 location = "LOS";
2473 fprintf (heap_dump_file, " location=\"%s\"", location);
2475 fprintf (heap_dump_file, "/>\n");
2478 static void
2479 dump_heap (const char *type, int num, const char *reason)
2481 ObjectList *list;
2482 LOSObject *bigobj;
2484 fprintf (heap_dump_file, "<collection type=\"%s\" num=\"%d\"", type, num);
2485 if (reason)
2486 fprintf (heap_dump_file, " reason=\"%s\"", reason);
2487 fprintf (heap_dump_file, ">\n");
2488 fprintf (heap_dump_file, "<other-mem-usage type=\"mempools\" size=\"%ld\"/>\n", mono_mempool_get_bytes_allocated ());
2489 mono_sgen_dump_internal_mem_usage (heap_dump_file);
2490 fprintf (heap_dump_file, "<pinned type=\"stack\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_STACK]);
2491 /* fprintf (heap_dump_file, "<pinned type=\"static-data\" bytes=\"%d\"/>\n", pinned_byte_counts [PIN_TYPE_STATIC_DATA]); */
2492 fprintf (heap_dump_file, "<pinned type=\"other\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_OTHER]);
2494 fprintf (heap_dump_file, "<pinned-objects>\n");
2495 for (list = pinned_objects; list; list = list->next)
2496 dump_object (list->obj, TRUE);
2497 fprintf (heap_dump_file, "</pinned-objects>\n");
2499 mono_sgen_dump_section (nursery_section, "nursery");
2501 major_collector.dump_heap (heap_dump_file);
2503 fprintf (heap_dump_file, "<los>\n");
2504 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
2505 dump_object ((MonoObject*)bigobj->data, FALSE);
2506 fprintf (heap_dump_file, "</los>\n");
2508 fprintf (heap_dump_file, "</collection>\n");
2511 void
2512 mono_sgen_register_moved_object (void *obj, void *destination)
2514 g_assert (mono_profiler_events & MONO_PROFILE_GC_MOVES);
2516 /* FIXME: handle this for parallel collector */
2517 g_assert (!major_collector.is_parallel);
2519 if (moved_objects_idx == MOVED_OBJECTS_NUM) {
2520 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
2521 moved_objects_idx = 0;
2523 moved_objects [moved_objects_idx++] = obj;
2524 moved_objects [moved_objects_idx++] = destination;
2527 static void
2528 init_stats (void)
2530 static gboolean inited = FALSE;
2532 if (inited)
2533 return;
2535 mono_counters_register ("Minor fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pre_collection_fragment_clear);
2536 mono_counters_register ("Minor pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pinning);
2537 mono_counters_register ("Minor scan remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_remsets);
2538 mono_counters_register ("Minor scan cardtables", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_card_table);
2539 mono_counters_register ("Minor scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_pinned);
2540 mono_counters_register ("Minor scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_registered_roots);
2541 mono_counters_register ("Minor scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_thread_data);
2542 mono_counters_register ("Minor finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_finish_gray_stack);
2543 mono_counters_register ("Minor fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_fragment_creation);
2545 mono_counters_register ("Major fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pre_collection_fragment_clear);
2546 mono_counters_register ("Major pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pinning);
2547 mono_counters_register ("Major scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_pinned);
2548 mono_counters_register ("Major scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_registered_roots);
2549 mono_counters_register ("Major scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_thread_data);
2550 mono_counters_register ("Major scan alloc_pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_alloc_pinned);
2551 mono_counters_register ("Major scan finalized", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_finalized);
2552 mono_counters_register ("Major scan big objects", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_big_objects);
2553 mono_counters_register ("Major finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_finish_gray_stack);
2554 mono_counters_register ("Major free big objects", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_free_bigobjs);
2555 mono_counters_register ("Major LOS sweep", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_los_sweep);
2556 mono_counters_register ("Major sweep", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_sweep);
2557 mono_counters_register ("Major fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_fragment_creation);
2560 #ifdef HEAVY_STATISTICS
2561 mono_counters_register ("WBarrier set field", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_field);
2562 mono_counters_register ("WBarrier set arrayref", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_arrayref);
2563 mono_counters_register ("WBarrier arrayref copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_arrayref_copy);
2564 mono_counters_register ("WBarrier generic store called", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store);
2565 mono_counters_register ("WBarrier generic store stored", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store_remset);
2566 mono_counters_register ("WBarrier set root", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_root);
2567 mono_counters_register ("WBarrier value copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_value_copy);
2568 mono_counters_register ("WBarrier object copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_object_copy);
2570 mono_counters_register ("# objects allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced);
2571 mono_counters_register ("bytes allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced);
2572 mono_counters_register ("# objects allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced_degraded);
2573 mono_counters_register ("bytes allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_degraded);
2574 mono_counters_register ("bytes allocated in LOS", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_los);
2576 mono_counters_register ("# copy_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_nursery);
2577 mono_counters_register ("# objects copied (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_nursery);
2578 mono_counters_register ("# copy_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_major);
2579 mono_counters_register ("# objects copied (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_major);
2581 mono_counters_register ("# scan_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_nursery);
2582 mono_counters_register ("# scan_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_major);
2584 mono_counters_register ("# nursery copy_object() failed from space", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_from_space);
2585 mono_counters_register ("# nursery copy_object() failed forwarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_forwarded);
2586 mono_counters_register ("# nursery copy_object() failed pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_pinned);
2588 mono_counters_register ("# wasted fragments used", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_used);
2589 mono_counters_register ("bytes in wasted fragments", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_bytes);
2591 mono_counters_register ("Store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets);
2592 mono_counters_register ("Unique store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets_unique);
2593 mono_counters_register ("Saved remsets 1", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_1);
2594 mono_counters_register ("Saved remsets 2", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_2);
2595 mono_counters_register ("Non-global remsets processed", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_local_remsets_processed);
2596 mono_counters_register ("Global remsets added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_added);
2597 mono_counters_register ("Global remsets re-added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_readded);
2598 mono_counters_register ("Global remsets processed", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_processed);
2599 mono_counters_register ("Global remsets discarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_discarded);
2600 #endif
2602 inited = TRUE;
2605 static gboolean
2606 need_major_collection (void)
2608 mword los_alloced = los_memory_usage - MIN (last_los_memory_usage, los_memory_usage);
2609 return minor_collection_sections_alloced * major_collector.section_size + los_alloced > minor_collection_allowance;
2613 * Collect objects in the nursery. Returns whether to trigger a major
2614 * collection.
2616 static gboolean
2617 collect_nursery (size_t requested_size)
2619 size_t max_garbage_amount;
2620 char *orig_nursery_next;
2621 TV_DECLARE (all_atv);
2622 TV_DECLARE (all_btv);
2623 TV_DECLARE (atv);
2624 TV_DECLARE (btv);
2626 mono_perfcounters->gc_collections0++;
2628 current_collection_generation = GENERATION_NURSERY;
2630 binary_protocol_collection (GENERATION_NURSERY);
2631 check_scan_starts ();
2633 degraded_mode = 0;
2634 orig_nursery_next = nursery_next;
2635 nursery_next = MAX (nursery_next, nursery_last_pinned_end);
2636 /* FIXME: optimize later to use the higher address where an object can be present */
2637 nursery_next = MAX (nursery_next, nursery_real_end);
2639 DEBUG (1, fprintf (gc_debug_file, "Start nursery collection %d %p-%p, size: %d\n", num_minor_gcs, nursery_start, nursery_next, (int)(nursery_next - nursery_start)));
2640 max_garbage_amount = nursery_next - nursery_start;
2641 g_assert (nursery_section->size >= max_garbage_amount);
2643 /* world must be stopped already */
2644 TV_GETTIME (all_atv);
2645 atv = all_atv;
2647 /* Pinning no longer depends on clearing all nursery fragments */
2648 clear_current_nursery_fragment (orig_nursery_next);
2650 TV_GETTIME (btv);
2651 time_minor_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
2653 if (xdomain_checks)
2654 check_for_xdomain_refs ();
2656 nursery_section->next_data = nursery_next;
2658 major_collector.start_nursery_collection ();
2660 gray_object_queue_init (&gray_queue, mono_sgen_get_unmanaged_allocator ());
2662 num_minor_gcs++;
2663 mono_stats.minor_gc_count ++;
2665 global_remset_cache_clear ();
2667 /* pin from pinned handles */
2668 init_pinning ();
2669 mono_profiler_gc_event (MONO_GC_EVENT_MARK_START, 0);
2670 pin_from_roots (nursery_start, nursery_next);
2671 /* identify pinned objects */
2672 optimize_pin_queue (0);
2673 next_pin_slot = pin_objects_from_addresses (nursery_section, pin_queue, pin_queue + next_pin_slot, nursery_start, nursery_next, &gray_queue);
2674 nursery_section->pin_queue_start = pin_queue;
2675 nursery_section->pin_queue_num_entries = next_pin_slot;
2676 TV_GETTIME (atv);
2677 time_minor_pinning += TV_ELAPSED_MS (btv, atv);
2678 DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (btv, atv)));
2679 DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
2681 if (consistency_check_at_minor_collection)
2682 check_consistency ();
2685 * walk all the roots and copy the young objects to the old generation,
2686 * starting from to_space
2689 scan_from_remsets (nursery_start, nursery_next, &gray_queue);
2690 /* we don't have complete write barrier yet, so we scan all the old generation sections */
2691 TV_GETTIME (btv);
2692 time_minor_scan_remsets += TV_ELAPSED_MS (atv, btv);
2693 DEBUG (2, fprintf (gc_debug_file, "Old generation scan: %d usecs\n", TV_ELAPSED (atv, btv)));
2695 if (use_cardtable) {
2696 atv = btv;
2697 card_tables_collect_stats (TRUE);
2698 scan_from_card_tables (nursery_start, nursery_next, &gray_queue);
2699 TV_GETTIME (btv);
2700 time_minor_scan_card_table += TV_ELAPSED_MS (atv, btv);
2703 drain_gray_stack (&gray_queue);
2705 TV_GETTIME (atv);
2706 time_minor_scan_pinned += TV_ELAPSED_MS (btv, atv);
2707 /* registered roots, this includes static fields */
2708 scan_from_registered_roots (major_collector.copy_object, nursery_start, nursery_next, ROOT_TYPE_NORMAL, &gray_queue);
2709 scan_from_registered_roots (major_collector.copy_object, nursery_start, nursery_next, ROOT_TYPE_WBARRIER, &gray_queue);
2710 TV_GETTIME (btv);
2711 time_minor_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
2712 /* thread data */
2713 scan_thread_data (nursery_start, nursery_next, TRUE);
2714 TV_GETTIME (atv);
2715 time_minor_scan_thread_data += TV_ELAPSED_MS (btv, atv);
2716 btv = atv;
2718 finish_gray_stack (nursery_start, nursery_next, GENERATION_NURSERY, &gray_queue);
2719 TV_GETTIME (atv);
2720 time_minor_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
2721 mono_profiler_gc_event (MONO_GC_EVENT_MARK_END, 0);
2723 /* walk the pin_queue, build up the fragment list of free memory, unmark
2724 * pinned objects as we go, memzero() the empty fragments so they are ready for the
2725 * next allocations.
2727 mono_profiler_gc_event (MONO_GC_EVENT_RECLAIM_START, 0);
2728 build_nursery_fragments (pin_queue, next_pin_slot);
2729 mono_profiler_gc_event (MONO_GC_EVENT_RECLAIM_END, 0);
2730 TV_GETTIME (btv);
2731 time_minor_fragment_creation += TV_ELAPSED_MS (atv, btv);
2732 DEBUG (2, fprintf (gc_debug_file, "Fragment creation: %d usecs, %lu bytes available\n", TV_ELAPSED (atv, btv), (unsigned long)fragment_total));
2734 if (consistency_check_at_minor_collection)
2735 check_major_refs ();
2737 major_collector.finish_nursery_collection ();
2739 TV_GETTIME (all_btv);
2740 mono_stats.minor_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
2742 if (heap_dump_file)
2743 dump_heap ("minor", num_minor_gcs - 1, NULL);
2745 /* prepare the pin queue for the next collection */
2746 last_num_pinned = next_pin_slot;
2747 next_pin_slot = 0;
2748 if (fin_ready_list || critical_fin_list) {
2749 DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
2750 mono_gc_finalize_notify ();
2752 pin_stats_reset ();
2754 g_assert (gray_object_queue_is_empty (&gray_queue));
2756 if (use_cardtable)
2757 card_tables_collect_stats (FALSE);
2759 check_scan_starts ();
2761 binary_protocol_flush_buffers (FALSE);
2763 current_collection_generation = -1;
2765 return need_major_collection ();
2768 static void
2769 major_do_collection (const char *reason)
2771 LOSObject *bigobj, *prevbo;
2772 TV_DECLARE (all_atv);
2773 TV_DECLARE (all_btv);
2774 TV_DECLARE (atv);
2775 TV_DECLARE (btv);
2776 /* FIXME: only use these values for the precise scan
2777 * note that to_space pointers should be excluded anyway...
2779 char *heap_start = NULL;
2780 char *heap_end = (char*)-1;
2781 int old_num_major_sections = major_collector.get_num_major_sections ();
2782 int num_major_sections, num_major_sections_saved, save_target, allowance_target;
2783 mword los_memory_saved, los_memory_alloced, old_los_memory_usage;
2785 mono_perfcounters->gc_collections1++;
2788 * A domain could have been freed, resulting in
2789 * los_memory_usage being less than last_los_memory_usage.
2791 los_memory_alloced = los_memory_usage - MIN (last_los_memory_usage, los_memory_usage);
2792 old_los_memory_usage = los_memory_usage;
2794 //count_ref_nonref_objs ();
2795 //consistency_check ();
2797 binary_protocol_collection (GENERATION_OLD);
2798 check_scan_starts ();
2799 gray_object_queue_init (&gray_queue, mono_sgen_get_unmanaged_allocator ());
2800 if (major_collector.is_parallel)
2801 gray_object_queue_init (&workers_distribute_gray_queue, mono_sgen_get_unmanaged_allocator ());
2803 degraded_mode = 0;
2804 DEBUG (1, fprintf (gc_debug_file, "Start major collection %d\n", num_major_gcs));
2805 num_major_gcs++;
2806 mono_stats.major_gc_count ++;
2808 /* world must be stopped already */
2809 TV_GETTIME (all_atv);
2810 atv = all_atv;
2812 /* Pinning depends on this */
2813 clear_nursery_fragments (nursery_next);
2815 TV_GETTIME (btv);
2816 time_major_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
2818 if (xdomain_checks)
2819 check_for_xdomain_refs ();
2821 nursery_section->next_data = nursery_real_end;
2822 /* we should also coalesce scanning from sections close to each other
2823 * and deal with pointers outside of the sections later.
2826 if (major_collector.start_major_collection)
2827 major_collector.start_major_collection ();
2829 /* The remsets are not useful for a major collection */
2830 clear_remsets ();
2831 global_remset_cache_clear ();
2832 if (use_cardtable)
2833 card_table_clear ();
2835 TV_GETTIME (atv);
2836 init_pinning ();
2837 DEBUG (6, fprintf (gc_debug_file, "Collecting pinned addresses\n"));
2838 pin_from_roots ((void*)lowest_heap_address, (void*)highest_heap_address);
2839 optimize_pin_queue (0);
2842 * pin_queue now contains all candidate pointers, sorted and
2843 * uniqued. We must do two passes now to figure out which
2844 * objects are pinned.
2846 * The first is to find within the pin_queue the area for each
2847 * section. This requires that the pin_queue be sorted. We
2848 * also process the LOS objects and pinned chunks here.
2850 * The second, destructive, pass is to reduce the section
2851 * areas to pointers to the actually pinned objects.
2853 DEBUG (6, fprintf (gc_debug_file, "Pinning from sections\n"));
2854 /* first pass for the sections */
2855 mono_sgen_find_section_pin_queue_start_end (nursery_section);
2856 major_collector.find_pin_queue_start_ends (WORKERS_DISTRIBUTE_GRAY_QUEUE);
2857 /* identify possible pointers to the insize of large objects */
2858 DEBUG (6, fprintf (gc_debug_file, "Pinning from large objects\n"));
2859 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
2860 int dummy;
2861 if (mono_sgen_find_optimized_pin_queue_area (bigobj->data, (char*)bigobj->data + bigobj->size, &dummy)) {
2862 pin_object (bigobj->data);
2863 /* FIXME: only enqueue if object has references */
2864 GRAY_OBJECT_ENQUEUE (WORKERS_DISTRIBUTE_GRAY_QUEUE, bigobj->data);
2865 if (heap_dump_file)
2866 mono_sgen_pin_stats_register_object ((char*) bigobj->data, safe_object_get_size ((MonoObject*) bigobj->data));
2867 DEBUG (6, fprintf (gc_debug_file, "Marked large object %p (%s) size: %lu from roots\n", bigobj->data, safe_name (bigobj->data), (unsigned long)bigobj->size));
2870 /* second pass for the sections */
2871 mono_sgen_pin_objects_in_section (nursery_section, WORKERS_DISTRIBUTE_GRAY_QUEUE);
2872 major_collector.pin_objects (WORKERS_DISTRIBUTE_GRAY_QUEUE);
2874 TV_GETTIME (btv);
2875 time_major_pinning += TV_ELAPSED_MS (atv, btv);
2876 DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (atv, btv)));
2877 DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
2879 major_collector.init_to_space ();
2881 workers_start_all_workers (1);
2883 TV_GETTIME (atv);
2884 time_major_scan_pinned += TV_ELAPSED_MS (btv, atv);
2886 /* registered roots, this includes static fields */
2887 scan_from_registered_roots (major_collector.copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_NORMAL, WORKERS_DISTRIBUTE_GRAY_QUEUE);
2888 scan_from_registered_roots (major_collector.copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_WBARRIER, WORKERS_DISTRIBUTE_GRAY_QUEUE);
2889 TV_GETTIME (btv);
2890 time_major_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
2892 /* Threads */
2893 /* FIXME: This is the wrong place for this, because it does
2894 pinning */
2895 scan_thread_data (heap_start, heap_end, TRUE);
2896 TV_GETTIME (atv);
2897 time_major_scan_thread_data += TV_ELAPSED_MS (btv, atv);
2899 TV_GETTIME (btv);
2900 time_major_scan_alloc_pinned += TV_ELAPSED_MS (atv, btv);
2902 /* scan the list of objects ready for finalization */
2903 scan_finalizer_entries (major_collector.copy_or_mark_object, fin_ready_list, WORKERS_DISTRIBUTE_GRAY_QUEUE);
2904 scan_finalizer_entries (major_collector.copy_or_mark_object, critical_fin_list, WORKERS_DISTRIBUTE_GRAY_QUEUE);
2905 TV_GETTIME (atv);
2906 time_major_scan_finalized += TV_ELAPSED_MS (btv, atv);
2907 DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (btv, atv)));
2909 TV_GETTIME (btv);
2910 time_major_scan_big_objects += TV_ELAPSED_MS (atv, btv);
2912 if (major_collector.is_parallel) {
2913 while (!gray_object_queue_is_empty (WORKERS_DISTRIBUTE_GRAY_QUEUE)) {
2914 workers_distribute_gray_queue_sections ();
2915 usleep (2000);
2918 workers_change_num_working (-1);
2919 workers_join ();
2921 if (major_collector.is_parallel)
2922 g_assert (gray_object_queue_is_empty (&gray_queue));
2924 /* all the objects in the heap */
2925 finish_gray_stack (heap_start, heap_end, GENERATION_OLD, &gray_queue);
2926 TV_GETTIME (atv);
2927 time_major_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
2929 /* sweep the big objects list */
2930 prevbo = NULL;
2931 for (bigobj = los_object_list; bigobj;) {
2932 if (object_is_pinned (bigobj->data)) {
2933 unpin_object (bigobj->data);
2934 } else {
2935 LOSObject *to_free;
2936 /* not referenced anywhere, so we can free it */
2937 if (prevbo)
2938 prevbo->next = bigobj->next;
2939 else
2940 los_object_list = bigobj->next;
2941 to_free = bigobj;
2942 bigobj = bigobj->next;
2943 free_large_object (to_free);
2944 continue;
2946 prevbo = bigobj;
2947 bigobj = bigobj->next;
2950 TV_GETTIME (btv);
2951 time_major_free_bigobjs += TV_ELAPSED_MS (atv, btv);
2953 los_sweep ();
2955 TV_GETTIME (atv);
2956 time_major_los_sweep += TV_ELAPSED_MS (btv, atv);
2958 major_collector.sweep ();
2960 TV_GETTIME (btv);
2961 time_major_sweep += TV_ELAPSED_MS (atv, btv);
2963 /* walk the pin_queue, build up the fragment list of free memory, unmark
2964 * pinned objects as we go, memzero() the empty fragments so they are ready for the
2965 * next allocations.
2967 build_nursery_fragments (nursery_section->pin_queue_start, nursery_section->pin_queue_num_entries);
2969 TV_GETTIME (atv);
2970 time_major_fragment_creation += TV_ELAPSED_MS (btv, atv);
2972 TV_GETTIME (all_btv);
2973 mono_stats.major_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
2975 if (heap_dump_file)
2976 dump_heap ("major", num_major_gcs - 1, reason);
2978 /* prepare the pin queue for the next collection */
2979 next_pin_slot = 0;
2980 if (fin_ready_list || critical_fin_list) {
2981 DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
2982 mono_gc_finalize_notify ();
2984 pin_stats_reset ();
2986 g_assert (gray_object_queue_is_empty (&gray_queue));
2988 num_major_sections = major_collector.get_num_major_sections ();
2990 num_major_sections_saved = MAX (old_num_major_sections - num_major_sections, 0);
2991 los_memory_saved = MAX (old_los_memory_usage - los_memory_usage, 1);
2993 save_target = ((num_major_sections * major_collector.section_size) + los_memory_saved) / 2;
2995 * We aim to allow the allocation of as many sections as is
2996 * necessary to reclaim save_target sections in the next
2997 * collection. We assume the collection pattern won't change.
2998 * In the last cycle, we had num_major_sections_saved for
2999 * minor_collection_sections_alloced. Assuming things won't
3000 * change, this must be the same ratio as save_target for
3001 * allowance_target, i.e.
3003 * num_major_sections_saved save_target
3004 * --------------------------------- == ----------------
3005 * minor_collection_sections_alloced allowance_target
3007 * hence:
3009 allowance_target = (mword)((double)save_target * (double)(minor_collection_sections_alloced * major_collector.section_size + los_memory_alloced) / (double)(num_major_sections_saved * major_collector.section_size + los_memory_saved));
3011 minor_collection_allowance = MAX (MIN (allowance_target, num_major_sections * major_collector.section_size + los_memory_usage), MIN_MINOR_COLLECTION_ALLOWANCE);
3013 minor_collection_sections_alloced = 0;
3014 last_los_memory_usage = los_memory_usage;
3016 major_collector.finish_major_collection ();
3018 check_scan_starts ();
3020 binary_protocol_flush_buffers (FALSE);
3022 //consistency_check ();
3025 static void
3026 major_collection (const char *reason)
3028 if (g_getenv ("MONO_GC_NO_MAJOR")) {
3029 collect_nursery (0);
3030 return;
3033 current_collection_generation = GENERATION_OLD;
3034 major_do_collection (reason);
3035 current_collection_generation = -1;
3039 * When deciding if it's better to collect or to expand, keep track
3040 * of how much garbage was reclaimed with the last collection: if it's too
3041 * little, expand.
3042 * This is called when we could not allocate a small object.
3044 static void __attribute__((noinline))
3045 minor_collect_or_expand_inner (size_t size)
3047 int do_minor_collection = 1;
3049 g_assert (nursery_section);
3050 if (do_minor_collection) {
3051 mono_profiler_gc_event (MONO_GC_EVENT_START, 0);
3052 stop_world (0);
3053 if (collect_nursery (size)) {
3054 mono_profiler_gc_event (MONO_GC_EVENT_START, 1);
3055 major_collection ("minor overflow");
3056 /* keep events symmetric */
3057 mono_profiler_gc_event (MONO_GC_EVENT_END, 1);
3059 DEBUG (2, fprintf (gc_debug_file, "Heap size: %lu, LOS size: %lu\n", (unsigned long)total_alloc, (unsigned long)los_memory_usage));
3060 restart_world (0);
3061 /* this also sets the proper pointers for the next allocation */
3062 if (!search_fragment_for_size (size)) {
3063 int i;
3064 /* TypeBuilder and MonoMethod are killing mcs with fragmentation */
3065 DEBUG (1, fprintf (gc_debug_file, "nursery collection didn't find enough room for %zd alloc (%d pinned)\n", size, last_num_pinned));
3066 for (i = 0; i < last_num_pinned; ++i) {
3067 DEBUG (3, fprintf (gc_debug_file, "Bastard pinning obj %p (%s), size: %d\n", pin_queue [i], safe_name (pin_queue [i]), safe_object_get_size (pin_queue [i])));
3069 degraded_mode = 1;
3071 mono_profiler_gc_event (MONO_GC_EVENT_END, 0);
3073 //report_internal_mem_usage ();
3077 * ######################################################################
3078 * ######## Memory allocation from the OS
3079 * ######################################################################
3080 * This section of code deals with getting memory from the OS and
3081 * allocating memory for GC-internal data structures.
3082 * Internal memory can be handled with a freelist for small objects.
3086 * Debug reporting.
3088 G_GNUC_UNUSED static void
3089 report_internal_mem_usage (void)
3091 printf ("Internal memory usage:\n");
3092 mono_sgen_report_internal_mem_usage ();
3093 printf ("Pinned memory usage:\n");
3094 major_collector.report_pinned_memory_usage ();
3098 * Allocate a big chunk of memory from the OS (usually 64KB to several megabytes).
3099 * This must not require any lock.
3101 void*
3102 mono_sgen_alloc_os_memory (size_t size, int activate)
3104 void *ptr;
3105 unsigned long prot_flags = activate? MONO_MMAP_READ|MONO_MMAP_WRITE: MONO_MMAP_NONE;
3107 prot_flags |= MONO_MMAP_PRIVATE | MONO_MMAP_ANON;
3108 size += pagesize - 1;
3109 size &= ~(pagesize - 1);
3110 ptr = mono_valloc (0, size, prot_flags);
3111 /* FIXME: CAS */
3112 total_alloc += size;
3113 return ptr;
3117 * Free the memory returned by mono_sgen_alloc_os_memory (), returning it to the OS.
3119 void
3120 mono_sgen_free_os_memory (void *addr, size_t size)
3122 mono_vfree (addr, size);
3124 size += pagesize - 1;
3125 size &= ~(pagesize - 1);
3126 /* FIXME: CAS */
3127 total_alloc -= size;
3131 * ######################################################################
3132 * ######## Object allocation
3133 * ######################################################################
3134 * This section of code deals with allocating memory for objects.
3135 * There are several ways:
3136 * *) allocate large objects
3137 * *) allocate normal objects
3138 * *) fast lock-free allocation
3139 * *) allocation of pinned objects
3142 static void
3143 setup_fragment (Fragment *frag, Fragment *prev, size_t size)
3145 /* remove from the list */
3146 if (prev)
3147 prev->next = frag->next;
3148 else
3149 nursery_fragments = frag->next;
3150 nursery_next = frag->fragment_start;
3151 nursery_frag_real_end = frag->fragment_end;
3153 DEBUG (4, fprintf (gc_debug_file, "Using nursery fragment %p-%p, size: %td (req: %zd)\n", nursery_next, nursery_frag_real_end, nursery_frag_real_end - nursery_next, size));
3154 frag->next = fragment_freelist;
3155 fragment_freelist = frag;
3158 /* check if we have a suitable fragment in nursery_fragments to be able to allocate
3159 * an object of size @size
3160 * Return FALSE if not found (which means we need a collection)
3162 static gboolean
3163 search_fragment_for_size (size_t size)
3165 Fragment *frag, *prev;
3166 DEBUG (4, fprintf (gc_debug_file, "Searching nursery fragment %p, size: %zd\n", nursery_frag_real_end, size));
3168 if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
3169 /* Clear the remaining space, pinning depends on this */
3170 memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
3173 prev = NULL;
3174 for (frag = nursery_fragments; frag; frag = frag->next) {
3175 if (size <= (frag->fragment_end - frag->fragment_start)) {
3176 setup_fragment (frag, prev, size);
3177 return TRUE;
3179 prev = frag;
3181 return FALSE;
3185 * Same as search_fragment_for_size but if search for @desired_size fails, try to satisfy @minimum_size.
3186 * This improves nursery usage.
3188 static int
3189 search_fragment_for_size_range (size_t desired_size, size_t minimum_size)
3191 Fragment *frag, *prev, *min_prev;
3192 DEBUG (4, fprintf (gc_debug_file, "Searching nursery fragment %p, desired size: %zd minimum size %zd\n", nursery_frag_real_end, desired_size, minimum_size));
3194 if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
3195 /* Clear the remaining space, pinning depends on this */
3196 memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
3199 min_prev = GINT_TO_POINTER (-1);
3200 prev = NULL;
3202 for (frag = nursery_fragments; frag; frag = frag->next) {
3203 int frag_size = frag->fragment_end - frag->fragment_start;
3204 if (desired_size <= frag_size) {
3205 setup_fragment (frag, prev, desired_size);
3206 return desired_size;
3208 if (minimum_size <= frag_size)
3209 min_prev = prev;
3211 prev = frag;
3214 if (min_prev != GINT_TO_POINTER (-1)) {
3215 int frag_size;
3216 if (min_prev)
3217 frag = min_prev->next;
3218 else
3219 frag = nursery_fragments;
3221 frag_size = frag->fragment_end - frag->fragment_start;
3222 HEAVY_STAT (++stat_wasted_fragments_used);
3223 HEAVY_STAT (stat_wasted_fragments_bytes += frag_size);
3225 setup_fragment (frag, min_prev, minimum_size);
3226 return frag_size;
3229 return 0;
3232 static void*
3233 alloc_degraded (MonoVTable *vtable, size_t size)
3235 if (need_major_collection ()) {
3236 mono_profiler_gc_event (MONO_GC_EVENT_START, 1);
3237 stop_world (1);
3238 major_collection ("degraded overflow");
3239 restart_world (1);
3240 mono_profiler_gc_event (MONO_GC_EVENT_END, 1);
3243 degraded_mode += size;
3244 return major_collector.alloc_degraded (vtable, size);
3248 * Provide a variant that takes just the vtable for small fixed-size objects.
3249 * The aligned size is already computed and stored in vt->gc_descr.
3250 * Note: every SCAN_START_SIZE or so we are given the chance to do some special
3251 * processing. We can keep track of where objects start, for example,
3252 * so when we scan the thread stacks for pinned objects, we can start
3253 * a search for the pinned object in SCAN_START_SIZE chunks.
3255 static void*
3256 mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
3258 /* FIXME: handle OOM */
3259 void **p;
3260 char *new_next;
3261 TLAB_ACCESS_INIT;
3263 HEAVY_STAT (++stat_objects_alloced);
3264 if (size <= MAX_SMALL_OBJ_SIZE)
3265 HEAVY_STAT (stat_bytes_alloced += size);
3266 else
3267 HEAVY_STAT (stat_bytes_alloced_los += size);
3269 size = ALIGN_UP (size);
3271 g_assert (vtable->gc_descr);
3273 if (G_UNLIKELY (collect_before_allocs)) {
3274 if (nursery_section) {
3275 mono_profiler_gc_event (MONO_GC_EVENT_START, 0);
3276 stop_world (0);
3277 collect_nursery (0);
3278 restart_world (0);
3279 mono_profiler_gc_event (MONO_GC_EVENT_END, 0);
3280 if (!degraded_mode && !search_fragment_for_size (size)) {
3281 // FIXME:
3282 g_assert_not_reached ();
3288 * We must already have the lock here instead of after the
3289 * fast path because we might be interrupted in the fast path
3290 * (after confirming that new_next < TLAB_TEMP_END) by the GC,
3291 * and we'll end up allocating an object in a fragment which
3292 * no longer belongs to us.
3294 * The managed allocator does not do this, but it's treated
3295 * specially by the world-stopping code.
3298 if (size > MAX_SMALL_OBJ_SIZE) {
3299 p = alloc_large_inner (vtable, size);
3300 } else {
3301 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
3303 p = (void**)TLAB_NEXT;
3304 /* FIXME: handle overflow */
3305 new_next = (char*)p + size;
3306 TLAB_NEXT = new_next;
3308 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
3309 /* Fast path */
3312 * FIXME: We might need a memory barrier here so the change to tlab_next is
3313 * visible before the vtable store.
3316 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3317 binary_protocol_alloc (p , vtable, size);
3318 g_assert (*p == NULL);
3319 *p = vtable;
3321 g_assert (TLAB_NEXT == new_next);
3323 return p;
3326 /* Slow path */
3328 /* there are two cases: the object is too big or we run out of space in the TLAB */
3329 /* we also reach here when the thread does its first allocation after a minor
3330 * collection, since the tlab_ variables are initialized to NULL.
3331 * there can be another case (from ORP), if we cooperate with the runtime a bit:
3332 * objects that need finalizers can have the high bit set in their size
3333 * so the above check fails and we can readily add the object to the queue.
3334 * This avoids taking again the GC lock when registering, but this is moot when
3335 * doing thread-local allocation, so it may not be a good idea.
3337 g_assert (TLAB_NEXT == new_next);
3338 if (TLAB_NEXT >= TLAB_REAL_END) {
3340 * Run out of space in the TLAB. When this happens, some amount of space
3341 * remains in the TLAB, but not enough to satisfy the current allocation
3342 * request. Currently, we retire the TLAB in all cases, later we could
3343 * keep it if the remaining space is above a treshold, and satisfy the
3344 * allocation directly from the nursery.
3346 TLAB_NEXT -= size;
3347 /* when running in degraded mode, we continue allocing that way
3348 * for a while, to decrease the number of useless nursery collections.
3350 if (degraded_mode && degraded_mode < DEFAULT_NURSERY_SIZE) {
3351 p = alloc_degraded (vtable, size);
3352 binary_protocol_alloc_degraded (p, vtable, size);
3353 return p;
3356 /*FIXME This codepath is current deadcode since tlab_size > MAX_SMALL_OBJ_SIZE*/
3357 if (size > tlab_size) {
3358 /* Allocate directly from the nursery */
3359 if (nursery_next + size >= nursery_frag_real_end) {
3360 if (!search_fragment_for_size (size)) {
3361 minor_collect_or_expand_inner (size);
3362 if (degraded_mode) {
3363 p = alloc_degraded (vtable, size);
3364 binary_protocol_alloc_degraded (p, vtable, size);
3365 return p;
3370 p = (void*)nursery_next;
3371 nursery_next += size;
3372 if (nursery_next > nursery_frag_real_end) {
3373 // no space left
3374 g_assert (0);
3377 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
3378 memset (p, 0, size);
3380 } else {
3381 int alloc_size = tlab_size;
3382 int available_in_nursery = nursery_frag_real_end - nursery_next;
3383 if (TLAB_START)
3384 DEBUG (3, fprintf (gc_debug_file, "Retire TLAB: %p-%p [%ld]\n", TLAB_START, TLAB_REAL_END, (long)(TLAB_REAL_END - TLAB_NEXT - size)));
3386 if (alloc_size >= available_in_nursery) {
3387 if (available_in_nursery > MAX_NURSERY_TLAB_WASTE && available_in_nursery > size) {
3388 alloc_size = available_in_nursery;
3389 } else {
3390 alloc_size = search_fragment_for_size_range (tlab_size, size);
3391 if (!alloc_size) {
3392 alloc_size = tlab_size;
3393 minor_collect_or_expand_inner (tlab_size);
3394 if (degraded_mode) {
3395 p = alloc_degraded (vtable, size);
3396 binary_protocol_alloc_degraded (p, vtable, size);
3397 return p;
3403 /* Allocate a new TLAB from the current nursery fragment */
3404 TLAB_START = nursery_next;
3405 nursery_next += alloc_size;
3406 TLAB_NEXT = TLAB_START;
3407 TLAB_REAL_END = TLAB_START + alloc_size;
3408 TLAB_TEMP_END = TLAB_START + MIN (SCAN_START_SIZE, alloc_size);
3410 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
3411 memset (TLAB_START, 0, alloc_size);
3414 /* Allocate from the TLAB */
3415 p = (void*)TLAB_NEXT;
3416 TLAB_NEXT += size;
3417 g_assert (TLAB_NEXT <= TLAB_REAL_END);
3419 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
3421 } else {
3422 /* Reached tlab_temp_end */
3424 /* record the scan start so we can find pinned objects more easily */
3425 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
3426 /* we just bump tlab_temp_end as well */
3427 TLAB_TEMP_END = MIN (TLAB_REAL_END, TLAB_NEXT + SCAN_START_SIZE);
3428 DEBUG (5, fprintf (gc_debug_file, "Expanding local alloc: %p-%p\n", TLAB_NEXT, TLAB_TEMP_END));
3432 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3433 binary_protocol_alloc (p, vtable, size);
3434 *p = vtable;
3436 return p;
3439 static void*
3440 mono_gc_try_alloc_obj_nolock (MonoVTable *vtable, size_t size)
3442 void **p;
3443 char *new_next;
3444 TLAB_ACCESS_INIT;
3446 size = ALIGN_UP (size);
3448 g_assert (vtable->gc_descr);
3449 if (size <= MAX_SMALL_OBJ_SIZE) {
3450 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
3452 p = (void**)TLAB_NEXT;
3453 /* FIXME: handle overflow */
3454 new_next = (char*)p + size;
3455 TLAB_NEXT = new_next;
3457 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
3458 /* Fast path */
3461 * FIXME: We might need a memory barrier here so the change to tlab_next is
3462 * visible before the vtable store.
3465 HEAVY_STAT (++stat_objects_alloced);
3466 HEAVY_STAT (stat_bytes_alloced += size);
3468 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3469 binary_protocol_alloc (p, vtable, size);
3470 g_assert (*p == NULL);
3471 *p = vtable;
3473 g_assert (TLAB_NEXT == new_next);
3475 return p;
3478 return NULL;
3481 void*
3482 mono_gc_alloc_obj (MonoVTable *vtable, size_t size)
3484 void *res;
3485 #ifndef DISABLE_CRITICAL_REGION
3486 TLAB_ACCESS_INIT;
3487 ENTER_CRITICAL_REGION;
3488 res = mono_gc_try_alloc_obj_nolock (vtable, size);
3489 if (res) {
3490 EXIT_CRITICAL_REGION;
3491 return res;
3493 EXIT_CRITICAL_REGION;
3494 #endif
3495 LOCK_GC;
3496 res = mono_gc_alloc_obj_nolock (vtable, size);
3497 UNLOCK_GC;
3498 return res;
3501 void*
3502 mono_gc_alloc_vector (MonoVTable *vtable, size_t size, uintptr_t max_length)
3504 MonoArray *arr;
3505 #ifndef DISABLE_CRITICAL_REGION
3506 TLAB_ACCESS_INIT;
3507 ENTER_CRITICAL_REGION;
3508 arr = mono_gc_try_alloc_obj_nolock (vtable, size);
3509 if (arr) {
3510 arr->max_length = max_length;
3511 EXIT_CRITICAL_REGION;
3512 return arr;
3514 EXIT_CRITICAL_REGION;
3515 #endif
3517 LOCK_GC;
3519 arr = mono_gc_alloc_obj_nolock (vtable, size);
3520 arr->max_length = max_length;
3522 UNLOCK_GC;
3524 return arr;
3527 void*
3528 mono_gc_alloc_array (MonoVTable *vtable, size_t size, uintptr_t max_length, uintptr_t bounds_size)
3530 MonoArray *arr;
3531 MonoArrayBounds *bounds;
3533 LOCK_GC;
3535 arr = mono_gc_alloc_obj_nolock (vtable, size);
3536 arr->max_length = max_length;
3538 bounds = (MonoArrayBounds*)((char*)arr + size - bounds_size);
3539 arr->bounds = bounds;
3541 UNLOCK_GC;
3543 return arr;
3546 void*
3547 mono_gc_alloc_string (MonoVTable *vtable, size_t size, gint32 len)
3549 MonoString *str;
3550 #ifndef DISABLE_CRITICAL_REGION
3551 TLAB_ACCESS_INIT;
3552 ENTER_CRITICAL_REGION;
3553 str = mono_gc_try_alloc_obj_nolock (vtable, size);
3554 if (str) {
3555 str->length = len;
3556 EXIT_CRITICAL_REGION;
3557 return str;
3559 EXIT_CRITICAL_REGION;
3560 #endif
3562 LOCK_GC;
3564 str = mono_gc_alloc_obj_nolock (vtable, size);
3565 str->length = len;
3567 UNLOCK_GC;
3569 return str;
3573 * To be used for interned strings and possibly MonoThread, reflection handles.
3574 * We may want to explicitly free these objects.
3576 void*
3577 mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size)
3579 /* FIXME: handle OOM */
3580 void **p;
3581 size = ALIGN_UP (size);
3582 LOCK_GC;
3583 if (size > MAX_SMALL_OBJ_SIZE) {
3584 /* large objects are always pinned anyway */
3585 p = alloc_large_inner (vtable, size);
3586 } else {
3587 DEBUG (9, g_assert (vtable->klass->inited));
3588 p = major_collector.alloc_small_pinned_obj (size, vtable->klass->has_references);
3590 DEBUG (6, fprintf (gc_debug_file, "Allocated pinned object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3591 binary_protocol_alloc_pinned (p, vtable, size);
3592 *p = vtable;
3593 UNLOCK_GC;
3594 return p;
3598 * ######################################################################
3599 * ######## Finalization support
3600 * ######################################################################
3604 * this is valid for the nursery: if the object has been forwarded it means it's
3605 * still refrenced from a root. If it is pinned it's still alive as well.
3606 * Return TRUE if @obj is ready to be finalized.
3608 #define object_is_fin_ready(obj) (!object_is_pinned (obj) && !object_is_forwarded (obj))
3610 static gboolean
3611 is_critical_finalizer (FinalizeEntry *entry)
3613 MonoObject *obj;
3614 MonoClass *class;
3616 if (!mono_defaults.critical_finalizer_object)
3617 return FALSE;
3619 obj = entry->object;
3620 class = ((MonoVTable*)LOAD_VTABLE (obj))->klass;
3622 return mono_class_has_parent (class, mono_defaults.critical_finalizer_object);
3625 static void
3626 queue_finalization_entry (FinalizeEntry *entry) {
3627 if (is_critical_finalizer (entry)) {
3628 entry->next = critical_fin_list;
3629 critical_fin_list = entry;
3630 } else {
3631 entry->next = fin_ready_list;
3632 fin_ready_list = entry;
3636 /* LOCKING: requires that the GC lock is held */
3637 static void
3638 rehash_fin_table (FinalizeEntryHashTable *hash_table)
3640 FinalizeEntry **finalizable_hash = hash_table->table;
3641 mword finalizable_hash_size = hash_table->size;
3642 int i;
3643 unsigned int hash;
3644 FinalizeEntry **new_hash;
3645 FinalizeEntry *entry, *next;
3646 int new_size = g_spaced_primes_closest (hash_table->num_registered);
3648 new_hash = mono_sgen_alloc_internal_dynamic (new_size * sizeof (FinalizeEntry*), INTERNAL_MEM_FIN_TABLE);
3649 for (i = 0; i < finalizable_hash_size; ++i) {
3650 for (entry = finalizable_hash [i]; entry; entry = next) {
3651 hash = mono_object_hash (entry->object) % new_size;
3652 next = entry->next;
3653 entry->next = new_hash [hash];
3654 new_hash [hash] = entry;
3657 mono_sgen_free_internal_dynamic (finalizable_hash, finalizable_hash_size * sizeof (FinalizeEntry*), INTERNAL_MEM_FIN_TABLE);
3658 hash_table->table = new_hash;
3659 hash_table->size = new_size;
3662 /* LOCKING: requires that the GC lock is held */
3663 static void
3664 rehash_fin_table_if_necessary (FinalizeEntryHashTable *hash_table)
3666 if (hash_table->num_registered >= hash_table->size * 2)
3667 rehash_fin_table (hash_table);
3670 /* LOCKING: requires that the GC lock is held */
3671 static void
3672 finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
3674 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
3675 FinalizeEntry *entry, *prev;
3676 int i;
3677 FinalizeEntry **finalizable_hash = hash_table->table;
3678 mword finalizable_hash_size = hash_table->size;
3680 if (no_finalize)
3681 return;
3682 for (i = 0; i < finalizable_hash_size; ++i) {
3683 prev = NULL;
3684 for (entry = finalizable_hash [i]; entry;) {
3685 if ((char*)entry->object >= start && (char*)entry->object < end && !major_collector.is_object_live (entry->object)) {
3686 gboolean is_fin_ready = object_is_fin_ready (entry->object);
3687 char *copy = entry->object;
3688 copy_func ((void**)&copy, queue);
3689 if (is_fin_ready) {
3690 char *from;
3691 FinalizeEntry *next;
3692 /* remove and put in fin_ready_list */
3693 if (prev)
3694 prev->next = entry->next;
3695 else
3696 finalizable_hash [i] = entry->next;
3697 next = entry->next;
3698 num_ready_finalizers++;
3699 hash_table->num_registered--;
3700 queue_finalization_entry (entry);
3701 /* Make it survive */
3702 from = entry->object;
3703 entry->object = copy;
3704 DEBUG (5, fprintf (gc_debug_file, "Queueing object for finalization: %p (%s) (was at %p) (%d/%d)\n", entry->object, safe_name (entry->object), from, num_ready_finalizers, hash_table->num_registered));
3705 entry = next;
3706 continue;
3707 } else {
3708 char *from = entry->object;
3709 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
3710 FinalizeEntry *next = entry->next;
3711 unsigned int major_hash;
3712 /* remove from the list */
3713 if (prev)
3714 prev->next = entry->next;
3715 else
3716 finalizable_hash [i] = entry->next;
3717 hash_table->num_registered--;
3719 entry->object = copy;
3721 /* insert it into the major hash */
3722 rehash_fin_table_if_necessary (&major_finalizable_hash);
3723 major_hash = mono_object_hash ((MonoObject*) copy) %
3724 major_finalizable_hash.size;
3725 entry->next = major_finalizable_hash.table [major_hash];
3726 major_finalizable_hash.table [major_hash] = entry;
3727 major_finalizable_hash.num_registered++;
3729 DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from));
3731 entry = next;
3732 continue;
3733 } else {
3734 /* update pointer */
3735 DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from));
3736 entry->object = copy;
3740 prev = entry;
3741 entry = entry->next;
3746 static int
3747 object_is_reachable (char *object, char *start, char *end)
3749 /*This happens for non nursery objects during minor collections. We just treat all objects as alive.*/
3750 if (object < start || object >= end)
3751 return TRUE;
3752 return !object_is_fin_ready (object) || major_collector.is_object_live (object);
3755 /* LOCKING: requires that the GC lock is held */
3756 static void
3757 null_ephemerons_for_domain (MonoDomain *domain)
3759 EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
3761 while (current) {
3762 MonoObject *object = (MonoObject*)current->array;
3764 if (object && !object->vtable) {
3765 EphemeronLinkNode *tmp = current;
3767 if (prev)
3768 prev->next = current->next;
3769 else
3770 ephemeron_list = current->next;
3772 current = current->next;
3773 mono_sgen_free_internal (tmp, INTERNAL_MEM_EPHEMERON_LINK);
3774 } else {
3775 prev = current;
3776 current = current->next;
3781 /* LOCKING: requires that the GC lock is held */
3782 static void
3783 clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end, GrayQueue *queue)
3785 int was_in_nursery, was_promoted;
3786 EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
3787 MonoArray *array;
3788 Ephemeron *cur, *array_end;
3789 char *tombstone;
3791 while (current) {
3792 char *object = current->array;
3794 if (!object_is_reachable (object, start, end)) {
3795 EphemeronLinkNode *tmp = current;
3797 DEBUG (5, fprintf (gc_debug_file, "Dead Ephemeron array at %p\n", object));
3799 if (prev)
3800 prev->next = current->next;
3801 else
3802 ephemeron_list = current->next;
3804 current = current->next;
3805 mono_sgen_free_internal (tmp, INTERNAL_MEM_EPHEMERON_LINK);
3807 continue;
3810 was_in_nursery = ptr_in_nursery (object);
3811 copy_func ((void**)&object, queue);
3812 current->array = object;
3814 /*The array was promoted, add global remsets for key/values left behind in nursery.*/
3815 was_promoted = was_in_nursery && !ptr_in_nursery (object);
3817 DEBUG (5, fprintf (gc_debug_file, "Clearing unreachable entries for ephemeron array at %p\n", object));
3819 array = (MonoArray*)object;
3820 cur = mono_array_addr (array, Ephemeron, 0);
3821 array_end = cur + mono_array_length_fast (array);
3822 tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
3824 for (; cur < array_end; ++cur) {
3825 char *key = (char*)cur->key;
3827 if (!key || key == tombstone)
3828 continue;
3830 DEBUG (5, fprintf (gc_debug_file, "[%td] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
3831 key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
3832 cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
3834 if (!object_is_reachable (key, start, end)) {
3835 cur->key = tombstone;
3836 cur->value = NULL;
3837 continue;
3840 if (was_promoted) {
3841 if (ptr_in_nursery (key)) {/*key was not promoted*/
3842 DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to key %p\n", key));
3843 mono_sgen_add_to_global_remset (&cur->key);
3845 if (ptr_in_nursery (cur->value)) {/*value was not promoted*/
3846 DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to value %p\n", cur->value));
3847 mono_sgen_add_to_global_remset (&cur->value);
3851 prev = current;
3852 current = current->next;
3856 /* LOCKING: requires that the GC lock is held */
3857 static int
3858 mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, GrayQueue *queue)
3860 int nothing_marked = 1;
3861 EphemeronLinkNode *current = ephemeron_list;
3862 MonoArray *array;
3863 Ephemeron *cur, *array_end;
3864 char *tombstone;
3866 for (current = ephemeron_list; current; current = current->next) {
3867 char *object = current->array;
3868 DEBUG (5, fprintf (gc_debug_file, "Ephemeron array at %p\n", object));
3870 /*We ignore arrays in old gen during minor collections since all objects are promoted by the remset machinery.*/
3871 if (object < start || object >= end)
3872 continue;
3874 /*It has to be alive*/
3875 if (!object_is_reachable (object, start, end)) {
3876 DEBUG (5, fprintf (gc_debug_file, "\tnot reachable\n"));
3877 continue;
3880 copy_func ((void**)&object, queue);
3882 array = (MonoArray*)object;
3883 cur = mono_array_addr (array, Ephemeron, 0);
3884 array_end = cur + mono_array_length_fast (array);
3885 tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
3887 for (; cur < array_end; ++cur) {
3888 char *key = cur->key;
3890 if (!key || key == tombstone)
3891 continue;
3893 DEBUG (5, fprintf (gc_debug_file, "[%td] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
3894 key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
3895 cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
3897 if (object_is_reachable (key, start, end)) {
3898 char *value = cur->value;
3900 copy_func ((void**)&cur->key, queue);
3901 if (value) {
3902 if (!object_is_reachable (value, start, end))
3903 nothing_marked = 0;
3904 copy_func ((void**)&cur->value, queue);
3910 DEBUG (5, fprintf (gc_debug_file, "Ephemeron run finished. Is it done %d\n", nothing_marked));
3911 return nothing_marked;
3914 /* LOCKING: requires that the GC lock is held */
3915 static void
3916 null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation, GrayQueue *queue)
3918 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
3919 DisappearingLink **disappearing_link_hash = hash->table;
3920 int disappearing_link_hash_size = hash->size;
3921 DisappearingLink *entry, *prev;
3922 int i;
3923 if (!hash->num_links)
3924 return;
3925 for (i = 0; i < disappearing_link_hash_size; ++i) {
3926 prev = NULL;
3927 for (entry = disappearing_link_hash [i]; entry;) {
3928 char *object = DISLINK_OBJECT (entry);
3929 if (object >= start && object < end && !major_collector.is_object_live (object)) {
3930 gboolean track = DISLINK_TRACK (entry);
3931 if (!track && object_is_fin_ready (object)) {
3932 void **p = entry->link;
3933 DisappearingLink *old;
3934 *p = NULL;
3935 /* remove from list */
3936 if (prev)
3937 prev->next = entry->next;
3938 else
3939 disappearing_link_hash [i] = entry->next;
3940 DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object));
3941 old = entry->next;
3942 mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK);
3943 entry = old;
3944 hash->num_links--;
3945 continue;
3946 } else {
3947 char *copy = object;
3948 copy_func ((void**)&copy, queue);
3950 /* Update pointer if it's moved. If the object
3951 * has been moved out of the nursery, we need to
3952 * remove the link from the minor hash table to
3953 * the major one.
3955 * FIXME: what if an object is moved earlier?
3958 if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
3959 void **link = entry->link;
3960 DisappearingLink *old;
3961 /* remove from list */
3962 if (prev)
3963 prev->next = entry->next;
3964 else
3965 disappearing_link_hash [i] = entry->next;
3966 old = entry->next;
3967 mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK);
3968 entry = old;
3969 hash->num_links--;
3971 add_or_remove_disappearing_link ((MonoObject*)copy, link,
3972 track, GENERATION_OLD);
3974 DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy));
3976 continue;
3977 } else {
3978 /* We set the track resurrection bit to
3979 * FALSE if the object is to be finalized
3980 * so that the object can be collected in
3981 * the next cycle (i.e. after it was
3982 * finalized).
3984 *entry->link = HIDE_POINTER (copy,
3985 object_is_fin_ready (object) ? FALSE : track);
3986 DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
3990 prev = entry;
3991 entry = entry->next;
3996 /* LOCKING: requires that the GC lock is held */
3997 static void
3998 null_links_for_domain (MonoDomain *domain, int generation)
4000 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
4001 DisappearingLink **disappearing_link_hash = hash->table;
4002 int disappearing_link_hash_size = hash->size;
4003 DisappearingLink *entry, *prev;
4004 int i;
4005 for (i = 0; i < disappearing_link_hash_size; ++i) {
4006 prev = NULL;
4007 for (entry = disappearing_link_hash [i]; entry; ) {
4008 char *object = DISLINK_OBJECT (entry);
4009 if (object && !((MonoObject*)object)->vtable) {
4010 DisappearingLink *next = entry->next;
4012 if (prev)
4013 prev->next = next;
4014 else
4015 disappearing_link_hash [i] = next;
4017 if (*(entry->link)) {
4018 *(entry->link) = NULL;
4019 g_warning ("Disappearing link %p not freed", entry->link);
4020 } else {
4021 mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK);
4024 entry = next;
4025 continue;
4027 prev = entry;
4028 entry = entry->next;
4033 /* LOCKING: requires that the GC lock is held */
4034 static int
4035 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
4036 FinalizeEntryHashTable *hash_table)
4038 FinalizeEntry **finalizable_hash = hash_table->table;
4039 mword finalizable_hash_size = hash_table->size;
4040 FinalizeEntry *entry, *prev;
4041 int i, count;
4043 if (no_finalize || !out_size || !out_array)
4044 return 0;
4045 count = 0;
4046 for (i = 0; i < finalizable_hash_size; ++i) {
4047 prev = NULL;
4048 for (entry = finalizable_hash [i]; entry;) {
4049 if (mono_object_domain (entry->object) == domain) {
4050 FinalizeEntry *next;
4051 /* remove and put in out_array */
4052 if (prev)
4053 prev->next = entry->next;
4054 else
4055 finalizable_hash [i] = entry->next;
4056 next = entry->next;
4057 hash_table->num_registered--;
4058 out_array [count ++] = entry->object;
4059 DEBUG (5, fprintf (gc_debug_file, "Collecting object for finalization: %p (%s) (%d/%d)\n", entry->object, safe_name (entry->object), num_ready_finalizers, hash_table->num_registered));
4060 entry = next;
4061 if (count == out_size)
4062 return count;
4063 continue;
4065 prev = entry;
4066 entry = entry->next;
4069 return count;
4073 * mono_gc_finalizers_for_domain:
4074 * @domain: the unloading appdomain
4075 * @out_array: output array
4076 * @out_size: size of output array
4078 * Store inside @out_array up to @out_size objects that belong to the unloading
4079 * appdomain @domain. Returns the number of stored items. Can be called repeteadly
4080 * until it returns 0.
4081 * The items are removed from the finalizer data structure, so the caller is supposed
4082 * to finalize them.
4083 * @out_array should be on the stack to allow the GC to know the objects are still alive.
4086 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
4088 int result;
4090 LOCK_GC;
4091 result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
4092 if (result < out_size) {
4093 result += finalizers_for_domain (domain, out_array + result, out_size - result,
4094 &major_finalizable_hash);
4096 UNLOCK_GC;
4098 return result;
4101 static void
4102 register_for_finalization (MonoObject *obj, void *user_data, int generation)
4104 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
4105 FinalizeEntry **finalizable_hash;
4106 mword finalizable_hash_size;
4107 FinalizeEntry *entry, *prev;
4108 unsigned int hash;
4109 if (no_finalize)
4110 return;
4111 g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
4112 hash = mono_object_hash (obj);
4113 LOCK_GC;
4114 rehash_fin_table_if_necessary (hash_table);
4115 finalizable_hash = hash_table->table;
4116 finalizable_hash_size = hash_table->size;
4117 hash %= finalizable_hash_size;
4118 prev = NULL;
4119 for (entry = finalizable_hash [hash]; entry; entry = entry->next) {
4120 if (entry->object == obj) {
4121 if (!user_data) {
4122 /* remove from the list */
4123 if (prev)
4124 prev->next = entry->next;
4125 else
4126 finalizable_hash [hash] = entry->next;
4127 hash_table->num_registered--;
4128 DEBUG (5, fprintf (gc_debug_file, "Removed finalizer %p for object: %p (%s) (%d)\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered));
4129 mono_sgen_free_internal (entry, INTERNAL_MEM_FINALIZE_ENTRY);
4131 UNLOCK_GC;
4132 return;
4134 prev = entry;
4136 if (!user_data) {
4137 /* request to deregister, but already out of the list */
4138 UNLOCK_GC;
4139 return;
4141 entry = mono_sgen_alloc_internal (INTERNAL_MEM_FINALIZE_ENTRY);
4142 entry->object = obj;
4143 entry->next = finalizable_hash [hash];
4144 finalizable_hash [hash] = entry;
4145 hash_table->num_registered++;
4146 DEBUG (5, fprintf (gc_debug_file, "Added finalizer %p for object: %p (%s) (%d) to %s table\n", entry, obj, obj->vtable->klass->name, hash_table->num_registered, generation_name (generation)));
4147 UNLOCK_GC;
4150 void
4151 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
4153 if (ptr_in_nursery (obj))
4154 register_for_finalization (obj, user_data, GENERATION_NURSERY);
4155 else
4156 register_for_finalization (obj, user_data, GENERATION_OLD);
4159 static void
4160 rehash_dislink (DisappearingLinkHashTable *hash_table)
4162 DisappearingLink **disappearing_link_hash = hash_table->table;
4163 int disappearing_link_hash_size = hash_table->size;
4164 int i;
4165 unsigned int hash;
4166 DisappearingLink **new_hash;
4167 DisappearingLink *entry, *next;
4168 int new_size = g_spaced_primes_closest (hash_table->num_links);
4170 new_hash = mono_sgen_alloc_internal_dynamic (new_size * sizeof (DisappearingLink*), INTERNAL_MEM_DISLINK_TABLE);
4171 for (i = 0; i < disappearing_link_hash_size; ++i) {
4172 for (entry = disappearing_link_hash [i]; entry; entry = next) {
4173 hash = mono_aligned_addr_hash (entry->link) % new_size;
4174 next = entry->next;
4175 entry->next = new_hash [hash];
4176 new_hash [hash] = entry;
4179 mono_sgen_free_internal_dynamic (disappearing_link_hash,
4180 disappearing_link_hash_size * sizeof (DisappearingLink*), INTERNAL_MEM_DISLINK_TABLE);
4181 hash_table->table = new_hash;
4182 hash_table->size = new_size;
4185 /* LOCKING: assumes the GC lock is held */
4186 static void
4187 add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation)
4189 DisappearingLinkHashTable *hash_table = get_dislink_hash_table (generation);
4190 DisappearingLink *entry, *prev;
4191 unsigned int hash;
4192 DisappearingLink **disappearing_link_hash = hash_table->table;
4193 int disappearing_link_hash_size = hash_table->size;
4195 if (hash_table->num_links >= disappearing_link_hash_size * 2) {
4196 rehash_dislink (hash_table);
4197 disappearing_link_hash = hash_table->table;
4198 disappearing_link_hash_size = hash_table->size;
4200 /* FIXME: add check that link is not in the heap */
4201 hash = mono_aligned_addr_hash (link) % disappearing_link_hash_size;
4202 entry = disappearing_link_hash [hash];
4203 prev = NULL;
4204 for (; entry; entry = entry->next) {
4205 /* link already added */
4206 if (link == entry->link) {
4207 /* NULL obj means remove */
4208 if (obj == NULL) {
4209 if (prev)
4210 prev->next = entry->next;
4211 else
4212 disappearing_link_hash [hash] = entry->next;
4213 hash_table->num_links--;
4214 DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d) from %s table\n", entry, hash_table->num_links, generation_name (generation)));
4215 mono_sgen_free_internal (entry, INTERNAL_MEM_DISLINK);
4216 *link = NULL;
4217 } else {
4218 *link = HIDE_POINTER (obj, track); /* we allow the change of object */
4220 return;
4222 prev = entry;
4224 if (obj == NULL)
4225 return;
4226 entry = mono_sgen_alloc_internal (INTERNAL_MEM_DISLINK);
4227 *link = HIDE_POINTER (obj, track);
4228 entry->link = link;
4229 entry->next = disappearing_link_hash [hash];
4230 disappearing_link_hash [hash] = entry;
4231 hash_table->num_links++;
4232 DEBUG (5, fprintf (gc_debug_file, "Added dislink %p for object: %p (%s) at %p to %s table\n", entry, obj, obj->vtable->klass->name, link, generation_name (generation)));
4235 /* LOCKING: assumes the GC lock is held */
4236 static void
4237 mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
4239 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_NURSERY);
4240 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_OLD);
4241 if (obj) {
4242 if (ptr_in_nursery (obj))
4243 add_or_remove_disappearing_link (obj, link, track, GENERATION_NURSERY);
4244 else
4245 add_or_remove_disappearing_link (obj, link, track, GENERATION_OLD);
4250 mono_gc_invoke_finalizers (void)
4252 FinalizeEntry *entry = NULL;
4253 gboolean entry_is_critical = FALSE;
4254 int count = 0;
4255 void *obj;
4256 /* FIXME: batch to reduce lock contention */
4257 while (fin_ready_list || critical_fin_list) {
4258 LOCK_GC;
4260 if (entry) {
4261 FinalizeEntry **list = entry_is_critical ? &critical_fin_list : &fin_ready_list;
4263 /* We have finalized entry in the last
4264 interation, now we need to remove it from
4265 the list. */
4266 if (*list == entry)
4267 *list = entry->next;
4268 else {
4269 FinalizeEntry *e = *list;
4270 while (e->next != entry)
4271 e = e->next;
4272 e->next = entry->next;
4274 mono_sgen_free_internal (entry, INTERNAL_MEM_FINALIZE_ENTRY);
4275 entry = NULL;
4278 /* Now look for the first non-null entry. */
4279 for (entry = fin_ready_list; entry && !entry->object; entry = entry->next)
4281 if (entry) {
4282 entry_is_critical = FALSE;
4283 } else {
4284 entry_is_critical = TRUE;
4285 for (entry = critical_fin_list; entry && !entry->object; entry = entry->next)
4289 if (entry) {
4290 g_assert (entry->object);
4291 num_ready_finalizers--;
4292 obj = entry->object;
4293 entry->object = NULL;
4294 DEBUG (7, fprintf (gc_debug_file, "Finalizing object %p (%s)\n", obj, safe_name (obj)));
4297 UNLOCK_GC;
4299 if (!entry)
4300 break;
4302 g_assert (entry->object == NULL);
4303 count++;
4304 /* the object is on the stack so it is pinned */
4305 /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/
4306 mono_gc_run_finalize (obj, NULL);
4308 g_assert (!entry);
4309 return count;
4312 gboolean
4313 mono_gc_pending_finalizers (void)
4315 return fin_ready_list || critical_fin_list;
4318 /* Negative value to remove */
4319 void
4320 mono_gc_add_memory_pressure (gint64 value)
4322 /* FIXME: Use interlocked functions */
4323 LOCK_GC;
4324 memory_pressure += value;
4325 UNLOCK_GC;
4328 void
4329 mono_sgen_register_major_sections_alloced (int num_sections)
4331 minor_collection_sections_alloced += num_sections;
4334 mword
4335 mono_sgen_get_minor_collection_allowance (void)
4337 return minor_collection_allowance;
4341 * ######################################################################
4342 * ######## registered roots support
4343 * ######################################################################
4346 static void
4347 rehash_roots (gboolean pinned)
4349 int i;
4350 unsigned int hash;
4351 RootRecord **new_hash;
4352 RootRecord *entry, *next;
4353 int new_size;
4355 new_size = g_spaced_primes_closest (num_roots_entries [pinned]);
4356 new_hash = mono_sgen_alloc_internal_dynamic (new_size * sizeof (RootRecord*), INTERNAL_MEM_ROOTS_TABLE);
4357 for (i = 0; i < roots_hash_size [pinned]; ++i) {
4358 for (entry = roots_hash [pinned][i]; entry; entry = next) {
4359 hash = mono_aligned_addr_hash (entry->start_root) % new_size;
4360 next = entry->next;
4361 entry->next = new_hash [hash];
4362 new_hash [hash] = entry;
4365 mono_sgen_free_internal_dynamic (roots_hash [pinned], roots_hash_size [pinned] * sizeof (RootRecord*), INTERNAL_MEM_ROOTS_TABLE);
4366 roots_hash [pinned] = new_hash;
4367 roots_hash_size [pinned] = new_size;
4370 static RootRecord*
4371 find_root (int root_type, char *start, guint32 addr_hash)
4373 RootRecord *new_root;
4375 guint32 hash = addr_hash % roots_hash_size [root_type];
4376 for (new_root = roots_hash [root_type][hash]; new_root; new_root = new_root->next) {
4377 /* we allow changing the size and the descriptor (for thread statics etc) */
4378 if (new_root->start_root == start) {
4379 return new_root;
4383 return NULL;
4387 * We do not coalesce roots.
4389 static int
4390 mono_gc_register_root_inner (char *start, size_t size, void *descr, int root_type)
4392 RootRecord *new_root;
4393 unsigned int hash, addr_hash = mono_aligned_addr_hash (start);
4394 int i;
4395 LOCK_GC;
4396 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
4397 if (num_roots_entries [i] >= roots_hash_size [i] * 2)
4398 rehash_roots (i);
4400 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
4401 new_root = find_root (i, start, addr_hash);
4402 /* we allow changing the size and the descriptor (for thread statics etc) */
4403 if (new_root) {
4404 size_t old_size = new_root->end_root - new_root->start_root;
4405 new_root->end_root = new_root->start_root + size;
4406 g_assert (((new_root->root_desc != 0) && (descr != NULL)) ||
4407 ((new_root->root_desc == 0) && (descr == NULL)));
4408 new_root->root_desc = (mword)descr;
4409 roots_size += size;
4410 roots_size -= old_size;
4411 UNLOCK_GC;
4412 return TRUE;
4415 new_root = mono_sgen_alloc_internal (INTERNAL_MEM_ROOT_RECORD);
4416 if (new_root) {
4417 new_root->start_root = start;
4418 new_root->end_root = new_root->start_root + size;
4419 new_root->root_desc = (mword)descr;
4420 roots_size += size;
4421 hash = addr_hash % roots_hash_size [root_type];
4422 num_roots_entries [root_type]++;
4423 new_root->next = roots_hash [root_type] [hash];
4424 roots_hash [root_type][hash] = new_root;
4425 DEBUG (3, fprintf (gc_debug_file, "Added root %p for range: %p-%p, descr: %p (%d/%d bytes)\n", new_root, new_root->start_root, new_root->end_root, descr, (int)size, (int)roots_size));
4426 } else {
4427 UNLOCK_GC;
4428 return FALSE;
4430 UNLOCK_GC;
4431 return TRUE;
4435 mono_gc_register_root (char *start, size_t size, void *descr)
4437 return mono_gc_register_root_inner (start, size, descr, descr ? ROOT_TYPE_NORMAL : ROOT_TYPE_PINNED);
4441 mono_gc_register_root_wbarrier (char *start, size_t size, void *descr)
4443 return mono_gc_register_root_inner (start, size, descr, ROOT_TYPE_WBARRIER);
4446 void
4447 mono_gc_deregister_root (char* addr)
4449 RootRecord *tmp, *prev;
4450 unsigned int hash, addr_hash = mono_aligned_addr_hash (addr);
4451 int root_type;
4453 LOCK_GC;
4454 for (root_type = 0; root_type < ROOT_TYPE_NUM; ++root_type) {
4455 hash = addr_hash % roots_hash_size [root_type];
4456 tmp = roots_hash [root_type][hash];
4457 prev = NULL;
4458 while (tmp) {
4459 if (tmp->start_root == (char*)addr) {
4460 if (prev)
4461 prev->next = tmp->next;
4462 else
4463 roots_hash [root_type][hash] = tmp->next;
4464 roots_size -= (tmp->end_root - tmp->start_root);
4465 num_roots_entries [root_type]--;
4466 DEBUG (3, fprintf (gc_debug_file, "Removed root %p for range: %p-%p\n", tmp, tmp->start_root, tmp->end_root));
4467 mono_sgen_free_internal (tmp, INTERNAL_MEM_ROOT_RECORD);
4468 break;
4470 prev = tmp;
4471 tmp = tmp->next;
4474 UNLOCK_GC;
4478 * ######################################################################
4479 * ######## Thread handling (stop/start code)
4480 * ######################################################################
4483 /* FIXME: handle large/small config */
4484 #define HASH_PTHREAD_T(id) (((unsigned int)(id) >> 4) * 2654435761u)
4486 static SgenThreadInfo* thread_table [THREAD_HASH_SIZE];
4488 #if USE_SIGNAL_BASED_START_STOP_WORLD
4490 static MonoSemType suspend_ack_semaphore;
4491 static MonoSemType *suspend_ack_semaphore_ptr;
4492 static unsigned int global_stop_count = 0;
4494 static sigset_t suspend_signal_mask;
4495 static mword cur_thread_regs [ARCH_NUM_REGS] = {0};
4497 /* LOCKING: assumes the GC lock is held */
4498 SgenThreadInfo**
4499 mono_sgen_get_thread_table (void)
4501 return thread_table;
4504 SgenThreadInfo*
4505 mono_sgen_thread_info_lookup (ARCH_THREAD_TYPE id)
4507 unsigned int hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
4508 SgenThreadInfo *info;
4510 info = thread_table [hash];
4511 while (info && !ARCH_THREAD_EQUALS (info->id, id)) {
4512 info = info->next;
4514 return info;
4517 static void
4518 update_current_thread_stack (void *start)
4520 void *ptr = cur_thread_regs;
4521 SgenThreadInfo *info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
4523 info->stack_start = align_pointer (&ptr);
4524 g_assert (info->stack_start >= info->stack_start_limit && info->stack_start < info->stack_end);
4525 ARCH_STORE_REGS (ptr);
4526 info->stopped_regs = ptr;
4527 if (gc_callbacks.thread_suspend_func)
4528 gc_callbacks.thread_suspend_func (info->runtime_data, NULL);
4532 * Define this and use the "xdomain-checks" MONO_GC_DEBUG option to
4533 * have cross-domain checks in the write barrier.
4535 //#define XDOMAIN_CHECKS_IN_WBARRIER
4537 #ifndef SGEN_BINARY_PROTOCOL
4538 #ifndef HEAVY_STATISTICS
4539 #define MANAGED_ALLOCATION
4540 #ifndef XDOMAIN_CHECKS_IN_WBARRIER
4541 #define MANAGED_WBARRIER
4542 #endif
4543 #endif
4544 #endif
4546 static gboolean
4547 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip);
4549 void
4550 mono_sgen_wait_for_suspend_ack (int count)
4552 int i, result;
4554 for (i = 0; i < count; ++i) {
4555 while ((result = MONO_SEM_WAIT (suspend_ack_semaphore_ptr)) != 0) {
4556 if (errno != EINTR) {
4557 g_error ("sem_wait ()");
4563 static int
4564 restart_threads_until_none_in_managed_allocator (void)
4566 SgenThreadInfo *info;
4567 int i, result, num_threads_died = 0;
4568 int sleep_duration = -1;
4570 for (;;) {
4571 int restart_count = 0, restarted_count = 0;
4572 /* restart all threads that stopped in the
4573 allocator */
4574 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4575 for (info = thread_table [i]; info; info = info->next) {
4576 if (info->skip)
4577 continue;
4578 if (!info->stack_start || info->in_critical_region ||
4579 is_ip_in_managed_allocator (info->stopped_domain, info->stopped_ip)) {
4580 binary_protocol_thread_restart ((gpointer)info->id);
4581 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
4582 result = thread_resume (pthread_mach_thread_np (info->id));
4583 #else
4584 result = pthread_kill (info->id, restart_signal_num);
4585 #endif
4586 if (result == 0) {
4587 ++restart_count;
4588 } else {
4589 info->skip = 1;
4591 } else {
4592 /* we set the stopped_ip to
4593 NULL for threads which
4594 we're not restarting so
4595 that we can easily identify
4596 the others */
4597 info->stopped_ip = NULL;
4598 info->stopped_domain = NULL;
4602 /* if no threads were restarted, we're done */
4603 if (restart_count == 0)
4604 break;
4606 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
4607 /* mach thread_resume is synchronous so we dont need to wait for them */
4608 #else
4609 /* wait for the threads to signal their restart */
4610 mono_sgen_wait_for_suspend_ack (restart_count);
4611 #endif
4613 if (sleep_duration < 0) {
4614 sched_yield ();
4615 sleep_duration = 0;
4616 } else {
4617 g_usleep (sleep_duration);
4618 sleep_duration += 10;
4621 /* stop them again */
4622 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4623 for (info = thread_table [i]; info; info = info->next) {
4624 if (info->skip || info->stopped_ip == NULL)
4625 continue;
4626 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
4627 result = thread_suspend (pthread_mach_thread_np (info->id));
4628 #else
4629 result = pthread_kill (info->id, suspend_signal_num);
4630 #endif
4631 if (result == 0) {
4632 ++restarted_count;
4633 } else {
4634 info->skip = 1;
4638 /* some threads might have died */
4639 num_threads_died += restart_count - restarted_count;
4640 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
4641 /* mach thread_resume is synchronous so we dont need to wait for them */
4642 #else
4643 /* wait for the threads to signal their suspension
4644 again */
4645 mono_sgen_wait_for_suspend_ack (restart_count);
4646 #endif
4649 return num_threads_died;
4652 /* LOCKING: assumes the GC lock is held (by the stopping thread) */
4653 static void
4654 suspend_handler (int sig, siginfo_t *siginfo, void *context)
4656 SgenThreadInfo *info;
4657 pthread_t id;
4658 int stop_count;
4659 int old_errno = errno;
4660 gpointer regs [ARCH_NUM_REGS];
4661 gpointer stack_start;
4663 id = pthread_self ();
4664 info = mono_sgen_thread_info_lookup (id);
4665 info->stopped_domain = mono_domain_get ();
4666 info->stopped_ip = (gpointer) ARCH_SIGCTX_IP (context);
4667 stop_count = global_stop_count;
4668 /* duplicate signal */
4669 if (0 && info->stop_count == stop_count) {
4670 errno = old_errno;
4671 return;
4673 #ifdef HAVE_KW_THREAD
4674 /* update the remset info in the thread data structure */
4675 info->remset = remembered_set;
4676 #endif
4677 stack_start = (char*) ARCH_SIGCTX_SP (context) - REDZONE_SIZE;
4678 /* If stack_start is not within the limits, then don't set it
4679 in info and we will be restarted. */
4680 if (stack_start >= info->stack_start_limit && info->stack_start <= info->stack_end) {
4681 info->stack_start = stack_start;
4683 ARCH_COPY_SIGCTX_REGS (regs, context);
4684 info->stopped_regs = regs;
4685 } else {
4686 g_assert (!info->stack_start);
4689 /* Notify the JIT */
4690 if (gc_callbacks.thread_suspend_func)
4691 gc_callbacks.thread_suspend_func (info->runtime_data, context);
4693 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for suspend from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
4694 /* notify the waiting thread */
4695 MONO_SEM_POST (suspend_ack_semaphore_ptr);
4696 info->stop_count = stop_count;
4698 /* wait until we receive the restart signal */
4699 do {
4700 info->signal = 0;
4701 sigsuspend (&suspend_signal_mask);
4702 } while (info->signal != restart_signal_num);
4704 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for resume from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
4705 /* notify the waiting thread */
4706 MONO_SEM_POST (suspend_ack_semaphore_ptr);
4708 errno = old_errno;
4711 static void
4712 restart_handler (int sig)
4714 SgenThreadInfo *info;
4715 int old_errno = errno;
4717 info = mono_sgen_thread_info_lookup (pthread_self ());
4718 info->signal = restart_signal_num;
4719 DEBUG (4, fprintf (gc_debug_file, "Restart handler in %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
4721 errno = old_errno;
4724 static void
4725 acquire_gc_locks (void)
4727 LOCK_INTERRUPTION;
4730 static void
4731 release_gc_locks (void)
4733 UNLOCK_INTERRUPTION;
4736 static TV_DECLARE (stop_world_time);
4737 static unsigned long max_pause_usec = 0;
4739 /* LOCKING: assumes the GC lock is held */
4740 static int
4741 stop_world (int generation)
4743 int count;
4745 mono_profiler_gc_event (MONO_GC_EVENT_PRE_STOP_WORLD, generation);
4746 acquire_gc_locks ();
4748 update_current_thread_stack (&count);
4750 global_stop_count++;
4751 DEBUG (3, fprintf (gc_debug_file, "stopping world n %d from %p %p\n", global_stop_count, mono_sgen_thread_info_lookup (ARCH_GET_THREAD ()), (gpointer)ARCH_GET_THREAD ()));
4752 TV_GETTIME (stop_world_time);
4753 count = mono_sgen_thread_handshake (suspend_signal_num);
4754 count -= restart_threads_until_none_in_managed_allocator ();
4755 g_assert (count >= 0);
4756 DEBUG (3, fprintf (gc_debug_file, "world stopped %d thread(s)\n", count));
4757 mono_profiler_gc_event (MONO_GC_EVENT_POST_STOP_WORLD, generation);
4758 return count;
4761 /* LOCKING: assumes the GC lock is held */
4762 static int
4763 restart_world (int generation)
4765 int count, i;
4766 SgenThreadInfo *info;
4767 TV_DECLARE (end_sw);
4768 unsigned long usec;
4770 /* notify the profiler of the leftovers */
4771 if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_GC_MOVES)) {
4772 if (moved_objects_idx) {
4773 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
4774 moved_objects_idx = 0;
4777 mono_profiler_gc_event (MONO_GC_EVENT_PRE_START_WORLD, generation);
4778 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4779 for (info = thread_table [i]; info; info = info->next) {
4780 info->stack_start = NULL;
4781 info->stopped_regs = NULL;
4785 release_gc_locks ();
4787 count = mono_sgen_thread_handshake (restart_signal_num);
4788 TV_GETTIME (end_sw);
4789 usec = TV_ELAPSED (stop_world_time, end_sw);
4790 max_pause_usec = MAX (usec, max_pause_usec);
4791 DEBUG (2, fprintf (gc_debug_file, "restarted %d thread(s) (pause time: %d usec, max: %d)\n", count, (int)usec, (int)max_pause_usec));
4792 mono_profiler_gc_event (MONO_GC_EVENT_POST_START_WORLD, generation);
4793 return count;
4796 #endif /* USE_SIGNAL_BASED_START_STOP_WORLD */
4799 mono_sgen_get_current_collection_generation (void)
4801 return current_collection_generation;
4804 void
4805 mono_gc_set_gc_callbacks (MonoGCCallbacks *callbacks)
4807 gc_callbacks = *callbacks;
4810 MonoGCCallbacks *
4811 mono_gc_get_gc_callbacks ()
4813 return &gc_callbacks;
4816 /* Variables holding start/end nursery so it won't have to be passed at every call */
4817 static void *scan_area_arg_start, *scan_area_arg_end;
4819 void
4820 mono_gc_conservatively_scan_area (void *start, void *end)
4822 conservatively_pin_objects_from (start, end, scan_area_arg_start, scan_area_arg_end, PIN_TYPE_STACK);
4825 void*
4826 mono_gc_scan_object (void *obj)
4828 g_assert_not_reached ();
4829 if (current_collection_generation == GENERATION_NURSERY)
4830 major_collector.copy_object (&obj, &gray_queue);
4831 else
4832 major_collector.copy_or_mark_object (&obj, &gray_queue);
4833 return obj;
4837 * Mark from thread stacks and registers.
4839 static void
4840 scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise)
4842 int i;
4843 SgenThreadInfo *info;
4845 scan_area_arg_start = start_nursery;
4846 scan_area_arg_end = end_nursery;
4848 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4849 for (info = thread_table [i]; info; info = info->next) {
4850 if (info->skip) {
4851 DEBUG (3, fprintf (gc_debug_file, "Skipping dead thread %p, range: %p-%p, size: %td\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start));
4852 continue;
4854 DEBUG (3, fprintf (gc_debug_file, "Scanning thread %p, range: %p-%p, size: %td, pinned=%d\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start, next_pin_slot));
4855 if (gc_callbacks.thread_mark_func && !conservative_stack_mark)
4856 gc_callbacks.thread_mark_func (info->runtime_data, info->stack_start, info->stack_end, precise);
4857 else if (!precise)
4858 conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery, PIN_TYPE_STACK);
4860 if (!precise)
4861 conservatively_pin_objects_from (info->stopped_regs, info->stopped_regs + ARCH_NUM_REGS,
4862 start_nursery, end_nursery, PIN_TYPE_STACK);
4867 static void
4868 find_pinning_ref_from_thread (char *obj, size_t size)
4870 int i;
4871 SgenThreadInfo *info;
4872 char *endobj = obj + size;
4874 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4875 for (info = thread_table [i]; info; info = info->next) {
4876 char **start = (char**)info->stack_start;
4877 if (info->skip)
4878 continue;
4879 while (start < (char**)info->stack_end) {
4880 if (*start >= obj && *start < endobj) {
4881 DEBUG (0, fprintf (gc_debug_file, "Object %p referenced in thread %p (id %p) at %p, stack: %p-%p\n", obj, info, (gpointer)info->id, start, info->stack_start, info->stack_end));
4883 start++;
4886 /* FIXME: check info->stopped_regs */
4891 static gboolean
4892 ptr_on_stack (void *ptr)
4894 gpointer stack_start = &stack_start;
4895 SgenThreadInfo *info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
4897 if (ptr >= stack_start && ptr < (gpointer)info->stack_end)
4898 return TRUE;
4899 return FALSE;
4902 static mword*
4903 handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global, GrayQueue *queue)
4905 void **ptr;
4906 mword count;
4907 mword desc;
4909 if (global)
4910 HEAVY_STAT (++stat_global_remsets_processed);
4911 else
4912 HEAVY_STAT (++stat_local_remsets_processed);
4914 /* FIXME: exclude stack locations */
4915 switch ((*p) & REMSET_TYPE_MASK) {
4916 case REMSET_LOCATION:
4917 ptr = (void**)(*p);
4918 //__builtin_prefetch (ptr);
4919 if (((void*)ptr < start_nursery || (void*)ptr >= end_nursery)) {
4920 gpointer old = *ptr;
4921 major_collector.copy_object (ptr, queue);
4922 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p\n", ptr, *ptr));
4923 if (old)
4924 binary_protocol_ptr_update (ptr, old, *ptr, (gpointer)LOAD_VTABLE (*ptr), safe_object_get_size (*ptr));
4925 if (!global && *ptr >= start_nursery && *ptr < end_nursery) {
4927 * If the object is pinned, each reference to it from nonpinned objects
4928 * becomes part of the global remset, which can grow very large.
4930 DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
4931 mono_sgen_add_to_global_remset (ptr);
4933 } else {
4934 DEBUG (9, fprintf (gc_debug_file, "Skipping remset at %p holding %p\n", ptr, *ptr));
4936 return p + 1;
4937 case REMSET_RANGE:
4938 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
4939 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
4940 return p + 2;
4941 count = p [1];
4942 while (count-- > 0) {
4943 major_collector.copy_object (ptr, queue);
4944 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p (count: %d)\n", ptr, *ptr, (int)count));
4945 if (!global && *ptr >= start_nursery && *ptr < end_nursery)
4946 mono_sgen_add_to_global_remset (ptr);
4947 ++ptr;
4949 return p + 2;
4950 case REMSET_OBJECT:
4951 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
4952 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
4953 return p + 1;
4954 major_collector.minor_scan_object ((char*)ptr, queue);
4955 return p + 1;
4956 case REMSET_VTYPE: {
4957 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
4958 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
4959 return p + 3;
4960 desc = p [1];
4961 count = p [2];
4962 while (count-- > 0)
4963 ptr = (void**) major_collector.minor_scan_vtype ((char*)ptr, desc, start_nursery, end_nursery, queue);
4964 return p + 3;
4966 default:
4967 g_assert_not_reached ();
4969 return NULL;
4972 #ifdef HEAVY_STATISTICS
4973 static mword*
4974 collect_store_remsets (RememberedSet *remset, mword *bumper)
4976 mword *p = remset->data;
4977 mword last = 0;
4978 mword last1 = 0;
4979 mword last2 = 0;
4981 while (p < remset->store_next) {
4982 switch ((*p) & REMSET_TYPE_MASK) {
4983 case REMSET_LOCATION:
4984 *bumper++ = *p;
4985 if (*p == last)
4986 ++stat_saved_remsets_1;
4987 last = *p;
4988 if (*p == last1 || *p == last2) {
4989 ++stat_saved_remsets_2;
4990 } else {
4991 last2 = last1;
4992 last1 = *p;
4994 p += 1;
4995 break;
4996 case REMSET_RANGE:
4997 p += 2;
4998 break;
4999 case REMSET_OBJECT:
5000 p += 1;
5001 break;
5002 case REMSET_VTYPE:
5003 p += 3;
5004 break;
5005 default:
5006 g_assert_not_reached ();
5010 return bumper;
5013 static void
5014 remset_stats (void)
5016 RememberedSet *remset;
5017 int size = 0;
5018 SgenThreadInfo *info;
5019 int i;
5020 mword *addresses, *bumper, *p, *r;
5022 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5023 for (info = thread_table [i]; info; info = info->next) {
5024 for (remset = info->remset; remset; remset = remset->next)
5025 size += remset->store_next - remset->data;
5028 for (remset = freed_thread_remsets; remset; remset = remset->next)
5029 size += remset->store_next - remset->data;
5030 for (remset = global_remset; remset; remset = remset->next)
5031 size += remset->store_next - remset->data;
5033 bumper = addresses = mono_sgen_alloc_internal_dynamic (sizeof (mword) * size, INTERNAL_MEM_STATISTICS);
5035 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5036 for (info = thread_table [i]; info; info = info->next) {
5037 for (remset = info->remset; remset; remset = remset->next)
5038 bumper = collect_store_remsets (remset, bumper);
5041 for (remset = global_remset; remset; remset = remset->next)
5042 bumper = collect_store_remsets (remset, bumper);
5043 for (remset = freed_thread_remsets; remset; remset = remset->next)
5044 bumper = collect_store_remsets (remset, bumper);
5046 g_assert (bumper <= addresses + size);
5048 stat_store_remsets += bumper - addresses;
5050 sort_addresses ((void**)addresses, bumper - addresses);
5051 p = addresses;
5052 r = addresses + 1;
5053 while (r < bumper) {
5054 if (*r != *p)
5055 *++p = *r;
5056 ++r;
5059 stat_store_remsets_unique += p - addresses;
5061 mono_sgen_free_internal_dynamic (addresses, sizeof (mword) * size, INTERNAL_MEM_STATISTICS);
5063 #endif
5065 static void
5066 clear_thread_store_remset_buffer (SgenThreadInfo *info)
5068 *info->store_remset_buffer_index_addr = 0;
5069 memset (*info->store_remset_buffer_addr, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
5072 static size_t
5073 remset_byte_size (RememberedSet *remset)
5075 return sizeof (RememberedSet) + (remset->end_set - remset->data) * sizeof (gpointer);
5078 static void
5079 scan_from_remsets (void *start_nursery, void *end_nursery, GrayQueue *queue)
5081 int i;
5082 SgenThreadInfo *info;
5083 RememberedSet *remset;
5084 GenericStoreRememberedSet *store_remset;
5085 mword *p, *next_p, *store_pos;
5087 #ifdef HEAVY_STATISTICS
5088 remset_stats ();
5089 #endif
5091 /* the global one */
5092 for (remset = global_remset; remset; remset = remset->next) {
5093 DEBUG (4, fprintf (gc_debug_file, "Scanning global remset range: %p-%p, size: %td\n", remset->data, remset->store_next, remset->store_next - remset->data));
5094 store_pos = remset->data;
5095 for (p = remset->data; p < remset->store_next; p = next_p) {
5096 void **ptr = (void**)p [0];
5098 /*Ignore previously processed remset.*/
5099 if (!global_remset_location_was_not_added (ptr)) {
5100 next_p = p + 1;
5101 continue;
5104 next_p = handle_remset (p, start_nursery, end_nursery, TRUE, queue);
5107 * Clear global remsets of locations which no longer point to the
5108 * nursery. Otherwise, they could grow indefinitely between major
5109 * collections.
5111 * Since all global remsets are location remsets, we don't need to unmask the pointer.
5113 if (ptr_in_nursery (*ptr)) {
5114 *store_pos ++ = p [0];
5115 HEAVY_STAT (++stat_global_remsets_readded);
5119 /* Truncate the remset */
5120 remset->store_next = store_pos;
5123 /* the generic store ones */
5124 store_remset = generic_store_remsets;
5125 while (store_remset) {
5126 GenericStoreRememberedSet *next = store_remset->next;
5128 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
5129 gpointer addr = store_remset->data [i];
5130 if (addr)
5131 handle_remset ((mword*)&addr, start_nursery, end_nursery, FALSE, queue);
5134 mono_sgen_free_internal (store_remset, INTERNAL_MEM_STORE_REMSET);
5136 store_remset = next;
5138 generic_store_remsets = NULL;
5140 /* the per-thread ones */
5141 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5142 for (info = thread_table [i]; info; info = info->next) {
5143 RememberedSet *next;
5144 int j;
5145 for (remset = info->remset; remset; remset = next) {
5146 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for thread %p, range: %p-%p, size: %td\n", info, remset->data, remset->store_next, remset->store_next - remset->data));
5147 for (p = remset->data; p < remset->store_next;)
5148 p = handle_remset (p, start_nursery, end_nursery, FALSE, queue);
5149 remset->store_next = remset->data;
5150 next = remset->next;
5151 remset->next = NULL;
5152 if (remset != info->remset) {
5153 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5154 mono_sgen_free_internal_dynamic (remset, remset_byte_size (remset), INTERNAL_MEM_REMSET);
5157 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j)
5158 handle_remset ((mword*)*info->store_remset_buffer_addr + j + 1, start_nursery, end_nursery, FALSE, queue);
5159 clear_thread_store_remset_buffer (info);
5163 /* the freed thread ones */
5164 while (freed_thread_remsets) {
5165 RememberedSet *next;
5166 remset = freed_thread_remsets;
5167 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for freed thread, range: %p-%p, size: %td\n", remset->data, remset->store_next, remset->store_next - remset->data));
5168 for (p = remset->data; p < remset->store_next;)
5169 p = handle_remset (p, start_nursery, end_nursery, FALSE, queue);
5170 next = remset->next;
5171 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5172 mono_sgen_free_internal_dynamic (remset, remset_byte_size (remset), INTERNAL_MEM_REMSET);
5173 freed_thread_remsets = next;
5178 * Clear the info in the remembered sets: we're doing a major collection, so
5179 * the per-thread ones are not needed and the global ones will be reconstructed
5180 * during the copy.
5182 static void
5183 clear_remsets (void)
5185 int i;
5186 SgenThreadInfo *info;
5187 RememberedSet *remset, *next;
5189 /* the global list */
5190 for (remset = global_remset; remset; remset = next) {
5191 remset->store_next = remset->data;
5192 next = remset->next;
5193 remset->next = NULL;
5194 if (remset != global_remset) {
5195 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5196 mono_sgen_free_internal_dynamic (remset, remset_byte_size (remset), INTERNAL_MEM_REMSET);
5199 /* the generic store ones */
5200 while (generic_store_remsets) {
5201 GenericStoreRememberedSet *gs_next = generic_store_remsets->next;
5202 mono_sgen_free_internal (generic_store_remsets, INTERNAL_MEM_STORE_REMSET);
5203 generic_store_remsets = gs_next;
5205 /* the per-thread ones */
5206 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5207 for (info = thread_table [i]; info; info = info->next) {
5208 for (remset = info->remset; remset; remset = next) {
5209 remset->store_next = remset->data;
5210 next = remset->next;
5211 remset->next = NULL;
5212 if (remset != info->remset) {
5213 DEBUG (3, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5214 mono_sgen_free_internal_dynamic (remset, remset_byte_size (remset), INTERNAL_MEM_REMSET);
5217 clear_thread_store_remset_buffer (info);
5221 /* the freed thread ones */
5222 while (freed_thread_remsets) {
5223 next = freed_thread_remsets->next;
5224 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", freed_thread_remsets->data));
5225 mono_sgen_free_internal_dynamic (freed_thread_remsets, remset_byte_size (freed_thread_remsets), INTERNAL_MEM_REMSET);
5226 freed_thread_remsets = next;
5231 * Clear the thread local TLAB variables for all threads.
5233 static void
5234 clear_tlabs (void)
5236 SgenThreadInfo *info;
5237 int i;
5239 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5240 for (info = thread_table [i]; info; info = info->next) {
5241 /* A new TLAB will be allocated when the thread does its first allocation */
5242 *info->tlab_start_addr = NULL;
5243 *info->tlab_next_addr = NULL;
5244 *info->tlab_temp_end_addr = NULL;
5245 *info->tlab_real_end_addr = NULL;
5250 /* LOCKING: assumes the GC lock is held */
5251 static SgenThreadInfo*
5252 gc_register_current_thread (void *addr)
5254 int hash;
5255 SgenThreadInfo* info = malloc (sizeof (SgenThreadInfo));
5256 #ifndef HAVE_KW_THREAD
5257 SgenThreadInfo *__thread_info__ = info;
5258 #endif
5260 if (!info)
5261 return NULL;
5263 memset (info, 0, sizeof (SgenThreadInfo));
5264 #ifndef HAVE_KW_THREAD
5265 info->tlab_start = info->tlab_next = info->tlab_temp_end = info->tlab_real_end = NULL;
5267 g_assert (!pthread_getspecific (thread_info_key));
5268 pthread_setspecific (thread_info_key, info);
5269 #else
5270 thread_info = info;
5271 #endif
5273 info->id = ARCH_GET_THREAD ();
5274 info->stop_count = -1;
5275 info->skip = 0;
5276 info->signal = 0;
5277 info->stack_start = NULL;
5278 info->tlab_start_addr = &TLAB_START;
5279 info->tlab_next_addr = &TLAB_NEXT;
5280 info->tlab_temp_end_addr = &TLAB_TEMP_END;
5281 info->tlab_real_end_addr = &TLAB_REAL_END;
5282 info->store_remset_buffer_addr = &STORE_REMSET_BUFFER;
5283 info->store_remset_buffer_index_addr = &STORE_REMSET_BUFFER_INDEX;
5284 info->stopped_ip = NULL;
5285 info->stopped_domain = NULL;
5286 info->stopped_regs = NULL;
5288 binary_protocol_thread_register ((gpointer)info->id);
5290 #ifdef HAVE_KW_THREAD
5291 tlab_next_addr = &tlab_next;
5292 store_remset_buffer_index_addr = &store_remset_buffer_index;
5293 #endif
5295 /* try to get it with attributes first */
5296 #if defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)
5298 size_t size;
5299 void *sstart;
5300 pthread_attr_t attr;
5301 pthread_getattr_np (pthread_self (), &attr);
5302 pthread_attr_getstack (&attr, &sstart, &size);
5303 info->stack_start_limit = sstart;
5304 info->stack_end = (char*)sstart + size;
5305 pthread_attr_destroy (&attr);
5307 #elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
5308 info->stack_end = (char*)pthread_get_stackaddr_np (pthread_self ());
5309 info->stack_start_limit = (char*)info->stack_end - pthread_get_stacksize_np (pthread_self ());
5310 #else
5312 /* FIXME: we assume the stack grows down */
5313 gsize stack_bottom = (gsize)addr;
5314 stack_bottom += 4095;
5315 stack_bottom &= ~4095;
5316 info->stack_end = (char*)stack_bottom;
5318 #endif
5320 #ifdef HAVE_KW_THREAD
5321 stack_end = info->stack_end;
5322 #endif
5324 /* hash into the table */
5325 hash = HASH_PTHREAD_T (info->id) % THREAD_HASH_SIZE;
5326 info->next = thread_table [hash];
5327 thread_table [hash] = info;
5329 info->remset = alloc_remset (DEFAULT_REMSET_SIZE, info);
5330 pthread_setspecific (remembered_set_key, info->remset);
5331 #ifdef HAVE_KW_THREAD
5332 remembered_set = info->remset;
5333 #endif
5335 STORE_REMSET_BUFFER = mono_sgen_alloc_internal (INTERNAL_MEM_STORE_REMSET);
5336 STORE_REMSET_BUFFER_INDEX = 0;
5338 DEBUG (3, fprintf (gc_debug_file, "registered thread %p (%p) (hash: %d)\n", info, (gpointer)info->id, hash));
5340 if (gc_callbacks.thread_attach_func)
5341 info->runtime_data = gc_callbacks.thread_attach_func ();
5343 return info;
5346 static void
5347 add_generic_store_remset_from_buffer (gpointer *buffer)
5349 GenericStoreRememberedSet *remset = mono_sgen_alloc_internal (INTERNAL_MEM_STORE_REMSET);
5350 memcpy (remset->data, buffer + 1, sizeof (gpointer) * (STORE_REMSET_BUFFER_SIZE - 1));
5351 remset->next = generic_store_remsets;
5352 generic_store_remsets = remset;
5355 static void
5356 unregister_current_thread (void)
5358 int hash;
5359 SgenThreadInfo *prev = NULL;
5360 SgenThreadInfo *p;
5361 RememberedSet *rset;
5362 ARCH_THREAD_TYPE id = ARCH_GET_THREAD ();
5364 binary_protocol_thread_unregister ((gpointer)id);
5366 hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
5367 p = thread_table [hash];
5368 assert (p);
5369 DEBUG (3, fprintf (gc_debug_file, "unregister thread %p (%p)\n", p, (gpointer)p->id));
5370 while (!ARCH_THREAD_EQUALS (p->id, id)) {
5371 prev = p;
5372 p = p->next;
5374 if (prev == NULL) {
5375 thread_table [hash] = p->next;
5376 } else {
5377 prev->next = p->next;
5379 if (p->remset) {
5380 if (freed_thread_remsets) {
5381 for (rset = p->remset; rset->next; rset = rset->next)
5383 rset->next = freed_thread_remsets;
5384 freed_thread_remsets = p->remset;
5385 } else {
5386 freed_thread_remsets = p->remset;
5389 if (*p->store_remset_buffer_index_addr)
5390 add_generic_store_remset_from_buffer (*p->store_remset_buffer_addr);
5391 mono_sgen_free_internal (*p->store_remset_buffer_addr, INTERNAL_MEM_STORE_REMSET);
5392 free (p);
5395 static void
5396 unregister_thread (void *k)
5398 g_assert (!mono_domain_get ());
5399 LOCK_GC;
5400 unregister_current_thread ();
5401 UNLOCK_GC;
5404 gboolean
5405 mono_gc_register_thread (void *baseptr)
5407 SgenThreadInfo *info;
5409 LOCK_GC;
5410 init_stats ();
5411 info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
5412 if (info == NULL)
5413 info = gc_register_current_thread (baseptr);
5414 UNLOCK_GC;
5416 /* Need a better place to initialize this */
5417 if (!array_fill_vtable && mono_get_root_domain ()) {
5418 array_fill_vtable = mono_class_vtable (mono_get_root_domain (), mono_array_class_get (mono_defaults.byte_class, 1));
5421 return info != NULL;
5424 #if USE_PTHREAD_INTERCEPT
5426 typedef struct {
5427 void *(*start_routine) (void *);
5428 void *arg;
5429 int flags;
5430 MonoSemType registered;
5431 } SgenThreadStartInfo;
5433 static void*
5434 gc_start_thread (void *arg)
5436 SgenThreadStartInfo *start_info = arg;
5437 SgenThreadInfo* info;
5438 void *t_arg = start_info->arg;
5439 void *(*start_func) (void*) = start_info->start_routine;
5440 void *result;
5441 int post_result;
5443 LOCK_GC;
5444 info = gc_register_current_thread (&result);
5445 UNLOCK_GC;
5446 post_result = MONO_SEM_POST (&(start_info->registered));
5447 g_assert (!post_result);
5448 result = start_func (t_arg);
5449 g_assert (!mono_domain_get ());
5451 * this is done by the pthread key dtor
5452 LOCK_GC;
5453 unregister_current_thread ();
5454 UNLOCK_GC;
5457 return result;
5461 mono_gc_pthread_create (pthread_t *new_thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
5463 SgenThreadStartInfo *start_info;
5464 int result;
5466 start_info = malloc (sizeof (SgenThreadStartInfo));
5467 if (!start_info)
5468 return ENOMEM;
5469 MONO_SEM_INIT (&(start_info->registered), 0);
5470 start_info->arg = arg;
5471 start_info->start_routine = start_routine;
5473 result = pthread_create (new_thread, attr, gc_start_thread, start_info);
5474 if (result == 0) {
5475 while (MONO_SEM_WAIT (&(start_info->registered)) != 0) {
5476 /*if (EINTR != errno) ABORT("sem_wait failed"); */
5479 MONO_SEM_DESTROY (&(start_info->registered));
5480 free (start_info);
5481 return result;
5485 mono_gc_pthread_join (pthread_t thread, void **retval)
5487 return pthread_join (thread, retval);
5491 mono_gc_pthread_detach (pthread_t thread)
5493 return pthread_detach (thread);
5496 #endif /* USE_PTHREAD_INTERCEPT */
5499 * ######################################################################
5500 * ######## Write barriers
5501 * ######################################################################
5505 * This causes the compile to extend the liveness of 'v' till the call to dummy_use
5507 static void
5508 dummy_use (gpointer v) {
5509 __asm__ volatile ("" : "=r"(v) : "r"(v));
5513 static RememberedSet*
5514 alloc_remset (int size, gpointer id) {
5515 RememberedSet* res = mono_sgen_alloc_internal_dynamic (sizeof (RememberedSet) + (size * sizeof (gpointer)), INTERNAL_MEM_REMSET);
5516 res->store_next = res->data;
5517 res->end_set = res->data + size;
5518 res->next = NULL;
5519 DEBUG (4, fprintf (gc_debug_file, "Allocated remset size %d at %p for %p\n", size, res->data, id));
5520 return res;
5524 * Note: the write barriers first do the needed GC work and then do the actual store:
5525 * this way the value is visible to the conservative GC scan after the write barrier
5526 * itself. If a GC interrupts the barrier in the middle, value will be kept alive by
5527 * the conservative scan, otherwise by the remembered set scan.
5529 void
5530 mono_gc_wbarrier_set_field (MonoObject *obj, gpointer field_ptr, MonoObject* value)
5532 HEAVY_STAT (++stat_wbarrier_set_field);
5533 if (ptr_in_nursery (field_ptr)) {
5534 *(void**)field_ptr = value;
5535 return;
5537 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", field_ptr));
5538 if (use_cardtable) {
5539 *(void**)field_ptr = value;
5540 if (ptr_in_nursery (value))
5541 sgen_card_table_mark_address ((mword)field_ptr);
5542 dummy_use (value);
5543 } else {
5544 RememberedSet *rs;
5545 TLAB_ACCESS_INIT;
5547 LOCK_GC;
5548 rs = REMEMBERED_SET;
5549 if (rs->store_next < rs->end_set) {
5550 *(rs->store_next++) = (mword)field_ptr;
5551 *(void**)field_ptr = value;
5552 UNLOCK_GC;
5553 return;
5555 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5556 rs->next = REMEMBERED_SET;
5557 REMEMBERED_SET = rs;
5558 #ifdef HAVE_KW_THREAD
5559 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5560 #endif
5561 *(rs->store_next++) = (mword)field_ptr;
5562 *(void**)field_ptr = value;
5563 UNLOCK_GC;
5567 void
5568 mono_gc_wbarrier_set_arrayref (MonoArray *arr, gpointer slot_ptr, MonoObject* value)
5570 HEAVY_STAT (++stat_wbarrier_set_arrayref);
5571 if (ptr_in_nursery (slot_ptr)) {
5572 *(void**)slot_ptr = value;
5573 return;
5575 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", slot_ptr));
5576 if (use_cardtable) {
5577 *(void**)slot_ptr = value;
5578 if (ptr_in_nursery (value))
5579 sgen_card_table_mark_address ((mword)slot_ptr);
5580 dummy_use (value);
5581 } else {
5582 RememberedSet *rs;
5583 TLAB_ACCESS_INIT;
5585 LOCK_GC;
5586 rs = REMEMBERED_SET;
5587 if (rs->store_next < rs->end_set) {
5588 *(rs->store_next++) = (mword)slot_ptr;
5589 *(void**)slot_ptr = value;
5590 UNLOCK_GC;
5591 return;
5593 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5594 rs->next = REMEMBERED_SET;
5595 REMEMBERED_SET = rs;
5596 #ifdef HAVE_KW_THREAD
5597 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5598 #endif
5599 *(rs->store_next++) = (mword)slot_ptr;
5600 *(void**)slot_ptr = value;
5601 UNLOCK_GC;
5605 void
5606 mono_gc_wbarrier_arrayref_copy (gpointer dest_ptr, gpointer src_ptr, int count)
5608 HEAVY_STAT (++stat_wbarrier_arrayref_copy);
5609 /*This check can be done without taking a lock since dest_ptr array is pinned*/
5610 if (ptr_in_nursery (dest_ptr) || count <= 0) {
5611 memmove (dest_ptr, src_ptr, count * sizeof (gpointer));
5612 return;
5615 if (use_cardtable) {
5616 gpointer *dest = dest_ptr;
5617 gpointer *src = src_ptr;
5619 /*overlapping that required backward copying*/
5620 if (src < dest && (src + count) > dest) {
5621 gpointer *start = dest;
5622 dest += count - 1;
5623 src += count - 1;
5625 for (; dest >= start; --src, --dest) {
5626 gpointer value = *src;
5627 *dest = value;
5628 if (ptr_in_nursery (value))
5629 sgen_card_table_mark_address ((mword)dest);
5630 dummy_use (value);
5632 } else {
5633 gpointer *end = dest + count;
5634 for (; dest < end; ++src, ++dest) {
5635 gpointer value = *src;
5636 *dest = value;
5637 if (ptr_in_nursery (value))
5638 sgen_card_table_mark_address ((mword)dest);
5639 dummy_use (value);
5642 } else {
5643 RememberedSet *rs;
5644 TLAB_ACCESS_INIT;
5645 LOCK_GC;
5646 memmove (dest_ptr, src_ptr, count * sizeof (gpointer));
5648 rs = REMEMBERED_SET;
5649 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p, %d\n", dest_ptr, count));
5650 if (rs->store_next + 1 < rs->end_set) {
5651 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
5652 *(rs->store_next++) = count;
5653 UNLOCK_GC;
5654 return;
5656 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5657 rs->next = REMEMBERED_SET;
5658 REMEMBERED_SET = rs;
5659 #ifdef HAVE_KW_THREAD
5660 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5661 #endif
5662 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
5663 *(rs->store_next++) = count;
5665 UNLOCK_GC;
5669 static char *found_obj;
5671 static void
5672 find_object_for_ptr_callback (char *obj, size_t size, char *ptr)
5674 if (ptr >= obj && ptr < obj + size) {
5675 g_assert (!found_obj);
5676 found_obj = obj;
5680 /* for use in the debugger */
5681 char* find_object_for_ptr (char *ptr);
5682 char*
5683 find_object_for_ptr (char *ptr)
5685 LOSObject *bigobj;
5687 if (ptr >= nursery_section->data && ptr < nursery_section->end_data) {
5688 found_obj = NULL;
5689 mono_sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data,
5690 (IterateObjectCallbackFunc)find_object_for_ptr_callback, ptr);
5691 if (found_obj)
5692 return found_obj;
5695 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
5696 if (ptr >= bigobj->data && ptr < bigobj->data + bigobj->size)
5697 return bigobj->data;
5701 * Very inefficient, but this is debugging code, supposed to
5702 * be called from gdb, so we don't care.
5704 found_obj = NULL;
5705 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)find_object_for_ptr_callback, ptr);
5706 return found_obj;
5709 static void
5710 evacuate_remset_buffer (void)
5712 gpointer *buffer;
5713 TLAB_ACCESS_INIT;
5715 buffer = STORE_REMSET_BUFFER;
5717 add_generic_store_remset_from_buffer (buffer);
5718 memset (buffer, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
5720 STORE_REMSET_BUFFER_INDEX = 0;
5723 void
5724 mono_gc_wbarrier_generic_nostore (gpointer ptr)
5726 gpointer *buffer;
5727 int index;
5728 TLAB_ACCESS_INIT;
5730 HEAVY_STAT (++stat_wbarrier_generic_store);
5732 #ifdef XDOMAIN_CHECKS_IN_WBARRIER
5733 /* FIXME: ptr_in_heap must be called with the GC lock held */
5734 if (xdomain_checks && *(MonoObject**)ptr && ptr_in_heap (ptr)) {
5735 char *start = find_object_for_ptr (ptr);
5736 MonoObject *value = *(MonoObject**)ptr;
5737 LOCK_GC;
5738 g_assert (start);
5739 if (start) {
5740 MonoObject *obj = (MonoObject*)start;
5741 if (obj->vtable->domain != value->vtable->domain)
5742 g_assert (is_xdomain_ref_allowed (ptr, start, obj->vtable->domain));
5744 UNLOCK_GC;
5746 #endif
5748 if (*(gpointer*)ptr)
5749 binary_protocol_wbarrier (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
5751 if (ptr_in_nursery (ptr) || ptr_on_stack (ptr) || !ptr_in_nursery (*(gpointer*)ptr)) {
5752 DEBUG (8, fprintf (gc_debug_file, "Skipping remset at %p\n", ptr));
5753 return;
5756 if (use_cardtable) {
5757 if (ptr_in_nursery(*(gpointer*)ptr))
5758 sgen_card_table_mark_address ((mword)ptr);
5759 return;
5762 LOCK_GC;
5764 buffer = STORE_REMSET_BUFFER;
5765 index = STORE_REMSET_BUFFER_INDEX;
5766 /* This simple optimization eliminates a sizable portion of
5767 entries. Comparing it to the last but one entry as well
5768 doesn't eliminate significantly more entries. */
5769 if (buffer [index] == ptr) {
5770 UNLOCK_GC;
5771 return;
5774 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", ptr));
5775 HEAVY_STAT (++stat_wbarrier_generic_store_remset);
5777 ++index;
5778 if (index >= STORE_REMSET_BUFFER_SIZE) {
5779 evacuate_remset_buffer ();
5780 index = STORE_REMSET_BUFFER_INDEX;
5781 g_assert (index == 0);
5782 ++index;
5784 buffer [index] = ptr;
5785 STORE_REMSET_BUFFER_INDEX = index;
5787 UNLOCK_GC;
5790 void
5791 mono_gc_wbarrier_generic_store (gpointer ptr, MonoObject* value)
5793 DEBUG (8, fprintf (gc_debug_file, "Wbarrier store at %p to %p (%s)\n", ptr, value, value ? safe_name (value) : "null"));
5794 *(void**)ptr = value;
5795 if (ptr_in_nursery (value))
5796 mono_gc_wbarrier_generic_nostore (ptr);
5797 dummy_use (value);
5800 void mono_gc_wbarrier_value_copy_bitmap (gpointer _dest, gpointer _src, int size, unsigned bitmap)
5802 mword *dest = _dest;
5803 mword *src = _src;
5805 while (size) {
5806 if (bitmap & 0x1)
5807 mono_gc_wbarrier_generic_store (dest, (MonoObject*)*src);
5808 else
5809 *dest = *src;
5810 ++src;
5811 ++dest;
5812 size -= SIZEOF_VOID_P;
5813 bitmap >>= 1;
5818 void
5819 mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *klass)
5821 RememberedSet *rs;
5822 size_t size = count * mono_class_value_size (klass, NULL);
5823 TLAB_ACCESS_INIT;
5824 HEAVY_STAT (++stat_wbarrier_value_copy);
5825 g_assert (klass->valuetype);
5826 LOCK_GC;
5827 memmove (dest, src, size);
5828 if (use_cardtable) {
5829 sgen_card_table_mark_range ((mword)dest, size);
5830 } else {
5831 rs = REMEMBERED_SET;
5832 if (ptr_in_nursery (dest) || ptr_on_stack (dest) || !klass->has_references) {
5833 UNLOCK_GC;
5834 return;
5836 g_assert (klass->gc_descr_inited);
5837 DEBUG (8, fprintf (gc_debug_file, "Adding value remset at %p, count %d, descr %p for class %s (%p)\n", dest, count, klass->gc_descr, klass->name, klass));
5839 if (rs->store_next + 3 < rs->end_set) {
5840 *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
5841 *(rs->store_next++) = (mword)klass->gc_descr;
5842 *(rs->store_next++) = (mword)count;
5843 UNLOCK_GC;
5844 return;
5846 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5847 rs->next = REMEMBERED_SET;
5848 REMEMBERED_SET = rs;
5849 #ifdef HAVE_KW_THREAD
5850 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5851 #endif
5852 *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
5853 *(rs->store_next++) = (mword)klass->gc_descr;
5854 *(rs->store_next++) = (mword)count;
5856 UNLOCK_GC;
5860 * mono_gc_wbarrier_object_copy:
5862 * Write barrier to call when obj is the result of a clone or copy of an object.
5864 void
5865 mono_gc_wbarrier_object_copy (MonoObject* obj, MonoObject *src)
5867 RememberedSet *rs;
5868 int size;
5870 TLAB_ACCESS_INIT;
5871 HEAVY_STAT (++stat_wbarrier_object_copy);
5872 rs = REMEMBERED_SET;
5873 DEBUG (6, fprintf (gc_debug_file, "Adding object remset for %p\n", obj));
5874 size = mono_object_class (obj)->instance_size;
5875 LOCK_GC;
5876 /* do not copy the sync state */
5877 memcpy ((char*)obj + sizeof (MonoObject), (char*)src + sizeof (MonoObject),
5878 size - sizeof (MonoObject));
5879 if (ptr_in_nursery (obj) || ptr_on_stack (obj)) {
5880 UNLOCK_GC;
5881 return;
5883 if (rs->store_next < rs->end_set) {
5884 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
5885 UNLOCK_GC;
5886 return;
5888 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5889 rs->next = REMEMBERED_SET;
5890 REMEMBERED_SET = rs;
5891 #ifdef HAVE_KW_THREAD
5892 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5893 #endif
5894 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
5895 UNLOCK_GC;
5899 * ######################################################################
5900 * ######## Collector debugging
5901 * ######################################################################
5904 const char*descriptor_types [] = {
5905 "run_length",
5906 "small_bitmap",
5907 "string",
5908 "complex",
5909 "vector",
5910 "array",
5911 "large_bitmap",
5912 "complex_arr"
5915 void
5916 describe_ptr (char *ptr)
5918 MonoVTable *vtable;
5919 mword desc;
5920 int type;
5922 if (ptr_in_nursery (ptr)) {
5923 printf ("Pointer inside nursery.\n");
5924 } else {
5925 if (major_collector.ptr_is_in_non_pinned_space (ptr)) {
5926 printf ("Pointer inside oldspace.\n");
5927 } else if (major_collector.obj_is_from_pinned_alloc (ptr)) {
5928 printf ("Pointer is inside a pinned chunk.\n");
5929 } else {
5930 printf ("Pointer unknown.\n");
5931 return;
5935 if (object_is_pinned (ptr))
5936 printf ("Object is pinned.\n");
5938 if (object_is_forwarded (ptr))
5939 printf ("Object is forwared.\n");
5941 // FIXME: Handle pointers to the inside of objects
5942 vtable = (MonoVTable*)LOAD_VTABLE (ptr);
5944 printf ("VTable: %p\n", vtable);
5945 if (vtable == NULL) {
5946 printf ("VTable is invalid (empty).\n");
5947 return;
5949 if (ptr_in_nursery (vtable)) {
5950 printf ("VTable is invalid (points inside nursery).\n");
5951 return;
5953 printf ("Class: %s\n", vtable->klass->name);
5955 desc = ((GCVTable*)vtable)->desc;
5956 printf ("Descriptor: %lx\n", (long)desc);
5958 type = desc & 0x7;
5959 printf ("Descriptor type: %d (%s)\n", type, descriptor_types [type]);
5962 static mword*
5963 find_in_remset_loc (mword *p, char *addr, gboolean *found)
5965 void **ptr;
5966 mword count, desc;
5967 size_t skip_size;
5969 switch ((*p) & REMSET_TYPE_MASK) {
5970 case REMSET_LOCATION:
5971 if (*p == (mword)addr)
5972 *found = TRUE;
5973 return p + 1;
5974 case REMSET_RANGE:
5975 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5976 count = p [1];
5977 if ((void**)addr >= ptr && (void**)addr < ptr + count)
5978 *found = TRUE;
5979 return p + 2;
5980 case REMSET_OBJECT:
5981 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5982 count = safe_object_get_size ((MonoObject*)ptr);
5983 count = ALIGN_UP (count);
5984 count /= sizeof (mword);
5985 if ((void**)addr >= ptr && (void**)addr < ptr + count)
5986 *found = TRUE;
5987 return p + 1;
5988 case REMSET_VTYPE:
5989 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5990 desc = p [1];
5991 count = p [2];
5993 switch (desc & 0x7) {
5994 case DESC_TYPE_RUN_LENGTH:
5995 OBJ_RUN_LEN_SIZE (skip_size, desc, ptr);
5996 break;
5997 case DESC_TYPE_SMALL_BITMAP:
5998 OBJ_BITMAP_SIZE (skip_size, desc, start);
5999 break;
6000 default:
6001 // FIXME:
6002 g_assert_not_reached ();
6005 /* The descriptor includes the size of MonoObject */
6006 skip_size -= sizeof (MonoObject);
6007 skip_size *= count;
6008 if ((void**)addr >= ptr && (void**)addr < ptr + (skip_size / sizeof (gpointer)))
6009 *found = TRUE;
6011 return p + 3;
6012 default:
6013 g_assert_not_reached ();
6015 return NULL;
6019 * Return whenever ADDR occurs in the remembered sets
6021 static gboolean
6022 find_in_remsets (char *addr)
6024 int i;
6025 SgenThreadInfo *info;
6026 RememberedSet *remset;
6027 GenericStoreRememberedSet *store_remset;
6028 mword *p;
6029 gboolean found = FALSE;
6031 /* the global one */
6032 for (remset = global_remset; remset; remset = remset->next) {
6033 DEBUG (4, fprintf (gc_debug_file, "Scanning global remset range: %p-%p, size: %td\n", remset->data, remset->store_next, remset->store_next - remset->data));
6034 for (p = remset->data; p < remset->store_next;) {
6035 p = find_in_remset_loc (p, addr, &found);
6036 if (found)
6037 return TRUE;
6041 /* the generic store ones */
6042 for (store_remset = generic_store_remsets; store_remset; store_remset = store_remset->next) {
6043 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
6044 if (store_remset->data [i] == addr)
6045 return TRUE;
6049 /* the per-thread ones */
6050 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
6051 for (info = thread_table [i]; info; info = info->next) {
6052 int j;
6053 for (remset = info->remset; remset; remset = remset->next) {
6054 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for thread %p, range: %p-%p, size: %td\n", info, remset->data, remset->store_next, remset->store_next - remset->data));
6055 for (p = remset->data; p < remset->store_next;) {
6056 p = find_in_remset_loc (p, addr, &found);
6057 if (found)
6058 return TRUE;
6061 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j) {
6062 if ((*info->store_remset_buffer_addr) [j + 1] == addr)
6063 return TRUE;
6068 /* the freed thread ones */
6069 for (remset = freed_thread_remsets; remset; remset = remset->next) {
6070 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for freed thread, range: %p-%p, size: %td\n", remset->data, remset->store_next, remset->store_next - remset->data));
6071 for (p = remset->data; p < remset->store_next;) {
6072 p = find_in_remset_loc (p, addr, &found);
6073 if (found)
6074 return TRUE;
6078 return FALSE;
6081 static gboolean missing_remsets;
6084 * We let a missing remset slide if the target object is pinned,
6085 * because the store might have happened but the remset not yet added,
6086 * but in that case the target must be pinned. We might theoretically
6087 * miss some missing remsets this way, but it's very unlikely.
6089 #undef HANDLE_PTR
6090 #define HANDLE_PTR(ptr,obj) do { \
6091 if (*(ptr) && (char*)*(ptr) >= nursery_start && (char*)*(ptr) < nursery_next) { \
6092 if (!find_in_remsets ((char*)(ptr)) && (!use_cardtable || !sgen_card_table_address_is_marked ((mword)ptr))) { \
6093 fprintf (gc_debug_file, "Oldspace->newspace reference %p at offset %td in object %p (%s.%s) not found in remsets.\n", *(ptr), (char*)(ptr) - (char*)(obj), (obj), ((MonoObject*)(obj))->vtable->klass->name_space, ((MonoObject*)(obj))->vtable->klass->name); \
6094 binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
6095 if (!object_is_pinned (*(ptr))) \
6096 missing_remsets = TRUE; \
6099 } while (0)
6102 * Check that each object reference which points into the nursery can
6103 * be found in the remembered sets.
6105 static void
6106 check_consistency_callback (char *start, size_t size, void *dummy)
6108 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
6109 DEBUG (8, fprintf (gc_debug_file, "Scanning object %p, vtable: %p (%s)\n", start, vt, vt->klass->name));
6111 #define SCAN_OBJECT_ACTION
6112 #include "sgen-scan-object.h"
6116 * Perform consistency check of the heap.
6118 * Assumes the world is stopped.
6120 static void
6121 check_consistency (void)
6123 LOSObject *bigobj;
6125 // Need to add more checks
6127 missing_remsets = FALSE;
6129 DEBUG (1, fprintf (gc_debug_file, "Begin heap consistency check...\n"));
6131 // Check that oldspace->newspace pointers are registered with the collector
6132 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
6134 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
6135 check_consistency_callback (bigobj->data, bigobj->size, NULL);
6137 DEBUG (1, fprintf (gc_debug_file, "Heap consistency check done.\n"));
6139 #ifdef SGEN_BINARY_PROTOCOL
6140 if (!binary_protocol_file)
6141 #endif
6142 g_assert (!missing_remsets);
6146 #undef HANDLE_PTR
6147 #define HANDLE_PTR(ptr,obj) do { \
6148 if (*(ptr) && !LOAD_VTABLE (*(ptr))) \
6149 g_error ("Could not load vtable for obj %p slot %d (size %d)", obj, (char*)ptr - (char*)obj, safe_object_get_size ((MonoObject*)obj)); \
6150 } while (0)
6152 static void
6153 check_major_refs_callback (char *start, size_t size, void *dummy)
6155 #define SCAN_OBJECT_ACTION
6156 #include "sgen-scan-object.h"
6159 static void
6160 check_major_refs (void)
6162 LOSObject *bigobj;
6164 major_collector.iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
6166 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
6167 check_major_refs_callback (bigobj->data, bigobj->size, NULL);
6170 /* Check that the reference is valid */
6171 #undef HANDLE_PTR
6172 #define HANDLE_PTR(ptr,obj) do { \
6173 if (*(ptr)) { \
6174 g_assert (safe_name (*(ptr)) != NULL); \
6176 } while (0)
6179 * check_object:
6181 * Perform consistency check on an object. Currently we only check that the
6182 * reference fields are valid.
6184 void
6185 check_object (char *start)
6187 if (!start)
6188 return;
6190 #include "sgen-scan-object.h"
6194 * ######################################################################
6195 * ######## Other mono public interface functions.
6196 * ######################################################################
6199 #define REFS_SIZE 128
6200 typedef struct {
6201 void *data;
6202 MonoGCReferences callback;
6203 int flags;
6204 int count;
6205 int called;
6206 MonoObject *refs [REFS_SIZE];
6207 } HeapWalkInfo;
6209 #undef HANDLE_PTR
6210 #define HANDLE_PTR(ptr,obj) do { \
6211 if (*(ptr)) { \
6212 if (hwi->count == REFS_SIZE) { \
6213 hwi->callback ((MonoObject*)start, mono_object_class (start), hwi->called? 0: size, hwi->count, hwi->refs, hwi->data); \
6214 hwi->count = 0; \
6215 hwi->called = 1; \
6217 hwi->refs [hwi->count++] = *(ptr); \
6219 } while (0)
6221 static void
6222 collect_references (HeapWalkInfo *hwi, char *start, size_t size)
6224 #include "sgen-scan-object.h"
6227 static void
6228 walk_references (char *start, size_t size, void *data)
6230 HeapWalkInfo *hwi = data;
6231 hwi->called = 0;
6232 hwi->count = 0;
6233 collect_references (hwi, start, size);
6234 if (hwi->count || !hwi->called)
6235 hwi->callback ((MonoObject*)start, mono_object_class (start), hwi->called? 0: size, hwi->count, hwi->refs, hwi->data);
6239 * mono_gc_walk_heap:
6240 * @flags: flags for future use
6241 * @callback: a function pointer called for each object in the heap
6242 * @data: a user data pointer that is passed to callback
6244 * This function can be used to iterate over all the live objects in the heap:
6245 * for each object, @callback is invoked, providing info about the object's
6246 * location in memory, its class, its size and the objects it references.
6247 * The object references may be buffered, so the callback may be invoked
6248 * multiple times for the same object: in all but the first call, the size
6249 * argument will be zero.
6250 * Note that this function can be only called in the #MONO_GC_EVENT_PRE_START_WORLD
6251 * profiler event handler.
6253 * Returns: a non-zero value if the GC doesn't support heap walking
6256 mono_gc_walk_heap (int flags, MonoGCReferences callback, void *data)
6258 HeapWalkInfo hwi;
6259 LOSObject *bigobj;
6261 hwi.flags = flags;
6262 hwi.callback = callback;
6263 hwi.data = data;
6265 clear_nursery_fragments (nursery_next);
6266 mono_sgen_scan_area_with_callback (nursery_section->data, nursery_section->end_data, walk_references, &hwi);
6268 major_collector.iterate_objects (TRUE, TRUE, walk_references, &hwi);
6270 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
6271 walk_references (bigobj->data, bigobj->size, &hwi);
6272 return 0;
6275 void
6276 mono_gc_collect (int generation)
6278 LOCK_GC;
6279 if (generation > 1)
6280 generation = 1;
6281 mono_profiler_gc_event (MONO_GC_EVENT_START, generation);
6282 stop_world (generation);
6283 if (generation == 0) {
6284 collect_nursery (0);
6285 } else {
6286 major_collection ("user request");
6288 restart_world (generation);
6289 mono_profiler_gc_event (MONO_GC_EVENT_END, generation);
6290 UNLOCK_GC;
6294 mono_gc_max_generation (void)
6296 return 1;
6300 mono_gc_collection_count (int generation)
6302 if (generation == 0)
6303 return num_minor_gcs;
6304 return num_major_gcs;
6307 int64_t
6308 mono_gc_get_used_size (void)
6310 gint64 tot = 0;
6311 LOCK_GC;
6312 tot = los_memory_usage;
6313 tot += nursery_section->next_data - nursery_section->data;
6314 tot += major_collector.get_used_size ();
6315 /* FIXME: account for pinned objects */
6316 UNLOCK_GC;
6317 return tot;
6320 int64_t
6321 mono_gc_get_heap_size (void)
6323 return total_alloc;
6326 void
6327 mono_gc_disable (void)
6329 LOCK_GC;
6330 gc_disabled++;
6331 UNLOCK_GC;
6334 void
6335 mono_gc_enable (void)
6337 LOCK_GC;
6338 gc_disabled--;
6339 UNLOCK_GC;
6343 mono_gc_get_los_limit (void)
6345 return MAX_SMALL_OBJ_SIZE;
6348 gboolean
6349 mono_object_is_alive (MonoObject* o)
6351 return TRUE;
6355 mono_gc_get_generation (MonoObject *obj)
6357 if (ptr_in_nursery (obj))
6358 return 0;
6359 return 1;
6362 void
6363 mono_gc_enable_events (void)
6367 void
6368 mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
6370 LOCK_GC;
6371 mono_gc_register_disappearing_link (obj, link_addr, track);
6372 UNLOCK_GC;
6375 void
6376 mono_gc_weak_link_remove (void **link_addr)
6378 LOCK_GC;
6379 mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
6380 UNLOCK_GC;
6383 MonoObject*
6384 mono_gc_weak_link_get (void **link_addr)
6386 if (!*link_addr)
6387 return NULL;
6388 return (MonoObject*) REVEAL_POINTER (*link_addr);
6391 gboolean
6392 mono_gc_ephemeron_array_add (MonoObject *obj)
6394 EphemeronLinkNode *node;
6396 LOCK_GC;
6398 node = mono_sgen_alloc_internal (INTERNAL_MEM_EPHEMERON_LINK);
6399 if (!node) {
6400 UNLOCK_GC;
6401 return FALSE;
6403 node->array = (char*)obj;
6404 node->next = ephemeron_list;
6405 ephemeron_list = node;
6407 DEBUG (5, fprintf (gc_debug_file, "Registered ephemeron array %p\n", obj));
6409 UNLOCK_GC;
6410 return TRUE;
6413 void*
6414 mono_gc_make_descr_from_bitmap (gsize *bitmap, int numbits)
6416 if (numbits == 0) {
6417 return (void*)MAKE_ROOT_DESC (ROOT_DESC_BITMAP, 0);
6418 } else if (numbits < ((sizeof (*bitmap) * 8) - ROOT_DESC_TYPE_SHIFT)) {
6419 return (void*)MAKE_ROOT_DESC (ROOT_DESC_BITMAP, bitmap [0]);
6420 } else {
6421 mword complex = alloc_complex_descriptor (bitmap, numbits);
6422 return (void*)MAKE_ROOT_DESC (ROOT_DESC_COMPLEX, complex);
6426 static void *all_ref_root_descrs [32];
6428 void*
6429 mono_gc_make_root_descr_all_refs (int numbits)
6431 gsize *gc_bitmap;
6432 void *descr;
6434 if (numbits < 32 && all_ref_root_descrs [numbits])
6435 return all_ref_root_descrs [numbits];
6437 gc_bitmap = g_malloc0 (ALIGN_TO (numbits, 8) + 1);
6438 memset (gc_bitmap, 0xff, numbits / 8);
6439 if (numbits % 8)
6440 gc_bitmap [numbits / 8] = (1 << (numbits % 8)) - 1;
6441 descr = mono_gc_make_descr_from_bitmap (gc_bitmap, numbits);
6442 g_free (gc_bitmap);
6444 if (numbits < 32)
6445 all_ref_root_descrs [numbits] = descr;
6447 return descr;
6450 void*
6451 mono_gc_make_root_descr_user (MonoGCRootMarkFunc marker)
6453 void *descr;
6455 g_assert (user_descriptors_next < MAX_USER_DESCRIPTORS);
6456 descr = (void*)MAKE_ROOT_DESC (ROOT_DESC_USER, (mword)user_descriptors_next);
6457 user_descriptors [user_descriptors_next ++] = marker;
6459 return descr;
6462 void*
6463 mono_gc_alloc_fixed (size_t size, void *descr)
6465 /* FIXME: do a single allocation */
6466 void *res = calloc (1, size);
6467 if (!res)
6468 return NULL;
6469 if (!mono_gc_register_root (res, size, descr)) {
6470 free (res);
6471 res = NULL;
6473 return res;
6476 void
6477 mono_gc_free_fixed (void* addr)
6479 mono_gc_deregister_root (addr);
6480 free (addr);
6483 void*
6484 mono_gc_invoke_with_gc_lock (MonoGCLockedCallbackFunc func, void *data)
6486 void *result;
6487 LOCK_INTERRUPTION;
6488 result = func (data);
6489 UNLOCK_INTERRUPTION;
6490 return result;
6493 gboolean
6494 mono_gc_is_gc_thread (void)
6496 gboolean result;
6497 LOCK_GC;
6498 result = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ()) != NULL;
6499 UNLOCK_GC;
6500 return result;
6503 void
6504 mono_gc_base_init (void)
6506 char *env;
6507 char **opts, **ptr;
6508 char *major_collector_opt = NULL;
6509 struct sigaction sinfo;
6511 #ifdef PLATFORM_ANDROID
6512 g_assert_not_reached ();
6513 #endif
6515 /* the gc_initialized guard seems to imply this method is
6516 idempotent, but LOCK_INIT(gc_mutex) might not be. It's
6517 defined in sgen-gc.h as nothing, so there's no danger at
6518 present. */
6519 LOCK_INIT (gc_mutex);
6520 LOCK_GC;
6521 if (gc_initialized) {
6522 UNLOCK_GC;
6523 return;
6525 pagesize = mono_pagesize ();
6526 gc_debug_file = stderr;
6528 LOCK_INIT (interruption_mutex);
6529 LOCK_INIT (global_remset_mutex);
6531 if ((env = getenv ("MONO_GC_PARAMS"))) {
6532 opts = g_strsplit (env, ",", -1);
6533 for (ptr = opts; *ptr; ++ptr) {
6534 char *opt = *ptr;
6535 if (g_str_has_prefix (opt, "major=")) {
6536 opt = strchr (opt, '=') + 1;
6537 major_collector_opt = g_strdup (opt);
6540 } else {
6541 opts = NULL;
6544 init_stats ();
6545 mono_sgen_init_internal_allocator ();
6547 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_FRAGMENT, sizeof (Fragment));
6548 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_SECTION, SGEN_SIZEOF_GC_MEM_SECTION);
6549 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_FINALIZE_ENTRY, sizeof (FinalizeEntry));
6550 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_DISLINK, sizeof (DisappearingLink));
6551 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_ROOT_RECORD, sizeof (RootRecord));
6552 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_GRAY_QUEUE, sizeof (GrayQueueSection));
6553 g_assert (sizeof (GenericStoreRememberedSet) == sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
6554 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_STORE_REMSET, sizeof (GenericStoreRememberedSet));
6555 mono_sgen_register_fixed_internal_mem_type (INTERNAL_MEM_EPHEMERON_LINK, sizeof (EphemeronLinkNode));
6557 if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep")) {
6558 mono_sgen_marksweep_init (&major_collector);
6559 } else if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep-fixed")) {
6560 mono_sgen_marksweep_fixed_init (&major_collector);
6561 } else if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep-par")) {
6562 mono_sgen_marksweep_par_init (&major_collector);
6563 workers_init (mono_cpu_count ());
6564 } else if (!major_collector_opt || !strcmp (major_collector_opt, "marksweep-fixed-par")) {
6565 mono_sgen_marksweep_fixed_par_init (&major_collector);
6566 workers_init (mono_cpu_count ());
6567 } else if (!strcmp (major_collector_opt, "copying")) {
6568 mono_sgen_copying_init (&major_collector);
6569 } else {
6570 fprintf (stderr, "Unknown major collector `%s'.\n", major_collector_opt);
6571 exit (1);
6574 #ifdef SGEN_HAVE_CARDTABLE
6575 use_cardtable = major_collector.supports_cardtable;
6576 #else
6577 use_cardtable = FALSE;
6578 #endif
6580 if (opts) {
6581 for (ptr = opts; *ptr; ++ptr) {
6582 char *opt = *ptr;
6583 if (g_str_has_prefix (opt, "major="))
6584 continue;
6585 if (g_str_has_prefix (opt, "wbarrier=")) {
6586 opt = strchr (opt, '=') + 1;
6587 if (strcmp (opt, "remset") == 0) {
6588 use_cardtable = FALSE;
6589 } else if (strcmp (opt, "cardtable") == 0) {
6590 if (!use_cardtable) {
6591 if (major_collector.supports_cardtable)
6592 fprintf (stderr, "The cardtable write barrier is not supported on this platform.\n");
6593 else
6594 fprintf (stderr, "The major collector does not support the cardtable write barrier.\n");
6595 exit (1);
6598 continue;
6600 #ifdef USER_CONFIG
6601 if (g_str_has_prefix (opt, "nursery-size=")) {
6602 long val;
6603 opt = strchr (opt, '=') + 1;
6604 if (*opt && mono_gc_parse_environment_string_extract_number (opt, &val)) {
6605 default_nursery_size = val;
6606 #ifdef SGEN_ALIGN_NURSERY
6607 if ((val & (val - 1))) {
6608 fprintf (stderr, "The nursery size must be a power of two.\n");
6609 exit (1);
6612 default_nursery_bits = 0;
6613 while (1 << (++ default_nursery_bits) != default_nursery_size)
6615 #endif
6616 } else {
6617 fprintf (stderr, "nursery-size must be an integer.\n");
6618 exit (1);
6620 continue;
6622 #endif
6623 if (!(major_collector.handle_gc_param && major_collector.handle_gc_param (opt))) {
6624 fprintf (stderr, "MONO_GC_PARAMS must be a comma-delimited list of one or more of the following:\n");
6625 fprintf (stderr, " nursery-size=N (where N is an integer, possibly with a k, m or a g suffix)\n");
6626 fprintf (stderr, " major=COLLECTOR (where COLLECTOR is `marksweep', `marksweep-par' or `copying')\n");
6627 fprintf (stderr, " wbarrier=WBARRIER (where WBARRIER is `remset' or `cardtable')\n");
6628 if (major_collector.print_gc_param_usage)
6629 major_collector.print_gc_param_usage ();
6630 exit (1);
6633 g_strfreev (opts);
6636 if (major_collector_opt)
6637 g_free (major_collector_opt);
6639 nursery_size = DEFAULT_NURSERY_SIZE;
6640 minor_collection_allowance = MIN_MINOR_COLLECTION_ALLOWANCE;
6642 alloc_nursery ();
6644 if ((env = getenv ("MONO_GC_DEBUG"))) {
6645 opts = g_strsplit (env, ",", -1);
6646 for (ptr = opts; ptr && *ptr; ptr ++) {
6647 char *opt = *ptr;
6648 if (opt [0] >= '0' && opt [0] <= '9') {
6649 gc_debug_level = atoi (opt);
6650 opt++;
6651 if (opt [0] == ':')
6652 opt++;
6653 if (opt [0]) {
6654 char *rf = g_strdup_printf ("%s.%d", opt, getpid ());
6655 gc_debug_file = fopen (rf, "wb");
6656 if (!gc_debug_file)
6657 gc_debug_file = stderr;
6658 g_free (rf);
6660 } else if (!strcmp (opt, "collect-before-allocs")) {
6661 collect_before_allocs = TRUE;
6662 } else if (!strcmp (opt, "check-at-minor-collections")) {
6663 consistency_check_at_minor_collection = TRUE;
6664 nursery_clear_policy = CLEAR_AT_GC;
6665 } else if (!strcmp (opt, "xdomain-checks")) {
6666 xdomain_checks = TRUE;
6667 } else if (!strcmp (opt, "clear-at-gc")) {
6668 nursery_clear_policy = CLEAR_AT_GC;
6669 } else if (!strcmp (opt, "conservative-stack-mark")) {
6670 conservative_stack_mark = TRUE;
6671 } else if (!strcmp (opt, "check-scan-starts")) {
6672 do_scan_starts_check = TRUE;
6673 } else if (g_str_has_prefix (opt, "heap-dump=")) {
6674 char *filename = strchr (opt, '=') + 1;
6675 nursery_clear_policy = CLEAR_AT_GC;
6676 heap_dump_file = fopen (filename, "w");
6677 if (heap_dump_file)
6678 fprintf (heap_dump_file, "<sgen-dump>\n");
6679 #ifdef SGEN_BINARY_PROTOCOL
6680 } else if (g_str_has_prefix (opt, "binary-protocol=")) {
6681 char *filename = strchr (opt, '=') + 1;
6682 binary_protocol_file = fopen (filename, "w");
6683 #endif
6684 } else {
6685 fprintf (stderr, "Invalid format for the MONO_GC_DEBUG env variable: '%s'\n", env);
6686 fprintf (stderr, "The format is: MONO_GC_DEBUG=[l[:filename]|<option>]+ where l is a debug level 0-9.\n");
6687 fprintf (stderr, "Valid options are: collect-before-allocs, check-at-minor-collections, xdomain-checks, clear-at-gc.\n");
6688 exit (1);
6691 g_strfreev (opts);
6694 suspend_ack_semaphore_ptr = &suspend_ack_semaphore;
6695 MONO_SEM_INIT (&suspend_ack_semaphore, 0);
6697 sigfillset (&sinfo.sa_mask);
6698 sinfo.sa_flags = SA_RESTART | SA_SIGINFO;
6699 sinfo.sa_sigaction = suspend_handler;
6700 if (sigaction (suspend_signal_num, &sinfo, NULL) != 0) {
6701 g_error ("failed sigaction");
6704 sinfo.sa_handler = restart_handler;
6705 if (sigaction (restart_signal_num, &sinfo, NULL) != 0) {
6706 g_error ("failed sigaction");
6709 sigfillset (&suspend_signal_mask);
6710 sigdelset (&suspend_signal_mask, restart_signal_num);
6712 global_remset = alloc_remset (1024, NULL);
6713 global_remset->next = NULL;
6715 pthread_key_create (&remembered_set_key, unregister_thread);
6717 #ifndef HAVE_KW_THREAD
6718 pthread_key_create (&thread_info_key, NULL);
6719 #endif
6721 if (use_cardtable)
6722 card_table_init ();
6724 gc_initialized = TRUE;
6725 UNLOCK_GC;
6726 mono_gc_register_thread (&sinfo);
6730 mono_gc_get_suspend_signal (void)
6732 return suspend_signal_num;
6735 enum {
6736 ATYPE_NORMAL,
6737 ATYPE_VECTOR,
6738 ATYPE_SMALL,
6739 ATYPE_NUM
6742 #ifdef HAVE_KW_THREAD
6743 #define EMIT_TLS_ACCESS(mb,dummy,offset) do { \
6744 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
6745 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
6746 mono_mb_emit_i4 ((mb), (offset)); \
6747 } while (0)
6748 #else
6749 #define EMIT_TLS_ACCESS(mb,member,dummy) do { \
6750 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
6751 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
6752 mono_mb_emit_i4 ((mb), thread_info_key); \
6753 mono_mb_emit_icon ((mb), G_STRUCT_OFFSET (SgenThreadInfo, member)); \
6754 mono_mb_emit_byte ((mb), CEE_ADD); \
6755 mono_mb_emit_byte ((mb), CEE_LDIND_I); \
6756 } while (0)
6757 #endif
6759 #ifdef MANAGED_ALLOCATION
6760 /* FIXME: Do this in the JIT, where specialized allocation sequences can be created
6761 * for each class. This is currently not easy to do, as it is hard to generate basic
6762 * blocks + branches, but it is easy with the linear IL codebase.
6764 * For this to work we'd need to solve the TLAB race, first. Now we
6765 * require the allocator to be in a few known methods to make sure
6766 * that they are executed atomically via the restart mechanism.
6768 static MonoMethod*
6769 create_allocator (int atype)
6771 int p_var, size_var;
6772 guint32 slowpath_branch, max_size_branch;
6773 MonoMethodBuilder *mb;
6774 MonoMethod *res;
6775 MonoMethodSignature *csig;
6776 static gboolean registered = FALSE;
6777 int tlab_next_addr_var, new_next_var;
6778 int num_params, i;
6779 const char *name = NULL;
6780 AllocatorWrapperInfo *info;
6782 #ifdef HAVE_KW_THREAD
6783 int tlab_next_addr_offset = -1;
6784 int tlab_temp_end_offset = -1;
6786 MONO_THREAD_VAR_OFFSET (tlab_next_addr, tlab_next_addr_offset);
6787 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
6789 g_assert (tlab_next_addr_offset != -1);
6790 g_assert (tlab_temp_end_offset != -1);
6791 #endif
6793 if (!registered) {
6794 mono_register_jit_icall (mono_gc_alloc_obj, "mono_gc_alloc_obj", mono_create_icall_signature ("object ptr int"), FALSE);
6795 mono_register_jit_icall (mono_gc_alloc_vector, "mono_gc_alloc_vector", mono_create_icall_signature ("object ptr int int"), FALSE);
6796 registered = TRUE;
6799 if (atype == ATYPE_SMALL) {
6800 num_params = 1;
6801 name = "AllocSmall";
6802 } else if (atype == ATYPE_NORMAL) {
6803 num_params = 1;
6804 name = "Alloc";
6805 } else if (atype == ATYPE_VECTOR) {
6806 num_params = 2;
6807 name = "AllocVector";
6808 } else {
6809 g_assert_not_reached ();
6812 csig = mono_metadata_signature_alloc (mono_defaults.corlib, num_params);
6813 csig->ret = &mono_defaults.object_class->byval_arg;
6814 for (i = 0; i < num_params; ++i)
6815 csig->params [i] = &mono_defaults.int_class->byval_arg;
6817 mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_ALLOC);
6818 size_var = mono_mb_add_local (mb, &mono_defaults.int32_class->byval_arg);
6819 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
6820 /* size = vtable->klass->instance_size; */
6821 mono_mb_emit_ldarg (mb, 0);
6822 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
6823 mono_mb_emit_byte (mb, CEE_ADD);
6824 mono_mb_emit_byte (mb, CEE_LDIND_I);
6825 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, instance_size));
6826 mono_mb_emit_byte (mb, CEE_ADD);
6827 /* FIXME: assert instance_size stays a 4 byte integer */
6828 mono_mb_emit_byte (mb, CEE_LDIND_U4);
6829 mono_mb_emit_stloc (mb, size_var);
6830 } else if (atype == ATYPE_VECTOR) {
6831 MonoExceptionClause *clause;
6832 int pos, pos_leave;
6833 MonoClass *oom_exc_class;
6834 MonoMethod *ctor;
6836 /* n > MONO_ARRAY_MAX_INDEX -> OverflowException */
6837 mono_mb_emit_ldarg (mb, 1);
6838 mono_mb_emit_icon (mb, MONO_ARRAY_MAX_INDEX);
6839 pos = mono_mb_emit_short_branch (mb, CEE_BLE_UN_S);
6840 mono_mb_emit_exception (mb, "OverflowException", NULL);
6841 mono_mb_patch_short_branch (mb, pos);
6843 clause = mono_image_alloc0 (mono_defaults.corlib, sizeof (MonoExceptionClause));
6844 clause->try_offset = mono_mb_get_label (mb);
6846 /* vtable->klass->sizes.element_size */
6847 mono_mb_emit_ldarg (mb, 0);
6848 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
6849 mono_mb_emit_byte (mb, CEE_ADD);
6850 mono_mb_emit_byte (mb, CEE_LDIND_I);
6851 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, sizes.element_size));
6852 mono_mb_emit_byte (mb, CEE_ADD);
6853 mono_mb_emit_byte (mb, CEE_LDIND_U4);
6855 /* * n */
6856 mono_mb_emit_ldarg (mb, 1);
6857 mono_mb_emit_byte (mb, CEE_MUL_OVF_UN);
6858 /* + sizeof (MonoArray) */
6859 mono_mb_emit_icon (mb, sizeof (MonoArray));
6860 mono_mb_emit_byte (mb, CEE_ADD_OVF_UN);
6861 mono_mb_emit_stloc (mb, size_var);
6863 pos_leave = mono_mb_emit_branch (mb, CEE_LEAVE);
6865 /* catch */
6866 clause->flags = MONO_EXCEPTION_CLAUSE_NONE;
6867 clause->try_len = mono_mb_get_pos (mb) - clause->try_offset;
6868 clause->data.catch_class = mono_class_from_name (mono_defaults.corlib,
6869 "System", "OverflowException");
6870 g_assert (clause->data.catch_class);
6871 clause->handler_offset = mono_mb_get_label (mb);
6873 oom_exc_class = mono_class_from_name (mono_defaults.corlib,
6874 "System", "OutOfMemoryException");
6875 g_assert (oom_exc_class);
6876 ctor = mono_class_get_method_from_name (oom_exc_class, ".ctor", 0);
6877 g_assert (ctor);
6879 mono_mb_emit_byte (mb, CEE_POP);
6880 mono_mb_emit_op (mb, CEE_NEWOBJ, ctor);
6881 mono_mb_emit_byte (mb, CEE_THROW);
6883 clause->handler_len = mono_mb_get_pos (mb) - clause->handler_offset;
6884 mono_mb_set_clauses (mb, 1, clause);
6885 mono_mb_patch_branch (mb, pos_leave);
6886 /* end catch */
6887 } else {
6888 g_assert_not_reached ();
6891 /* size += ALLOC_ALIGN - 1; */
6892 mono_mb_emit_ldloc (mb, size_var);
6893 mono_mb_emit_icon (mb, ALLOC_ALIGN - 1);
6894 mono_mb_emit_byte (mb, CEE_ADD);
6895 /* size &= ~(ALLOC_ALIGN - 1); */
6896 mono_mb_emit_icon (mb, ~(ALLOC_ALIGN - 1));
6897 mono_mb_emit_byte (mb, CEE_AND);
6898 mono_mb_emit_stloc (mb, size_var);
6900 /* if (size > MAX_SMALL_OBJ_SIZE) goto slowpath */
6901 if (atype != ATYPE_SMALL) {
6902 mono_mb_emit_ldloc (mb, size_var);
6903 mono_mb_emit_icon (mb, MAX_SMALL_OBJ_SIZE);
6904 max_size_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BGT_S);
6908 * We need to modify tlab_next, but the JIT only supports reading, so we read
6909 * another tls var holding its address instead.
6912 /* tlab_next_addr (local) = tlab_next_addr (TLS var) */
6913 tlab_next_addr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
6914 EMIT_TLS_ACCESS (mb, tlab_next_addr, tlab_next_addr_offset);
6915 mono_mb_emit_stloc (mb, tlab_next_addr_var);
6917 /* p = (void**)tlab_next; */
6918 p_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
6919 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
6920 mono_mb_emit_byte (mb, CEE_LDIND_I);
6921 mono_mb_emit_stloc (mb, p_var);
6923 /* new_next = (char*)p + size; */
6924 new_next_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
6925 mono_mb_emit_ldloc (mb, p_var);
6926 mono_mb_emit_ldloc (mb, size_var);
6927 mono_mb_emit_byte (mb, CEE_CONV_I);
6928 mono_mb_emit_byte (mb, CEE_ADD);
6929 mono_mb_emit_stloc (mb, new_next_var);
6931 /* tlab_next = new_next */
6932 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
6933 mono_mb_emit_ldloc (mb, new_next_var);
6934 mono_mb_emit_byte (mb, CEE_STIND_I);
6936 /* if (G_LIKELY (new_next < tlab_temp_end)) */
6937 mono_mb_emit_ldloc (mb, new_next_var);
6938 EMIT_TLS_ACCESS (mb, tlab_temp_end, tlab_temp_end_offset);
6939 slowpath_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BLT_UN_S);
6941 /* Slowpath */
6942 if (atype != ATYPE_SMALL)
6943 mono_mb_patch_short_branch (mb, max_size_branch);
6945 mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
6946 mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN);
6948 /* FIXME: mono_gc_alloc_obj takes a 'size_t' as an argument, not an int32 */
6949 mono_mb_emit_ldarg (mb, 0);
6950 mono_mb_emit_ldloc (mb, size_var);
6951 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
6952 mono_mb_emit_icall (mb, mono_gc_alloc_obj);
6953 } else if (atype == ATYPE_VECTOR) {
6954 mono_mb_emit_ldarg (mb, 1);
6955 mono_mb_emit_icall (mb, mono_gc_alloc_vector);
6956 } else {
6957 g_assert_not_reached ();
6959 mono_mb_emit_byte (mb, CEE_RET);
6961 /* Fastpath */
6962 mono_mb_patch_short_branch (mb, slowpath_branch);
6964 /* FIXME: Memory barrier */
6966 /* *p = vtable; */
6967 mono_mb_emit_ldloc (mb, p_var);
6968 mono_mb_emit_ldarg (mb, 0);
6969 mono_mb_emit_byte (mb, CEE_STIND_I);
6971 if (atype == ATYPE_VECTOR) {
6972 /* arr->max_length = max_length; */
6973 mono_mb_emit_ldloc (mb, p_var);
6974 mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoArray, max_length));
6975 mono_mb_emit_ldarg (mb, 1);
6976 mono_mb_emit_byte (mb, CEE_STIND_I);
6979 /* return p */
6980 mono_mb_emit_ldloc (mb, p_var);
6981 mono_mb_emit_byte (mb, CEE_RET);
6983 res = mono_mb_create_method (mb, csig, 8);
6984 mono_mb_free (mb);
6985 mono_method_get_header (res)->init_locals = FALSE;
6987 info = mono_image_alloc0 (mono_defaults.corlib, sizeof (AllocatorWrapperInfo));
6988 info->gc_name = "sgen";
6989 info->alloc_type = atype;
6990 mono_marshal_set_wrapper_info (res, info);
6992 return res;
6994 #endif
6996 const char *
6997 mono_gc_get_gc_name (void)
6999 return "sgen";
7002 static MonoMethod* alloc_method_cache [ATYPE_NUM];
7003 static MonoMethod *write_barrier_method;
7005 static gboolean
7006 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip)
7008 MonoJitInfo *ji;
7009 MonoMethod *method;
7010 int i;
7012 if (!mono_thread_internal_current ())
7013 /* Happens during thread attach */
7014 return FALSE;
7016 if (!ip || !domain)
7017 return FALSE;
7018 ji = mono_jit_info_table_find (domain, ip);
7019 if (!ji)
7020 return FALSE;
7021 method = ji->method;
7023 if (method == write_barrier_method)
7024 return TRUE;
7025 for (i = 0; i < ATYPE_NUM; ++i)
7026 if (method == alloc_method_cache [i])
7027 return TRUE;
7028 return FALSE;
7032 * Generate an allocator method implementing the fast path of mono_gc_alloc_obj ().
7033 * The signature of the called method is:
7034 * object allocate (MonoVTable *vtable)
7036 MonoMethod*
7037 mono_gc_get_managed_allocator (MonoVTable *vtable, gboolean for_box)
7039 #ifdef MANAGED_ALLOCATION
7040 MonoClass *klass = vtable->klass;
7042 #ifdef HAVE_KW_THREAD
7043 int tlab_next_offset = -1;
7044 int tlab_temp_end_offset = -1;
7045 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7046 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7048 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7049 return NULL;
7050 #endif
7052 if (!mono_runtime_has_tls_get ())
7053 return NULL;
7054 if (klass->instance_size > tlab_size)
7055 return NULL;
7056 if (klass->has_finalize || klass->marshalbyref || (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS))
7057 return NULL;
7058 if (klass->rank)
7059 return NULL;
7060 if (klass->byval_arg.type == MONO_TYPE_STRING)
7061 return NULL;
7062 if (collect_before_allocs)
7063 return NULL;
7065 if (ALIGN_TO (klass->instance_size, ALLOC_ALIGN) < MAX_SMALL_OBJ_SIZE)
7066 return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL);
7067 else
7068 return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL);
7069 #else
7070 return NULL;
7071 #endif
7074 MonoMethod*
7075 mono_gc_get_managed_array_allocator (MonoVTable *vtable, int rank)
7077 #ifdef MANAGED_ALLOCATION
7078 MonoClass *klass = vtable->klass;
7080 #ifdef HAVE_KW_THREAD
7081 int tlab_next_offset = -1;
7082 int tlab_temp_end_offset = -1;
7083 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7084 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7086 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7087 return NULL;
7088 #endif
7090 if (rank != 1)
7091 return NULL;
7092 if (!mono_runtime_has_tls_get ())
7093 return NULL;
7094 if (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS)
7095 return NULL;
7096 if (collect_before_allocs)
7097 return NULL;
7098 g_assert (!klass->has_finalize && !klass->marshalbyref);
7100 return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR);
7101 #else
7102 return NULL;
7103 #endif
7106 MonoMethod*
7107 mono_gc_get_managed_allocator_by_type (int atype)
7109 #ifdef MANAGED_ALLOCATION
7110 MonoMethod *res;
7112 if (!mono_runtime_has_tls_get ())
7113 return NULL;
7115 mono_loader_lock ();
7116 res = alloc_method_cache [atype];
7117 if (!res)
7118 res = alloc_method_cache [atype] = create_allocator (atype);
7119 mono_loader_unlock ();
7120 return res;
7121 #else
7122 return NULL;
7123 #endif
7126 guint32
7127 mono_gc_get_managed_allocator_types (void)
7129 return ATYPE_NUM;
7133 MonoMethod*
7134 mono_gc_get_write_barrier (void)
7136 MonoMethod *res;
7137 MonoMethodBuilder *mb;
7138 MonoMethodSignature *sig;
7139 #ifdef MANAGED_WBARRIER
7140 int label_no_wb_1, label_no_wb_2, label_no_wb_3, label_no_wb_4, label_need_wb, label_slow_path;
7141 #ifndef SGEN_ALIGN_NURSERY
7142 int label_continue_1, label_continue_2, label_no_wb_5;
7143 int dereferenced_var;
7144 #endif
7145 int buffer_var, buffer_index_var, dummy_var;
7147 #ifdef HAVE_KW_THREAD
7148 int stack_end_offset = -1, store_remset_buffer_offset = -1;
7149 int store_remset_buffer_index_offset = -1, store_remset_buffer_index_addr_offset = -1;
7151 MONO_THREAD_VAR_OFFSET (stack_end, stack_end_offset);
7152 g_assert (stack_end_offset != -1);
7153 MONO_THREAD_VAR_OFFSET (store_remset_buffer, store_remset_buffer_offset);
7154 g_assert (store_remset_buffer_offset != -1);
7155 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index, store_remset_buffer_index_offset);
7156 g_assert (store_remset_buffer_index_offset != -1);
7157 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7158 g_assert (store_remset_buffer_index_addr_offset != -1);
7159 #endif
7160 #endif
7162 g_assert (!use_cardtable);
7164 // FIXME: Maybe create a separate version for ctors (the branch would be
7165 // correctly predicted more times)
7166 if (write_barrier_method)
7167 return write_barrier_method;
7169 /* Create the IL version of mono_gc_barrier_generic_store () */
7170 sig = mono_metadata_signature_alloc (mono_defaults.corlib, 1);
7171 sig->ret = &mono_defaults.void_class->byval_arg;
7172 sig->params [0] = &mono_defaults.int_class->byval_arg;
7174 mb = mono_mb_new (mono_defaults.object_class, "wbarrier", MONO_WRAPPER_WRITE_BARRIER);
7176 #ifdef MANAGED_WBARRIER
7177 if (mono_runtime_has_tls_get ()) {
7178 #ifdef SGEN_ALIGN_NURSERY
7179 // if (ptr_in_nursery (ptr)) return;
7181 * Masking out the bits might be faster, but we would have to use 64 bit
7182 * immediates, which might be slower.
7184 mono_mb_emit_ldarg (mb, 0);
7185 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7186 mono_mb_emit_byte (mb, CEE_SHR_UN);
7187 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7188 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BEQ);
7190 // if (!ptr_in_nursery (*ptr)) return;
7191 mono_mb_emit_ldarg (mb, 0);
7192 mono_mb_emit_byte (mb, CEE_LDIND_I);
7193 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7194 mono_mb_emit_byte (mb, CEE_SHR_UN);
7195 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7196 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BNE_UN);
7197 #else
7199 // if (ptr < (nursery_start)) goto continue;
7200 mono_mb_emit_ldarg (mb, 0);
7201 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7202 label_continue_1 = mono_mb_emit_branch (mb, CEE_BLT);
7204 // if (ptr >= nursery_real_end)) goto continue;
7205 mono_mb_emit_ldarg (mb, 0);
7206 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7207 label_continue_2 = mono_mb_emit_branch (mb, CEE_BGE);
7209 // Otherwise return
7210 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BR);
7212 // continue:
7213 mono_mb_patch_branch (mb, label_continue_1);
7214 mono_mb_patch_branch (mb, label_continue_2);
7216 // Dereference and store in local var
7217 dereferenced_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7218 mono_mb_emit_ldarg (mb, 0);
7219 mono_mb_emit_byte (mb, CEE_LDIND_I);
7220 mono_mb_emit_stloc (mb, dereferenced_var);
7222 // if (*ptr < nursery_start) return;
7223 mono_mb_emit_ldloc (mb, dereferenced_var);
7224 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7225 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BLT);
7227 // if (*ptr >= nursery_end) return;
7228 mono_mb_emit_ldloc (mb, dereferenced_var);
7229 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7230 label_no_wb_5 = mono_mb_emit_branch (mb, CEE_BGE);
7232 #endif
7233 // if (ptr >= stack_end) goto need_wb;
7234 mono_mb_emit_ldarg (mb, 0);
7235 EMIT_TLS_ACCESS (mb, stack_end, stack_end_offset);
7236 label_need_wb = mono_mb_emit_branch (mb, CEE_BGE_UN);
7238 // if (ptr >= stack_start) return;
7239 dummy_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7240 mono_mb_emit_ldarg (mb, 0);
7241 mono_mb_emit_ldloc_addr (mb, dummy_var);
7242 label_no_wb_3 = mono_mb_emit_branch (mb, CEE_BGE_UN);
7244 // need_wb:
7245 mono_mb_patch_branch (mb, label_need_wb);
7247 // buffer = STORE_REMSET_BUFFER;
7248 buffer_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7249 EMIT_TLS_ACCESS (mb, store_remset_buffer, store_remset_buffer_offset);
7250 mono_mb_emit_stloc (mb, buffer_var);
7252 // buffer_index = STORE_REMSET_BUFFER_INDEX;
7253 buffer_index_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7254 EMIT_TLS_ACCESS (mb, store_remset_buffer_index, store_remset_buffer_index_offset);
7255 mono_mb_emit_stloc (mb, buffer_index_var);
7257 // if (buffer [buffer_index] == ptr) return;
7258 mono_mb_emit_ldloc (mb, buffer_var);
7259 mono_mb_emit_ldloc (mb, buffer_index_var);
7260 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7261 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7262 mono_mb_emit_byte (mb, CEE_SHL);
7263 mono_mb_emit_byte (mb, CEE_ADD);
7264 mono_mb_emit_byte (mb, CEE_LDIND_I);
7265 mono_mb_emit_ldarg (mb, 0);
7266 label_no_wb_4 = mono_mb_emit_branch (mb, CEE_BEQ);
7268 // ++buffer_index;
7269 mono_mb_emit_ldloc (mb, buffer_index_var);
7270 mono_mb_emit_icon (mb, 1);
7271 mono_mb_emit_byte (mb, CEE_ADD);
7272 mono_mb_emit_stloc (mb, buffer_index_var);
7274 // if (buffer_index >= STORE_REMSET_BUFFER_SIZE) goto slow_path;
7275 mono_mb_emit_ldloc (mb, buffer_index_var);
7276 mono_mb_emit_icon (mb, STORE_REMSET_BUFFER_SIZE);
7277 label_slow_path = mono_mb_emit_branch (mb, CEE_BGE);
7279 // buffer [buffer_index] = ptr;
7280 mono_mb_emit_ldloc (mb, buffer_var);
7281 mono_mb_emit_ldloc (mb, buffer_index_var);
7282 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7283 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7284 mono_mb_emit_byte (mb, CEE_SHL);
7285 mono_mb_emit_byte (mb, CEE_ADD);
7286 mono_mb_emit_ldarg (mb, 0);
7287 mono_mb_emit_byte (mb, CEE_STIND_I);
7289 // STORE_REMSET_BUFFER_INDEX = buffer_index;
7290 EMIT_TLS_ACCESS (mb, store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7291 mono_mb_emit_ldloc (mb, buffer_index_var);
7292 mono_mb_emit_byte (mb, CEE_STIND_I);
7294 // return;
7295 mono_mb_patch_branch (mb, label_no_wb_1);
7296 mono_mb_patch_branch (mb, label_no_wb_2);
7297 mono_mb_patch_branch (mb, label_no_wb_3);
7298 mono_mb_patch_branch (mb, label_no_wb_4);
7299 #ifndef SGEN_ALIGN_NURSERY
7300 mono_mb_patch_branch (mb, label_no_wb_5);
7301 #endif
7302 mono_mb_emit_byte (mb, CEE_RET);
7304 // slow path
7305 mono_mb_patch_branch (mb, label_slow_path);
7307 #endif
7309 mono_mb_emit_ldarg (mb, 0);
7310 mono_mb_emit_icall (mb, mono_gc_wbarrier_generic_nostore);
7311 mono_mb_emit_byte (mb, CEE_RET);
7313 res = mono_mb_create_method (mb, sig, 16);
7314 mono_mb_free (mb);
7316 mono_loader_lock ();
7317 if (write_barrier_method) {
7318 /* Already created */
7319 mono_free_method (res);
7320 } else {
7321 /* double-checked locking */
7322 mono_memory_barrier ();
7323 write_barrier_method = res;
7325 mono_loader_unlock ();
7327 return write_barrier_method;
7330 char*
7331 mono_gc_get_description (void)
7333 return g_strdup ("sgen");
7336 void
7337 mono_gc_set_desktop_mode (void)
7341 gboolean
7342 mono_gc_is_moving (void)
7344 return TRUE;
7347 gboolean
7348 mono_gc_is_disabled (void)
7350 return FALSE;
7353 void
7354 mono_sgen_debug_printf (int level, const char *format, ...)
7356 va_list ap;
7358 if (level > gc_debug_level)
7359 return;
7361 va_start (ap, format);
7362 vfprintf (gc_debug_file, format, ap);
7363 va_end (ap);
7366 #endif /* HAVE_SGEN_GC */