2010-07-09 Mark Probst <mark.probst@gmail.com>
[mono-project/dkf.git] / mono / metadata / sgen-gc.c
blob59e3a223d5ea3e245d539f15d3ec3b2ae3273c12
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-archdep.h"
203 #include "metadata/mono-gc.h"
204 #include "metadata/method-builder.h"
205 #include "metadata/profiler-private.h"
206 #include "metadata/monitor.h"
207 #include "metadata/threadpool-internals.h"
208 #include "metadata/mempool-internals.h"
209 #include "metadata/marshal.h"
210 #include "utils/mono-mmap.h"
211 #include "utils/mono-time.h"
212 #include "utils/mono-semaphore.h"
213 #include "utils/mono-counters.h"
215 #include <mono/utils/memcheck.h>
217 #if defined(__MACH__)
218 #include "utils/mach-support.h"
219 #endif
221 #define OPDEF(a,b,c,d,e,f,g,h,i,j) \
222 a = i,
224 enum {
225 #include "mono/cil/opcode.def"
226 CEE_LAST
229 #undef OPDEF
232 * ######################################################################
233 * ######## Types and constants used by the GC.
234 * ######################################################################
237 static int gc_initialized = 0;
238 static int gc_debug_level = 0;
239 static FILE* gc_debug_file;
240 /* If set, do a minor collection before every allocation */
241 static gboolean collect_before_allocs = FALSE;
242 /* If set, do a heap consistency check before each minor collection */
243 static gboolean consistency_check_at_minor_collection = FALSE;
244 /* If set, check that there are no references to the domain left at domain unload */
245 static gboolean xdomain_checks = FALSE;
246 /* If not null, dump the heap after each collection into this file */
247 static FILE *heap_dump_file = NULL;
248 /* If set, mark stacks conservatively, even if precise marking is possible */
249 static gboolean conservative_stack_mark = TRUE;
250 /* If set, do a plausibility check on the scan_starts before and after
251 each collection */
252 static gboolean do_scan_starts_check = FALSE;
255 * Turning on heavy statistics will turn off the managed allocator and
256 * the managed write barrier.
258 //#define HEAVY_STATISTICS
260 #ifdef HEAVY_STATISTICS
261 #define HEAVY_STAT(x) x
262 #else
263 #define HEAVY_STAT(x)
264 #endif
266 #ifdef HEAVY_STATISTICS
267 static long long stat_objects_alloced = 0;
268 static long long stat_bytes_alloced = 0;
269 static long long stat_objects_alloced_degraded = 0;
270 static long long stat_bytes_alloced_degraded = 0;
271 static long long stat_bytes_alloced_los = 0;
273 static long long stat_copy_object_called_nursery = 0;
274 static long long stat_objects_copied_nursery = 0;
275 static long long stat_copy_object_called_major = 0;
276 static long long stat_objects_copied_major = 0;
278 static long long stat_scan_object_called_nursery = 0;
279 static long long stat_scan_object_called_major = 0;
281 static long long stat_nursery_copy_object_failed_from_space = 0;
282 static long long stat_nursery_copy_object_failed_forwarded = 0;
283 static long long stat_nursery_copy_object_failed_pinned = 0;
285 static long long stat_store_remsets = 0;
286 static long long stat_store_remsets_unique = 0;
287 static long long stat_saved_remsets_1 = 0;
288 static long long stat_saved_remsets_2 = 0;
289 static long long stat_global_remsets_added = 0;
290 static long long stat_global_remsets_readded = 0;
291 static long long stat_global_remsets_processed = 0;
292 static long long stat_global_remsets_discarded = 0;
294 static long long stat_wasted_fragments_used = 0;
295 static long long stat_wasted_fragments_bytes = 0;
297 static int stat_wbarrier_set_field = 0;
298 static int stat_wbarrier_set_arrayref = 0;
299 static int stat_wbarrier_arrayref_copy = 0;
300 static int stat_wbarrier_generic_store = 0;
301 static int stat_wbarrier_generic_store_remset = 0;
302 static int stat_wbarrier_set_root = 0;
303 static int stat_wbarrier_value_copy = 0;
304 static int stat_wbarrier_object_copy = 0;
305 #endif
307 static long long time_minor_pre_collection_fragment_clear = 0;
308 static long long time_minor_pinning = 0;
309 static long long time_minor_scan_remsets = 0;
310 static long long time_minor_scan_pinned = 0;
311 static long long time_minor_scan_registered_roots = 0;
312 static long long time_minor_scan_thread_data = 0;
313 static long long time_minor_finish_gray_stack = 0;
314 static long long time_minor_fragment_creation = 0;
316 static long long time_major_pre_collection_fragment_clear = 0;
317 static long long time_major_pinning = 0;
318 static long long time_major_scan_pinned = 0;
319 static long long time_major_scan_registered_roots = 0;
320 static long long time_major_scan_thread_data = 0;
321 static long long time_major_scan_alloc_pinned = 0;
322 static long long time_major_scan_finalized = 0;
323 static long long time_major_scan_big_objects = 0;
324 static long long time_major_finish_gray_stack = 0;
325 static long long time_major_free_bigobjs = 0;
326 static long long time_major_los_sweep = 0;
327 static long long time_major_sweep = 0;
328 static long long time_major_fragment_creation = 0;
330 static long long pinned_chunk_bytes_alloced = 0;
331 static long long large_internal_bytes_alloced = 0;
333 /* Keep in sync with internal_mem_names in dump_heap()! */
334 enum {
335 INTERNAL_MEM_PIN_QUEUE,
336 INTERNAL_MEM_FRAGMENT,
337 INTERNAL_MEM_SECTION,
338 INTERNAL_MEM_SCAN_STARTS,
339 INTERNAL_MEM_FIN_TABLE,
340 INTERNAL_MEM_FINALIZE_ENTRY,
341 INTERNAL_MEM_DISLINK_TABLE,
342 INTERNAL_MEM_DISLINK,
343 INTERNAL_MEM_ROOTS_TABLE,
344 INTERNAL_MEM_ROOT_RECORD,
345 INTERNAL_MEM_STATISTICS,
346 INTERNAL_MEM_REMSET,
347 INTERNAL_MEM_GRAY_QUEUE,
348 INTERNAL_MEM_STORE_REMSET,
349 INTERNAL_MEM_MS_TABLES,
350 INTERNAL_MEM_MS_BLOCK_INFO,
351 INTERNAL_MEM_EPHEMERON_LINK,
352 INTERNAL_MEM_MAX
355 static long small_internal_mem_bytes [INTERNAL_MEM_MAX];
358 void
359 mono_gc_flush_info (void)
361 fflush (gc_debug_file);
365 /* Define this to allow the user to change some of the constants by specifying
366 * their values in the MONO_GC_PARAMS environmental variable. See
367 * mono_gc_base_init for details. */
368 #define USER_CONFIG 1
370 #define TV_DECLARE(name) gint64 name
371 #define TV_GETTIME(tv) tv = mono_100ns_ticks ()
372 #define TV_ELAPSED(start,end) (int)((end-start) / 10)
373 #define TV_ELAPSED_MS(start,end) ((TV_ELAPSED((start),(end)) + 500) / 1000)
375 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
377 #define GC_BITS_PER_WORD (sizeof (mword) * 8)
379 enum {
380 MEMORY_ROLE_GEN0,
381 MEMORY_ROLE_GEN1,
382 MEMORY_ROLE_PINNED
385 typedef struct _Block Block;
386 struct _Block {
387 void *next;
388 unsigned char role;
391 /* each request from the OS ends up in a GCMemSection */
392 typedef struct _GCMemSection GCMemSection;
393 struct _GCMemSection {
394 Block block;
395 char *data;
396 mword size;
397 /* pointer where more data could be allocated if it fits */
398 char *next_data;
399 char *end_data;
401 * scan starts is an array of pointers to objects equally spaced in the allocation area
402 * They let use quickly find pinned objects from pinning pointers.
404 char **scan_starts;
405 /* in major collections indexes in the pin_queue for objects that pin this section */
406 int pin_queue_start;
407 int pin_queue_end;
408 unsigned short num_scan_start;
409 gboolean is_to_space;
412 #define SIZEOF_GC_MEM_SECTION ((sizeof (GCMemSection) + 7) & ~7)
414 /* Pinned objects are allocated in the LOS space if bigger than half a page
415 * or from freelists otherwise. We assume that pinned objects are relatively few
416 * and they have a slow dying speed (like interned strings, thread objects).
417 * As such they will be collected only at major collections.
418 * free lists are not global: when we need memory we allocate a PinnedChunk.
419 * Each pinned chunk is made of several pages, the first of wich is used
420 * internally for bookeeping (here think of a page as 4KB). The bookeeping
421 * includes the freelists vectors and info about the object size of each page
422 * in the pinned chunk. So, when needed, a free page is found in a pinned chunk,
423 * a size is assigned to it, the page is divided in the proper chunks and each
424 * chunk is added to the freelist. To not waste space, the remaining space in the
425 * first page is used as objects of size 16 or 32 (need to measure which are more
426 * common).
427 * We use this same structure to allocate memory used internally by the GC, so
428 * we never use malloc/free if we need to alloc during collection: the world is stopped
429 * and malloc/free will deadlock.
430 * When we want to iterate over pinned objects, we just scan a page at a time
431 * linearly according to the size of objects in the page: the next pointer used to link
432 * the items in the freelist uses the same word as the vtable. Since we keep freelists
433 * for each pinned chunk, if the word points outside the pinned chunk it means
434 * it is an object.
435 * We could avoid this expensive scanning in creative ways. We could have a policy
436 * of putting in the pinned space only objects we know about that have no struct fields
437 * with references and we can easily use a even expensive write barrier for them,
438 * since pointer writes on such objects should be rare.
439 * The best compromise is to just alloc interned strings and System.MonoType in them.
440 * It would be nice to allocate MonoThread in it, too: must check that we properly
441 * use write barriers so we don't have to do any expensive scanning of the whole pinned
442 * chunk list during minor collections. We can avoid it now because we alloc in it only
443 * reference-free objects.
445 #define PINNED_FIRST_SLOT_SIZE (sizeof (gpointer) * 4)
446 #define MAX_FREELIST_SIZE 8192
447 typedef struct _PinnedChunk PinnedChunk;
448 struct _PinnedChunk {
449 Block block;
450 int num_pages;
451 int *page_sizes; /* a 0 means the page is still unused */
452 void **free_list;
453 void *start_data;
454 void *data [1]; /* page sizes and free lists are stored here */
457 /* The method used to clear the nursery */
458 /* Clearing at nursery collections is the safest, but has bad interactions with caches.
459 * Clearing at TLAB creation is much faster, but more complex and it might expose hard
460 * to find bugs.
462 typedef enum {
463 CLEAR_AT_GC,
464 CLEAR_AT_TLAB_CREATION
465 } NurseryClearPolicy;
467 static NurseryClearPolicy nursery_clear_policy = CLEAR_AT_TLAB_CREATION;
470 * If this is set, the nursery is aligned to an address aligned to its size, ie.
471 * a 1MB nursery will be aligned to an address divisible by 1MB. This allows us to
472 * speed up ptr_in_nursery () checks which are very frequent. This requires the
473 * nursery size to be a compile time constant.
475 #define ALIGN_NURSERY 1
478 * The young generation is divided into fragments. This is because
479 * we can hand one fragments to a thread for lock-less fast alloc and
480 * because the young generation ends up fragmented anyway by pinned objects.
481 * Once a collection is done, a list of fragments is created. When doing
482 * thread local alloc we use smallish nurseries so we allow new threads to
483 * allocate memory from gen0 without triggering a collection. Threads that
484 * are found to allocate lots of memory are given bigger fragments. This
485 * should make the finalizer thread use little nursery memory after a while.
486 * We should start assigning threads very small fragments: if there are many
487 * threads the nursery will be full of reserved space that the threads may not
488 * use at all, slowing down allocation speed.
489 * Thread local allocation is done from areas of memory Hotspot calls Thread Local
490 * Allocation Buffers (TLABs).
492 typedef struct _Fragment Fragment;
494 struct _Fragment {
495 Fragment *next;
496 char *fragment_start;
497 char *fragment_limit; /* the current soft limit for allocation */
498 char *fragment_end;
501 /* the runtime can register areas of memory as roots: we keep two lists of roots,
502 * a pinned root set for conservatively scanned roots and a normal one for
503 * precisely scanned roots (currently implemented as a single list).
505 typedef struct _RootRecord RootRecord;
506 struct _RootRecord {
507 RootRecord *next;
508 char *start_root;
509 char *end_root;
510 mword root_desc;
514 * We're never actually using the first element. It's always set to
515 * NULL to simplify the elimination of consecutive duplicate
516 * entries.
518 #define STORE_REMSET_BUFFER_SIZE 1024
520 typedef struct _GenericStoreRememberedSet GenericStoreRememberedSet;
521 struct _GenericStoreRememberedSet {
522 GenericStoreRememberedSet *next;
523 /* We need one entry less because the first entry of store
524 remset buffers is always a dummy and we don't copy it. */
525 gpointer data [STORE_REMSET_BUFFER_SIZE - 1];
528 /* we have 4 possible values in the low 2 bits */
529 enum {
530 REMSET_LOCATION, /* just a pointer to the exact location */
531 REMSET_RANGE, /* range of pointer fields */
532 REMSET_OBJECT, /* mark all the object for scanning */
533 REMSET_VTYPE, /* a valuetype array described by a gc descriptor and a count */
534 REMSET_TYPE_MASK = 0x3
537 #ifdef HAVE_KW_THREAD
538 static __thread RememberedSet *remembered_set MONO_TLS_FAST;
539 #endif
540 static pthread_key_t remembered_set_key;
541 static RememberedSet *global_remset;
542 static RememberedSet *freed_thread_remsets;
543 static GenericStoreRememberedSet *generic_store_remsets = NULL;
545 /*A two slots cache for recently inserted remsets */
546 static gpointer global_remset_cache [2];
548 /* FIXME: later choose a size that takes into account the RememberedSet struct
549 * and doesn't waste any alloc paddin space.
551 #define DEFAULT_REMSET_SIZE 1024
552 static RememberedSet* alloc_remset (int size, gpointer id);
554 /* Structure that corresponds to a MonoVTable: desc is a mword so requires
555 * no cast from a pointer to an integer
557 typedef struct {
558 MonoClass *klass;
559 mword desc;
560 } GCVTable;
562 /* these bits are set in the object vtable: we could merge them since an object can be
563 * either pinned or forwarded but not both.
564 * We store them in the vtable slot because the bits are used in the sync block for
565 * other purposes: if we merge them and alloc the sync blocks aligned to 8 bytes, we can change
566 * this and use bit 3 in the syncblock (with the lower two bits both set for forwarded, that
567 * would be an invalid combination for the monitor and hash code).
568 * The values are already shifted.
569 * The forwarding address is stored in the sync block.
571 #define FORWARDED_BIT 1
572 #define PINNED_BIT 2
573 #define VTABLE_BITS_MASK 0x3
575 /* returns NULL if not forwarded, or the forwarded address */
576 #define object_is_forwarded(obj) (((mword*)(obj))[0] & FORWARDED_BIT? (void*)(((mword*)(obj))[1]): NULL)
577 /* set the forwarded address fw_addr for object obj */
578 #define forward_object(obj,fw_addr) do { \
579 ((mword*)(obj))[0] |= FORWARDED_BIT; \
580 ((mword*)(obj))[1] = (mword)(fw_addr); \
581 } while (0)
583 #define object_is_pinned(obj) (((mword*)(obj))[0] & PINNED_BIT)
584 #define pin_object(obj) do { \
585 ((mword*)(obj))[0] |= PINNED_BIT; \
586 } while (0)
587 #define unpin_object(obj) do { \
588 ((mword*)(obj))[0] &= ~PINNED_BIT; \
589 } while (0)
591 #ifdef ALIGN_NURSERY
592 #define ptr_in_nursery(ptr) (((mword)(ptr) & ~((1 << DEFAULT_NURSERY_BITS) - 1)) == (mword)nursery_start)
593 #else
594 #define ptr_in_nursery(ptr) ((char*)(ptr) >= nursery_start && (char*)(ptr) < nursery_real_end)
595 #endif
598 * Since we set bits in the vtable, use the macro to load it from the pointer to
599 * an object that is potentially pinned.
601 #define LOAD_VTABLE(addr) ((*(mword*)(addr)) & ~VTABLE_BITS_MASK)
603 static const char*
604 safe_name (void* obj)
606 MonoVTable *vt = (MonoVTable*)LOAD_VTABLE (obj);
607 return vt->klass->name;
610 static inline guint
611 safe_object_get_size (MonoObject* o)
613 MonoClass *klass = ((MonoVTable*)LOAD_VTABLE (o))->klass;
614 if (klass == mono_defaults.string_class) {
615 return sizeof (MonoString) + 2 * mono_string_length_fast ((MonoString*) o) + 2;
616 } else if (klass->rank) {
617 MonoArray *array = (MonoArray*)o;
618 size_t size = sizeof (MonoArray) + klass->sizes.element_size * mono_array_length_fast (array);
619 if (G_UNLIKELY (array->bounds)) {
620 size += sizeof (mono_array_size_t) - 1;
621 size &= ~(sizeof (mono_array_size_t) - 1);
622 size += sizeof (MonoArrayBounds) * klass->rank;
624 return size;
625 } else {
626 /* from a created object: the class must be inited already */
627 return klass->instance_size;
632 * ######################################################################
633 * ######## Global data.
634 * ######################################################################
636 static LOCK_DECLARE (gc_mutex);
637 static int gc_disabled = 0;
638 static int num_minor_gcs = 0;
639 static int num_major_gcs = 0;
641 #ifdef USER_CONFIG
643 /* good sizes are 512KB-1MB: larger ones increase a lot memzeroing time */
644 #define DEFAULT_NURSERY_SIZE (default_nursery_size)
645 static int default_nursery_size = (1 << 22);
646 #ifdef ALIGN_NURSERY
647 /* The number of trailing 0 bits in DEFAULT_NURSERY_SIZE */
648 #define DEFAULT_NURSERY_BITS (default_nursery_bits)
649 static int default_nursery_bits = 22;
650 #endif
652 #else
654 #define DEFAULT_NURSERY_SIZE (4*1024*1024)
655 #ifdef ALIGN_NURSERY
656 #define DEFAULT_NURSERY_BITS 22
657 #endif
659 #endif
661 #define MIN_MINOR_COLLECTION_ALLOWANCE (DEFAULT_NURSERY_SIZE * 4)
662 /* to quickly find the head of an object pinned by a conservative address
663 * we keep track of the objects allocated for each SCAN_START_SIZE memory
664 * chunk in the nursery or other memory sections. Larger values have less
665 * memory overhead and bigger runtime cost. 4-8 KB are reasonable values.
667 #define SCAN_START_SIZE (4096*2)
668 /* the minimum size of a fragment that we consider useful for allocation */
669 #define FRAGMENT_MIN_SIZE (512)
670 /* This is a fixed value used for pinned chunks, not the system pagesize */
671 #define FREELIST_PAGESIZE (16*1024)
673 static mword pagesize = 4096;
674 static mword nursery_size;
675 static int degraded_mode = 0;
677 static mword total_alloc = 0;
678 /* use this to tune when to do a major/minor collection */
679 static mword memory_pressure = 0;
680 static int minor_collection_allowance;
681 static int minor_collection_sections_alloced = 0;
683 static GCMemSection *nursery_section = NULL;
684 static mword lowest_heap_address = ~(mword)0;
685 static mword highest_heap_address = 0;
687 static LOCK_DECLARE (interruption_mutex);
689 typedef struct _FinalizeEntry FinalizeEntry;
690 struct _FinalizeEntry {
691 FinalizeEntry *next;
692 void *object;
695 typedef struct _FinalizeEntryHashTable FinalizeEntryHashTable;
696 struct _FinalizeEntryHashTable {
697 FinalizeEntry **table;
698 mword size;
699 int num_registered;
702 typedef struct _DisappearingLink DisappearingLink;
703 struct _DisappearingLink {
704 DisappearingLink *next;
705 void **link;
708 typedef struct _DisappearingLinkHashTable DisappearingLinkHashTable;
709 struct _DisappearingLinkHashTable {
710 DisappearingLink **table;
711 mword size;
712 int num_links;
715 typedef struct _EphemeronLinkNode EphemeronLinkNode;
717 struct _EphemeronLinkNode {
718 EphemeronLinkNode *next;
719 char *array;
722 typedef struct {
723 void *key;
724 void *value;
725 } Ephemeron;
727 #define LARGE_INTERNAL_MEM_HEADER_MAGIC 0x7d289f3a
729 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
730 struct _LargeInternalMemHeader {
731 guint32 magic;
732 size_t size;
733 double data[0];
736 enum {
737 GENERATION_NURSERY,
738 GENERATION_OLD,
739 GENERATION_MAX
742 int current_collection_generation = -1;
745 * The link pointer is hidden by negating each bit. We use the lowest
746 * bit of the link (before negation) to store whether it needs
747 * resurrection tracking.
749 #define HIDE_POINTER(p,t) ((gpointer)(~((gulong)(p)|((t)?1:0))))
750 #define REVEAL_POINTER(p) ((gpointer)((~(gulong)(p))&~3L))
752 #define DISLINK_OBJECT(d) (REVEAL_POINTER (*(d)->link))
753 #define DISLINK_TRACK(d) ((~(gulong)(*(d)->link)) & 1)
756 * The finalizable hash has the object as the key, the
757 * disappearing_link hash, has the link address as key.
759 static FinalizeEntryHashTable minor_finalizable_hash;
760 static FinalizeEntryHashTable major_finalizable_hash;
761 /* objects that are ready to be finalized */
762 static FinalizeEntry *fin_ready_list = NULL;
763 static FinalizeEntry *critical_fin_list = NULL;
765 static DisappearingLinkHashTable minor_disappearing_link_hash;
766 static DisappearingLinkHashTable major_disappearing_link_hash;
768 static EphemeronLinkNode *ephemeron_list;
770 static int num_ready_finalizers = 0;
771 static int no_finalize = 0;
773 /* keep each size a multiple of ALLOC_ALIGN */
774 /* on 64 bit systems 8 is likely completely unused. */
775 static const int freelist_sizes [] = {
776 8, 16, 24, 32, 40, 48, 64, 80,
777 96, 128, 160, 192, 224, 256, 320, 384,
778 448, 512, 584, 680, 816, 1024, 1360, 2048,
779 2336, 2728, 3272, 4096, 5456, 8192 };
780 #define FREELIST_NUM_SLOTS (sizeof (freelist_sizes) / sizeof (freelist_sizes [0]))
782 /* This is also the MAJOR_SECTION_SIZE for the copying major
783 collector */
784 #define PINNED_CHUNK_SIZE (128 * 1024)
786 /* internal_chunk_list is used for allocating structures needed by the GC */
787 static PinnedChunk *internal_chunk_list = NULL;
789 static int slot_for_size (size_t size);
791 enum {
792 ROOT_TYPE_NORMAL = 0, /* "normal" roots */
793 ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */
794 ROOT_TYPE_WBARRIER = 2, /* roots with a write barrier */
795 ROOT_TYPE_NUM
798 /* registered roots: the key to the hash is the root start address */
800 * Different kinds of roots are kept separate to speed up pin_from_roots () for example.
802 static RootRecord **roots_hash [ROOT_TYPE_NUM] = { NULL, NULL };
803 static int roots_hash_size [ROOT_TYPE_NUM] = { 0, 0, 0 };
804 static mword roots_size = 0; /* amount of memory in the root set */
805 static int num_roots_entries [ROOT_TYPE_NUM] = { 0, 0, 0 };
808 * The current allocation cursors
809 * We allocate objects in the nursery.
810 * The nursery is the area between nursery_start and nursery_real_end.
811 * Allocation is done from a Thread Local Allocation Buffer (TLAB). TLABs are allocated
812 * from nursery fragments.
813 * tlab_next is the pointer to the space inside the TLAB where the next object will
814 * be allocated.
815 * tlab_temp_end is the pointer to the end of the temporary space reserved for
816 * the allocation: it allows us to set the scan starts at reasonable intervals.
817 * tlab_real_end points to the end of the TLAB.
818 * nursery_frag_real_end points to the end of the currently used nursery fragment.
819 * nursery_first_pinned_start points to the start of the first pinned object in the nursery
820 * nursery_last_pinned_end points to the end of the last pinned object in the nursery
821 * At the next allocation, the area of the nursery where objects can be present is
822 * between MIN(nursery_first_pinned_start, first_fragment_start) and
823 * MAX(nursery_last_pinned_end, nursery_frag_real_end)
825 static char *nursery_start = NULL;
827 #ifdef HAVE_KW_THREAD
828 #define TLAB_ACCESS_INIT
829 #define TLAB_START tlab_start
830 #define TLAB_NEXT tlab_next
831 #define TLAB_TEMP_END tlab_temp_end
832 #define TLAB_REAL_END tlab_real_end
833 #define REMEMBERED_SET remembered_set
834 #define STORE_REMSET_BUFFER store_remset_buffer
835 #define STORE_REMSET_BUFFER_INDEX store_remset_buffer_index
836 #define IN_CRITICAL_REGION thread_info->in_critical_region
837 #else
838 static pthread_key_t thread_info_key;
839 #define TLAB_ACCESS_INIT SgenThreadInfo *__thread_info__ = pthread_getspecific (thread_info_key)
840 #define TLAB_START (__thread_info__->tlab_start)
841 #define TLAB_NEXT (__thread_info__->tlab_next)
842 #define TLAB_TEMP_END (__thread_info__->tlab_temp_end)
843 #define TLAB_REAL_END (__thread_info__->tlab_real_end)
844 #define REMEMBERED_SET (__thread_info__->remset)
845 #define STORE_REMSET_BUFFER (__thread_info__->store_remset_buffer)
846 #define STORE_REMSET_BUFFER_INDEX (__thread_info__->store_remset_buffer_index)
847 #define IN_CRITICAL_REGION (__thread_info__->in_critical_region)
848 #endif
850 /* we use the memory barrier only to prevent compiler reordering (a memory constraint may be enough) */
851 #define ENTER_CRITICAL_REGION do {IN_CRITICAL_REGION = 1;mono_memory_barrier ();} while (0)
852 #define EXIT_CRITICAL_REGION do {IN_CRITICAL_REGION = 0;mono_memory_barrier ();} while (0)
855 * FIXME: What is faster, a TLS variable pointing to a structure, or separate TLS
856 * variables for next+temp_end ?
858 #ifdef HAVE_KW_THREAD
859 static __thread SgenThreadInfo *thread_info;
860 static __thread char *tlab_start;
861 static __thread char *tlab_next;
862 static __thread char *tlab_temp_end;
863 static __thread char *tlab_real_end;
864 static __thread gpointer *store_remset_buffer;
865 static __thread long store_remset_buffer_index;
866 /* Used by the managed allocator/wbarrier */
867 static __thread char **tlab_next_addr;
868 static __thread char *stack_end;
869 static __thread long *store_remset_buffer_index_addr;
870 #endif
871 static char *nursery_next = NULL;
872 static char *nursery_frag_real_end = NULL;
873 static char *nursery_real_end = NULL;
874 static char *nursery_last_pinned_end = NULL;
876 /* The size of a TLAB */
877 /* The bigger the value, the less often we have to go to the slow path to allocate a new
878 * one, but the more space is wasted by threads not allocating much memory.
879 * FIXME: Tune this.
880 * FIXME: Make this self-tuning for each thread.
882 static guint32 tlab_size = (1024 * 4);
884 /*How much space is tolerable to be wasted from the current fragment when allocating a new TLAB*/
885 #define MAX_NURSERY_TLAB_WASTE 512
887 /* fragments that are free and ready to be used for allocation */
888 static Fragment *nursery_fragments = NULL;
889 /* freeelist of fragment structures */
890 static Fragment *fragment_freelist = NULL;
893 * Objects bigger then this go into the large object space. This size
894 * has a few constraints. It must fit into the major heap, which in
895 * the case of the copying collector means that it must fit into a
896 * pinned chunk. It must also play well with the GC descriptors, some
897 * of which (DESC_TYPE_RUN_LENGTH, DESC_TYPE_SMALL_BITMAP) encode the
898 * object size.
900 #define MAX_SMALL_OBJ_SIZE 8000
902 /* Functions supplied by the runtime to be called by the GC */
903 static MonoGCCallbacks gc_callbacks;
905 #define ALLOC_ALIGN 8
906 #define ALLOC_ALIGN_BITS 3
908 #define MOVED_OBJECTS_NUM 64
909 static void *moved_objects [MOVED_OBJECTS_NUM];
910 static int moved_objects_idx = 0;
913 * ######################################################################
914 * ######## Macros and function declarations.
915 * ######################################################################
918 #define UPDATE_HEAP_BOUNDARIES(low,high) do { \
919 if ((mword)(low) < lowest_heap_address) \
920 lowest_heap_address = (mword)(low); \
921 if ((mword)(high) > highest_heap_address) \
922 highest_heap_address = (mword)(high); \
923 } while (0)
924 #define ADDR_IN_HEAP_BOUNDARIES(addr) ((p) >= lowest_heap_address && (p) < highest_heap_address)
926 inline static void*
927 align_pointer (void *ptr)
929 mword p = (mword)ptr;
930 p += sizeof (gpointer) - 1;
931 p &= ~ (sizeof (gpointer) - 1);
932 return (void*)p;
935 typedef void (*CopyOrMarkObjectFunc) (void**);
936 typedef char* (*ScanObjectFunc) (char*);
938 /* forward declarations */
939 static void* get_internal_mem (size_t size, int type);
940 static void free_internal_mem (void *addr, int type);
941 static void* get_os_memory (size_t size, int activate);
942 static void* get_os_memory_aligned (mword size, mword alignment, gboolean activate);
943 static void free_os_memory (void *addr, size_t size);
944 static G_GNUC_UNUSED void report_internal_mem_usage (void);
946 static int stop_world (void);
947 static int restart_world (void);
948 static void add_to_global_remset (gpointer ptr);
949 static void scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise);
950 static void scan_from_remsets (void *start_nursery, void *end_nursery);
951 static void scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type);
952 static void scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list);
953 static void find_pinning_ref_from_thread (char *obj, size_t size);
954 static void update_current_thread_stack (void *start);
955 static void finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation);
956 static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation);
957 static void null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation);
958 static void null_links_for_domain (MonoDomain *domain, int generation);
959 static gboolean search_fragment_for_size (size_t size);
960 static int search_fragment_for_size_range (size_t desired_size, size_t minimum_size);
961 static void build_nursery_fragments (int start_pin, int end_pin);
962 static void clear_nursery_fragments (char *next);
963 static void pin_from_roots (void *start_nursery, void *end_nursery);
964 static int pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery);
965 static void pin_objects_in_section (GCMemSection *section);
966 static void optimize_pin_queue (int start_slot);
967 static void clear_remsets (void);
968 static void clear_tlabs (void);
969 typedef void (*IterateObjectCallbackFunc) (char*, size_t, void*);
970 static void scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data);
971 static void scan_object (char *start);
972 static void major_scan_object (char *start);
973 static void* copy_object_no_checks (void *obj);
974 static void copy_object (void **obj_slot);
975 static void* get_chunk_freelist (PinnedChunk *chunk, int slot);
976 static PinnedChunk* alloc_pinned_chunk (void);
977 static void sort_addresses (void **array, int size);
978 static void drain_gray_stack (void);
979 static void finish_gray_stack (char *start_addr, char *end_addr, int generation);
980 static gboolean need_major_collection (void);
981 static void major_collection (const char *reason);
983 static void mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track);
985 void describe_ptr (char *ptr);
986 void check_object (char *start);
988 static void check_consistency (void);
989 static void check_major_refs (void);
990 static void check_section_scan_starts (GCMemSection *section);
991 static void check_scan_starts (void);
992 static void check_for_xdomain_refs (void);
993 static void dump_occupied (char *start, char *end, char *section_start);
994 static void dump_section (GCMemSection *section, const char *type);
995 static void dump_heap (const char *type, int num, const char *reason);
996 static void report_pinned_chunk (PinnedChunk *chunk, int seq);
998 void mono_gc_scan_for_specific_ref (MonoObject *key);
1000 static void init_stats (void);
1002 static int mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end);
1003 static void clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end);
1004 static void null_ephemerons_for_domain (MonoDomain *domain);
1006 //#define BINARY_PROTOCOL
1007 #include "sgen-protocol.c"
1008 #include "sgen-pinning.c"
1009 #include "sgen-pinning-stats.c"
1010 #include "sgen-gray.c"
1011 #include "sgen-los.c"
1014 * ######################################################################
1015 * ######## GC descriptors
1016 * ######################################################################
1017 * Used to quickly get the info the GC needs about an object: size and
1018 * where the references are held.
1020 /* objects are aligned to 8 bytes boundaries
1021 * A descriptor is a pointer in MonoVTable, so 32 or 64 bits of size.
1022 * The low 3 bits define the type of the descriptor. The other bits
1023 * depend on the type.
1024 * As a general rule the 13 remaining low bits define the size, either
1025 * of the whole object or of the elements in the arrays. While for objects
1026 * the size is already in bytes, for arrays we need to shift, because
1027 * array elements might be smaller than 8 bytes. In case of arrays, we
1028 * use two bits to describe what the additional high bits represents,
1029 * so the default behaviour can handle element sizes less than 2048 bytes.
1030 * The high 16 bits, if 0 it means the object is pointer-free.
1031 * This design should make it easy and fast to skip over ptr-free data.
1032 * The first 4 types should cover >95% of the objects.
1033 * Note that since the size of objects is limited to 64K, larger objects
1034 * will be allocated in the large object heap.
1035 * If we want 4-bytes alignment, we need to put vector and small bitmap
1036 * inside complex.
1038 enum {
1040 * We don't use 0 so that 0 isn't a valid GC descriptor. No
1041 * deep reason for this other than to be able to identify a
1042 * non-inited descriptor for debugging.
1044 * If an object contains no references, its GC descriptor is
1045 * always DESC_TYPE_RUN_LENGTH, without a size, no exceptions.
1046 * This is so that we can quickly check for that in
1047 * copy_object_no_checks(), without having to fetch the
1048 * object's class.
1050 DESC_TYPE_RUN_LENGTH = 1, /* 15 bits aligned byte size | 1-3 (offset, numptr) bytes tuples */
1051 DESC_TYPE_SMALL_BITMAP, /* 15 bits aligned byte size | 16-48 bit bitmap */
1052 DESC_TYPE_COMPLEX, /* index for bitmap into complex_descriptors */
1053 DESC_TYPE_VECTOR, /* 10 bits element size | 1 bit array | 2 bits desc | element desc */
1054 DESC_TYPE_ARRAY, /* 10 bits element size | 1 bit array | 2 bits desc | element desc */
1055 DESC_TYPE_LARGE_BITMAP, /* | 29-61 bitmap bits */
1056 DESC_TYPE_COMPLEX_ARR, /* index for bitmap into complex_descriptors */
1057 /* subtypes for arrays and vectors */
1058 DESC_TYPE_V_PTRFREE = 0,/* there are no refs: keep first so it has a zero value */
1059 DESC_TYPE_V_REFS, /* all the array elements are refs */
1060 DESC_TYPE_V_RUN_LEN, /* elements are run-length encoded as DESC_TYPE_RUN_LENGTH */
1061 DESC_TYPE_V_BITMAP /* elements are as the bitmap in DESC_TYPE_SMALL_BITMAP */
1064 #define OBJECT_HEADER_WORDS (sizeof(MonoObject)/sizeof(gpointer))
1065 #define LOW_TYPE_BITS 3
1066 #define SMALL_BITMAP_SHIFT 16
1067 #define SMALL_BITMAP_SIZE (GC_BITS_PER_WORD - SMALL_BITMAP_SHIFT)
1068 #define VECTOR_INFO_SHIFT 14
1069 #define VECTOR_ELSIZE_SHIFT 3
1070 #define LARGE_BITMAP_SIZE (GC_BITS_PER_WORD - LOW_TYPE_BITS)
1071 #define MAX_ELEMENT_SIZE 0x3ff
1072 #define VECTOR_SUBTYPE_PTRFREE (DESC_TYPE_V_PTRFREE << VECTOR_INFO_SHIFT)
1073 #define VECTOR_SUBTYPE_REFS (DESC_TYPE_V_REFS << VECTOR_INFO_SHIFT)
1074 #define VECTOR_SUBTYPE_RUN_LEN (DESC_TYPE_V_RUN_LEN << VECTOR_INFO_SHIFT)
1075 #define VECTOR_SUBTYPE_BITMAP (DESC_TYPE_V_BITMAP << VECTOR_INFO_SHIFT)
1078 /* Root bitmap descriptors are simpler: the lower three bits describe the type
1079 * and we either have 30/62 bitmap bits or nibble-based run-length,
1080 * or a complex descriptor, or a user defined marker function.
1082 enum {
1083 ROOT_DESC_CONSERVATIVE, /* 0, so matches NULL value */
1084 ROOT_DESC_BITMAP,
1085 ROOT_DESC_RUN_LEN,
1086 ROOT_DESC_COMPLEX,
1087 ROOT_DESC_USER,
1088 ROOT_DESC_TYPE_MASK = 0x7,
1089 ROOT_DESC_TYPE_SHIFT = 3,
1092 #define MAKE_ROOT_DESC(type,val) ((type) | ((val) << ROOT_DESC_TYPE_SHIFT))
1094 #define MAX_USER_DESCRIPTORS 16
1096 static gsize* complex_descriptors = NULL;
1097 static int complex_descriptors_size = 0;
1098 static int complex_descriptors_next = 0;
1099 static MonoGCRootMarkFunc user_descriptors [MAX_USER_DESCRIPTORS];
1100 static int user_descriptors_next = 0;
1102 static int
1103 alloc_complex_descriptor (gsize *bitmap, int numbits)
1105 int nwords, res, i;
1107 numbits = ALIGN_TO (numbits, GC_BITS_PER_WORD);
1108 nwords = numbits / GC_BITS_PER_WORD + 1;
1110 LOCK_GC;
1111 res = complex_descriptors_next;
1112 /* linear search, so we don't have duplicates with domain load/unload
1113 * this should not be performance critical or we'd have bigger issues
1114 * (the number and size of complex descriptors should be small).
1116 for (i = 0; i < complex_descriptors_next; ) {
1117 if (complex_descriptors [i] == nwords) {
1118 int j, found = TRUE;
1119 for (j = 0; j < nwords - 1; ++j) {
1120 if (complex_descriptors [i + 1 + j] != bitmap [j]) {
1121 found = FALSE;
1122 break;
1125 if (found) {
1126 UNLOCK_GC;
1127 return i;
1130 i += complex_descriptors [i];
1132 if (complex_descriptors_next + nwords > complex_descriptors_size) {
1133 int new_size = complex_descriptors_size * 2 + nwords;
1134 complex_descriptors = g_realloc (complex_descriptors, new_size * sizeof (gsize));
1135 complex_descriptors_size = new_size;
1137 DEBUG (6, fprintf (gc_debug_file, "Complex descriptor %d, size: %d (total desc memory: %d)\n", res, nwords, complex_descriptors_size));
1138 complex_descriptors_next += nwords;
1139 complex_descriptors [res] = nwords;
1140 for (i = 0; i < nwords - 1; ++i) {
1141 complex_descriptors [res + 1 + i] = bitmap [i];
1142 DEBUG (6, fprintf (gc_debug_file, "\tvalue: %p\n", (void*)complex_descriptors [res + 1 + i]));
1144 UNLOCK_GC;
1145 return res;
1149 * Descriptor builders.
1151 void*
1152 mono_gc_make_descr_for_string (gsize *bitmap, int numbits)
1154 return (void*) DESC_TYPE_RUN_LENGTH;
1157 void*
1158 mono_gc_make_descr_for_object (gsize *bitmap, int numbits, size_t obj_size)
1160 int first_set = -1, num_set = 0, last_set = -1, i;
1161 mword desc = 0;
1162 size_t stored_size = obj_size;
1163 for (i = 0; i < numbits; ++i) {
1164 if (bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
1165 if (first_set < 0)
1166 first_set = i;
1167 last_set = i;
1168 num_set++;
1172 * We don't encode the size of types that don't contain
1173 * references because they might not be aligned, i.e. the
1174 * bottom two bits might be set, which would clash with the
1175 * bits we need to encode the descriptor type. Since we don't
1176 * use the encoded size to skip objects, other than for
1177 * processing remsets, in which case only the positions of
1178 * references are relevant, this is not a problem.
1180 if (first_set < 0)
1181 return (void*)DESC_TYPE_RUN_LENGTH;
1182 g_assert (!(stored_size & 0x3));
1183 if (stored_size <= MAX_SMALL_OBJ_SIZE) {
1184 /* check run-length encoding first: one byte offset, one byte number of pointers
1185 * on 64 bit archs, we can have 3 runs, just one on 32.
1186 * It may be better to use nibbles.
1188 if (first_set < 0) {
1189 desc = DESC_TYPE_RUN_LENGTH | (stored_size << 1);
1190 DEBUG (6, fprintf (gc_debug_file, "Ptrfree descriptor %p, size: %zd\n", (void*)desc, stored_size));
1191 return (void*) desc;
1192 } else if (first_set < 256 && num_set < 256 && (first_set + num_set == last_set + 1)) {
1193 desc = DESC_TYPE_RUN_LENGTH | (stored_size << 1) | (first_set << 16) | (num_set << 24);
1194 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));
1195 return (void*) desc;
1197 /* we know the 2-word header is ptr-free */
1198 if (last_set < SMALL_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
1199 desc = DESC_TYPE_SMALL_BITMAP | (stored_size << 1) | ((*bitmap >> OBJECT_HEADER_WORDS) << SMALL_BITMAP_SHIFT);
1200 DEBUG (6, fprintf (gc_debug_file, "Smallbitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
1201 return (void*) desc;
1204 /* we know the 2-word header is ptr-free */
1205 if (last_set < LARGE_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
1206 desc = DESC_TYPE_LARGE_BITMAP | ((*bitmap >> OBJECT_HEADER_WORDS) << LOW_TYPE_BITS);
1207 DEBUG (6, fprintf (gc_debug_file, "Largebitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
1208 return (void*) desc;
1210 /* it's a complex object ... */
1211 desc = DESC_TYPE_COMPLEX | (alloc_complex_descriptor (bitmap, last_set + 1) << LOW_TYPE_BITS);
1212 return (void*) desc;
1215 /* If the array holds references, numbits == 1 and the first bit is set in elem_bitmap */
1216 void*
1217 mono_gc_make_descr_for_array (int vector, gsize *elem_bitmap, int numbits, size_t elem_size)
1219 int first_set = -1, num_set = 0, last_set = -1, i;
1220 mword desc = vector? DESC_TYPE_VECTOR: DESC_TYPE_ARRAY;
1221 for (i = 0; i < numbits; ++i) {
1222 if (elem_bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
1223 if (first_set < 0)
1224 first_set = i;
1225 last_set = i;
1226 num_set++;
1229 /* See comment at the definition of DESC_TYPE_RUN_LENGTH. */
1230 if (first_set < 0)
1231 return (void*)DESC_TYPE_RUN_LENGTH;
1232 if (elem_size <= MAX_ELEMENT_SIZE) {
1233 desc |= elem_size << VECTOR_ELSIZE_SHIFT;
1234 if (!num_set) {
1235 return (void*)(desc | VECTOR_SUBTYPE_PTRFREE);
1237 /* Note: we also handle structs with just ref fields */
1238 if (num_set * sizeof (gpointer) == elem_size) {
1239 return (void*)(desc | VECTOR_SUBTYPE_REFS | ((gssize)(-1) << 16));
1241 /* FIXME: try run-len first */
1242 /* Note: we can't skip the object header here, because it's not present */
1243 if (last_set <= SMALL_BITMAP_SIZE) {
1244 return (void*)(desc | VECTOR_SUBTYPE_BITMAP | (*elem_bitmap << 16));
1247 /* it's am array of complex structs ... */
1248 desc = DESC_TYPE_COMPLEX_ARR;
1249 desc |= alloc_complex_descriptor (elem_bitmap, last_set + 1) << LOW_TYPE_BITS;
1250 return (void*) desc;
1253 /* Return the bitmap encoded by a descriptor */
1254 gsize*
1255 mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
1257 mword d = (mword)descr;
1258 gsize *bitmap;
1260 switch (d & 0x7) {
1261 case DESC_TYPE_RUN_LENGTH: {
1262 int first_set = (d >> 16) & 0xff;
1263 int num_set = (d >> 24) & 0xff;
1264 int i;
1266 bitmap = g_new0 (gsize, (first_set + num_set + 7) / 8);
1268 for (i = first_set; i < first_set + num_set; ++i)
1269 bitmap [i / GC_BITS_PER_WORD] |= ((gsize)1 << (i % GC_BITS_PER_WORD));
1271 *numbits = first_set + num_set;
1273 return bitmap;
1275 case DESC_TYPE_SMALL_BITMAP:
1276 bitmap = g_new0 (gsize, 1);
1278 bitmap [0] = (d >> SMALL_BITMAP_SHIFT) << OBJECT_HEADER_WORDS;
1280 *numbits = GC_BITS_PER_WORD;
1282 return bitmap;
1283 default:
1284 g_assert_not_reached ();
1288 /* helper macros to scan and traverse objects, macros because we resue them in many functions */
1289 #define STRING_SIZE(size,str) do { \
1290 (size) = sizeof (MonoString) + 2 * mono_string_length_fast ((MonoString*)(str)) + 2; \
1291 (size) += (ALLOC_ALIGN - 1); \
1292 (size) &= ~(ALLOC_ALIGN - 1); \
1293 } while (0)
1295 #define OBJ_RUN_LEN_SIZE(size,desc,obj) do { \
1296 (size) = ((desc) & 0xfff8) >> 1; \
1297 } while (0)
1299 #define OBJ_BITMAP_SIZE(size,desc,obj) do { \
1300 (size) = ((desc) & 0xfff8) >> 1; \
1301 } while (0)
1303 //#define PREFETCH(addr) __asm__ __volatile__ (" prefetchnta %0": : "m"(*(char *)(addr)))
1304 #define PREFETCH(addr)
1306 /* code using these macros must define a HANDLE_PTR(ptr) macro that does the work */
1307 #define OBJ_RUN_LEN_FOREACH_PTR(desc,obj) do { \
1308 if ((desc) & 0xffff0000) { \
1309 /* there are pointers */ \
1310 void **_objptr_end; \
1311 void **_objptr = (void**)(obj); \
1312 _objptr += ((desc) >> 16) & 0xff; \
1313 _objptr_end = _objptr + (((desc) >> 24) & 0xff); \
1314 while (_objptr < _objptr_end) { \
1315 HANDLE_PTR (_objptr, (obj)); \
1316 _objptr++; \
1319 } while (0)
1321 /* a bitmap desc means that there are pointer references or we'd have
1322 * choosen run-length, instead: add an assert to check.
1324 #define OBJ_BITMAP_FOREACH_PTR(desc,obj) do { \
1325 /* there are pointers */ \
1326 void **_objptr = (void**)(obj); \
1327 gsize _bmap = (desc) >> 16; \
1328 _objptr += OBJECT_HEADER_WORDS; \
1329 while (_bmap) { \
1330 if ((_bmap & 1)) { \
1331 HANDLE_PTR (_objptr, (obj)); \
1333 _bmap >>= 1; \
1334 ++_objptr; \
1336 } while (0)
1338 #define OBJ_LARGE_BITMAP_FOREACH_PTR(vt,obj) do { \
1339 /* there are pointers */ \
1340 void **_objptr = (void**)(obj); \
1341 gsize _bmap = (vt)->desc >> LOW_TYPE_BITS; \
1342 _objptr += OBJECT_HEADER_WORDS; \
1343 while (_bmap) { \
1344 if ((_bmap & 1)) { \
1345 HANDLE_PTR (_objptr, (obj)); \
1347 _bmap >>= 1; \
1348 ++_objptr; \
1350 } while (0)
1352 #define OBJ_COMPLEX_FOREACH_PTR(vt,obj) do { \
1353 /* there are pointers */ \
1354 void **_objptr = (void**)(obj); \
1355 gsize *bitmap_data = complex_descriptors + ((vt)->desc >> LOW_TYPE_BITS); \
1356 int bwords = (*bitmap_data) - 1; \
1357 void **start_run = _objptr; \
1358 bitmap_data++; \
1359 if (0) { \
1360 MonoObject *myobj = (MonoObject*)obj; \
1361 g_print ("found %d at %p (0x%zx): %s.%s\n", bwords, (obj), (vt)->desc, myobj->vtable->klass->name_space, myobj->vtable->klass->name); \
1363 while (bwords-- > 0) { \
1364 gsize _bmap = *bitmap_data++; \
1365 _objptr = start_run; \
1366 /*g_print ("bitmap: 0x%x/%d at %p\n", _bmap, bwords, _objptr);*/ \
1367 while (_bmap) { \
1368 if ((_bmap & 1)) { \
1369 HANDLE_PTR (_objptr, (obj)); \
1371 _bmap >>= 1; \
1372 ++_objptr; \
1374 start_run += GC_BITS_PER_WORD; \
1376 } while (0)
1378 /* this one is untested */
1379 #define OBJ_COMPLEX_ARR_FOREACH_PTR(vt,obj) do { \
1380 /* there are pointers */ \
1381 gsize *mbitmap_data = complex_descriptors + ((vt)->desc >> LOW_TYPE_BITS); \
1382 int mbwords = (*mbitmap_data++) - 1; \
1383 int el_size = mono_array_element_size (vt->klass); \
1384 char *e_start = (char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector); \
1385 char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj)); \
1386 if (0) \
1387 g_print ("found %d at %p (0x%zx): %s.%s\n", mbwords, (obj), (vt)->desc, vt->klass->name_space, vt->klass->name); \
1388 while (e_start < e_end) { \
1389 void **_objptr = (void**)e_start; \
1390 gsize *bitmap_data = mbitmap_data; \
1391 unsigned int bwords = mbwords; \
1392 while (bwords-- > 0) { \
1393 gsize _bmap = *bitmap_data++; \
1394 void **start_run = _objptr; \
1395 /*g_print ("bitmap: 0x%x\n", _bmap);*/ \
1396 while (_bmap) { \
1397 if ((_bmap & 1)) { \
1398 HANDLE_PTR (_objptr, (obj)); \
1400 _bmap >>= 1; \
1401 ++_objptr; \
1403 _objptr = start_run + GC_BITS_PER_WORD; \
1405 e_start += el_size; \
1407 } while (0)
1409 #define OBJ_VECTOR_FOREACH_PTR(vt,obj) do { \
1410 /* note: 0xffffc000 excludes DESC_TYPE_V_PTRFREE */ \
1411 if ((vt)->desc & 0xffffc000) { \
1412 int el_size = ((vt)->desc >> 3) & MAX_ELEMENT_SIZE; \
1413 /* there are pointers */ \
1414 int etype = (vt)->desc & 0xc000; \
1415 if (etype == (DESC_TYPE_V_REFS << 14)) { \
1416 void **p = (void**)((char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector)); \
1417 void **end_refs = (void**)((char*)p + el_size * mono_array_length_fast ((MonoArray*)(obj))); \
1418 /* Note: this code can handle also arrays of struct with only references in them */ \
1419 while (p < end_refs) { \
1420 HANDLE_PTR (p, (obj)); \
1421 ++p; \
1423 } else if (etype == DESC_TYPE_V_RUN_LEN << 14) { \
1424 int offset = ((vt)->desc >> 16) & 0xff; \
1425 int num_refs = ((vt)->desc >> 24) & 0xff; \
1426 char *e_start = (char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector); \
1427 char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj)); \
1428 while (e_start < e_end) { \
1429 void **p = (void**)e_start; \
1430 int i; \
1431 p += offset; \
1432 for (i = 0; i < num_refs; ++i) { \
1433 HANDLE_PTR (p + i, (obj)); \
1435 e_start += el_size; \
1437 } else if (etype == DESC_TYPE_V_BITMAP << 14) { \
1438 char *e_start = (char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector); \
1439 char *e_end = e_start + el_size * mono_array_length_fast ((MonoArray*)(obj)); \
1440 while (e_start < e_end) { \
1441 void **p = (void**)e_start; \
1442 gsize _bmap = (vt)->desc >> 16; \
1443 /* Note: there is no object header here to skip */ \
1444 while (_bmap) { \
1445 if ((_bmap & 1)) { \
1446 HANDLE_PTR (p, (obj)); \
1448 _bmap >>= 1; \
1449 ++p; \
1451 e_start += el_size; \
1455 } while (0)
1457 //#include "sgen-major-copying.c"
1458 #include "sgen-marksweep.c"
1460 static gboolean
1461 is_xdomain_ref_allowed (gpointer *ptr, char *obj, MonoDomain *domain)
1463 MonoObject *o = (MonoObject*)(obj);
1464 MonoObject *ref = (MonoObject*)*(ptr);
1465 int offset = (char*)(ptr) - (char*)o;
1467 if (o->vtable->klass == mono_defaults.thread_class && offset == G_STRUCT_OFFSET (MonoThread, internal_thread))
1468 return TRUE;
1469 if (o->vtable->klass == mono_defaults.internal_thread_class && offset == G_STRUCT_OFFSET (MonoInternalThread, current_appcontext))
1470 return TRUE;
1471 if (mono_class_has_parent (o->vtable->klass, mono_defaults.real_proxy_class) &&
1472 offset == G_STRUCT_OFFSET (MonoRealProxy, unwrapped_server))
1473 return TRUE;
1474 /* Thread.cached_culture_info */
1475 if (!strcmp (ref->vtable->klass->name_space, "System.Globalization") &&
1476 !strcmp (ref->vtable->klass->name, "CultureInfo") &&
1477 !strcmp(o->vtable->klass->name_space, "System") &&
1478 !strcmp(o->vtable->klass->name, "Object[]"))
1479 return TRUE;
1481 * 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
1482 * at System.IO.MemoryStream..ctor (byte[]) [0x00017] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.IO/MemoryStream.cs:81
1483 * at (wrapper remoting-invoke-with-check) System.IO.MemoryStream..ctor (byte[]) <IL 0x00020, 0xffffffff>
1484 * at System.Runtime.Remoting.Messaging.CADMethodCallMessage.GetArguments () [0x0000d] in /home/schani/Work/novell/trunk/mcs/class/corlib/System.Runtime.Remoting.Messaging/CADMessages.cs:327
1485 * 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
1486 * 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
1487 * at (wrapper remoting-invoke-with-check) System.AppDomain.ProcessMessageInDomain (byte[],System.Runtime.Remoting.Messaging.CADMethodCallMessage,byte[]&,System.Runtime.Remoting.Messaging.CADMethodReturnMessage&) <IL 0x0003d, 0xffffffff>
1488 * 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
1489 * at (wrapper runtime-invoke) object.runtime_invoke_CrossAppDomainSink/ProcessMessageRes_object_object (object,intptr,intptr,intptr) <IL 0x0004c, 0xffffffff>
1491 if (!strcmp (ref->vtable->klass->name_space, "System") &&
1492 !strcmp (ref->vtable->klass->name, "Byte[]") &&
1493 !strcmp (o->vtable->klass->name_space, "System.IO") &&
1494 !strcmp (o->vtable->klass->name, "MemoryStream"))
1495 return TRUE;
1496 /* append_job() in threadpool.c */
1497 if (!strcmp (ref->vtable->klass->name_space, "System.Runtime.Remoting.Messaging") &&
1498 !strcmp (ref->vtable->klass->name, "AsyncResult") &&
1499 !strcmp (o->vtable->klass->name_space, "System") &&
1500 !strcmp (o->vtable->klass->name, "Object[]") &&
1501 mono_thread_pool_is_queue_array ((MonoArray*) o))
1502 return TRUE;
1503 return FALSE;
1506 static void
1507 check_reference_for_xdomain (gpointer *ptr, char *obj, MonoDomain *domain)
1509 MonoObject *o = (MonoObject*)(obj);
1510 MonoObject *ref = (MonoObject*)*(ptr);
1511 int offset = (char*)(ptr) - (char*)o;
1512 MonoClass *class;
1513 MonoClassField *field;
1514 char *str;
1516 if (!ref || ref->vtable->domain == domain)
1517 return;
1518 if (is_xdomain_ref_allowed (ptr, obj, domain))
1519 return;
1521 field = NULL;
1522 for (class = o->vtable->klass; class; class = class->parent) {
1523 int i;
1525 for (i = 0; i < class->field.count; ++i) {
1526 if (class->fields[i].offset == offset) {
1527 field = &class->fields[i];
1528 break;
1531 if (field)
1532 break;
1535 if (ref->vtable->klass == mono_defaults.string_class)
1536 str = mono_string_to_utf8 ((MonoString*)ref);
1537 else
1538 str = NULL;
1539 g_print ("xdomain reference in %p (%s.%s) at offset %d (%s) to %p (%s.%s) (%s) - pointed to by:\n",
1540 o, o->vtable->klass->name_space, o->vtable->klass->name,
1541 offset, field ? field->name : "",
1542 ref, ref->vtable->klass->name_space, ref->vtable->klass->name, str ? str : "");
1543 mono_gc_scan_for_specific_ref (o);
1544 if (str)
1545 g_free (str);
1548 #undef HANDLE_PTR
1549 #define HANDLE_PTR(ptr,obj) check_reference_for_xdomain ((ptr), (obj), domain)
1551 static void
1552 scan_object_for_xdomain_refs (char *start, mword size, void *data)
1554 MonoDomain *domain = ((MonoObject*)start)->vtable->domain;
1556 #include "sgen-scan-object.h"
1559 #undef HANDLE_PTR
1560 #define HANDLE_PTR(ptr,obj) do { \
1561 if ((MonoObject*)*(ptr) == key) { \
1562 g_print ("found ref to %p in object %p (%s) at offset %td\n", \
1563 key, (obj), safe_name ((obj)), ((char*)(ptr) - (char*)(obj))); \
1565 } while (0)
1567 static void
1568 scan_object_for_specific_ref (char *start, MonoObject *key)
1570 #include "sgen-scan-object.h"
1573 static void
1574 scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data)
1576 while (start < end) {
1577 size_t size;
1578 if (!*(void**)start) {
1579 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
1580 continue;
1583 size = safe_object_get_size ((MonoObject*) start);
1584 size += ALLOC_ALIGN - 1;
1585 size &= ~(ALLOC_ALIGN - 1);
1587 callback (start, size, data);
1589 start += size;
1593 static void
1594 scan_object_for_specific_ref_callback (char *obj, size_t size, MonoObject *key)
1596 scan_object_for_specific_ref (obj, key);
1599 static void
1600 check_root_obj_specific_ref (RootRecord *root, MonoObject *key, MonoObject *obj)
1602 if (key != obj)
1603 return;
1604 g_print ("found ref to %p in root record %p\n", key, root);
1607 static MonoObject *check_key = NULL;
1608 static RootRecord *check_root = NULL;
1610 static void
1611 check_root_obj_specific_ref_from_marker (void **obj)
1613 check_root_obj_specific_ref (check_root, check_key, *obj);
1616 static void
1617 scan_roots_for_specific_ref (MonoObject *key, int root_type)
1619 int i;
1620 RootRecord *root;
1621 check_key = key;
1622 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1623 for (root = roots_hash [root_type][i]; root; root = root->next) {
1624 void **start_root = (void**)root->start_root;
1625 mword desc = root->root_desc;
1627 check_root = root;
1629 switch (desc & ROOT_DESC_TYPE_MASK) {
1630 case ROOT_DESC_BITMAP:
1631 desc >>= ROOT_DESC_TYPE_SHIFT;
1632 while (desc) {
1633 if (desc & 1)
1634 check_root_obj_specific_ref (root, key, *start_root);
1635 desc >>= 1;
1636 start_root++;
1638 return;
1639 case ROOT_DESC_COMPLEX: {
1640 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1641 int bwords = (*bitmap_data) - 1;
1642 void **start_run = start_root;
1643 bitmap_data++;
1644 while (bwords-- > 0) {
1645 gsize bmap = *bitmap_data++;
1646 void **objptr = start_run;
1647 while (bmap) {
1648 if (bmap & 1)
1649 check_root_obj_specific_ref (root, key, *objptr);
1650 bmap >>= 1;
1651 ++objptr;
1653 start_run += GC_BITS_PER_WORD;
1655 break;
1657 case ROOT_DESC_USER: {
1658 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1659 marker (start_root, check_root_obj_specific_ref_from_marker);
1660 break;
1662 case ROOT_DESC_RUN_LEN:
1663 g_assert_not_reached ();
1664 default:
1665 g_assert_not_reached ();
1669 check_key = NULL;
1670 check_root = NULL;
1673 void
1674 mono_gc_scan_for_specific_ref (MonoObject *key)
1676 LOSObject *bigobj;
1677 RootRecord *root;
1678 int i;
1680 scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1681 (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1683 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1685 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1686 scan_object_for_specific_ref (bigobj->data, key);
1688 scan_roots_for_specific_ref (key, ROOT_TYPE_NORMAL);
1689 scan_roots_for_specific_ref (key, ROOT_TYPE_WBARRIER);
1691 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
1692 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
1693 void **ptr = (void**)root->start_root;
1695 while (ptr < (void**)root->end_root) {
1696 check_root_obj_specific_ref (root, *ptr, key);
1697 ++ptr;
1703 /* Clear all remaining nursery fragments */
1704 static void
1705 clear_nursery_fragments (char *next)
1707 Fragment *frag;
1708 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
1709 g_assert (next <= nursery_frag_real_end);
1710 memset (next, 0, nursery_frag_real_end - next);
1711 for (frag = nursery_fragments; frag; frag = frag->next) {
1712 memset (frag->fragment_start, 0, frag->fragment_end - frag->fragment_start);
1717 static gboolean
1718 need_remove_object_for_domain (char *start, MonoDomain *domain)
1720 if (mono_object_domain (start) == domain) {
1721 DEBUG (4, fprintf (gc_debug_file, "Need to cleanup object %p\n", start));
1722 binary_protocol_cleanup (start, (gpointer)LOAD_VTABLE (start), safe_object_get_size ((MonoObject*)start));
1723 return TRUE;
1725 return FALSE;
1728 static void
1729 process_object_for_domain_clearing (char *start, MonoDomain *domain)
1731 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
1732 if (vt->klass == mono_defaults.internal_thread_class)
1733 g_assert (mono_object_domain (start) == mono_get_root_domain ());
1734 /* The object could be a proxy for an object in the domain
1735 we're deleting. */
1736 if (mono_class_has_parent (vt->klass, mono_defaults.real_proxy_class)) {
1737 MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server;
1739 /* The server could already have been zeroed out, so
1740 we need to check for that, too. */
1741 if (server && (!LOAD_VTABLE (server) || mono_object_domain (server) == domain)) {
1742 DEBUG (4, fprintf (gc_debug_file, "Cleaning up remote pointer in %p to object %p\n",
1743 start, server));
1744 ((MonoRealProxy*)start)->unwrapped_server = NULL;
1749 static MonoDomain *check_domain = NULL;
1751 static void
1752 check_obj_not_in_domain (void **o)
1754 g_assert (((MonoObject*)(*o))->vtable->domain != check_domain);
1757 static void
1758 scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type)
1760 int i;
1761 RootRecord *root;
1762 check_domain = domain;
1763 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1764 for (root = roots_hash [root_type][i]; root; root = root->next) {
1765 void **start_root = (void**)root->start_root;
1766 mword desc = root->root_desc;
1768 /* The MonoDomain struct is allowed to hold
1769 references to objects in its own domain. */
1770 if (start_root == (void**)domain)
1771 continue;
1773 switch (desc & ROOT_DESC_TYPE_MASK) {
1774 case ROOT_DESC_BITMAP:
1775 desc >>= ROOT_DESC_TYPE_SHIFT;
1776 while (desc) {
1777 if ((desc & 1) && *start_root)
1778 check_obj_not_in_domain (*start_root);
1779 desc >>= 1;
1780 start_root++;
1782 break;
1783 case ROOT_DESC_COMPLEX: {
1784 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1785 int bwords = (*bitmap_data) - 1;
1786 void **start_run = start_root;
1787 bitmap_data++;
1788 while (bwords-- > 0) {
1789 gsize bmap = *bitmap_data++;
1790 void **objptr = start_run;
1791 while (bmap) {
1792 if ((bmap & 1) && *objptr)
1793 check_obj_not_in_domain (*objptr);
1794 bmap >>= 1;
1795 ++objptr;
1797 start_run += GC_BITS_PER_WORD;
1799 break;
1801 case ROOT_DESC_USER: {
1802 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1803 marker (start_root, check_obj_not_in_domain);
1804 break;
1806 case ROOT_DESC_RUN_LEN:
1807 g_assert_not_reached ();
1808 default:
1809 g_assert_not_reached ();
1813 check_domain = NULL;
1816 static void
1817 check_for_xdomain_refs (void)
1819 LOSObject *bigobj;
1821 scan_area_with_callback (nursery_section->data, nursery_section->end_data, (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
1823 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)scan_object_for_xdomain_refs, NULL);
1825 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1826 scan_object_for_xdomain_refs (bigobj->data, bigobj->size, NULL);
1829 static gboolean
1830 clear_domain_process_object (char *obj, MonoDomain *domain)
1832 gboolean remove;
1834 process_object_for_domain_clearing (obj, domain);
1835 remove = need_remove_object_for_domain (obj, domain);
1837 if (remove && ((MonoObject*)obj)->synchronisation) {
1838 void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)obj);
1839 if (dislink)
1840 mono_gc_register_disappearing_link (NULL, dislink, FALSE);
1843 return remove;
1846 static void
1847 clear_domain_process_minor_object_callback (char *obj, size_t size, MonoDomain *domain)
1849 if (clear_domain_process_object (obj, domain))
1850 memset (obj, 0, size);
1853 static void
1854 clear_domain_process_major_object_callback (char *obj, size_t size, MonoDomain *domain)
1856 clear_domain_process_object (obj, domain);
1859 static void
1860 clear_domain_free_major_non_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1862 if (need_remove_object_for_domain (obj, domain))
1863 major_free_non_pinned_object (obj, size);
1866 static void
1867 clear_domain_free_major_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1869 if (need_remove_object_for_domain (obj, domain))
1870 free_pinned_object (obj, size);
1874 * When appdomains are unloaded we can easily remove objects that have finalizers,
1875 * but all the others could still be present in random places on the heap.
1876 * We need a sweep to get rid of them even though it's going to be costly
1877 * with big heaps.
1878 * The reason we need to remove them is because we access the vtable and class
1879 * structures to know the object size and the reference bitmap: once the domain is
1880 * unloaded the point to random memory.
1882 void
1883 mono_gc_clear_domain (MonoDomain * domain)
1885 LOSObject *bigobj, *prev;
1886 int i;
1888 LOCK_GC;
1890 clear_nursery_fragments (nursery_next);
1892 if (xdomain_checks && domain != mono_get_root_domain ()) {
1893 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_NORMAL);
1894 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_WBARRIER);
1895 check_for_xdomain_refs ();
1898 scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1899 (IterateObjectCallbackFunc)clear_domain_process_minor_object_callback, domain);
1901 /*Ephemerons and dislinks must be processed before LOS since they might end up pointing
1902 to memory returned to the OS.*/
1903 null_ephemerons_for_domain (domain);
1905 for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
1906 null_links_for_domain (domain, i);
1908 /* We need two passes over major and large objects because
1909 freeing such objects might give their memory back to the OS
1910 (in the case of large objects) or obliterate its vtable
1911 (pinned objects with major-copying or pinned and non-pinned
1912 objects with major-mark&sweep), but we might need to
1913 dereference a pointer from an object to another object if
1914 the first object is a proxy. */
1915 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)clear_domain_process_major_object_callback, domain);
1916 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1917 clear_domain_process_object (bigobj->data, domain);
1919 prev = NULL;
1920 for (bigobj = los_object_list; bigobj;) {
1921 if (need_remove_object_for_domain (bigobj->data, domain)) {
1922 LOSObject *to_free = bigobj;
1923 if (prev)
1924 prev->next = bigobj->next;
1925 else
1926 los_object_list = bigobj->next;
1927 bigobj = bigobj->next;
1928 DEBUG (4, fprintf (gc_debug_file, "Freeing large object %p\n",
1929 bigobj->data));
1930 free_large_object (to_free);
1931 continue;
1933 prev = bigobj;
1934 bigobj = bigobj->next;
1936 major_iterate_objects (TRUE, FALSE, (IterateObjectCallbackFunc)clear_domain_free_major_non_pinned_object_callback, domain);
1937 major_iterate_objects (FALSE, TRUE, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain);
1939 UNLOCK_GC;
1942 static void
1943 global_remset_cache_clear (void)
1945 memset (global_remset_cache, 0, sizeof (global_remset_cache));
1949 * Tries to check if a given remset location was already added to the global remset.
1950 * It can
1952 * A 2 entry, LRU cache of recently saw location remsets.
1954 * It's hand-coded instead of done using loops to reduce the number of memory references on cache hit.
1956 * Returns TRUE is the element was added..
1958 static gboolean
1959 global_remset_location_was_not_added (gpointer ptr)
1962 gpointer first = global_remset_cache [0], second;
1963 if (first == ptr) {
1964 HEAVY_STAT (++stat_global_remsets_discarded);
1965 return FALSE;
1968 second = global_remset_cache [1];
1970 if (second == ptr) {
1971 /*Move the second to the front*/
1972 global_remset_cache [0] = second;
1973 global_remset_cache [1] = first;
1975 HEAVY_STAT (++stat_global_remsets_discarded);
1976 return FALSE;
1979 global_remset_cache [0] = second;
1980 global_remset_cache [1] = ptr;
1981 return TRUE;
1985 * add_to_global_remset:
1987 * The global remset contains locations which point into newspace after
1988 * a minor collection. This can happen if the objects they point to are pinned.
1990 static void
1991 add_to_global_remset (gpointer ptr)
1993 RememberedSet *rs;
1995 g_assert (!ptr_in_nursery (ptr) && ptr_in_nursery (*(gpointer*)ptr));
1997 if (!global_remset_location_was_not_added (ptr))
1998 return;
2000 DEBUG (8, fprintf (gc_debug_file, "Adding global remset for %p\n", ptr));
2001 binary_protocol_global_remset (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
2003 HEAVY_STAT (++stat_global_remsets_added);
2006 * FIXME: If an object remains pinned, we need to add it at every minor collection.
2007 * To avoid uncontrolled growth of the global remset, only add each pointer once.
2009 if (global_remset->store_next + 3 < global_remset->end_set) {
2010 *(global_remset->store_next++) = (mword)ptr;
2011 return;
2013 rs = alloc_remset (global_remset->end_set - global_remset->data, NULL);
2014 rs->next = global_remset;
2015 global_remset = rs;
2016 *(global_remset->store_next++) = (mword)ptr;
2019 int global_rs_size = 0;
2021 for (rs = global_remset; rs; rs = rs->next) {
2022 global_rs_size += rs->store_next - rs->data;
2024 DEBUG (4, fprintf (gc_debug_file, "Global remset now has size %d\n", global_rs_size));
2029 * FIXME: allocate before calling this function and pass the
2030 * destination address.
2032 static void*
2033 copy_object_no_checks (void *obj)
2035 static const void *copy_labels [] = { &&LAB_0, &&LAB_1, &&LAB_2, &&LAB_3, &&LAB_4, &&LAB_5, &&LAB_6, &&LAB_7, &&LAB_8 };
2037 mword objsize;
2038 char *destination;
2039 MonoVTable *vt = ((MonoObject*)obj)->vtable;
2040 gboolean has_references = vt->gc_descr != (void*)DESC_TYPE_RUN_LENGTH;
2042 objsize = safe_object_get_size ((MonoObject*)obj);
2043 objsize += ALLOC_ALIGN - 1;
2044 objsize &= ~(ALLOC_ALIGN - 1);
2046 DEBUG (9, g_assert (vt->klass->inited));
2047 MAJOR_GET_COPY_OBJECT_SPACE (destination, objsize, has_references);
2049 DEBUG (9, fprintf (gc_debug_file, " (to %p, %s size: %lu)\n", destination, ((MonoObject*)obj)->vtable->klass->name, (unsigned long)objsize));
2050 binary_protocol_copy (obj, destination, ((MonoObject*)obj)->vtable, objsize);
2052 if (objsize <= sizeof (gpointer) * 8) {
2053 mword *dest = (mword*)destination;
2054 goto *copy_labels [objsize / sizeof (gpointer)];
2055 LAB_8:
2056 (dest) [7] = ((mword*)obj) [7];
2057 LAB_7:
2058 (dest) [6] = ((mword*)obj) [6];
2059 LAB_6:
2060 (dest) [5] = ((mword*)obj) [5];
2061 LAB_5:
2062 (dest) [4] = ((mword*)obj) [4];
2063 LAB_4:
2064 (dest) [3] = ((mword*)obj) [3];
2065 LAB_3:
2066 (dest) [2] = ((mword*)obj) [2];
2067 LAB_2:
2068 (dest) [1] = ((mword*)obj) [1];
2069 LAB_1:
2070 (dest) [0] = ((mword*)obj) [0];
2071 LAB_0:
2073 } else {
2074 #if 0
2076 int ecx;
2077 char* esi = obj;
2078 char* edi = destination;
2079 __asm__ __volatile__(
2080 "rep; movsl"
2081 : "=&c" (ecx), "=&D" (edi), "=&S" (esi)
2082 : "0" (objsize/4), "1" (edi),"2" (esi)
2083 : "memory"
2086 #else
2087 memcpy (destination, obj, objsize);
2088 #endif
2090 /* adjust array->bounds */
2091 DEBUG (9, g_assert (vt->gc_descr));
2092 if (G_UNLIKELY (vt->rank && ((MonoArray*)obj)->bounds)) {
2093 MonoArray *array = (MonoArray*)destination;
2094 array->bounds = (MonoArrayBounds*)((char*)destination + ((char*)((MonoArray*)obj)->bounds - (char*)obj));
2095 DEBUG (9, fprintf (gc_debug_file, "Array instance %p: size: %lu, rank: %d, length: %lu\n", array, (unsigned long)objsize, vt->rank, (unsigned long)mono_array_length (array)));
2097 /* set the forwarding pointer */
2098 forward_object (obj, destination);
2099 if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_GC_MOVES)) {
2100 if (moved_objects_idx == MOVED_OBJECTS_NUM) {
2101 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
2102 moved_objects_idx = 0;
2104 moved_objects [moved_objects_idx++] = obj;
2105 moved_objects [moved_objects_idx++] = destination;
2107 obj = destination;
2108 if (has_references) {
2109 DEBUG (9, fprintf (gc_debug_file, "Enqueuing gray object %p (%s)\n", obj, safe_name (obj)));
2110 GRAY_OBJECT_ENQUEUE (obj);
2112 return obj;
2116 * This is how the copying happens from the nursery to the old generation.
2117 * We assume that at this time all the pinned objects have been identified and
2118 * marked as such.
2119 * We run scan_object() for each pinned object so that each referenced
2120 * objects if possible are copied. The new gray objects created can have
2121 * scan_object() run on them right away, too.
2122 * Then we run copy_object() for the precisely tracked roots. At this point
2123 * all the roots are either gray or black. We run scan_object() on the gray
2124 * objects until no more gray objects are created.
2125 * At the end of the process we walk again the pinned list and we unmark
2126 * the pinned flag. As we go we also create the list of free space for use
2127 * in the next allocation runs.
2129 * We need to remember objects from the old generation that point to the new one
2130 * (or just addresses?).
2132 * copy_object could be made into a macro once debugged (use inline for now).
2135 static void __attribute__((noinline))
2136 copy_object (void **obj_slot)
2138 char *forwarded;
2139 char *obj = *obj_slot;
2141 DEBUG (9, g_assert (current_collection_generation == GENERATION_NURSERY));
2143 HEAVY_STAT (++stat_copy_object_called_nursery);
2145 if (!ptr_in_nursery (obj)) {
2146 HEAVY_STAT (++stat_nursery_copy_object_failed_from_space);
2147 return;
2150 DEBUG (9, fprintf (gc_debug_file, "Precise copy of %p from %p", obj, obj_slot));
2153 * Before we can copy the object we must make sure that we are
2154 * allowed to, i.e. that the object not pinned or not already
2155 * forwarded.
2158 if ((forwarded = object_is_forwarded (obj))) {
2159 DEBUG (9, g_assert (((MonoVTable*)LOAD_VTABLE(obj))->gc_descr));
2160 DEBUG (9, fprintf (gc_debug_file, " (already forwarded to %p)\n", forwarded));
2161 HEAVY_STAT (++stat_nursery_copy_object_failed_forwarded);
2162 *obj_slot = forwarded;
2163 return;
2165 if (object_is_pinned (obj)) {
2166 DEBUG (9, g_assert (((MonoVTable*)LOAD_VTABLE(obj))->gc_descr));
2167 DEBUG (9, fprintf (gc_debug_file, " (pinned, no change)\n"));
2168 HEAVY_STAT (++stat_nursery_copy_object_failed_pinned);
2169 return;
2172 HEAVY_STAT (++stat_objects_copied_nursery);
2174 *obj_slot = copy_object_no_checks (obj);
2177 #undef HANDLE_PTR
2178 #define HANDLE_PTR(ptr,obj) do { \
2179 void *__old = *(ptr); \
2180 void *__copy; \
2181 if (__old) { \
2182 copy_object ((ptr)); \
2183 __copy = *(ptr); \
2184 DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old)); \
2185 if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
2186 add_to_global_remset ((ptr)); \
2188 } while (0)
2191 * Scan the object pointed to by @start for references to
2192 * other objects between @from_start and @from_end and copy
2193 * them to the gray_objects area.
2195 static void
2196 scan_object (char *start)
2198 #include "sgen-scan-object.h"
2200 HEAVY_STAT (++stat_scan_object_called_nursery);
2204 * scan_vtype:
2206 * Scan the valuetype pointed to by START, described by DESC for references to
2207 * other objects between @from_start and @from_end and copy them to the gray_objects area.
2208 * Returns a pointer to the end of the object.
2210 static char*
2211 scan_vtype (char *start, mword desc, char* from_start, char* from_end)
2213 size_t skip_size;
2215 /* The descriptors include info about the MonoObject header as well */
2216 start -= sizeof (MonoObject);
2218 switch (desc & 0x7) {
2219 case DESC_TYPE_RUN_LENGTH:
2220 OBJ_RUN_LEN_FOREACH_PTR (desc,start);
2221 OBJ_RUN_LEN_SIZE (skip_size, desc, start);
2222 g_assert (skip_size);
2223 return start + skip_size;
2224 case DESC_TYPE_SMALL_BITMAP:
2225 OBJ_BITMAP_FOREACH_PTR (desc,start);
2226 OBJ_BITMAP_SIZE (skip_size, desc, start);
2227 return start + skip_size;
2228 case DESC_TYPE_LARGE_BITMAP:
2229 case DESC_TYPE_COMPLEX:
2230 // FIXME:
2231 g_assert_not_reached ();
2232 break;
2233 default:
2234 // The other descriptors can't happen with vtypes
2235 g_assert_not_reached ();
2236 break;
2238 return NULL;
2241 #undef HANDLE_PTR
2242 #define HANDLE_PTR(ptr,obj) do { \
2243 void *__old = *(ptr); \
2244 void *__copy; \
2245 if (__old) { \
2246 major_copy_or_mark_object ((ptr)); \
2247 __copy = *(ptr); \
2248 DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old)); \
2249 if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
2250 add_to_global_remset ((ptr)); \
2252 } while (0)
2254 static void
2255 major_scan_object (char *start)
2257 #include "sgen-scan-object.h"
2259 HEAVY_STAT (++stat_scan_object_called_major);
2263 * drain_gray_stack:
2265 * Scan objects in the gray stack until the stack is empty. This should be called
2266 * frequently after each object is copied, to achieve better locality and cache
2267 * usage.
2269 static void inline
2270 drain_gray_stack (void)
2272 char *obj;
2274 if (current_collection_generation == GENERATION_NURSERY) {
2275 for (;;) {
2276 GRAY_OBJECT_DEQUEUE (obj);
2277 if (!obj)
2278 break;
2279 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
2280 scan_object (obj);
2282 } else {
2283 for (;;) {
2284 GRAY_OBJECT_DEQUEUE (obj);
2285 if (!obj)
2286 break;
2287 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
2288 major_scan_object (obj);
2294 * Addresses from start to end are already sorted. This function finds
2295 * the object header for each address and pins the object. The
2296 * addresses must be inside the passed section. The (start of the)
2297 * address array is overwritten with the addresses of the actually
2298 * pinned objects. Return the number of pinned objects.
2300 static int
2301 pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery)
2303 void *last = NULL;
2304 int count = 0;
2305 void *search_start;
2306 void *last_obj = NULL;
2307 size_t last_obj_size = 0;
2308 void *addr;
2309 int idx;
2310 void **definitely_pinned = start;
2311 while (start < end) {
2312 addr = *start;
2313 /* the range check should be reduntant */
2314 if (addr != last && addr >= start_nursery && addr < end_nursery) {
2315 DEBUG (5, fprintf (gc_debug_file, "Considering pinning addr %p\n", addr));
2316 /* multiple pointers to the same object */
2317 if (addr >= last_obj && (char*)addr < (char*)last_obj + last_obj_size) {
2318 start++;
2319 continue;
2321 idx = ((char*)addr - (char*)section->data) / SCAN_START_SIZE;
2322 g_assert (idx < section->num_scan_start);
2323 search_start = (void*)section->scan_starts [idx];
2324 if (!search_start || search_start > addr) {
2325 while (idx) {
2326 --idx;
2327 search_start = section->scan_starts [idx];
2328 if (search_start && search_start <= addr)
2329 break;
2331 if (!search_start || search_start > addr)
2332 search_start = start_nursery;
2334 if (search_start < last_obj)
2335 search_start = (char*)last_obj + last_obj_size;
2336 /* now addr should be in an object a short distance from search_start
2337 * Note that search_start must point to zeroed mem or point to an object.
2339 do {
2340 if (!*(void**)search_start) {
2341 mword p = (mword)search_start;
2342 p += sizeof (gpointer);
2343 p += ALLOC_ALIGN - 1;
2344 p &= ~(ALLOC_ALIGN - 1);
2345 search_start = (void*)p;
2346 continue;
2348 last_obj = search_start;
2349 last_obj_size = safe_object_get_size ((MonoObject*)search_start);
2350 last_obj_size += ALLOC_ALIGN - 1;
2351 last_obj_size &= ~(ALLOC_ALIGN - 1);
2352 DEBUG (8, fprintf (gc_debug_file, "Pinned try match %p (%s), size %zd\n", last_obj, safe_name (last_obj), last_obj_size));
2353 if (addr >= search_start && (char*)addr < (char*)last_obj + last_obj_size) {
2354 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));
2355 binary_protocol_pin (search_start, (gpointer)LOAD_VTABLE (search_start), safe_object_get_size (search_start));
2356 pin_object (search_start);
2357 GRAY_OBJECT_ENQUEUE (search_start);
2358 if (heap_dump_file)
2359 pin_stats_register_object (search_start, last_obj_size);
2360 definitely_pinned [count] = search_start;
2361 count++;
2362 break;
2364 /* skip to the next object */
2365 search_start = (void*)((char*)search_start + last_obj_size);
2366 } while (search_start <= addr);
2367 /* we either pinned the correct object or we ignored the addr because
2368 * it points to unused zeroed memory.
2370 last = addr;
2372 start++;
2374 //printf ("effective pinned: %d (at the end: %d)\n", count, (char*)end_nursery - (char*)last);
2375 return count;
2378 static void
2379 pin_objects_in_section (GCMemSection *section)
2381 int start = section->pin_queue_start;
2382 int end = section->pin_queue_end;
2383 if (start != end) {
2384 int reduced_to;
2385 reduced_to = pin_objects_from_addresses (section, pin_queue + start, pin_queue + end,
2386 section->data, section->next_data);
2387 section->pin_queue_start = start;
2388 section->pin_queue_end = start + reduced_to;
2392 /* Sort the addresses in array in increasing order.
2393 * Done using a by-the book heap sort. Which has decent and stable performance, is pretty cache efficient.
2395 static void
2396 sort_addresses (void **array, int size)
2398 int i;
2399 void *tmp;
2401 for (i = 1; i < size; ++i) {
2402 int child = i;
2403 while (child > 0) {
2404 int parent = (child - 1) / 2;
2406 if (array [parent] >= array [child])
2407 break;
2409 tmp = array [parent];
2410 array [parent] = array [child];
2411 array [child] = tmp;
2413 child = parent;
2417 for (i = size - 1; i > 0; --i) {
2418 int end, root;
2419 tmp = array [i];
2420 array [i] = array [0];
2421 array [0] = tmp;
2423 end = i - 1;
2424 root = 0;
2426 while (root * 2 + 1 <= end) {
2427 int child = root * 2 + 1;
2429 if (child < end && array [child] < array [child + 1])
2430 ++child;
2431 if (array [root] >= array [child])
2432 break;
2434 tmp = array [root];
2435 array [root] = array [child];
2436 array [child] = tmp;
2438 root = child;
2443 static G_GNUC_UNUSED void
2444 print_nursery_gaps (void* start_nursery, void *end_nursery)
2446 int i;
2447 gpointer first = start_nursery;
2448 gpointer next;
2449 for (i = 0; i < next_pin_slot; ++i) {
2450 next = pin_queue [i];
2451 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %td\n", first, next, (char*)next-(char*)first);
2452 first = next;
2454 next = end_nursery;
2455 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %td\n", first, next, (char*)next-(char*)first);
2458 /* reduce the info in the pin queue, removing duplicate pointers and sorting them */
2459 static void
2460 optimize_pin_queue (int start_slot)
2462 void **start, **cur, **end;
2463 /* sort and uniq pin_queue: we just sort and we let the rest discard multiple values */
2464 /* it may be better to keep ranges of pinned memory instead of individually pinning objects */
2465 DEBUG (5, fprintf (gc_debug_file, "Sorting pin queue, size: %d\n", next_pin_slot));
2466 if ((next_pin_slot - start_slot) > 1)
2467 sort_addresses (pin_queue + start_slot, next_pin_slot - start_slot);
2468 start = cur = pin_queue + start_slot;
2469 end = pin_queue + next_pin_slot;
2470 while (cur < end) {
2471 *start = *cur++;
2472 while (*start == *cur && cur < end)
2473 cur++;
2474 start++;
2476 next_pin_slot = start - pin_queue;
2477 DEBUG (5, fprintf (gc_debug_file, "Pin queue reduced to size: %d\n", next_pin_slot));
2478 //DEBUG (6, print_nursery_gaps (start_nursery, end_nursery));
2483 * Scan the memory between start and end and queue values which could be pointers
2484 * to the area between start_nursery and end_nursery for later consideration.
2485 * Typically used for thread stacks.
2487 static void
2488 conservatively_pin_objects_from (void **start, void **end, void *start_nursery, void *end_nursery, int pin_type)
2490 int count = 0;
2491 while (start < end) {
2492 if (*start >= start_nursery && *start < end_nursery) {
2494 * *start can point to the middle of an object
2495 * note: should we handle pointing at the end of an object?
2496 * pinning in C# code disallows pointing at the end of an object
2497 * but there is some small chance that an optimizing C compiler
2498 * may keep the only reference to an object by pointing
2499 * at the end of it. We ignore this small chance for now.
2500 * Pointers to the end of an object are indistinguishable
2501 * from pointers to the start of the next object in memory
2502 * so if we allow that we'd need to pin two objects...
2503 * We queue the pointer in an array, the
2504 * array will then be sorted and uniqued. This way
2505 * we can coalesce several pinning pointers and it should
2506 * be faster since we'd do a memory scan with increasing
2507 * addresses. Note: we can align the address to the allocation
2508 * alignment, so the unique process is more effective.
2510 mword addr = (mword)*start;
2511 addr &= ~(ALLOC_ALIGN - 1);
2512 if (addr >= (mword)start_nursery && addr < (mword)end_nursery)
2513 pin_stage_ptr ((void*)addr);
2514 if (heap_dump_file)
2515 pin_stats_register_address ((char*)addr, pin_type);
2516 DEBUG (6, if (count) fprintf (gc_debug_file, "Pinning address %p\n", (void*)addr));
2517 count++;
2519 start++;
2521 DEBUG (7, if (count) fprintf (gc_debug_file, "found %d potential pinned heap pointers\n", count));
2525 * Debugging function: find in the conservative roots where @obj is being pinned.
2527 static G_GNUC_UNUSED void
2528 find_pinning_reference (char *obj, size_t size)
2530 RootRecord *root;
2531 int i;
2532 char *endobj = obj + size;
2533 for (i = 0; i < roots_hash_size [0]; ++i) {
2534 for (root = roots_hash [0][i]; root; root = root->next) {
2535 /* if desc is non-null it has precise info */
2536 if (!root->root_desc) {
2537 char ** start = (char**)root->start_root;
2538 while (start < (char**)root->end_root) {
2539 if (*start >= obj && *start < endobj) {
2540 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));
2542 start++;
2547 find_pinning_ref_from_thread (obj, size);
2551 * The first thing we do in a collection is to identify pinned objects.
2552 * This function considers all the areas of memory that need to be
2553 * conservatively scanned.
2555 static void
2556 pin_from_roots (void *start_nursery, void *end_nursery)
2558 RootRecord *root;
2559 int i;
2560 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]));
2561 /* objects pinned from the API are inside these roots */
2562 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
2563 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
2564 DEBUG (6, fprintf (gc_debug_file, "Pinned roots %p-%p\n", root->start_root, root->end_root));
2565 conservatively_pin_objects_from ((void**)root->start_root, (void**)root->end_root, start_nursery, end_nursery, PIN_TYPE_OTHER);
2568 /* now deal with the thread stacks
2569 * in the future we should be able to conservatively scan only:
2570 * *) the cpu registers
2571 * *) the unmanaged stack frames
2572 * *) the _last_ managed stack frame
2573 * *) pointers slots in managed frames
2575 scan_thread_data (start_nursery, end_nursery, FALSE);
2577 evacuate_pin_staging_area ();
2581 * The memory area from start_root to end_root contains pointers to objects.
2582 * Their position is precisely described by @desc (this means that the pointer
2583 * can be either NULL or the pointer to the start of an object).
2584 * This functions copies them to to_space updates them.
2586 static void
2587 precisely_scan_objects_from (CopyOrMarkObjectFunc copy_func, void** start_root, void** end_root, char* n_start, char *n_end, mword desc)
2589 switch (desc & ROOT_DESC_TYPE_MASK) {
2590 case ROOT_DESC_BITMAP:
2591 desc >>= ROOT_DESC_TYPE_SHIFT;
2592 while (desc) {
2593 if ((desc & 1) && *start_root) {
2594 copy_func (start_root);
2595 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", start_root, *start_root));
2596 drain_gray_stack ();
2598 desc >>= 1;
2599 start_root++;
2601 return;
2602 case ROOT_DESC_COMPLEX: {
2603 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
2604 int bwords = (*bitmap_data) - 1;
2605 void **start_run = start_root;
2606 bitmap_data++;
2607 while (bwords-- > 0) {
2608 gsize bmap = *bitmap_data++;
2609 void **objptr = start_run;
2610 while (bmap) {
2611 if ((bmap & 1) && *objptr) {
2612 copy_func (objptr);
2613 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", objptr, *objptr));
2614 drain_gray_stack ();
2616 bmap >>= 1;
2617 ++objptr;
2619 start_run += GC_BITS_PER_WORD;
2621 break;
2623 case ROOT_DESC_USER: {
2624 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
2625 marker (start_root, copy_func);
2626 break;
2628 case ROOT_DESC_RUN_LEN:
2629 g_assert_not_reached ();
2630 default:
2631 g_assert_not_reached ();
2635 static Fragment*
2636 alloc_fragment (void)
2638 Fragment *frag = fragment_freelist;
2639 if (frag) {
2640 fragment_freelist = frag->next;
2641 frag->next = NULL;
2642 return frag;
2644 frag = get_internal_mem (sizeof (Fragment), INTERNAL_MEM_FRAGMENT);
2645 frag->next = NULL;
2646 return frag;
2649 /* size must be a power of 2 */
2650 static void*
2651 get_os_memory_aligned (mword size, mword alignment, gboolean activate)
2653 /* Allocate twice the memory to be able to put the block on an aligned address */
2654 char *mem = get_os_memory (size + alignment, activate);
2655 char *aligned;
2657 g_assert (mem);
2659 aligned = (char*)((mword)(mem + (alignment - 1)) & ~(alignment - 1));
2660 g_assert (aligned >= mem && aligned + size <= mem + size + alignment && !((mword)aligned & (alignment - 1)));
2662 if (aligned > mem)
2663 free_os_memory (mem, aligned - mem);
2664 if (aligned + size < mem + size + alignment)
2665 free_os_memory (aligned + size, (mem + size + alignment) - (aligned + size));
2667 return aligned;
2671 * Allocate and setup the data structures needed to be able to allocate objects
2672 * in the nursery. The nursery is stored in nursery_section.
2674 static void
2675 alloc_nursery (void)
2677 GCMemSection *section;
2678 char *data;
2679 int scan_starts;
2680 Fragment *frag;
2681 int alloc_size;
2683 if (nursery_section)
2684 return;
2685 DEBUG (2, fprintf (gc_debug_file, "Allocating nursery size: %lu\n", (unsigned long)nursery_size));
2686 /* later we will alloc a larger area for the nursery but only activate
2687 * what we need. The rest will be used as expansion if we have too many pinned
2688 * objects in the existing nursery.
2690 /* FIXME: handle OOM */
2691 section = get_internal_mem (SIZEOF_GC_MEM_SECTION, INTERNAL_MEM_SECTION);
2693 g_assert (nursery_size == DEFAULT_NURSERY_SIZE);
2694 alloc_size = nursery_size;
2695 #ifdef ALIGN_NURSERY
2696 data = get_os_memory_aligned (alloc_size, alloc_size, TRUE);
2697 #else
2698 data = get_os_memory (alloc_size, TRUE);
2699 #endif
2700 nursery_start = data;
2701 nursery_real_end = nursery_start + nursery_size;
2702 UPDATE_HEAP_BOUNDARIES (nursery_start, nursery_real_end);
2703 nursery_next = nursery_start;
2704 total_alloc += alloc_size;
2705 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));
2706 section->data = section->next_data = data;
2707 section->size = alloc_size;
2708 section->end_data = nursery_real_end;
2709 scan_starts = (alloc_size + SCAN_START_SIZE - 1) / SCAN_START_SIZE;
2710 section->scan_starts = get_internal_mem (sizeof (char*) * scan_starts, INTERNAL_MEM_SCAN_STARTS);
2711 section->num_scan_start = scan_starts;
2712 section->block.role = MEMORY_ROLE_GEN0;
2713 section->block.next = NULL;
2715 nursery_section = section;
2717 /* Setup the single first large fragment */
2718 frag = alloc_fragment ();
2719 frag->fragment_start = nursery_start;
2720 frag->fragment_limit = nursery_start;
2721 frag->fragment_end = nursery_real_end;
2722 nursery_frag_real_end = nursery_real_end;
2723 /* FIXME: frag here is lost */
2726 static void
2727 scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list) {
2728 FinalizeEntry *fin;
2730 for (fin = list; fin; fin = fin->next) {
2731 if (!fin->object)
2732 continue;
2733 DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
2734 copy_func (&fin->object);
2738 static mword fragment_total = 0;
2740 * We found a fragment of free memory in the nursery: memzero it and if
2741 * it is big enough, add it to the list of fragments that can be used for
2742 * allocation.
2744 static void
2745 add_nursery_frag (size_t frag_size, char* frag_start, char* frag_end)
2747 Fragment *fragment;
2748 DEBUG (4, fprintf (gc_debug_file, "Found empty fragment: %p-%p, size: %zd\n", frag_start, frag_end, frag_size));
2749 binary_protocol_empty (frag_start, frag_size);
2750 /* memsetting just the first chunk start is bound to provide better cache locality */
2751 if (nursery_clear_policy == CLEAR_AT_GC)
2752 memset (frag_start, 0, frag_size);
2753 /* Not worth dealing with smaller fragments: need to tune */
2754 if (frag_size >= FRAGMENT_MIN_SIZE) {
2755 fragment = alloc_fragment ();
2756 fragment->fragment_start = frag_start;
2757 fragment->fragment_limit = frag_start;
2758 fragment->fragment_end = frag_end;
2759 fragment->next = nursery_fragments;
2760 nursery_fragments = fragment;
2761 fragment_total += frag_size;
2762 } else {
2763 /* Clear unused fragments, pinning depends on this */
2764 /*TODO place an int[] here instead of the memset if size justify it*/
2765 memset (frag_start, 0, frag_size);
2769 static const char*
2770 generation_name (int generation)
2772 switch (generation) {
2773 case GENERATION_NURSERY: return "nursery";
2774 case GENERATION_OLD: return "old";
2775 default: g_assert_not_reached ();
2779 static DisappearingLinkHashTable*
2780 get_dislink_hash_table (int generation)
2782 switch (generation) {
2783 case GENERATION_NURSERY: return &minor_disappearing_link_hash;
2784 case GENERATION_OLD: return &major_disappearing_link_hash;
2785 default: g_assert_not_reached ();
2789 static FinalizeEntryHashTable*
2790 get_finalize_entry_hash_table (int generation)
2792 switch (generation) {
2793 case GENERATION_NURSERY: return &minor_finalizable_hash;
2794 case GENERATION_OLD: return &major_finalizable_hash;
2795 default: g_assert_not_reached ();
2799 static void
2800 finish_gray_stack (char *start_addr, char *end_addr, int generation)
2802 TV_DECLARE (atv);
2803 TV_DECLARE (btv);
2804 int fin_ready;
2805 int ephemeron_rounds = 0;
2806 CopyOrMarkObjectFunc copy_func = current_collection_generation == GENERATION_NURSERY ? copy_object : major_copy_or_mark_object;
2809 * We copied all the reachable objects. Now it's the time to copy
2810 * the objects that were not referenced by the roots, but by the copied objects.
2811 * we built a stack of objects pointed to by gray_start: they are
2812 * additional roots and we may add more items as we go.
2813 * We loop until gray_start == gray_objects which means no more objects have
2814 * been added. Note this is iterative: no recursion is involved.
2815 * We need to walk the LO list as well in search of marked big objects
2816 * (use a flag since this is needed only on major collections). We need to loop
2817 * here as well, so keep a counter of marked LO (increasing it in copy_object).
2818 * To achieve better cache locality and cache usage, we drain the gray stack
2819 * frequently, after each object is copied, and just finish the work here.
2821 drain_gray_stack ();
2822 TV_GETTIME (atv);
2823 DEBUG (2, fprintf (gc_debug_file, "%s generation done\n", generation_name (generation)));
2824 /* walk the finalization queue and move also the objects that need to be
2825 * finalized: use the finalized objects as new roots so the objects they depend
2826 * on are also not reclaimed. As with the roots above, only objects in the nursery
2827 * are marked/copied.
2828 * We need a loop here, since objects ready for finalizers may reference other objects
2829 * that are fin-ready. Speedup with a flag?
2831 do {
2833 * Walk the ephemeron tables marking all values with reachable keys. This must be completely done
2834 * before processing finalizable objects to avoid finalizing reachable values.
2836 * It must be done inside the finalizaters loop since objects must not be removed from CWT tables
2837 * while they are been finalized.
2839 int done_with_ephemerons = 0;
2840 do {
2841 done_with_ephemerons = mark_ephemerons_in_range (copy_func, start_addr, end_addr);
2842 drain_gray_stack ();
2843 ++ephemeron_rounds;
2844 } while (!done_with_ephemerons);
2846 fin_ready = num_ready_finalizers;
2847 finalize_in_range (copy_func, start_addr, end_addr, generation);
2848 if (generation == GENERATION_OLD)
2849 finalize_in_range (copy_func, nursery_start, nursery_real_end, GENERATION_NURSERY);
2851 /* drain the new stack that might have been created */
2852 DEBUG (6, fprintf (gc_debug_file, "Precise scan of gray area post fin\n"));
2853 drain_gray_stack ();
2854 } while (fin_ready != num_ready_finalizers);
2857 * Clear ephemeron pairs with unreachable keys.
2858 * We pass the copy func so we can figure out if an array was promoted or not.
2860 clear_unreachable_ephemerons (copy_func, start_addr, end_addr);
2862 TV_GETTIME (btv);
2863 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));
2866 * handle disappearing links
2867 * Note we do this after checking the finalization queue because if an object
2868 * survives (at least long enough to be finalized) we don't clear the link.
2869 * This also deals with a possible issue with the monitor reclamation: with the Boehm
2870 * GC a finalized object my lose the monitor because it is cleared before the finalizer is
2871 * called.
2873 g_assert (gray_object_queue_is_empty ());
2874 for (;;) {
2875 null_link_in_range (copy_func, start_addr, end_addr, generation);
2876 if (generation == GENERATION_OLD)
2877 null_link_in_range (copy_func, start_addr, end_addr, GENERATION_NURSERY);
2878 if (gray_object_queue_is_empty ())
2879 break;
2880 drain_gray_stack ();
2883 g_assert (gray_object_queue_is_empty ());
2886 static void
2887 check_section_scan_starts (GCMemSection *section)
2889 int i;
2890 for (i = 0; i < section->num_scan_start; ++i) {
2891 if (section->scan_starts [i]) {
2892 guint size = safe_object_get_size ((MonoObject*) section->scan_starts [i]);
2893 g_assert (size >= sizeof (MonoObject) && size <= MAX_SMALL_OBJ_SIZE);
2898 static void
2899 check_scan_starts (void)
2901 if (!do_scan_starts_check)
2902 return;
2903 check_section_scan_starts (nursery_section);
2904 major_check_scan_starts ();
2907 static int last_num_pinned = 0;
2909 static void
2910 build_nursery_fragments (int start_pin, int end_pin)
2912 char *frag_start, *frag_end;
2913 size_t frag_size;
2914 int i;
2916 while (nursery_fragments) {
2917 Fragment *next = nursery_fragments->next;
2918 nursery_fragments->next = fragment_freelist;
2919 fragment_freelist = nursery_fragments;
2920 nursery_fragments = next;
2922 frag_start = nursery_start;
2923 fragment_total = 0;
2924 /* clear scan starts */
2925 memset (nursery_section->scan_starts, 0, nursery_section->num_scan_start * sizeof (gpointer));
2926 for (i = start_pin; i < end_pin; ++i) {
2927 frag_end = pin_queue [i];
2928 /* remove the pin bit from pinned objects */
2929 unpin_object (frag_end);
2930 nursery_section->scan_starts [((char*)frag_end - (char*)nursery_section->data)/SCAN_START_SIZE] = frag_end;
2931 frag_size = frag_end - frag_start;
2932 if (frag_size)
2933 add_nursery_frag (frag_size, frag_start, frag_end);
2934 frag_size = safe_object_get_size ((MonoObject*)pin_queue [i]);
2935 frag_size += ALLOC_ALIGN - 1;
2936 frag_size &= ~(ALLOC_ALIGN - 1);
2937 frag_start = (char*)pin_queue [i] + frag_size;
2939 nursery_last_pinned_end = frag_start;
2940 frag_end = nursery_real_end;
2941 frag_size = frag_end - frag_start;
2942 if (frag_size)
2943 add_nursery_frag (frag_size, frag_start, frag_end);
2944 if (!nursery_fragments) {
2945 DEBUG (1, fprintf (gc_debug_file, "Nursery fully pinned (%d)\n", end_pin - start_pin));
2946 for (i = start_pin; i < end_pin; ++i) {
2947 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])));
2949 degraded_mode = 1;
2952 nursery_next = nursery_frag_real_end = NULL;
2954 /* Clear TLABs for all threads */
2955 clear_tlabs ();
2958 static void
2959 scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type)
2961 int i;
2962 RootRecord *root;
2963 for (i = 0; i < roots_hash_size [root_type]; ++i) {
2964 for (root = roots_hash [root_type][i]; root; root = root->next) {
2965 DEBUG (6, fprintf (gc_debug_file, "Precise root scan %p-%p (desc: %p)\n", root->start_root, root->end_root, (void*)root->root_desc));
2966 precisely_scan_objects_from (copy_func, (void**)root->start_root, (void**)root->end_root, addr_start, addr_end, root->root_desc);
2971 static void
2972 dump_occupied (char *start, char *end, char *section_start)
2974 fprintf (heap_dump_file, "<occupied offset=\"%td\" size=\"%td\"/>\n", start - section_start, end - start);
2977 static void
2978 dump_section (GCMemSection *section, const char *type)
2980 char *start = section->data;
2981 char *end = section->data + section->size;
2982 char *occ_start = NULL;
2983 GCVTable *vt;
2984 char *old_start = NULL; /* just for debugging */
2986 fprintf (heap_dump_file, "<section type=\"%s\" size=\"%lu\">\n", type, (unsigned long)section->size);
2988 while (start < end) {
2989 guint size;
2990 MonoClass *class;
2992 if (!*(void**)start) {
2993 if (occ_start) {
2994 dump_occupied (occ_start, start, section->data);
2995 occ_start = NULL;
2997 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
2998 continue;
3000 g_assert (start < section->next_data);
3002 if (!occ_start)
3003 occ_start = start;
3005 vt = (GCVTable*)LOAD_VTABLE (start);
3006 class = vt->klass;
3008 size = safe_object_get_size ((MonoObject*) start);
3009 size += ALLOC_ALIGN - 1;
3010 size &= ~(ALLOC_ALIGN - 1);
3013 fprintf (heap_dump_file, "<object offset=\"%d\" class=\"%s.%s\" size=\"%d\"/>\n",
3014 start - section->data,
3015 vt->klass->name_space, vt->klass->name,
3016 size);
3019 old_start = start;
3020 start += size;
3022 if (occ_start)
3023 dump_occupied (occ_start, start, section->data);
3025 fprintf (heap_dump_file, "</section>\n");
3028 static void
3029 dump_object (MonoObject *obj, gboolean dump_location)
3031 static char class_name [1024];
3033 MonoClass *class = mono_object_class (obj);
3034 int i, j;
3037 * Python's XML parser is too stupid to parse angle brackets
3038 * in strings, so we just ignore them;
3040 i = j = 0;
3041 while (class->name [i] && j < sizeof (class_name) - 1) {
3042 if (!strchr ("<>\"", class->name [i]))
3043 class_name [j++] = class->name [i];
3044 ++i;
3046 g_assert (j < sizeof (class_name));
3047 class_name [j] = 0;
3049 fprintf (heap_dump_file, "<object class=\"%s.%s\" size=\"%d\"",
3050 class->name_space, class_name,
3051 safe_object_get_size (obj));
3052 if (dump_location) {
3053 const char *location;
3054 if (ptr_in_nursery (obj))
3055 location = "nursery";
3056 else if (safe_object_get_size (obj) <= MAX_SMALL_OBJ_SIZE)
3057 location = "major";
3058 else
3059 location = "LOS";
3060 fprintf (heap_dump_file, " location=\"%s\"", location);
3062 fprintf (heap_dump_file, "/>\n");
3065 static void
3066 dump_heap (const char *type, int num, const char *reason)
3068 static char const *internal_mem_names [] = { "pin-queue", "fragment", "section", "scan-starts",
3069 "fin-table", "finalize-entry", "dislink-table",
3070 "dislink", "roots-table", "root-record", "statistics",
3071 "remset", "gray-queue", "store-remset", "marksweep-tables",
3072 "marksweep-block-info", "ephemeron-link" };
3074 ObjectList *list;
3075 LOSObject *bigobj;
3076 int i;
3078 fprintf (heap_dump_file, "<collection type=\"%s\" num=\"%d\"", type, num);
3079 if (reason)
3080 fprintf (heap_dump_file, " reason=\"%s\"", reason);
3081 fprintf (heap_dump_file, ">\n");
3082 fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%lld\"/>\n", pinned_chunk_bytes_alloced);
3083 fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%lld\"/>\n", large_internal_bytes_alloced);
3084 fprintf (heap_dump_file, "<other-mem-usage type=\"mempools\" size=\"%ld\"/>\n", mono_mempool_get_bytes_allocated ());
3085 for (i = 0; i < INTERNAL_MEM_MAX; ++i)
3086 fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n", internal_mem_names [i], small_internal_mem_bytes [i]);
3087 fprintf (heap_dump_file, "<pinned type=\"stack\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_STACK]);
3088 /* fprintf (heap_dump_file, "<pinned type=\"static-data\" bytes=\"%d\"/>\n", pinned_byte_counts [PIN_TYPE_STATIC_DATA]); */
3089 fprintf (heap_dump_file, "<pinned type=\"other\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_OTHER]);
3091 fprintf (heap_dump_file, "<pinned-objects>\n");
3092 for (list = pinned_objects; list; list = list->next)
3093 dump_object (list->obj, TRUE);
3094 fprintf (heap_dump_file, "</pinned-objects>\n");
3096 dump_section (nursery_section, "nursery");
3098 major_dump_heap ();
3100 fprintf (heap_dump_file, "<los>\n");
3101 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
3102 dump_object ((MonoObject*)bigobj->data, FALSE);
3103 fprintf (heap_dump_file, "</los>\n");
3105 fprintf (heap_dump_file, "</collection>\n");
3108 static void
3109 init_stats (void)
3111 static gboolean inited = FALSE;
3113 if (inited)
3114 return;
3116 mono_counters_register ("Minor fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pre_collection_fragment_clear);
3117 mono_counters_register ("Minor pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pinning);
3118 mono_counters_register ("Minor scan remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_remsets);
3119 mono_counters_register ("Minor scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_pinned);
3120 mono_counters_register ("Minor scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_registered_roots);
3121 mono_counters_register ("Minor scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_thread_data);
3122 mono_counters_register ("Minor finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_finish_gray_stack);
3123 mono_counters_register ("Minor fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_fragment_creation);
3125 mono_counters_register ("Major fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pre_collection_fragment_clear);
3126 mono_counters_register ("Major pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pinning);
3127 mono_counters_register ("Major scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_pinned);
3128 mono_counters_register ("Major scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_registered_roots);
3129 mono_counters_register ("Major scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_thread_data);
3130 mono_counters_register ("Major scan alloc_pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_alloc_pinned);
3131 mono_counters_register ("Major scan finalized", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_finalized);
3132 mono_counters_register ("Major scan big objects", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_big_objects);
3133 mono_counters_register ("Major finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_finish_gray_stack);
3134 mono_counters_register ("Major free big objects", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_free_bigobjs);
3135 mono_counters_register ("Major LOS sweep", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_los_sweep);
3136 mono_counters_register ("Major sweep", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_sweep);
3137 mono_counters_register ("Major fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_fragment_creation);
3139 #ifdef HEAVY_STATISTICS
3140 mono_counters_register ("WBarrier set field", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_field);
3141 mono_counters_register ("WBarrier set arrayref", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_arrayref);
3142 mono_counters_register ("WBarrier arrayref copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_arrayref_copy);
3143 mono_counters_register ("WBarrier generic store called", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store);
3144 mono_counters_register ("WBarrier generic store stored", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store_remset);
3145 mono_counters_register ("WBarrier set root", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_root);
3146 mono_counters_register ("WBarrier value copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_value_copy);
3147 mono_counters_register ("WBarrier object copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_object_copy);
3149 mono_counters_register ("# objects allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced);
3150 mono_counters_register ("bytes allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced);
3151 mono_counters_register ("# objects allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced_degraded);
3152 mono_counters_register ("bytes allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_degraded);
3153 mono_counters_register ("bytes allocated in LOS", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_los);
3155 mono_counters_register ("# copy_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_nursery);
3156 mono_counters_register ("# objects copied (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_nursery);
3157 mono_counters_register ("# copy_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_major);
3158 mono_counters_register ("# objects copied (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_major);
3160 mono_counters_register ("# scan_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_nursery);
3161 mono_counters_register ("# scan_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_major);
3163 mono_counters_register ("# nursery copy_object() failed from space", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_from_space);
3164 mono_counters_register ("# nursery copy_object() failed forwarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_forwarded);
3165 mono_counters_register ("# nursery copy_object() failed pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_pinned);
3167 mono_counters_register ("# wasted fragments used", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_used);
3168 mono_counters_register ("bytes in wasted fragments", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_wasted_fragments_bytes);
3170 mono_counters_register ("Store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets);
3171 mono_counters_register ("Unique store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets_unique);
3172 mono_counters_register ("Saved remsets 1", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_1);
3173 mono_counters_register ("Saved remsets 2", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_2);
3174 mono_counters_register ("Global remsets added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_added);
3175 mono_counters_register ("Global remsets re-added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_readded);
3176 mono_counters_register ("Global remsets processed", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_processed);
3177 mono_counters_register ("Global remsets discarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_discarded);
3179 #endif
3181 inited = TRUE;
3184 static gboolean
3185 need_major_collection (void)
3187 mword los_alloced = los_memory_usage - MIN (last_los_memory_usage, los_memory_usage);
3188 return minor_collection_sections_alloced * MAJOR_SECTION_SIZE + los_alloced > minor_collection_allowance;
3192 * Collect objects in the nursery. Returns whether to trigger a major
3193 * collection.
3195 static gboolean
3196 collect_nursery (size_t requested_size)
3198 size_t max_garbage_amount;
3199 char *orig_nursery_next;
3200 TV_DECLARE (all_atv);
3201 TV_DECLARE (all_btv);
3202 TV_DECLARE (atv);
3203 TV_DECLARE (btv);
3205 current_collection_generation = GENERATION_NURSERY;
3207 init_stats ();
3208 binary_protocol_collection (GENERATION_NURSERY);
3209 check_scan_starts ();
3211 degraded_mode = 0;
3212 orig_nursery_next = nursery_next;
3213 nursery_next = MAX (nursery_next, nursery_last_pinned_end);
3214 /* FIXME: optimize later to use the higher address where an object can be present */
3215 nursery_next = MAX (nursery_next, nursery_real_end);
3217 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)));
3218 max_garbage_amount = nursery_next - nursery_start;
3219 g_assert (nursery_section->size >= max_garbage_amount);
3221 /* world must be stopped already */
3222 TV_GETTIME (all_atv);
3223 TV_GETTIME (atv);
3225 /* Pinning depends on this */
3226 clear_nursery_fragments (orig_nursery_next);
3228 TV_GETTIME (btv);
3229 time_minor_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
3231 if (xdomain_checks)
3232 check_for_xdomain_refs ();
3234 nursery_section->next_data = nursery_next;
3236 major_start_nursery_collection ();
3238 gray_object_queue_init ();
3240 num_minor_gcs++;
3241 mono_stats.minor_gc_count ++;
3243 global_remset_cache_clear ();
3245 /* pin from pinned handles */
3246 init_pinning ();
3247 pin_from_roots (nursery_start, nursery_next);
3248 /* identify pinned objects */
3249 optimize_pin_queue (0);
3250 next_pin_slot = pin_objects_from_addresses (nursery_section, pin_queue, pin_queue + next_pin_slot, nursery_start, nursery_next);
3251 nursery_section->pin_queue_start = 0;
3252 nursery_section->pin_queue_end = next_pin_slot;
3253 TV_GETTIME (atv);
3254 time_minor_pinning += TV_ELAPSED_MS (btv, atv);
3255 DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (btv, atv)));
3256 DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
3258 if (consistency_check_at_minor_collection)
3259 check_consistency ();
3262 * walk all the roots and copy the young objects to the old generation,
3263 * starting from to_space
3266 scan_from_remsets (nursery_start, nursery_next);
3267 /* we don't have complete write barrier yet, so we scan all the old generation sections */
3268 TV_GETTIME (btv);
3269 time_minor_scan_remsets += TV_ELAPSED_MS (atv, btv);
3270 DEBUG (2, fprintf (gc_debug_file, "Old generation scan: %d usecs\n", TV_ELAPSED (atv, btv)));
3272 drain_gray_stack ();
3274 TV_GETTIME (atv);
3275 time_minor_scan_pinned += TV_ELAPSED_MS (btv, atv);
3276 /* registered roots, this includes static fields */
3277 scan_from_registered_roots (copy_object, nursery_start, nursery_next, ROOT_TYPE_NORMAL);
3278 scan_from_registered_roots (copy_object, nursery_start, nursery_next, ROOT_TYPE_WBARRIER);
3279 TV_GETTIME (btv);
3280 time_minor_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
3281 /* thread data */
3282 scan_thread_data (nursery_start, nursery_next, TRUE);
3283 TV_GETTIME (atv);
3284 time_minor_scan_thread_data += TV_ELAPSED_MS (btv, atv);
3285 btv = atv;
3287 finish_gray_stack (nursery_start, nursery_next, GENERATION_NURSERY);
3288 TV_GETTIME (atv);
3289 time_minor_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
3291 /* walk the pin_queue, build up the fragment list of free memory, unmark
3292 * pinned objects as we go, memzero() the empty fragments so they are ready for the
3293 * next allocations.
3295 build_nursery_fragments (0, next_pin_slot);
3296 TV_GETTIME (btv);
3297 time_minor_fragment_creation += TV_ELAPSED_MS (atv, btv);
3298 DEBUG (2, fprintf (gc_debug_file, "Fragment creation: %d usecs, %lu bytes available\n", TV_ELAPSED (atv, btv), (unsigned long)fragment_total));
3300 if (consistency_check_at_minor_collection)
3301 check_major_refs ();
3303 major_finish_nursery_collection ();
3305 TV_GETTIME (all_btv);
3306 mono_stats.minor_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
3308 if (heap_dump_file)
3309 dump_heap ("minor", num_minor_gcs - 1, NULL);
3311 /* prepare the pin queue for the next collection */
3312 last_num_pinned = next_pin_slot;
3313 next_pin_slot = 0;
3314 if (fin_ready_list || critical_fin_list) {
3315 DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
3316 mono_gc_finalize_notify ();
3318 pin_stats_reset ();
3320 g_assert (gray_object_queue_is_empty ());
3322 check_scan_starts ();
3324 binary_protocol_flush_buffers ();
3326 current_collection_generation = -1;
3328 return need_major_collection ();
3331 static void
3332 major_do_collection (const char *reason)
3334 LOSObject *bigobj, *prevbo;
3335 TV_DECLARE (all_atv);
3336 TV_DECLARE (all_btv);
3337 TV_DECLARE (atv);
3338 TV_DECLARE (btv);
3339 /* FIXME: only use these values for the precise scan
3340 * note that to_space pointers should be excluded anyway...
3342 char *heap_start = NULL;
3343 char *heap_end = (char*)-1;
3344 int old_num_major_sections = num_major_sections;
3345 int num_major_sections_saved, save_target, allowance_target;
3346 mword los_memory_saved, los_memory_alloced, old_los_memory_usage;
3349 * A domain could have been freed, resulting in
3350 * los_memory_usage being less than last_los_memory_usage.
3352 los_memory_alloced = los_memory_usage - MIN (last_los_memory_usage, los_memory_usage);
3353 old_los_memory_usage = los_memory_usage;
3355 //count_ref_nonref_objs ();
3356 //consistency_check ();
3358 init_stats ();
3359 binary_protocol_collection (GENERATION_OLD);
3360 check_scan_starts ();
3361 gray_object_queue_init ();
3363 degraded_mode = 0;
3364 DEBUG (1, fprintf (gc_debug_file, "Start major collection %d\n", num_major_gcs));
3365 num_major_gcs++;
3366 mono_stats.major_gc_count ++;
3368 /* world must be stopped already */
3369 TV_GETTIME (all_atv);
3370 TV_GETTIME (atv);
3372 /* Pinning depends on this */
3373 clear_nursery_fragments (nursery_next);
3375 TV_GETTIME (btv);
3376 time_major_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
3378 if (xdomain_checks)
3379 check_for_xdomain_refs ();
3381 nursery_section->next_data = nursery_real_end;
3382 /* we should also coalesce scanning from sections close to each other
3383 * and deal with pointers outside of the sections later.
3385 /* The remsets are not useful for a major collection */
3386 clear_remsets ();
3387 global_remset_cache_clear ();
3389 TV_GETTIME (atv);
3390 init_pinning ();
3391 DEBUG (6, fprintf (gc_debug_file, "Collecting pinned addresses\n"));
3392 pin_from_roots ((void*)lowest_heap_address, (void*)highest_heap_address);
3393 optimize_pin_queue (0);
3396 * pin_queue now contains all candidate pointers, sorted and
3397 * uniqued. We must do two passes now to figure out which
3398 * objects are pinned.
3400 * The first is to find within the pin_queue the area for each
3401 * section. This requires that the pin_queue be sorted. We
3402 * also process the LOS objects and pinned chunks here.
3404 * The second, destructive, pass is to reduce the section
3405 * areas to pointers to the actually pinned objects.
3407 DEBUG (6, fprintf (gc_debug_file, "Pinning from sections\n"));
3408 /* first pass for the sections */
3409 find_section_pin_queue_start_end (nursery_section);
3410 major_find_pin_queue_start_ends ();
3411 /* identify possible pointers to the insize of large objects */
3412 DEBUG (6, fprintf (gc_debug_file, "Pinning from large objects\n"));
3413 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
3414 int start, end;
3415 find_optimized_pin_queue_area (bigobj->data, (char*)bigobj->data + bigobj->size, &start, &end);
3416 if (start != end) {
3417 pin_object (bigobj->data);
3418 /* FIXME: only enqueue if object has references */
3419 GRAY_OBJECT_ENQUEUE (bigobj->data);
3420 if (heap_dump_file)
3421 pin_stats_register_object ((char*) bigobj->data, safe_object_get_size ((MonoObject*) bigobj->data));
3422 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));
3425 /* second pass for the sections */
3426 pin_objects_in_section (nursery_section);
3427 major_pin_objects ();
3429 TV_GETTIME (btv);
3430 time_major_pinning += TV_ELAPSED_MS (atv, btv);
3431 DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (atv, btv)));
3432 DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
3434 major_init_to_space ();
3436 drain_gray_stack ();
3438 TV_GETTIME (atv);
3439 time_major_scan_pinned += TV_ELAPSED_MS (btv, atv);
3441 /* registered roots, this includes static fields */
3442 scan_from_registered_roots (major_copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_NORMAL);
3443 scan_from_registered_roots (major_copy_or_mark_object, heap_start, heap_end, ROOT_TYPE_WBARRIER);
3444 TV_GETTIME (btv);
3445 time_major_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
3447 /* Threads */
3448 /* FIXME: This is the wrong place for this, because it does
3449 pinning */
3450 scan_thread_data (heap_start, heap_end, TRUE);
3451 TV_GETTIME (atv);
3452 time_major_scan_thread_data += TV_ELAPSED_MS (btv, atv);
3454 TV_GETTIME (btv);
3455 time_major_scan_alloc_pinned += TV_ELAPSED_MS (atv, btv);
3457 /* scan the list of objects ready for finalization */
3458 scan_finalizer_entries (major_copy_or_mark_object, fin_ready_list);
3459 scan_finalizer_entries (major_copy_or_mark_object, critical_fin_list);
3460 TV_GETTIME (atv);
3461 time_major_scan_finalized += TV_ELAPSED_MS (btv, atv);
3462 DEBUG (2, fprintf (gc_debug_file, "Root scan: %d usecs\n", TV_ELAPSED (btv, atv)));
3464 TV_GETTIME (btv);
3465 time_major_scan_big_objects += TV_ELAPSED_MS (atv, btv);
3467 /* all the objects in the heap */
3468 finish_gray_stack (heap_start, heap_end, GENERATION_OLD);
3469 TV_GETTIME (atv);
3470 time_major_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
3472 /* sweep the big objects list */
3473 prevbo = NULL;
3474 for (bigobj = los_object_list; bigobj;) {
3475 if (object_is_pinned (bigobj->data)) {
3476 unpin_object (bigobj->data);
3477 } else {
3478 LOSObject *to_free;
3479 /* not referenced anywhere, so we can free it */
3480 if (prevbo)
3481 prevbo->next = bigobj->next;
3482 else
3483 los_object_list = bigobj->next;
3484 to_free = bigobj;
3485 bigobj = bigobj->next;
3486 free_large_object (to_free);
3487 continue;
3489 prevbo = bigobj;
3490 bigobj = bigobj->next;
3493 TV_GETTIME (btv);
3494 time_major_free_bigobjs += TV_ELAPSED_MS (atv, btv);
3496 los_sweep ();
3498 TV_GETTIME (atv);
3499 time_major_los_sweep += TV_ELAPSED_MS (btv, atv);
3501 major_sweep ();
3503 TV_GETTIME (btv);
3504 time_major_sweep += TV_ELAPSED_MS (atv, btv);
3506 /* walk the pin_queue, build up the fragment list of free memory, unmark
3507 * pinned objects as we go, memzero() the empty fragments so they are ready for the
3508 * next allocations.
3510 build_nursery_fragments (nursery_section->pin_queue_start, nursery_section->pin_queue_end);
3512 TV_GETTIME (atv);
3513 time_major_fragment_creation += TV_ELAPSED_MS (btv, atv);
3515 TV_GETTIME (all_btv);
3516 mono_stats.major_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
3518 if (heap_dump_file)
3519 dump_heap ("major", num_major_gcs - 1, reason);
3521 /* prepare the pin queue for the next collection */
3522 next_pin_slot = 0;
3523 if (fin_ready_list || critical_fin_list) {
3524 DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
3525 mono_gc_finalize_notify ();
3527 pin_stats_reset ();
3529 g_assert (gray_object_queue_is_empty ());
3531 num_major_sections_saved = MAX (old_num_major_sections - num_major_sections, 0);
3532 los_memory_saved = MAX (old_los_memory_usage - los_memory_usage, 1);
3534 save_target = ((num_major_sections * MAJOR_SECTION_SIZE) + los_memory_saved) / 2;
3536 * We aim to allow the allocation of as many sections as is
3537 * necessary to reclaim save_target sections in the next
3538 * collection. We assume the collection pattern won't change.
3539 * In the last cycle, we had num_major_sections_saved for
3540 * minor_collection_sections_alloced. Assuming things won't
3541 * change, this must be the same ratio as save_target for
3542 * allowance_target, i.e.
3544 * num_major_sections_saved save_target
3545 * --------------------------------- == ----------------
3546 * minor_collection_sections_alloced allowance_target
3548 * hence:
3550 allowance_target = (mword)((double)save_target * (double)(minor_collection_sections_alloced * MAJOR_SECTION_SIZE + los_memory_alloced) / (double)(num_major_sections_saved * MAJOR_SECTION_SIZE + los_memory_saved));
3552 minor_collection_allowance = MAX (MIN (allowance_target, num_major_sections * MAJOR_SECTION_SIZE + los_memory_usage), MIN_MINOR_COLLECTION_ALLOWANCE);
3554 minor_collection_sections_alloced = 0;
3555 last_los_memory_usage = los_memory_usage;
3557 major_finish_major_collection ();
3559 check_scan_starts ();
3561 binary_protocol_flush_buffers ();
3563 //consistency_check ();
3566 static void
3567 major_collection (const char *reason)
3569 if (g_getenv ("MONO_GC_NO_MAJOR")) {
3570 collect_nursery (0);
3571 return;
3574 current_collection_generation = GENERATION_OLD;
3575 major_do_collection (reason);
3576 current_collection_generation = -1;
3580 * When deciding if it's better to collect or to expand, keep track
3581 * of how much garbage was reclaimed with the last collection: if it's too
3582 * little, expand.
3583 * This is called when we could not allocate a small object.
3585 static void __attribute__((noinline))
3586 minor_collect_or_expand_inner (size_t size)
3588 int do_minor_collection = 1;
3590 if (!nursery_section) {
3591 alloc_nursery ();
3592 return;
3594 if (do_minor_collection) {
3595 stop_world ();
3596 if (collect_nursery (size))
3597 major_collection ("minor overflow");
3598 DEBUG (2, fprintf (gc_debug_file, "Heap size: %lu, LOS size: %lu\n", (unsigned long)total_alloc, (unsigned long)los_memory_usage));
3599 restart_world ();
3600 /* this also sets the proper pointers for the next allocation */
3601 if (!search_fragment_for_size (size)) {
3602 int i;
3603 /* TypeBuilder and MonoMethod are killing mcs with fragmentation */
3604 DEBUG (1, fprintf (gc_debug_file, "nursery collection didn't find enough room for %zd alloc (%d pinned)\n", size, last_num_pinned));
3605 for (i = 0; i < last_num_pinned; ++i) {
3606 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])));
3608 degraded_mode = 1;
3611 //report_internal_mem_usage ();
3615 * ######################################################################
3616 * ######## Memory allocation from the OS
3617 * ######################################################################
3618 * This section of code deals with getting memory from the OS and
3619 * allocating memory for GC-internal data structures.
3620 * Internal memory can be handled with a freelist for small objects.
3624 * Allocate a big chunk of memory from the OS (usually 64KB to several megabytes).
3625 * This must not require any lock.
3627 static void*
3628 get_os_memory (size_t size, int activate)
3630 void *ptr;
3631 unsigned long prot_flags = activate? MONO_MMAP_READ|MONO_MMAP_WRITE: MONO_MMAP_NONE;
3633 prot_flags |= MONO_MMAP_PRIVATE | MONO_MMAP_ANON;
3634 size += pagesize - 1;
3635 size &= ~(pagesize - 1);
3636 ptr = mono_valloc (0, size, prot_flags);
3637 return ptr;
3641 * Free the memory returned by get_os_memory (), returning it to the OS.
3643 static void
3644 free_os_memory (void *addr, size_t size)
3646 mono_vfree (addr, size);
3650 * Debug reporting.
3652 static void
3653 report_pinned_chunk (PinnedChunk *chunk, int seq) {
3654 void **p;
3655 int i, free_pages, num_free, free_mem;
3656 free_pages = 0;
3657 for (i = 0; i < chunk->num_pages; ++i) {
3658 if (!chunk->page_sizes [i])
3659 free_pages++;
3661 printf ("Pinned chunk %d at %p, size: %d, pages: %d, free: %d\n", seq, chunk, chunk->num_pages * FREELIST_PAGESIZE, chunk->num_pages, free_pages);
3662 free_mem = FREELIST_PAGESIZE * free_pages;
3663 for (i = 0; i < FREELIST_NUM_SLOTS; ++i) {
3664 if (!chunk->free_list [i])
3665 continue;
3666 num_free = 0;
3667 p = chunk->free_list [i];
3668 while (p) {
3669 num_free++;
3670 p = *p;
3672 printf ("\tfree list of size %d, %d items\n", freelist_sizes [i], num_free);
3673 free_mem += freelist_sizes [i] * num_free;
3675 printf ("\tfree memory in chunk: %d\n", free_mem);
3679 * Debug reporting.
3681 static G_GNUC_UNUSED void
3682 report_internal_mem_usage (void) {
3683 PinnedChunk *chunk;
3684 int i;
3685 printf ("Internal memory usage:\n");
3686 i = 0;
3687 for (chunk = internal_chunk_list; chunk; chunk = chunk->block.next) {
3688 report_pinned_chunk (chunk, i++);
3690 printf ("Pinned memory usage:\n");
3691 major_report_pinned_memory_usage ();
3695 * Find the slot number in the freelist for memory chunks that
3696 * can contain @size objects.
3698 static int
3699 slot_for_size (size_t size)
3701 int slot;
3702 /* do a binary search or lookup table later. */
3703 for (slot = 0; slot < FREELIST_NUM_SLOTS; ++slot) {
3704 if (freelist_sizes [slot] >= size)
3705 return slot;
3707 g_assert_not_reached ();
3708 return -1;
3712 * Build a free list for @size memory chunks from the memory area between
3713 * start_page and end_page.
3715 static void
3716 build_freelist (PinnedChunk *chunk, int slot, int size, char *start_page, char *end_page)
3718 void **p, **end;
3719 int count = 0;
3720 /*g_print ("building freelist for slot %d, size %d in %p\n", slot, size, chunk);*/
3721 p = (void**)start_page;
3722 end = (void**)(end_page - size);
3723 g_assert (!chunk->free_list [slot]);
3724 chunk->free_list [slot] = p;
3725 while ((char*)p + size <= (char*)end) {
3726 count++;
3727 *p = (void*)((char*)p + size);
3728 p = *p;
3730 *p = NULL;
3731 /*g_print ("%d items created, max: %d\n", count, (end_page - start_page) / size);*/
3734 static PinnedChunk*
3735 alloc_pinned_chunk (void)
3737 PinnedChunk *chunk;
3738 int offset;
3739 int size = PINNED_CHUNK_SIZE;
3741 chunk = get_os_memory_aligned (size, size, TRUE);
3742 chunk->block.role = MEMORY_ROLE_PINNED;
3744 UPDATE_HEAP_BOUNDARIES (chunk, ((char*)chunk + size));
3745 total_alloc += size;
3746 pinned_chunk_bytes_alloced += size;
3748 /* setup the bookeeping fields */
3749 chunk->num_pages = size / FREELIST_PAGESIZE;
3750 offset = G_STRUCT_OFFSET (PinnedChunk, data);
3751 chunk->page_sizes = (void*)((char*)chunk + offset);
3752 offset += sizeof (int) * chunk->num_pages;
3753 offset += ALLOC_ALIGN - 1;
3754 offset &= ~(ALLOC_ALIGN - 1);
3755 chunk->free_list = (void*)((char*)chunk + offset);
3756 offset += sizeof (void*) * FREELIST_NUM_SLOTS;
3757 offset += ALLOC_ALIGN - 1;
3758 offset &= ~(ALLOC_ALIGN - 1);
3759 chunk->start_data = (void*)((char*)chunk + offset);
3761 /* allocate the first page to the freelist */
3762 chunk->page_sizes [0] = PINNED_FIRST_SLOT_SIZE;
3763 build_freelist (chunk, slot_for_size (PINNED_FIRST_SLOT_SIZE), PINNED_FIRST_SLOT_SIZE, chunk->start_data, ((char*)chunk + FREELIST_PAGESIZE));
3764 DEBUG (4, fprintf (gc_debug_file, "Allocated pinned chunk %p, size: %d\n", chunk, size));
3765 return chunk;
3768 /* assumes freelist for slot is empty, so try to alloc a new page */
3769 static void*
3770 get_chunk_freelist (PinnedChunk *chunk, int slot)
3772 int i;
3773 void **p;
3774 p = chunk->free_list [slot];
3775 if (p) {
3776 chunk->free_list [slot] = *p;
3777 return p;
3779 for (i = 0; i < chunk->num_pages; ++i) {
3780 int size;
3781 if (chunk->page_sizes [i])
3782 continue;
3783 size = freelist_sizes [slot];
3784 chunk->page_sizes [i] = size;
3785 build_freelist (chunk, slot, size, (char*)chunk + FREELIST_PAGESIZE * i, (char*)chunk + FREELIST_PAGESIZE * (i + 1));
3786 break;
3788 /* try again */
3789 p = chunk->free_list [slot];
3790 if (p) {
3791 chunk->free_list [slot] = *p;
3792 return p;
3794 return NULL;
3797 /* used for the GC-internal data structures */
3798 static void*
3799 get_internal_mem (size_t size, int type)
3801 int slot;
3802 void *res = NULL;
3803 PinnedChunk *pchunk;
3805 if (size > freelist_sizes [FREELIST_NUM_SLOTS - 1]) {
3806 LargeInternalMemHeader *mh;
3808 size += sizeof (LargeInternalMemHeader);
3809 mh = get_os_memory (size, TRUE);
3810 mh->magic = LARGE_INTERNAL_MEM_HEADER_MAGIC;
3811 mh->size = size;
3813 large_internal_bytes_alloced += size;
3815 return mh->data;
3818 slot = slot_for_size (size);
3819 g_assert (size <= freelist_sizes [slot]);
3821 small_internal_mem_bytes [type] += freelist_sizes [slot];
3823 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3824 void **p = pchunk->free_list [slot];
3825 if (p) {
3826 pchunk->free_list [slot] = *p;
3827 memset (p, 0, size);
3828 return p;
3831 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3832 res = get_chunk_freelist (pchunk, slot);
3833 if (res) {
3834 memset (res, 0, size);
3835 return res;
3838 pchunk = alloc_pinned_chunk ();
3839 /* FIXME: handle OOM */
3840 pchunk->block.next = internal_chunk_list;
3841 internal_chunk_list = pchunk;
3842 res = get_chunk_freelist (pchunk, slot);
3843 memset (res, 0, size);
3844 return res;
3847 static void
3848 free_internal_mem (void *addr, int type)
3850 PinnedChunk *pchunk;
3851 LargeInternalMemHeader *mh;
3852 if (!addr)
3853 return;
3854 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3855 /*printf ("trying to free %p in %p (pages: %d)\n", addr, pchunk, pchunk->num_pages);*/
3856 if (addr >= (void*)pchunk && (char*)addr < (char*)pchunk + pchunk->num_pages * FREELIST_PAGESIZE) {
3857 int offset = (char*)addr - (char*)pchunk;
3858 int page = offset / FREELIST_PAGESIZE;
3859 int slot = slot_for_size (pchunk->page_sizes [page]);
3860 void **p = addr;
3861 *p = pchunk->free_list [slot];
3862 pchunk->free_list [slot] = p;
3864 small_internal_mem_bytes [type] -= freelist_sizes [slot];
3866 return;
3869 mh = (LargeInternalMemHeader*)((char*)addr - G_STRUCT_OFFSET (LargeInternalMemHeader, data));
3870 g_assert (mh->magic == LARGE_INTERNAL_MEM_HEADER_MAGIC);
3871 large_internal_bytes_alloced -= mh->size;
3872 free_os_memory (mh, mh->size);
3876 * ######################################################################
3877 * ######## Object allocation
3878 * ######################################################################
3879 * This section of code deals with allocating memory for objects.
3880 * There are several ways:
3881 * *) allocate large objects
3882 * *) allocate normal objects
3883 * *) fast lock-free allocation
3884 * *) allocation of pinned objects
3887 static void
3888 setup_fragment (Fragment *frag, Fragment *prev, size_t size)
3890 /* remove from the list */
3891 if (prev)
3892 prev->next = frag->next;
3893 else
3894 nursery_fragments = frag->next;
3895 nursery_next = frag->fragment_start;
3896 nursery_frag_real_end = frag->fragment_end;
3898 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));
3899 frag->next = fragment_freelist;
3900 fragment_freelist = frag;
3903 /* check if we have a suitable fragment in nursery_fragments to be able to allocate
3904 * an object of size @size
3905 * Return FALSE if not found (which means we need a collection)
3907 static gboolean
3908 search_fragment_for_size (size_t size)
3910 Fragment *frag, *prev;
3911 DEBUG (4, fprintf (gc_debug_file, "Searching nursery fragment %p, size: %zd\n", nursery_frag_real_end, size));
3913 if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
3914 /* Clear the remaining space, pinning depends on this */
3915 memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
3917 prev = NULL;
3918 for (frag = nursery_fragments; frag; frag = frag->next) {
3919 if (size <= (frag->fragment_end - frag->fragment_start)) {
3920 setup_fragment (frag, prev, size);
3921 return TRUE;
3923 prev = frag;
3925 return FALSE;
3929 * Same as search_fragment_for_size but if search for @desired_size fails, try to satisfy @minimum_size.
3930 * This improves nursery usage.
3932 static int
3933 search_fragment_for_size_range (size_t desired_size, size_t minimum_size)
3935 Fragment *frag, *prev, *min_prev;
3936 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));
3938 if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
3939 /* Clear the remaining space, pinning depends on this */
3940 memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
3942 min_prev = GINT_TO_POINTER (-1);
3943 prev = NULL;
3945 for (frag = nursery_fragments; frag; frag = frag->next) {
3946 int frag_size = frag->fragment_end - frag->fragment_start;
3947 if (desired_size <= frag_size) {
3948 setup_fragment (frag, prev, desired_size);
3949 return desired_size;
3951 if (minimum_size <= frag_size)
3952 min_prev = prev;
3954 prev = frag;
3957 if (min_prev != GINT_TO_POINTER (-1)) {
3958 int frag_size;
3959 if (min_prev)
3960 frag = min_prev->next;
3961 else
3962 frag = nursery_fragments;
3964 frag_size = frag->fragment_end - frag->fragment_start;
3965 HEAVY_STAT (++stat_wasted_fragments_used);
3966 HEAVY_STAT (stat_wasted_fragments_bytes += frag_size);
3968 setup_fragment (frag, min_prev, minimum_size);
3969 return frag_size;
3972 return 0;
3975 static void*
3976 alloc_degraded (MonoVTable *vtable, size_t size)
3978 if (need_major_collection ()) {
3979 stop_world ();
3980 major_collection ("degraded overflow");
3981 restart_world ();
3984 return major_alloc_degraded (vtable, size);
3988 * Provide a variant that takes just the vtable for small fixed-size objects.
3989 * The aligned size is already computed and stored in vt->gc_descr.
3990 * Note: every SCAN_START_SIZE or so we are given the chance to do some special
3991 * processing. We can keep track of where objects start, for example,
3992 * so when we scan the thread stacks for pinned objects, we can start
3993 * a search for the pinned object in SCAN_START_SIZE chunks.
3995 static void*
3996 mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
3998 /* FIXME: handle OOM */
3999 void **p;
4000 char *new_next;
4001 TLAB_ACCESS_INIT;
4003 HEAVY_STAT (++stat_objects_alloced);
4004 if (size <= MAX_SMALL_OBJ_SIZE)
4005 HEAVY_STAT (stat_bytes_alloced += size);
4006 else
4007 HEAVY_STAT (stat_bytes_alloced_los += size);
4009 size += ALLOC_ALIGN - 1;
4010 size &= ~(ALLOC_ALIGN - 1);
4012 g_assert (vtable->gc_descr);
4014 if (G_UNLIKELY (collect_before_allocs)) {
4015 if (nursery_section) {
4016 stop_world ();
4017 collect_nursery (0);
4018 restart_world ();
4019 if (!degraded_mode && !search_fragment_for_size (size)) {
4020 // FIXME:
4021 g_assert_not_reached ();
4027 * We must already have the lock here instead of after the
4028 * fast path because we might be interrupted in the fast path
4029 * (after confirming that new_next < TLAB_TEMP_END) by the GC,
4030 * and we'll end up allocating an object in a fragment which
4031 * no longer belongs to us.
4033 * The managed allocator does not do this, but it's treated
4034 * specially by the world-stopping code.
4037 if (size > MAX_SMALL_OBJ_SIZE) {
4038 p = alloc_large_inner (vtable, size);
4039 } else {
4040 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
4042 p = (void**)TLAB_NEXT;
4043 /* FIXME: handle overflow */
4044 new_next = (char*)p + size;
4045 TLAB_NEXT = new_next;
4047 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
4048 /* Fast path */
4051 * FIXME: We might need a memory barrier here so the change to tlab_next is
4052 * visible before the vtable store.
4055 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
4056 binary_protocol_alloc (p , vtable, size);
4057 g_assert (*p == NULL);
4058 *p = vtable;
4060 g_assert (TLAB_NEXT == new_next);
4062 return p;
4065 /* Slow path */
4067 /* there are two cases: the object is too big or we run out of space in the TLAB */
4068 /* we also reach here when the thread does its first allocation after a minor
4069 * collection, since the tlab_ variables are initialized to NULL.
4070 * there can be another case (from ORP), if we cooperate with the runtime a bit:
4071 * objects that need finalizers can have the high bit set in their size
4072 * so the above check fails and we can readily add the object to the queue.
4073 * This avoids taking again the GC lock when registering, but this is moot when
4074 * doing thread-local allocation, so it may not be a good idea.
4076 g_assert (TLAB_NEXT == new_next);
4077 if (TLAB_NEXT >= TLAB_REAL_END) {
4079 * Run out of space in the TLAB. When this happens, some amount of space
4080 * remains in the TLAB, but not enough to satisfy the current allocation
4081 * request. Currently, we retire the TLAB in all cases, later we could
4082 * keep it if the remaining space is above a treshold, and satisfy the
4083 * allocation directly from the nursery.
4085 TLAB_NEXT -= size;
4086 /* when running in degraded mode, we continue allocing that way
4087 * for a while, to decrease the number of useless nursery collections.
4089 if (degraded_mode && degraded_mode < DEFAULT_NURSERY_SIZE) {
4090 p = alloc_degraded (vtable, size);
4091 binary_protocol_alloc_degraded (p, vtable, size);
4092 return p;
4095 /*FIXME This codepath is current deadcode since tlab_size > MAX_SMALL_OBJ_SIZE*/
4096 if (size > tlab_size) {
4097 /* Allocate directly from the nursery */
4098 if (nursery_next + size >= nursery_frag_real_end) {
4099 if (!search_fragment_for_size (size)) {
4100 minor_collect_or_expand_inner (size);
4101 if (degraded_mode) {
4102 p = alloc_degraded (vtable, size);
4103 binary_protocol_alloc_degraded (p, vtable, size);
4104 return p;
4109 p = (void*)nursery_next;
4110 nursery_next += size;
4111 if (nursery_next > nursery_frag_real_end) {
4112 // no space left
4113 g_assert (0);
4116 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
4117 memset (p, 0, size);
4118 } else {
4119 int alloc_size = tlab_size;
4120 int available_in_nursery = nursery_frag_real_end - nursery_next;
4121 if (TLAB_START)
4122 DEBUG (3, fprintf (gc_debug_file, "Retire TLAB: %p-%p [%ld]\n", TLAB_START, TLAB_REAL_END, (long)(TLAB_REAL_END - TLAB_NEXT - size)));
4124 if (alloc_size >= available_in_nursery) {
4125 if (available_in_nursery > MAX_NURSERY_TLAB_WASTE && available_in_nursery > size) {
4126 alloc_size = available_in_nursery;
4127 } else {
4128 alloc_size = search_fragment_for_size_range (tlab_size, size);
4129 if (!alloc_size) {
4130 alloc_size = tlab_size;
4131 minor_collect_or_expand_inner (tlab_size);
4132 if (degraded_mode) {
4133 p = alloc_degraded (vtable, size);
4134 binary_protocol_alloc_degraded (p, vtable, size);
4135 return p;
4141 /* Allocate a new TLAB from the current nursery fragment */
4142 TLAB_START = nursery_next;
4143 nursery_next += alloc_size;
4144 TLAB_NEXT = TLAB_START;
4145 TLAB_REAL_END = TLAB_START + alloc_size;
4146 TLAB_TEMP_END = TLAB_START + MIN (SCAN_START_SIZE, alloc_size);
4148 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
4149 memset (TLAB_START, 0, alloc_size);
4151 /* Allocate from the TLAB */
4152 p = (void*)TLAB_NEXT;
4153 TLAB_NEXT += size;
4154 g_assert (TLAB_NEXT <= TLAB_REAL_END);
4156 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
4158 } else {
4159 /* Reached tlab_temp_end */
4161 /* record the scan start so we can find pinned objects more easily */
4162 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
4163 /* we just bump tlab_temp_end as well */
4164 TLAB_TEMP_END = MIN (TLAB_REAL_END, TLAB_NEXT + SCAN_START_SIZE);
4165 DEBUG (5, fprintf (gc_debug_file, "Expanding local alloc: %p-%p\n", TLAB_NEXT, TLAB_TEMP_END));
4169 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
4170 binary_protocol_alloc (p, vtable, size);
4171 *p = vtable;
4173 return p;
4176 static void*
4177 mono_gc_try_alloc_obj_nolock (MonoVTable *vtable, size_t size)
4179 void **p;
4180 char *new_next;
4181 TLAB_ACCESS_INIT;
4183 size += ALLOC_ALIGN - 1;
4184 size &= ~(ALLOC_ALIGN - 1);
4186 g_assert (vtable->gc_descr);
4187 if (size <= MAX_SMALL_OBJ_SIZE) {
4188 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
4190 p = (void**)TLAB_NEXT;
4191 /* FIXME: handle overflow */
4192 new_next = (char*)p + size;
4193 TLAB_NEXT = new_next;
4195 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
4196 /* Fast path */
4199 * FIXME: We might need a memory barrier here so the change to tlab_next is
4200 * visible before the vtable store.
4203 HEAVY_STAT (++stat_objects_alloced);
4204 HEAVY_STAT (stat_bytes_alloced += size);
4206 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
4207 binary_protocol_alloc (p, vtable, size);
4208 g_assert (*p == NULL);
4209 *p = vtable;
4211 g_assert (TLAB_NEXT == new_next);
4213 return p;
4216 return NULL;
4219 void*
4220 mono_gc_alloc_obj (MonoVTable *vtable, size_t size)
4222 void *res;
4223 #ifndef DISABLE_CRITICAL_REGION
4224 TLAB_ACCESS_INIT;
4225 ENTER_CRITICAL_REGION;
4226 res = mono_gc_try_alloc_obj_nolock (vtable, size);
4227 if (res) {
4228 EXIT_CRITICAL_REGION;
4229 return res;
4231 EXIT_CRITICAL_REGION;
4232 #endif
4233 LOCK_GC;
4234 res = mono_gc_alloc_obj_nolock (vtable, size);
4235 UNLOCK_GC;
4236 return res;
4239 void*
4240 mono_gc_alloc_vector (MonoVTable *vtable, size_t size, uintptr_t max_length)
4242 MonoArray *arr;
4243 #ifndef DISABLE_CRITICAL_REGION
4244 TLAB_ACCESS_INIT;
4245 ENTER_CRITICAL_REGION;
4246 arr = mono_gc_try_alloc_obj_nolock (vtable, size);
4247 if (arr) {
4248 arr->max_length = max_length;
4249 EXIT_CRITICAL_REGION;
4250 return arr;
4252 EXIT_CRITICAL_REGION;
4253 #endif
4255 LOCK_GC;
4257 arr = mono_gc_alloc_obj_nolock (vtable, size);
4258 arr->max_length = max_length;
4260 UNLOCK_GC;
4262 return arr;
4265 void*
4266 mono_gc_alloc_array (MonoVTable *vtable, size_t size, uintptr_t max_length, uintptr_t bounds_size)
4268 MonoArray *arr;
4269 MonoArrayBounds *bounds;
4271 LOCK_GC;
4273 arr = mono_gc_alloc_obj_nolock (vtable, size);
4274 arr->max_length = max_length;
4276 bounds = (MonoArrayBounds*)((char*)arr + size - bounds_size);
4277 arr->bounds = bounds;
4279 UNLOCK_GC;
4281 return arr;
4284 void*
4285 mono_gc_alloc_string (MonoVTable *vtable, size_t size, gint32 len)
4287 MonoString *str;
4288 #ifndef DISABLE_CRITICAL_REGION
4289 TLAB_ACCESS_INIT;
4290 ENTER_CRITICAL_REGION;
4291 str = mono_gc_try_alloc_obj_nolock (vtable, size);
4292 if (str) {
4293 str->length = len;
4294 EXIT_CRITICAL_REGION;
4295 return str;
4297 EXIT_CRITICAL_REGION;
4298 #endif
4300 LOCK_GC;
4302 str = mono_gc_alloc_obj_nolock (vtable, size);
4303 str->length = len;
4305 UNLOCK_GC;
4307 return str;
4311 * To be used for interned strings and possibly MonoThread, reflection handles.
4312 * We may want to explicitly free these objects.
4314 void*
4315 mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size)
4317 /* FIXME: handle OOM */
4318 void **p;
4319 size += ALLOC_ALIGN - 1;
4320 size &= ~(ALLOC_ALIGN - 1);
4321 LOCK_GC;
4322 if (size > MAX_SMALL_OBJ_SIZE) {
4323 /* large objects are always pinned anyway */
4324 p = alloc_large_inner (vtable, size);
4325 } else {
4326 DEBUG (9, g_assert (vtable->klass->inited));
4327 p = major_alloc_small_pinned_obj (size, vtable->klass->has_references);
4329 DEBUG (6, fprintf (gc_debug_file, "Allocated pinned object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
4330 binary_protocol_alloc_pinned (p, vtable, size);
4331 *p = vtable;
4332 UNLOCK_GC;
4333 return p;
4337 * ######################################################################
4338 * ######## Finalization support
4339 * ######################################################################
4343 * this is valid for the nursery: if the object has been forwarded it means it's
4344 * still refrenced from a root. If it is pinned it's still alive as well.
4345 * Return TRUE if @obj is ready to be finalized.
4347 #define object_is_fin_ready(obj) (!object_is_pinned (obj) && !object_is_forwarded (obj))
4349 static gboolean
4350 is_critical_finalizer (FinalizeEntry *entry)
4352 MonoObject *obj;
4353 MonoClass *class;
4355 if (!mono_defaults.critical_finalizer_object)
4356 return FALSE;
4358 obj = entry->object;
4359 class = ((MonoVTable*)LOAD_VTABLE (obj))->klass;
4361 return mono_class_has_parent (class, mono_defaults.critical_finalizer_object);
4364 static void
4365 queue_finalization_entry (FinalizeEntry *entry) {
4366 if (is_critical_finalizer (entry)) {
4367 entry->next = critical_fin_list;
4368 critical_fin_list = entry;
4369 } else {
4370 entry->next = fin_ready_list;
4371 fin_ready_list = entry;
4375 /* LOCKING: requires that the GC lock is held */
4376 static void
4377 rehash_fin_table (FinalizeEntryHashTable *hash_table)
4379 FinalizeEntry **finalizable_hash = hash_table->table;
4380 mword finalizable_hash_size = hash_table->size;
4381 int i;
4382 unsigned int hash;
4383 FinalizeEntry **new_hash;
4384 FinalizeEntry *entry, *next;
4385 int new_size = g_spaced_primes_closest (hash_table->num_registered);
4387 new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*), INTERNAL_MEM_FIN_TABLE);
4388 for (i = 0; i < finalizable_hash_size; ++i) {
4389 for (entry = finalizable_hash [i]; entry; entry = next) {
4390 hash = mono_object_hash (entry->object) % new_size;
4391 next = entry->next;
4392 entry->next = new_hash [hash];
4393 new_hash [hash] = entry;
4396 free_internal_mem (finalizable_hash, INTERNAL_MEM_FIN_TABLE);
4397 hash_table->table = new_hash;
4398 hash_table->size = new_size;
4401 /* LOCKING: requires that the GC lock is held */
4402 static void
4403 rehash_fin_table_if_necessary (FinalizeEntryHashTable *hash_table)
4405 if (hash_table->num_registered >= hash_table->size * 2)
4406 rehash_fin_table (hash_table);
4409 /* LOCKING: requires that the GC lock is held */
4410 static void
4411 finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation)
4413 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
4414 FinalizeEntry *entry, *prev;
4415 int i;
4416 FinalizeEntry **finalizable_hash = hash_table->table;
4417 mword finalizable_hash_size = hash_table->size;
4419 if (no_finalize)
4420 return;
4421 for (i = 0; i < finalizable_hash_size; ++i) {
4422 prev = NULL;
4423 for (entry = finalizable_hash [i]; entry;) {
4424 if ((char*)entry->object >= start && (char*)entry->object < end && !major_is_object_live (entry->object)) {
4425 gboolean is_fin_ready = object_is_fin_ready (entry->object);
4426 char *copy = entry->object;
4427 copy_func ((void**)&copy);
4428 if (is_fin_ready) {
4429 char *from;
4430 FinalizeEntry *next;
4431 /* remove and put in fin_ready_list */
4432 if (prev)
4433 prev->next = entry->next;
4434 else
4435 finalizable_hash [i] = entry->next;
4436 next = entry->next;
4437 num_ready_finalizers++;
4438 hash_table->num_registered--;
4439 queue_finalization_entry (entry);
4440 /* Make it survive */
4441 from = entry->object;
4442 entry->object = copy;
4443 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));
4444 entry = next;
4445 continue;
4446 } else {
4447 char *from = entry->object;
4448 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
4449 FinalizeEntry *next = entry->next;
4450 unsigned int major_hash;
4451 /* remove from the list */
4452 if (prev)
4453 prev->next = entry->next;
4454 else
4455 finalizable_hash [i] = entry->next;
4456 hash_table->num_registered--;
4458 entry->object = copy;
4460 /* insert it into the major hash */
4461 rehash_fin_table_if_necessary (&major_finalizable_hash);
4462 major_hash = mono_object_hash ((MonoObject*) copy) %
4463 major_finalizable_hash.size;
4464 entry->next = major_finalizable_hash.table [major_hash];
4465 major_finalizable_hash.table [major_hash] = entry;
4466 major_finalizable_hash.num_registered++;
4468 DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from));
4470 entry = next;
4471 continue;
4472 } else {
4473 /* update pointer */
4474 DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from));
4475 entry->object = copy;
4479 prev = entry;
4480 entry = entry->next;
4485 static int
4486 object_is_reachable (char *object, char *start, char *end)
4488 /*This happens for non nursery objects during minor collections. We just treat all objects as alive.*/
4489 if (object < start || object >= end)
4490 return TRUE;
4491 return !object_is_fin_ready (object) || major_is_object_live (object);
4494 /* LOCKING: requires that the GC lock is held */
4495 static void
4496 null_ephemerons_for_domain (MonoDomain *domain)
4498 EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
4500 while (current) {
4501 MonoObject *object = (MonoObject*)current->array;
4503 if (object && !object->vtable) {
4504 EphemeronLinkNode *tmp = current;
4506 if (prev)
4507 prev->next = current->next;
4508 else
4509 ephemeron_list = current->next;
4511 current = current->next;
4512 free_internal_mem (tmp, INTERNAL_MEM_EPHEMERON_LINK);
4513 } else {
4514 prev = current;
4515 current = current->next;
4520 /* LOCKING: requires that the GC lock is held */
4521 static void
4522 clear_unreachable_ephemerons (CopyOrMarkObjectFunc copy_func, char *start, char *end)
4524 int was_in_nursery, was_promoted;
4525 EphemeronLinkNode *current = ephemeron_list, *prev = NULL;
4526 MonoArray *array;
4527 Ephemeron *cur, *array_end;
4528 char *tombstone;
4530 while (current) {
4531 char *object = current->array;
4533 if (!object_is_reachable (object, start, end)) {
4534 EphemeronLinkNode *tmp = current;
4536 DEBUG (5, fprintf (gc_debug_file, "Dead Ephemeron array at %p\n", object));
4538 if (prev)
4539 prev->next = current->next;
4540 else
4541 ephemeron_list = current->next;
4543 current = current->next;
4544 free_internal_mem (tmp, INTERNAL_MEM_EPHEMERON_LINK);
4546 continue;
4549 was_in_nursery = ptr_in_nursery (object);
4550 copy_func ((void**)&object);
4551 current->array = object;
4553 /*The array was promoted, add global remsets for key/values left behind in nursery.*/
4554 was_promoted = was_in_nursery && !ptr_in_nursery (object);
4556 DEBUG (5, fprintf (gc_debug_file, "Clearing unreachable entries for ephemeron array at %p\n", object));
4558 array = (MonoArray*)object;
4559 cur = mono_array_addr (array, Ephemeron, 0);
4560 array_end = cur + mono_array_length_fast (array);
4561 tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
4563 for (; cur < array_end; ++cur) {
4564 char *key = (char*)cur->key;
4566 if (!key || key == tombstone)
4567 continue;
4569 DEBUG (5, fprintf (gc_debug_file, "[%td] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
4570 key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
4571 cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
4573 if (!object_is_reachable (key, start, end)) {
4574 cur->key = tombstone;
4575 cur->value = NULL;
4576 continue;
4579 if (was_promoted) {
4580 if (ptr_in_nursery (key)) {/*key was not promoted*/
4581 DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to key %p\n", key));
4582 add_to_global_remset (&cur->key);
4584 if (ptr_in_nursery (cur->value)) {/*value was not promoted*/
4585 DEBUG (5, fprintf (gc_debug_file, "\tAdded remset to value %p\n", cur->value));
4586 add_to_global_remset (&cur->value);
4590 prev = current;
4591 current = current->next;
4595 /* LOCKING: requires that the GC lock is held */
4596 static int
4597 mark_ephemerons_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end)
4599 int nothing_marked = 1;
4600 EphemeronLinkNode *current = ephemeron_list;
4601 MonoArray *array;
4602 Ephemeron *cur, *array_end;
4603 char *tombstone;
4605 for (current = ephemeron_list; current; current = current->next) {
4606 char *object = current->array;
4607 DEBUG (5, fprintf (gc_debug_file, "Ephemeron array at %p\n", object));
4609 /*We ignore arrays in old gen during minor collections since all objects are promoted by the remset machinery.*/
4610 if (object < start || object >= end)
4611 continue;
4613 /*It has to be alive*/
4614 if (!object_is_reachable (object, start, end)) {
4615 DEBUG (5, fprintf (gc_debug_file, "\tnot reachable\n"));
4616 continue;
4619 copy_func ((void**)&object);
4621 array = (MonoArray*)object;
4622 cur = mono_array_addr (array, Ephemeron, 0);
4623 array_end = cur + mono_array_length_fast (array);
4624 tombstone = (char*)((MonoVTable*)LOAD_VTABLE (object))->domain->ephemeron_tombstone;
4626 for (; cur < array_end; ++cur) {
4627 char *key = cur->key;
4629 if (!key || key == tombstone)
4630 continue;
4632 DEBUG (5, fprintf (gc_debug_file, "[%td] key %p (%s) value %p (%s)\n", cur - mono_array_addr (array, Ephemeron, 0),
4633 key, object_is_reachable (key, start, end) ? "reachable" : "unreachable",
4634 cur->value, cur->value && object_is_reachable (cur->value, start, end) ? "reachable" : "unreachable"));
4636 if (object_is_reachable (key, start, end)) {
4637 char *value = cur->value;
4639 copy_func ((void**)&cur->key);
4640 if (value) {
4641 if (!object_is_reachable (value, start, end))
4642 nothing_marked = 0;
4643 copy_func ((void**)&cur->value);
4649 DEBUG (5, fprintf (gc_debug_file, "Ephemeron run finished. Is it done %d\n", nothing_marked));
4650 return nothing_marked;
4653 /* LOCKING: requires that the GC lock is held */
4654 static void
4655 null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation)
4657 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
4658 DisappearingLink **disappearing_link_hash = hash->table;
4659 int disappearing_link_hash_size = hash->size;
4660 DisappearingLink *entry, *prev;
4661 int i;
4662 if (!hash->num_links)
4663 return;
4664 for (i = 0; i < disappearing_link_hash_size; ++i) {
4665 prev = NULL;
4666 for (entry = disappearing_link_hash [i]; entry;) {
4667 char *object = DISLINK_OBJECT (entry);
4668 if (object >= start && object < end && !major_is_object_live (object)) {
4669 gboolean track = DISLINK_TRACK (entry);
4670 if (!track && object_is_fin_ready (object)) {
4671 void **p = entry->link;
4672 DisappearingLink *old;
4673 *p = NULL;
4674 /* remove from list */
4675 if (prev)
4676 prev->next = entry->next;
4677 else
4678 disappearing_link_hash [i] = entry->next;
4679 DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object));
4680 old = entry->next;
4681 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4682 entry = old;
4683 hash->num_links--;
4684 continue;
4685 } else {
4686 char *copy = object;
4687 copy_func ((void**)&copy);
4689 /* Update pointer if it's moved. If the object
4690 * has been moved out of the nursery, we need to
4691 * remove the link from the minor hash table to
4692 * the major one.
4694 * FIXME: what if an object is moved earlier?
4697 if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
4698 void **link = entry->link;
4699 DisappearingLink *old;
4700 /* remove from list */
4701 if (prev)
4702 prev->next = entry->next;
4703 else
4704 disappearing_link_hash [i] = entry->next;
4705 old = entry->next;
4706 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4707 entry = old;
4708 hash->num_links--;
4710 add_or_remove_disappearing_link ((MonoObject*)copy, link,
4711 track, GENERATION_OLD);
4713 DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy));
4715 continue;
4716 } else {
4717 /* We set the track resurrection bit to
4718 * FALSE if the object is to be finalized
4719 * so that the object can be collected in
4720 * the next cycle (i.e. after it was
4721 * finalized).
4723 *entry->link = HIDE_POINTER (copy,
4724 object_is_fin_ready (object) ? FALSE : track);
4725 DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
4729 prev = entry;
4730 entry = entry->next;
4735 /* LOCKING: requires that the GC lock is held */
4736 static void
4737 null_links_for_domain (MonoDomain *domain, int generation)
4739 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
4740 DisappearingLink **disappearing_link_hash = hash->table;
4741 int disappearing_link_hash_size = hash->size;
4742 DisappearingLink *entry, *prev;
4743 int i;
4744 for (i = 0; i < disappearing_link_hash_size; ++i) {
4745 prev = NULL;
4746 for (entry = disappearing_link_hash [i]; entry; ) {
4747 char *object = DISLINK_OBJECT (entry);
4748 if (object && !((MonoObject*)object)->vtable) {
4749 DisappearingLink *next = entry->next;
4751 if (prev)
4752 prev->next = next;
4753 else
4754 disappearing_link_hash [i] = next;
4756 if (*(entry->link)) {
4757 *(entry->link) = NULL;
4758 g_warning ("Disappearing link %p not freed", entry->link);
4759 } else {
4760 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4763 entry = next;
4764 continue;
4766 prev = entry;
4767 entry = entry->next;
4772 /* LOCKING: requires that the GC lock is held */
4773 static int
4774 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
4775 FinalizeEntryHashTable *hash_table)
4777 FinalizeEntry **finalizable_hash = hash_table->table;
4778 mword finalizable_hash_size = hash_table->size;
4779 FinalizeEntry *entry, *prev;
4780 int i, count;
4782 if (no_finalize || !out_size || !out_array)
4783 return 0;
4784 count = 0;
4785 for (i = 0; i < finalizable_hash_size; ++i) {
4786 prev = NULL;
4787 for (entry = finalizable_hash [i]; entry;) {
4788 if (mono_object_domain (entry->object) == domain) {
4789 FinalizeEntry *next;
4790 /* remove and put in out_array */
4791 if (prev)
4792 prev->next = entry->next;
4793 else
4794 finalizable_hash [i] = entry->next;
4795 next = entry->next;
4796 hash_table->num_registered--;
4797 out_array [count ++] = entry->object;
4798 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));
4799 entry = next;
4800 if (count == out_size)
4801 return count;
4802 continue;
4804 prev = entry;
4805 entry = entry->next;
4808 return count;
4812 * mono_gc_finalizers_for_domain:
4813 * @domain: the unloading appdomain
4814 * @out_array: output array
4815 * @out_size: size of output array
4817 * Store inside @out_array up to @out_size objects that belong to the unloading
4818 * appdomain @domain. Returns the number of stored items. Can be called repeteadly
4819 * until it returns 0.
4820 * The items are removed from the finalizer data structure, so the caller is supposed
4821 * to finalize them.
4822 * @out_array should be on the stack to allow the GC to know the objects are still alive.
4825 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
4827 int result;
4829 LOCK_GC;
4830 result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
4831 if (result < out_size) {
4832 result += finalizers_for_domain (domain, out_array + result, out_size - result,
4833 &major_finalizable_hash);
4835 UNLOCK_GC;
4837 return result;
4840 static void
4841 register_for_finalization (MonoObject *obj, void *user_data, int generation)
4843 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
4844 FinalizeEntry **finalizable_hash;
4845 mword finalizable_hash_size;
4846 FinalizeEntry *entry, *prev;
4847 unsigned int hash;
4848 if (no_finalize)
4849 return;
4850 g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
4851 hash = mono_object_hash (obj);
4852 LOCK_GC;
4853 rehash_fin_table_if_necessary (hash_table);
4854 finalizable_hash = hash_table->table;
4855 finalizable_hash_size = hash_table->size;
4856 hash %= finalizable_hash_size;
4857 prev = NULL;
4858 for (entry = finalizable_hash [hash]; entry; entry = entry->next) {
4859 if (entry->object == obj) {
4860 if (!user_data) {
4861 /* remove from the list */
4862 if (prev)
4863 prev->next = entry->next;
4864 else
4865 finalizable_hash [hash] = entry->next;
4866 hash_table->num_registered--;
4867 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));
4868 free_internal_mem (entry, INTERNAL_MEM_FINALIZE_ENTRY);
4870 UNLOCK_GC;
4871 return;
4873 prev = entry;
4875 if (!user_data) {
4876 /* request to deregister, but already out of the list */
4877 UNLOCK_GC;
4878 return;
4880 entry = get_internal_mem (sizeof (FinalizeEntry), INTERNAL_MEM_FINALIZE_ENTRY);
4881 entry->object = obj;
4882 entry->next = finalizable_hash [hash];
4883 finalizable_hash [hash] = entry;
4884 hash_table->num_registered++;
4885 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)));
4886 UNLOCK_GC;
4889 void
4890 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
4892 if (ptr_in_nursery (obj))
4893 register_for_finalization (obj, user_data, GENERATION_NURSERY);
4894 else
4895 register_for_finalization (obj, user_data, GENERATION_OLD);
4898 static void
4899 rehash_dislink (DisappearingLinkHashTable *hash_table)
4901 DisappearingLink **disappearing_link_hash = hash_table->table;
4902 int disappearing_link_hash_size = hash_table->size;
4903 int i;
4904 unsigned int hash;
4905 DisappearingLink **new_hash;
4906 DisappearingLink *entry, *next;
4907 int new_size = g_spaced_primes_closest (hash_table->num_links);
4909 new_hash = get_internal_mem (new_size * sizeof (DisappearingLink*), INTERNAL_MEM_DISLINK_TABLE);
4910 for (i = 0; i < disappearing_link_hash_size; ++i) {
4911 for (entry = disappearing_link_hash [i]; entry; entry = next) {
4912 hash = mono_aligned_addr_hash (entry->link) % new_size;
4913 next = entry->next;
4914 entry->next = new_hash [hash];
4915 new_hash [hash] = entry;
4918 free_internal_mem (disappearing_link_hash, INTERNAL_MEM_DISLINK_TABLE);
4919 hash_table->table = new_hash;
4920 hash_table->size = new_size;
4923 /* LOCKING: assumes the GC lock is held */
4924 static void
4925 add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation)
4927 DisappearingLinkHashTable *hash_table = get_dislink_hash_table (generation);
4928 DisappearingLink *entry, *prev;
4929 unsigned int hash;
4930 DisappearingLink **disappearing_link_hash = hash_table->table;
4931 int disappearing_link_hash_size = hash_table->size;
4933 if (hash_table->num_links >= disappearing_link_hash_size * 2) {
4934 rehash_dislink (hash_table);
4935 disappearing_link_hash = hash_table->table;
4936 disappearing_link_hash_size = hash_table->size;
4938 /* FIXME: add check that link is not in the heap */
4939 hash = mono_aligned_addr_hash (link) % disappearing_link_hash_size;
4940 entry = disappearing_link_hash [hash];
4941 prev = NULL;
4942 for (; entry; entry = entry->next) {
4943 /* link already added */
4944 if (link == entry->link) {
4945 /* NULL obj means remove */
4946 if (obj == NULL) {
4947 if (prev)
4948 prev->next = entry->next;
4949 else
4950 disappearing_link_hash [hash] = entry->next;
4951 hash_table->num_links--;
4952 DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d) from %s table\n", entry, hash_table->num_links, generation_name (generation)));
4953 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4954 *link = NULL;
4955 } else {
4956 *link = HIDE_POINTER (obj, track); /* we allow the change of object */
4958 return;
4960 prev = entry;
4962 if (obj == NULL)
4963 return;
4964 entry = get_internal_mem (sizeof (DisappearingLink), INTERNAL_MEM_DISLINK);
4965 *link = HIDE_POINTER (obj, track);
4966 entry->link = link;
4967 entry->next = disappearing_link_hash [hash];
4968 disappearing_link_hash [hash] = entry;
4969 hash_table->num_links++;
4970 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)));
4973 /* LOCKING: assumes the GC lock is held */
4974 static void
4975 mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
4977 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_NURSERY);
4978 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_OLD);
4979 if (obj) {
4980 if (ptr_in_nursery (obj))
4981 add_or_remove_disappearing_link (obj, link, track, GENERATION_NURSERY);
4982 else
4983 add_or_remove_disappearing_link (obj, link, track, GENERATION_OLD);
4988 mono_gc_invoke_finalizers (void)
4990 FinalizeEntry *entry = NULL;
4991 gboolean entry_is_critical = FALSE;
4992 int count = 0;
4993 void *obj;
4994 /* FIXME: batch to reduce lock contention */
4995 while (fin_ready_list || critical_fin_list) {
4996 LOCK_GC;
4998 if (entry) {
4999 FinalizeEntry **list = entry_is_critical ? &critical_fin_list : &fin_ready_list;
5001 /* We have finalized entry in the last
5002 interation, now we need to remove it from
5003 the list. */
5004 if (*list == entry)
5005 *list = entry->next;
5006 else {
5007 FinalizeEntry *e = *list;
5008 while (e->next != entry)
5009 e = e->next;
5010 e->next = entry->next;
5012 free_internal_mem (entry, INTERNAL_MEM_FINALIZE_ENTRY);
5013 entry = NULL;
5016 /* Now look for the first non-null entry. */
5017 for (entry = fin_ready_list; entry && !entry->object; entry = entry->next)
5019 if (entry) {
5020 entry_is_critical = FALSE;
5021 } else {
5022 entry_is_critical = TRUE;
5023 for (entry = critical_fin_list; entry && !entry->object; entry = entry->next)
5027 if (entry) {
5028 g_assert (entry->object);
5029 num_ready_finalizers--;
5030 obj = entry->object;
5031 entry->object = NULL;
5032 DEBUG (7, fprintf (gc_debug_file, "Finalizing object %p (%s)\n", obj, safe_name (obj)));
5035 UNLOCK_GC;
5037 if (!entry)
5038 break;
5040 g_assert (entry->object == NULL);
5041 count++;
5042 /* the object is on the stack so it is pinned */
5043 /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/
5044 mono_gc_run_finalize (obj, NULL);
5046 g_assert (!entry);
5047 return count;
5050 gboolean
5051 mono_gc_pending_finalizers (void)
5053 return fin_ready_list || critical_fin_list;
5056 /* Negative value to remove */
5057 void
5058 mono_gc_add_memory_pressure (gint64 value)
5060 /* FIXME: Use interlocked functions */
5061 LOCK_GC;
5062 memory_pressure += value;
5063 UNLOCK_GC;
5067 * ######################################################################
5068 * ######## registered roots support
5069 * ######################################################################
5072 static void
5073 rehash_roots (gboolean pinned)
5075 int i;
5076 unsigned int hash;
5077 RootRecord **new_hash;
5078 RootRecord *entry, *next;
5079 int new_size;
5081 new_size = g_spaced_primes_closest (num_roots_entries [pinned]);
5082 new_hash = get_internal_mem (new_size * sizeof (RootRecord*), INTERNAL_MEM_ROOTS_TABLE);
5083 for (i = 0; i < roots_hash_size [pinned]; ++i) {
5084 for (entry = roots_hash [pinned][i]; entry; entry = next) {
5085 hash = mono_aligned_addr_hash (entry->start_root) % new_size;
5086 next = entry->next;
5087 entry->next = new_hash [hash];
5088 new_hash [hash] = entry;
5091 free_internal_mem (roots_hash [pinned], INTERNAL_MEM_ROOTS_TABLE);
5092 roots_hash [pinned] = new_hash;
5093 roots_hash_size [pinned] = new_size;
5096 static RootRecord*
5097 find_root (int root_type, char *start, guint32 addr_hash)
5099 RootRecord *new_root;
5101 guint32 hash = addr_hash % roots_hash_size [root_type];
5102 for (new_root = roots_hash [root_type][hash]; new_root; new_root = new_root->next) {
5103 /* we allow changing the size and the descriptor (for thread statics etc) */
5104 if (new_root->start_root == start) {
5105 return new_root;
5109 return NULL;
5113 * We do not coalesce roots.
5115 static int
5116 mono_gc_register_root_inner (char *start, size_t size, void *descr, int root_type)
5118 RootRecord *new_root;
5119 unsigned int hash, addr_hash = mono_aligned_addr_hash (start);
5120 int i;
5121 LOCK_GC;
5122 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
5123 if (num_roots_entries [i] >= roots_hash_size [i] * 2)
5124 rehash_roots (i);
5126 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
5127 new_root = find_root (i, start, addr_hash);
5128 /* we allow changing the size and the descriptor (for thread statics etc) */
5129 if (new_root) {
5130 size_t old_size = new_root->end_root - new_root->start_root;
5131 new_root->end_root = new_root->start_root + size;
5132 g_assert (((new_root->root_desc != 0) && (descr != NULL)) ||
5133 ((new_root->root_desc == 0) && (descr == NULL)));
5134 new_root->root_desc = (mword)descr;
5135 roots_size += size;
5136 roots_size -= old_size;
5137 UNLOCK_GC;
5138 return TRUE;
5141 new_root = get_internal_mem (sizeof (RootRecord), INTERNAL_MEM_ROOT_RECORD);
5142 if (new_root) {
5143 new_root->start_root = start;
5144 new_root->end_root = new_root->start_root + size;
5145 new_root->root_desc = (mword)descr;
5146 roots_size += size;
5147 hash = addr_hash % roots_hash_size [root_type];
5148 num_roots_entries [root_type]++;
5149 new_root->next = roots_hash [root_type] [hash];
5150 roots_hash [root_type][hash] = new_root;
5151 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));
5152 } else {
5153 UNLOCK_GC;
5154 return FALSE;
5156 UNLOCK_GC;
5157 return TRUE;
5161 mono_gc_register_root (char *start, size_t size, void *descr)
5163 return mono_gc_register_root_inner (start, size, descr, descr ? ROOT_TYPE_NORMAL : ROOT_TYPE_PINNED);
5167 mono_gc_register_root_wbarrier (char *start, size_t size, void *descr)
5169 return mono_gc_register_root_inner (start, size, descr, ROOT_TYPE_WBARRIER);
5172 void
5173 mono_gc_deregister_root (char* addr)
5175 RootRecord *tmp, *prev;
5176 unsigned int hash, addr_hash = mono_aligned_addr_hash (addr);
5177 int root_type;
5179 LOCK_GC;
5180 for (root_type = 0; root_type < ROOT_TYPE_NUM; ++root_type) {
5181 hash = addr_hash % roots_hash_size [root_type];
5182 tmp = roots_hash [root_type][hash];
5183 prev = NULL;
5184 while (tmp) {
5185 if (tmp->start_root == (char*)addr) {
5186 if (prev)
5187 prev->next = tmp->next;
5188 else
5189 roots_hash [root_type][hash] = tmp->next;
5190 roots_size -= (tmp->end_root - tmp->start_root);
5191 num_roots_entries [root_type]--;
5192 DEBUG (3, fprintf (gc_debug_file, "Removed root %p for range: %p-%p\n", tmp, tmp->start_root, tmp->end_root));
5193 free_internal_mem (tmp, INTERNAL_MEM_ROOT_RECORD);
5194 break;
5196 prev = tmp;
5197 tmp = tmp->next;
5200 UNLOCK_GC;
5204 * ######################################################################
5205 * ######## Thread handling (stop/start code)
5206 * ######################################################################
5209 /* FIXME: handle large/small config */
5210 #define HASH_PTHREAD_T(id) (((unsigned int)(id) >> 4) * 2654435761u)
5212 static SgenThreadInfo* thread_table [THREAD_HASH_SIZE];
5214 #if USE_SIGNAL_BASED_START_STOP_WORLD
5216 static MonoSemType suspend_ack_semaphore;
5217 static MonoSemType *suspend_ack_semaphore_ptr;
5218 static unsigned int global_stop_count = 0;
5220 static sigset_t suspend_signal_mask;
5221 static mword cur_thread_regs [ARCH_NUM_REGS] = {0};
5223 /* LOCKING: assumes the GC lock is held */
5224 SgenThreadInfo**
5225 mono_sgen_get_thread_table (void)
5227 return thread_table;
5230 SgenThreadInfo*
5231 mono_sgen_thread_info_lookup (ARCH_THREAD_TYPE id)
5233 unsigned int hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
5234 SgenThreadInfo *info;
5236 info = thread_table [hash];
5237 while (info && !ARCH_THREAD_EQUALS (info->id, id)) {
5238 info = info->next;
5240 return info;
5243 static void
5244 update_current_thread_stack (void *start)
5246 void *ptr = cur_thread_regs;
5247 SgenThreadInfo *info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
5249 info->stack_start = align_pointer (&ptr);
5250 g_assert (info->stack_start >= info->stack_start_limit && info->stack_start < info->stack_end);
5251 ARCH_STORE_REGS (ptr);
5252 info->stopped_regs = ptr;
5253 if (gc_callbacks.thread_suspend_func)
5254 gc_callbacks.thread_suspend_func (info->runtime_data, NULL);
5258 * Define this and use the "xdomain-checks" MONO_GC_DEBUG option to
5259 * have cross-domain checks in the write barrier.
5261 //#define XDOMAIN_CHECKS_IN_WBARRIER
5263 #ifndef BINARY_PROTOCOL
5264 #ifndef HEAVY_STATISTICS
5265 #define MANAGED_ALLOCATION
5266 #ifndef XDOMAIN_CHECKS_IN_WBARRIER
5267 #define MANAGED_WBARRIER
5268 #endif
5269 #endif
5270 #endif
5272 static gboolean
5273 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip);
5275 void
5276 mono_sgen_wait_for_suspend_ack (int count)
5278 int i, result;
5280 for (i = 0; i < count; ++i) {
5281 while ((result = MONO_SEM_WAIT (suspend_ack_semaphore_ptr)) != 0) {
5282 if (errno != EINTR) {
5283 g_error ("sem_wait ()");
5289 static int
5290 restart_threads_until_none_in_managed_allocator (void)
5292 SgenThreadInfo *info;
5293 int i, result, num_threads_died = 0;
5294 int sleep_duration = -1;
5296 for (;;) {
5297 int restart_count = 0, restarted_count = 0;
5298 /* restart all threads that stopped in the
5299 allocator */
5300 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5301 for (info = thread_table [i]; info; info = info->next) {
5302 if (info->skip)
5303 continue;
5304 if (!info->stack_start || info->in_critical_region ||
5305 is_ip_in_managed_allocator (info->stopped_domain, info->stopped_ip)) {
5306 binary_protocol_thread_restart ((gpointer)info->id);
5307 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
5308 result = thread_resume (pthread_mach_thread_np (info->id));
5309 #else
5310 result = pthread_kill (info->id, restart_signal_num);
5311 #endif
5312 if (result == 0) {
5313 ++restart_count;
5314 } else {
5315 info->skip = 1;
5317 } else {
5318 /* we set the stopped_ip to
5319 NULL for threads which
5320 we're not restarting so
5321 that we can easily identify
5322 the others */
5323 info->stopped_ip = NULL;
5324 info->stopped_domain = NULL;
5328 /* if no threads were restarted, we're done */
5329 if (restart_count == 0)
5330 break;
5332 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
5333 /* mach thread_resume is synchronous so we dont need to wait for them */
5334 #else
5335 /* wait for the threads to signal their restart */
5336 mono_sgen_wait_for_suspend_ack (restart_count);
5337 #endif
5339 if (sleep_duration < 0) {
5340 sched_yield ();
5341 sleep_duration = 0;
5342 } else {
5343 g_usleep (sleep_duration);
5344 sleep_duration += 10;
5347 /* stop them again */
5348 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5349 for (info = thread_table [i]; info; info = info->next) {
5350 if (info->skip || info->stopped_ip == NULL)
5351 continue;
5352 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
5353 result = thread_suspend (pthread_mach_thread_np (info->id));
5354 #else
5355 result = pthread_kill (info->id, suspend_signal_num);
5356 #endif
5357 if (result == 0) {
5358 ++restarted_count;
5359 } else {
5360 info->skip = 1;
5364 /* some threads might have died */
5365 num_threads_died += restart_count - restarted_count;
5366 #if defined(__MACH__) && MONO_MACH_ARCH_SUPPORTED
5367 /* mach thread_resume is synchronous so we dont need to wait for them */
5368 #else
5369 /* wait for the threads to signal their suspension
5370 again */
5371 mono_sgen_wait_for_suspend_ack (restart_count);
5372 #endif
5375 return num_threads_died;
5378 /* LOCKING: assumes the GC lock is held (by the stopping thread) */
5379 static void
5380 suspend_handler (int sig, siginfo_t *siginfo, void *context)
5382 SgenThreadInfo *info;
5383 pthread_t id;
5384 int stop_count;
5385 int old_errno = errno;
5386 gpointer regs [ARCH_NUM_REGS];
5387 gpointer stack_start;
5389 id = pthread_self ();
5390 info = mono_sgen_thread_info_lookup (id);
5391 info->stopped_domain = mono_domain_get ();
5392 info->stopped_ip = (gpointer) ARCH_SIGCTX_IP (context);
5393 stop_count = global_stop_count;
5394 /* duplicate signal */
5395 if (0 && info->stop_count == stop_count) {
5396 errno = old_errno;
5397 return;
5399 #ifdef HAVE_KW_THREAD
5400 /* update the remset info in the thread data structure */
5401 info->remset = remembered_set;
5402 #endif
5403 stack_start = (char*) ARCH_SIGCTX_SP (context) - REDZONE_SIZE;
5404 /* If stack_start is not within the limits, then don't set it
5405 in info and we will be restarted. */
5406 if (stack_start >= info->stack_start_limit && info->stack_start <= info->stack_end) {
5407 info->stack_start = stack_start;
5409 ARCH_COPY_SIGCTX_REGS (regs, context);
5410 info->stopped_regs = regs;
5411 } else {
5412 g_assert (!info->stack_start);
5415 /* Notify the JIT */
5416 if (gc_callbacks.thread_suspend_func)
5417 gc_callbacks.thread_suspend_func (info->runtime_data, context);
5419 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for suspend from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
5420 /* notify the waiting thread */
5421 MONO_SEM_POST (suspend_ack_semaphore_ptr);
5422 info->stop_count = stop_count;
5424 /* wait until we receive the restart signal */
5425 do {
5426 info->signal = 0;
5427 sigsuspend (&suspend_signal_mask);
5428 } while (info->signal != restart_signal_num);
5430 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for resume from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
5431 /* notify the waiting thread */
5432 MONO_SEM_POST (suspend_ack_semaphore_ptr);
5434 errno = old_errno;
5437 static void
5438 restart_handler (int sig)
5440 SgenThreadInfo *info;
5441 int old_errno = errno;
5443 info = mono_sgen_thread_info_lookup (pthread_self ());
5444 info->signal = restart_signal_num;
5445 DEBUG (4, fprintf (gc_debug_file, "Restart handler in %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
5447 errno = old_errno;
5450 static void
5451 acquire_gc_locks (void)
5453 LOCK_INTERRUPTION;
5456 static void
5457 release_gc_locks (void)
5459 UNLOCK_INTERRUPTION;
5462 static TV_DECLARE (stop_world_time);
5463 static unsigned long max_pause_usec = 0;
5465 /* LOCKING: assumes the GC lock is held */
5466 static int
5467 stop_world (void)
5469 int count;
5471 acquire_gc_locks ();
5473 update_current_thread_stack (&count);
5475 global_stop_count++;
5476 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 ()));
5477 TV_GETTIME (stop_world_time);
5478 count = mono_sgen_thread_handshake (suspend_signal_num);
5479 count -= restart_threads_until_none_in_managed_allocator ();
5480 g_assert (count >= 0);
5481 DEBUG (3, fprintf (gc_debug_file, "world stopped %d thread(s)\n", count));
5482 return count;
5485 /* LOCKING: assumes the GC lock is held */
5486 static int
5487 restart_world (void)
5489 int count, i;
5490 SgenThreadInfo *info;
5491 TV_DECLARE (end_sw);
5492 unsigned long usec;
5494 /* notify the profiler of the leftovers */
5495 if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_GC_MOVES)) {
5496 if (moved_objects_idx) {
5497 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
5498 moved_objects_idx = 0;
5501 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5502 for (info = thread_table [i]; info; info = info->next) {
5503 info->stack_start = NULL;
5504 info->stopped_regs = NULL;
5508 release_gc_locks ();
5510 count = mono_sgen_thread_handshake (restart_signal_num);
5511 TV_GETTIME (end_sw);
5512 usec = TV_ELAPSED (stop_world_time, end_sw);
5513 max_pause_usec = MAX (usec, max_pause_usec);
5514 DEBUG (2, fprintf (gc_debug_file, "restarted %d thread(s) (pause time: %d usec, max: %d)\n", count, (int)usec, (int)max_pause_usec));
5515 return count;
5518 #endif /* USE_SIGNAL_BASED_START_STOP_WORLD */
5520 void
5521 mono_gc_set_gc_callbacks (MonoGCCallbacks *callbacks)
5523 gc_callbacks = *callbacks;
5526 MonoGCCallbacks *
5527 mono_gc_get_gc_callbacks ()
5529 return &gc_callbacks;
5532 /* Variables holding start/end nursery so it won't have to be passed at every call */
5533 static void *scan_area_arg_start, *scan_area_arg_end;
5535 void
5536 mono_gc_conservatively_scan_area (void *start, void *end)
5538 conservatively_pin_objects_from (start, end, scan_area_arg_start, scan_area_arg_end, PIN_TYPE_STACK);
5541 void*
5542 mono_gc_scan_object (void *obj)
5544 if (current_collection_generation == GENERATION_NURSERY)
5545 copy_object (&obj);
5546 else
5547 major_copy_or_mark_object (&obj);
5548 return obj;
5552 * Mark from thread stacks and registers.
5554 static void
5555 scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise)
5557 int i;
5558 SgenThreadInfo *info;
5560 scan_area_arg_start = start_nursery;
5561 scan_area_arg_end = end_nursery;
5563 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5564 for (info = thread_table [i]; info; info = info->next) {
5565 if (info->skip) {
5566 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));
5567 continue;
5569 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));
5570 if (gc_callbacks.thread_mark_func && !conservative_stack_mark)
5571 gc_callbacks.thread_mark_func (info->runtime_data, info->stack_start, info->stack_end, precise);
5572 else if (!precise)
5573 conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery, PIN_TYPE_STACK);
5575 if (!precise)
5576 conservatively_pin_objects_from (info->stopped_regs, info->stopped_regs + ARCH_NUM_REGS,
5577 start_nursery, end_nursery, PIN_TYPE_STACK);
5582 static void
5583 find_pinning_ref_from_thread (char *obj, size_t size)
5585 int i;
5586 SgenThreadInfo *info;
5587 char *endobj = obj + size;
5589 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5590 for (info = thread_table [i]; info; info = info->next) {
5591 char **start = (char**)info->stack_start;
5592 if (info->skip)
5593 continue;
5594 while (start < (char**)info->stack_end) {
5595 if (*start >= obj && *start < endobj) {
5596 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));
5598 start++;
5601 /* FIXME: check info->stopped_regs */
5606 static gboolean
5607 ptr_on_stack (void *ptr)
5609 gpointer stack_start = &stack_start;
5610 SgenThreadInfo *info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
5612 if (ptr >= stack_start && ptr < (gpointer)info->stack_end)
5613 return TRUE;
5614 return FALSE;
5617 static mword*
5618 handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global)
5620 void **ptr;
5621 mword count;
5622 mword desc;
5624 if (global)
5625 HEAVY_STAT (++stat_global_remsets_processed);
5627 /* FIXME: exclude stack locations */
5628 switch ((*p) & REMSET_TYPE_MASK) {
5629 case REMSET_LOCATION:
5630 ptr = (void**)(*p);
5631 //__builtin_prefetch (ptr);
5632 if (((void*)ptr < start_nursery || (void*)ptr >= end_nursery)) {
5633 gpointer old = *ptr;
5634 copy_object (ptr);
5635 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p\n", ptr, *ptr));
5636 if (old)
5637 binary_protocol_ptr_update (ptr, old, *ptr, (gpointer)LOAD_VTABLE (*ptr), safe_object_get_size (*ptr));
5638 if (!global && *ptr >= start_nursery && *ptr < end_nursery) {
5640 * If the object is pinned, each reference to it from nonpinned objects
5641 * becomes part of the global remset, which can grow very large.
5643 DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
5644 add_to_global_remset (ptr);
5646 } else {
5647 DEBUG (9, fprintf (gc_debug_file, "Skipping remset at %p holding %p\n", ptr, *ptr));
5649 return p + 1;
5650 case REMSET_RANGE:
5651 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5652 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5653 return p + 2;
5654 count = p [1];
5655 while (count-- > 0) {
5656 copy_object (ptr);
5657 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p (count: %d)\n", ptr, *ptr, (int)count));
5658 if (!global && *ptr >= start_nursery && *ptr < end_nursery)
5659 add_to_global_remset (ptr);
5660 ++ptr;
5662 return p + 2;
5663 case REMSET_OBJECT:
5664 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5665 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5666 return p + 1;
5667 scan_object ((char*)ptr);
5668 return p + 1;
5669 case REMSET_VTYPE: {
5670 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5671 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5672 return p + 3;
5673 desc = p [1];
5674 count = p [2];
5675 while (count-- > 0)
5676 ptr = (void**) scan_vtype ((char*)ptr, desc, start_nursery, end_nursery);
5677 return p + 3;
5679 default:
5680 g_assert_not_reached ();
5682 return NULL;
5685 #ifdef HEAVY_STATISTICS
5686 static mword*
5687 collect_store_remsets (RememberedSet *remset, mword *bumper)
5689 mword *p = remset->data;
5690 mword last = 0;
5691 mword last1 = 0;
5692 mword last2 = 0;
5694 while (p < remset->store_next) {
5695 switch ((*p) & REMSET_TYPE_MASK) {
5696 case REMSET_LOCATION:
5697 *bumper++ = *p;
5698 if (*p == last)
5699 ++stat_saved_remsets_1;
5700 last = *p;
5701 if (*p == last1 || *p == last2) {
5702 ++stat_saved_remsets_2;
5703 } else {
5704 last2 = last1;
5705 last1 = *p;
5707 p += 1;
5708 break;
5709 case REMSET_RANGE:
5710 p += 2;
5711 break;
5712 case REMSET_OBJECT:
5713 p += 1;
5714 break;
5715 case REMSET_VTYPE:
5716 p += 3;
5717 break;
5718 default:
5719 g_assert_not_reached ();
5723 return bumper;
5726 static void
5727 remset_stats (void)
5729 RememberedSet *remset;
5730 int size = 0;
5731 SgenThreadInfo *info;
5732 int i;
5733 mword *addresses, *bumper, *p, *r;
5735 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5736 for (info = thread_table [i]; info; info = info->next) {
5737 for (remset = info->remset; remset; remset = remset->next)
5738 size += remset->store_next - remset->data;
5741 for (remset = freed_thread_remsets; remset; remset = remset->next)
5742 size += remset->store_next - remset->data;
5743 for (remset = global_remset; remset; remset = remset->next)
5744 size += remset->store_next - remset->data;
5746 bumper = addresses = get_internal_mem (sizeof (mword) * size, INTERNAL_MEM_STATISTICS);
5748 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5749 for (info = thread_table [i]; info; info = info->next) {
5750 for (remset = info->remset; remset; remset = remset->next)
5751 bumper = collect_store_remsets (remset, bumper);
5754 for (remset = global_remset; remset; remset = remset->next)
5755 bumper = collect_store_remsets (remset, bumper);
5756 for (remset = freed_thread_remsets; remset; remset = remset->next)
5757 bumper = collect_store_remsets (remset, bumper);
5759 g_assert (bumper <= addresses + size);
5761 stat_store_remsets += bumper - addresses;
5763 sort_addresses ((void**)addresses, bumper - addresses);
5764 p = addresses;
5765 r = addresses + 1;
5766 while (r < bumper) {
5767 if (*r != *p)
5768 *++p = *r;
5769 ++r;
5772 stat_store_remsets_unique += p - addresses;
5774 free_internal_mem (addresses, INTERNAL_MEM_STATISTICS);
5776 #endif
5778 static void
5779 clear_thread_store_remset_buffer (SgenThreadInfo *info)
5781 *info->store_remset_buffer_index_addr = 0;
5782 memset (*info->store_remset_buffer_addr, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
5785 static void
5786 scan_from_remsets (void *start_nursery, void *end_nursery)
5788 int i;
5789 SgenThreadInfo *info;
5790 RememberedSet *remset;
5791 GenericStoreRememberedSet *store_remset;
5792 mword *p, *next_p, *store_pos;
5794 #ifdef HEAVY_STATISTICS
5795 remset_stats ();
5796 #endif
5798 /* the global one */
5799 for (remset = global_remset; remset; remset = remset->next) {
5800 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));
5801 store_pos = remset->data;
5802 for (p = remset->data; p < remset->store_next; p = next_p) {
5803 void **ptr = (void**)p [0];
5805 /*Ignore previously processed remset.*/
5806 if (!global_remset_location_was_not_added (ptr)) {
5807 next_p = p + 1;
5808 continue;
5811 next_p = handle_remset (p, start_nursery, end_nursery, TRUE);
5814 * Clear global remsets of locations which no longer point to the
5815 * nursery. Otherwise, they could grow indefinitely between major
5816 * collections.
5818 * Since all global remsets are location remsets, we don't need to unmask the pointer.
5820 if (ptr_in_nursery (*ptr)) {
5821 *store_pos ++ = p [0];
5822 HEAVY_STAT (++stat_global_remsets_readded);
5826 /* Truncate the remset */
5827 remset->store_next = store_pos;
5830 /* the generic store ones */
5831 store_remset = generic_store_remsets;
5832 while (store_remset) {
5833 GenericStoreRememberedSet *next = store_remset->next;
5835 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
5836 gpointer addr = store_remset->data [i];
5837 if (addr)
5838 handle_remset ((mword*)&addr, start_nursery, end_nursery, FALSE);
5841 free_internal_mem (store_remset, INTERNAL_MEM_STORE_REMSET);
5843 store_remset = next;
5845 generic_store_remsets = NULL;
5847 /* the per-thread ones */
5848 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5849 for (info = thread_table [i]; info; info = info->next) {
5850 RememberedSet *next;
5851 int j;
5852 for (remset = info->remset; remset; remset = next) {
5853 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));
5854 for (p = remset->data; p < remset->store_next;) {
5855 p = handle_remset (p, start_nursery, end_nursery, FALSE);
5857 remset->store_next = remset->data;
5858 next = remset->next;
5859 remset->next = NULL;
5860 if (remset != info->remset) {
5861 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5862 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5865 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j)
5866 handle_remset ((mword*)*info->store_remset_buffer_addr + j + 1, start_nursery, end_nursery, FALSE);
5867 clear_thread_store_remset_buffer (info);
5871 /* the freed thread ones */
5872 while (freed_thread_remsets) {
5873 RememberedSet *next;
5874 remset = freed_thread_remsets;
5875 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));
5876 for (p = remset->data; p < remset->store_next;) {
5877 p = handle_remset (p, start_nursery, end_nursery, FALSE);
5879 next = remset->next;
5880 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5881 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5882 freed_thread_remsets = next;
5887 * Clear the info in the remembered sets: we're doing a major collection, so
5888 * the per-thread ones are not needed and the global ones will be reconstructed
5889 * during the copy.
5891 static void
5892 clear_remsets (void)
5894 int i;
5895 SgenThreadInfo *info;
5896 RememberedSet *remset, *next;
5898 /* the global list */
5899 for (remset = global_remset; remset; remset = next) {
5900 remset->store_next = remset->data;
5901 next = remset->next;
5902 remset->next = NULL;
5903 if (remset != global_remset) {
5904 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5905 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5908 /* the generic store ones */
5909 while (generic_store_remsets) {
5910 GenericStoreRememberedSet *gs_next = generic_store_remsets->next;
5911 free_internal_mem (generic_store_remsets, INTERNAL_MEM_STORE_REMSET);
5912 generic_store_remsets = gs_next;
5914 /* the per-thread ones */
5915 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5916 for (info = thread_table [i]; info; info = info->next) {
5917 for (remset = info->remset; remset; remset = next) {
5918 remset->store_next = remset->data;
5919 next = remset->next;
5920 remset->next = NULL;
5921 if (remset != info->remset) {
5922 DEBUG (3, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5923 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5926 clear_thread_store_remset_buffer (info);
5930 /* the freed thread ones */
5931 while (freed_thread_remsets) {
5932 next = freed_thread_remsets->next;
5933 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", freed_thread_remsets->data));
5934 free_internal_mem (freed_thread_remsets, INTERNAL_MEM_REMSET);
5935 freed_thread_remsets = next;
5940 * Clear the thread local TLAB variables for all threads.
5942 static void
5943 clear_tlabs (void)
5945 SgenThreadInfo *info;
5946 int i;
5948 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5949 for (info = thread_table [i]; info; info = info->next) {
5950 /* A new TLAB will be allocated when the thread does its first allocation */
5951 *info->tlab_start_addr = NULL;
5952 *info->tlab_next_addr = NULL;
5953 *info->tlab_temp_end_addr = NULL;
5954 *info->tlab_real_end_addr = NULL;
5959 /* LOCKING: assumes the GC lock is held */
5960 static SgenThreadInfo*
5961 gc_register_current_thread (void *addr)
5963 int hash;
5964 SgenThreadInfo* info = malloc (sizeof (SgenThreadInfo));
5965 #ifndef HAVE_KW_THREAD
5966 SgenThreadInfo *__thread_info__ = info;
5967 #endif
5969 if (!info)
5970 return NULL;
5972 memset (info, 0, sizeof (SgenThreadInfo));
5973 #ifndef HAVE_KW_THREAD
5974 info->tlab_start = info->tlab_next = info->tlab_temp_end = info->tlab_real_end = NULL;
5976 g_assert (!pthread_getspecific (thread_info_key));
5977 pthread_setspecific (thread_info_key, info);
5978 #else
5979 thread_info = info;
5980 #endif
5982 info->id = ARCH_GET_THREAD ();
5983 info->stop_count = -1;
5984 info->skip = 0;
5985 info->signal = 0;
5986 info->stack_start = NULL;
5987 info->tlab_start_addr = &TLAB_START;
5988 info->tlab_next_addr = &TLAB_NEXT;
5989 info->tlab_temp_end_addr = &TLAB_TEMP_END;
5990 info->tlab_real_end_addr = &TLAB_REAL_END;
5991 info->store_remset_buffer_addr = &STORE_REMSET_BUFFER;
5992 info->store_remset_buffer_index_addr = &STORE_REMSET_BUFFER_INDEX;
5993 info->stopped_ip = NULL;
5994 info->stopped_domain = NULL;
5995 info->stopped_regs = NULL;
5997 binary_protocol_thread_register ((gpointer)info->id);
5999 #ifdef HAVE_KW_THREAD
6000 tlab_next_addr = &tlab_next;
6001 store_remset_buffer_index_addr = &store_remset_buffer_index;
6002 #endif
6004 /* try to get it with attributes first */
6005 #if defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)
6007 size_t size;
6008 void *sstart;
6009 pthread_attr_t attr;
6010 pthread_getattr_np (pthread_self (), &attr);
6011 pthread_attr_getstack (&attr, &sstart, &size);
6012 info->stack_start_limit = sstart;
6013 info->stack_end = (char*)sstart + size;
6014 pthread_attr_destroy (&attr);
6016 #elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
6017 info->stack_end = (char*)pthread_get_stackaddr_np (pthread_self ());
6018 info->stack_start_limit = (char*)info->stack_end - pthread_get_stacksize_np (pthread_self ());
6019 #else
6021 /* FIXME: we assume the stack grows down */
6022 gsize stack_bottom = (gsize)addr;
6023 stack_bottom += 4095;
6024 stack_bottom &= ~4095;
6025 info->stack_end = (char*)stack_bottom;
6027 #endif
6029 #ifdef HAVE_KW_THREAD
6030 stack_end = info->stack_end;
6031 #endif
6033 /* hash into the table */
6034 hash = HASH_PTHREAD_T (info->id) % THREAD_HASH_SIZE;
6035 info->next = thread_table [hash];
6036 thread_table [hash] = info;
6038 info->remset = alloc_remset (DEFAULT_REMSET_SIZE, info);
6039 pthread_setspecific (remembered_set_key, info->remset);
6040 #ifdef HAVE_KW_THREAD
6041 remembered_set = info->remset;
6042 #endif
6044 STORE_REMSET_BUFFER = get_internal_mem (sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE, INTERNAL_MEM_STORE_REMSET);
6045 STORE_REMSET_BUFFER_INDEX = 0;
6047 DEBUG (3, fprintf (gc_debug_file, "registered thread %p (%p) (hash: %d)\n", info, (gpointer)info->id, hash));
6049 if (gc_callbacks.thread_attach_func)
6050 info->runtime_data = gc_callbacks.thread_attach_func ();
6052 return info;
6055 static void
6056 add_generic_store_remset_from_buffer (gpointer *buffer)
6058 GenericStoreRememberedSet *remset = get_internal_mem (sizeof (GenericStoreRememberedSet), INTERNAL_MEM_STORE_REMSET);
6059 memcpy (remset->data, buffer + 1, sizeof (gpointer) * (STORE_REMSET_BUFFER_SIZE - 1));
6060 remset->next = generic_store_remsets;
6061 generic_store_remsets = remset;
6064 static void
6065 unregister_current_thread (void)
6067 int hash;
6068 SgenThreadInfo *prev = NULL;
6069 SgenThreadInfo *p;
6070 RememberedSet *rset;
6071 ARCH_THREAD_TYPE id = ARCH_GET_THREAD ();
6073 binary_protocol_thread_unregister ((gpointer)id);
6075 hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
6076 p = thread_table [hash];
6077 assert (p);
6078 DEBUG (3, fprintf (gc_debug_file, "unregister thread %p (%p)\n", p, (gpointer)p->id));
6079 while (!ARCH_THREAD_EQUALS (p->id, id)) {
6080 prev = p;
6081 p = p->next;
6083 if (prev == NULL) {
6084 thread_table [hash] = p->next;
6085 } else {
6086 prev->next = p->next;
6088 if (p->remset) {
6089 if (freed_thread_remsets) {
6090 for (rset = p->remset; rset->next; rset = rset->next)
6092 rset->next = freed_thread_remsets;
6093 freed_thread_remsets = p->remset;
6094 } else {
6095 freed_thread_remsets = p->remset;
6098 if (*p->store_remset_buffer_index_addr)
6099 add_generic_store_remset_from_buffer (*p->store_remset_buffer_addr);
6100 free_internal_mem (*p->store_remset_buffer_addr, INTERNAL_MEM_STORE_REMSET);
6101 free (p);
6104 static void
6105 unregister_thread (void *k)
6107 g_assert (!mono_domain_get ());
6108 LOCK_GC;
6109 unregister_current_thread ();
6110 UNLOCK_GC;
6113 gboolean
6114 mono_gc_register_thread (void *baseptr)
6116 SgenThreadInfo *info;
6118 LOCK_GC;
6119 init_stats ();
6120 info = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ());
6121 if (info == NULL)
6122 info = gc_register_current_thread (baseptr);
6123 UNLOCK_GC;
6124 return info != NULL;
6127 #if USE_PTHREAD_INTERCEPT
6129 #undef pthread_create
6130 #undef pthread_join
6131 #undef pthread_detach
6133 typedef struct {
6134 void *(*start_routine) (void *);
6135 void *arg;
6136 int flags;
6137 MonoSemType registered;
6138 } SgenThreadStartInfo;
6140 static void*
6141 gc_start_thread (void *arg)
6143 SgenThreadStartInfo *start_info = arg;
6144 SgenThreadInfo* info;
6145 void *t_arg = start_info->arg;
6146 void *(*start_func) (void*) = start_info->start_routine;
6147 void *result;
6148 int post_result;
6150 LOCK_GC;
6151 info = gc_register_current_thread (&result);
6152 UNLOCK_GC;
6153 post_result = MONO_SEM_POST (&(start_info->registered));
6154 g_assert (!post_result);
6155 result = start_func (t_arg);
6156 g_assert (!mono_domain_get ());
6158 * this is done by the pthread key dtor
6159 LOCK_GC;
6160 unregister_current_thread ();
6161 UNLOCK_GC;
6164 return result;
6168 mono_gc_pthread_create (pthread_t *new_thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
6170 SgenThreadStartInfo *start_info;
6171 int result;
6173 start_info = malloc (sizeof (SgenThreadStartInfo));
6174 if (!start_info)
6175 return ENOMEM;
6176 result = MONO_SEM_INIT (&(start_info->registered), 0);
6177 g_assert (!result);
6178 start_info->arg = arg;
6179 start_info->start_routine = start_routine;
6181 result = pthread_create (new_thread, attr, gc_start_thread, start_info);
6182 if (result == 0) {
6183 while (MONO_SEM_WAIT (&(start_info->registered)) != 0) {
6184 /*if (EINTR != errno) ABORT("sem_wait failed"); */
6187 MONO_SEM_DESTROY (&(start_info->registered));
6188 free (start_info);
6189 return result;
6193 mono_gc_pthread_join (pthread_t thread, void **retval)
6195 return pthread_join (thread, retval);
6199 mono_gc_pthread_detach (pthread_t thread)
6201 return pthread_detach (thread);
6204 #endif /* USE_PTHREAD_INTERCEPT */
6207 * ######################################################################
6208 * ######## Write barriers
6209 * ######################################################################
6212 static RememberedSet*
6213 alloc_remset (int size, gpointer id) {
6214 RememberedSet* res = get_internal_mem (sizeof (RememberedSet) + (size * sizeof (gpointer)), INTERNAL_MEM_REMSET);
6215 res->store_next = res->data;
6216 res->end_set = res->data + size;
6217 res->next = NULL;
6218 DEBUG (4, fprintf (gc_debug_file, "Allocated remset size %d at %p for %p\n", size, res->data, id));
6219 return res;
6223 * Note: the write barriers first do the needed GC work and then do the actual store:
6224 * this way the value is visible to the conservative GC scan after the write barrier
6225 * itself. If a GC interrupts the barrier in the middle, value will be kept alive by
6226 * the conservative scan, otherwise by the remembered set scan.
6228 void
6229 mono_gc_wbarrier_set_field (MonoObject *obj, gpointer field_ptr, MonoObject* value)
6231 RememberedSet *rs;
6232 TLAB_ACCESS_INIT;
6233 HEAVY_STAT (++stat_wbarrier_set_field);
6234 if (ptr_in_nursery (field_ptr)) {
6235 *(void**)field_ptr = value;
6236 return;
6238 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", field_ptr));
6239 LOCK_GC;
6240 rs = REMEMBERED_SET;
6241 if (rs->store_next < rs->end_set) {
6242 *(rs->store_next++) = (mword)field_ptr;
6243 *(void**)field_ptr = value;
6244 UNLOCK_GC;
6245 return;
6247 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6248 rs->next = REMEMBERED_SET;
6249 REMEMBERED_SET = rs;
6250 #ifdef HAVE_KW_THREAD
6251 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6252 #endif
6253 *(rs->store_next++) = (mword)field_ptr;
6254 *(void**)field_ptr = value;
6255 UNLOCK_GC;
6258 void
6259 mono_gc_wbarrier_set_arrayref (MonoArray *arr, gpointer slot_ptr, MonoObject* value)
6261 RememberedSet *rs;
6262 TLAB_ACCESS_INIT;
6263 HEAVY_STAT (++stat_wbarrier_set_arrayref);
6264 if (ptr_in_nursery (slot_ptr)) {
6265 *(void**)slot_ptr = value;
6266 return;
6268 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", slot_ptr));
6269 LOCK_GC;
6270 rs = REMEMBERED_SET;
6271 if (rs->store_next < rs->end_set) {
6272 *(rs->store_next++) = (mword)slot_ptr;
6273 *(void**)slot_ptr = value;
6274 UNLOCK_GC;
6275 return;
6277 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6278 rs->next = REMEMBERED_SET;
6279 REMEMBERED_SET = rs;
6280 #ifdef HAVE_KW_THREAD
6281 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6282 #endif
6283 *(rs->store_next++) = (mword)slot_ptr;
6284 *(void**)slot_ptr = value;
6285 UNLOCK_GC;
6288 void
6289 mono_gc_wbarrier_arrayref_copy (gpointer dest_ptr, gpointer src_ptr, int count)
6291 RememberedSet *rs;
6292 TLAB_ACCESS_INIT;
6293 HEAVY_STAT (++stat_wbarrier_arrayref_copy);
6294 LOCK_GC;
6295 memmove (dest_ptr, src_ptr, count * sizeof (gpointer));
6296 if (ptr_in_nursery (dest_ptr)) {
6297 UNLOCK_GC;
6298 return;
6300 rs = REMEMBERED_SET;
6301 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p, %d\n", dest_ptr, count));
6302 if (rs->store_next + 1 < rs->end_set) {
6303 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
6304 *(rs->store_next++) = count;
6305 UNLOCK_GC;
6306 return;
6308 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6309 rs->next = REMEMBERED_SET;
6310 REMEMBERED_SET = rs;
6311 #ifdef HAVE_KW_THREAD
6312 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6313 #endif
6314 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
6315 *(rs->store_next++) = count;
6316 UNLOCK_GC;
6319 static char *found_obj;
6321 static void
6322 find_object_for_ptr_callback (char *obj, size_t size, char *ptr)
6324 if (ptr >= obj && ptr < obj + size) {
6325 g_assert (!found_obj);
6326 found_obj = obj;
6330 /* for use in the debugger */
6331 char* find_object_for_ptr (char *ptr);
6332 char*
6333 find_object_for_ptr (char *ptr)
6335 LOSObject *bigobj;
6337 if (ptr >= nursery_section->data && ptr < nursery_section->end_data) {
6338 found_obj = NULL;
6339 scan_area_with_callback (nursery_section->data, nursery_section->end_data,
6340 (IterateObjectCallbackFunc)find_object_for_ptr_callback, ptr);
6341 if (found_obj)
6342 return found_obj;
6345 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
6346 if (ptr >= bigobj->data && ptr < bigobj->data + bigobj->size)
6347 return bigobj->data;
6351 * Very inefficient, but this is debugging code, supposed to
6352 * be called from gdb, so we don't care.
6354 found_obj = NULL;
6355 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)find_object_for_ptr_callback, ptr);
6356 return found_obj;
6359 static void
6360 evacuate_remset_buffer (void)
6362 gpointer *buffer;
6363 TLAB_ACCESS_INIT;
6365 buffer = STORE_REMSET_BUFFER;
6367 add_generic_store_remset_from_buffer (buffer);
6368 memset (buffer, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
6370 STORE_REMSET_BUFFER_INDEX = 0;
6373 void
6374 mono_gc_wbarrier_generic_nostore (gpointer ptr)
6376 gpointer *buffer;
6377 int index;
6378 TLAB_ACCESS_INIT;
6380 HEAVY_STAT (++stat_wbarrier_generic_store);
6382 #ifdef XDOMAIN_CHECKS_IN_WBARRIER
6383 /* FIXME: ptr_in_heap must be called with the GC lock held */
6384 if (xdomain_checks && *(MonoObject**)ptr && ptr_in_heap (ptr)) {
6385 char *start = find_object_for_ptr (ptr);
6386 MonoObject *value = *(MonoObject**)ptr;
6387 LOCK_GC;
6388 g_assert (start);
6389 if (start) {
6390 MonoObject *obj = (MonoObject*)start;
6391 if (obj->vtable->domain != value->vtable->domain)
6392 g_assert (is_xdomain_ref_allowed (ptr, start, obj->vtable->domain));
6394 UNLOCK_GC;
6396 #endif
6398 LOCK_GC;
6400 if (*(gpointer*)ptr)
6401 binary_protocol_wbarrier (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
6403 if (ptr_in_nursery (ptr) || ptr_on_stack (ptr) || !ptr_in_nursery (*(gpointer*)ptr)) {
6404 DEBUG (8, fprintf (gc_debug_file, "Skipping remset at %p\n", ptr));
6405 UNLOCK_GC;
6406 return;
6409 buffer = STORE_REMSET_BUFFER;
6410 index = STORE_REMSET_BUFFER_INDEX;
6411 /* This simple optimization eliminates a sizable portion of
6412 entries. Comparing it to the last but one entry as well
6413 doesn't eliminate significantly more entries. */
6414 if (buffer [index] == ptr) {
6415 UNLOCK_GC;
6416 return;
6419 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", ptr));
6420 HEAVY_STAT (++stat_wbarrier_generic_store_remset);
6422 ++index;
6423 if (index >= STORE_REMSET_BUFFER_SIZE) {
6424 evacuate_remset_buffer ();
6425 index = STORE_REMSET_BUFFER_INDEX;
6426 g_assert (index == 0);
6427 ++index;
6429 buffer [index] = ptr;
6430 STORE_REMSET_BUFFER_INDEX = index;
6432 UNLOCK_GC;
6435 void
6436 mono_gc_wbarrier_generic_store (gpointer ptr, MonoObject* value)
6438 DEBUG (8, fprintf (gc_debug_file, "Wbarrier store at %p to %p (%s)\n", ptr, value, value ? safe_name (value) : "null"));
6439 *(void**)ptr = value;
6440 if (ptr_in_nursery (value))
6441 mono_gc_wbarrier_generic_nostore (ptr);
6444 void mono_gc_wbarrier_value_copy_bitmap (gpointer _dest, gpointer _src, int size, unsigned bitmap)
6446 mword *dest = _dest;
6447 mword *src = _src;
6449 while (size) {
6450 if (bitmap & 0x1)
6451 mono_gc_wbarrier_generic_store (dest, (MonoObject*)*src);
6452 else
6453 *dest = *src;
6454 ++src;
6455 ++dest;
6456 size -= SIZEOF_VOID_P;
6457 bitmap >>= 1;
6462 void
6463 mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *klass)
6465 RememberedSet *rs;
6466 TLAB_ACCESS_INIT;
6467 HEAVY_STAT (++stat_wbarrier_value_copy);
6468 g_assert (klass->valuetype);
6469 LOCK_GC;
6470 memmove (dest, src, count * mono_class_value_size (klass, NULL));
6471 rs = REMEMBERED_SET;
6472 if (ptr_in_nursery (dest) || ptr_on_stack (dest) || !klass->has_references) {
6473 UNLOCK_GC;
6474 return;
6476 g_assert (klass->gc_descr_inited);
6477 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));
6479 if (rs->store_next + 3 < rs->end_set) {
6480 *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
6481 *(rs->store_next++) = (mword)klass->gc_descr;
6482 *(rs->store_next++) = (mword)count;
6483 UNLOCK_GC;
6484 return;
6486 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6487 rs->next = REMEMBERED_SET;
6488 REMEMBERED_SET = rs;
6489 #ifdef HAVE_KW_THREAD
6490 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6491 #endif
6492 *(rs->store_next++) = (mword)dest | REMSET_VTYPE;
6493 *(rs->store_next++) = (mword)klass->gc_descr;
6494 *(rs->store_next++) = (mword)count;
6495 UNLOCK_GC;
6499 * mono_gc_wbarrier_object_copy:
6501 * Write barrier to call when obj is the result of a clone or copy of an object.
6503 void
6504 mono_gc_wbarrier_object_copy (MonoObject* obj, MonoObject *src)
6506 RememberedSet *rs;
6507 int size;
6509 TLAB_ACCESS_INIT;
6510 HEAVY_STAT (++stat_wbarrier_object_copy);
6511 rs = REMEMBERED_SET;
6512 DEBUG (6, fprintf (gc_debug_file, "Adding object remset for %p\n", obj));
6513 size = mono_object_class (obj)->instance_size;
6514 LOCK_GC;
6515 /* do not copy the sync state */
6516 memcpy ((char*)obj + sizeof (MonoObject), (char*)src + sizeof (MonoObject),
6517 size - sizeof (MonoObject));
6518 if (ptr_in_nursery (obj) || ptr_on_stack (obj)) {
6519 UNLOCK_GC;
6520 return;
6522 if (rs->store_next < rs->end_set) {
6523 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
6524 UNLOCK_GC;
6525 return;
6527 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6528 rs->next = REMEMBERED_SET;
6529 REMEMBERED_SET = rs;
6530 #ifdef HAVE_KW_THREAD
6531 mono_sgen_thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6532 #endif
6533 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
6534 UNLOCK_GC;
6538 * ######################################################################
6539 * ######## Collector debugging
6540 * ######################################################################
6543 const char*descriptor_types [] = {
6544 "run_length",
6545 "small_bitmap",
6546 "string",
6547 "complex",
6548 "vector",
6549 "array",
6550 "large_bitmap",
6551 "complex_arr"
6554 void
6555 describe_ptr (char *ptr)
6557 MonoVTable *vtable;
6558 mword desc;
6559 int type;
6561 if (ptr_in_nursery (ptr)) {
6562 printf ("Pointer inside nursery.\n");
6563 } else {
6564 if (major_ptr_is_in_non_pinned_space (ptr)) {
6565 printf ("Pointer inside oldspace.\n");
6566 } else if (obj_is_from_pinned_alloc (ptr)) {
6567 printf ("Pointer is inside a pinned chunk.\n");
6568 } else {
6569 printf ("Pointer unknown.\n");
6570 return;
6574 if (object_is_pinned (ptr))
6575 printf ("Object is pinned.\n");
6577 if (object_is_forwarded (ptr))
6578 printf ("Object is forwared.\n");
6580 // FIXME: Handle pointers to the inside of objects
6581 vtable = (MonoVTable*)LOAD_VTABLE (ptr);
6583 printf ("VTable: %p\n", vtable);
6584 if (vtable == NULL) {
6585 printf ("VTable is invalid (empty).\n");
6586 return;
6588 if (ptr_in_nursery (vtable)) {
6589 printf ("VTable is invalid (points inside nursery).\n");
6590 return;
6592 printf ("Class: %s\n", vtable->klass->name);
6594 desc = ((GCVTable*)vtable)->desc;
6595 printf ("Descriptor: %lx\n", (long)desc);
6597 type = desc & 0x7;
6598 printf ("Descriptor type: %d (%s)\n", type, descriptor_types [type]);
6601 static mword*
6602 find_in_remset_loc (mword *p, char *addr, gboolean *found)
6604 void **ptr;
6605 mword count, desc;
6606 size_t skip_size;
6608 switch ((*p) & REMSET_TYPE_MASK) {
6609 case REMSET_LOCATION:
6610 if (*p == (mword)addr)
6611 *found = TRUE;
6612 return p + 1;
6613 case REMSET_RANGE:
6614 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6615 count = p [1];
6616 if ((void**)addr >= ptr && (void**)addr < ptr + count)
6617 *found = TRUE;
6618 return p + 2;
6619 case REMSET_OBJECT:
6620 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6621 count = safe_object_get_size ((MonoObject*)ptr);
6622 count += (ALLOC_ALIGN - 1);
6623 count &= (ALLOC_ALIGN - 1);
6624 count /= sizeof (mword);
6625 if ((void**)addr >= ptr && (void**)addr < ptr + count)
6626 *found = TRUE;
6627 return p + 1;
6628 case REMSET_VTYPE:
6629 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6630 desc = p [1];
6631 count = p [2];
6633 switch (desc & 0x7) {
6634 case DESC_TYPE_RUN_LENGTH:
6635 OBJ_RUN_LEN_SIZE (skip_size, desc, ptr);
6636 break;
6637 case DESC_TYPE_SMALL_BITMAP:
6638 OBJ_BITMAP_SIZE (skip_size, desc, start);
6639 break;
6640 default:
6641 // FIXME:
6642 g_assert_not_reached ();
6645 /* The descriptor includes the size of MonoObject */
6646 skip_size -= sizeof (MonoObject);
6647 skip_size *= count;
6648 if ((void**)addr >= ptr && (void**)addr < ptr + (skip_size / sizeof (gpointer)))
6649 *found = TRUE;
6651 return p + 3;
6652 default:
6653 g_assert_not_reached ();
6655 return NULL;
6659 * Return whenever ADDR occurs in the remembered sets
6661 static gboolean
6662 find_in_remsets (char *addr)
6664 int i;
6665 SgenThreadInfo *info;
6666 RememberedSet *remset;
6667 GenericStoreRememberedSet *store_remset;
6668 mword *p;
6669 gboolean found = FALSE;
6671 /* the global one */
6672 for (remset = global_remset; remset; remset = remset->next) {
6673 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));
6674 for (p = remset->data; p < remset->store_next;) {
6675 p = find_in_remset_loc (p, addr, &found);
6676 if (found)
6677 return TRUE;
6681 /* the generic store ones */
6682 for (store_remset = generic_store_remsets; store_remset; store_remset = store_remset->next) {
6683 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
6684 if (store_remset->data [i] == addr)
6685 return TRUE;
6689 /* the per-thread ones */
6690 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
6691 for (info = thread_table [i]; info; info = info->next) {
6692 int j;
6693 for (remset = info->remset; remset; remset = remset->next) {
6694 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));
6695 for (p = remset->data; p < remset->store_next;) {
6696 p = find_in_remset_loc (p, addr, &found);
6697 if (found)
6698 return TRUE;
6701 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j) {
6702 if ((*info->store_remset_buffer_addr) [j + 1] == addr)
6703 return TRUE;
6708 /* the freed thread ones */
6709 for (remset = freed_thread_remsets; remset; remset = remset->next) {
6710 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));
6711 for (p = remset->data; p < remset->store_next;) {
6712 p = find_in_remset_loc (p, addr, &found);
6713 if (found)
6714 return TRUE;
6718 return FALSE;
6721 static gboolean missing_remsets;
6724 * We let a missing remset slide if the target object is pinned,
6725 * because the store might have happened but the remset not yet added,
6726 * but in that case the target must be pinned. We might theoretically
6727 * miss some missing remsets this way, but it's very unlikely.
6729 #undef HANDLE_PTR
6730 #define HANDLE_PTR(ptr,obj) do { \
6731 if (*(ptr) && (char*)*(ptr) >= nursery_start && (char*)*(ptr) < nursery_next) { \
6732 if (!find_in_remsets ((char*)(ptr))) { \
6733 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); \
6734 binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
6735 if (!object_is_pinned (*(ptr))) \
6736 missing_remsets = TRUE; \
6739 } while (0)
6742 * Check that each object reference which points into the nursery can
6743 * be found in the remembered sets.
6745 static void
6746 check_consistency_callback (char *start, size_t size, void *dummy)
6748 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
6749 DEBUG (8, fprintf (gc_debug_file, "Scanning object %p, vtable: %p (%s)\n", start, vt, vt->klass->name));
6751 #define SCAN_OBJECT_ACTION
6752 #include "sgen-scan-object.h"
6756 * Perform consistency check of the heap.
6758 * Assumes the world is stopped.
6760 static void
6761 check_consistency (void)
6763 LOSObject *bigobj;
6765 // Need to add more checks
6767 missing_remsets = FALSE;
6769 DEBUG (1, fprintf (gc_debug_file, "Begin heap consistency check...\n"));
6771 // Check that oldspace->newspace pointers are registered with the collector
6772 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
6774 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
6775 check_consistency_callback (bigobj->data, bigobj->size, NULL);
6777 DEBUG (1, fprintf (gc_debug_file, "Heap consistency check done.\n"));
6779 #ifdef BINARY_PROTOCOL
6780 if (!binary_protocol_file)
6781 #endif
6782 g_assert (!missing_remsets);
6786 #undef HANDLE_PTR
6787 #define HANDLE_PTR(ptr,obj) do { \
6788 if (*(ptr)) \
6789 g_assert (LOAD_VTABLE (*(ptr))); \
6790 } while (0)
6792 static void
6793 check_major_refs_callback (char *start, size_t size, void *dummy)
6795 #define SCAN_OBJECT_ACTION
6796 #include "sgen-scan-object.h"
6799 static void
6800 check_major_refs (void)
6802 LOSObject *bigobj;
6804 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_major_refs_callback, NULL);
6806 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
6807 check_major_refs_callback (bigobj->data, bigobj->size, NULL);
6810 /* Check that the reference is valid */
6811 #undef HANDLE_PTR
6812 #define HANDLE_PTR(ptr,obj) do { \
6813 if (*(ptr)) { \
6814 g_assert (safe_name (*(ptr)) != NULL); \
6816 } while (0)
6819 * check_object:
6821 * Perform consistency check on an object. Currently we only check that the
6822 * reference fields are valid.
6824 void
6825 check_object (char *start)
6827 if (!start)
6828 return;
6830 #include "sgen-scan-object.h"
6834 * ######################################################################
6835 * ######## Other mono public interface functions.
6836 * ######################################################################
6839 void
6840 mono_gc_collect (int generation)
6842 LOCK_GC;
6843 stop_world ();
6844 if (generation == 0) {
6845 collect_nursery (0);
6846 } else {
6847 major_collection ("user request");
6849 restart_world ();
6850 UNLOCK_GC;
6854 mono_gc_max_generation (void)
6856 return 1;
6860 mono_gc_collection_count (int generation)
6862 if (generation == 0)
6863 return num_minor_gcs;
6864 return num_major_gcs;
6867 int64_t
6868 mono_gc_get_used_size (void)
6870 gint64 tot = 0;
6871 LOCK_GC;
6872 tot = los_memory_usage;
6873 tot += nursery_section->next_data - nursery_section->data;
6874 tot += major_get_used_size ();
6875 /* FIXME: account for pinned objects */
6876 UNLOCK_GC;
6877 return tot;
6880 int64_t
6881 mono_gc_get_heap_size (void)
6883 return total_alloc;
6886 void
6887 mono_gc_disable (void)
6889 LOCK_GC;
6890 gc_disabled++;
6891 UNLOCK_GC;
6894 void
6895 mono_gc_enable (void)
6897 LOCK_GC;
6898 gc_disabled--;
6899 UNLOCK_GC;
6903 mono_gc_get_los_limit (void)
6905 return MAX_SMALL_OBJ_SIZE;
6908 gboolean
6909 mono_object_is_alive (MonoObject* o)
6911 return TRUE;
6915 mono_gc_get_generation (MonoObject *obj)
6917 if (ptr_in_nursery (obj))
6918 return 0;
6919 return 1;
6922 void
6923 mono_gc_enable_events (void)
6927 void
6928 mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
6930 LOCK_GC;
6931 mono_gc_register_disappearing_link (obj, link_addr, track);
6932 UNLOCK_GC;
6935 void
6936 mono_gc_weak_link_remove (void **link_addr)
6938 LOCK_GC;
6939 mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
6940 UNLOCK_GC;
6943 MonoObject*
6944 mono_gc_weak_link_get (void **link_addr)
6946 if (!*link_addr)
6947 return NULL;
6948 return (MonoObject*) REVEAL_POINTER (*link_addr);
6951 gboolean
6952 mono_gc_ephemeron_array_add (MonoObject *obj)
6954 EphemeronLinkNode *node;
6956 LOCK_GC;
6958 node = get_internal_mem (sizeof (EphemeronLinkNode), INTERNAL_MEM_EPHEMERON_LINK);
6959 if (!node) {
6960 UNLOCK_GC;
6961 return FALSE;
6963 node->array = (char*)obj;
6964 node->next = ephemeron_list;
6965 ephemeron_list = node;
6967 DEBUG (5, fprintf (gc_debug_file, "Registered ephemeron array %p\n", obj));
6969 UNLOCK_GC;
6970 return TRUE;
6973 void*
6974 mono_gc_make_descr_from_bitmap (gsize *bitmap, int numbits)
6976 if (numbits < ((sizeof (*bitmap) * 8) - ROOT_DESC_TYPE_SHIFT)) {
6977 return (void*)MAKE_ROOT_DESC (ROOT_DESC_BITMAP, bitmap [0]);
6978 } else {
6979 mword complex = alloc_complex_descriptor (bitmap, numbits);
6980 return (void*)MAKE_ROOT_DESC (ROOT_DESC_COMPLEX, complex);
6984 void*
6985 mono_gc_make_root_descr_user (MonoGCRootMarkFunc marker)
6987 void *descr;
6989 g_assert (user_descriptors_next < MAX_USER_DESCRIPTORS);
6990 descr = (void*)MAKE_ROOT_DESC (ROOT_DESC_USER, (mword)user_descriptors_next);
6991 user_descriptors [user_descriptors_next ++] = marker;
6993 return descr;
6996 void*
6997 mono_gc_alloc_fixed (size_t size, void *descr)
6999 /* FIXME: do a single allocation */
7000 void *res = calloc (1, size);
7001 if (!res)
7002 return NULL;
7003 if (!mono_gc_register_root (res, size, descr)) {
7004 free (res);
7005 res = NULL;
7007 return res;
7010 void
7011 mono_gc_free_fixed (void* addr)
7013 mono_gc_deregister_root (addr);
7014 free (addr);
7017 void*
7018 mono_gc_invoke_with_gc_lock (MonoGCLockedCallbackFunc func, void *data)
7020 void *result;
7021 LOCK_INTERRUPTION;
7022 result = func (data);
7023 UNLOCK_INTERRUPTION;
7024 return result;
7027 gboolean
7028 mono_gc_is_gc_thread (void)
7030 gboolean result;
7031 LOCK_GC;
7032 result = mono_sgen_thread_info_lookup (ARCH_GET_THREAD ()) != NULL;
7033 UNLOCK_GC;
7034 return result;
7037 #ifdef USER_CONFIG
7039 /* Tries to extract a number from the passed string, taking in to account m, k
7040 * and g suffixes */
7041 static gboolean
7042 parse_environment_string_extract_number (gchar *str, glong *out)
7044 char *endptr;
7045 int len = strlen (str), shift = 0;
7046 glong val;
7047 gboolean is_suffix = FALSE;
7048 char suffix;
7050 switch (str [len - 1]) {
7051 case 'g':
7052 case 'G':
7053 shift += 10;
7054 case 'm':
7055 case 'M':
7056 shift += 10;
7057 case 'k':
7058 case 'K':
7059 shift += 10;
7060 is_suffix = TRUE;
7061 suffix = str [len - 1];
7062 break;
7065 errno = 0;
7066 val = strtol (str, &endptr, 10);
7068 if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
7069 || (errno != 0 && val == 0) || (endptr == str))
7070 return FALSE;
7072 if (is_suffix) {
7073 if (*(endptr + 1)) /* Invalid string. */
7074 return FALSE;
7075 val <<= shift;
7078 *out = val;
7079 return TRUE;
7082 #endif
7084 void
7085 mono_gc_base_init (void)
7087 char *env;
7088 char **opts, **ptr;
7089 struct sigaction sinfo;
7091 LOCK_INIT (gc_mutex);
7092 LOCK_GC;
7093 if (gc_initialized) {
7094 UNLOCK_GC;
7095 return;
7097 pagesize = mono_pagesize ();
7098 gc_debug_file = stderr;
7100 #ifdef USER_CONFIG
7102 if ((env = getenv ("MONO_GC_PARAMS"))) {
7103 if (g_str_has_prefix (env, "nursery-size")) {
7104 int index = 0;
7105 long val;
7106 while (env [index] && env [index++] != '=')
7108 if (env [index] && parse_environment_string_extract_number (env
7109 + index, &val)) {
7110 default_nursery_size = val;
7111 #ifdef ALIGN_NURSERY
7112 if ((val & (val - 1))) {
7113 fprintf (stderr, "The nursery size must be a power of two.\n");
7114 exit (1);
7117 default_nursery_bits = 0;
7118 while (1 << (++ default_nursery_bits) != default_nursery_size)
7120 #endif
7121 } else {
7122 fprintf (stderr, "nursery-size must be an integer.\n");
7123 exit (1);
7125 } else {
7126 fprintf (stderr, "MONO_GC_PARAMS must be of the form 'nursery-size=N' (where N is an integer, possibly with a k, m or a g suffix).\n");
7127 exit (1);
7131 #endif
7133 nursery_size = DEFAULT_NURSERY_SIZE;
7134 minor_collection_allowance = MIN_MINOR_COLLECTION_ALLOWANCE;
7136 major_init ();
7138 if ((env = getenv ("MONO_GC_DEBUG"))) {
7139 opts = g_strsplit (env, ",", -1);
7140 for (ptr = opts; ptr && *ptr; ptr ++) {
7141 char *opt = *ptr;
7142 if (opt [0] >= '0' && opt [0] <= '9') {
7143 gc_debug_level = atoi (opt);
7144 opt++;
7145 if (opt [0] == ':')
7146 opt++;
7147 if (opt [0]) {
7148 char *rf = g_strdup_printf ("%s.%d", opt, getpid ());
7149 gc_debug_file = fopen (rf, "wb");
7150 if (!gc_debug_file)
7151 gc_debug_file = stderr;
7152 g_free (rf);
7154 } else if (!strcmp (opt, "collect-before-allocs")) {
7155 collect_before_allocs = TRUE;
7156 } else if (!strcmp (opt, "check-at-minor-collections")) {
7157 consistency_check_at_minor_collection = TRUE;
7158 nursery_clear_policy = CLEAR_AT_GC;
7159 } else if (!strcmp (opt, "xdomain-checks")) {
7160 xdomain_checks = TRUE;
7161 } else if (!strcmp (opt, "clear-at-gc")) {
7162 nursery_clear_policy = CLEAR_AT_GC;
7163 } else if (!strcmp (opt, "conservative-stack-mark")) {
7164 conservative_stack_mark = TRUE;
7165 } else if (!strcmp (opt, "check-scan-starts")) {
7166 do_scan_starts_check = TRUE;
7167 } else if (g_str_has_prefix (opt, "heap-dump=")) {
7168 char *filename = strchr (opt, '=') + 1;
7169 nursery_clear_policy = CLEAR_AT_GC;
7170 heap_dump_file = fopen (filename, "w");
7171 if (heap_dump_file)
7172 fprintf (heap_dump_file, "<sgen-dump>\n");
7173 #ifdef BINARY_PROTOCOL
7174 } else if (g_str_has_prefix (opt, "binary-protocol=")) {
7175 char *filename = strchr (opt, '=') + 1;
7176 binary_protocol_file = fopen (filename, "w");
7177 #endif
7178 } else {
7179 fprintf (stderr, "Invalid format for the MONO_GC_DEBUG env variable: '%s'\n", env);
7180 fprintf (stderr, "The format is: MONO_GC_DEBUG=[l[:filename]|<option>]+ where l is a debug level 0-9.\n");
7181 fprintf (stderr, "Valid options are: collect-before-allocs, check-at-minor-collections, xdomain-checks, clear-at-gc.\n");
7182 exit (1);
7185 g_strfreev (opts);
7188 suspend_ack_semaphore_ptr = &suspend_ack_semaphore;
7189 MONO_SEM_INIT (&suspend_ack_semaphore, 0);
7191 sigfillset (&sinfo.sa_mask);
7192 sinfo.sa_flags = SA_RESTART | SA_SIGINFO;
7193 sinfo.sa_sigaction = suspend_handler;
7194 if (sigaction (suspend_signal_num, &sinfo, NULL) != 0) {
7195 g_error ("failed sigaction");
7198 sinfo.sa_handler = restart_handler;
7199 if (sigaction (restart_signal_num, &sinfo, NULL) != 0) {
7200 g_error ("failed sigaction");
7203 sigfillset (&suspend_signal_mask);
7204 sigdelset (&suspend_signal_mask, restart_signal_num);
7206 global_remset = alloc_remset (1024, NULL);
7207 global_remset->next = NULL;
7209 pthread_key_create (&remembered_set_key, unregister_thread);
7211 #ifndef HAVE_KW_THREAD
7212 pthread_key_create (&thread_info_key, NULL);
7213 #endif
7215 gc_initialized = TRUE;
7216 UNLOCK_GC;
7217 mono_gc_register_thread (&sinfo);
7221 mono_gc_get_suspend_signal (void)
7223 return suspend_signal_num;
7226 enum {
7227 ATYPE_NORMAL,
7228 ATYPE_VECTOR,
7229 ATYPE_SMALL,
7230 ATYPE_NUM
7233 #ifdef HAVE_KW_THREAD
7234 #define EMIT_TLS_ACCESS(mb,dummy,offset) do { \
7235 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
7236 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
7237 mono_mb_emit_i4 ((mb), (offset)); \
7238 } while (0)
7239 #else
7240 #define EMIT_TLS_ACCESS(mb,member,dummy) do { \
7241 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
7242 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
7243 mono_mb_emit_i4 ((mb), thread_info_key); \
7244 mono_mb_emit_icon ((mb), G_STRUCT_OFFSET (SgenThreadInfo, member)); \
7245 mono_mb_emit_byte ((mb), CEE_ADD); \
7246 mono_mb_emit_byte ((mb), CEE_LDIND_I); \
7247 } while (0)
7248 #endif
7250 #ifdef MANAGED_ALLOCATION
7251 /* FIXME: Do this in the JIT, where specialized allocation sequences can be created
7252 * for each class. This is currently not easy to do, as it is hard to generate basic
7253 * blocks + branches, but it is easy with the linear IL codebase.
7255 * For this to work we'd need to solve the TLAB race, first. Now we
7256 * require the allocator to be in a few known methods to make sure
7257 * that they are executed atomically via the restart mechanism.
7259 static MonoMethod*
7260 create_allocator (int atype)
7262 int p_var, size_var;
7263 guint32 slowpath_branch, max_size_branch;
7264 MonoMethodBuilder *mb;
7265 MonoMethod *res;
7266 MonoMethodSignature *csig;
7267 static gboolean registered = FALSE;
7268 int tlab_next_addr_var, new_next_var;
7269 int num_params, i;
7270 const char *name = NULL;
7271 AllocatorWrapperInfo *info;
7273 #ifdef HAVE_KW_THREAD
7274 int tlab_next_addr_offset = -1;
7275 int tlab_temp_end_offset = -1;
7277 MONO_THREAD_VAR_OFFSET (tlab_next_addr, tlab_next_addr_offset);
7278 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7280 g_assert (tlab_next_addr_offset != -1);
7281 g_assert (tlab_temp_end_offset != -1);
7282 #endif
7284 if (!registered) {
7285 mono_register_jit_icall (mono_gc_alloc_obj, "mono_gc_alloc_obj", mono_create_icall_signature ("object ptr int"), FALSE);
7286 mono_register_jit_icall (mono_gc_alloc_vector, "mono_gc_alloc_vector", mono_create_icall_signature ("object ptr int int"), FALSE);
7287 registered = TRUE;
7290 if (atype == ATYPE_SMALL) {
7291 num_params = 1;
7292 name = "AllocSmall";
7293 } else if (atype == ATYPE_NORMAL) {
7294 num_params = 1;
7295 name = "Alloc";
7296 } else if (atype == ATYPE_VECTOR) {
7297 num_params = 2;
7298 name = "AllocVector";
7299 } else {
7300 g_assert_not_reached ();
7303 csig = mono_metadata_signature_alloc (mono_defaults.corlib, num_params);
7304 csig->ret = &mono_defaults.object_class->byval_arg;
7305 for (i = 0; i < num_params; ++i)
7306 csig->params [i] = &mono_defaults.int_class->byval_arg;
7308 mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_ALLOC);
7309 size_var = mono_mb_add_local (mb, &mono_defaults.int32_class->byval_arg);
7310 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
7311 /* size = vtable->klass->instance_size; */
7312 mono_mb_emit_ldarg (mb, 0);
7313 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
7314 mono_mb_emit_byte (mb, CEE_ADD);
7315 mono_mb_emit_byte (mb, CEE_LDIND_I);
7316 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, instance_size));
7317 mono_mb_emit_byte (mb, CEE_ADD);
7318 /* FIXME: assert instance_size stays a 4 byte integer */
7319 mono_mb_emit_byte (mb, CEE_LDIND_U4);
7320 mono_mb_emit_stloc (mb, size_var);
7321 } else if (atype == ATYPE_VECTOR) {
7322 MonoExceptionClause *clause;
7323 int pos, pos_leave;
7324 MonoClass *oom_exc_class;
7325 MonoMethod *ctor;
7327 /* n > MONO_ARRAY_MAX_INDEX -> OverflowException */
7328 mono_mb_emit_ldarg (mb, 1);
7329 mono_mb_emit_icon (mb, MONO_ARRAY_MAX_INDEX);
7330 pos = mono_mb_emit_short_branch (mb, CEE_BLE_UN_S);
7331 mono_mb_emit_exception (mb, "OverflowException", NULL);
7332 mono_mb_patch_short_branch (mb, pos);
7334 clause = mono_image_alloc0 (mono_defaults.corlib, sizeof (MonoExceptionClause));
7335 clause->try_offset = mono_mb_get_label (mb);
7337 /* vtable->klass->sizes.element_size */
7338 mono_mb_emit_ldarg (mb, 0);
7339 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
7340 mono_mb_emit_byte (mb, CEE_ADD);
7341 mono_mb_emit_byte (mb, CEE_LDIND_I);
7342 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, sizes.element_size));
7343 mono_mb_emit_byte (mb, CEE_ADD);
7344 mono_mb_emit_byte (mb, CEE_LDIND_U4);
7346 /* * n */
7347 mono_mb_emit_ldarg (mb, 1);
7348 mono_mb_emit_byte (mb, CEE_MUL_OVF_UN);
7349 /* + sizeof (MonoArray) */
7350 mono_mb_emit_icon (mb, sizeof (MonoArray));
7351 mono_mb_emit_byte (mb, CEE_ADD_OVF_UN);
7352 mono_mb_emit_stloc (mb, size_var);
7354 pos_leave = mono_mb_emit_branch (mb, CEE_LEAVE);
7356 /* catch */
7357 clause->flags = MONO_EXCEPTION_CLAUSE_NONE;
7358 clause->try_len = mono_mb_get_pos (mb) - clause->try_offset;
7359 clause->data.catch_class = mono_class_from_name (mono_defaults.corlib,
7360 "System", "OverflowException");
7361 g_assert (clause->data.catch_class);
7362 clause->handler_offset = mono_mb_get_label (mb);
7364 oom_exc_class = mono_class_from_name (mono_defaults.corlib,
7365 "System", "OutOfMemoryException");
7366 g_assert (oom_exc_class);
7367 ctor = mono_class_get_method_from_name (oom_exc_class, ".ctor", 0);
7368 g_assert (ctor);
7370 mono_mb_emit_byte (mb, CEE_POP);
7371 mono_mb_emit_op (mb, CEE_NEWOBJ, ctor);
7372 mono_mb_emit_byte (mb, CEE_THROW);
7374 clause->handler_len = mono_mb_get_pos (mb) - clause->handler_offset;
7375 mono_mb_set_clauses (mb, 1, clause);
7376 mono_mb_patch_branch (mb, pos_leave);
7377 /* end catch */
7378 } else {
7379 g_assert_not_reached ();
7382 /* size += ALLOC_ALIGN - 1; */
7383 mono_mb_emit_ldloc (mb, size_var);
7384 mono_mb_emit_icon (mb, ALLOC_ALIGN - 1);
7385 mono_mb_emit_byte (mb, CEE_ADD);
7386 /* size &= ~(ALLOC_ALIGN - 1); */
7387 mono_mb_emit_icon (mb, ~(ALLOC_ALIGN - 1));
7388 mono_mb_emit_byte (mb, CEE_AND);
7389 mono_mb_emit_stloc (mb, size_var);
7391 /* if (size > MAX_SMALL_OBJ_SIZE) goto slowpath */
7392 if (atype != ATYPE_SMALL) {
7393 mono_mb_emit_ldloc (mb, size_var);
7394 mono_mb_emit_icon (mb, MAX_SMALL_OBJ_SIZE);
7395 max_size_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BGT_S);
7399 * We need to modify tlab_next, but the JIT only supports reading, so we read
7400 * another tls var holding its address instead.
7403 /* tlab_next_addr (local) = tlab_next_addr (TLS var) */
7404 tlab_next_addr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7405 EMIT_TLS_ACCESS (mb, tlab_next_addr, tlab_next_addr_offset);
7406 mono_mb_emit_stloc (mb, tlab_next_addr_var);
7408 /* p = (void**)tlab_next; */
7409 p_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7410 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
7411 mono_mb_emit_byte (mb, CEE_LDIND_I);
7412 mono_mb_emit_stloc (mb, p_var);
7414 /* new_next = (char*)p + size; */
7415 new_next_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7416 mono_mb_emit_ldloc (mb, p_var);
7417 mono_mb_emit_ldloc (mb, size_var);
7418 mono_mb_emit_byte (mb, CEE_CONV_I);
7419 mono_mb_emit_byte (mb, CEE_ADD);
7420 mono_mb_emit_stloc (mb, new_next_var);
7422 /* tlab_next = new_next */
7423 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
7424 mono_mb_emit_ldloc (mb, new_next_var);
7425 mono_mb_emit_byte (mb, CEE_STIND_I);
7427 /* if (G_LIKELY (new_next < tlab_temp_end)) */
7428 mono_mb_emit_ldloc (mb, new_next_var);
7429 EMIT_TLS_ACCESS (mb, tlab_temp_end, tlab_temp_end_offset);
7430 slowpath_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BLT_UN_S);
7432 /* Slowpath */
7433 if (atype != ATYPE_SMALL)
7434 mono_mb_patch_short_branch (mb, max_size_branch);
7436 mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
7437 mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN);
7439 /* FIXME: mono_gc_alloc_obj takes a 'size_t' as an argument, not an int32 */
7440 mono_mb_emit_ldarg (mb, 0);
7441 mono_mb_emit_ldloc (mb, size_var);
7442 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
7443 mono_mb_emit_icall (mb, mono_gc_alloc_obj);
7444 } else if (atype == ATYPE_VECTOR) {
7445 mono_mb_emit_ldarg (mb, 1);
7446 mono_mb_emit_icall (mb, mono_gc_alloc_vector);
7447 } else {
7448 g_assert_not_reached ();
7450 mono_mb_emit_byte (mb, CEE_RET);
7452 /* Fastpath */
7453 mono_mb_patch_short_branch (mb, slowpath_branch);
7455 /* FIXME: Memory barrier */
7457 /* *p = vtable; */
7458 mono_mb_emit_ldloc (mb, p_var);
7459 mono_mb_emit_ldarg (mb, 0);
7460 mono_mb_emit_byte (mb, CEE_STIND_I);
7462 if (atype == ATYPE_VECTOR) {
7463 /* arr->max_length = max_length; */
7464 mono_mb_emit_ldloc (mb, p_var);
7465 mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoArray, max_length));
7466 mono_mb_emit_ldarg (mb, 1);
7467 mono_mb_emit_byte (mb, CEE_STIND_I);
7470 /* return p */
7471 mono_mb_emit_ldloc (mb, p_var);
7472 mono_mb_emit_byte (mb, CEE_RET);
7474 res = mono_mb_create_method (mb, csig, 8);
7475 mono_mb_free (mb);
7476 mono_method_get_header (res)->init_locals = FALSE;
7478 info = mono_image_alloc0 (mono_defaults.corlib, sizeof (AllocatorWrapperInfo));
7479 info->alloc_type = atype;
7480 mono_marshal_set_wrapper_info (res, info);
7482 return res;
7484 #endif
7486 static MonoMethod* alloc_method_cache [ATYPE_NUM];
7487 static MonoMethod *write_barrier_method;
7489 static gboolean
7490 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip)
7492 MonoJitInfo *ji;
7493 MonoMethod *method;
7494 int i;
7496 if (!ip || !domain)
7497 return FALSE;
7498 ji = mono_jit_info_table_find (domain, ip);
7499 if (!ji)
7500 return FALSE;
7501 method = ji->method;
7503 if (method == write_barrier_method)
7504 return TRUE;
7505 for (i = 0; i < ATYPE_NUM; ++i)
7506 if (method == alloc_method_cache [i])
7507 return TRUE;
7508 return FALSE;
7512 * Generate an allocator method implementing the fast path of mono_gc_alloc_obj ().
7513 * The signature of the called method is:
7514 * object allocate (MonoVTable *vtable)
7516 MonoMethod*
7517 mono_gc_get_managed_allocator (MonoVTable *vtable, gboolean for_box)
7519 #ifdef MANAGED_ALLOCATION
7520 MonoClass *klass = vtable->klass;
7522 #ifdef HAVE_KW_THREAD
7523 int tlab_next_offset = -1;
7524 int tlab_temp_end_offset = -1;
7525 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7526 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7528 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7529 return NULL;
7530 #endif
7532 if (!mono_runtime_has_tls_get ())
7533 return NULL;
7534 if (klass->instance_size > tlab_size)
7535 return NULL;
7536 if (klass->has_finalize || klass->marshalbyref || (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS))
7537 return NULL;
7538 if (klass->rank)
7539 return NULL;
7540 if (klass->byval_arg.type == MONO_TYPE_STRING)
7541 return NULL;
7542 if (collect_before_allocs)
7543 return NULL;
7545 if (ALIGN_TO (klass->instance_size, ALLOC_ALIGN) < MAX_SMALL_OBJ_SIZE)
7546 return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL);
7547 else
7548 return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL);
7549 #else
7550 return NULL;
7551 #endif
7554 MonoMethod*
7555 mono_gc_get_managed_array_allocator (MonoVTable *vtable, int rank)
7557 #ifdef MANAGED_ALLOCATION
7558 MonoClass *klass = vtable->klass;
7560 #ifdef HAVE_KW_THREAD
7561 int tlab_next_offset = -1;
7562 int tlab_temp_end_offset = -1;
7563 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7564 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7566 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7567 return NULL;
7568 #endif
7570 if (rank != 1)
7571 return NULL;
7572 if (!mono_runtime_has_tls_get ())
7573 return NULL;
7574 if (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS)
7575 return NULL;
7576 if (collect_before_allocs)
7577 return NULL;
7578 g_assert (!klass->has_finalize && !klass->marshalbyref);
7580 return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR);
7581 #else
7582 return NULL;
7583 #endif
7586 MonoMethod*
7587 mono_gc_get_managed_allocator_by_type (int atype)
7589 #ifdef MANAGED_ALLOCATION
7590 MonoMethod *res;
7592 if (!mono_runtime_has_tls_get ())
7593 return NULL;
7595 mono_loader_lock ();
7596 res = alloc_method_cache [atype];
7597 if (!res)
7598 res = alloc_method_cache [atype] = create_allocator (atype);
7599 mono_loader_unlock ();
7600 return res;
7601 #else
7602 return NULL;
7603 #endif
7606 guint32
7607 mono_gc_get_managed_allocator_types (void)
7609 return ATYPE_NUM;
7613 MonoMethod*
7614 mono_gc_get_write_barrier (void)
7616 MonoMethod *res;
7617 MonoMethodBuilder *mb;
7618 MonoMethodSignature *sig;
7619 #ifdef MANAGED_WBARRIER
7620 int label_no_wb_1, label_no_wb_2, label_no_wb_3, label_no_wb_4, label_need_wb, label_slow_path;
7621 #ifndef ALIGN_NURSERY
7622 int label_continue_1, label_continue_2, label_no_wb_5;
7623 int dereferenced_var;
7624 #endif
7625 int buffer_var, buffer_index_var, dummy_var;
7627 #ifdef HAVE_KW_THREAD
7628 int stack_end_offset = -1, store_remset_buffer_offset = -1;
7629 int store_remset_buffer_index_offset = -1, store_remset_buffer_index_addr_offset = -1;
7631 MONO_THREAD_VAR_OFFSET (stack_end, stack_end_offset);
7632 g_assert (stack_end_offset != -1);
7633 MONO_THREAD_VAR_OFFSET (store_remset_buffer, store_remset_buffer_offset);
7634 g_assert (store_remset_buffer_offset != -1);
7635 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index, store_remset_buffer_index_offset);
7636 g_assert (store_remset_buffer_index_offset != -1);
7637 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7638 g_assert (store_remset_buffer_index_addr_offset != -1);
7639 #endif
7640 #endif
7642 // FIXME: Maybe create a separate version for ctors (the branch would be
7643 // correctly predicted more times)
7644 if (write_barrier_method)
7645 return write_barrier_method;
7647 /* Create the IL version of mono_gc_barrier_generic_store () */
7648 sig = mono_metadata_signature_alloc (mono_defaults.corlib, 1);
7649 sig->ret = &mono_defaults.void_class->byval_arg;
7650 sig->params [0] = &mono_defaults.int_class->byval_arg;
7652 mb = mono_mb_new (mono_defaults.object_class, "wbarrier", MONO_WRAPPER_WRITE_BARRIER);
7654 #ifdef MANAGED_WBARRIER
7655 if (mono_runtime_has_tls_get ()) {
7656 #ifdef ALIGN_NURSERY
7657 // if (ptr_in_nursery (ptr)) return;
7659 * Masking out the bits might be faster, but we would have to use 64 bit
7660 * immediates, which might be slower.
7662 mono_mb_emit_ldarg (mb, 0);
7663 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7664 mono_mb_emit_byte (mb, CEE_SHR_UN);
7665 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7666 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BEQ);
7668 // if (!ptr_in_nursery (*ptr)) return;
7669 mono_mb_emit_ldarg (mb, 0);
7670 mono_mb_emit_byte (mb, CEE_LDIND_I);
7671 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7672 mono_mb_emit_byte (mb, CEE_SHR_UN);
7673 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7674 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BNE_UN);
7675 #else
7677 // if (ptr < (nursery_start)) goto continue;
7678 mono_mb_emit_ldarg (mb, 0);
7679 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7680 label_continue_1 = mono_mb_emit_branch (mb, CEE_BLT);
7682 // if (ptr >= nursery_real_end)) goto continue;
7683 mono_mb_emit_ldarg (mb, 0);
7684 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7685 label_continue_2 = mono_mb_emit_branch (mb, CEE_BGE);
7687 // Otherwise return
7688 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BR);
7690 // continue:
7691 mono_mb_patch_branch (mb, label_continue_1);
7692 mono_mb_patch_branch (mb, label_continue_2);
7694 // Dereference and store in local var
7695 dereferenced_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7696 mono_mb_emit_ldarg (mb, 0);
7697 mono_mb_emit_byte (mb, CEE_LDIND_I);
7698 mono_mb_emit_stloc (mb, dereferenced_var);
7700 // if (*ptr < nursery_start) return;
7701 mono_mb_emit_ldloc (mb, dereferenced_var);
7702 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7703 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BLT);
7705 // if (*ptr >= nursery_end) return;
7706 mono_mb_emit_ldloc (mb, dereferenced_var);
7707 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7708 label_no_wb_5 = mono_mb_emit_branch (mb, CEE_BGE);
7710 #endif
7711 // if (ptr >= stack_end) goto need_wb;
7712 mono_mb_emit_ldarg (mb, 0);
7713 EMIT_TLS_ACCESS (mb, stack_end, stack_end_offset);
7714 label_need_wb = mono_mb_emit_branch (mb, CEE_BGE_UN);
7716 // if (ptr >= stack_start) return;
7717 dummy_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7718 mono_mb_emit_ldarg (mb, 0);
7719 mono_mb_emit_ldloc_addr (mb, dummy_var);
7720 label_no_wb_3 = mono_mb_emit_branch (mb, CEE_BGE_UN);
7722 // need_wb:
7723 mono_mb_patch_branch (mb, label_need_wb);
7725 // buffer = STORE_REMSET_BUFFER;
7726 buffer_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7727 EMIT_TLS_ACCESS (mb, store_remset_buffer, store_remset_buffer_offset);
7728 mono_mb_emit_stloc (mb, buffer_var);
7730 // buffer_index = STORE_REMSET_BUFFER_INDEX;
7731 buffer_index_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7732 EMIT_TLS_ACCESS (mb, store_remset_buffer_index, store_remset_buffer_index_offset);
7733 mono_mb_emit_stloc (mb, buffer_index_var);
7735 // if (buffer [buffer_index] == ptr) return;
7736 mono_mb_emit_ldloc (mb, buffer_var);
7737 mono_mb_emit_ldloc (mb, buffer_index_var);
7738 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7739 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7740 mono_mb_emit_byte (mb, CEE_SHL);
7741 mono_mb_emit_byte (mb, CEE_ADD);
7742 mono_mb_emit_byte (mb, CEE_LDIND_I);
7743 mono_mb_emit_ldarg (mb, 0);
7744 label_no_wb_4 = mono_mb_emit_branch (mb, CEE_BEQ);
7746 // ++buffer_index;
7747 mono_mb_emit_ldloc (mb, buffer_index_var);
7748 mono_mb_emit_icon (mb, 1);
7749 mono_mb_emit_byte (mb, CEE_ADD);
7750 mono_mb_emit_stloc (mb, buffer_index_var);
7752 // if (buffer_index >= STORE_REMSET_BUFFER_SIZE) goto slow_path;
7753 mono_mb_emit_ldloc (mb, buffer_index_var);
7754 mono_mb_emit_icon (mb, STORE_REMSET_BUFFER_SIZE);
7755 label_slow_path = mono_mb_emit_branch (mb, CEE_BGE);
7757 // buffer [buffer_index] = ptr;
7758 mono_mb_emit_ldloc (mb, buffer_var);
7759 mono_mb_emit_ldloc (mb, buffer_index_var);
7760 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7761 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7762 mono_mb_emit_byte (mb, CEE_SHL);
7763 mono_mb_emit_byte (mb, CEE_ADD);
7764 mono_mb_emit_ldarg (mb, 0);
7765 mono_mb_emit_byte (mb, CEE_STIND_I);
7767 // STORE_REMSET_BUFFER_INDEX = buffer_index;
7768 EMIT_TLS_ACCESS (mb, store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7769 mono_mb_emit_ldloc (mb, buffer_index_var);
7770 mono_mb_emit_byte (mb, CEE_STIND_I);
7772 // return;
7773 mono_mb_patch_branch (mb, label_no_wb_1);
7774 mono_mb_patch_branch (mb, label_no_wb_2);
7775 mono_mb_patch_branch (mb, label_no_wb_3);
7776 mono_mb_patch_branch (mb, label_no_wb_4);
7777 #ifndef ALIGN_NURSERY
7778 mono_mb_patch_branch (mb, label_no_wb_5);
7779 #endif
7780 mono_mb_emit_byte (mb, CEE_RET);
7782 // slow path
7783 mono_mb_patch_branch (mb, label_slow_path);
7785 #endif
7787 mono_mb_emit_ldarg (mb, 0);
7788 mono_mb_emit_icall (mb, mono_gc_wbarrier_generic_nostore);
7789 mono_mb_emit_byte (mb, CEE_RET);
7791 res = mono_mb_create_method (mb, sig, 16);
7792 mono_mb_free (mb);
7794 mono_loader_lock ();
7795 if (write_barrier_method) {
7796 /* Already created */
7797 mono_free_method (res);
7798 } else {
7799 /* double-checked locking */
7800 mono_memory_barrier ();
7801 write_barrier_method = res;
7803 mono_loader_unlock ();
7805 return write_barrier_method;
7808 char*
7809 mono_gc_get_description (void)
7811 return g_strdup ("sgen");
7814 void
7815 mono_gc_set_desktop_mode (void)
7819 gboolean
7820 mono_gc_is_moving (void)
7822 return TRUE;
7825 gboolean
7826 mono_gc_is_disabled (void)
7828 return FALSE;
7831 #endif /* HAVE_SGEN_GC */