2 * mini-gc.c: GC interface for the mono JIT
5 * Zoltan Varga (vargaz@gmail.com)
7 * Copyright 2009 Novell, Inc (http://www.novell.com)
16 #include <mono/metadata/gc-internal.h>
17 #include <mono/utils/mono-counters.h>
19 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
22 #define DEBUG(s) do { s; } while (0)
28 #define DEBUG_GC_MAP(s) do { s; fflush (stdout); } while (0)
30 #define DEBUG_GC_MAP(s)
33 #define GC_BITS_PER_WORD (sizeof (gsize) * 8)
36 * Per-thread data kept by this module. This is stored in the GC and passed to us as
37 * parameters, instead of being stored in a TLS variable, since during a collection,
38 * only the collection thread is active.
44 MonoJitTlsData *jit_tls;
48 /* Stack slot doesn't contain a reference */
50 /* Stack slot contains a reference */
52 /* No info, slot needs to be scanned conservatively */
57 * Contains information needed to mark a stack frame.
58 * FIXME: Optimize the memory usage.
61 /* The frame pointer register */
63 /* The offset of the local variable area in the stack frame relative to the frame pointer */
65 /* The size of the locals area. Can't use nslots as it includes padding */
67 /* The number of stack slots */
73 /* A pair of low pc offset-high pc offset for each SLOT_REF value in gc_refs */
74 guint32 live_ranges [MONO_ZERO_LEN_ARRAY];
78 static guint32 gc_maps_size;
81 thread_attach_func (void)
83 return g_new0 (TlsData, 1);
87 thread_suspend_func (gpointer user_data, void *sigctx)
89 TlsData *tls = user_data;
91 tls->lmf = mono_get_lmf ();
93 mono_arch_sigctx_to_monoctx (sigctx, &tls->ctx);
94 tls->has_context = TRUE;
96 tls->has_context = FALSE;
98 tls->jit_tls = TlsGetValue (mono_jit_tls_id);
102 thread_mark_func (gpointer user_data, guint8 *stack_start, guint8 *stack_end, gboolean precise)
104 TlsData *tls = user_data;
105 MonoJitInfo *ji, res;
106 MonoContext ctx, new_ctx;
107 MonoLMF *lmf = tls->lmf;
109 gboolean last = TRUE, managed;
111 guint8* fp, *locals_start, *locals_end;
113 int scanned = 0, scanned_precisely, scanned_conservatively;
115 if (mono_thread_internal_current () == NULL) {
117 mono_gc_conservatively_scan_area (stack_start, stack_end);
121 /* Number of bytes scanned based on GC map data */
123 /* Number of bytes scanned precisely based on GC map data */
124 scanned_precisely = 0;
125 /* Number of bytes scanned conservatively based on GC map data */
126 scanned_conservatively = 0;
128 /* FIXME: sgen-gc.c calls this multiple times for each major collection from pin_from_roots */
130 /* FIXME: Use real gc descriptors instead of bitmaps */
132 /* This is one past the last address which we have scanned */
133 stack_limit = stack_start;
135 DEBUG (printf ("*** %s stack marking %p-%p ***\n", precise ? "Precise" : "Conservative", stack_start, stack_end));
137 if (!tls->has_context) {
138 memset (&new_ctx, 0, sizeof (ctx));
141 memcpy (&ctx, &new_ctx, sizeof (ctx));
143 g_assert ((guint64)stack_limit % sizeof (gpointer) == 0);
145 // FIXME: This doesn't work with appdomain transitions
146 ji = mono_find_jit_info (mono_domain_get (), tls->jit_tls, &res, NULL,
147 &ctx, &new_ctx, NULL, &lmf, NULL, &managed);
148 if (ji == (gpointer)-1)
151 /* The last frame can be in any state so mark conservatively */
157 /* These frames are returned by mono_find_jit_info () two times */
161 /* Scan the frame of this method */
164 * A frame contains the following:
169 * - localloc-ed memory
170 * Currently, only the locals are scanned precisely.
176 DEBUG (char *fname = mono_method_full_name (ji->method, TRUE); printf ("Mark(%d): No GC map for %s\n", precise, fname); g_free (fname));
181 if (map->frame_reg == AMD64_RSP)
182 fp = (guint8*)ctx.rsp;
183 else if (map->frame_reg == AMD64_RBP)
184 fp = (guint8*)ctx.rbp;
186 g_assert_not_reached ();
189 g_assert_not_reached ();
192 locals_start = fp + map->locals_offset;
193 locals_end = locals_start + map->locals_size;
195 pc_offset = (guint8*)MONO_CONTEXT_GET_IP (&ctx) - (guint8*)ji->code_start;
196 g_assert (pc_offset >= 0);
198 DEBUG (char *fname = mono_method_full_name (ji->method, TRUE); printf ("Mark(%d): %s+0x%x (%p) limit=%p fp=%p locals=%p-%p (%d)\n", precise, fname, pc_offset, (gpointer)MONO_CONTEXT_GET_IP (&ctx), stack_limit, fp, locals_start, locals_end, (int)(locals_end - locals_start)); g_free (fname));
201 * FIXME: Add a function to mark using a bitmap, to avoid doing a
202 * call for each object.
205 scanned += locals_end - locals_start;
207 /* Pinning needs to be done first, then the precise scan later */
210 g_assert (locals_start >= stack_limit);
212 if (locals_start > stack_limit) {
213 /* This scans the previously skipped frames as well */
214 DEBUG (printf ("\tscan area %p-%p.\n", stack_limit, locals_start));
215 mono_gc_conservatively_scan_area (stack_limit, locals_start);
222 for (i = 0; i < map->nslots; ++i) {
223 if (map->slots [i] == SLOT_PIN) {
224 DEBUG (printf ("\tscan slot %s0x%x(fp)=%p.\n", (guint8*)p > (guint8*)fp ? "" : "-", ABS ((int)((gssize)p - (gssize)fp)), p));
225 mono_gc_conservatively_scan_area (p, p + sizeof (gpointer));
226 scanned_conservatively += sizeof (gpointer);
228 p += sizeof (gpointer);
232 stack_limit = locals_end;
237 for (i = 0; i < map->nslots; ++i) {
238 if (map->slots [i] == SLOT_REF) {
239 MonoObject **ptr = (MonoObject**)(locals_start + (i * sizeof (gpointer)));
240 MonoObject *obj = *ptr;
242 if (pc_offset >= map->live_ranges [loffset] && pc_offset < map->live_ranges [loffset + 1]) {
244 *ptr = mono_gc_scan_object (obj);
245 DEBUG (printf ("\tref %s0x%x(fp)=%p: %p -> %p.\n", (guint8*)ptr >= (guint8*)fp ? "" : "-", ABS ((int)((gssize)ptr - (gssize)fp)), ptr, obj, *ptr));
247 DEBUG (printf ("\tref %s0x%x(fp)=%p: %p.\n", (guint8*)ptr >= (guint8*)fp ? "" : "-", ABS ((int)((gssize)ptr - (gssize)fp)), ptr, obj));
250 DEBUG (printf ("\tref %s0x%x(fp)=%p: dead\n", (guint8*)ptr >= (guint8*)fp ? "" : "-", ABS ((int)((gssize)ptr - (gssize)fp)), ptr));
254 scanned_precisely += sizeof (gpointer);
255 } else if (map->slots [i] == SLOT_NOREF) {
256 scanned_precisely += sizeof (gpointer);
263 if (stack_limit < stack_end && !precise) {
264 DEBUG (printf ("\tscan area %p-%p.\n", stack_limit, stack_end));
265 mono_gc_conservatively_scan_area (stack_limit, stack_end);
270 DEBUG (printf ("\tno context, scan area %p-%p.\n", stack_start, stack_end));
271 mono_gc_conservatively_scan_area (stack_start, stack_end);
275 DEBUG (printf ("Marked %d bytes, p=%d,c=%d out of %d.\n", scanned, scanned_precisely, scanned_conservatively, (int)(stack_end - stack_start)));
277 //mono_gc_conservatively_scan_area (stack_start, stack_end);
280 #define set_slot(slots, nslots, pos, val) do { \
281 g_assert ((pos) < (nslots)); \
282 (slots) [(pos)] = (val); \
286 mini_gc_create_gc_map (MonoCompile *cfg)
289 int i, nslots, alloc_size, loffset, min_offset, max_offset;
290 StackSlotType *slots = NULL;
291 gboolean norefs = FALSE;
292 guint32 *live_range_start, *live_range_end;
295 * This doesn't work yet, because the live ranges used to calculate the GC map
296 * are not concrete/precise enough for several reasons:
297 * - the current calculation of MonoMethodVar->live_range_start/end is incorrect,
298 * it doesn't take into account loops etc. It needs to use the results of the
299 * liveness analysis pass.
300 * - the current liveness analysis pass is too conservative, ie. the live_in/out
301 * sets computed by it are sometimes include too many variables, for example because
302 * of the bogus links between bblocks. This means the live_in/out sets cannot be
303 * used to reliably compute precise live ranges.
304 * - stack slots are shared, which means the live ranges of stack slots have holes
306 * - the live ranges of variables used in out-of-line bblocks also have holes in
311 if (!(cfg->comp_done & MONO_COMP_LIVENESS))
312 /* Without liveness info, the live ranges are not precise enough */
316 min_offset = ALIGN_TO (cfg->locals_min_stack_offset, sizeof (gpointer));
317 max_offset = cfg->locals_max_stack_offset;
319 /* min/max stack offset needs to be computed in mono_arch_allocate_vars () */
323 for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
324 MonoInst *ins = cfg->varinfo [i];
325 MonoType *t = ins->inst_vtype;
327 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references))
329 if (MONO_TYPE_ISSTRUCT (t))
331 if (t->byref || t->type == MONO_TYPE_PTR)
333 if (ins && ins->opcode == OP_REGOFFSET && MONO_TYPE_IS_REFERENCE (ins->inst_vtype))
337 if (i == cfg->num_varinfo)
340 if (cfg->verbose_level > 1)
341 printf ("GC Map for %s: 0x%x-0x%x\n", mono_method_full_name (cfg->method, TRUE), min_offset, max_offset);
343 nslots = (max_offset - min_offset) / sizeof (gpointer);
345 alloc_size = nslots * sizeof (StackSlotType);
346 slots = mono_domain_alloc0 (cfg->domain, alloc_size);
347 for (i = 0; i < nslots; ++i)
348 slots [i] = SLOT_NOREF;
349 gc_maps_size += alloc_size;
351 live_range_start = g_new (guint32, nslots);
352 live_range_end = g_new (guint32, nslots);
355 for (i = 0; i < nslots; ++i) {
356 live_range_start [i] = (guint32)-1;
357 live_range_end [i] = 0;
360 for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
361 MonoInst *ins = cfg->varinfo [i];
362 MonoType *t = ins->inst_vtype;
369 vmv = MONO_VARINFO (cfg, i);
371 if (ins->opcode != OP_REGOFFSET)
374 if (ins->inst_offset % sizeof (gpointer) != 0)
377 pos = (ins->inst_offset - min_offset) / sizeof (gpointer);
379 if ((MONO_TYPE_ISSTRUCT (t) && !ins->klass->has_references))
382 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references)) {
387 if (ins->klass->generic_container || mono_class_is_open_constructed_type (t)) {
388 /* FIXME: Generic sharing */
391 mono_class_compute_gc_descriptor (ins->klass);
393 bitmap = mono_gc_get_bitmap_for_descr (ins->klass->gc_descr, &numbits);
396 for (j = 0; j < numbits; ++j) {
397 if (bitmap [j / GC_BITS_PER_WORD] & ((gsize)1 << (j % GC_BITS_PER_WORD))) {
398 /* The descriptor is for the boxed object */
399 set_slot (slots, nslots, (pos + j - (sizeof (MonoObject) / sizeof (gpointer))), SLOT_REF);
404 if (cfg->verbose_level > 1)
405 printf ("\tvtype at fp+0x%x: %s -> 0x%x\n", (int)ins->inst_offset, mono_type_full_name (ins->inst_vtype), (int)ins->inst_offset);
407 // FIXME: These have no live range
415 if (ins->backend.is_pinvoke)
421 if (ins->backend.is_pinvoke)
422 size = mono_class_native_size (ins->klass, NULL);
424 size = mono_class_value_size (ins->klass, NULL);
425 for (j = 0; j < size / sizeof (gpointer); ++j)
426 set_slot (slots, nslots, pos + j, SLOT_PIN);
431 if (ins->inst_offset < min_offset || ins->inst_offset >= max_offset)
435 if (t->byref || t->type == MONO_TYPE_PTR || t->type == MONO_TYPE_I || t->type == MONO_TYPE_U) {
436 slots [pos] = SLOT_PIN;
439 if (vmv && !vmv->live_range_start) {
440 g_assert (pos >= 0 && pos < nslots);
441 slots [pos] = SLOT_PIN;
445 if (MONO_TYPE_IS_REFERENCE (ins->inst_vtype)) {
446 set_slot (slots, nslots, pos, SLOT_REF);
448 /* Stack slots holding refs shouldn't be shared */
449 g_assert (!live_range_end [pos]);
450 live_range_start [pos] = vmv->live_range_start;
451 live_range_end [pos] = vmv->live_range_end;
453 if (cfg->verbose_level > 1)
454 printf ("\tref at %s0x%x(fp) (slot=%d): %s [0x%x - 0x%x]\n", ins->inst_offset < 0 ? "-" : "", (ins->inst_offset < 0) ? -(int)ins->inst_offset : (int)ins->inst_offset, pos, mono_type_full_name (ins->inst_vtype), vmv->live_range_start, vmv->live_range_end);
458 alloc_size = sizeof (GCMap) + (norefs ? 0 : (nslots - MONO_ZERO_LEN_ARRAY) * sizeof (guint32) * 2);
459 map = mono_domain_alloc0 (cfg->domain, alloc_size);
460 gc_maps_size += alloc_size;
462 map->frame_reg = cfg->frame_reg;
463 map->locals_offset = min_offset;
464 map->locals_size = ALIGN_TO (max_offset - min_offset, sizeof (gpointer));
465 map->nslots = nslots;
469 for (i = 0; i < nslots; ++i) {
470 if (map->slots [i] == SLOT_REF) {
471 map->live_ranges [loffset ++] = live_range_start [i];
472 map->live_ranges [loffset ++] = live_range_end [i];
479 static int precise_count;
483 if (getenv ("MONO_PRECISE_COUNT")) {
484 if (precise_count == atoi (getenv ("MONO_PRECISE_COUNT")))
485 printf ("LAST: %s\n", mono_method_full_name (cfg->method, TRUE));
486 if (precise_count > atoi (getenv ("MONO_PRECISE_COUNT"))) {
487 for (i = 0; i < nslots; ++i)
488 map->slots [i] = SLOT_PIN;
495 cfg->jit_info->gc_info = map;
497 g_free (live_range_start);
498 g_free (live_range_end);
506 memset (&cb, 0, sizeof (cb));
507 cb.thread_attach_func = thread_attach_func;
508 cb.thread_suspend_func = thread_suspend_func;
509 /* Comment this out to disable precise stack marking */
510 cb.thread_mark_func = thread_mark_func;
511 mono_gc_set_gc_callbacks (&cb);
513 mono_counters_register ("GC Maps size",
514 MONO_COUNTER_GC | MONO_COUNTER_INT, &gc_maps_size);
525 mini_gc_create_gc_map (MonoCompile *cfg)