2010-04-28 Mark Probst <mark.probst@gmail.com>
[mono-project/dkf.git] / mono / metadata / sgen-gc.c
blob708abb57aaa3641efa60ccf341b3efa8adf4221f
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.
173 #include "config.h"
174 #ifdef HAVE_SGEN_GC
176 #include <unistd.h>
177 #include <stdio.h>
178 #include <string.h>
179 #include <semaphore.h>
180 #include <signal.h>
181 #include <errno.h>
182 #include <assert.h>
183 #include <pthread.h>
184 #include "metadata/metadata-internals.h"
185 #include "metadata/class-internals.h"
186 #include "metadata/gc-internal.h"
187 #include "metadata/object-internals.h"
188 #include "metadata/threads.h"
189 #include "metadata/sgen-gc.h"
190 #include "metadata/sgen-archdep.h"
191 #include "metadata/mono-gc.h"
192 #include "metadata/method-builder.h"
193 #include "metadata/profiler-private.h"
194 #include "metadata/monitor.h"
195 #include "metadata/threadpool-internals.h"
196 #include "metadata/mempool-internals.h"
197 #include "metadata/marshal.h"
198 #include "utils/mono-mmap.h"
199 #include "utils/mono-time.h"
200 #include "utils/mono-semaphore.h"
201 #include "utils/mono-counters.h"
203 #include <mono/utils/memcheck.h>
205 #define OPDEF(a,b,c,d,e,f,g,h,i,j) \
206 a = i,
208 enum {
209 #include "mono/cil/opcode.def"
210 CEE_LAST
213 #undef OPDEF
216 * ######################################################################
217 * ######## Types and constants used by the GC.
218 * ######################################################################
220 #if SIZEOF_VOID_P == 4
221 typedef guint32 mword;
222 #else
223 typedef guint64 mword;
224 #endif
226 static int gc_initialized = 0;
227 static int gc_debug_level = 0;
228 static FILE* gc_debug_file;
229 /* If set, do a minor collection before every allocation */
230 static gboolean collect_before_allocs = FALSE;
231 /* If set, do a heap consistency check before each minor collection */
232 static gboolean consistency_check_at_minor_collection = FALSE;
233 /* If set, check that there are no references to the domain left at domain unload */
234 static gboolean xdomain_checks = FALSE;
235 /* If not null, dump the heap after each collection into this file */
236 static FILE *heap_dump_file = NULL;
237 /* If set, mark stacks conservatively, even if precise marking is possible */
238 static gboolean conservative_stack_mark = TRUE;
239 /* If set, do a plausibility check on the scan_starts before and after
240 each collection */
241 static gboolean do_scan_starts_check = FALSE;
244 * Turning on heavy statistics will turn off the managed allocator and
245 * the managed write barrier.
247 //#define HEAVY_STATISTICS
249 #ifdef HEAVY_STATISTICS
250 #define HEAVY_STAT(x) x
251 #else
252 #define HEAVY_STAT(x)
253 #endif
255 #ifdef HEAVY_STATISTICS
256 static long stat_objects_alloced = 0;
257 static long stat_bytes_alloced = 0;
258 static long stat_objects_alloced_degraded = 0;
259 static long stat_bytes_alloced_degraded = 0;
260 static long stat_bytes_alloced_los = 0;
262 static long stat_copy_object_called_nursery = 0;
263 static long stat_objects_copied_nursery = 0;
264 static long stat_copy_object_called_major = 0;
265 static long stat_objects_copied_major = 0;
267 static long stat_scan_object_called_nursery = 0;
268 static long stat_scan_object_called_major = 0;
270 static long stat_nursery_copy_object_failed_from_space = 0;
271 static long stat_nursery_copy_object_failed_forwarded = 0;
272 static long stat_nursery_copy_object_failed_pinned = 0;
274 static long stat_store_remsets = 0;
275 static long stat_store_remsets_unique = 0;
276 static long stat_saved_remsets_1 = 0;
277 static long stat_saved_remsets_2 = 0;
278 static long stat_global_remsets_added = 0;
279 static long stat_global_remsets_processed = 0;
281 static int stat_wbarrier_set_field = 0;
282 static int stat_wbarrier_set_arrayref = 0;
283 static int stat_wbarrier_arrayref_copy = 0;
284 static int stat_wbarrier_generic_store = 0;
285 static int stat_wbarrier_generic_store_remset = 0;
286 static int stat_wbarrier_set_root = 0;
287 static int stat_wbarrier_value_copy = 0;
288 static int stat_wbarrier_object_copy = 0;
289 #endif
291 static long time_minor_pre_collection_fragment_clear = 0;
292 static long time_minor_pinning = 0;
293 static long time_minor_scan_remsets = 0;
294 static long time_minor_scan_pinned = 0;
295 static long time_minor_scan_registered_roots = 0;
296 static long time_minor_scan_thread_data = 0;
297 static long time_minor_finish_gray_stack = 0;
298 static long time_minor_fragment_creation = 0;
300 static long time_major_pre_collection_fragment_clear = 0;
301 static long time_major_pinning = 0;
302 static long time_major_scan_pinned = 0;
303 static long time_major_scan_registered_roots = 0;
304 static long time_major_scan_thread_data = 0;
305 static long time_major_scan_alloc_pinned = 0;
306 static long time_major_scan_finalized = 0;
307 static long time_major_scan_big_objects = 0;
308 static long time_major_finish_gray_stack = 0;
309 static long time_major_sweep = 0;
310 static long time_major_fragment_creation = 0;
312 static long pinned_chunk_bytes_alloced = 0;
313 static long large_internal_bytes_alloced = 0;
315 /* Keep in sync with internal_mem_names in dump_heap()! */
316 enum {
317 INTERNAL_MEM_PIN_QUEUE,
318 INTERNAL_MEM_FRAGMENT,
319 INTERNAL_MEM_SECTION,
320 INTERNAL_MEM_SCAN_STARTS,
321 INTERNAL_MEM_FIN_TABLE,
322 INTERNAL_MEM_FINALIZE_ENTRY,
323 INTERNAL_MEM_DISLINK_TABLE,
324 INTERNAL_MEM_DISLINK,
325 INTERNAL_MEM_ROOTS_TABLE,
326 INTERNAL_MEM_ROOT_RECORD,
327 INTERNAL_MEM_STATISTICS,
328 INTERNAL_MEM_REMSET,
329 INTERNAL_MEM_GRAY_QUEUE,
330 INTERNAL_MEM_STORE_REMSET,
331 INTERNAL_MEM_MS_TABLES,
332 INTERNAL_MEM_MS_BLOCK_INFO,
333 INTERNAL_MEM_MAX
336 static long small_internal_mem_bytes [INTERNAL_MEM_MAX];
339 void
340 mono_gc_flush_info (void)
342 fflush (gc_debug_file);
346 #define MAX_DEBUG_LEVEL 2
347 #define DEBUG(level,a) do {if (G_UNLIKELY ((level) <= MAX_DEBUG_LEVEL && (level) <= gc_debug_level)) a;} while (0)
349 /* Define this to allow the user to change some of the constants by specifying
350 * their values in the MONO_GC_PARAMS environmental variable. See
351 * mono_gc_base_init for details. */
352 #define USER_CONFIG 1
354 #define TV_DECLARE(name) gint64 name
355 #define TV_GETTIME(tv) tv = mono_100ns_ticks ()
356 #define TV_ELAPSED(start,end) (int)((end-start) / 10)
357 #define TV_ELAPSED_MS(start,end) ((TV_ELAPSED((start),(end)) + 500) / 1000)
359 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
361 #define GC_BITS_PER_WORD (sizeof (mword) * 8)
363 enum {
364 MEMORY_ROLE_GEN0,
365 MEMORY_ROLE_GEN1,
366 MEMORY_ROLE_PINNED
369 typedef struct _Block Block;
370 struct _Block {
371 void *next;
372 unsigned char role;
375 /* each request from the OS ends up in a GCMemSection */
376 typedef struct _GCMemSection GCMemSection;
377 struct _GCMemSection {
378 Block block;
379 char *data;
380 mword size;
381 /* pointer where more data could be allocated if it fits */
382 char *next_data;
383 char *end_data;
385 * scan starts is an array of pointers to objects equally spaced in the allocation area
386 * They let use quickly find pinned objects from pinning pointers.
388 char **scan_starts;
389 /* in major collections indexes in the pin_queue for objects that pin this section */
390 int pin_queue_start;
391 int pin_queue_end;
392 unsigned short num_scan_start;
393 gboolean is_to_space;
396 #define SIZEOF_GC_MEM_SECTION ((sizeof (GCMemSection) + 7) & ~7)
398 /* large object space struct: 64+ KB */
399 /* we could make this limit much smaller to avoid memcpy copy
400 * and potentially have more room in the GC descriptor: need to measure
401 * This also means that such small OS objects will need to be
402 * allocated in a different way (using pinned chunks).
403 * We may want to put large but smaller than 64k objects in the fixed space
404 * when we move the object from one generation to another (to limit the
405 * pig in the snake effect).
406 * Note: it may be worth to have an optimized copy function, since we can
407 * assume that objects are aligned and have a multiple of 8 size.
408 * FIXME: This structure needs to be a multiple of 8 bytes in size: this is not
409 * true if MONO_ZERO_LEN_ARRAY is nonzero.
411 typedef struct _LOSObject LOSObject;
412 struct _LOSObject {
413 LOSObject *next;
414 mword size; /* this is the object size */
415 guint16 role;
416 int dummy; /* to have a sizeof (LOSObject) a multiple of ALLOC_ALIGN and data starting at same alignment */
417 char data [MONO_ZERO_LEN_ARRAY];
420 /* Pinned objects are allocated in the LOS space if bigger than half a page
421 * or from freelists otherwise. We assume that pinned objects are relatively few
422 * and they have a slow dying speed (like interned strings, thread objects).
423 * As such they will be collected only at major collections.
424 * free lists are not global: when we need memory we allocate a PinnedChunk.
425 * Each pinned chunk is made of several pages, the first of wich is used
426 * internally for bookeeping (here think of a page as 4KB). The bookeeping
427 * includes the freelists vectors and info about the object size of each page
428 * in the pinned chunk. So, when needed, a free page is found in a pinned chunk,
429 * a size is assigned to it, the page is divided in the proper chunks and each
430 * chunk is added to the freelist. To not waste space, the remaining space in the
431 * first page is used as objects of size 16 or 32 (need to measure which are more
432 * common).
433 * We use this same structure to allocate memory used internally by the GC, so
434 * we never use malloc/free if we need to alloc during collection: the world is stopped
435 * and malloc/free will deadlock.
436 * When we want to iterate over pinned objects, we just scan a page at a time
437 * linearly according to the size of objects in the page: the next pointer used to link
438 * the items in the freelist uses the same word as the vtable. Since we keep freelists
439 * for each pinned chunk, if the word points outside the pinned chunk it means
440 * it is an object.
441 * We could avoid this expensive scanning in creative ways. We could have a policy
442 * of putting in the pinned space only objects we know about that have no struct fields
443 * with references and we can easily use a even expensive write barrier for them,
444 * since pointer writes on such objects should be rare.
445 * The best compromise is to just alloc interned strings and System.MonoType in them.
446 * It would be nice to allocate MonoThread in it, too: must check that we properly
447 * use write barriers so we don't have to do any expensive scanning of the whole pinned
448 * chunk list during minor collections. We can avoid it now because we alloc in it only
449 * reference-free objects.
451 #define PINNED_FIRST_SLOT_SIZE (sizeof (gpointer) * 4)
452 #define MAX_FREELIST_SIZE 2048
453 #define PINNED_PAGE_SIZE (4096)
454 #define PINNED_CHUNK_MIN_SIZE (4096*8)
455 typedef struct _PinnedChunk PinnedChunk;
456 struct _PinnedChunk {
457 Block block;
458 int num_pages;
459 int *page_sizes; /* a 0 means the page is still unused */
460 void **free_list;
461 void *start_data;
462 void *data [1]; /* page sizes and free lists are stored here */
465 /* The method used to clear the nursery */
466 /* Clearing at nursery collections is the safest, but has bad interactions with caches.
467 * Clearing at TLAB creation is much faster, but more complex and it might expose hard
468 * to find bugs.
470 typedef enum {
471 CLEAR_AT_GC,
472 CLEAR_AT_TLAB_CREATION
473 } NurseryClearPolicy;
475 static NurseryClearPolicy nursery_clear_policy = CLEAR_AT_TLAB_CREATION;
478 * If this is set, the nursery is aligned to an address aligned to its size, ie.
479 * a 1MB nursery will be aligned to an address divisible by 1MB. This allows us to
480 * speed up ptr_in_nursery () checks which are very frequent. This requires the
481 * nursery size to be a compile time constant.
483 #define ALIGN_NURSERY 1
486 * The young generation is divided into fragments. This is because
487 * we can hand one fragments to a thread for lock-less fast alloc and
488 * because the young generation ends up fragmented anyway by pinned objects.
489 * Once a collection is done, a list of fragments is created. When doing
490 * thread local alloc we use smallish nurseries so we allow new threads to
491 * allocate memory from gen0 without triggering a collection. Threads that
492 * are found to allocate lots of memory are given bigger fragments. This
493 * should make the finalizer thread use little nursery memory after a while.
494 * We should start assigning threads very small fragments: if there are many
495 * threads the nursery will be full of reserved space that the threads may not
496 * use at all, slowing down allocation speed.
497 * Thread local allocation is done from areas of memory Hotspot calls Thread Local
498 * Allocation Buffers (TLABs).
500 typedef struct _Fragment Fragment;
502 struct _Fragment {
503 Fragment *next;
504 char *fragment_start;
505 char *fragment_limit; /* the current soft limit for allocation */
506 char *fragment_end;
509 /* the runtime can register areas of memory as roots: we keep two lists of roots,
510 * a pinned root set for conservatively scanned roots and a normal one for
511 * precisely scanned roots (currently implemented as a single list).
513 typedef struct _RootRecord RootRecord;
514 struct _RootRecord {
515 RootRecord *next;
516 char *start_root;
517 char *end_root;
518 mword root_desc;
521 /* for use with write barriers */
522 typedef struct _RememberedSet RememberedSet;
523 struct _RememberedSet {
524 mword *store_next;
525 mword *end_set;
526 RememberedSet *next;
527 mword data [MONO_ZERO_LEN_ARRAY];
531 * We're never actually using the first element. It's always set to
532 * NULL to simplify the elimination of consecutive duplicate
533 * entries.
535 #define STORE_REMSET_BUFFER_SIZE 1024
537 typedef struct _GenericStoreRememberedSet GenericStoreRememberedSet;
538 struct _GenericStoreRememberedSet {
539 GenericStoreRememberedSet *next;
540 /* We need one entry less because the first entry of store
541 remset buffers is always a dummy and we don't copy it. */
542 gpointer data [STORE_REMSET_BUFFER_SIZE - 1];
545 /* we have 4 possible values in the low 2 bits */
546 enum {
547 REMSET_LOCATION, /* just a pointer to the exact location */
548 REMSET_RANGE, /* range of pointer fields */
549 REMSET_OBJECT, /* mark all the object for scanning */
550 REMSET_OTHER, /* all others */
551 REMSET_TYPE_MASK = 0x3
554 /* Subtypes of REMSET_OTHER */
555 enum {
556 REMSET_VTYPE, /* a valuetype array described by a gc descriptor and a count */
557 REMSET_ROOT_LOCATION, /* a location inside a root */
560 #ifdef HAVE_KW_THREAD
561 static __thread RememberedSet *remembered_set MONO_TLS_FAST;
562 #endif
563 static pthread_key_t remembered_set_key;
564 static RememberedSet *global_remset;
565 static RememberedSet *freed_thread_remsets;
566 static GenericStoreRememberedSet *generic_store_remsets = NULL;
568 /* FIXME: later choose a size that takes into account the RememberedSet struct
569 * and doesn't waste any alloc paddin space.
571 #define DEFAULT_REMSET_SIZE 1024
572 static RememberedSet* alloc_remset (int size, gpointer id);
574 /* Structure that corresponds to a MonoVTable: desc is a mword so requires
575 * no cast from a pointer to an integer
577 typedef struct {
578 MonoClass *klass;
579 mword desc;
580 } GCVTable;
582 /* these bits are set in the object vtable: we could merge them since an object can be
583 * either pinned or forwarded but not both.
584 * We store them in the vtable slot because the bits are used in the sync block for
585 * other purposes: if we merge them and alloc the sync blocks aligned to 8 bytes, we can change
586 * this and use bit 3 in the syncblock (with the lower two bits both set for forwarded, that
587 * would be an invalid combination for the monitor and hash code).
588 * The values are already shifted.
589 * The forwarding address is stored in the sync block.
591 #define FORWARDED_BIT 1
592 #define PINNED_BIT 2
593 #define VTABLE_BITS_MASK 0x3
595 /* returns NULL if not forwarded, or the forwarded address */
596 #define object_is_forwarded(obj) (((mword*)(obj))[0] & FORWARDED_BIT? (void*)(((mword*)(obj))[1]): NULL)
597 /* set the forwarded address fw_addr for object obj */
598 #define forward_object(obj,fw_addr) do { \
599 ((mword*)(obj))[0] |= FORWARDED_BIT; \
600 ((mword*)(obj))[1] = (mword)(fw_addr); \
601 } while (0)
603 #define object_is_pinned(obj) (((mword*)(obj))[0] & PINNED_BIT)
604 #define pin_object(obj) do { \
605 ((mword*)(obj))[0] |= PINNED_BIT; \
606 } while (0)
607 #define unpin_object(obj) do { \
608 ((mword*)(obj))[0] &= ~PINNED_BIT; \
609 } while (0)
611 #ifdef ALIGN_NURSERY
612 #define ptr_in_nursery(ptr) (((mword)(ptr) & ~((1 << DEFAULT_NURSERY_BITS) - 1)) == (mword)nursery_start)
613 #else
614 #define ptr_in_nursery(ptr) ((char*)(ptr) >= nursery_start && (char*)(ptr) < nursery_real_end)
615 #endif
618 * Since we set bits in the vtable, use the macro to load it from the pointer to
619 * an object that is potentially pinned.
621 #define LOAD_VTABLE(addr) ((*(mword*)(addr)) & ~VTABLE_BITS_MASK)
623 static const char*
624 safe_name (void* obj)
626 MonoVTable *vt = (MonoVTable*)LOAD_VTABLE (obj);
627 return vt->klass->name;
630 static inline guint
631 safe_object_get_size (MonoObject* o)
633 MonoClass *klass = ((MonoVTable*)LOAD_VTABLE (o))->klass;
634 if (klass == mono_defaults.string_class) {
635 return sizeof (MonoString) + 2 * mono_string_length ((MonoString*) o) + 2;
636 } else if (klass->rank) {
637 MonoArray *array = (MonoArray*)o;
638 size_t size = sizeof (MonoArray) + klass->sizes.element_size * mono_array_length (array);
639 if (G_UNLIKELY (array->bounds)) {
640 size += sizeof (mono_array_size_t) - 1;
641 size &= ~(sizeof (mono_array_size_t) - 1);
642 size += sizeof (MonoArrayBounds) * klass->rank;
644 return size;
645 } else {
646 /* from a created object: the class must be inited already */
647 return klass->instance_size;
652 * ######################################################################
653 * ######## Global data.
654 * ######################################################################
656 static LOCK_DECLARE (gc_mutex);
657 static int gc_disabled = 0;
658 static int num_minor_gcs = 0;
659 static int num_major_gcs = 0;
661 #ifdef USER_CONFIG
663 /* good sizes are 512KB-1MB: larger ones increase a lot memzeroing time */
664 #define DEFAULT_NURSERY_SIZE (default_nursery_size)
665 static int default_nursery_size = (1 << 20);
666 #ifdef ALIGN_NURSERY
667 /* The number of trailing 0 bits in DEFAULT_NURSERY_SIZE */
668 #define DEFAULT_NURSERY_BITS (default_nursery_bits)
669 static int default_nursery_bits = 20;
670 #endif
672 #else
674 #define DEFAULT_NURSERY_SIZE (1024*512*2)
675 #ifdef ALIGN_NURSERY
676 #define DEFAULT_NURSERY_BITS 20
677 #endif
679 #endif
681 #define MIN_LOS_ALLOWANCE (DEFAULT_NURSERY_SIZE * 2)
682 /* to quickly find the head of an object pinned by a conservative address
683 * we keep track of the objects allocated for each SCAN_START_SIZE memory
684 * chunk in the nursery or other memory sections. Larger values have less
685 * memory overhead and bigger runtime cost. 4-8 KB are reasonable values.
687 #define SCAN_START_SIZE (4096*2)
688 /* the minimum size of a fragment that we consider useful for allocation */
689 #define FRAGMENT_MIN_SIZE (512)
690 /* This is a fixed value used for pinned chunks, not the system pagesize */
691 #define FREELIST_PAGESIZE 4096
693 static mword pagesize = 4096;
694 static mword nursery_size;
695 static int degraded_mode = 0;
697 static LOSObject *los_object_list = NULL;
698 static mword los_memory_usage = 0;
699 static mword los_num_objects = 0;
700 static mword next_los_collection = 2*1024*1024; /* 2 MB, need to tune */
701 static mword total_alloc = 0;
702 /* use this to tune when to do a major/minor collection */
703 static mword memory_pressure = 0;
705 static GCMemSection *nursery_section = NULL;
706 static mword lowest_heap_address = ~(mword)0;
707 static mword highest_heap_address = 0;
709 static LOCK_DECLARE (interruption_mutex);
711 typedef struct _FinalizeEntry FinalizeEntry;
712 struct _FinalizeEntry {
713 FinalizeEntry *next;
714 void *object;
717 typedef struct _FinalizeEntryHashTable FinalizeEntryHashTable;
718 struct _FinalizeEntryHashTable {
719 FinalizeEntry **table;
720 mword size;
721 int num_registered;
724 typedef struct _DisappearingLink DisappearingLink;
725 struct _DisappearingLink {
726 DisappearingLink *next;
727 void **link;
730 typedef struct _DisappearingLinkHashTable DisappearingLinkHashTable;
731 struct _DisappearingLinkHashTable {
732 DisappearingLink **table;
733 mword size;
734 int num_links;
737 #define LARGE_INTERNAL_MEM_HEADER_MAGIC 0x7d289f3a
739 typedef struct _LargeInternalMemHeader LargeInternalMemHeader;
740 struct _LargeInternalMemHeader {
741 guint32 magic;
742 size_t size;
743 double data[0];
746 enum {
747 GENERATION_NURSERY,
748 GENERATION_OLD,
749 GENERATION_MAX
752 int current_collection_generation = -1;
755 * The link pointer is hidden by negating each bit. We use the lowest
756 * bit of the link (before negation) to store whether it needs
757 * resurrection tracking.
759 #define HIDE_POINTER(p,t) ((gpointer)(~((gulong)(p)|((t)?1:0))))
760 #define REVEAL_POINTER(p) ((gpointer)((~(gulong)(p))&~3L))
762 #define DISLINK_OBJECT(d) (REVEAL_POINTER (*(d)->link))
763 #define DISLINK_TRACK(d) ((~(gulong)(*(d)->link)) & 1)
766 * The finalizable hash has the object as the key, the
767 * disappearing_link hash, has the link address as key.
769 static FinalizeEntryHashTable minor_finalizable_hash;
770 static FinalizeEntryHashTable major_finalizable_hash;
771 /* objects that are ready to be finalized */
772 static FinalizeEntry *fin_ready_list = NULL;
773 static FinalizeEntry *critical_fin_list = NULL;
775 static DisappearingLinkHashTable minor_disappearing_link_hash;
776 static DisappearingLinkHashTable major_disappearing_link_hash;
778 static int num_ready_finalizers = 0;
779 static int no_finalize = 0;
781 /* keep each size a multiple of ALLOC_ALIGN */
782 /* on 64 bit systems 8 is likely completely unused. */
783 static const int freelist_sizes [] = {
784 8, 16, 24, 32, 40, 48, 64, 80,
785 96, 128, 160, 192, 224, 256, 320, 384,
786 448, 512, 584, 680, 816, 1024, 1360, 2048};
787 #define FREELIST_NUM_SLOTS (sizeof (freelist_sizes) / sizeof (freelist_sizes [0]))
789 /* This is also the MAJOR_SECTION_SIZE for the copying major
790 collector */
791 #define PINNED_CHUNK_SIZE (128 * 1024)
793 /* internal_chunk_list is used for allocating structures needed by the GC */
794 static PinnedChunk *internal_chunk_list = NULL;
796 static int slot_for_size (size_t size);
798 enum {
799 ROOT_TYPE_NORMAL = 0, /* "normal" roots */
800 ROOT_TYPE_PINNED = 1, /* roots without a GC descriptor */
801 ROOT_TYPE_WBARRIER = 2, /* roots with a write barrier */
802 ROOT_TYPE_NUM
805 /* registered roots: the key to the hash is the root start address */
807 * Different kinds of roots are kept separate to speed up pin_from_roots () for example.
809 static RootRecord **roots_hash [ROOT_TYPE_NUM] = { NULL, NULL };
810 static int roots_hash_size [ROOT_TYPE_NUM] = { 0, 0, 0 };
811 static mword roots_size = 0; /* amount of memory in the root set */
812 static int num_roots_entries [ROOT_TYPE_NUM] = { 0, 0, 0 };
815 * The current allocation cursors
816 * We allocate objects in the nursery.
817 * The nursery is the area between nursery_start and nursery_real_end.
818 * Allocation is done from a Thread Local Allocation Buffer (TLAB). TLABs are allocated
819 * from nursery fragments.
820 * tlab_next is the pointer to the space inside the TLAB where the next object will
821 * be allocated.
822 * tlab_temp_end is the pointer to the end of the temporary space reserved for
823 * the allocation: it allows us to set the scan starts at reasonable intervals.
824 * tlab_real_end points to the end of the TLAB.
825 * nursery_frag_real_end points to the end of the currently used nursery fragment.
826 * nursery_first_pinned_start points to the start of the first pinned object in the nursery
827 * nursery_last_pinned_end points to the end of the last pinned object in the nursery
828 * At the next allocation, the area of the nursery where objects can be present is
829 * between MIN(nursery_first_pinned_start, first_fragment_start) and
830 * MAX(nursery_last_pinned_end, nursery_frag_real_end)
832 static char *nursery_start = NULL;
834 /* eventually share with MonoThread? */
835 typedef struct _SgenThreadInfo SgenThreadInfo;
837 struct _SgenThreadInfo {
838 SgenThreadInfo *next;
839 ARCH_THREAD_TYPE id;
840 unsigned int stop_count; /* to catch duplicate signals */
841 int signal;
842 int skip;
843 volatile int in_critical_region;
844 void *stack_end;
845 void *stack_start;
846 void *stack_start_limit;
847 char **tlab_next_addr;
848 char **tlab_start_addr;
849 char **tlab_temp_end_addr;
850 char **tlab_real_end_addr;
851 gpointer **store_remset_buffer_addr;
852 long *store_remset_buffer_index_addr;
853 RememberedSet *remset;
854 gpointer runtime_data;
855 gpointer stopped_ip; /* only valid if the thread is stopped */
856 MonoDomain *stopped_domain; /* ditto */
857 gpointer *stopped_regs; /* ditto */
858 #ifndef HAVE_KW_THREAD
859 char *tlab_start;
860 char *tlab_next;
861 char *tlab_temp_end;
862 char *tlab_real_end;
863 gpointer *store_remset_buffer;
864 long store_remset_buffer_index;
865 #endif
868 #ifdef HAVE_KW_THREAD
869 #define TLAB_ACCESS_INIT
870 #define TLAB_START tlab_start
871 #define TLAB_NEXT tlab_next
872 #define TLAB_TEMP_END tlab_temp_end
873 #define TLAB_REAL_END tlab_real_end
874 #define REMEMBERED_SET remembered_set
875 #define STORE_REMSET_BUFFER store_remset_buffer
876 #define STORE_REMSET_BUFFER_INDEX store_remset_buffer_index
877 #define IN_CRITICAL_REGION thread_info->in_critical_region
878 #else
879 static pthread_key_t thread_info_key;
880 #define TLAB_ACCESS_INIT SgenThreadInfo *__thread_info__ = pthread_getspecific (thread_info_key)
881 #define TLAB_START (__thread_info__->tlab_start)
882 #define TLAB_NEXT (__thread_info__->tlab_next)
883 #define TLAB_TEMP_END (__thread_info__->tlab_temp_end)
884 #define TLAB_REAL_END (__thread_info__->tlab_real_end)
885 #define REMEMBERED_SET (__thread_info__->remset)
886 #define STORE_REMSET_BUFFER (__thread_info__->store_remset_buffer)
887 #define STORE_REMSET_BUFFER_INDEX (__thread_info__->store_remset_buffer_index)
888 #define IN_CRITICAL_REGION (__thread_info__->in_critical_region)
889 #endif
891 /* we use the memory barrier only to prevent compiler reordering (a memory constraint may be enough) */
892 #define ENTER_CRITICAL_REGION do {IN_CRITICAL_REGION = 1;mono_memory_barrier ();} while (0)
893 #define EXIT_CRITICAL_REGION do {IN_CRITICAL_REGION = 0;mono_memory_barrier ();} while (0)
896 * FIXME: What is faster, a TLS variable pointing to a structure, or separate TLS
897 * variables for next+temp_end ?
899 #ifdef HAVE_KW_THREAD
900 static __thread SgenThreadInfo *thread_info;
901 static __thread char *tlab_start;
902 static __thread char *tlab_next;
903 static __thread char *tlab_temp_end;
904 static __thread char *tlab_real_end;
905 static __thread gpointer *store_remset_buffer;
906 static __thread long store_remset_buffer_index;
907 /* Used by the managed allocator/wbarrier */
908 static __thread char **tlab_next_addr;
909 static __thread char *stack_end;
910 static __thread long *store_remset_buffer_index_addr;
911 #endif
912 static char *nursery_next = NULL;
913 static char *nursery_frag_real_end = NULL;
914 static char *nursery_real_end = NULL;
915 static char *nursery_last_pinned_end = NULL;
917 /* The size of a TLAB */
918 /* The bigger the value, the less often we have to go to the slow path to allocate a new
919 * one, but the more space is wasted by threads not allocating much memory.
920 * FIXME: Tune this.
921 * FIXME: Make this self-tuning for each thread.
923 static guint32 tlab_size = (1024 * 4);
925 /* fragments that are free and ready to be used for allocation */
926 static Fragment *nursery_fragments = NULL;
927 /* freeelist of fragment structures */
928 static Fragment *fragment_freelist = NULL;
930 /* objects bigger then this go into the large object space */
931 #define MAX_SMALL_OBJ_SIZE 2040
933 /* Functions supplied by the runtime to be called by the GC */
934 static MonoGCCallbacks gc_callbacks;
936 #define ALLOC_ALIGN 8
938 #define MOVED_OBJECTS_NUM 64
939 static void *moved_objects [MOVED_OBJECTS_NUM];
940 static int moved_objects_idx = 0;
943 * ######################################################################
944 * ######## Macros and function declarations.
945 * ######################################################################
948 #define UPDATE_HEAP_BOUNDARIES(low,high) do { \
949 if ((mword)(low) < lowest_heap_address) \
950 lowest_heap_address = (mword)(low); \
951 if ((mword)(high) > highest_heap_address) \
952 highest_heap_address = (mword)(high); \
953 } while (0)
954 #define ADDR_IN_HEAP_BOUNDARIES(addr) ((p) >= lowest_heap_address && (p) < highest_heap_address)
956 inline static void*
957 align_pointer (void *ptr)
959 mword p = (mword)ptr;
960 p += sizeof (gpointer) - 1;
961 p &= ~ (sizeof (gpointer) - 1);
962 return (void*)p;
965 typedef void (*CopyOrMarkObjectFunc) (void**);
966 typedef char* (*ScanObjectFunc) (char*);
968 /* forward declarations */
969 static void* get_internal_mem (size_t size, int type);
970 static void free_internal_mem (void *addr, int type);
971 static void* get_os_memory (size_t size, int activate);
972 static void* get_os_memory_aligned (mword size, mword alignment, gboolean activate);
973 static void free_os_memory (void *addr, size_t size);
974 static G_GNUC_UNUSED void report_internal_mem_usage (void);
976 static int stop_world (void);
977 static int restart_world (void);
978 static void add_to_global_remset (gpointer ptr, gboolean root);
979 static void scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise);
980 static void scan_from_remsets (void *start_nursery, void *end_nursery);
981 static void scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type);
982 static void scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list);
983 static void find_pinning_ref_from_thread (char *obj, size_t size);
984 static void update_current_thread_stack (void *start);
985 static void finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation);
986 static void add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation);
987 static void null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation);
988 static void null_links_for_domain (MonoDomain *domain, int generation);
989 static gboolean search_fragment_for_size (size_t size);
990 static void build_nursery_fragments (int start_pin, int end_pin);
991 static void clear_nursery_fragments (char *next);
992 static void pin_from_roots (void *start_nursery, void *end_nursery);
993 static int pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery);
994 static void pin_objects_in_section (GCMemSection *section);
995 static void optimize_pin_queue (int start_slot);
996 static void clear_remsets (void);
997 static void clear_tlabs (void);
998 typedef void (*IterateObjectCallbackFunc) (char*, size_t, void*);
999 static void scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data);
1000 static char* scan_object (char *start);
1001 static char* major_scan_object (char *start);
1002 static void* copy_object_no_checks (void *obj);
1003 static void copy_object (void **obj_slot);
1004 static void* get_chunk_freelist (PinnedChunk *chunk, int slot);
1005 static PinnedChunk* alloc_pinned_chunk (void);
1006 static void free_large_object (LOSObject *obj);
1007 static void sort_addresses (void **array, int size);
1008 static void drain_gray_stack (void);
1009 static void finish_gray_stack (char *start_addr, char *end_addr, int generation);
1011 static void mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track);
1013 void describe_ptr (char *ptr);
1014 static void check_consistency (void);
1015 static void check_section_scan_starts (GCMemSection *section);
1016 static void check_scan_starts (void);
1017 static void check_for_xdomain_refs (void);
1018 static void dump_occupied (char *start, char *end, char *section_start);
1019 static void dump_section (GCMemSection *section, const char *type);
1020 static void dump_heap (const char *type, int num, const char *reason);
1021 static void commit_stats (int generation);
1022 static void report_pinned_chunk (PinnedChunk *chunk, int seq);
1024 void mono_gc_scan_for_specific_ref (MonoObject *key);
1026 static void init_stats (void);
1028 //#define BINARY_PROTOCOL
1029 #include "sgen-protocol.c"
1030 #include "sgen-pinning.c"
1031 #include "sgen-pinning-stats.c"
1032 #include "sgen-gray.c"
1035 * ######################################################################
1036 * ######## GC descriptors
1037 * ######################################################################
1038 * Used to quickly get the info the GC needs about an object: size and
1039 * where the references are held.
1041 /* objects are aligned to 8 bytes boundaries
1042 * A descriptor is a pointer in MonoVTable, so 32 or 64 bits of size.
1043 * The low 3 bits define the type of the descriptor. The other bits
1044 * depend on the type.
1045 * As a general rule the 13 remaining low bits define the size, either
1046 * of the whole object or of the elements in the arrays. While for objects
1047 * the size is already in bytes, for arrays we need to shift, because
1048 * array elements might be smaller than 8 bytes. In case of arrays, we
1049 * use two bits to describe what the additional high bits represents,
1050 * so the default behaviour can handle element sizes less than 2048 bytes.
1051 * The high 16 bits, if 0 it means the object is pointer-free.
1052 * This design should make it easy and fast to skip over ptr-free data.
1053 * The first 4 types should cover >95% of the objects.
1054 * Note that since the size of objects is limited to 64K, larger objects
1055 * will be allocated in the large object heap.
1056 * If we want 4-bytes alignment, we need to put vector and small bitmap
1057 * inside complex.
1059 enum {
1060 DESC_TYPE_RUN_LENGTH, /* 16 bits aligned byte size | 1-3 (offset, numptr) bytes tuples */
1061 DESC_TYPE_SMALL_BITMAP, /* 16 bits aligned byte size | 16-48 bit bitmap */
1062 DESC_TYPE_STRING, /* nothing */
1063 DESC_TYPE_COMPLEX, /* index for bitmap into complex_descriptors */
1064 DESC_TYPE_VECTOR, /* 10 bits element size | 1 bit array | 2 bits desc | element desc */
1065 DESC_TYPE_ARRAY, /* 10 bits element size | 1 bit array | 2 bits desc | element desc */
1066 DESC_TYPE_LARGE_BITMAP, /* | 29-61 bitmap bits */
1067 DESC_TYPE_COMPLEX_ARR, /* index for bitmap into complex_descriptors */
1068 /* subtypes for arrays and vectors */
1069 DESC_TYPE_V_PTRFREE = 0,/* there are no refs: keep first so it has a zero value */
1070 DESC_TYPE_V_REFS, /* all the array elements are refs */
1071 DESC_TYPE_V_RUN_LEN, /* elements are run-length encoded as DESC_TYPE_RUN_LENGTH */
1072 DESC_TYPE_V_BITMAP /* elements are as the bitmap in DESC_TYPE_SMALL_BITMAP */
1075 #define OBJECT_HEADER_WORDS (sizeof(MonoObject)/sizeof(gpointer))
1076 #define LOW_TYPE_BITS 3
1077 #define SMALL_BITMAP_SHIFT 16
1078 #define SMALL_BITMAP_SIZE (GC_BITS_PER_WORD - SMALL_BITMAP_SHIFT)
1079 #define VECTOR_INFO_SHIFT 14
1080 #define VECTOR_ELSIZE_SHIFT 3
1081 #define LARGE_BITMAP_SIZE (GC_BITS_PER_WORD - LOW_TYPE_BITS)
1082 #define MAX_ELEMENT_SIZE 0x3ff
1083 #define VECTOR_SUBTYPE_PTRFREE (DESC_TYPE_V_PTRFREE << VECTOR_INFO_SHIFT)
1084 #define VECTOR_SUBTYPE_REFS (DESC_TYPE_V_REFS << VECTOR_INFO_SHIFT)
1085 #define VECTOR_SUBTYPE_RUN_LEN (DESC_TYPE_V_RUN_LEN << VECTOR_INFO_SHIFT)
1086 #define VECTOR_SUBTYPE_BITMAP (DESC_TYPE_V_BITMAP << VECTOR_INFO_SHIFT)
1089 /* Root bitmap descriptors are simpler: the lower three bits describe the type
1090 * and we either have 30/62 bitmap bits or nibble-based run-length,
1091 * or a complex descriptor, or a user defined marker function.
1093 enum {
1094 ROOT_DESC_CONSERVATIVE, /* 0, so matches NULL value */
1095 ROOT_DESC_BITMAP,
1096 ROOT_DESC_RUN_LEN,
1097 ROOT_DESC_COMPLEX,
1098 ROOT_DESC_USER,
1099 ROOT_DESC_TYPE_MASK = 0x7,
1100 ROOT_DESC_TYPE_SHIFT = 3,
1103 #define MAKE_ROOT_DESC(type,val) ((type) | ((val) << ROOT_DESC_TYPE_SHIFT))
1105 #define MAX_USER_DESCRIPTORS 16
1107 static gsize* complex_descriptors = NULL;
1108 static int complex_descriptors_size = 0;
1109 static int complex_descriptors_next = 0;
1110 static MonoGCRootMarkFunc user_descriptors [MAX_USER_DESCRIPTORS];
1111 static int user_descriptors_next = 0;
1113 static int
1114 alloc_complex_descriptor (gsize *bitmap, int numbits)
1116 int nwords, res, i;
1118 numbits = ALIGN_TO (numbits, GC_BITS_PER_WORD);
1119 nwords = numbits / GC_BITS_PER_WORD + 1;
1121 LOCK_GC;
1122 res = complex_descriptors_next;
1123 /* linear search, so we don't have duplicates with domain load/unload
1124 * this should not be performance critical or we'd have bigger issues
1125 * (the number and size of complex descriptors should be small).
1127 for (i = 0; i < complex_descriptors_next; ) {
1128 if (complex_descriptors [i] == nwords) {
1129 int j, found = TRUE;
1130 for (j = 0; j < nwords - 1; ++j) {
1131 if (complex_descriptors [i + 1 + j] != bitmap [j]) {
1132 found = FALSE;
1133 break;
1136 if (found) {
1137 UNLOCK_GC;
1138 return i;
1141 i += complex_descriptors [i];
1143 if (complex_descriptors_next + nwords > complex_descriptors_size) {
1144 int new_size = complex_descriptors_size * 2 + nwords;
1145 complex_descriptors = g_realloc (complex_descriptors, new_size * sizeof (gsize));
1146 complex_descriptors_size = new_size;
1148 DEBUG (6, fprintf (gc_debug_file, "Complex descriptor %d, size: %d (total desc memory: %d)\n", res, nwords, complex_descriptors_size));
1149 complex_descriptors_next += nwords;
1150 complex_descriptors [res] = nwords;
1151 for (i = 0; i < nwords - 1; ++i) {
1152 complex_descriptors [res + 1 + i] = bitmap [i];
1153 DEBUG (6, fprintf (gc_debug_file, "\tvalue: %p\n", (void*)complex_descriptors [res + 1 + i]));
1155 UNLOCK_GC;
1156 return res;
1160 * Descriptor builders.
1162 void*
1163 mono_gc_make_descr_for_string (gsize *bitmap, int numbits)
1165 return (void*) DESC_TYPE_STRING;
1168 void*
1169 mono_gc_make_descr_for_object (gsize *bitmap, int numbits, size_t obj_size)
1171 int first_set = -1, num_set = 0, last_set = -1, i;
1172 mword desc = 0;
1173 size_t stored_size = obj_size;
1174 stored_size += ALLOC_ALIGN - 1;
1175 stored_size &= ~(ALLOC_ALIGN - 1);
1176 for (i = 0; i < numbits; ++i) {
1177 if (bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
1178 if (first_set < 0)
1179 first_set = i;
1180 last_set = i;
1181 num_set++;
1184 if (stored_size <= MAX_SMALL_OBJ_SIZE) {
1185 /* check run-length encoding first: one byte offset, one byte number of pointers
1186 * on 64 bit archs, we can have 3 runs, just one on 32.
1187 * It may be better to use nibbles.
1189 if (first_set < 0) {
1190 desc = DESC_TYPE_RUN_LENGTH | stored_size;
1191 DEBUG (6, fprintf (gc_debug_file, "Ptrfree descriptor %p, size: %zd\n", (void*)desc, stored_size));
1192 return (void*) desc;
1193 } else if (first_set < 256 && num_set < 256 && (first_set + num_set == last_set + 1)) {
1194 desc = DESC_TYPE_RUN_LENGTH | stored_size | (first_set << 16) | (num_set << 24);
1195 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));
1196 return (void*) desc;
1198 /* we know the 2-word header is ptr-free */
1199 if (last_set < SMALL_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
1200 desc = DESC_TYPE_SMALL_BITMAP | stored_size | ((*bitmap >> OBJECT_HEADER_WORDS) << SMALL_BITMAP_SHIFT);
1201 DEBUG (6, fprintf (gc_debug_file, "Smallbitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
1202 return (void*) desc;
1205 /* we know the 2-word header is ptr-free */
1206 if (last_set < LARGE_BITMAP_SIZE + OBJECT_HEADER_WORDS) {
1207 desc = DESC_TYPE_LARGE_BITMAP | ((*bitmap >> OBJECT_HEADER_WORDS) << LOW_TYPE_BITS);
1208 DEBUG (6, fprintf (gc_debug_file, "Largebitmap descriptor %p, size: %zd, last set: %d\n", (void*)desc, stored_size, last_set));
1209 return (void*) desc;
1211 /* it's a complex object ... */
1212 desc = DESC_TYPE_COMPLEX | (alloc_complex_descriptor (bitmap, last_set + 1) << LOW_TYPE_BITS);
1213 return (void*) desc;
1216 /* If the array holds references, numbits == 1 and the first bit is set in elem_bitmap */
1217 void*
1218 mono_gc_make_descr_for_array (int vector, gsize *elem_bitmap, int numbits, size_t elem_size)
1220 int first_set = -1, num_set = 0, last_set = -1, i;
1221 mword desc = vector? DESC_TYPE_VECTOR: DESC_TYPE_ARRAY;
1222 for (i = 0; i < numbits; ++i) {
1223 if (elem_bitmap [i / GC_BITS_PER_WORD] & ((gsize)1 << (i % GC_BITS_PER_WORD))) {
1224 if (first_set < 0)
1225 first_set = i;
1226 last_set = i;
1227 num_set++;
1230 if (elem_size <= MAX_ELEMENT_SIZE) {
1231 desc |= elem_size << VECTOR_ELSIZE_SHIFT;
1232 if (!num_set) {
1233 return (void*)(desc | VECTOR_SUBTYPE_PTRFREE);
1235 /* Note: we also handle structs with just ref fields */
1236 if (num_set * sizeof (gpointer) == elem_size) {
1237 return (void*)(desc | VECTOR_SUBTYPE_REFS | ((gssize)(-1) << 16));
1239 /* FIXME: try run-len first */
1240 /* Note: we can't skip the object header here, because it's not present */
1241 if (last_set <= SMALL_BITMAP_SIZE) {
1242 return (void*)(desc | VECTOR_SUBTYPE_BITMAP | (*elem_bitmap << 16));
1245 /* it's am array of complex structs ... */
1246 desc = DESC_TYPE_COMPLEX_ARR;
1247 desc |= alloc_complex_descriptor (elem_bitmap, last_set + 1) << LOW_TYPE_BITS;
1248 return (void*) desc;
1251 /* Return the bitmap encoded by a descriptor */
1252 gsize*
1253 mono_gc_get_bitmap_for_descr (void *descr, int *numbits)
1255 mword d = (mword)descr;
1256 gsize *bitmap;
1258 switch (d & 0x7) {
1259 case DESC_TYPE_RUN_LENGTH: {
1260 int first_set = (d >> 16) & 0xff;
1261 int num_set = (d >> 24) & 0xff;
1262 int i;
1264 bitmap = g_new0 (gsize, (first_set + num_set + 7) / 8);
1266 for (i = first_set; i < first_set + num_set; ++i)
1267 bitmap [i / GC_BITS_PER_WORD] |= ((gsize)1 << (i % GC_BITS_PER_WORD));
1269 *numbits = first_set + num_set;
1271 return bitmap;
1273 case DESC_TYPE_SMALL_BITMAP:
1274 bitmap = g_new0 (gsize, 1);
1276 bitmap [0] = (d >> SMALL_BITMAP_SHIFT) << OBJECT_HEADER_WORDS;
1278 *numbits = GC_BITS_PER_WORD;
1280 return bitmap;
1281 default:
1282 g_assert_not_reached ();
1286 /* helper macros to scan and traverse objects, macros because we resue them in many functions */
1287 #define STRING_SIZE(size,str) do { \
1288 (size) = sizeof (MonoString) + 2 * mono_string_length ((MonoString*)(str)) + 2; \
1289 (size) += (ALLOC_ALIGN - 1); \
1290 (size) &= ~(ALLOC_ALIGN - 1); \
1291 } while (0)
1293 #define OBJ_RUN_LEN_SIZE(size,desc,obj) do { \
1294 (size) = (desc) & 0xfff8; \
1295 } while (0)
1297 #define OBJ_BITMAP_SIZE(size,desc,obj) do { \
1298 (size) = (desc) & 0xfff8; \
1299 } while (0)
1301 //#define PREFETCH(addr) __asm__ __volatile__ (" prefetchnta %0": : "m"(*(char *)(addr)))
1302 #define PREFETCH(addr)
1304 /* code using these macros must define a HANDLE_PTR(ptr) macro that does the work */
1305 #define OBJ_RUN_LEN_FOREACH_PTR(desc,obj) do { \
1306 if ((desc) & 0xffff0000) { \
1307 /* there are pointers */ \
1308 void **_objptr_end; \
1309 void **_objptr = (void**)(obj); \
1310 _objptr += ((desc) >> 16) & 0xff; \
1311 _objptr_end = _objptr + (((desc) >> 24) & 0xff); \
1312 while (_objptr < _objptr_end) { \
1313 HANDLE_PTR (_objptr, (obj)); \
1314 _objptr++; \
1317 } while (0)
1319 /* a bitmap desc means that there are pointer references or we'd have
1320 * choosen run-length, instead: add an assert to check.
1322 #define OBJ_BITMAP_FOREACH_PTR(desc,obj) do { \
1323 /* there are pointers */ \
1324 void **_objptr = (void**)(obj); \
1325 gsize _bmap = (desc) >> 16; \
1326 _objptr += OBJECT_HEADER_WORDS; \
1327 while (_bmap) { \
1328 if ((_bmap & 1)) { \
1329 HANDLE_PTR (_objptr, (obj)); \
1331 _bmap >>= 1; \
1332 ++_objptr; \
1334 } while (0)
1336 #define OBJ_LARGE_BITMAP_FOREACH_PTR(vt,obj) do { \
1337 /* there are pointers */ \
1338 void **_objptr = (void**)(obj); \
1339 gsize _bmap = (vt)->desc >> LOW_TYPE_BITS; \
1340 _objptr += OBJECT_HEADER_WORDS; \
1341 while (_bmap) { \
1342 if ((_bmap & 1)) { \
1343 HANDLE_PTR (_objptr, (obj)); \
1345 _bmap >>= 1; \
1346 ++_objptr; \
1348 } while (0)
1350 #define OBJ_COMPLEX_FOREACH_PTR(vt,obj) do { \
1351 /* there are pointers */ \
1352 void **_objptr = (void**)(obj); \
1353 gsize *bitmap_data = complex_descriptors + ((vt)->desc >> LOW_TYPE_BITS); \
1354 int bwords = (*bitmap_data) - 1; \
1355 void **start_run = _objptr; \
1356 bitmap_data++; \
1357 if (0) { \
1358 MonoObject *myobj = (MonoObject*)obj; \
1359 g_print ("found %d at %p (0x%zx): %s.%s\n", bwords, (obj), (vt)->desc, myobj->vtable->klass->name_space, myobj->vtable->klass->name); \
1361 while (bwords-- > 0) { \
1362 gsize _bmap = *bitmap_data++; \
1363 _objptr = start_run; \
1364 /*g_print ("bitmap: 0x%x/%d at %p\n", _bmap, bwords, _objptr);*/ \
1365 while (_bmap) { \
1366 if ((_bmap & 1)) { \
1367 HANDLE_PTR (_objptr, (obj)); \
1369 _bmap >>= 1; \
1370 ++_objptr; \
1372 start_run += GC_BITS_PER_WORD; \
1374 } while (0)
1376 /* this one is untested */
1377 #define OBJ_COMPLEX_ARR_FOREACH_PTR(vt,obj) do { \
1378 /* there are pointers */ \
1379 gsize *mbitmap_data = complex_descriptors + ((vt)->desc >> LOW_TYPE_BITS); \
1380 int mbwords = (*mbitmap_data++) - 1; \
1381 int el_size = mono_array_element_size (((MonoObject*)(obj))->vtable->klass); \
1382 char *e_start = (char*)(obj) + G_STRUCT_OFFSET (MonoArray, vector); \
1383 char *e_end = e_start + el_size * mono_array_length ((MonoArray*)(obj)); \
1384 if (0) { \
1385 MonoObject *myobj = (MonoObject*)start; \
1386 g_print ("found %d at %p (0x%zx): %s.%s\n", mbwords, (obj), (vt)->desc, myobj->vtable->klass->name_space, myobj->vtable->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 ((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 ((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 ((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 char*
1552 scan_object_for_xdomain_refs (char *start)
1554 MonoDomain *domain = ((MonoObject*)start)->vtable->domain;
1556 #include "sgen-scan-object.h"
1558 return start;
1561 static void
1562 scan_area_for_xdomain_refs (char *start, char *end)
1564 while (start < end) {
1565 if (!*(void**)start) {
1566 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
1567 continue;
1570 start = scan_object_for_xdomain_refs (start);
1574 #undef HANDLE_PTR
1575 #define HANDLE_PTR(ptr,obj) do { \
1576 if ((MonoObject*)*(ptr) == key) { \
1577 g_print ("found ref to %p in object %p (%s) at offset %zd\n", \
1578 key, (obj), safe_name ((obj)), ((char*)(ptr) - (char*)(obj))); \
1580 } while (0)
1582 static char*
1583 scan_object_for_specific_ref (char *start, MonoObject *key)
1585 #include "sgen-scan-object.h"
1587 return start;
1590 static void
1591 scan_area_with_callback (char *start, char *end, IterateObjectCallbackFunc callback, void *data)
1593 while (start < end) {
1594 size_t size;
1595 if (!*(void**)start) {
1596 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
1597 continue;
1600 size = safe_object_get_size ((MonoObject*) start);
1601 size += ALLOC_ALIGN - 1;
1602 size &= ~(ALLOC_ALIGN - 1);
1604 callback (start, size, data);
1606 start += size;
1610 static void
1611 scan_object_for_specific_ref_callback (char *obj, size_t size, MonoObject *key)
1613 scan_object_for_specific_ref (obj, key);
1616 static void
1617 check_root_obj_specific_ref (RootRecord *root, MonoObject *key, MonoObject *obj)
1619 if (key != obj)
1620 return;
1621 g_print ("found ref to %p in root record %p\n", key, root);
1624 static MonoObject *check_key = NULL;
1625 static RootRecord *check_root = NULL;
1627 static void
1628 check_root_obj_specific_ref_from_marker (void **obj)
1630 check_root_obj_specific_ref (check_root, check_key, *obj);
1633 static void
1634 scan_roots_for_specific_ref (MonoObject *key, int root_type)
1636 int i;
1637 RootRecord *root;
1638 check_key = key;
1639 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1640 for (root = roots_hash [root_type][i]; root; root = root->next) {
1641 void **start_root = (void**)root->start_root;
1642 mword desc = root->root_desc;
1644 check_root = root;
1646 switch (desc & ROOT_DESC_TYPE_MASK) {
1647 case ROOT_DESC_BITMAP:
1648 desc >>= ROOT_DESC_TYPE_SHIFT;
1649 while (desc) {
1650 if (desc & 1)
1651 check_root_obj_specific_ref (root, key, *start_root);
1652 desc >>= 1;
1653 start_root++;
1655 return;
1656 case ROOT_DESC_COMPLEX: {
1657 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1658 int bwords = (*bitmap_data) - 1;
1659 void **start_run = start_root;
1660 bitmap_data++;
1661 while (bwords-- > 0) {
1662 gsize bmap = *bitmap_data++;
1663 void **objptr = start_run;
1664 while (bmap) {
1665 if (bmap & 1)
1666 check_root_obj_specific_ref (root, key, *objptr);
1667 bmap >>= 1;
1668 ++objptr;
1670 start_run += GC_BITS_PER_WORD;
1672 break;
1674 case ROOT_DESC_USER: {
1675 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1676 marker (start_root, check_root_obj_specific_ref_from_marker);
1677 break;
1679 case ROOT_DESC_RUN_LEN:
1680 g_assert_not_reached ();
1681 default:
1682 g_assert_not_reached ();
1686 check_key = NULL;
1687 check_root = NULL;
1690 void
1691 mono_gc_scan_for_specific_ref (MonoObject *key)
1693 LOSObject *bigobj;
1694 RootRecord *root;
1695 int i;
1697 scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1698 (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1700 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)scan_object_for_specific_ref_callback, key);
1702 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1703 scan_object_for_specific_ref (bigobj->data, key);
1705 scan_roots_for_specific_ref (key, ROOT_TYPE_NORMAL);
1706 scan_roots_for_specific_ref (key, ROOT_TYPE_WBARRIER);
1708 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
1709 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
1710 void **ptr = (void**)root->start_root;
1712 while (ptr < (void**)root->end_root) {
1713 check_root_obj_specific_ref (root, *ptr, key);
1714 ++ptr;
1720 /* Clear all remaining nursery fragments */
1721 static void
1722 clear_nursery_fragments (char *next)
1724 Fragment *frag;
1725 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION) {
1726 g_assert (next <= nursery_frag_real_end);
1727 memset (next, 0, nursery_frag_real_end - next);
1728 for (frag = nursery_fragments; frag; frag = frag->next) {
1729 memset (frag->fragment_start, 0, frag->fragment_end - frag->fragment_start);
1734 static gboolean
1735 need_remove_object_for_domain (char *start, MonoDomain *domain)
1737 if (mono_object_domain (start) == domain) {
1738 DEBUG (4, fprintf (gc_debug_file, "Need to cleanup object %p\n", start));
1739 binary_protocol_cleanup (start, (gpointer)LOAD_VTABLE (start), safe_object_get_size ((MonoObject*)start));
1740 return TRUE;
1742 return FALSE;
1745 static void
1746 process_object_for_domain_clearing (char *start, MonoDomain *domain)
1748 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
1749 if (vt->klass == mono_defaults.internal_thread_class)
1750 g_assert (mono_object_domain (start) == mono_get_root_domain ());
1751 /* The object could be a proxy for an object in the domain
1752 we're deleting. */
1753 if (mono_class_has_parent (vt->klass, mono_defaults.real_proxy_class)) {
1754 MonoObject *server = ((MonoRealProxy*)start)->unwrapped_server;
1756 /* The server could already have been zeroed out, so
1757 we need to check for that, too. */
1758 if (server && (!LOAD_VTABLE (server) || mono_object_domain (server) == domain)) {
1759 DEBUG (4, fprintf (gc_debug_file, "Cleaning up remote pointer in %p to object %p\n",
1760 start, server));
1761 ((MonoRealProxy*)start)->unwrapped_server = NULL;
1766 static MonoDomain *check_domain = NULL;
1768 static void
1769 check_obj_not_in_domain (void **o)
1771 g_assert (((MonoObject*)(*o))->vtable->domain != check_domain);
1774 static void
1775 scan_for_registered_roots_in_domain (MonoDomain *domain, int root_type)
1777 int i;
1778 RootRecord *root;
1779 check_domain = domain;
1780 for (i = 0; i < roots_hash_size [root_type]; ++i) {
1781 for (root = roots_hash [root_type][i]; root; root = root->next) {
1782 void **start_root = (void**)root->start_root;
1783 mword desc = root->root_desc;
1785 /* The MonoDomain struct is allowed to hold
1786 references to objects in its own domain. */
1787 if (start_root == (void**)domain)
1788 continue;
1790 switch (desc & ROOT_DESC_TYPE_MASK) {
1791 case ROOT_DESC_BITMAP:
1792 desc >>= ROOT_DESC_TYPE_SHIFT;
1793 while (desc) {
1794 if ((desc & 1) && *start_root)
1795 check_obj_not_in_domain (*start_root);
1796 desc >>= 1;
1797 start_root++;
1799 break;
1800 case ROOT_DESC_COMPLEX: {
1801 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
1802 int bwords = (*bitmap_data) - 1;
1803 void **start_run = start_root;
1804 bitmap_data++;
1805 while (bwords-- > 0) {
1806 gsize bmap = *bitmap_data++;
1807 void **objptr = start_run;
1808 while (bmap) {
1809 if ((bmap & 1) && *objptr)
1810 check_obj_not_in_domain (*objptr);
1811 bmap >>= 1;
1812 ++objptr;
1814 start_run += GC_BITS_PER_WORD;
1816 break;
1818 case ROOT_DESC_USER: {
1819 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
1820 marker (start_root, check_obj_not_in_domain);
1821 break;
1823 case ROOT_DESC_RUN_LEN:
1824 g_assert_not_reached ();
1825 default:
1826 g_assert_not_reached ();
1830 check_domain = NULL;
1833 static void
1834 scan_pinned_object_for_xdomain_refs_callback (char *obj, size_t size, gpointer dummy)
1836 scan_object_for_xdomain_refs (obj);
1839 static void
1840 check_for_xdomain_refs (void)
1842 LOSObject *bigobj;
1844 scan_area_for_xdomain_refs (nursery_section->data, nursery_section->end_data);
1846 major_iterate_objects (TRUE, TRUE, scan_pinned_object_for_xdomain_refs_callback, NULL);
1848 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1849 scan_object_for_xdomain_refs (bigobj->data);
1852 static gboolean
1853 clear_domain_process_object (char *obj, MonoDomain *domain)
1855 gboolean remove;
1857 process_object_for_domain_clearing (obj, domain);
1858 remove = need_remove_object_for_domain (obj, domain);
1860 if (remove && ((MonoObject*)obj)->synchronisation) {
1861 void **dislink = mono_monitor_get_object_monitor_weak_link ((MonoObject*)obj);
1862 if (dislink)
1863 mono_gc_register_disappearing_link (NULL, dislink, FALSE);
1866 return remove;
1869 static void
1870 clear_domain_process_minor_object_callback (char *obj, size_t size, MonoDomain *domain)
1872 if (clear_domain_process_object (obj, domain))
1873 memset (obj, 0, size);
1876 static void
1877 clear_domain_process_major_object_callback (char *obj, size_t size, MonoDomain *domain)
1879 clear_domain_process_object (obj, domain);
1882 static void
1883 clear_domain_free_major_non_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1885 if (need_remove_object_for_domain (obj, domain))
1886 major_free_non_pinned_object (obj, size);
1889 static void
1890 clear_domain_free_major_pinned_object_callback (char *obj, size_t size, MonoDomain *domain)
1892 if (need_remove_object_for_domain (obj, domain))
1893 free_pinned_object (obj, size);
1897 * When appdomains are unloaded we can easily remove objects that have finalizers,
1898 * but all the others could still be present in random places on the heap.
1899 * We need a sweep to get rid of them even though it's going to be costly
1900 * with big heaps.
1901 * The reason we need to remove them is because we access the vtable and class
1902 * structures to know the object size and the reference bitmap: once the domain is
1903 * unloaded the point to random memory.
1905 void
1906 mono_gc_clear_domain (MonoDomain * domain)
1908 LOSObject *bigobj, *prev;
1909 int i;
1911 LOCK_GC;
1913 clear_nursery_fragments (nursery_next);
1915 if (xdomain_checks && domain != mono_get_root_domain ()) {
1916 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_NORMAL);
1917 scan_for_registered_roots_in_domain (domain, ROOT_TYPE_WBARRIER);
1918 check_for_xdomain_refs ();
1921 scan_area_with_callback (nursery_section->data, nursery_section->end_data,
1922 (IterateObjectCallbackFunc)clear_domain_process_minor_object_callback, domain);
1924 /* We need two passes over major and large objects because
1925 freeing such objects might give their memory back to the OS
1926 (in the case of large objects) or obliterate its vtable
1927 (pinned objects with major-copying or pinned and non-pinned
1928 objects with major-mark&sweep), but we might need to
1929 dereference a pointer from an object to another object if
1930 the first object is a proxy. */
1931 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)clear_domain_process_major_object_callback, domain);
1932 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
1933 clear_domain_process_object (bigobj->data, domain);
1935 prev = NULL;
1936 for (bigobj = los_object_list; bigobj;) {
1937 if (need_remove_object_for_domain (bigobj->data, domain)) {
1938 LOSObject *to_free = bigobj;
1939 if (prev)
1940 prev->next = bigobj->next;
1941 else
1942 los_object_list = bigobj->next;
1943 bigobj = bigobj->next;
1944 DEBUG (4, fprintf (gc_debug_file, "Freeing large object %p\n",
1945 bigobj->data));
1946 free_large_object (to_free);
1947 continue;
1949 prev = bigobj;
1950 bigobj = bigobj->next;
1952 major_iterate_objects (TRUE, FALSE, (IterateObjectCallbackFunc)clear_domain_free_major_non_pinned_object_callback, domain);
1953 major_iterate_objects (FALSE, TRUE, (IterateObjectCallbackFunc)clear_domain_free_major_pinned_object_callback, domain);
1955 for (i = GENERATION_NURSERY; i < GENERATION_MAX; ++i)
1956 null_links_for_domain (domain, i);
1958 UNLOCK_GC;
1962 * add_to_global_remset:
1964 * The global remset contains locations which point into newspace after
1965 * a minor collection. This can happen if the objects they point to are pinned.
1967 static void
1968 add_to_global_remset (gpointer ptr, gboolean root)
1970 RememberedSet *rs;
1972 DEBUG (8, fprintf (gc_debug_file, "Adding global remset for %p\n", ptr));
1973 binary_protocol_global_remset (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
1975 g_assert (!root);
1976 g_assert (!ptr_in_nursery (ptr) && ptr_in_nursery (*(gpointer*)ptr));
1978 HEAVY_STAT (++stat_global_remsets_added);
1981 * FIXME: If an object remains pinned, we need to add it at every minor collection.
1982 * To avoid uncontrolled growth of the global remset, only add each pointer once.
1984 if (global_remset->store_next + 3 < global_remset->end_set) {
1985 if (root) {
1986 *(global_remset->store_next++) = (mword)ptr | REMSET_OTHER;
1987 *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION;
1988 } else {
1989 *(global_remset->store_next++) = (mword)ptr;
1991 return;
1993 rs = alloc_remset (global_remset->end_set - global_remset->data, NULL);
1994 rs->next = global_remset;
1995 global_remset = rs;
1996 if (root) {
1997 *(global_remset->store_next++) = (mword)ptr | REMSET_OTHER;
1998 *(global_remset->store_next++) = (mword)REMSET_ROOT_LOCATION;
1999 } else {
2000 *(global_remset->store_next++) = (mword)ptr;
2004 int global_rs_size = 0;
2006 for (rs = global_remset; rs; rs = rs->next) {
2007 global_rs_size += rs->store_next - rs->data;
2009 DEBUG (4, fprintf (gc_debug_file, "Global remset now has size %d\n", global_rs_size));
2014 * FIXME: allocate before calling this function and pass the
2015 * destination address.
2017 static void*
2018 copy_object_no_checks (void *obj)
2020 static const void *copy_labels [] = { &&LAB_0, &&LAB_1, &&LAB_2, &&LAB_3, &&LAB_4, &&LAB_5, &&LAB_6, &&LAB_7, &&LAB_8 };
2022 mword objsize;
2023 char *destination;
2024 MonoVTable *vt;
2026 objsize = safe_object_get_size ((MonoObject*)obj);
2027 objsize += ALLOC_ALIGN - 1;
2028 objsize &= ~(ALLOC_ALIGN - 1);
2030 MAJOR_GET_COPY_OBJECT_SPACE (destination, objsize);
2032 DEBUG (9, fprintf (gc_debug_file, " (to %p, %s size: %zd)\n", destination, ((MonoObject*)obj)->vtable->klass->name, objsize));
2033 binary_protocol_copy (obj, destination, ((MonoObject*)obj)->vtable, objsize);
2035 if (objsize <= sizeof (gpointer) * 8) {
2036 mword *dest = (mword*)destination;
2037 goto *copy_labels [objsize / sizeof (gpointer)];
2038 LAB_8:
2039 (dest) [7] = ((mword*)obj) [7];
2040 LAB_7:
2041 (dest) [6] = ((mword*)obj) [6];
2042 LAB_6:
2043 (dest) [5] = ((mword*)obj) [5];
2044 LAB_5:
2045 (dest) [4] = ((mword*)obj) [4];
2046 LAB_4:
2047 (dest) [3] = ((mword*)obj) [3];
2048 LAB_3:
2049 (dest) [2] = ((mword*)obj) [2];
2050 LAB_2:
2051 (dest) [1] = ((mword*)obj) [1];
2052 LAB_1:
2053 (dest) [0] = ((mword*)obj) [0];
2054 LAB_0:
2056 } else {
2057 #if 0
2059 int ecx;
2060 char* esi = obj;
2061 char* edi = destination;
2062 __asm__ __volatile__(
2063 "rep; movsl"
2064 : "=&c" (ecx), "=&D" (edi), "=&S" (esi)
2065 : "0" (objsize/4), "1" (edi),"2" (esi)
2066 : "memory"
2069 #else
2070 memcpy (destination, obj, objsize);
2071 #endif
2073 /* adjust array->bounds */
2074 vt = ((MonoObject*)obj)->vtable;
2075 DEBUG (9, g_assert (vt->gc_descr));
2076 if (G_UNLIKELY (vt->rank && ((MonoArray*)obj)->bounds)) {
2077 MonoArray *array = (MonoArray*)destination;
2078 array->bounds = (MonoArrayBounds*)((char*)destination + ((char*)((MonoArray*)obj)->bounds - (char*)obj));
2079 DEBUG (9, fprintf (gc_debug_file, "Array instance %p: size: %zd, rank: %d, length: %d\n", array, objsize, vt->rank, mono_array_length (array)));
2081 /* set the forwarding pointer */
2082 forward_object (obj, destination);
2083 if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_GC_MOVES)) {
2084 if (moved_objects_idx == MOVED_OBJECTS_NUM) {
2085 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
2086 moved_objects_idx = 0;
2088 moved_objects [moved_objects_idx++] = obj;
2089 moved_objects [moved_objects_idx++] = destination;
2091 obj = destination;
2092 DEBUG (9, fprintf (gc_debug_file, "Enqueuing gray object %p (%s)\n", obj, safe_name (obj)));
2093 GRAY_OBJECT_ENQUEUE (obj);
2094 return obj;
2098 * This is how the copying happens from the nursery to the old generation.
2099 * We assume that at this time all the pinned objects have been identified and
2100 * marked as such.
2101 * We run scan_object() for each pinned object so that each referenced
2102 * objects if possible are copied. The new gray objects created can have
2103 * scan_object() run on them right away, too.
2104 * Then we run copy_object() for the precisely tracked roots. At this point
2105 * all the roots are either gray or black. We run scan_object() on the gray
2106 * objects until no more gray objects are created.
2107 * At the end of the process we walk again the pinned list and we unmark
2108 * the pinned flag. As we go we also create the list of free space for use
2109 * in the next allocation runs.
2111 * We need to remember objects from the old generation that point to the new one
2112 * (or just addresses?).
2114 * copy_object could be made into a macro once debugged (use inline for now).
2117 static void __attribute__((noinline))
2118 copy_object (void **obj_slot)
2120 char *forwarded;
2121 char *obj = *obj_slot;
2123 DEBUG (9, g_assert (current_collection_generation == GENERATION_NURSERY));
2125 HEAVY_STAT (++stat_copy_object_called_nursery);
2127 if (!ptr_in_nursery (obj)) {
2128 HEAVY_STAT (++stat_nursery_copy_object_failed_from_space);
2129 return;
2132 DEBUG (9, fprintf (gc_debug_file, "Precise copy of %p from %p", obj, obj_slot));
2135 * Before we can copy the object we must make sure that we are
2136 * allowed to, i.e. that the object not pinned or not already
2137 * forwarded.
2140 if ((forwarded = object_is_forwarded (obj))) {
2141 DEBUG (9, g_assert (((MonoVTable*)LOAD_VTABLE(obj))->gc_descr));
2142 DEBUG (9, fprintf (gc_debug_file, " (already forwarded to %p)\n", forwarded));
2143 HEAVY_STAT (++stat_nursery_copy_object_failed_forwarded);
2144 *obj_slot = forwarded;
2145 return;
2147 if (object_is_pinned (obj)) {
2148 DEBUG (9, g_assert (((MonoVTable*)LOAD_VTABLE(obj))->gc_descr));
2149 DEBUG (9, fprintf (gc_debug_file, " (pinned, no change)\n"));
2150 HEAVY_STAT (++stat_nursery_copy_object_failed_pinned);
2151 return;
2154 HEAVY_STAT (++stat_objects_copied_nursery);
2156 *obj_slot = copy_object_no_checks (obj);
2159 #undef HANDLE_PTR
2160 #define HANDLE_PTR(ptr,obj) do { \
2161 void *__old = *(ptr); \
2162 void *__copy; \
2163 if (__old) { \
2164 copy_object ((ptr)); \
2165 __copy = *(ptr); \
2166 DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old)); \
2167 if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
2168 add_to_global_remset ((ptr), FALSE); \
2170 } while (0)
2173 * Scan the object pointed to by @start for references to
2174 * other objects between @from_start and @from_end and copy
2175 * them to the gray_objects area.
2176 * Returns a pointer to the end of the object.
2178 static char*
2179 scan_object (char *start)
2181 #include "sgen-scan-object.h"
2183 HEAVY_STAT (++stat_scan_object_called_nursery);
2185 return start;
2189 * scan_vtype:
2191 * Scan the valuetype pointed to by START, described by DESC for references to
2192 * other objects between @from_start and @from_end and copy them to the gray_objects area.
2193 * Returns a pointer to the end of the object.
2195 static char*
2196 scan_vtype (char *start, mword desc, char* from_start, char* from_end)
2198 size_t skip_size;
2200 /* The descriptors include info about the MonoObject header as well */
2201 start -= sizeof (MonoObject);
2203 switch (desc & 0x7) {
2204 case DESC_TYPE_RUN_LENGTH:
2205 OBJ_RUN_LEN_FOREACH_PTR (desc,start);
2206 OBJ_RUN_LEN_SIZE (skip_size, desc, start);
2207 g_assert (skip_size);
2208 return start + skip_size;
2209 case DESC_TYPE_SMALL_BITMAP:
2210 OBJ_BITMAP_FOREACH_PTR (desc,start);
2211 OBJ_BITMAP_SIZE (skip_size, desc, start);
2212 return start + skip_size;
2213 case DESC_TYPE_LARGE_BITMAP:
2214 case DESC_TYPE_COMPLEX:
2215 // FIXME:
2216 g_assert_not_reached ();
2217 break;
2218 default:
2219 // The other descriptors can't happen with vtypes
2220 g_assert_not_reached ();
2221 break;
2223 return NULL;
2226 #undef HANDLE_PTR
2227 #define HANDLE_PTR(ptr,obj) do { \
2228 void *__old = *(ptr); \
2229 void *__copy; \
2230 if (__old) { \
2231 major_copy_or_mark_object ((ptr)); \
2232 __copy = *(ptr); \
2233 DEBUG (9, if (__old != __copy) fprintf (gc_debug_file, "Overwrote field at %p with %p (was: %p)\n", (ptr), *(ptr), __old)); \
2234 if (G_UNLIKELY (ptr_in_nursery (__copy) && !ptr_in_nursery ((ptr)))) \
2235 add_to_global_remset ((ptr), FALSE); \
2237 } while (0)
2239 static char*
2240 major_scan_object (char *start)
2242 #include "sgen-scan-object.h"
2244 HEAVY_STAT (++stat_scan_object_called_major);
2246 return start;
2250 * drain_gray_stack:
2252 * Scan objects in the gray stack until the stack is empty. This should be called
2253 * frequently after each object is copied, to achieve better locality and cache
2254 * usage.
2256 static void inline
2257 drain_gray_stack (void)
2259 char *obj;
2261 if (current_collection_generation == GENERATION_NURSERY) {
2262 for (;;) {
2263 GRAY_OBJECT_DEQUEUE (obj);
2264 if (!obj)
2265 break;
2266 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
2267 scan_object (obj);
2269 } else {
2270 for (;;) {
2271 GRAY_OBJECT_DEQUEUE (obj);
2272 if (!obj)
2273 break;
2274 DEBUG (9, fprintf (gc_debug_file, "Precise gray object scan %p (%s)\n", obj, safe_name (obj)));
2275 major_scan_object (obj);
2281 * Addresses from start to end are already sorted. This function finds
2282 * the object header for each address and pins the object. The
2283 * addresses must be inside the passed section. The (start of the)
2284 * address array is overwritten with the addresses of the actually
2285 * pinned objects. Return the number of pinned objects.
2287 static int
2288 pin_objects_from_addresses (GCMemSection *section, void **start, void **end, void *start_nursery, void *end_nursery)
2290 void *last = NULL;
2291 int count = 0;
2292 void *search_start;
2293 void *last_obj = NULL;
2294 size_t last_obj_size = 0;
2295 void *addr;
2296 int idx;
2297 void **definitely_pinned = start;
2298 while (start < end) {
2299 addr = *start;
2300 /* the range check should be reduntant */
2301 if (addr != last && addr >= start_nursery && addr < end_nursery) {
2302 DEBUG (5, fprintf (gc_debug_file, "Considering pinning addr %p\n", addr));
2303 /* multiple pointers to the same object */
2304 if (addr >= last_obj && (char*)addr < (char*)last_obj + last_obj_size) {
2305 start++;
2306 continue;
2308 idx = ((char*)addr - (char*)section->data) / SCAN_START_SIZE;
2309 g_assert (idx < section->num_scan_start);
2310 search_start = (void*)section->scan_starts [idx];
2311 if (!search_start || search_start > addr) {
2312 while (idx) {
2313 --idx;
2314 search_start = section->scan_starts [idx];
2315 if (search_start && search_start <= addr)
2316 break;
2318 if (!search_start || search_start > addr)
2319 search_start = start_nursery;
2321 if (search_start < last_obj)
2322 search_start = (char*)last_obj + last_obj_size;
2323 /* now addr should be in an object a short distance from search_start
2324 * Note that search_start must point to zeroed mem or point to an object.
2326 do {
2327 if (!*(void**)search_start) {
2328 mword p = (mword)search_start;
2329 p += sizeof (gpointer);
2330 p += ALLOC_ALIGN - 1;
2331 p &= ~(ALLOC_ALIGN - 1);
2332 search_start = (void*)p;
2333 continue;
2335 last_obj = search_start;
2336 last_obj_size = safe_object_get_size ((MonoObject*)search_start);
2337 last_obj_size += ALLOC_ALIGN - 1;
2338 last_obj_size &= ~(ALLOC_ALIGN - 1);
2339 DEBUG (8, fprintf (gc_debug_file, "Pinned try match %p (%s), size %zd\n", last_obj, safe_name (last_obj), last_obj_size));
2340 if (addr >= search_start && (char*)addr < (char*)last_obj + last_obj_size) {
2341 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));
2342 binary_protocol_pin (search_start, (gpointer)LOAD_VTABLE (search_start), safe_object_get_size (search_start));
2343 pin_object (search_start);
2344 GRAY_OBJECT_ENQUEUE (search_start);
2345 if (heap_dump_file)
2346 pin_stats_register_object (search_start, last_obj_size);
2347 definitely_pinned [count] = search_start;
2348 count++;
2349 break;
2351 /* skip to the next object */
2352 search_start = (void*)((char*)search_start + last_obj_size);
2353 } while (search_start <= addr);
2354 /* we either pinned the correct object or we ignored the addr because
2355 * it points to unused zeroed memory.
2357 last = addr;
2359 start++;
2361 //printf ("effective pinned: %d (at the end: %d)\n", count, (char*)end_nursery - (char*)last);
2362 return count;
2365 static void
2366 pin_objects_in_section (GCMemSection *section)
2368 int start = section->pin_queue_start;
2369 int end = section->pin_queue_end;
2370 if (start != end) {
2371 int reduced_to;
2372 reduced_to = pin_objects_from_addresses (section, pin_queue + start, pin_queue + end,
2373 section->data, section->next_data);
2374 section->pin_queue_start = start;
2375 section->pin_queue_end = start + reduced_to;
2379 static int
2380 new_gap (int gap)
2382 gap = (gap * 10) / 13;
2383 if (gap == 9 || gap == 10)
2384 return 11;
2385 if (gap < 1)
2386 return 1;
2387 return gap;
2390 #if 0
2391 static int
2392 compare_addr (const void *a, const void *b)
2394 return *(const void **)a - *(const void **)b;
2396 #endif
2398 /* sort the addresses in array in increasing order */
2399 static void
2400 sort_addresses (void **array, int size)
2403 * qsort is slower as predicted.
2404 * qsort (array, size, sizeof (gpointer), compare_addr);
2405 * return;
2407 int gap = size;
2408 int swapped, end;
2409 while (TRUE) {
2410 int i;
2411 gap = new_gap (gap);
2412 swapped = FALSE;
2413 end = size - gap;
2414 for (i = 0; i < end; i++) {
2415 int j = i + gap;
2416 if (array [i] > array [j]) {
2417 void* val = array [i];
2418 array [i] = array [j];
2419 array [j] = val;
2420 swapped = TRUE;
2423 if (gap == 1 && !swapped)
2424 break;
2428 static G_GNUC_UNUSED void
2429 print_nursery_gaps (void* start_nursery, void *end_nursery)
2431 int i;
2432 gpointer first = start_nursery;
2433 gpointer next;
2434 for (i = 0; i < next_pin_slot; ++i) {
2435 next = pin_queue [i];
2436 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %zd\n", first, next, (char*)next-(char*)first);
2437 first = next;
2439 next = end_nursery;
2440 fprintf (gc_debug_file, "Nursery range: %p-%p, size: %zd\n", first, next, (char*)next-(char*)first);
2443 /* reduce the info in the pin queue, removing duplicate pointers and sorting them */
2444 static void
2445 optimize_pin_queue (int start_slot)
2447 void **start, **cur, **end;
2448 /* sort and uniq pin_queue: we just sort and we let the rest discard multiple values */
2449 /* it may be better to keep ranges of pinned memory instead of individually pinning objects */
2450 DEBUG (5, fprintf (gc_debug_file, "Sorting pin queue, size: %d\n", next_pin_slot));
2451 if ((next_pin_slot - start_slot) > 1)
2452 sort_addresses (pin_queue + start_slot, next_pin_slot - start_slot);
2453 start = cur = pin_queue + start_slot;
2454 end = pin_queue + next_pin_slot;
2455 while (cur < end) {
2456 *start = *cur++;
2457 while (*start == *cur && cur < end)
2458 cur++;
2459 start++;
2461 next_pin_slot = start - pin_queue;
2462 DEBUG (5, fprintf (gc_debug_file, "Pin queue reduced to size: %d\n", next_pin_slot));
2463 //DEBUG (6, print_nursery_gaps (start_nursery, end_nursery));
2468 * Scan the memory between start and end and queue values which could be pointers
2469 * to the area between start_nursery and end_nursery for later consideration.
2470 * Typically used for thread stacks.
2472 static void
2473 conservatively_pin_objects_from (void **start, void **end, void *start_nursery, void *end_nursery, int pin_type)
2475 int count = 0;
2476 while (start < end) {
2477 if (*start >= start_nursery && *start < end_nursery) {
2479 * *start can point to the middle of an object
2480 * note: should we handle pointing at the end of an object?
2481 * pinning in C# code disallows pointing at the end of an object
2482 * but there is some small chance that an optimizing C compiler
2483 * may keep the only reference to an object by pointing
2484 * at the end of it. We ignore this small chance for now.
2485 * Pointers to the end of an object are indistinguishable
2486 * from pointers to the start of the next object in memory
2487 * so if we allow that we'd need to pin two objects...
2488 * We queue the pointer in an array, the
2489 * array will then be sorted and uniqued. This way
2490 * we can coalesce several pinning pointers and it should
2491 * be faster since we'd do a memory scan with increasing
2492 * addresses. Note: we can align the address to the allocation
2493 * alignment, so the unique process is more effective.
2495 mword addr = (mword)*start;
2496 addr &= ~(ALLOC_ALIGN - 1);
2497 if (addr >= (mword)start_nursery && addr < (mword)end_nursery)
2498 pin_stage_ptr ((void*)addr);
2499 if (heap_dump_file)
2500 pin_stats_register_address ((char*)addr, pin_type);
2501 DEBUG (6, if (count) fprintf (gc_debug_file, "Pinning address %p\n", (void*)addr));
2502 count++;
2504 start++;
2506 DEBUG (7, if (count) fprintf (gc_debug_file, "found %d potential pinned heap pointers\n", count));
2510 * Debugging function: find in the conservative roots where @obj is being pinned.
2512 static G_GNUC_UNUSED void
2513 find_pinning_reference (char *obj, size_t size)
2515 RootRecord *root;
2516 int i;
2517 char *endobj = obj + size;
2518 for (i = 0; i < roots_hash_size [0]; ++i) {
2519 for (root = roots_hash [0][i]; root; root = root->next) {
2520 /* if desc is non-null it has precise info */
2521 if (!root->root_desc) {
2522 char ** start = (char**)root->start_root;
2523 while (start < (char**)root->end_root) {
2524 if (*start >= obj && *start < endobj) {
2525 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));
2527 start++;
2532 find_pinning_ref_from_thread (obj, size);
2536 * The first thing we do in a collection is to identify pinned objects.
2537 * This function considers all the areas of memory that need to be
2538 * conservatively scanned.
2540 static void
2541 pin_from_roots (void *start_nursery, void *end_nursery)
2543 RootRecord *root;
2544 int i;
2545 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]));
2546 /* objects pinned from the API are inside these roots */
2547 for (i = 0; i < roots_hash_size [ROOT_TYPE_PINNED]; ++i) {
2548 for (root = roots_hash [ROOT_TYPE_PINNED][i]; root; root = root->next) {
2549 DEBUG (6, fprintf (gc_debug_file, "Pinned roots %p-%p\n", root->start_root, root->end_root));
2550 conservatively_pin_objects_from ((void**)root->start_root, (void**)root->end_root, start_nursery, end_nursery, PIN_TYPE_OTHER);
2553 /* now deal with the thread stacks
2554 * in the future we should be able to conservatively scan only:
2555 * *) the cpu registers
2556 * *) the unmanaged stack frames
2557 * *) the _last_ managed stack frame
2558 * *) pointers slots in managed frames
2560 scan_thread_data (start_nursery, end_nursery, FALSE);
2562 evacuate_pin_staging_area ();
2566 * The memory area from start_root to end_root contains pointers to objects.
2567 * Their position is precisely described by @desc (this means that the pointer
2568 * can be either NULL or the pointer to the start of an object).
2569 * This functions copies them to to_space updates them.
2571 static void
2572 precisely_scan_objects_from (CopyOrMarkObjectFunc copy_func, void** start_root, void** end_root, char* n_start, char *n_end, mword desc)
2574 switch (desc & ROOT_DESC_TYPE_MASK) {
2575 case ROOT_DESC_BITMAP:
2576 desc >>= ROOT_DESC_TYPE_SHIFT;
2577 while (desc) {
2578 if ((desc & 1) && *start_root) {
2579 copy_func (start_root);
2580 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", start_root, *start_root));
2581 drain_gray_stack ();
2583 desc >>= 1;
2584 start_root++;
2586 return;
2587 case ROOT_DESC_COMPLEX: {
2588 gsize *bitmap_data = complex_descriptors + (desc >> ROOT_DESC_TYPE_SHIFT);
2589 int bwords = (*bitmap_data) - 1;
2590 void **start_run = start_root;
2591 bitmap_data++;
2592 while (bwords-- > 0) {
2593 gsize bmap = *bitmap_data++;
2594 void **objptr = start_run;
2595 while (bmap) {
2596 if ((bmap & 1) && *objptr) {
2597 copy_func (objptr);
2598 DEBUG (9, fprintf (gc_debug_file, "Overwrote root at %p with %p\n", objptr, *objptr));
2599 drain_gray_stack ();
2601 bmap >>= 1;
2602 ++objptr;
2604 start_run += GC_BITS_PER_WORD;
2606 break;
2608 case ROOT_DESC_USER: {
2609 MonoGCRootMarkFunc marker = user_descriptors [desc >> ROOT_DESC_TYPE_SHIFT];
2610 marker (start_root, copy_func);
2611 break;
2613 case ROOT_DESC_RUN_LEN:
2614 g_assert_not_reached ();
2615 default:
2616 g_assert_not_reached ();
2620 static Fragment*
2621 alloc_fragment (void)
2623 Fragment *frag = fragment_freelist;
2624 if (frag) {
2625 fragment_freelist = frag->next;
2626 frag->next = NULL;
2627 return frag;
2629 frag = get_internal_mem (sizeof (Fragment), INTERNAL_MEM_FRAGMENT);
2630 frag->next = NULL;
2631 return frag;
2634 /* size must be a power of 2 */
2635 static void*
2636 get_os_memory_aligned (mword size, mword alignment, gboolean activate)
2638 /* Allocate twice the memory to be able to put the block on an aligned address */
2639 char *mem = get_os_memory (size + alignment, activate);
2640 char *aligned;
2642 g_assert (mem);
2644 aligned = (char*)((mword)(mem + (alignment - 1)) & ~(alignment - 1));
2645 g_assert (aligned >= mem && aligned + size <= mem + size + alignment && !((mword)aligned & (alignment - 1)));
2647 if (aligned > mem)
2648 free_os_memory (mem, aligned - mem);
2649 if (aligned + size < mem + size + alignment)
2650 free_os_memory (aligned + size, (mem + size + alignment) - (aligned + size));
2652 return aligned;
2656 * Allocate and setup the data structures needed to be able to allocate objects
2657 * in the nursery. The nursery is stored in nursery_section.
2659 static void
2660 alloc_nursery (void)
2662 GCMemSection *section;
2663 char *data;
2664 int scan_starts;
2665 Fragment *frag;
2666 int alloc_size;
2668 if (nursery_section)
2669 return;
2670 DEBUG (2, fprintf (gc_debug_file, "Allocating nursery size: %zd\n", nursery_size));
2671 /* later we will alloc a larger area for the nursery but only activate
2672 * what we need. The rest will be used as expansion if we have too many pinned
2673 * objects in the existing nursery.
2675 /* FIXME: handle OOM */
2676 section = get_internal_mem (SIZEOF_GC_MEM_SECTION, INTERNAL_MEM_SECTION);
2678 g_assert (nursery_size == DEFAULT_NURSERY_SIZE);
2679 alloc_size = nursery_size;
2680 #ifdef ALIGN_NURSERY
2681 data = get_os_memory_aligned (alloc_size, alloc_size, TRUE);
2682 #else
2683 data = get_os_memory (alloc_size, TRUE);
2684 #endif
2685 nursery_start = data;
2686 nursery_real_end = nursery_start + nursery_size;
2687 UPDATE_HEAP_BOUNDARIES (nursery_start, nursery_real_end);
2688 nursery_next = nursery_start;
2689 total_alloc += alloc_size;
2690 DEBUG (4, fprintf (gc_debug_file, "Expanding nursery size (%p-%p): %zd, total: %zd\n", data, data + alloc_size, nursery_size, total_alloc));
2691 section->data = section->next_data = data;
2692 section->size = alloc_size;
2693 section->end_data = nursery_real_end;
2694 scan_starts = (alloc_size + SCAN_START_SIZE - 1) / SCAN_START_SIZE;
2695 section->scan_starts = get_internal_mem (sizeof (char*) * scan_starts, INTERNAL_MEM_SCAN_STARTS);
2696 section->num_scan_start = scan_starts;
2697 section->block.role = MEMORY_ROLE_GEN0;
2698 section->block.next = NULL;
2700 nursery_section = section;
2702 /* Setup the single first large fragment */
2703 frag = alloc_fragment ();
2704 frag->fragment_start = nursery_start;
2705 frag->fragment_limit = nursery_start;
2706 frag->fragment_end = nursery_real_end;
2707 nursery_frag_real_end = nursery_real_end;
2708 /* FIXME: frag here is lost */
2711 static void
2712 scan_finalizer_entries (CopyOrMarkObjectFunc copy_func, FinalizeEntry *list) {
2713 FinalizeEntry *fin;
2715 for (fin = list; fin; fin = fin->next) {
2716 if (!fin->object)
2717 continue;
2718 DEBUG (5, fprintf (gc_debug_file, "Scan of fin ready object: %p (%s)\n", fin->object, safe_name (fin->object)));
2719 copy_func (&fin->object);
2723 static mword fragment_total = 0;
2725 * We found a fragment of free memory in the nursery: memzero it and if
2726 * it is big enough, add it to the list of fragments that can be used for
2727 * allocation.
2729 static void
2730 add_nursery_frag (size_t frag_size, char* frag_start, char* frag_end)
2732 Fragment *fragment;
2733 DEBUG (4, fprintf (gc_debug_file, "Found empty fragment: %p-%p, size: %zd\n", frag_start, frag_end, frag_size));
2734 binary_protocol_empty (frag_start, frag_size);
2735 /* memsetting just the first chunk start is bound to provide better cache locality */
2736 if (nursery_clear_policy == CLEAR_AT_GC)
2737 memset (frag_start, 0, frag_size);
2738 /* Not worth dealing with smaller fragments: need to tune */
2739 if (frag_size >= FRAGMENT_MIN_SIZE) {
2740 fragment = alloc_fragment ();
2741 fragment->fragment_start = frag_start;
2742 fragment->fragment_limit = frag_start;
2743 fragment->fragment_end = frag_end;
2744 fragment->next = nursery_fragments;
2745 nursery_fragments = fragment;
2746 fragment_total += frag_size;
2747 } else {
2748 /* Clear unused fragments, pinning depends on this */
2749 memset (frag_start, 0, frag_size);
2753 static const char*
2754 generation_name (int generation)
2756 switch (generation) {
2757 case GENERATION_NURSERY: return "nursery";
2758 case GENERATION_OLD: return "old";
2759 default: g_assert_not_reached ();
2763 static DisappearingLinkHashTable*
2764 get_dislink_hash_table (int generation)
2766 switch (generation) {
2767 case GENERATION_NURSERY: return &minor_disappearing_link_hash;
2768 case GENERATION_OLD: return &major_disappearing_link_hash;
2769 default: g_assert_not_reached ();
2773 static FinalizeEntryHashTable*
2774 get_finalize_entry_hash_table (int generation)
2776 switch (generation) {
2777 case GENERATION_NURSERY: return &minor_finalizable_hash;
2778 case GENERATION_OLD: return &major_finalizable_hash;
2779 default: g_assert_not_reached ();
2783 static void
2784 finish_gray_stack (char *start_addr, char *end_addr, int generation)
2786 TV_DECLARE (atv);
2787 TV_DECLARE (btv);
2788 int fin_ready;
2789 CopyOrMarkObjectFunc copy_func = current_collection_generation == GENERATION_NURSERY ? copy_object : major_copy_or_mark_object;
2792 * We copied all the reachable objects. Now it's the time to copy
2793 * the objects that were not referenced by the roots, but by the copied objects.
2794 * we built a stack of objects pointed to by gray_start: they are
2795 * additional roots and we may add more items as we go.
2796 * We loop until gray_start == gray_objects which means no more objects have
2797 * been added. Note this is iterative: no recursion is involved.
2798 * We need to walk the LO list as well in search of marked big objects
2799 * (use a flag since this is needed only on major collections). We need to loop
2800 * here as well, so keep a counter of marked LO (increasing it in copy_object).
2801 * To achieve better cache locality and cache usage, we drain the gray stack
2802 * frequently, after each object is copied, and just finish the work here.
2804 drain_gray_stack ();
2805 TV_GETTIME (atv);
2806 DEBUG (2, fprintf (gc_debug_file, "%s generation done\n", generation_name (generation)));
2807 /* walk the finalization queue and move also the objects that need to be
2808 * finalized: use the finalized objects as new roots so the objects they depend
2809 * on are also not reclaimed. As with the roots above, only objects in the nursery
2810 * are marked/copied.
2811 * We need a loop here, since objects ready for finalizers may reference other objects
2812 * that are fin-ready. Speedup with a flag?
2814 do {
2815 fin_ready = num_ready_finalizers;
2816 finalize_in_range (copy_func, start_addr, end_addr, generation);
2817 if (generation == GENERATION_OLD)
2818 finalize_in_range (copy_func, nursery_start, nursery_real_end, GENERATION_NURSERY);
2820 /* drain the new stack that might have been created */
2821 DEBUG (6, fprintf (gc_debug_file, "Precise scan of gray area post fin\n"));
2822 drain_gray_stack ();
2823 } while (fin_ready != num_ready_finalizers);
2824 TV_GETTIME (btv);
2825 DEBUG (2, fprintf (gc_debug_file, "Finalize queue handling scan for %s generation: %d usecs\n", generation_name (generation), TV_ELAPSED (atv, btv)));
2828 * handle disappearing links
2829 * Note we do this after checking the finalization queue because if an object
2830 * survives (at least long enough to be finalized) we don't clear the link.
2831 * This also deals with a possible issue with the monitor reclamation: with the Boehm
2832 * GC a finalized object my lose the monitor because it is cleared before the finalizer is
2833 * called.
2835 g_assert (gray_object_queue_is_empty ());
2836 for (;;) {
2837 null_link_in_range (copy_func, start_addr, end_addr, generation);
2838 if (generation == GENERATION_OLD)
2839 null_link_in_range (copy_func, start_addr, end_addr, GENERATION_NURSERY);
2840 if (gray_object_queue_is_empty ())
2841 break;
2842 drain_gray_stack ();
2845 g_assert (gray_object_queue_is_empty ());
2848 static void
2849 check_section_scan_starts (GCMemSection *section)
2851 int i;
2852 for (i = 0; i < section->num_scan_start; ++i) {
2853 if (section->scan_starts [i]) {
2854 guint size = safe_object_get_size ((MonoObject*) section->scan_starts [i]);
2855 g_assert (size >= sizeof (MonoObject) && size <= MAX_SMALL_OBJ_SIZE);
2860 static void
2861 check_scan_starts (void)
2863 if (!do_scan_starts_check)
2864 return;
2865 check_section_scan_starts (nursery_section);
2866 major_check_scan_starts ();
2869 static int last_num_pinned = 0;
2871 static void
2872 build_nursery_fragments (int start_pin, int end_pin)
2874 char *frag_start, *frag_end;
2875 size_t frag_size;
2876 int i;
2878 while (nursery_fragments) {
2879 Fragment *next = nursery_fragments->next;
2880 nursery_fragments->next = fragment_freelist;
2881 fragment_freelist = nursery_fragments;
2882 nursery_fragments = next;
2884 frag_start = nursery_start;
2885 fragment_total = 0;
2886 /* clear scan starts */
2887 memset (nursery_section->scan_starts, 0, nursery_section->num_scan_start * sizeof (gpointer));
2888 for (i = start_pin; i < end_pin; ++i) {
2889 frag_end = pin_queue [i];
2890 /* remove the pin bit from pinned objects */
2891 unpin_object (frag_end);
2892 nursery_section->scan_starts [((char*)frag_end - (char*)nursery_section->data)/SCAN_START_SIZE] = frag_end;
2893 frag_size = frag_end - frag_start;
2894 if (frag_size)
2895 add_nursery_frag (frag_size, frag_start, frag_end);
2896 frag_size = safe_object_get_size ((MonoObject*)pin_queue [i]);
2897 frag_size += ALLOC_ALIGN - 1;
2898 frag_size &= ~(ALLOC_ALIGN - 1);
2899 frag_start = (char*)pin_queue [i] + frag_size;
2901 nursery_last_pinned_end = frag_start;
2902 frag_end = nursery_real_end;
2903 frag_size = frag_end - frag_start;
2904 if (frag_size)
2905 add_nursery_frag (frag_size, frag_start, frag_end);
2906 if (!nursery_fragments) {
2907 DEBUG (1, fprintf (gc_debug_file, "Nursery fully pinned (%d)\n", end_pin - start_pin));
2908 for (i = start_pin; i < end_pin; ++i) {
2909 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])));
2911 degraded_mode = 1;
2914 nursery_next = nursery_frag_real_end = NULL;
2916 /* Clear TLABs for all threads */
2917 clear_tlabs ();
2920 static void
2921 scan_from_registered_roots (CopyOrMarkObjectFunc copy_func, char *addr_start, char *addr_end, int root_type)
2923 int i;
2924 RootRecord *root;
2925 for (i = 0; i < roots_hash_size [root_type]; ++i) {
2926 for (root = roots_hash [root_type][i]; root; root = root->next) {
2927 DEBUG (6, fprintf (gc_debug_file, "Precise root scan %p-%p (desc: %p)\n", root->start_root, root->end_root, (void*)root->root_desc));
2928 precisely_scan_objects_from (copy_func, (void**)root->start_root, (void**)root->end_root, addr_start, addr_end, root->root_desc);
2933 static void
2934 dump_occupied (char *start, char *end, char *section_start)
2936 fprintf (heap_dump_file, "<occupied offset=\"%zd\" size=\"%zd\"/>\n", start - section_start, end - start);
2939 static void
2940 dump_section (GCMemSection *section, const char *type)
2942 char *start = section->data;
2943 char *end = section->data + section->size;
2944 char *occ_start = NULL;
2945 GCVTable *vt;
2946 char *old_start = NULL; /* just for debugging */
2948 fprintf (heap_dump_file, "<section type=\"%s\" size=\"%zu\">\n", type, section->size);
2950 while (start < end) {
2951 guint size;
2952 MonoClass *class;
2954 if (!*(void**)start) {
2955 if (occ_start) {
2956 dump_occupied (occ_start, start, section->data);
2957 occ_start = NULL;
2959 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
2960 continue;
2962 g_assert (start < section->next_data);
2964 if (!occ_start)
2965 occ_start = start;
2967 vt = (GCVTable*)LOAD_VTABLE (start);
2968 class = vt->klass;
2970 size = safe_object_get_size ((MonoObject*) start);
2971 size += ALLOC_ALIGN - 1;
2972 size &= ~(ALLOC_ALIGN - 1);
2975 fprintf (heap_dump_file, "<object offset=\"%d\" class=\"%s.%s\" size=\"%d\"/>\n",
2976 start - section->data,
2977 vt->klass->name_space, vt->klass->name,
2978 size);
2981 old_start = start;
2982 start += size;
2984 if (occ_start)
2985 dump_occupied (occ_start, start, section->data);
2987 fprintf (heap_dump_file, "</section>\n");
2990 static void
2991 dump_object (MonoObject *obj, gboolean dump_location)
2993 static char class_name [1024];
2995 MonoClass *class = mono_object_class (obj);
2996 int i, j;
2999 * Python's XML parser is too stupid to parse angle brackets
3000 * in strings, so we just ignore them;
3002 i = j = 0;
3003 while (class->name [i] && j < sizeof (class_name) - 1) {
3004 if (!strchr ("<>\"", class->name [i]))
3005 class_name [j++] = class->name [i];
3006 ++i;
3008 g_assert (j < sizeof (class_name));
3009 class_name [j] = 0;
3011 fprintf (heap_dump_file, "<object class=\"%s.%s\" size=\"%d\"",
3012 class->name_space, class_name,
3013 safe_object_get_size (obj));
3014 if (dump_location) {
3015 const char *location;
3016 if (ptr_in_nursery (obj))
3017 location = "nursery";
3018 else if (safe_object_get_size (obj) <= MAX_SMALL_OBJ_SIZE)
3019 location = "major";
3020 else
3021 location = "LOS";
3022 fprintf (heap_dump_file, " location=\"%s\"", location);
3024 fprintf (heap_dump_file, "/>\n");
3027 static void
3028 dump_heap (const char *type, int num, const char *reason)
3030 static char const *internal_mem_names [] = { "pin-queue", "fragment", "section", "scan-starts",
3031 "fin-table", "finalize-entry", "dislink-table",
3032 "dislink", "roots-table", "root-record", "statistics",
3033 "remset", "gray-queue", "store-remset", "marksweep-tables",
3034 "marksweep-block-info" };
3036 ObjectList *list;
3037 LOSObject *bigobj;
3038 int i;
3040 fprintf (heap_dump_file, "<collection type=\"%s\" num=\"%d\"", type, num);
3041 if (reason)
3042 fprintf (heap_dump_file, " reason=\"%s\"", reason);
3043 fprintf (heap_dump_file, ">\n");
3044 fprintf (heap_dump_file, "<other-mem-usage type=\"pinned-chunks\" size=\"%ld\"/>\n", pinned_chunk_bytes_alloced);
3045 fprintf (heap_dump_file, "<other-mem-usage type=\"large-internal\" size=\"%ld\"/>\n", large_internal_bytes_alloced);
3046 fprintf (heap_dump_file, "<other-mem-usage type=\"mempools\" size=\"%ld\"/>\n", mono_mempool_get_bytes_allocated ());
3047 for (i = 0; i < INTERNAL_MEM_MAX; ++i)
3048 fprintf (heap_dump_file, "<other-mem-usage type=\"%s\" size=\"%ld\"/>\n", internal_mem_names [i], small_internal_mem_bytes [i]);
3049 fprintf (heap_dump_file, "<pinned type=\"stack\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_STACK]);
3050 /* fprintf (heap_dump_file, "<pinned type=\"static-data\" bytes=\"%d\"/>\n", pinned_byte_counts [PIN_TYPE_STATIC_DATA]); */
3051 fprintf (heap_dump_file, "<pinned type=\"other\" bytes=\"%zu\"/>\n", pinned_byte_counts [PIN_TYPE_OTHER]);
3053 fprintf (heap_dump_file, "<pinned-objects>\n");
3054 for (list = pinned_objects; list; list = list->next)
3055 dump_object (list->obj, TRUE);
3056 fprintf (heap_dump_file, "</pinned-objects>\n");
3058 dump_section (nursery_section, "nursery");
3060 major_dump_heap ();
3062 fprintf (heap_dump_file, "<los>\n");
3063 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next)
3064 dump_object ((MonoObject*)bigobj->data, FALSE);
3065 fprintf (heap_dump_file, "</los>\n");
3067 fprintf (heap_dump_file, "</collection>\n");
3070 static void
3071 init_stats (void)
3073 static gboolean inited = FALSE;
3075 if (inited)
3076 return;
3078 mono_counters_register ("Minor fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pre_collection_fragment_clear);
3079 mono_counters_register ("Minor pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_pinning);
3080 mono_counters_register ("Minor scan remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_remsets);
3081 mono_counters_register ("Minor scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_pinned);
3082 mono_counters_register ("Minor scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_registered_roots);
3083 mono_counters_register ("Minor scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_scan_thread_data);
3084 mono_counters_register ("Minor finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_finish_gray_stack);
3085 mono_counters_register ("Minor fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_minor_fragment_creation);
3087 mono_counters_register ("Major fragment clear", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pre_collection_fragment_clear);
3088 mono_counters_register ("Major pinning", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_pinning);
3089 mono_counters_register ("Major scan pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_pinned);
3090 mono_counters_register ("Major scan registered roots", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_registered_roots);
3091 mono_counters_register ("Major scan thread data", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_thread_data);
3092 mono_counters_register ("Major scan alloc_pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_alloc_pinned);
3093 mono_counters_register ("Major scan finalized", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_finalized);
3094 mono_counters_register ("Major scan big objects", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_scan_big_objects);
3095 mono_counters_register ("Major finish gray stack", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_finish_gray_stack);
3096 mono_counters_register ("Major sweep", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_sweep);
3097 mono_counters_register ("Major fragment creation", MONO_COUNTER_GC | MONO_COUNTER_LONG, &time_major_fragment_creation);
3099 #ifdef HEAVY_STATISTICS
3100 mono_counters_register ("WBarrier set field", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_field);
3101 mono_counters_register ("WBarrier set arrayref", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_arrayref);
3102 mono_counters_register ("WBarrier arrayref copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_arrayref_copy);
3103 mono_counters_register ("WBarrier generic store called", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store);
3104 mono_counters_register ("WBarrier generic store stored", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_generic_store_remset);
3105 mono_counters_register ("WBarrier set root", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_set_root);
3106 mono_counters_register ("WBarrier value copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_value_copy);
3107 mono_counters_register ("WBarrier object copy", MONO_COUNTER_GC | MONO_COUNTER_INT, &stat_wbarrier_object_copy);
3109 mono_counters_register ("# objects allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced);
3110 mono_counters_register ("bytes allocated", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced);
3111 mono_counters_register ("# objects allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_alloced_degraded);
3112 mono_counters_register ("bytes allocated degraded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_degraded);
3113 mono_counters_register ("bytes allocated in LOS", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_bytes_alloced_los);
3115 mono_counters_register ("# copy_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_nursery);
3116 mono_counters_register ("# objects copied (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_nursery);
3117 mono_counters_register ("# copy_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_copy_object_called_major);
3118 mono_counters_register ("# objects copied (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_objects_copied_major);
3120 mono_counters_register ("# scan_object() called (nursery)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_nursery);
3121 mono_counters_register ("# scan_object() called (major)", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_scan_object_called_major);
3123 mono_counters_register ("# nursery copy_object() failed from space", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_from_space);
3124 mono_counters_register ("# nursery copy_object() failed forwarded", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_forwarded);
3125 mono_counters_register ("# nursery copy_object() failed pinned", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_nursery_copy_object_failed_pinned);
3127 mono_counters_register ("Store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets);
3128 mono_counters_register ("Unique store remsets", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_store_remsets_unique);
3129 mono_counters_register ("Saved remsets 1", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_1);
3130 mono_counters_register ("Saved remsets 2", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_saved_remsets_2);
3131 mono_counters_register ("Global remsets added", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_added);
3132 mono_counters_register ("Global remsets processed", MONO_COUNTER_GC | MONO_COUNTER_LONG, &stat_global_remsets_processed);
3133 #endif
3135 inited = TRUE;
3139 * Collect objects in the nursery. Returns whether to trigger a major
3140 * collection.
3142 static gboolean
3143 collect_nursery (size_t requested_size)
3145 size_t max_garbage_amount;
3146 char *orig_nursery_next;
3147 TV_DECLARE (all_atv);
3148 TV_DECLARE (all_btv);
3149 TV_DECLARE (atv);
3150 TV_DECLARE (btv);
3152 current_collection_generation = GENERATION_NURSERY;
3154 init_stats ();
3155 binary_protocol_collection (GENERATION_NURSERY);
3156 check_scan_starts ();
3158 degraded_mode = 0;
3159 orig_nursery_next = nursery_next;
3160 nursery_next = MAX (nursery_next, nursery_last_pinned_end);
3161 /* FIXME: optimize later to use the higher address where an object can be present */
3162 nursery_next = MAX (nursery_next, nursery_real_end);
3164 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)));
3165 max_garbage_amount = nursery_next - nursery_start;
3166 g_assert (nursery_section->size >= max_garbage_amount);
3168 /* world must be stopped already */
3169 TV_GETTIME (all_atv);
3170 TV_GETTIME (atv);
3172 /* Pinning depends on this */
3173 clear_nursery_fragments (orig_nursery_next);
3175 TV_GETTIME (btv);
3176 time_minor_pre_collection_fragment_clear += TV_ELAPSED_MS (atv, btv);
3178 if (xdomain_checks)
3179 check_for_xdomain_refs ();
3181 nursery_section->next_data = nursery_next;
3183 major_start_nursery_collection ();
3185 gray_object_queue_init ();
3187 num_minor_gcs++;
3188 mono_stats.minor_gc_count ++;
3189 /* pin from pinned handles */
3190 init_pinning ();
3191 pin_from_roots (nursery_start, nursery_next);
3192 /* identify pinned objects */
3193 optimize_pin_queue (0);
3194 next_pin_slot = pin_objects_from_addresses (nursery_section, pin_queue, pin_queue + next_pin_slot, nursery_start, nursery_next);
3195 nursery_section->pin_queue_start = 0;
3196 nursery_section->pin_queue_end = next_pin_slot;
3197 TV_GETTIME (atv);
3198 time_minor_pinning += TV_ELAPSED_MS (btv, atv);
3199 DEBUG (2, fprintf (gc_debug_file, "Finding pinned pointers: %d in %d usecs\n", next_pin_slot, TV_ELAPSED (btv, atv)));
3200 DEBUG (4, fprintf (gc_debug_file, "Start scan with %d pinned objects\n", next_pin_slot));
3202 if (consistency_check_at_minor_collection)
3203 check_consistency ();
3206 * walk all the roots and copy the young objects to the old generation,
3207 * starting from to_space
3210 scan_from_remsets (nursery_start, nursery_next);
3211 /* we don't have complete write barrier yet, so we scan all the old generation sections */
3212 TV_GETTIME (btv);
3213 time_minor_scan_remsets += TV_ELAPSED_MS (atv, btv);
3214 DEBUG (2, fprintf (gc_debug_file, "Old generation scan: %d usecs\n", TV_ELAPSED (atv, btv)));
3216 drain_gray_stack ();
3218 TV_GETTIME (atv);
3219 time_minor_scan_pinned += TV_ELAPSED_MS (btv, atv);
3220 /* registered roots, this includes static fields */
3221 scan_from_registered_roots (copy_object, nursery_start, nursery_next, ROOT_TYPE_NORMAL);
3222 scan_from_registered_roots (copy_object, nursery_start, nursery_next, ROOT_TYPE_WBARRIER);
3223 TV_GETTIME (btv);
3224 time_minor_scan_registered_roots += TV_ELAPSED_MS (atv, btv);
3225 /* thread data */
3226 scan_thread_data (nursery_start, nursery_next, TRUE);
3227 TV_GETTIME (atv);
3228 time_minor_scan_thread_data += TV_ELAPSED_MS (btv, atv);
3229 btv = atv;
3231 finish_gray_stack (nursery_start, nursery_next, GENERATION_NURSERY);
3232 TV_GETTIME (atv);
3233 time_minor_finish_gray_stack += TV_ELAPSED_MS (btv, atv);
3235 /* walk the pin_queue, build up the fragment list of free memory, unmark
3236 * pinned objects as we go, memzero() the empty fragments so they are ready for the
3237 * next allocations.
3239 build_nursery_fragments (0, next_pin_slot);
3240 TV_GETTIME (btv);
3241 time_minor_fragment_creation += TV_ELAPSED_MS (atv, btv);
3242 DEBUG (2, fprintf (gc_debug_file, "Fragment creation: %d usecs, %zd bytes available\n", TV_ELAPSED (atv, btv), fragment_total));
3244 major_finish_nursery_collection ();
3246 TV_GETTIME (all_btv);
3247 mono_stats.minor_gc_time_usecs += TV_ELAPSED (all_atv, all_btv);
3249 if (heap_dump_file)
3250 dump_heap ("minor", num_minor_gcs - 1, NULL);
3252 /* prepare the pin queue for the next collection */
3253 last_num_pinned = next_pin_slot;
3254 next_pin_slot = 0;
3255 if (fin_ready_list || critical_fin_list) {
3256 DEBUG (4, fprintf (gc_debug_file, "Finalizer-thread wakeup: ready %d\n", num_ready_finalizers));
3257 mono_gc_finalize_notify ();
3259 pin_stats_reset ();
3261 g_assert (gray_object_queue_is_empty ());
3263 check_scan_starts ();
3265 current_collection_generation = -1;
3267 return major_need_major_collection ();
3270 static void
3271 major_collection (const char *reason)
3273 if (g_getenv ("MONO_GC_NO_MAJOR")) {
3274 collect_nursery (0);
3275 return;
3278 current_collection_generation = GENERATION_OLD;
3279 major_do_collection (reason);
3280 current_collection_generation = -1;
3284 * When deciding if it's better to collect or to expand, keep track
3285 * of how much garbage was reclaimed with the last collection: if it's too
3286 * little, expand.
3287 * This is called when we could not allocate a small object.
3289 static void __attribute__((noinline))
3290 minor_collect_or_expand_inner (size_t size)
3292 int do_minor_collection = 1;
3294 if (!nursery_section) {
3295 alloc_nursery ();
3296 return;
3298 if (do_minor_collection) {
3299 stop_world ();
3300 if (collect_nursery (size))
3301 major_collection ("minor overflow");
3302 DEBUG (2, fprintf (gc_debug_file, "Heap size: %zd, LOS size: %zd\n", total_alloc, los_memory_usage));
3303 restart_world ();
3304 /* this also sets the proper pointers for the next allocation */
3305 if (!search_fragment_for_size (size)) {
3306 int i;
3307 /* TypeBuilder and MonoMethod are killing mcs with fragmentation */
3308 DEBUG (1, fprintf (gc_debug_file, "nursery collection didn't find enough room for %zd alloc (%d pinned)\n", size, last_num_pinned));
3309 for (i = 0; i < last_num_pinned; ++i) {
3310 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])));
3312 degraded_mode = 1;
3315 //report_internal_mem_usage ();
3319 * ######################################################################
3320 * ######## Memory allocation from the OS
3321 * ######################################################################
3322 * This section of code deals with getting memory from the OS and
3323 * allocating memory for GC-internal data structures.
3324 * Internal memory can be handled with a freelist for small objects.
3328 * Allocate a big chunk of memory from the OS (usually 64KB to several megabytes).
3329 * This must not require any lock.
3331 static void*
3332 get_os_memory (size_t size, int activate)
3334 void *ptr;
3335 unsigned long prot_flags = activate? MONO_MMAP_READ|MONO_MMAP_WRITE: MONO_MMAP_NONE;
3337 prot_flags |= MONO_MMAP_PRIVATE | MONO_MMAP_ANON;
3338 size += pagesize - 1;
3339 size &= ~(pagesize - 1);
3340 ptr = mono_valloc (0, size, prot_flags);
3341 return ptr;
3345 * Free the memory returned by get_os_memory (), returning it to the OS.
3347 static void
3348 free_os_memory (void *addr, size_t size)
3350 mono_vfree (addr, size);
3354 * Debug reporting.
3356 static void
3357 report_pinned_chunk (PinnedChunk *chunk, int seq) {
3358 void **p;
3359 int i, free_pages, num_free, free_mem;
3360 free_pages = 0;
3361 for (i = 0; i < chunk->num_pages; ++i) {
3362 if (!chunk->page_sizes [i])
3363 free_pages++;
3365 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);
3366 free_mem = FREELIST_PAGESIZE * free_pages;
3367 for (i = 0; i < FREELIST_NUM_SLOTS; ++i) {
3368 if (!chunk->free_list [i])
3369 continue;
3370 num_free = 0;
3371 p = chunk->free_list [i];
3372 while (p) {
3373 num_free++;
3374 p = *p;
3376 printf ("\tfree list of size %d, %d items\n", freelist_sizes [i], num_free);
3377 free_mem += freelist_sizes [i] * num_free;
3379 printf ("\tfree memory in chunk: %d\n", free_mem);
3383 * Debug reporting.
3385 static G_GNUC_UNUSED void
3386 report_internal_mem_usage (void) {
3387 PinnedChunk *chunk;
3388 int i;
3389 printf ("Internal memory usage:\n");
3390 i = 0;
3391 for (chunk = internal_chunk_list; chunk; chunk = chunk->block.next) {
3392 report_pinned_chunk (chunk, i++);
3394 printf ("Pinned memory usage:\n");
3395 major_report_pinned_memory_usage ();
3399 * Find the slot number in the freelist for memory chunks that
3400 * can contain @size objects.
3402 static int
3403 slot_for_size (size_t size)
3405 int slot;
3406 /* do a binary search or lookup table later. */
3407 for (slot = 0; slot < FREELIST_NUM_SLOTS; ++slot) {
3408 if (freelist_sizes [slot] >= size)
3409 return slot;
3411 g_assert_not_reached ();
3412 return -1;
3416 * Build a free list for @size memory chunks from the memory area between
3417 * start_page and end_page.
3419 static void
3420 build_freelist (PinnedChunk *chunk, int slot, int size, char *start_page, char *end_page)
3422 void **p, **end;
3423 int count = 0;
3424 /*g_print ("building freelist for slot %d, size %d in %p\n", slot, size, chunk);*/
3425 p = (void**)start_page;
3426 end = (void**)(end_page - size);
3427 g_assert (!chunk->free_list [slot]);
3428 chunk->free_list [slot] = p;
3429 while ((char*)p + size <= (char*)end) {
3430 count++;
3431 *p = (void*)((char*)p + size);
3432 p = *p;
3434 *p = NULL;
3435 /*g_print ("%d items created, max: %d\n", count, (end_page - start_page) / size);*/
3438 static PinnedChunk*
3439 alloc_pinned_chunk (void)
3441 PinnedChunk *chunk;
3442 int offset;
3443 int size = PINNED_CHUNK_SIZE;
3445 chunk = get_os_memory_aligned (size, size, TRUE);
3446 chunk->block.role = MEMORY_ROLE_PINNED;
3448 UPDATE_HEAP_BOUNDARIES (chunk, ((char*)chunk + size));
3449 total_alloc += size;
3450 pinned_chunk_bytes_alloced += size;
3452 /* setup the bookeeping fields */
3453 chunk->num_pages = size / FREELIST_PAGESIZE;
3454 offset = G_STRUCT_OFFSET (PinnedChunk, data);
3455 chunk->page_sizes = (void*)((char*)chunk + offset);
3456 offset += sizeof (int) * chunk->num_pages;
3457 offset += ALLOC_ALIGN - 1;
3458 offset &= ~(ALLOC_ALIGN - 1);
3459 chunk->free_list = (void*)((char*)chunk + offset);
3460 offset += sizeof (void*) * FREELIST_NUM_SLOTS;
3461 offset += ALLOC_ALIGN - 1;
3462 offset &= ~(ALLOC_ALIGN - 1);
3463 chunk->start_data = (void*)((char*)chunk + offset);
3465 /* allocate the first page to the freelist */
3466 chunk->page_sizes [0] = PINNED_FIRST_SLOT_SIZE;
3467 build_freelist (chunk, slot_for_size (PINNED_FIRST_SLOT_SIZE), PINNED_FIRST_SLOT_SIZE, chunk->start_data, ((char*)chunk + FREELIST_PAGESIZE));
3468 DEBUG (4, fprintf (gc_debug_file, "Allocated pinned chunk %p, size: %d\n", chunk, size));
3469 return chunk;
3472 /* assumes freelist for slot is empty, so try to alloc a new page */
3473 static void*
3474 get_chunk_freelist (PinnedChunk *chunk, int slot)
3476 int i;
3477 void **p;
3478 p = chunk->free_list [slot];
3479 if (p) {
3480 chunk->free_list [slot] = *p;
3481 return p;
3483 for (i = 0; i < chunk->num_pages; ++i) {
3484 int size;
3485 if (chunk->page_sizes [i])
3486 continue;
3487 size = freelist_sizes [slot];
3488 chunk->page_sizes [i] = size;
3489 build_freelist (chunk, slot, size, (char*)chunk + FREELIST_PAGESIZE * i, (char*)chunk + FREELIST_PAGESIZE * (i + 1));
3490 break;
3492 /* try again */
3493 p = chunk->free_list [slot];
3494 if (p) {
3495 chunk->free_list [slot] = *p;
3496 return p;
3498 return NULL;
3501 /* used for the GC-internal data structures */
3502 static void*
3503 get_internal_mem (size_t size, int type)
3505 int slot;
3506 void *res = NULL;
3507 PinnedChunk *pchunk;
3509 if (size > freelist_sizes [FREELIST_NUM_SLOTS - 1]) {
3510 LargeInternalMemHeader *mh;
3512 size += sizeof (LargeInternalMemHeader);
3513 mh = get_os_memory (size, TRUE);
3514 mh->magic = LARGE_INTERNAL_MEM_HEADER_MAGIC;
3515 mh->size = size;
3517 large_internal_bytes_alloced += size;
3519 return mh->data;
3522 slot = slot_for_size (size);
3523 g_assert (size <= freelist_sizes [slot]);
3525 small_internal_mem_bytes [type] += freelist_sizes [slot];
3527 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3528 void **p = pchunk->free_list [slot];
3529 if (p) {
3530 pchunk->free_list [slot] = *p;
3531 memset (p, 0, size);
3532 return p;
3535 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3536 res = get_chunk_freelist (pchunk, slot);
3537 if (res) {
3538 memset (res, 0, size);
3539 return res;
3542 pchunk = alloc_pinned_chunk ();
3543 /* FIXME: handle OOM */
3544 pchunk->block.next = internal_chunk_list;
3545 internal_chunk_list = pchunk;
3546 res = get_chunk_freelist (pchunk, slot);
3547 memset (res, 0, size);
3548 return res;
3551 static void
3552 free_internal_mem (void *addr, int type)
3554 PinnedChunk *pchunk;
3555 LargeInternalMemHeader *mh;
3556 if (!addr)
3557 return;
3558 for (pchunk = internal_chunk_list; pchunk; pchunk = pchunk->block.next) {
3559 /*printf ("trying to free %p in %p (pages: %d)\n", addr, pchunk, pchunk->num_pages);*/
3560 if (addr >= (void*)pchunk && (char*)addr < (char*)pchunk + pchunk->num_pages * FREELIST_PAGESIZE) {
3561 int offset = (char*)addr - (char*)pchunk;
3562 int page = offset / FREELIST_PAGESIZE;
3563 int slot = slot_for_size (pchunk->page_sizes [page]);
3564 void **p = addr;
3565 *p = pchunk->free_list [slot];
3566 pchunk->free_list [slot] = p;
3568 small_internal_mem_bytes [type] -= freelist_sizes [slot];
3570 return;
3573 mh = (LargeInternalMemHeader*)((char*)addr - G_STRUCT_OFFSET (LargeInternalMemHeader, data));
3574 g_assert (mh->magic == LARGE_INTERNAL_MEM_HEADER_MAGIC);
3575 large_internal_bytes_alloced -= mh->size;
3576 free_os_memory (mh, mh->size);
3580 * ######################################################################
3581 * ######## Object allocation
3582 * ######################################################################
3583 * This section of code deals with allocating memory for objects.
3584 * There are several ways:
3585 * *) allocate large objects
3586 * *) allocate normal objects
3587 * *) fast lock-free allocation
3588 * *) allocation of pinned objects
3591 static void
3592 free_large_object (LOSObject *obj)
3594 size_t size = obj->size;
3595 DEBUG (4, fprintf (gc_debug_file, "Freed large object %p, size %zd\n", obj->data, obj->size));
3596 binary_protocol_empty (obj->data, obj->size);
3598 los_memory_usage -= size;
3599 size += sizeof (LOSObject);
3600 size += pagesize - 1;
3601 size &= ~(pagesize - 1);
3602 total_alloc -= size;
3603 los_num_objects--;
3604 free_os_memory (obj, size);
3608 * Objects with size >= 64KB are allocated in the large object space.
3609 * They are currently kept track of with a linked list.
3610 * They don't move, so there is no need to pin them during collection
3611 * and we avoid the memcpy overhead.
3613 static void* __attribute__((noinline))
3614 alloc_large_inner (MonoVTable *vtable, size_t size)
3616 LOSObject *obj;
3617 void **vtslot;
3618 size_t alloc_size;
3620 g_assert (size > MAX_SMALL_OBJ_SIZE);
3622 if (los_memory_usage > next_los_collection) {
3623 static mword last_los_memory_usage = 0;
3625 mword los_memory_alloced;
3626 mword old_los_memory_usage;
3627 mword los_memory_saved;
3628 mword save_target;
3629 mword allowance_target;
3630 mword allowance;
3632 DEBUG (4, fprintf (gc_debug_file, "Should trigger major collection: req size %zd (los already: %zu, limit: %zu)\n", size, los_memory_usage, next_los_collection));
3633 stop_world ();
3635 g_assert (los_memory_usage >= last_los_memory_usage);
3636 los_memory_alloced = los_memory_usage - last_los_memory_usage;
3637 old_los_memory_usage = los_memory_usage;
3639 major_collection ("LOS overflow");
3641 los_memory_saved = MAX (old_los_memory_usage - los_memory_usage, 1);
3642 save_target = los_memory_usage / 2;
3644 * see the comment at the end of major_collection()
3645 * for the explanation for this calculation.
3647 allowance_target = (mword)((double)save_target * (double)los_memory_alloced / (double)los_memory_saved);
3648 allowance = MAX (MIN (allowance_target, los_memory_usage), MIN_LOS_ALLOWANCE);
3649 next_los_collection = los_memory_usage + allowance;
3651 last_los_memory_usage = los_memory_usage;
3653 restart_world ();
3655 alloc_size = size;
3656 alloc_size += sizeof (LOSObject);
3657 alloc_size += pagesize - 1;
3658 alloc_size &= ~(pagesize - 1);
3659 /* FIXME: handle OOM */
3660 obj = get_os_memory (alloc_size, TRUE);
3661 g_assert (!((mword)obj->data & (ALLOC_ALIGN - 1)));
3662 obj->size = size;
3663 vtslot = (void**)obj->data;
3664 *vtslot = vtable;
3665 total_alloc += alloc_size;
3666 UPDATE_HEAP_BOUNDARIES (obj->data, (char*)obj->data + size);
3667 obj->next = los_object_list;
3668 los_object_list = obj;
3669 los_memory_usage += size;
3670 los_num_objects++;
3671 DEBUG (4, fprintf (gc_debug_file, "Allocated large object %p, vtable: %p (%s), size: %zd\n", obj->data, vtable, vtable->klass->name, size));
3672 binary_protocol_alloc (obj->data, vtable, size);
3673 return obj->data;
3676 /* check if we have a suitable fragment in nursery_fragments to be able to allocate
3677 * an object of size @size
3678 * Return FALSE if not found (which means we need a collection)
3680 static gboolean
3681 search_fragment_for_size (size_t size)
3683 Fragment *frag, *prev;
3684 DEBUG (4, fprintf (gc_debug_file, "Searching nursery fragment %p, size: %zd\n", nursery_frag_real_end, size));
3686 if (nursery_frag_real_end > nursery_next && nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
3687 /* Clear the remaining space, pinning depends on this */
3688 memset (nursery_next, 0, nursery_frag_real_end - nursery_next);
3690 prev = NULL;
3691 for (frag = nursery_fragments; frag; frag = frag->next) {
3692 if (size <= (frag->fragment_end - frag->fragment_start)) {
3693 /* remove from the list */
3694 if (prev)
3695 prev->next = frag->next;
3696 else
3697 nursery_fragments = frag->next;
3698 nursery_next = frag->fragment_start;
3699 nursery_frag_real_end = frag->fragment_end;
3701 DEBUG (4, fprintf (gc_debug_file, "Using nursery fragment %p-%p, size: %zd (req: %zd)\n", nursery_next, nursery_frag_real_end, nursery_frag_real_end - nursery_next, size));
3702 frag->next = fragment_freelist;
3703 fragment_freelist = frag;
3704 return TRUE;
3706 prev = frag;
3708 return FALSE;
3712 * Provide a variant that takes just the vtable for small fixed-size objects.
3713 * The aligned size is already computed and stored in vt->gc_descr.
3714 * Note: every SCAN_START_SIZE or so we are given the chance to do some special
3715 * processing. We can keep track of where objects start, for example,
3716 * so when we scan the thread stacks for pinned objects, we can start
3717 * a search for the pinned object in SCAN_START_SIZE chunks.
3719 static void*
3720 mono_gc_alloc_obj_nolock (MonoVTable *vtable, size_t size)
3722 /* FIXME: handle OOM */
3723 void **p;
3724 char *new_next;
3725 gboolean res;
3726 TLAB_ACCESS_INIT;
3728 HEAVY_STAT (++stat_objects_alloced);
3729 if (size <= MAX_SMALL_OBJ_SIZE)
3730 HEAVY_STAT (stat_bytes_alloced += size);
3731 else
3732 HEAVY_STAT (stat_bytes_alloced_los += size);
3734 size += ALLOC_ALIGN - 1;
3735 size &= ~(ALLOC_ALIGN - 1);
3737 g_assert (vtable->gc_descr);
3739 if (G_UNLIKELY (collect_before_allocs)) {
3740 if (nursery_section) {
3741 stop_world ();
3742 collect_nursery (0);
3743 restart_world ();
3744 if (!degraded_mode && !search_fragment_for_size (size)) {
3745 // FIXME:
3746 g_assert_not_reached ();
3752 * We must already have the lock here instead of after the
3753 * fast path because we might be interrupted in the fast path
3754 * (after confirming that new_next < TLAB_TEMP_END) by the GC,
3755 * and we'll end up allocating an object in a fragment which
3756 * no longer belongs to us.
3758 * The managed allocator does not do this, but it's treated
3759 * specially by the world-stopping code.
3762 if (size > MAX_SMALL_OBJ_SIZE) {
3763 p = alloc_large_inner (vtable, size);
3764 } else {
3765 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
3767 p = (void**)TLAB_NEXT;
3768 /* FIXME: handle overflow */
3769 new_next = (char*)p + size;
3770 TLAB_NEXT = new_next;
3772 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
3773 /* Fast path */
3776 * FIXME: We might need a memory barrier here so the change to tlab_next is
3777 * visible before the vtable store.
3780 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3781 binary_protocol_alloc (p , vtable, size);
3782 g_assert (*p == NULL);
3783 *p = vtable;
3785 g_assert (TLAB_NEXT == new_next);
3787 return p;
3790 /* Slow path */
3792 /* there are two cases: the object is too big or we run out of space in the TLAB */
3793 /* we also reach here when the thread does its first allocation after a minor
3794 * collection, since the tlab_ variables are initialized to NULL.
3795 * there can be another case (from ORP), if we cooperate with the runtime a bit:
3796 * objects that need finalizers can have the high bit set in their size
3797 * so the above check fails and we can readily add the object to the queue.
3798 * This avoids taking again the GC lock when registering, but this is moot when
3799 * doing thread-local allocation, so it may not be a good idea.
3801 g_assert (TLAB_NEXT == new_next);
3802 if (TLAB_NEXT >= TLAB_REAL_END) {
3804 * Run out of space in the TLAB. When this happens, some amount of space
3805 * remains in the TLAB, but not enough to satisfy the current allocation
3806 * request. Currently, we retire the TLAB in all cases, later we could
3807 * keep it if the remaining space is above a treshold, and satisfy the
3808 * allocation directly from the nursery.
3810 TLAB_NEXT -= size;
3811 /* when running in degraded mode, we continue allocing that way
3812 * for a while, to decrease the number of useless nursery collections.
3814 if (degraded_mode && degraded_mode < DEFAULT_NURSERY_SIZE) {
3815 p = alloc_degraded (vtable, size);
3816 return p;
3819 if (size > tlab_size) {
3820 /* Allocate directly from the nursery */
3821 if (nursery_next + size >= nursery_frag_real_end) {
3822 if (!search_fragment_for_size (size)) {
3823 minor_collect_or_expand_inner (size);
3824 if (degraded_mode) {
3825 p = alloc_degraded (vtable, size);
3826 return p;
3831 p = (void*)nursery_next;
3832 nursery_next += size;
3833 if (nursery_next > nursery_frag_real_end) {
3834 // no space left
3835 g_assert (0);
3838 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
3839 memset (p, 0, size);
3840 } else {
3841 if (TLAB_START)
3842 DEBUG (3, fprintf (gc_debug_file, "Retire TLAB: %p-%p [%ld]\n", TLAB_START, TLAB_REAL_END, (long)(TLAB_REAL_END - TLAB_NEXT - size)));
3844 if (nursery_next + tlab_size >= nursery_frag_real_end) {
3845 res = search_fragment_for_size (tlab_size);
3846 if (!res) {
3847 minor_collect_or_expand_inner (tlab_size);
3848 if (degraded_mode) {
3849 p = alloc_degraded (vtable, size);
3850 return p;
3855 /* Allocate a new TLAB from the current nursery fragment */
3856 TLAB_START = nursery_next;
3857 nursery_next += tlab_size;
3858 TLAB_NEXT = TLAB_START;
3859 TLAB_REAL_END = TLAB_START + tlab_size;
3860 TLAB_TEMP_END = TLAB_START + MIN (SCAN_START_SIZE, tlab_size);
3862 if (nursery_clear_policy == CLEAR_AT_TLAB_CREATION)
3863 memset (TLAB_START, 0, tlab_size);
3865 /* Allocate from the TLAB */
3866 p = (void*)TLAB_NEXT;
3867 TLAB_NEXT += size;
3868 g_assert (TLAB_NEXT <= TLAB_REAL_END);
3870 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
3872 } else {
3873 /* Reached tlab_temp_end */
3875 /* record the scan start so we can find pinned objects more easily */
3876 nursery_section->scan_starts [((char*)p - (char*)nursery_section->data)/SCAN_START_SIZE] = (char*)p;
3877 /* we just bump tlab_temp_end as well */
3878 TLAB_TEMP_END = MIN (TLAB_REAL_END, TLAB_NEXT + SCAN_START_SIZE);
3879 DEBUG (5, fprintf (gc_debug_file, "Expanding local alloc: %p-%p\n", TLAB_NEXT, TLAB_TEMP_END));
3883 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3884 binary_protocol_alloc (p, vtable, size);
3885 *p = vtable;
3887 return p;
3890 static void*
3891 mono_gc_try_alloc_obj_nolock (MonoVTable *vtable, size_t size)
3893 void **p;
3894 char *new_next;
3895 TLAB_ACCESS_INIT;
3897 size += ALLOC_ALIGN - 1;
3898 size &= ~(ALLOC_ALIGN - 1);
3900 g_assert (vtable->gc_descr);
3901 if (size <= MAX_SMALL_OBJ_SIZE) {
3902 /* tlab_next and tlab_temp_end are TLS vars so accessing them might be expensive */
3904 p = (void**)TLAB_NEXT;
3905 /* FIXME: handle overflow */
3906 new_next = (char*)p + size;
3907 TLAB_NEXT = new_next;
3909 if (G_LIKELY (new_next < TLAB_TEMP_END)) {
3910 /* Fast path */
3913 * FIXME: We might need a memory barrier here so the change to tlab_next is
3914 * visible before the vtable store.
3917 HEAVY_STAT (++stat_objects_alloced);
3918 HEAVY_STAT (stat_bytes_alloced += size);
3920 DEBUG (6, fprintf (gc_debug_file, "Allocated object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
3921 binary_protocol_alloc (p, vtable, size);
3922 g_assert (*p == NULL);
3923 *p = vtable;
3925 g_assert (TLAB_NEXT == new_next);
3927 return p;
3930 return NULL;
3933 void*
3934 mono_gc_alloc_obj (MonoVTable *vtable, size_t size)
3936 void *res;
3937 #ifndef DISABLE_CRITICAL_REGION
3938 TLAB_ACCESS_INIT;
3939 ENTER_CRITICAL_REGION;
3940 res = mono_gc_try_alloc_obj_nolock (vtable, size);
3941 if (res) {
3942 EXIT_CRITICAL_REGION;
3943 return res;
3945 EXIT_CRITICAL_REGION;
3946 #endif
3947 LOCK_GC;
3948 res = mono_gc_alloc_obj_nolock (vtable, size);
3949 UNLOCK_GC;
3950 return res;
3953 void*
3954 mono_gc_alloc_vector (MonoVTable *vtable, size_t size, uintptr_t max_length)
3956 MonoArray *arr;
3957 #ifndef DISABLE_CRITICAL_REGION
3958 TLAB_ACCESS_INIT;
3959 ENTER_CRITICAL_REGION;
3960 arr = mono_gc_try_alloc_obj_nolock (vtable, size);
3961 if (arr) {
3962 arr->max_length = max_length;
3963 EXIT_CRITICAL_REGION;
3964 return arr;
3966 EXIT_CRITICAL_REGION;
3967 #endif
3969 LOCK_GC;
3971 arr = mono_gc_alloc_obj_nolock (vtable, size);
3972 arr->max_length = max_length;
3974 UNLOCK_GC;
3976 return arr;
3979 void*
3980 mono_gc_alloc_array (MonoVTable *vtable, size_t size, uintptr_t max_length, uintptr_t bounds_size)
3982 MonoArray *arr;
3983 MonoArrayBounds *bounds;
3985 LOCK_GC;
3987 arr = mono_gc_alloc_obj_nolock (vtable, size);
3988 arr->max_length = max_length;
3990 bounds = (MonoArrayBounds*)((char*)arr + size - bounds_size);
3991 arr->bounds = bounds;
3993 UNLOCK_GC;
3995 return arr;
3998 void*
3999 mono_gc_alloc_string (MonoVTable *vtable, size_t size, gint32 len)
4001 MonoString *str;
4002 #ifndef DISABLE_CRITICAL_REGION
4003 TLAB_ACCESS_INIT;
4004 ENTER_CRITICAL_REGION;
4005 str = mono_gc_try_alloc_obj_nolock (vtable, size);
4006 if (str) {
4007 str->length = len;
4008 EXIT_CRITICAL_REGION;
4009 return str;
4011 EXIT_CRITICAL_REGION;
4012 #endif
4014 LOCK_GC;
4016 str = mono_gc_alloc_obj_nolock (vtable, size);
4017 str->length = len;
4019 UNLOCK_GC;
4021 return str;
4025 * To be used for interned strings and possibly MonoThread, reflection handles.
4026 * We may want to explicitly free these objects.
4028 void*
4029 mono_gc_alloc_pinned_obj (MonoVTable *vtable, size_t size)
4031 /* FIXME: handle OOM */
4032 void **p;
4033 size += ALLOC_ALIGN - 1;
4034 size &= ~(ALLOC_ALIGN - 1);
4035 LOCK_GC;
4036 if (size > MAX_SMALL_OBJ_SIZE) {
4037 /* large objects are always pinned anyway */
4038 p = alloc_large_inner (vtable, size);
4039 } else {
4040 p = major_alloc_small_pinned_obj (size);
4042 DEBUG (6, fprintf (gc_debug_file, "Allocated pinned object %p, vtable: %p (%s), size: %zd\n", p, vtable, vtable->klass->name, size));
4043 binary_protocol_alloc (p, vtable, size);
4044 *p = vtable;
4045 UNLOCK_GC;
4046 return p;
4050 * ######################################################################
4051 * ######## Finalization support
4052 * ######################################################################
4056 * this is valid for the nursery: if the object has been forwarded it means it's
4057 * still refrenced from a root. If it is pinned it's still alive as well.
4058 * Return TRUE if @obj is ready to be finalized.
4060 #define object_is_fin_ready(obj) (!object_is_pinned (obj) && !object_is_forwarded (obj))
4062 static gboolean
4063 is_critical_finalizer (FinalizeEntry *entry)
4065 MonoObject *obj;
4066 MonoClass *class;
4068 if (!mono_defaults.critical_finalizer_object)
4069 return FALSE;
4071 obj = entry->object;
4072 class = ((MonoVTable*)LOAD_VTABLE (obj))->klass;
4074 return mono_class_has_parent (class, mono_defaults.critical_finalizer_object);
4077 static void
4078 queue_finalization_entry (FinalizeEntry *entry) {
4079 if (is_critical_finalizer (entry)) {
4080 entry->next = critical_fin_list;
4081 critical_fin_list = entry;
4082 } else {
4083 entry->next = fin_ready_list;
4084 fin_ready_list = entry;
4088 /* LOCKING: requires that the GC lock is held */
4089 static void
4090 rehash_fin_table (FinalizeEntryHashTable *hash_table)
4092 FinalizeEntry **finalizable_hash = hash_table->table;
4093 mword finalizable_hash_size = hash_table->size;
4094 int i;
4095 unsigned int hash;
4096 FinalizeEntry **new_hash;
4097 FinalizeEntry *entry, *next;
4098 int new_size = g_spaced_primes_closest (hash_table->num_registered);
4100 new_hash = get_internal_mem (new_size * sizeof (FinalizeEntry*), INTERNAL_MEM_FIN_TABLE);
4101 for (i = 0; i < finalizable_hash_size; ++i) {
4102 for (entry = finalizable_hash [i]; entry; entry = next) {
4103 hash = mono_object_hash (entry->object) % new_size;
4104 next = entry->next;
4105 entry->next = new_hash [hash];
4106 new_hash [hash] = entry;
4109 free_internal_mem (finalizable_hash, INTERNAL_MEM_FIN_TABLE);
4110 hash_table->table = new_hash;
4111 hash_table->size = new_size;
4114 /* LOCKING: requires that the GC lock is held */
4115 static void
4116 rehash_fin_table_if_necessary (FinalizeEntryHashTable *hash_table)
4118 if (hash_table->num_registered >= hash_table->size * 2)
4119 rehash_fin_table (hash_table);
4122 /* LOCKING: requires that the GC lock is held */
4123 static void
4124 finalize_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation)
4126 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
4127 FinalizeEntry *entry, *prev;
4128 int i;
4129 FinalizeEntry **finalizable_hash = hash_table->table;
4130 mword finalizable_hash_size = hash_table->size;
4132 if (no_finalize)
4133 return;
4134 for (i = 0; i < finalizable_hash_size; ++i) {
4135 prev = NULL;
4136 for (entry = finalizable_hash [i]; entry;) {
4137 if ((char*)entry->object >= start && (char*)entry->object < end && !major_is_object_live (entry->object)) {
4138 gboolean is_fin_ready = object_is_fin_ready (entry->object);
4139 char *copy = entry->object;
4140 copy_func ((void**)&copy);
4141 if (is_fin_ready) {
4142 char *from;
4143 FinalizeEntry *next;
4144 /* remove and put in fin_ready_list */
4145 if (prev)
4146 prev->next = entry->next;
4147 else
4148 finalizable_hash [i] = entry->next;
4149 next = entry->next;
4150 num_ready_finalizers++;
4151 hash_table->num_registered--;
4152 queue_finalization_entry (entry);
4153 /* Make it survive */
4154 from = entry->object;
4155 entry->object = copy;
4156 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));
4157 entry = next;
4158 continue;
4159 } else {
4160 char *from = entry->object;
4161 if (hash_table == &minor_finalizable_hash && !ptr_in_nursery (copy)) {
4162 FinalizeEntry *next = entry->next;
4163 unsigned int major_hash;
4164 /* remove from the list */
4165 if (prev)
4166 prev->next = entry->next;
4167 else
4168 finalizable_hash [i] = entry->next;
4169 hash_table->num_registered--;
4171 entry->object = copy;
4173 /* insert it into the major hash */
4174 rehash_fin_table_if_necessary (&major_finalizable_hash);
4175 major_hash = mono_object_hash ((MonoObject*) copy) %
4176 major_finalizable_hash.size;
4177 entry->next = major_finalizable_hash.table [major_hash];
4178 major_finalizable_hash.table [major_hash] = entry;
4179 major_finalizable_hash.num_registered++;
4181 DEBUG (5, fprintf (gc_debug_file, "Promoting finalization of object %p (%s) (was at %p) to major table\n", copy, safe_name (copy), from));
4183 entry = next;
4184 continue;
4185 } else {
4186 /* update pointer */
4187 DEBUG (5, fprintf (gc_debug_file, "Updating object for finalization: %p (%s) (was at %p)\n", entry->object, safe_name (entry->object), from));
4188 entry->object = copy;
4192 prev = entry;
4193 entry = entry->next;
4198 /* LOCKING: requires that the GC lock is held */
4199 static void
4200 null_link_in_range (CopyOrMarkObjectFunc copy_func, char *start, char *end, int generation)
4202 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
4203 DisappearingLink **disappearing_link_hash = hash->table;
4204 int disappearing_link_hash_size = hash->size;
4205 DisappearingLink *entry, *prev;
4206 int i;
4207 if (!hash->num_links)
4208 return;
4209 for (i = 0; i < disappearing_link_hash_size; ++i) {
4210 prev = NULL;
4211 for (entry = disappearing_link_hash [i]; entry;) {
4212 char *object = DISLINK_OBJECT (entry);
4213 if (object >= start && object < end && !major_is_object_live (object)) {
4214 gboolean track = DISLINK_TRACK (entry);
4215 if (!track && object_is_fin_ready (object)) {
4216 void **p = entry->link;
4217 DisappearingLink *old;
4218 *p = NULL;
4219 /* remove from list */
4220 if (prev)
4221 prev->next = entry->next;
4222 else
4223 disappearing_link_hash [i] = entry->next;
4224 DEBUG (5, fprintf (gc_debug_file, "Dislink nullified at %p to GCed object %p\n", p, object));
4225 old = entry->next;
4226 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4227 entry = old;
4228 hash->num_links--;
4229 continue;
4230 } else {
4231 char *copy = object;
4232 copy_func ((void**)&copy);
4234 /* Update pointer if it's moved. If the object
4235 * has been moved out of the nursery, we need to
4236 * remove the link from the minor hash table to
4237 * the major one.
4239 * FIXME: what if an object is moved earlier?
4242 if (hash == &minor_disappearing_link_hash && !ptr_in_nursery (copy)) {
4243 void **link = entry->link;
4244 DisappearingLink *old;
4245 /* remove from list */
4246 if (prev)
4247 prev->next = entry->next;
4248 else
4249 disappearing_link_hash [i] = entry->next;
4250 old = entry->next;
4251 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4252 entry = old;
4253 hash->num_links--;
4255 add_or_remove_disappearing_link ((MonoObject*)copy, link,
4256 track, GENERATION_OLD);
4258 DEBUG (5, fprintf (gc_debug_file, "Upgraded dislink at %p to major because object %p moved to %p\n", link, object, copy));
4260 continue;
4261 } else {
4262 /* We set the track resurrection bit to
4263 * FALSE if the object is to be finalized
4264 * so that the object can be collected in
4265 * the next cycle (i.e. after it was
4266 * finalized).
4268 *entry->link = HIDE_POINTER (copy,
4269 object_is_fin_ready (object) ? FALSE : track);
4270 DEBUG (5, fprintf (gc_debug_file, "Updated dislink at %p to %p\n", entry->link, DISLINK_OBJECT (entry)));
4274 prev = entry;
4275 entry = entry->next;
4280 /* LOCKING: requires that the GC lock is held */
4281 static void
4282 null_links_for_domain (MonoDomain *domain, int generation)
4284 DisappearingLinkHashTable *hash = get_dislink_hash_table (generation);
4285 DisappearingLink **disappearing_link_hash = hash->table;
4286 int disappearing_link_hash_size = hash->size;
4287 DisappearingLink *entry, *prev;
4288 int i;
4289 for (i = 0; i < disappearing_link_hash_size; ++i) {
4290 prev = NULL;
4291 for (entry = disappearing_link_hash [i]; entry; ) {
4292 char *object = DISLINK_OBJECT (entry);
4293 /* FIXME: actually there should be no object
4294 left in the domain with a non-null vtable
4295 (provided we remove the Thread special
4296 case) */
4297 if (object && (!((MonoObject*)object)->vtable || mono_object_domain (object) == domain)) {
4298 DisappearingLink *next = entry->next;
4300 if (prev)
4301 prev->next = next;
4302 else
4303 disappearing_link_hash [i] = next;
4305 if (*(entry->link)) {
4306 *(entry->link) = NULL;
4307 g_warning ("Disappearing link %p not freed", entry->link);
4308 } else {
4309 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4312 entry = next;
4313 continue;
4315 prev = entry;
4316 entry = entry->next;
4321 /* LOCKING: requires that the GC lock is held */
4322 static int
4323 finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size,
4324 FinalizeEntryHashTable *hash_table)
4326 FinalizeEntry **finalizable_hash = hash_table->table;
4327 mword finalizable_hash_size = hash_table->size;
4328 FinalizeEntry *entry, *prev;
4329 int i, count;
4331 if (no_finalize || !out_size || !out_array)
4332 return 0;
4333 count = 0;
4334 for (i = 0; i < finalizable_hash_size; ++i) {
4335 prev = NULL;
4336 for (entry = finalizable_hash [i]; entry;) {
4337 if (mono_object_domain (entry->object) == domain) {
4338 FinalizeEntry *next;
4339 /* remove and put in out_array */
4340 if (prev)
4341 prev->next = entry->next;
4342 else
4343 finalizable_hash [i] = entry->next;
4344 next = entry->next;
4345 hash_table->num_registered--;
4346 out_array [count ++] = entry->object;
4347 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));
4348 entry = next;
4349 if (count == out_size)
4350 return count;
4351 continue;
4353 prev = entry;
4354 entry = entry->next;
4357 return count;
4361 * mono_gc_finalizers_for_domain:
4362 * @domain: the unloading appdomain
4363 * @out_array: output array
4364 * @out_size: size of output array
4366 * Store inside @out_array up to @out_size objects that belong to the unloading
4367 * appdomain @domain. Returns the number of stored items. Can be called repeteadly
4368 * until it returns 0.
4369 * The items are removed from the finalizer data structure, so the caller is supposed
4370 * to finalize them.
4371 * @out_array should be on the stack to allow the GC to know the objects are still alive.
4374 mono_gc_finalizers_for_domain (MonoDomain *domain, MonoObject **out_array, int out_size)
4376 int result;
4378 LOCK_GC;
4379 result = finalizers_for_domain (domain, out_array, out_size, &minor_finalizable_hash);
4380 if (result < out_size) {
4381 result += finalizers_for_domain (domain, out_array + result, out_size - result,
4382 &major_finalizable_hash);
4384 UNLOCK_GC;
4386 return result;
4389 static void
4390 register_for_finalization (MonoObject *obj, void *user_data, int generation)
4392 FinalizeEntryHashTable *hash_table = get_finalize_entry_hash_table (generation);
4393 FinalizeEntry **finalizable_hash;
4394 mword finalizable_hash_size;
4395 FinalizeEntry *entry, *prev;
4396 unsigned int hash;
4397 if (no_finalize)
4398 return;
4399 g_assert (user_data == NULL || user_data == mono_gc_run_finalize);
4400 hash = mono_object_hash (obj);
4401 LOCK_GC;
4402 rehash_fin_table_if_necessary (hash_table);
4403 finalizable_hash = hash_table->table;
4404 finalizable_hash_size = hash_table->size;
4405 hash %= finalizable_hash_size;
4406 prev = NULL;
4407 for (entry = finalizable_hash [hash]; entry; entry = entry->next) {
4408 if (entry->object == obj) {
4409 if (!user_data) {
4410 /* remove from the list */
4411 if (prev)
4412 prev->next = entry->next;
4413 else
4414 finalizable_hash [hash] = entry->next;
4415 hash_table->num_registered--;
4416 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));
4417 free_internal_mem (entry, INTERNAL_MEM_FINALIZE_ENTRY);
4419 UNLOCK_GC;
4420 return;
4422 prev = entry;
4424 if (!user_data) {
4425 /* request to deregister, but already out of the list */
4426 UNLOCK_GC;
4427 return;
4429 entry = get_internal_mem (sizeof (FinalizeEntry), INTERNAL_MEM_FINALIZE_ENTRY);
4430 entry->object = obj;
4431 entry->next = finalizable_hash [hash];
4432 finalizable_hash [hash] = entry;
4433 hash_table->num_registered++;
4434 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)));
4435 UNLOCK_GC;
4438 void
4439 mono_gc_register_for_finalization (MonoObject *obj, void *user_data)
4441 if (ptr_in_nursery (obj))
4442 register_for_finalization (obj, user_data, GENERATION_NURSERY);
4443 else
4444 register_for_finalization (obj, user_data, GENERATION_OLD);
4447 static void
4448 rehash_dislink (DisappearingLinkHashTable *hash_table)
4450 DisappearingLink **disappearing_link_hash = hash_table->table;
4451 int disappearing_link_hash_size = hash_table->size;
4452 int i;
4453 unsigned int hash;
4454 DisappearingLink **new_hash;
4455 DisappearingLink *entry, *next;
4456 int new_size = g_spaced_primes_closest (hash_table->num_links);
4458 new_hash = get_internal_mem (new_size * sizeof (DisappearingLink*), INTERNAL_MEM_DISLINK_TABLE);
4459 for (i = 0; i < disappearing_link_hash_size; ++i) {
4460 for (entry = disappearing_link_hash [i]; entry; entry = next) {
4461 hash = mono_aligned_addr_hash (entry->link) % new_size;
4462 next = entry->next;
4463 entry->next = new_hash [hash];
4464 new_hash [hash] = entry;
4467 free_internal_mem (disappearing_link_hash, INTERNAL_MEM_DISLINK_TABLE);
4468 hash_table->table = new_hash;
4469 hash_table->size = new_size;
4472 /* LOCKING: assumes the GC lock is held */
4473 static void
4474 add_or_remove_disappearing_link (MonoObject *obj, void **link, gboolean track, int generation)
4476 DisappearingLinkHashTable *hash_table = get_dislink_hash_table (generation);
4477 DisappearingLink *entry, *prev;
4478 unsigned int hash;
4479 DisappearingLink **disappearing_link_hash = hash_table->table;
4480 int disappearing_link_hash_size = hash_table->size;
4482 if (hash_table->num_links >= disappearing_link_hash_size * 2) {
4483 rehash_dislink (hash_table);
4484 disappearing_link_hash = hash_table->table;
4485 disappearing_link_hash_size = hash_table->size;
4487 /* FIXME: add check that link is not in the heap */
4488 hash = mono_aligned_addr_hash (link) % disappearing_link_hash_size;
4489 entry = disappearing_link_hash [hash];
4490 prev = NULL;
4491 for (; entry; entry = entry->next) {
4492 /* link already added */
4493 if (link == entry->link) {
4494 /* NULL obj means remove */
4495 if (obj == NULL) {
4496 if (prev)
4497 prev->next = entry->next;
4498 else
4499 disappearing_link_hash [hash] = entry->next;
4500 hash_table->num_links--;
4501 DEBUG (5, fprintf (gc_debug_file, "Removed dislink %p (%d) from %s table\n", entry, hash_table->num_links, generation_name (generation)));
4502 free_internal_mem (entry, INTERNAL_MEM_DISLINK);
4503 *link = NULL;
4504 } else {
4505 *link = HIDE_POINTER (obj, track); /* we allow the change of object */
4507 return;
4509 prev = entry;
4511 if (obj == NULL)
4512 return;
4513 entry = get_internal_mem (sizeof (DisappearingLink), INTERNAL_MEM_DISLINK);
4514 *link = HIDE_POINTER (obj, track);
4515 entry->link = link;
4516 entry->next = disappearing_link_hash [hash];
4517 disappearing_link_hash [hash] = entry;
4518 hash_table->num_links++;
4519 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)));
4522 /* LOCKING: assumes the GC lock is held */
4523 static void
4524 mono_gc_register_disappearing_link (MonoObject *obj, void **link, gboolean track)
4526 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_NURSERY);
4527 add_or_remove_disappearing_link (NULL, link, FALSE, GENERATION_OLD);
4528 if (obj) {
4529 if (ptr_in_nursery (obj))
4530 add_or_remove_disappearing_link (obj, link, track, GENERATION_NURSERY);
4531 else
4532 add_or_remove_disappearing_link (obj, link, track, GENERATION_OLD);
4537 mono_gc_invoke_finalizers (void)
4539 FinalizeEntry *entry = NULL;
4540 gboolean entry_is_critical = FALSE;
4541 int count = 0;
4542 void *obj;
4543 /* FIXME: batch to reduce lock contention */
4544 while (fin_ready_list || critical_fin_list) {
4545 LOCK_GC;
4547 if (entry) {
4548 FinalizeEntry **list = entry_is_critical ? &critical_fin_list : &fin_ready_list;
4550 /* We have finalized entry in the last
4551 interation, now we need to remove it from
4552 the list. */
4553 if (*list == entry)
4554 *list = entry->next;
4555 else {
4556 FinalizeEntry *e = *list;
4557 while (e->next != entry)
4558 e = e->next;
4559 e->next = entry->next;
4561 free_internal_mem (entry, INTERNAL_MEM_FINALIZE_ENTRY);
4562 entry = NULL;
4565 /* Now look for the first non-null entry. */
4566 for (entry = fin_ready_list; entry && !entry->object; entry = entry->next)
4568 if (entry) {
4569 entry_is_critical = FALSE;
4570 } else {
4571 entry_is_critical = TRUE;
4572 for (entry = critical_fin_list; entry && !entry->object; entry = entry->next)
4576 if (entry) {
4577 g_assert (entry->object);
4578 num_ready_finalizers--;
4579 obj = entry->object;
4580 entry->object = NULL;
4581 DEBUG (7, fprintf (gc_debug_file, "Finalizing object %p (%s)\n", obj, safe_name (obj)));
4584 UNLOCK_GC;
4586 if (!entry)
4587 break;
4589 g_assert (entry->object == NULL);
4590 count++;
4591 /* the object is on the stack so it is pinned */
4592 /*g_print ("Calling finalizer for object: %p (%s)\n", entry->object, safe_name (entry->object));*/
4593 mono_gc_run_finalize (obj, NULL);
4595 g_assert (!entry);
4596 return count;
4599 gboolean
4600 mono_gc_pending_finalizers (void)
4602 return fin_ready_list || critical_fin_list;
4605 /* Negative value to remove */
4606 void
4607 mono_gc_add_memory_pressure (gint64 value)
4609 /* FIXME: Use interlocked functions */
4610 LOCK_GC;
4611 memory_pressure += value;
4612 UNLOCK_GC;
4616 * ######################################################################
4617 * ######## registered roots support
4618 * ######################################################################
4621 static void
4622 rehash_roots (gboolean pinned)
4624 int i;
4625 unsigned int hash;
4626 RootRecord **new_hash;
4627 RootRecord *entry, *next;
4628 int new_size;
4630 new_size = g_spaced_primes_closest (num_roots_entries [pinned]);
4631 new_hash = get_internal_mem (new_size * sizeof (RootRecord*), INTERNAL_MEM_ROOTS_TABLE);
4632 for (i = 0; i < roots_hash_size [pinned]; ++i) {
4633 for (entry = roots_hash [pinned][i]; entry; entry = next) {
4634 hash = mono_aligned_addr_hash (entry->start_root) % new_size;
4635 next = entry->next;
4636 entry->next = new_hash [hash];
4637 new_hash [hash] = entry;
4640 free_internal_mem (roots_hash [pinned], INTERNAL_MEM_ROOTS_TABLE);
4641 roots_hash [pinned] = new_hash;
4642 roots_hash_size [pinned] = new_size;
4645 static RootRecord*
4646 find_root (int root_type, char *start, guint32 addr_hash)
4648 RootRecord *new_root;
4650 guint32 hash = addr_hash % roots_hash_size [root_type];
4651 for (new_root = roots_hash [root_type][hash]; new_root; new_root = new_root->next) {
4652 /* we allow changing the size and the descriptor (for thread statics etc) */
4653 if (new_root->start_root == start) {
4654 return new_root;
4658 return NULL;
4662 * We do not coalesce roots.
4664 static int
4665 mono_gc_register_root_inner (char *start, size_t size, void *descr, int root_type)
4667 RootRecord *new_root;
4668 unsigned int hash, addr_hash = mono_aligned_addr_hash (start);
4669 int i;
4670 LOCK_GC;
4671 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
4672 if (num_roots_entries [i] >= roots_hash_size [i] * 2)
4673 rehash_roots (i);
4675 for (i = 0; i < ROOT_TYPE_NUM; ++i) {
4676 new_root = find_root (i, start, addr_hash);
4677 /* we allow changing the size and the descriptor (for thread statics etc) */
4678 if (new_root) {
4679 size_t old_size = new_root->end_root - new_root->start_root;
4680 new_root->end_root = new_root->start_root + size;
4681 g_assert (((new_root->root_desc != 0) && (descr != NULL)) ||
4682 ((new_root->root_desc == 0) && (descr == NULL)));
4683 new_root->root_desc = (mword)descr;
4684 roots_size += size;
4685 roots_size -= old_size;
4686 UNLOCK_GC;
4687 return TRUE;
4690 new_root = get_internal_mem (sizeof (RootRecord), INTERNAL_MEM_ROOT_RECORD);
4691 if (new_root) {
4692 new_root->start_root = start;
4693 new_root->end_root = new_root->start_root + size;
4694 new_root->root_desc = (mword)descr;
4695 roots_size += size;
4696 hash = addr_hash % roots_hash_size [root_type];
4697 num_roots_entries [root_type]++;
4698 new_root->next = roots_hash [root_type] [hash];
4699 roots_hash [root_type][hash] = new_root;
4700 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));
4701 } else {
4702 UNLOCK_GC;
4703 return FALSE;
4705 UNLOCK_GC;
4706 return TRUE;
4710 mono_gc_register_root (char *start, size_t size, void *descr)
4712 return mono_gc_register_root_inner (start, size, descr, descr ? ROOT_TYPE_NORMAL : ROOT_TYPE_PINNED);
4716 mono_gc_register_root_wbarrier (char *start, size_t size, void *descr)
4718 return mono_gc_register_root_inner (start, size, descr, ROOT_TYPE_WBARRIER);
4721 void
4722 mono_gc_deregister_root (char* addr)
4724 RootRecord *tmp, *prev;
4725 unsigned int hash, addr_hash = mono_aligned_addr_hash (addr);
4726 int root_type;
4728 LOCK_GC;
4729 for (root_type = 0; root_type < ROOT_TYPE_NUM; ++root_type) {
4730 hash = addr_hash % roots_hash_size [root_type];
4731 tmp = roots_hash [root_type][hash];
4732 prev = NULL;
4733 while (tmp) {
4734 if (tmp->start_root == (char*)addr) {
4735 if (prev)
4736 prev->next = tmp->next;
4737 else
4738 roots_hash [root_type][hash] = tmp->next;
4739 roots_size -= (tmp->end_root - tmp->start_root);
4740 num_roots_entries [root_type]--;
4741 DEBUG (3, fprintf (gc_debug_file, "Removed root %p for range: %p-%p\n", tmp, tmp->start_root, tmp->end_root));
4742 free_internal_mem (tmp, INTERNAL_MEM_ROOT_RECORD);
4743 break;
4745 prev = tmp;
4746 tmp = tmp->next;
4749 UNLOCK_GC;
4753 * ######################################################################
4754 * ######## Thread handling (stop/start code)
4755 * ######################################################################
4758 /* FIXME: handle large/small config */
4759 #define THREAD_HASH_SIZE 11
4760 #define HASH_PTHREAD_T(id) (((unsigned int)(id) >> 4) * 2654435761u)
4762 static SgenThreadInfo* thread_table [THREAD_HASH_SIZE];
4764 #if USE_SIGNAL_BASED_START_STOP_WORLD
4766 static MonoSemType suspend_ack_semaphore;
4767 static MonoSemType *suspend_ack_semaphore_ptr;
4768 static unsigned int global_stop_count = 0;
4769 #ifdef __APPLE__
4770 static int suspend_signal_num = SIGXFSZ;
4771 #else
4772 static int suspend_signal_num = SIGPWR;
4773 #endif
4774 static int restart_signal_num = SIGXCPU;
4775 static sigset_t suspend_signal_mask;
4776 static mword cur_thread_regs [ARCH_NUM_REGS] = {0};
4778 /* LOCKING: assumes the GC lock is held */
4779 static SgenThreadInfo*
4780 thread_info_lookup (ARCH_THREAD_TYPE id)
4782 unsigned int hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
4783 SgenThreadInfo *info;
4785 info = thread_table [hash];
4786 while (info && !ARCH_THREAD_EQUALS (info->id, id)) {
4787 info = info->next;
4789 return info;
4792 static void
4793 update_current_thread_stack (void *start)
4795 void *ptr = cur_thread_regs;
4796 SgenThreadInfo *info = thread_info_lookup (ARCH_GET_THREAD ());
4798 info->stack_start = align_pointer (&ptr);
4799 g_assert (info->stack_start >= info->stack_start_limit && info->stack_start < info->stack_end);
4800 ARCH_STORE_REGS (ptr);
4801 info->stopped_regs = ptr;
4802 if (gc_callbacks.thread_suspend_func)
4803 gc_callbacks.thread_suspend_func (info->runtime_data, NULL);
4806 static const char*
4807 signal_desc (int signum)
4809 if (signum == suspend_signal_num)
4810 return "suspend";
4811 if (signum == restart_signal_num)
4812 return "restart";
4813 return "unknown";
4817 * Define this and use the "xdomain-checks" MONO_GC_DEBUG option to
4818 * have cross-domain checks in the write barrier.
4820 //#define XDOMAIN_CHECKS_IN_WBARRIER
4822 #ifndef BINARY_PROTOCOL
4823 #ifndef HEAVY_STATISTICS
4824 #define MANAGED_ALLOCATION
4825 #ifndef XDOMAIN_CHECKS_IN_WBARRIER
4826 #define MANAGED_WBARRIER
4827 #endif
4828 #endif
4829 #endif
4831 static gboolean
4832 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip);
4834 static void
4835 wait_for_suspend_ack (int count)
4837 int i, result;
4839 for (i = 0; i < count; ++i) {
4840 while ((result = MONO_SEM_WAIT (suspend_ack_semaphore_ptr)) != 0) {
4841 if (errno != EINTR) {
4842 g_error ("sem_wait ()");
4848 /* LOCKING: assumes the GC lock is held */
4849 static int
4850 thread_handshake (int signum)
4852 int count, i, result;
4853 SgenThreadInfo *info;
4854 pthread_t me = pthread_self ();
4856 count = 0;
4857 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4858 for (info = thread_table [i]; info; info = info->next) {
4859 DEBUG (4, fprintf (gc_debug_file, "considering thread %p for signal %d (%s)\n", info, signum, signal_desc (signum)));
4860 if (ARCH_THREAD_EQUALS (info->id, me)) {
4861 DEBUG (4, fprintf (gc_debug_file, "Skip (equal): %p, %p\n", (void*)me, (void*)info->id));
4862 continue;
4864 /*if (signum == suspend_signal_num && info->stop_count == global_stop_count)
4865 continue;*/
4866 result = pthread_kill (info->id, signum);
4867 if (result == 0) {
4868 DEBUG (4, fprintf (gc_debug_file, "thread %p signal sent\n", info));
4869 count++;
4870 } else {
4871 DEBUG (4, fprintf (gc_debug_file, "thread %p signal failed: %d (%s)\n", (void*)info->id, result, strerror (result)));
4872 info->skip = 1;
4877 wait_for_suspend_ack (count);
4879 return count;
4882 static int
4883 restart_threads_until_none_in_managed_allocator (void)
4885 SgenThreadInfo *info;
4886 int i, result, num_threads_died = 0;
4887 int sleep_duration = -1;
4889 for (;;) {
4890 int restart_count = 0, restarted_count = 0;
4891 /* restart all threads that stopped in the
4892 allocator */
4893 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4894 for (info = thread_table [i]; info; info = info->next) {
4895 if (info->skip)
4896 continue;
4897 if (!info->stack_start || info->in_critical_region ||
4898 is_ip_in_managed_allocator (info->stopped_domain, info->stopped_ip)) {
4899 binary_protocol_thread_restart ((gpointer)info->id);
4900 result = pthread_kill (info->id, restart_signal_num);
4901 if (result == 0) {
4902 ++restart_count;
4903 } else {
4904 info->skip = 1;
4906 } else {
4907 /* we set the stopped_ip to
4908 NULL for threads which
4909 we're not restarting so
4910 that we can easily identify
4911 the others */
4912 info->stopped_ip = NULL;
4913 info->stopped_domain = NULL;
4917 /* if no threads were restarted, we're done */
4918 if (restart_count == 0)
4919 break;
4921 /* wait for the threads to signal their restart */
4922 wait_for_suspend_ack (restart_count);
4924 if (sleep_duration < 0) {
4925 sched_yield ();
4926 sleep_duration = 0;
4927 } else {
4928 g_usleep (sleep_duration);
4929 sleep_duration += 10;
4932 /* stop them again */
4933 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
4934 for (info = thread_table [i]; info; info = info->next) {
4935 if (info->skip || info->stopped_ip == NULL)
4936 continue;
4937 result = pthread_kill (info->id, suspend_signal_num);
4938 if (result == 0) {
4939 ++restarted_count;
4940 } else {
4941 info->skip = 1;
4945 /* some threads might have died */
4946 num_threads_died += restart_count - restarted_count;
4947 /* wait for the threads to signal their suspension
4948 again */
4949 wait_for_suspend_ack (restart_count);
4952 return num_threads_died;
4955 /* LOCKING: assumes the GC lock is held (by the stopping thread) */
4956 static void
4957 suspend_handler (int sig, siginfo_t *siginfo, void *context)
4959 SgenThreadInfo *info;
4960 pthread_t id;
4961 int stop_count;
4962 int old_errno = errno;
4963 gpointer regs [ARCH_NUM_REGS];
4964 gpointer stack_start;
4966 id = pthread_self ();
4967 info = thread_info_lookup (id);
4968 info->stopped_domain = mono_domain_get ();
4969 info->stopped_ip = (gpointer) ARCH_SIGCTX_IP (context);
4970 stop_count = global_stop_count;
4971 /* duplicate signal */
4972 if (0 && info->stop_count == stop_count) {
4973 errno = old_errno;
4974 return;
4976 #ifdef HAVE_KW_THREAD
4977 /* update the remset info in the thread data structure */
4978 info->remset = remembered_set;
4979 #endif
4980 stack_start = (char*) ARCH_SIGCTX_SP (context) - REDZONE_SIZE;
4981 /* If stack_start is not within the limits, then don't set it
4982 in info and we will be restarted. */
4983 if (stack_start >= info->stack_start_limit && info->stack_start <= info->stack_end) {
4984 info->stack_start = stack_start;
4986 ARCH_COPY_SIGCTX_REGS (regs, context);
4987 info->stopped_regs = regs;
4988 } else {
4989 g_assert (!info->stack_start);
4992 /* Notify the JIT */
4993 if (gc_callbacks.thread_suspend_func)
4994 gc_callbacks.thread_suspend_func (info->runtime_data, context);
4996 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for suspend from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
4997 /* notify the waiting thread */
4998 MONO_SEM_POST (suspend_ack_semaphore_ptr);
4999 info->stop_count = stop_count;
5001 /* wait until we receive the restart signal */
5002 do {
5003 info->signal = 0;
5004 sigsuspend (&suspend_signal_mask);
5005 } while (info->signal != restart_signal_num);
5007 DEBUG (4, fprintf (gc_debug_file, "Posting suspend_ack_semaphore for resume from %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
5008 /* notify the waiting thread */
5009 MONO_SEM_POST (suspend_ack_semaphore_ptr);
5011 errno = old_errno;
5014 static void
5015 restart_handler (int sig)
5017 SgenThreadInfo *info;
5018 int old_errno = errno;
5020 info = thread_info_lookup (pthread_self ());
5021 info->signal = restart_signal_num;
5022 DEBUG (4, fprintf (gc_debug_file, "Restart handler in %p %p\n", info, (gpointer)ARCH_GET_THREAD ()));
5024 errno = old_errno;
5027 static void
5028 acquire_gc_locks (void)
5030 LOCK_INTERRUPTION;
5033 static void
5034 release_gc_locks (void)
5036 UNLOCK_INTERRUPTION;
5039 static TV_DECLARE (stop_world_time);
5040 static unsigned long max_pause_usec = 0;
5042 /* LOCKING: assumes the GC lock is held */
5043 static int
5044 stop_world (void)
5046 int count;
5048 acquire_gc_locks ();
5050 update_current_thread_stack (&count);
5052 global_stop_count++;
5053 DEBUG (3, fprintf (gc_debug_file, "stopping world n %d from %p %p\n", global_stop_count, thread_info_lookup (ARCH_GET_THREAD ()), (gpointer)ARCH_GET_THREAD ()));
5054 TV_GETTIME (stop_world_time);
5055 count = thread_handshake (suspend_signal_num);
5056 count -= restart_threads_until_none_in_managed_allocator ();
5057 g_assert (count >= 0);
5058 DEBUG (3, fprintf (gc_debug_file, "world stopped %d thread(s)\n", count));
5059 return count;
5062 /* LOCKING: assumes the GC lock is held */
5063 static int
5064 restart_world (void)
5066 int count, i;
5067 SgenThreadInfo *info;
5068 TV_DECLARE (end_sw);
5069 unsigned long usec;
5071 /* notify the profiler of the leftovers */
5072 if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_GC_MOVES)) {
5073 if (moved_objects_idx) {
5074 mono_profiler_gc_moves (moved_objects, moved_objects_idx);
5075 moved_objects_idx = 0;
5078 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5079 for (info = thread_table [i]; info; info = info->next) {
5080 info->stack_start = NULL;
5081 info->stopped_regs = NULL;
5085 release_gc_locks ();
5087 count = thread_handshake (restart_signal_num);
5088 TV_GETTIME (end_sw);
5089 usec = TV_ELAPSED (stop_world_time, end_sw);
5090 max_pause_usec = MAX (usec, max_pause_usec);
5091 DEBUG (2, fprintf (gc_debug_file, "restarted %d thread(s) (pause time: %d usec, max: %d)\n", count, (int)usec, (int)max_pause_usec));
5092 return count;
5095 #endif /* USE_SIGNAL_BASED_START_STOP_WORLD */
5097 void
5098 mono_gc_set_gc_callbacks (MonoGCCallbacks *callbacks)
5100 gc_callbacks = *callbacks;
5103 /* Variables holding start/end nursery so it won't have to be passed at every call */
5104 static void *scan_area_arg_start, *scan_area_arg_end;
5106 void
5107 mono_gc_conservatively_scan_area (void *start, void *end)
5109 conservatively_pin_objects_from (start, end, scan_area_arg_start, scan_area_arg_end, PIN_TYPE_STACK);
5112 void*
5113 mono_gc_scan_object (void *obj)
5115 if (current_collection_generation == GENERATION_NURSERY)
5116 copy_object (&obj);
5117 else
5118 major_copy_or_mark_object (&obj);
5119 return obj;
5123 * Mark from thread stacks and registers.
5125 static void
5126 scan_thread_data (void *start_nursery, void *end_nursery, gboolean precise)
5128 int i;
5129 SgenThreadInfo *info;
5131 scan_area_arg_start = start_nursery;
5132 scan_area_arg_end = end_nursery;
5134 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5135 for (info = thread_table [i]; info; info = info->next) {
5136 if (info->skip) {
5137 DEBUG (3, fprintf (gc_debug_file, "Skipping dead thread %p, range: %p-%p, size: %zd\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start));
5138 continue;
5140 DEBUG (3, fprintf (gc_debug_file, "Scanning thread %p, range: %p-%p, size: %zd, pinned=%d\n", info, info->stack_start, info->stack_end, (char*)info->stack_end - (char*)info->stack_start, next_pin_slot));
5141 if (gc_callbacks.thread_mark_func && !conservative_stack_mark)
5142 gc_callbacks.thread_mark_func (info->runtime_data, info->stack_start, info->stack_end, precise);
5143 else if (!precise)
5144 conservatively_pin_objects_from (info->stack_start, info->stack_end, start_nursery, end_nursery, PIN_TYPE_STACK);
5146 if (!precise)
5147 conservatively_pin_objects_from (info->stopped_regs, info->stopped_regs + ARCH_NUM_REGS,
5148 start_nursery, end_nursery, PIN_TYPE_STACK);
5153 static void
5154 find_pinning_ref_from_thread (char *obj, size_t size)
5156 int i;
5157 SgenThreadInfo *info;
5158 char *endobj = obj + size;
5160 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5161 for (info = thread_table [i]; info; info = info->next) {
5162 char **start = (char**)info->stack_start;
5163 if (info->skip)
5164 continue;
5165 while (start < (char**)info->stack_end) {
5166 if (*start >= obj && *start < endobj) {
5167 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));
5169 start++;
5172 /* FIXME: check info->stopped_regs */
5177 static gboolean
5178 ptr_on_stack (void *ptr)
5180 gpointer stack_start = &stack_start;
5181 SgenThreadInfo *info = thread_info_lookup (ARCH_GET_THREAD ());
5183 if (ptr >= stack_start && ptr < (gpointer)info->stack_end)
5184 return TRUE;
5185 return FALSE;
5188 static mword*
5189 handle_remset (mword *p, void *start_nursery, void *end_nursery, gboolean global)
5191 void **ptr;
5192 mword count;
5193 mword desc;
5195 if (global)
5196 HEAVY_STAT (++stat_global_remsets_processed);
5198 /* FIXME: exclude stack locations */
5199 switch ((*p) & REMSET_TYPE_MASK) {
5200 case REMSET_LOCATION:
5201 ptr = (void**)(*p);
5202 //__builtin_prefetch (ptr);
5203 if (((void*)ptr < start_nursery || (void*)ptr >= end_nursery)) {
5204 gpointer old = *ptr;
5205 copy_object (ptr);
5206 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p\n", ptr, *ptr));
5207 if (old)
5208 binary_protocol_ptr_update (ptr, old, *ptr, (gpointer)LOAD_VTABLE (*ptr), safe_object_get_size (*ptr));
5209 if (!global && *ptr >= start_nursery && *ptr < end_nursery) {
5211 * If the object is pinned, each reference to it from nonpinned objects
5212 * becomes part of the global remset, which can grow very large.
5214 DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
5215 add_to_global_remset (ptr, FALSE);
5217 } else {
5218 DEBUG (9, fprintf (gc_debug_file, "Skipping remset at %p holding %p\n", ptr, *ptr));
5220 return p + 1;
5221 case REMSET_RANGE:
5222 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5223 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5224 return p + 2;
5225 count = p [1];
5226 while (count-- > 0) {
5227 copy_object (ptr);
5228 DEBUG (9, fprintf (gc_debug_file, "Overwrote remset at %p with %p (count: %d)\n", ptr, *ptr, (int)count));
5229 if (!global && *ptr >= start_nursery && *ptr < end_nursery)
5230 add_to_global_remset (ptr, FALSE);
5231 ++ptr;
5233 return p + 2;
5234 case REMSET_OBJECT:
5235 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5236 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5237 return p + 1;
5238 scan_object ((char*)ptr);
5239 return p + 1;
5240 case REMSET_OTHER: {
5241 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
5243 switch (p [1]) {
5244 case REMSET_VTYPE:
5245 if (((void*)ptr >= start_nursery && (void*)ptr < end_nursery))
5246 return p + 4;
5247 desc = p [2];
5248 count = p [3];
5249 while (count-- > 0)
5250 ptr = (void**) scan_vtype ((char*)ptr, desc, start_nursery, end_nursery);
5251 return p + 4;
5252 case REMSET_ROOT_LOCATION:
5253 /* Same as REMSET_LOCATION, but the address is not required to be in the heap */
5254 copy_object (ptr);
5255 DEBUG (9, fprintf (gc_debug_file, "Overwrote root location remset at %p with %p\n", ptr, *ptr));
5256 if (!global && *ptr >= start_nursery && *ptr < end_nursery) {
5258 * If the object is pinned, each reference to it from nonpinned objects
5259 * becomes part of the global remset, which can grow very large.
5261 DEBUG (9, fprintf (gc_debug_file, "Add to global remset because of pinning %p (%p %s)\n", ptr, *ptr, safe_name (*ptr)));
5262 add_to_global_remset (ptr, TRUE);
5264 return p + 2;
5265 default:
5266 g_assert_not_reached ();
5268 break;
5270 default:
5271 g_assert_not_reached ();
5273 return NULL;
5276 #ifdef HEAVY_STATISTICS
5277 static mword*
5278 collect_store_remsets (RememberedSet *remset, mword *bumper)
5280 mword *p = remset->data;
5281 mword last = 0;
5282 mword last1 = 0;
5283 mword last2 = 0;
5285 while (p < remset->store_next) {
5286 switch ((*p) & REMSET_TYPE_MASK) {
5287 case REMSET_LOCATION:
5288 *bumper++ = *p;
5289 if (*p == last)
5290 ++stat_saved_remsets_1;
5291 last = *p;
5292 if (*p == last1 || *p == last2) {
5293 ++stat_saved_remsets_2;
5294 } else {
5295 last2 = last1;
5296 last1 = *p;
5298 p += 1;
5299 break;
5300 case REMSET_RANGE:
5301 p += 2;
5302 break;
5303 case REMSET_OBJECT:
5304 p += 1;
5305 break;
5306 case REMSET_OTHER:
5307 switch (p [1]) {
5308 case REMSET_VTYPE:
5309 p += 4;
5310 break;
5311 case REMSET_ROOT_LOCATION:
5312 p += 2;
5313 break;
5314 default:
5315 g_assert_not_reached ();
5317 break;
5318 default:
5319 g_assert_not_reached ();
5323 return bumper;
5326 static void
5327 remset_stats (void)
5329 RememberedSet *remset;
5330 int size = 0;
5331 SgenThreadInfo *info;
5332 int i;
5333 mword *addresses, *bumper, *p, *r;
5335 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5336 for (info = thread_table [i]; info; info = info->next) {
5337 for (remset = info->remset; remset; remset = remset->next)
5338 size += remset->store_next - remset->data;
5341 for (remset = freed_thread_remsets; remset; remset = remset->next)
5342 size += remset->store_next - remset->data;
5343 for (remset = global_remset; remset; remset = remset->next)
5344 size += remset->store_next - remset->data;
5346 bumper = addresses = get_internal_mem (sizeof (mword) * size, INTERNAL_MEM_STATISTICS);
5348 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5349 for (info = thread_table [i]; info; info = info->next) {
5350 for (remset = info->remset; remset; remset = remset->next)
5351 bumper = collect_store_remsets (remset, bumper);
5354 for (remset = global_remset; remset; remset = remset->next)
5355 bumper = collect_store_remsets (remset, bumper);
5356 for (remset = freed_thread_remsets; remset; remset = remset->next)
5357 bumper = collect_store_remsets (remset, bumper);
5359 g_assert (bumper <= addresses + size);
5361 stat_store_remsets += bumper - addresses;
5363 sort_addresses ((void**)addresses, bumper - addresses);
5364 p = addresses;
5365 r = addresses + 1;
5366 while (r < bumper) {
5367 if (*r != *p)
5368 *++p = *r;
5369 ++r;
5372 stat_store_remsets_unique += p - addresses;
5374 free_internal_mem (addresses, INTERNAL_MEM_STATISTICS);
5376 #endif
5378 static void
5379 clear_thread_store_remset_buffer (SgenThreadInfo *info)
5381 *info->store_remset_buffer_index_addr = 0;
5382 memset (*info->store_remset_buffer_addr, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
5385 static void
5386 scan_from_remsets (void *start_nursery, void *end_nursery)
5388 int i;
5389 SgenThreadInfo *info;
5390 RememberedSet *remset;
5391 GenericStoreRememberedSet *store_remset;
5392 mword *p, *next_p, *store_pos;
5394 #ifdef HEAVY_STATISTICS
5395 remset_stats ();
5396 #endif
5398 /* the global one */
5399 for (remset = global_remset; remset; remset = remset->next) {
5400 DEBUG (4, fprintf (gc_debug_file, "Scanning global remset range: %p-%p, size: %zd\n", remset->data, remset->store_next, remset->store_next - remset->data));
5401 store_pos = remset->data;
5402 for (p = remset->data; p < remset->store_next; p = next_p) {
5403 mword ptr;
5405 next_p = handle_remset (p, start_nursery, end_nursery, TRUE);
5408 * Clear global remsets of locations which no longer point to the
5409 * nursery. Otherwise, they could grow indefinitely between major
5410 * collections.
5412 ptr = (p [0] & ~REMSET_TYPE_MASK);
5413 if ((p [0] & REMSET_TYPE_MASK) == REMSET_LOCATION) {
5414 if (ptr_in_nursery (*(void**)ptr))
5415 *store_pos ++ = p [0];
5416 } else {
5417 g_assert ((p [0] & REMSET_TYPE_MASK) == REMSET_OTHER);
5418 g_assert (p [1] == REMSET_ROOT_LOCATION);
5419 if (ptr_in_nursery (*(void**)ptr)) {
5420 *store_pos ++ = p [0];
5421 *store_pos ++ = p [1];
5426 /* Truncate the remset */
5427 remset->store_next = store_pos;
5430 /* the generic store ones */
5431 store_remset = generic_store_remsets;
5432 while (store_remset) {
5433 GenericStoreRememberedSet *next = store_remset->next;
5435 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
5436 gpointer addr = store_remset->data [i];
5437 if (addr)
5438 handle_remset ((mword*)&addr, start_nursery, end_nursery, FALSE);
5441 free_internal_mem (store_remset, INTERNAL_MEM_STORE_REMSET);
5443 store_remset = next;
5445 generic_store_remsets = NULL;
5447 /* the per-thread ones */
5448 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5449 for (info = thread_table [i]; info; info = info->next) {
5450 RememberedSet *next;
5451 int j;
5452 for (remset = info->remset; remset; remset = next) {
5453 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for thread %p, range: %p-%p, size: %zd\n", info, remset->data, remset->store_next, remset->store_next - remset->data));
5454 for (p = remset->data; p < remset->store_next;) {
5455 p = handle_remset (p, start_nursery, end_nursery, FALSE);
5457 remset->store_next = remset->data;
5458 next = remset->next;
5459 remset->next = NULL;
5460 if (remset != info->remset) {
5461 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5462 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5465 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j)
5466 handle_remset ((mword*)*info->store_remset_buffer_addr + j + 1, start_nursery, end_nursery, FALSE);
5467 clear_thread_store_remset_buffer (info);
5471 /* the freed thread ones */
5472 while (freed_thread_remsets) {
5473 RememberedSet *next;
5474 remset = freed_thread_remsets;
5475 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for freed thread, range: %p-%p, size: %zd\n", remset->data, remset->store_next, remset->store_next - remset->data));
5476 for (p = remset->data; p < remset->store_next;) {
5477 p = handle_remset (p, start_nursery, end_nursery, FALSE);
5479 next = remset->next;
5480 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5481 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5482 freed_thread_remsets = next;
5487 * Clear the info in the remembered sets: we're doing a major collection, so
5488 * the per-thread ones are not needed and the global ones will be reconstructed
5489 * during the copy.
5491 static void
5492 clear_remsets (void)
5494 int i;
5495 SgenThreadInfo *info;
5496 RememberedSet *remset, *next;
5498 /* the global list */
5499 for (remset = global_remset; remset; remset = next) {
5500 remset->store_next = remset->data;
5501 next = remset->next;
5502 remset->next = NULL;
5503 if (remset != global_remset) {
5504 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5505 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5508 /* the generic store ones */
5509 while (generic_store_remsets) {
5510 GenericStoreRememberedSet *gs_next = generic_store_remsets->next;
5511 free_internal_mem (generic_store_remsets, INTERNAL_MEM_STORE_REMSET);
5512 generic_store_remsets = gs_next;
5514 /* the per-thread ones */
5515 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5516 for (info = thread_table [i]; info; info = info->next) {
5517 for (remset = info->remset; remset; remset = next) {
5518 remset->store_next = remset->data;
5519 next = remset->next;
5520 remset->next = NULL;
5521 if (remset != info->remset) {
5522 DEBUG (3, fprintf (gc_debug_file, "Freed remset at %p\n", remset->data));
5523 free_internal_mem (remset, INTERNAL_MEM_REMSET);
5526 clear_thread_store_remset_buffer (info);
5530 /* the freed thread ones */
5531 while (freed_thread_remsets) {
5532 next = freed_thread_remsets->next;
5533 DEBUG (4, fprintf (gc_debug_file, "Freed remset at %p\n", freed_thread_remsets->data));
5534 free_internal_mem (freed_thread_remsets, INTERNAL_MEM_REMSET);
5535 freed_thread_remsets = next;
5540 * Clear the thread local TLAB variables for all threads.
5542 static void
5543 clear_tlabs (void)
5545 SgenThreadInfo *info;
5546 int i;
5548 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
5549 for (info = thread_table [i]; info; info = info->next) {
5550 /* A new TLAB will be allocated when the thread does its first allocation */
5551 *info->tlab_start_addr = NULL;
5552 *info->tlab_next_addr = NULL;
5553 *info->tlab_temp_end_addr = NULL;
5554 *info->tlab_real_end_addr = NULL;
5559 /* LOCKING: assumes the GC lock is held */
5560 static SgenThreadInfo*
5561 gc_register_current_thread (void *addr)
5563 int hash;
5564 SgenThreadInfo* info = malloc (sizeof (SgenThreadInfo));
5565 #ifndef HAVE_KW_THREAD
5566 SgenThreadInfo *__thread_info__ = info;
5567 #endif
5569 if (!info)
5570 return NULL;
5572 memset (info, 0, sizeof (SgenThreadInfo));
5573 #ifndef HAVE_KW_THREAD
5574 info->tlab_start = info->tlab_next = info->tlab_temp_end = info->tlab_real_end = NULL;
5576 g_assert (!pthread_getspecific (thread_info_key));
5577 pthread_setspecific (thread_info_key, info);
5578 #else
5579 thread_info = info;
5580 #endif
5582 info->id = ARCH_GET_THREAD ();
5583 info->stop_count = -1;
5584 info->skip = 0;
5585 info->signal = 0;
5586 info->stack_start = NULL;
5587 info->tlab_start_addr = &TLAB_START;
5588 info->tlab_next_addr = &TLAB_NEXT;
5589 info->tlab_temp_end_addr = &TLAB_TEMP_END;
5590 info->tlab_real_end_addr = &TLAB_REAL_END;
5591 info->store_remset_buffer_addr = &STORE_REMSET_BUFFER;
5592 info->store_remset_buffer_index_addr = &STORE_REMSET_BUFFER_INDEX;
5593 info->stopped_ip = NULL;
5594 info->stopped_domain = NULL;
5595 info->stopped_regs = NULL;
5597 binary_protocol_thread_register ((gpointer)info->id);
5599 #ifdef HAVE_KW_THREAD
5600 tlab_next_addr = &tlab_next;
5601 store_remset_buffer_index_addr = &store_remset_buffer_index;
5602 #endif
5604 /* try to get it with attributes first */
5605 #if defined(HAVE_PTHREAD_GETATTR_NP) && defined(HAVE_PTHREAD_ATTR_GETSTACK)
5607 size_t size;
5608 void *sstart;
5609 pthread_attr_t attr;
5610 pthread_getattr_np (pthread_self (), &attr);
5611 pthread_attr_getstack (&attr, &sstart, &size);
5612 info->stack_start_limit = sstart;
5613 info->stack_end = (char*)sstart + size;
5614 pthread_attr_destroy (&attr);
5616 #elif defined(HAVE_PTHREAD_GET_STACKSIZE_NP) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
5617 info->stack_end = (char*)pthread_get_stackaddr_np (pthread_self ());
5618 info->stack_start_limit = (char*)info->stack_end - pthread_get_stacksize_np (pthread_self ());
5619 #else
5621 /* FIXME: we assume the stack grows down */
5622 gsize stack_bottom = (gsize)addr;
5623 stack_bottom += 4095;
5624 stack_bottom &= ~4095;
5625 info->stack_end = (char*)stack_bottom;
5627 #endif
5629 #ifdef HAVE_KW_THREAD
5630 stack_end = info->stack_end;
5631 #endif
5633 /* hash into the table */
5634 hash = HASH_PTHREAD_T (info->id) % THREAD_HASH_SIZE;
5635 info->next = thread_table [hash];
5636 thread_table [hash] = info;
5638 info->remset = alloc_remset (DEFAULT_REMSET_SIZE, info);
5639 pthread_setspecific (remembered_set_key, info->remset);
5640 #ifdef HAVE_KW_THREAD
5641 remembered_set = info->remset;
5642 #endif
5644 STORE_REMSET_BUFFER = get_internal_mem (sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE, INTERNAL_MEM_STORE_REMSET);
5645 STORE_REMSET_BUFFER_INDEX = 0;
5647 DEBUG (3, fprintf (gc_debug_file, "registered thread %p (%p) (hash: %d)\n", info, (gpointer)info->id, hash));
5649 if (gc_callbacks.thread_attach_func)
5650 info->runtime_data = gc_callbacks.thread_attach_func ();
5652 return info;
5655 static void
5656 add_generic_store_remset_from_buffer (gpointer *buffer)
5658 GenericStoreRememberedSet *remset = get_internal_mem (sizeof (GenericStoreRememberedSet), INTERNAL_MEM_STORE_REMSET);
5659 memcpy (remset->data, buffer + 1, sizeof (gpointer) * (STORE_REMSET_BUFFER_SIZE - 1));
5660 remset->next = generic_store_remsets;
5661 generic_store_remsets = remset;
5664 static void
5665 unregister_current_thread (void)
5667 int hash;
5668 SgenThreadInfo *prev = NULL;
5669 SgenThreadInfo *p;
5670 RememberedSet *rset;
5671 ARCH_THREAD_TYPE id = ARCH_GET_THREAD ();
5673 binary_protocol_thread_unregister ((gpointer)id);
5675 hash = HASH_PTHREAD_T (id) % THREAD_HASH_SIZE;
5676 p = thread_table [hash];
5677 assert (p);
5678 DEBUG (3, fprintf (gc_debug_file, "unregister thread %p (%p)\n", p, (gpointer)p->id));
5679 while (!ARCH_THREAD_EQUALS (p->id, id)) {
5680 prev = p;
5681 p = p->next;
5683 if (prev == NULL) {
5684 thread_table [hash] = p->next;
5685 } else {
5686 prev->next = p->next;
5688 if (p->remset) {
5689 if (freed_thread_remsets) {
5690 for (rset = p->remset; rset->next; rset = rset->next)
5692 rset->next = freed_thread_remsets;
5693 freed_thread_remsets = p->remset;
5694 } else {
5695 freed_thread_remsets = p->remset;
5698 if (*p->store_remset_buffer_index_addr)
5699 add_generic_store_remset_from_buffer (*p->store_remset_buffer_addr);
5700 free_internal_mem (*p->store_remset_buffer_addr, INTERNAL_MEM_STORE_REMSET);
5701 free (p);
5704 static void
5705 unregister_thread (void *k)
5707 g_assert (!mono_domain_get ());
5708 LOCK_GC;
5709 unregister_current_thread ();
5710 UNLOCK_GC;
5713 gboolean
5714 mono_gc_register_thread (void *baseptr)
5716 SgenThreadInfo *info;
5718 LOCK_GC;
5719 init_stats ();
5720 info = thread_info_lookup (ARCH_GET_THREAD ());
5721 if (info == NULL)
5722 info = gc_register_current_thread (baseptr);
5723 UNLOCK_GC;
5724 return info != NULL;
5727 #if USE_PTHREAD_INTERCEPT
5729 #undef pthread_create
5730 #undef pthread_join
5731 #undef pthread_detach
5733 typedef struct {
5734 void *(*start_routine) (void *);
5735 void *arg;
5736 int flags;
5737 MonoSemType registered;
5738 } SgenThreadStartInfo;
5740 static void*
5741 gc_start_thread (void *arg)
5743 SgenThreadStartInfo *start_info = arg;
5744 SgenThreadInfo* info;
5745 void *t_arg = start_info->arg;
5746 void *(*start_func) (void*) = start_info->start_routine;
5747 void *result;
5748 int post_result;
5750 LOCK_GC;
5751 info = gc_register_current_thread (&result);
5752 UNLOCK_GC;
5753 post_result = MONO_SEM_POST (&(start_info->registered));
5754 g_assert (!post_result);
5755 result = start_func (t_arg);
5756 g_assert (!mono_domain_get ());
5758 * this is done by the pthread key dtor
5759 LOCK_GC;
5760 unregister_current_thread ();
5761 UNLOCK_GC;
5764 return result;
5768 mono_gc_pthread_create (pthread_t *new_thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
5770 SgenThreadStartInfo *start_info;
5771 int result;
5773 start_info = malloc (sizeof (SgenThreadStartInfo));
5774 if (!start_info)
5775 return ENOMEM;
5776 result = MONO_SEM_INIT (&(start_info->registered), 0);
5777 g_assert (!result);
5778 start_info->arg = arg;
5779 start_info->start_routine = start_routine;
5781 result = pthread_create (new_thread, attr, gc_start_thread, start_info);
5782 if (result == 0) {
5783 while (MONO_SEM_WAIT (&(start_info->registered)) != 0) {
5784 /*if (EINTR != errno) ABORT("sem_wait failed"); */
5787 MONO_SEM_DESTROY (&(start_info->registered));
5788 free (start_info);
5789 return result;
5793 mono_gc_pthread_join (pthread_t thread, void **retval)
5795 return pthread_join (thread, retval);
5799 mono_gc_pthread_detach (pthread_t thread)
5801 return pthread_detach (thread);
5804 #endif /* USE_PTHREAD_INTERCEPT */
5807 * ######################################################################
5808 * ######## Write barriers
5809 * ######################################################################
5812 static RememberedSet*
5813 alloc_remset (int size, gpointer id) {
5814 RememberedSet* res = get_internal_mem (sizeof (RememberedSet) + (size * sizeof (gpointer)), INTERNAL_MEM_REMSET);
5815 res->store_next = res->data;
5816 res->end_set = res->data + size;
5817 res->next = NULL;
5818 DEBUG (4, fprintf (gc_debug_file, "Allocated remset size %d at %p for %p\n", size, res->data, id));
5819 return res;
5823 * Note: the write barriers first do the needed GC work and then do the actual store:
5824 * this way the value is visible to the conservative GC scan after the write barrier
5825 * itself. If a GC interrupts the barrier in the middle, value will be kept alive by
5826 * the conservative scan, otherwise by the remembered set scan.
5828 void
5829 mono_gc_wbarrier_set_field (MonoObject *obj, gpointer field_ptr, MonoObject* value)
5831 RememberedSet *rs;
5832 TLAB_ACCESS_INIT;
5833 HEAVY_STAT (++stat_wbarrier_set_field);
5834 if (ptr_in_nursery (field_ptr)) {
5835 *(void**)field_ptr = value;
5836 return;
5838 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", field_ptr));
5839 LOCK_GC;
5840 rs = REMEMBERED_SET;
5841 if (rs->store_next < rs->end_set) {
5842 *(rs->store_next++) = (mword)field_ptr;
5843 *(void**)field_ptr = value;
5844 UNLOCK_GC;
5845 return;
5847 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5848 rs->next = REMEMBERED_SET;
5849 REMEMBERED_SET = rs;
5850 #ifdef HAVE_KW_THREAD
5851 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5852 #endif
5853 *(rs->store_next++) = (mword)field_ptr;
5854 *(void**)field_ptr = value;
5855 UNLOCK_GC;
5858 void
5859 mono_gc_wbarrier_set_arrayref (MonoArray *arr, gpointer slot_ptr, MonoObject* value)
5861 RememberedSet *rs;
5862 TLAB_ACCESS_INIT;
5863 HEAVY_STAT (++stat_wbarrier_set_arrayref);
5864 if (ptr_in_nursery (slot_ptr)) {
5865 *(void**)slot_ptr = value;
5866 return;
5868 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", slot_ptr));
5869 LOCK_GC;
5870 rs = REMEMBERED_SET;
5871 if (rs->store_next < rs->end_set) {
5872 *(rs->store_next++) = (mword)slot_ptr;
5873 *(void**)slot_ptr = value;
5874 UNLOCK_GC;
5875 return;
5877 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5878 rs->next = REMEMBERED_SET;
5879 REMEMBERED_SET = rs;
5880 #ifdef HAVE_KW_THREAD
5881 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5882 #endif
5883 *(rs->store_next++) = (mword)slot_ptr;
5884 *(void**)slot_ptr = value;
5885 UNLOCK_GC;
5888 void
5889 mono_gc_wbarrier_arrayref_copy (gpointer dest_ptr, gpointer src_ptr, int count)
5891 RememberedSet *rs;
5892 TLAB_ACCESS_INIT;
5893 HEAVY_STAT (++stat_wbarrier_arrayref_copy);
5894 LOCK_GC;
5895 memmove (dest_ptr, src_ptr, count * sizeof (gpointer));
5896 if (ptr_in_nursery (dest_ptr)) {
5897 UNLOCK_GC;
5898 return;
5900 rs = REMEMBERED_SET;
5901 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p, %d\n", dest_ptr, count));
5902 if (rs->store_next + 1 < rs->end_set) {
5903 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
5904 *(rs->store_next++) = count;
5905 UNLOCK_GC;
5906 return;
5908 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
5909 rs->next = REMEMBERED_SET;
5910 REMEMBERED_SET = rs;
5911 #ifdef HAVE_KW_THREAD
5912 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
5913 #endif
5914 *(rs->store_next++) = (mword)dest_ptr | REMSET_RANGE;
5915 *(rs->store_next++) = count;
5916 UNLOCK_GC;
5919 static char*
5920 find_object_for_ptr_in_area (char *ptr, char *start, char *end)
5922 while (start < end) {
5923 char *old_start;
5925 if (!*(void**)start) {
5926 start += sizeof (void*); /* should be ALLOC_ALIGN, really */
5927 continue;
5930 old_start = start;
5932 #define SCAN_OBJECT_NOSCAN
5933 #include "sgen-scan-object.h"
5935 if (ptr >= old_start && ptr < start)
5936 return old_start;
5939 return NULL;
5942 static char *found_obj;
5944 static void
5945 find_object_for_ptr_callback (char *obj, size_t size, char *ptr)
5947 if (ptr >= obj && ptr < obj + size) {
5948 g_assert (!found_obj);
5949 found_obj = obj;
5953 /* for use in the debugger */
5954 char* find_object_for_ptr (char *ptr);
5955 char*
5956 find_object_for_ptr (char *ptr)
5958 LOSObject *bigobj;
5960 if (ptr >= nursery_section->data && ptr < nursery_section->end_data)
5961 return find_object_for_ptr_in_area (ptr, nursery_section->data, nursery_section->end_data);
5963 for (bigobj = los_object_list; bigobj; bigobj = bigobj->next) {
5964 if (ptr >= bigobj->data && ptr < bigobj->data + bigobj->size)
5965 return bigobj->data;
5969 * Very inefficient, but this is debugging code, supposed to
5970 * be called from gdb, so we don't care.
5972 found_obj = NULL;
5973 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)find_object_for_ptr_callback, ptr);
5974 return found_obj;
5977 static void
5978 evacuate_remset_buffer (void)
5980 gpointer *buffer;
5981 TLAB_ACCESS_INIT;
5983 buffer = STORE_REMSET_BUFFER;
5985 add_generic_store_remset_from_buffer (buffer);
5986 memset (buffer, 0, sizeof (gpointer) * STORE_REMSET_BUFFER_SIZE);
5988 STORE_REMSET_BUFFER_INDEX = 0;
5991 void
5992 mono_gc_wbarrier_generic_nostore (gpointer ptr)
5994 gpointer *buffer;
5995 int index;
5996 TLAB_ACCESS_INIT;
5998 HEAVY_STAT (++stat_wbarrier_generic_store);
6000 #ifdef XDOMAIN_CHECKS_IN_WBARRIER
6001 /* FIXME: ptr_in_heap must be called with the GC lock held */
6002 if (xdomain_checks && *(MonoObject**)ptr && ptr_in_heap (ptr)) {
6003 char *start = find_object_for_ptr (ptr);
6004 MonoObject *value = *(MonoObject**)ptr;
6005 LOCK_GC;
6006 g_assert (start);
6007 if (start) {
6008 MonoObject *obj = (MonoObject*)start;
6009 if (obj->vtable->domain != value->vtable->domain)
6010 g_assert (is_xdomain_ref_allowed (ptr, start, obj->vtable->domain));
6012 UNLOCK_GC;
6014 #endif
6016 LOCK_GC;
6018 if (*(gpointer*)ptr)
6019 binary_protocol_wbarrier (ptr, *(gpointer*)ptr, (gpointer)LOAD_VTABLE (*(gpointer*)ptr));
6021 if (ptr_in_nursery (ptr) || ptr_on_stack (ptr) || !ptr_in_nursery (*(gpointer*)ptr)) {
6022 DEBUG (8, fprintf (gc_debug_file, "Skipping remset at %p\n", ptr));
6023 UNLOCK_GC;
6024 return;
6027 buffer = STORE_REMSET_BUFFER;
6028 index = STORE_REMSET_BUFFER_INDEX;
6029 /* This simple optimization eliminates a sizable portion of
6030 entries. Comparing it to the last but one entry as well
6031 doesn't eliminate significantly more entries. */
6032 if (buffer [index] == ptr) {
6033 UNLOCK_GC;
6034 return;
6037 DEBUG (8, fprintf (gc_debug_file, "Adding remset at %p\n", ptr));
6038 HEAVY_STAT (++stat_wbarrier_generic_store_remset);
6040 ++index;
6041 if (index >= STORE_REMSET_BUFFER_SIZE) {
6042 evacuate_remset_buffer ();
6043 index = STORE_REMSET_BUFFER_INDEX;
6044 g_assert (index == 0);
6045 ++index;
6047 buffer [index] = ptr;
6048 STORE_REMSET_BUFFER_INDEX = index;
6050 UNLOCK_GC;
6053 void
6054 mono_gc_wbarrier_generic_store (gpointer ptr, MonoObject* value)
6056 DEBUG (8, fprintf (gc_debug_file, "Wbarrier store at %p to %p (%s)\n", ptr, value, value ? safe_name (value) : "null"));
6057 *(void**)ptr = value;
6058 if (ptr_in_nursery (value))
6059 mono_gc_wbarrier_generic_nostore (ptr);
6062 void
6063 mono_gc_wbarrier_set_root (gpointer ptr, MonoObject *value)
6065 RememberedSet *rs;
6066 TLAB_ACCESS_INIT;
6067 HEAVY_STAT (++stat_wbarrier_set_root);
6068 if (ptr_in_nursery (ptr))
6069 return;
6070 DEBUG (8, fprintf (gc_debug_file, "Adding root remset at %p (%s)\n", ptr, value ? safe_name (value) : "null"));
6072 rs = REMEMBERED_SET;
6073 if (rs->store_next + 2 < rs->end_set) {
6074 *(rs->store_next++) = (mword)ptr | REMSET_OTHER;
6075 *(rs->store_next++) = (mword)REMSET_ROOT_LOCATION;
6076 *(void**)ptr = value;
6077 return;
6079 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6080 rs->next = REMEMBERED_SET;
6081 REMEMBERED_SET = rs;
6082 #ifdef HAVE_KW_THREAD
6083 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6084 #endif
6085 *(rs->store_next++) = (mword)ptr | REMSET_OTHER;
6086 *(rs->store_next++) = (mword)REMSET_ROOT_LOCATION;
6088 *(void**)ptr = value;
6091 void
6092 mono_gc_wbarrier_value_copy (gpointer dest, gpointer src, int count, MonoClass *klass)
6094 RememberedSet *rs;
6095 TLAB_ACCESS_INIT;
6096 HEAVY_STAT (++stat_wbarrier_value_copy);
6097 g_assert (klass->valuetype);
6098 LOCK_GC;
6099 memmove (dest, src, count * mono_class_value_size (klass, NULL));
6100 rs = REMEMBERED_SET;
6101 if (ptr_in_nursery (dest) || ptr_on_stack (dest)) {
6102 UNLOCK_GC;
6103 return;
6105 g_assert (klass->gc_descr_inited);
6106 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));
6108 if (rs->store_next + 3 < rs->end_set) {
6109 *(rs->store_next++) = (mword)dest | REMSET_OTHER;
6110 *(rs->store_next++) = (mword)REMSET_VTYPE;
6111 *(rs->store_next++) = (mword)klass->gc_descr;
6112 *(rs->store_next++) = (mword)count;
6113 UNLOCK_GC;
6114 return;
6116 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6117 rs->next = REMEMBERED_SET;
6118 REMEMBERED_SET = rs;
6119 #ifdef HAVE_KW_THREAD
6120 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6121 #endif
6122 *(rs->store_next++) = (mword)dest | REMSET_OTHER;
6123 *(rs->store_next++) = (mword)REMSET_VTYPE;
6124 *(rs->store_next++) = (mword)klass->gc_descr;
6125 *(rs->store_next++) = (mword)count;
6126 UNLOCK_GC;
6130 * mono_gc_wbarrier_object_copy:
6132 * Write barrier to call when obj is the result of a clone or copy of an object.
6134 void
6135 mono_gc_wbarrier_object_copy (MonoObject* obj, MonoObject *src)
6137 RememberedSet *rs;
6138 int size;
6140 TLAB_ACCESS_INIT;
6141 HEAVY_STAT (++stat_wbarrier_object_copy);
6142 rs = REMEMBERED_SET;
6143 DEBUG (6, fprintf (gc_debug_file, "Adding object remset for %p\n", obj));
6144 size = mono_object_class (obj)->instance_size;
6145 LOCK_GC;
6146 /* do not copy the sync state */
6147 memcpy ((char*)obj + sizeof (MonoObject), (char*)src + sizeof (MonoObject),
6148 size - sizeof (MonoObject));
6149 if (ptr_in_nursery (obj) || ptr_on_stack (obj)) {
6150 UNLOCK_GC;
6151 return;
6153 if (rs->store_next < rs->end_set) {
6154 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
6155 UNLOCK_GC;
6156 return;
6158 rs = alloc_remset (rs->end_set - rs->data, (void*)1);
6159 rs->next = REMEMBERED_SET;
6160 REMEMBERED_SET = rs;
6161 #ifdef HAVE_KW_THREAD
6162 thread_info_lookup (ARCH_GET_THREAD ())->remset = rs;
6163 #endif
6164 *(rs->store_next++) = (mword)obj | REMSET_OBJECT;
6165 UNLOCK_GC;
6169 * ######################################################################
6170 * ######## Collector debugging
6171 * ######################################################################
6174 const char*descriptor_types [] = {
6175 "run_length",
6176 "small_bitmap",
6177 "string",
6178 "complex",
6179 "vector",
6180 "array",
6181 "large_bitmap",
6182 "complex_arr"
6185 void
6186 describe_ptr (char *ptr)
6188 MonoVTable *vtable;
6189 mword desc;
6190 int type;
6192 if (ptr_in_nursery (ptr)) {
6193 printf ("Pointer inside nursery.\n");
6194 } else {
6195 if (major_ptr_is_in_non_pinned_space (ptr)) {
6196 printf ("Pointer inside oldspace.\n");
6197 } else if (obj_is_from_pinned_alloc (ptr)) {
6198 printf ("Pointer is inside a pinned chunk.\n");
6199 } else {
6200 printf ("Pointer unknown.\n");
6201 return;
6205 if (object_is_pinned (ptr))
6206 printf ("Object is pinned.\n");
6208 if (object_is_forwarded (ptr))
6209 printf ("Object is forwared.\n");
6211 // FIXME: Handle pointers to the inside of objects
6212 vtable = (MonoVTable*)LOAD_VTABLE (ptr);
6214 printf ("VTable: %p\n", vtable);
6215 if (vtable == NULL) {
6216 printf ("VTable is invalid (empty).\n");
6217 return;
6219 if (ptr_in_nursery (vtable)) {
6220 printf ("VTable is invalid (points inside nursery).\n");
6221 return;
6223 printf ("Class: %s\n", vtable->klass->name);
6225 desc = ((GCVTable*)vtable)->desc;
6226 printf ("Descriptor: %lx\n", (long)desc);
6228 type = desc & 0x7;
6229 printf ("Descriptor type: %d (%s)\n", type, descriptor_types [type]);
6232 static mword*
6233 find_in_remset_loc (mword *p, char *addr, gboolean *found)
6235 void **ptr;
6236 mword count, desc;
6237 size_t skip_size;
6239 switch ((*p) & REMSET_TYPE_MASK) {
6240 case REMSET_LOCATION:
6241 if (*p == (mword)addr)
6242 *found = TRUE;
6243 return p + 1;
6244 case REMSET_RANGE:
6245 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6246 count = p [1];
6247 if ((void**)addr >= ptr && (void**)addr < ptr + count)
6248 *found = TRUE;
6249 return p + 2;
6250 case REMSET_OBJECT:
6251 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6252 count = safe_object_get_size ((MonoObject*)ptr);
6253 count += (ALLOC_ALIGN - 1);
6254 count &= (ALLOC_ALIGN - 1);
6255 count /= sizeof (mword);
6256 if ((void**)addr >= ptr && (void**)addr < ptr + count)
6257 *found = TRUE;
6258 return p + 1;
6259 case REMSET_OTHER: {
6260 switch (p [1]) {
6261 case REMSET_VTYPE:
6262 ptr = (void**)(*p & ~REMSET_TYPE_MASK);
6263 desc = p [2];
6264 count = p [3];
6266 switch (desc & 0x7) {
6267 case DESC_TYPE_RUN_LENGTH:
6268 OBJ_RUN_LEN_SIZE (skip_size, desc, ptr);
6269 break;
6270 case DESC_TYPE_SMALL_BITMAP:
6271 OBJ_BITMAP_SIZE (skip_size, desc, start);
6272 break;
6273 default:
6274 // FIXME:
6275 g_assert_not_reached ();
6278 /* The descriptor includes the size of MonoObject */
6279 skip_size -= sizeof (MonoObject);
6280 skip_size *= count;
6281 if ((void**)addr >= ptr && (void**)addr < ptr + (skip_size / sizeof (gpointer)))
6282 *found = TRUE;
6284 return p + 4;
6285 case REMSET_ROOT_LOCATION:
6286 return p + 2;
6287 default:
6288 g_assert_not_reached ();
6290 break;
6292 default:
6293 g_assert_not_reached ();
6295 return NULL;
6299 * Return whenever ADDR occurs in the remembered sets
6301 static gboolean
6302 find_in_remsets (char *addr)
6304 int i;
6305 SgenThreadInfo *info;
6306 RememberedSet *remset;
6307 GenericStoreRememberedSet *store_remset;
6308 mword *p;
6309 gboolean found = FALSE;
6311 /* the global one */
6312 for (remset = global_remset; remset; remset = remset->next) {
6313 DEBUG (4, fprintf (gc_debug_file, "Scanning global remset range: %p-%p, size: %zd\n", remset->data, remset->store_next, remset->store_next - remset->data));
6314 for (p = remset->data; p < remset->store_next;) {
6315 p = find_in_remset_loc (p, addr, &found);
6316 if (found)
6317 return TRUE;
6321 /* the generic store ones */
6322 for (store_remset = generic_store_remsets; store_remset; store_remset = store_remset->next) {
6323 for (i = 0; i < STORE_REMSET_BUFFER_SIZE - 1; ++i) {
6324 if (store_remset->data [i] == addr)
6325 return TRUE;
6329 /* the per-thread ones */
6330 for (i = 0; i < THREAD_HASH_SIZE; ++i) {
6331 for (info = thread_table [i]; info; info = info->next) {
6332 int j;
6333 for (remset = info->remset; remset; remset = remset->next) {
6334 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for thread %p, range: %p-%p, size: %zd\n", info, remset->data, remset->store_next, remset->store_next - remset->data));
6335 for (p = remset->data; p < remset->store_next;) {
6336 p = find_in_remset_loc (p, addr, &found);
6337 if (found)
6338 return TRUE;
6341 for (j = 0; j < *info->store_remset_buffer_index_addr; ++j) {
6342 if ((*info->store_remset_buffer_addr) [j + 1] == addr)
6343 return TRUE;
6348 /* the freed thread ones */
6349 for (remset = freed_thread_remsets; remset; remset = remset->next) {
6350 DEBUG (4, fprintf (gc_debug_file, "Scanning remset for freed thread, range: %p-%p, size: %zd\n", remset->data, remset->store_next, remset->store_next - remset->data));
6351 for (p = remset->data; p < remset->store_next;) {
6352 p = find_in_remset_loc (p, addr, &found);
6353 if (found)
6354 return TRUE;
6358 return FALSE;
6361 static gboolean missing_remsets;
6364 * We let a missing remset slide if the target object is pinned,
6365 * because the store might have happened but the remset not yet added,
6366 * but in that case the target must be pinned. We might theoretically
6367 * miss some missing remsets this way, but it's very unlikely.
6369 #undef HANDLE_PTR
6370 #define HANDLE_PTR(ptr,obj) do { \
6371 if (*(ptr) && (char*)*(ptr) >= nursery_start && (char*)*(ptr) < nursery_next) { \
6372 if (!find_in_remsets ((char*)(ptr))) { \
6373 fprintf (gc_debug_file, "Oldspace->newspace reference %p at offset %zd 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); \
6374 binary_protocol_missing_remset ((obj), (gpointer)LOAD_VTABLE ((obj)), (char*)(ptr) - (char*)(obj), *(ptr), (gpointer)LOAD_VTABLE(*(ptr)), object_is_pinned (*(ptr))); \
6375 if (!object_is_pinned (*(ptr))) \
6376 missing_remsets = TRUE; \
6379 } while (0)
6382 * Check that each object reference which points into the nursery can
6383 * be found in the remembered sets.
6385 static void
6386 check_consistency_callback (char *start, size_t size, void *dummy)
6388 GCVTable *vt = (GCVTable*)LOAD_VTABLE (start);
6389 DEBUG (8, fprintf (gc_debug_file, "Scanning object %p, vtable: %p (%s)\n", start, vt, vt->klass->name));
6391 #define SCAN_OBJECT_ACTION
6392 #include "sgen-scan-object.h"
6396 * Perform consistency check of the heap.
6398 * Assumes the world is stopped.
6400 static void
6401 check_consistency (void)
6403 // Need to add more checks
6405 missing_remsets = FALSE;
6407 DEBUG (1, fprintf (gc_debug_file, "Begin heap consistency check...\n"));
6409 // Check that oldspace->newspace pointers are registered with the collector
6410 major_iterate_objects (TRUE, TRUE, (IterateObjectCallbackFunc)check_consistency_callback, NULL);
6412 DEBUG (1, fprintf (gc_debug_file, "Heap consistency check done.\n"));
6414 #ifdef BINARY_PROTOCOL
6415 if (!binary_protocol_file)
6416 #endif
6417 g_assert (!missing_remsets);
6420 /* Check that the reference is valid */
6421 #undef HANDLE_PTR
6422 #define HANDLE_PTR(ptr,obj) do { \
6423 if (*(ptr)) { \
6424 g_assert (safe_name (*(ptr)) != NULL); \
6426 } while (0)
6429 * check_object:
6431 * Perform consistency check on an object. Currently we only check that the
6432 * reference fields are valid.
6434 char*
6435 check_object (char *start)
6437 if (!start)
6438 return NULL;
6440 #include "sgen-scan-object.h"
6442 return start;
6446 * ######################################################################
6447 * ######## Other mono public interface functions.
6448 * ######################################################################
6451 void
6452 mono_gc_collect (int generation)
6454 LOCK_GC;
6455 stop_world ();
6456 if (generation == 0) {
6457 collect_nursery (0);
6458 } else {
6459 major_collection ("user request");
6461 restart_world ();
6462 UNLOCK_GC;
6466 mono_gc_max_generation (void)
6468 return 1;
6472 mono_gc_collection_count (int generation)
6474 if (generation == 0)
6475 return num_minor_gcs;
6476 return num_major_gcs;
6479 int64_t
6480 mono_gc_get_used_size (void)
6482 gint64 tot = 0;
6483 LOCK_GC;
6484 tot = los_memory_usage;
6485 tot += nursery_section->next_data - nursery_section->data;
6486 tot += major_get_used_size ();
6487 /* FIXME: account for pinned objects */
6488 UNLOCK_GC;
6489 return tot;
6492 int64_t
6493 mono_gc_get_heap_size (void)
6495 return total_alloc;
6498 void
6499 mono_gc_disable (void)
6501 LOCK_GC;
6502 gc_disabled++;
6503 UNLOCK_GC;
6506 void
6507 mono_gc_enable (void)
6509 LOCK_GC;
6510 gc_disabled--;
6511 UNLOCK_GC;
6515 mono_gc_get_los_limit (void)
6517 return MAX_SMALL_OBJ_SIZE;
6520 gboolean
6521 mono_object_is_alive (MonoObject* o)
6523 return TRUE;
6527 mono_gc_get_generation (MonoObject *obj)
6529 if (ptr_in_nursery (obj))
6530 return 0;
6531 return 1;
6534 void
6535 mono_gc_enable_events (void)
6539 void
6540 mono_gc_weak_link_add (void **link_addr, MonoObject *obj, gboolean track)
6542 LOCK_GC;
6543 mono_gc_register_disappearing_link (obj, link_addr, track);
6544 UNLOCK_GC;
6547 void
6548 mono_gc_weak_link_remove (void **link_addr)
6550 LOCK_GC;
6551 mono_gc_register_disappearing_link (NULL, link_addr, FALSE);
6552 UNLOCK_GC;
6555 MonoObject*
6556 mono_gc_weak_link_get (void **link_addr)
6558 if (!*link_addr)
6559 return NULL;
6560 return (MonoObject*) REVEAL_POINTER (*link_addr);
6563 void*
6564 mono_gc_make_descr_from_bitmap (gsize *bitmap, int numbits)
6566 if (numbits < ((sizeof (*bitmap) * 8) - ROOT_DESC_TYPE_SHIFT)) {
6567 return (void*)MAKE_ROOT_DESC (ROOT_DESC_BITMAP, bitmap [0]);
6568 } else {
6569 mword complex = alloc_complex_descriptor (bitmap, numbits);
6570 return (void*)MAKE_ROOT_DESC (ROOT_DESC_COMPLEX, complex);
6574 void*
6575 mono_gc_make_root_descr_user (MonoGCRootMarkFunc marker)
6577 void *descr;
6579 g_assert (user_descriptors_next < MAX_USER_DESCRIPTORS);
6580 descr = (void*)MAKE_ROOT_DESC (ROOT_DESC_USER, (mword)user_descriptors_next);
6581 user_descriptors [user_descriptors_next ++] = marker;
6583 return descr;
6586 void*
6587 mono_gc_alloc_fixed (size_t size, void *descr)
6589 /* FIXME: do a single allocation */
6590 void *res = calloc (1, size);
6591 if (!res)
6592 return NULL;
6593 if (!mono_gc_register_root (res, size, descr)) {
6594 free (res);
6595 res = NULL;
6597 return res;
6600 void
6601 mono_gc_free_fixed (void* addr)
6603 mono_gc_deregister_root (addr);
6604 free (addr);
6607 void*
6608 mono_gc_invoke_with_gc_lock (MonoGCLockedCallbackFunc func, void *data)
6610 void *result;
6611 LOCK_INTERRUPTION;
6612 result = func (data);
6613 UNLOCK_INTERRUPTION;
6614 return result;
6617 gboolean
6618 mono_gc_is_gc_thread (void)
6620 gboolean result;
6621 LOCK_GC;
6622 result = thread_info_lookup (ARCH_GET_THREAD ()) != NULL;
6623 UNLOCK_GC;
6624 return result;
6627 #ifdef USER_CONFIG
6629 /* Tries to extract a number from the passed string, taking in to account m, k
6630 * and g suffixes */
6631 static gboolean
6632 parse_environment_string_extract_number (gchar *str, glong *out)
6634 char *endptr;
6635 int len = strlen (str), shift = 0;
6636 glong val;
6637 gboolean is_suffix = FALSE;
6638 char suffix;
6640 switch (str [len - 1]) {
6641 case 'g':
6642 case 'G':
6643 shift += 10;
6644 case 'm':
6645 case 'M':
6646 shift += 10;
6647 case 'k':
6648 case 'K':
6649 shift += 10;
6650 is_suffix = TRUE;
6651 suffix = str [len - 1];
6652 break;
6655 errno = 0;
6656 val = strtol (str, &endptr, 10);
6658 if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN))
6659 || (errno != 0 && val == 0) || (endptr == str))
6660 return FALSE;
6662 if (is_suffix) {
6663 if (*(endptr + 1)) /* Invalid string. */
6664 return FALSE;
6665 val <<= shift;
6668 *out = val;
6669 return TRUE;
6672 #endif
6674 void
6675 mono_gc_base_init (void)
6677 char *env;
6678 char **opts, **ptr;
6679 struct sigaction sinfo;
6681 LOCK_INIT (gc_mutex);
6682 LOCK_GC;
6683 if (gc_initialized) {
6684 UNLOCK_GC;
6685 return;
6687 pagesize = mono_pagesize ();
6688 gc_debug_file = stderr;
6690 #ifdef USER_CONFIG
6692 if ((env = getenv ("MONO_GC_PARAMS"))) {
6693 if (g_str_has_prefix (env, "nursery-size")) {
6694 int index = 0;
6695 long val;
6696 while (env [index] && env [index++] != '=')
6698 if (env [index] && parse_environment_string_extract_number (env
6699 + index, &val)) {
6700 default_nursery_size = val;
6701 #ifdef ALIGN_NURSERY
6702 if ((val & (val - 1))) {
6703 fprintf (stderr, "The nursery size must be a power of two.\n");
6704 exit (1);
6707 default_nursery_bits = 0;
6708 while (1 << (++ default_nursery_bits) != default_nursery_size)
6710 #endif
6711 } else {
6712 fprintf (stderr, "nursery-size must be an integer.\n");
6713 exit (1);
6715 } else {
6716 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");
6717 exit (1);
6721 #endif
6723 nursery_size = DEFAULT_NURSERY_SIZE;
6725 major_init ();
6727 if ((env = getenv ("MONO_GC_DEBUG"))) {
6728 opts = g_strsplit (env, ",", -1);
6729 for (ptr = opts; ptr && *ptr; ptr ++) {
6730 char *opt = *ptr;
6731 if (opt [0] >= '0' && opt [0] <= '9') {
6732 gc_debug_level = atoi (opt);
6733 opt++;
6734 if (opt [0] == ':')
6735 opt++;
6736 if (opt [0]) {
6737 char *rf = g_strdup_printf ("%s.%d", opt, getpid ());
6738 gc_debug_file = fopen (rf, "wb");
6739 if (!gc_debug_file)
6740 gc_debug_file = stderr;
6741 g_free (rf);
6743 } else if (!strcmp (opt, "collect-before-allocs")) {
6744 collect_before_allocs = TRUE;
6745 } else if (!strcmp (opt, "check-at-minor-collections")) {
6746 consistency_check_at_minor_collection = TRUE;
6747 } else if (!strcmp (opt, "xdomain-checks")) {
6748 xdomain_checks = TRUE;
6749 } else if (!strcmp (opt, "clear-at-gc")) {
6750 nursery_clear_policy = CLEAR_AT_GC;
6751 } else if (!strcmp (opt, "conservative-stack-mark")) {
6752 conservative_stack_mark = TRUE;
6753 } else if (!strcmp (opt, "check-scan-starts")) {
6754 do_scan_starts_check = TRUE;
6755 } else if (g_str_has_prefix (opt, "heap-dump=")) {
6756 char *filename = strchr (opt, '=') + 1;
6757 nursery_clear_policy = CLEAR_AT_GC;
6758 heap_dump_file = fopen (filename, "w");
6759 if (heap_dump_file)
6760 fprintf (heap_dump_file, "<sgen-dump>\n");
6761 #ifdef BINARY_PROTOCOL
6762 } else if (g_str_has_prefix (opt, "binary-protocol=")) {
6763 char *filename = strchr (opt, '=') + 1;
6764 binary_protocol_file = fopen (filename, "w");
6765 #endif
6766 } else {
6767 fprintf (stderr, "Invalid format for the MONO_GC_DEBUG env variable: '%s'\n", env);
6768 fprintf (stderr, "The format is: MONO_GC_DEBUG=[l[:filename]|<option>]+ where l is a debug level 0-9.\n");
6769 fprintf (stderr, "Valid options are: collect-before-allocs, check-at-minor-collections, xdomain-checks, clear-at-gc.\n");
6770 exit (1);
6773 g_strfreev (opts);
6776 suspend_ack_semaphore_ptr = &suspend_ack_semaphore;
6777 MONO_SEM_INIT (&suspend_ack_semaphore, 0);
6779 sigfillset (&sinfo.sa_mask);
6780 sinfo.sa_flags = SA_RESTART | SA_SIGINFO;
6781 sinfo.sa_sigaction = suspend_handler;
6782 if (sigaction (suspend_signal_num, &sinfo, NULL) != 0) {
6783 g_error ("failed sigaction");
6786 sinfo.sa_handler = restart_handler;
6787 if (sigaction (restart_signal_num, &sinfo, NULL) != 0) {
6788 g_error ("failed sigaction");
6791 sigfillset (&suspend_signal_mask);
6792 sigdelset (&suspend_signal_mask, restart_signal_num);
6794 global_remset = alloc_remset (1024, NULL);
6795 global_remset->next = NULL;
6797 pthread_key_create (&remembered_set_key, unregister_thread);
6799 #ifndef HAVE_KW_THREAD
6800 pthread_key_create (&thread_info_key, NULL);
6801 #endif
6803 gc_initialized = TRUE;
6804 UNLOCK_GC;
6805 mono_gc_register_thread (&sinfo);
6809 mono_gc_get_suspend_signal (void)
6811 return suspend_signal_num;
6814 enum {
6815 ATYPE_NORMAL,
6816 ATYPE_VECTOR,
6817 ATYPE_SMALL,
6818 ATYPE_NUM
6821 #ifdef HAVE_KW_THREAD
6822 #define EMIT_TLS_ACCESS(mb,dummy,offset) do { \
6823 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
6824 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
6825 mono_mb_emit_i4 ((mb), (offset)); \
6826 } while (0)
6827 #else
6828 #define EMIT_TLS_ACCESS(mb,member,dummy) do { \
6829 mono_mb_emit_byte ((mb), MONO_CUSTOM_PREFIX); \
6830 mono_mb_emit_byte ((mb), CEE_MONO_TLS); \
6831 mono_mb_emit_i4 ((mb), thread_info_key); \
6832 mono_mb_emit_icon ((mb), G_STRUCT_OFFSET (SgenThreadInfo, member)); \
6833 mono_mb_emit_byte ((mb), CEE_ADD); \
6834 mono_mb_emit_byte ((mb), CEE_LDIND_I); \
6835 } while (0)
6836 #endif
6838 #ifdef MANAGED_ALLOCATION
6839 /* FIXME: Do this in the JIT, where specialized allocation sequences can be created
6840 * for each class. This is currently not easy to do, as it is hard to generate basic
6841 * blocks + branches, but it is easy with the linear IL codebase.
6843 * For this to work we'd need to solve the TLAB race, first. Now we
6844 * require the allocator to be in a few known methods to make sure
6845 * that they are executed atomically via the restart mechanism.
6847 static MonoMethod*
6848 create_allocator (int atype)
6850 int p_var, size_var;
6851 guint32 slowpath_branch, max_size_branch;
6852 MonoMethodBuilder *mb;
6853 MonoMethod *res;
6854 MonoMethodSignature *csig;
6855 static gboolean registered = FALSE;
6856 int tlab_next_addr_var, new_next_var;
6857 int num_params, i;
6858 const char *name = NULL;
6859 AllocatorWrapperInfo *info;
6861 #ifdef HAVE_KW_THREAD
6862 int tlab_next_addr_offset = -1;
6863 int tlab_temp_end_offset = -1;
6865 MONO_THREAD_VAR_OFFSET (tlab_next_addr, tlab_next_addr_offset);
6866 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
6868 g_assert (tlab_next_addr_offset != -1);
6869 g_assert (tlab_temp_end_offset != -1);
6870 #endif
6872 if (!registered) {
6873 mono_register_jit_icall (mono_gc_alloc_obj, "mono_gc_alloc_obj", mono_create_icall_signature ("object ptr int"), FALSE);
6874 mono_register_jit_icall (mono_gc_alloc_vector, "mono_gc_alloc_vector", mono_create_icall_signature ("object ptr int int"), FALSE);
6875 registered = TRUE;
6878 if (atype == ATYPE_SMALL) {
6879 num_params = 1;
6880 name = "AllocSmall";
6881 } else if (atype == ATYPE_NORMAL) {
6882 num_params = 1;
6883 name = "Alloc";
6884 } else if (atype == ATYPE_VECTOR) {
6885 num_params = 2;
6886 name = "AllocVector";
6887 } else {
6888 g_assert_not_reached ();
6891 csig = mono_metadata_signature_alloc (mono_defaults.corlib, num_params);
6892 csig->ret = &mono_defaults.object_class->byval_arg;
6893 for (i = 0; i < num_params; ++i)
6894 csig->params [i] = &mono_defaults.int_class->byval_arg;
6896 mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_ALLOC);
6897 size_var = mono_mb_add_local (mb, &mono_defaults.int32_class->byval_arg);
6898 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
6899 /* size = vtable->klass->instance_size; */
6900 mono_mb_emit_ldarg (mb, 0);
6901 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
6902 mono_mb_emit_byte (mb, CEE_ADD);
6903 mono_mb_emit_byte (mb, CEE_LDIND_I);
6904 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, instance_size));
6905 mono_mb_emit_byte (mb, CEE_ADD);
6906 /* FIXME: assert instance_size stays a 4 byte integer */
6907 mono_mb_emit_byte (mb, CEE_LDIND_U4);
6908 mono_mb_emit_stloc (mb, size_var);
6909 } else if (atype == ATYPE_VECTOR) {
6910 MonoExceptionClause *clause;
6911 int pos, pos_leave;
6912 MonoClass *oom_exc_class;
6913 MonoMethod *ctor;
6915 /* n > MONO_ARRAY_MAX_INDEX -> OverflowException */
6916 mono_mb_emit_ldarg (mb, 1);
6917 mono_mb_emit_icon (mb, MONO_ARRAY_MAX_INDEX);
6918 pos = mono_mb_emit_short_branch (mb, CEE_BLE_UN_S);
6919 mono_mb_emit_exception (mb, "OverflowException", NULL);
6920 mono_mb_patch_short_branch (mb, pos);
6922 clause = mono_image_alloc0 (mono_defaults.corlib, sizeof (MonoExceptionClause));
6923 clause->try_offset = mono_mb_get_label (mb);
6925 /* vtable->klass->sizes.element_size */
6926 mono_mb_emit_ldarg (mb, 0);
6927 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoVTable, klass));
6928 mono_mb_emit_byte (mb, CEE_ADD);
6929 mono_mb_emit_byte (mb, CEE_LDIND_I);
6930 mono_mb_emit_icon (mb, G_STRUCT_OFFSET (MonoClass, sizes.element_size));
6931 mono_mb_emit_byte (mb, CEE_ADD);
6932 mono_mb_emit_byte (mb, CEE_LDIND_U4);
6934 /* * n */
6935 mono_mb_emit_ldarg (mb, 1);
6936 mono_mb_emit_byte (mb, CEE_MUL_OVF_UN);
6937 /* + sizeof (MonoArray) */
6938 mono_mb_emit_icon (mb, sizeof (MonoArray));
6939 mono_mb_emit_byte (mb, CEE_ADD_OVF_UN);
6940 mono_mb_emit_stloc (mb, size_var);
6942 pos_leave = mono_mb_emit_branch (mb, CEE_LEAVE);
6944 /* catch */
6945 clause->flags = MONO_EXCEPTION_CLAUSE_NONE;
6946 clause->try_len = mono_mb_get_pos (mb) - clause->try_offset;
6947 clause->data.catch_class = mono_class_from_name (mono_defaults.corlib,
6948 "System", "OverflowException");
6949 g_assert (clause->data.catch_class);
6950 clause->handler_offset = mono_mb_get_label (mb);
6952 oom_exc_class = mono_class_from_name (mono_defaults.corlib,
6953 "System", "OutOfMemoryException");
6954 g_assert (oom_exc_class);
6955 ctor = mono_class_get_method_from_name (oom_exc_class, ".ctor", 0);
6956 g_assert (ctor);
6958 mono_mb_emit_byte (mb, CEE_POP);
6959 mono_mb_emit_op (mb, CEE_NEWOBJ, ctor);
6960 mono_mb_emit_byte (mb, CEE_THROW);
6962 clause->handler_len = mono_mb_get_pos (mb) - clause->handler_offset;
6963 mono_mb_set_clauses (mb, 1, clause);
6964 mono_mb_patch_branch (mb, pos_leave);
6965 /* end catch */
6966 } else {
6967 g_assert_not_reached ();
6970 /* size += ALLOC_ALIGN - 1; */
6971 mono_mb_emit_ldloc (mb, size_var);
6972 mono_mb_emit_icon (mb, ALLOC_ALIGN - 1);
6973 mono_mb_emit_byte (mb, CEE_ADD);
6974 /* size &= ~(ALLOC_ALIGN - 1); */
6975 mono_mb_emit_icon (mb, ~(ALLOC_ALIGN - 1));
6976 mono_mb_emit_byte (mb, CEE_AND);
6977 mono_mb_emit_stloc (mb, size_var);
6979 /* if (size > MAX_SMALL_OBJ_SIZE) goto slowpath */
6980 if (atype != ATYPE_SMALL) {
6981 mono_mb_emit_ldloc (mb, size_var);
6982 mono_mb_emit_icon (mb, MAX_SMALL_OBJ_SIZE);
6983 max_size_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BGT_S);
6987 * We need to modify tlab_next, but the JIT only supports reading, so we read
6988 * another tls var holding its address instead.
6991 /* tlab_next_addr (local) = tlab_next_addr (TLS var) */
6992 tlab_next_addr_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
6993 EMIT_TLS_ACCESS (mb, tlab_next_addr, tlab_next_addr_offset);
6994 mono_mb_emit_stloc (mb, tlab_next_addr_var);
6996 /* p = (void**)tlab_next; */
6997 p_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
6998 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
6999 mono_mb_emit_byte (mb, CEE_LDIND_I);
7000 mono_mb_emit_stloc (mb, p_var);
7002 /* new_next = (char*)p + size; */
7003 new_next_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7004 mono_mb_emit_ldloc (mb, p_var);
7005 mono_mb_emit_ldloc (mb, size_var);
7006 mono_mb_emit_byte (mb, CEE_CONV_I);
7007 mono_mb_emit_byte (mb, CEE_ADD);
7008 mono_mb_emit_stloc (mb, new_next_var);
7010 /* tlab_next = new_next */
7011 mono_mb_emit_ldloc (mb, tlab_next_addr_var);
7012 mono_mb_emit_ldloc (mb, new_next_var);
7013 mono_mb_emit_byte (mb, CEE_STIND_I);
7015 /* if (G_LIKELY (new_next < tlab_temp_end)) */
7016 mono_mb_emit_ldloc (mb, new_next_var);
7017 EMIT_TLS_ACCESS (mb, tlab_temp_end, tlab_temp_end_offset);
7018 slowpath_branch = mono_mb_emit_short_branch (mb, MONO_CEE_BLT_UN_S);
7020 /* Slowpath */
7021 if (atype != ATYPE_SMALL)
7022 mono_mb_patch_short_branch (mb, max_size_branch);
7024 mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
7025 mono_mb_emit_byte (mb, CEE_MONO_NOT_TAKEN);
7027 /* FIXME: mono_gc_alloc_obj takes a 'size_t' as an argument, not an int32 */
7028 mono_mb_emit_ldarg (mb, 0);
7029 mono_mb_emit_ldloc (mb, size_var);
7030 if (atype == ATYPE_NORMAL || atype == ATYPE_SMALL) {
7031 mono_mb_emit_icall (mb, mono_gc_alloc_obj);
7032 } else if (atype == ATYPE_VECTOR) {
7033 mono_mb_emit_ldarg (mb, 1);
7034 mono_mb_emit_icall (mb, mono_gc_alloc_vector);
7035 } else {
7036 g_assert_not_reached ();
7038 mono_mb_emit_byte (mb, CEE_RET);
7040 /* Fastpath */
7041 mono_mb_patch_short_branch (mb, slowpath_branch);
7043 /* FIXME: Memory barrier */
7045 /* *p = vtable; */
7046 mono_mb_emit_ldloc (mb, p_var);
7047 mono_mb_emit_ldarg (mb, 0);
7048 mono_mb_emit_byte (mb, CEE_STIND_I);
7050 if (atype == ATYPE_VECTOR) {
7051 /* arr->max_length = max_length; */
7052 mono_mb_emit_ldloc (mb, p_var);
7053 mono_mb_emit_ldflda (mb, G_STRUCT_OFFSET (MonoArray, max_length));
7054 mono_mb_emit_ldarg (mb, 1);
7055 mono_mb_emit_byte (mb, CEE_STIND_I);
7058 /* return p */
7059 mono_mb_emit_ldloc (mb, p_var);
7060 mono_mb_emit_byte (mb, CEE_RET);
7062 res = mono_mb_create_method (mb, csig, 8);
7063 mono_mb_free (mb);
7064 mono_method_get_header (res)->init_locals = FALSE;
7066 info = mono_image_alloc0 (mono_defaults.corlib, sizeof (AllocatorWrapperInfo));
7067 info->alloc_type = atype;
7068 mono_marshal_set_wrapper_info (res, info);
7070 return res;
7072 #endif
7074 static MonoMethod* alloc_method_cache [ATYPE_NUM];
7075 static MonoMethod *write_barrier_method;
7077 static gboolean
7078 is_ip_in_managed_allocator (MonoDomain *domain, gpointer ip)
7080 MonoJitInfo *ji;
7081 MonoMethod *method;
7082 int i;
7084 if (!ip || !domain)
7085 return FALSE;
7086 ji = mono_jit_info_table_find (domain, ip);
7087 if (!ji)
7088 return FALSE;
7089 method = ji->method;
7091 if (method == write_barrier_method)
7092 return TRUE;
7093 for (i = 0; i < ATYPE_NUM; ++i)
7094 if (method == alloc_method_cache [i])
7095 return TRUE;
7096 return FALSE;
7100 * Generate an allocator method implementing the fast path of mono_gc_alloc_obj ().
7101 * The signature of the called method is:
7102 * object allocate (MonoVTable *vtable)
7104 MonoMethod*
7105 mono_gc_get_managed_allocator (MonoVTable *vtable, gboolean for_box)
7107 #ifdef MANAGED_ALLOCATION
7108 MonoClass *klass = vtable->klass;
7110 #ifdef HAVE_KW_THREAD
7111 int tlab_next_offset = -1;
7112 int tlab_temp_end_offset = -1;
7113 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7114 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7116 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7117 return NULL;
7118 #endif
7120 if (!mono_runtime_has_tls_get ())
7121 return NULL;
7122 if (klass->instance_size > tlab_size)
7123 return NULL;
7124 if (klass->has_finalize || klass->marshalbyref || (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS))
7125 return NULL;
7126 if (klass->rank)
7127 return NULL;
7128 if (klass->byval_arg.type == MONO_TYPE_STRING)
7129 return NULL;
7130 if (collect_before_allocs)
7131 return NULL;
7133 if (ALIGN_TO (klass->instance_size, ALLOC_ALIGN) < MAX_SMALL_OBJ_SIZE)
7134 return mono_gc_get_managed_allocator_by_type (ATYPE_SMALL);
7135 else
7136 return mono_gc_get_managed_allocator_by_type (ATYPE_NORMAL);
7137 #else
7138 return NULL;
7139 #endif
7142 MonoMethod*
7143 mono_gc_get_managed_array_allocator (MonoVTable *vtable, int rank)
7145 #ifdef MANAGED_ALLOCATION
7146 MonoClass *klass = vtable->klass;
7148 #ifdef HAVE_KW_THREAD
7149 int tlab_next_offset = -1;
7150 int tlab_temp_end_offset = -1;
7151 MONO_THREAD_VAR_OFFSET (tlab_next, tlab_next_offset);
7152 MONO_THREAD_VAR_OFFSET (tlab_temp_end, tlab_temp_end_offset);
7154 if (tlab_next_offset == -1 || tlab_temp_end_offset == -1)
7155 return NULL;
7156 #endif
7158 if (rank != 1)
7159 return NULL;
7160 if (!mono_runtime_has_tls_get ())
7161 return NULL;
7162 if (mono_profiler_get_events () & MONO_PROFILE_ALLOCATIONS)
7163 return NULL;
7164 if (collect_before_allocs)
7165 return NULL;
7166 g_assert (!klass->has_finalize && !klass->marshalbyref);
7168 return mono_gc_get_managed_allocator_by_type (ATYPE_VECTOR);
7169 #else
7170 return NULL;
7171 #endif
7174 MonoMethod*
7175 mono_gc_get_managed_allocator_by_type (int atype)
7177 #ifdef MANAGED_ALLOCATION
7178 MonoMethod *res;
7180 if (!mono_runtime_has_tls_get ())
7181 return NULL;
7183 mono_loader_lock ();
7184 res = alloc_method_cache [atype];
7185 if (!res)
7186 res = alloc_method_cache [atype] = create_allocator (atype);
7187 mono_loader_unlock ();
7188 return res;
7189 #else
7190 return NULL;
7191 #endif
7194 guint32
7195 mono_gc_get_managed_allocator_types (void)
7197 return ATYPE_NUM;
7201 MonoMethod*
7202 mono_gc_get_write_barrier (void)
7204 MonoMethod *res;
7205 MonoMethodBuilder *mb;
7206 MonoMethodSignature *sig;
7207 #ifdef MANAGED_WBARRIER
7208 int label_no_wb_1, label_no_wb_2, label_no_wb_3, label_no_wb_4, label_need_wb, label_slow_path;
7209 #ifndef ALIGN_NURSERY
7210 int label_continue_1, label_continue_2, label_no_wb_5;
7211 int dereferenced_var;
7212 #endif
7213 int buffer_var, buffer_index_var, dummy_var;
7215 #ifdef HAVE_KW_THREAD
7216 int stack_end_offset = -1, store_remset_buffer_offset = -1;
7217 int store_remset_buffer_index_offset = -1, store_remset_buffer_index_addr_offset = -1;
7219 MONO_THREAD_VAR_OFFSET (stack_end, stack_end_offset);
7220 g_assert (stack_end_offset != -1);
7221 MONO_THREAD_VAR_OFFSET (store_remset_buffer, store_remset_buffer_offset);
7222 g_assert (store_remset_buffer_offset != -1);
7223 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index, store_remset_buffer_index_offset);
7224 g_assert (store_remset_buffer_index_offset != -1);
7225 MONO_THREAD_VAR_OFFSET (store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7226 g_assert (store_remset_buffer_index_addr_offset != -1);
7227 #endif
7228 #endif
7230 // FIXME: Maybe create a separate version for ctors (the branch would be
7231 // correctly predicted more times)
7232 if (write_barrier_method)
7233 return write_barrier_method;
7235 /* Create the IL version of mono_gc_barrier_generic_store () */
7236 sig = mono_metadata_signature_alloc (mono_defaults.corlib, 1);
7237 sig->ret = &mono_defaults.void_class->byval_arg;
7238 sig->params [0] = &mono_defaults.int_class->byval_arg;
7240 mb = mono_mb_new (mono_defaults.object_class, "wbarrier", MONO_WRAPPER_WRITE_BARRIER);
7242 #ifdef MANAGED_WBARRIER
7243 if (mono_runtime_has_tls_get ()) {
7244 #ifdef ALIGN_NURSERY
7245 // if (ptr_in_nursery (ptr)) return;
7247 * Masking out the bits might be faster, but we would have to use 64 bit
7248 * immediates, which might be slower.
7250 mono_mb_emit_ldarg (mb, 0);
7251 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7252 mono_mb_emit_byte (mb, CEE_SHR_UN);
7253 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7254 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BEQ);
7256 // if (!ptr_in_nursery (*ptr)) return;
7257 mono_mb_emit_ldarg (mb, 0);
7258 mono_mb_emit_byte (mb, CEE_LDIND_I);
7259 mono_mb_emit_icon (mb, DEFAULT_NURSERY_BITS);
7260 mono_mb_emit_byte (mb, CEE_SHR_UN);
7261 mono_mb_emit_icon (mb, (mword)nursery_start >> DEFAULT_NURSERY_BITS);
7262 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BNE_UN);
7263 #else
7265 // if (ptr < (nursery_start)) goto continue;
7266 mono_mb_emit_ldarg (mb, 0);
7267 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7268 label_continue_1 = mono_mb_emit_branch (mb, CEE_BLT);
7270 // if (ptr >= nursery_real_end)) goto continue;
7271 mono_mb_emit_ldarg (mb, 0);
7272 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7273 label_continue_2 = mono_mb_emit_branch (mb, CEE_BGE);
7275 // Otherwise return
7276 label_no_wb_1 = mono_mb_emit_branch (mb, CEE_BR);
7278 // continue:
7279 mono_mb_patch_branch (mb, label_continue_1);
7280 mono_mb_patch_branch (mb, label_continue_2);
7282 // Dereference and store in local var
7283 dereferenced_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7284 mono_mb_emit_ldarg (mb, 0);
7285 mono_mb_emit_byte (mb, CEE_LDIND_I);
7286 mono_mb_emit_stloc (mb, dereferenced_var);
7288 // if (*ptr < nursery_start) return;
7289 mono_mb_emit_ldloc (mb, dereferenced_var);
7290 mono_mb_emit_ptr (mb, (gpointer) nursery_start);
7291 label_no_wb_2 = mono_mb_emit_branch (mb, CEE_BLT);
7293 // if (*ptr >= nursery_end) return;
7294 mono_mb_emit_ldloc (mb, dereferenced_var);
7295 mono_mb_emit_ptr (mb, (gpointer) nursery_real_end);
7296 label_no_wb_5 = mono_mb_emit_branch (mb, CEE_BGE);
7298 #endif
7299 // if (ptr >= stack_end) goto need_wb;
7300 mono_mb_emit_ldarg (mb, 0);
7301 EMIT_TLS_ACCESS (mb, stack_end, stack_end_offset);
7302 label_need_wb = mono_mb_emit_branch (mb, CEE_BGE_UN);
7304 // if (ptr >= stack_start) return;
7305 dummy_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7306 mono_mb_emit_ldarg (mb, 0);
7307 mono_mb_emit_ldloc_addr (mb, dummy_var);
7308 label_no_wb_3 = mono_mb_emit_branch (mb, CEE_BGE_UN);
7310 // need_wb:
7311 mono_mb_patch_branch (mb, label_need_wb);
7313 // buffer = STORE_REMSET_BUFFER;
7314 buffer_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7315 EMIT_TLS_ACCESS (mb, store_remset_buffer, store_remset_buffer_offset);
7316 mono_mb_emit_stloc (mb, buffer_var);
7318 // buffer_index = STORE_REMSET_BUFFER_INDEX;
7319 buffer_index_var = mono_mb_add_local (mb, &mono_defaults.int_class->byval_arg);
7320 EMIT_TLS_ACCESS (mb, store_remset_buffer_index, store_remset_buffer_index_offset);
7321 mono_mb_emit_stloc (mb, buffer_index_var);
7323 // if (buffer [buffer_index] == ptr) return;
7324 mono_mb_emit_ldloc (mb, buffer_var);
7325 mono_mb_emit_ldloc (mb, buffer_index_var);
7326 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7327 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7328 mono_mb_emit_byte (mb, CEE_SHL);
7329 mono_mb_emit_byte (mb, CEE_ADD);
7330 mono_mb_emit_byte (mb, CEE_LDIND_I);
7331 mono_mb_emit_ldarg (mb, 0);
7332 label_no_wb_4 = mono_mb_emit_branch (mb, CEE_BEQ);
7334 // ++buffer_index;
7335 mono_mb_emit_ldloc (mb, buffer_index_var);
7336 mono_mb_emit_icon (mb, 1);
7337 mono_mb_emit_byte (mb, CEE_ADD);
7338 mono_mb_emit_stloc (mb, buffer_index_var);
7340 // if (buffer_index >= STORE_REMSET_BUFFER_SIZE) goto slow_path;
7341 mono_mb_emit_ldloc (mb, buffer_index_var);
7342 mono_mb_emit_icon (mb, STORE_REMSET_BUFFER_SIZE);
7343 label_slow_path = mono_mb_emit_branch (mb, CEE_BGE);
7345 // buffer [buffer_index] = ptr;
7346 mono_mb_emit_ldloc (mb, buffer_var);
7347 mono_mb_emit_ldloc (mb, buffer_index_var);
7348 g_assert (sizeof (gpointer) == 4 || sizeof (gpointer) == 8);
7349 mono_mb_emit_icon (mb, sizeof (gpointer) == 4 ? 2 : 3);
7350 mono_mb_emit_byte (mb, CEE_SHL);
7351 mono_mb_emit_byte (mb, CEE_ADD);
7352 mono_mb_emit_ldarg (mb, 0);
7353 mono_mb_emit_byte (mb, CEE_STIND_I);
7355 // STORE_REMSET_BUFFER_INDEX = buffer_index;
7356 EMIT_TLS_ACCESS (mb, store_remset_buffer_index_addr, store_remset_buffer_index_addr_offset);
7357 mono_mb_emit_ldloc (mb, buffer_index_var);
7358 mono_mb_emit_byte (mb, CEE_STIND_I);
7360 // return;
7361 mono_mb_patch_branch (mb, label_no_wb_1);
7362 mono_mb_patch_branch (mb, label_no_wb_2);
7363 mono_mb_patch_branch (mb, label_no_wb_3);
7364 mono_mb_patch_branch (mb, label_no_wb_4);
7365 #ifndef ALIGN_NURSERY
7366 mono_mb_patch_branch (mb, label_no_wb_5);
7367 #endif
7368 mono_mb_emit_byte (mb, CEE_RET);
7370 // slow path
7371 mono_mb_patch_branch (mb, label_slow_path);
7373 #endif
7375 mono_mb_emit_ldarg (mb, 0);
7376 mono_mb_emit_icall (mb, mono_gc_wbarrier_generic_nostore);
7377 mono_mb_emit_byte (mb, CEE_RET);
7379 res = mono_mb_create_method (mb, sig, 16);
7380 mono_mb_free (mb);
7382 mono_loader_lock ();
7383 if (write_barrier_method) {
7384 /* Already created */
7385 mono_free_method (res);
7386 } else {
7387 /* double-checked locking */
7388 mono_memory_barrier ();
7389 write_barrier_method = res;
7391 mono_loader_unlock ();
7393 return write_barrier_method;
7396 #endif /* HAVE_SGEN_GC */