2010-01-18 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mono / mini / mini-gc.c
1 /*
2  * mini-gc.c: GC interface for the mono JIT
3  *
4  * Author:
5  *   Zoltan Varga (vargaz@gmail.com)
6  *
7  * Copyright 2009 Novell, Inc (http://www.novell.com)
8  */
9
10 #include "config.h"
11 #include "mini-gc.h"
12
13 #if 0
14 //#ifdef HAVE_SGEN_GC
15
16 #include <mono/metadata/gc-internal.h>
17 #include <mono/utils/mono-counters.h>
18
19 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
20
21 #if 1
22 #define DEBUG(s) do { s; } while (0)
23 #else
24 #define DEBUG(s)
25 #endif
26
27 #if 1
28 #define DEBUG_GC_MAP(s) do { s; fflush (stdout); } while (0)
29 #else
30 #define DEBUG_GC_MAP(s)
31 #endif
32
33 #define GC_BITS_PER_WORD (sizeof (gsize) * 8)
34
35 /*
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.
39  */
40 typedef struct {
41         MonoLMF *lmf;
42         MonoContext ctx;
43         gboolean has_context;
44         MonoJitTlsData *jit_tls;
45 } TlsData;
46
47 typedef enum {
48         /* Stack slot doesn't contain a reference */
49         SLOT_NOREF = 0,
50         /* Stack slot contains a reference */
51         SLOT_REF = 1,
52         /* No info, slot needs to be scanned conservatively */
53         SLOT_PIN = 2
54 } StackSlotType;
55
56 /* 
57  * Contains information needed to mark a stack frame.
58  * FIXME: Optimize the memory usage.
59  */
60 typedef struct {
61         /* The frame pointer register */
62         int frame_reg;
63         /* The offset of the local variable area in the stack frame relative to the frame pointer */
64         int locals_offset;
65         /* The size of the locals area. Can't use nslots as it includes padding */
66         int locals_size;
67         /* The number of stack slots */
68         int nslots;
69         /* 
70          * The gc map itself.
71          */
72         StackSlotType *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];
75 } GCMap;
76
77 /* Statistics */
78 static guint32 gc_maps_size;
79
80 static gpointer
81 thread_attach_func (void)
82 {
83         return g_new0 (TlsData, 1);
84 }
85
86 static void
87 thread_suspend_func (gpointer user_data, void *sigctx)
88 {
89         TlsData *tls = user_data;
90
91         tls->lmf = mono_get_lmf ();
92         if (sigctx) {
93                 mono_arch_sigctx_to_monoctx (sigctx, &tls->ctx);
94                 tls->has_context = TRUE;
95         } else {
96                 tls->has_context = FALSE;
97         }
98         tls->jit_tls = TlsGetValue (mono_jit_tls_id);
99 }
100
101 static void
102 thread_mark_func (gpointer user_data, guint8 *stack_start, guint8 *stack_end, gboolean precise)
103 {
104         TlsData *tls = user_data;
105         MonoJitInfo *ji, res;
106         MonoContext ctx, new_ctx;
107         MonoLMF *lmf = tls->lmf;
108         guint8 *stack_limit;
109         gboolean last = TRUE, managed;
110         GCMap *map;
111         guint8* fp, *locals_start, *locals_end;
112         int i, pc_offset;
113         int scanned = 0, scanned_precisely, scanned_conservatively;
114
115         if (mono_thread_internal_current () == NULL) {
116                 if (!precise)
117                         mono_gc_conservatively_scan_area (stack_start, stack_end);                      
118                 return;
119         }
120
121         /* Number of bytes scanned based on GC map data */
122         scanned = 0;
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;
127
128         /* FIXME: sgen-gc.c calls this multiple times for each major collection from pin_from_roots */
129
130         /* FIXME: Use real gc descriptors instead of bitmaps */
131
132         /* This is one past the last address which we have scanned */
133         stack_limit = stack_start;
134
135         DEBUG (printf ("*** %s stack marking %p-%p ***\n", precise ? "Precise" : "Conservative", stack_start, stack_end));
136
137         if (!tls->has_context) {
138                 memset (&new_ctx, 0, sizeof (ctx));
139
140                 while (TRUE) {
141                         memcpy (&ctx, &new_ctx, sizeof (ctx));
142
143                         g_assert ((guint64)stack_limit % sizeof (gpointer) == 0);
144
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)
149                                 break;
150
151                         /* The last frame can be in any state so mark conservatively */
152                         if (last) {
153                                 last = FALSE;
154                                 continue;
155                         }
156
157                         /* These frames are returned by mono_find_jit_info () two times */
158                         if (!managed)
159                                 continue;
160
161                         /* Scan the frame of this method */
162
163                         /*
164                          * A frame contains the following:
165                          * - saved registers
166                          * - saved args
167                          * - locals
168                          * - spill area
169                          * - localloc-ed memory
170                          * Currently, only the locals are scanned precisely.
171                          */
172
173                         map = ji->gc_info;
174
175                         if (!map) {
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));
177                                 continue;
178                         }
179
180 #ifdef __x86_64__
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;
185                         else
186                                 g_assert_not_reached ();
187 #else
188                         fp = NULL;
189                         g_assert_not_reached ();
190 #endif
191
192                         locals_start = fp + map->locals_offset;
193                         locals_end = locals_start + map->locals_size;
194
195                         pc_offset = (guint8*)MONO_CONTEXT_GET_IP (&ctx) - (guint8*)ji->code_start;
196                         g_assert (pc_offset >= 0);
197
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));
199
200                         /* 
201                          * FIXME: Add a function to mark using a bitmap, to avoid doing a 
202                          * call for each object.
203                          */
204
205                         scanned += locals_end - locals_start;
206
207                         /* Pinning needs to be done first, then the precise scan later */
208
209                         if (!precise) {
210                                 g_assert (locals_start >= stack_limit);
211
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);
216                                 }
217
218                                 if (map->slots) {
219                                         guint8 *p;
220
221                                         p = 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);
227                                                 }
228                                                 p += sizeof (gpointer);
229                                         }
230                                 }
231
232                                 stack_limit = locals_end;
233                         } else {
234                                 if (map->slots) {
235                                         int loffset = 0;
236
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;
241
242                                                         if (pc_offset >= map->live_ranges [loffset] && pc_offset < map->live_ranges [loffset + 1]) {
243                                                                 if (obj) {
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));
246                                                                 } else {
247                                                                         DEBUG (printf ("\tref %s0x%x(fp)=%p: %p.\n", (guint8*)ptr >= (guint8*)fp ? "" : "-", ABS ((int)((gssize)ptr - (gssize)fp)), ptr, obj));
248                                                                 }
249                                                         } else {
250                                                                         DEBUG (printf ("\tref %s0x%x(fp)=%p: dead\n", (guint8*)ptr >= (guint8*)fp ? "" : "-", ABS ((int)((gssize)ptr - (gssize)fp)), ptr));
251                                                         }
252
253                                                         loffset += 2;
254                                                         scanned_precisely += sizeof (gpointer);
255                                                 } else if (map->slots [i] == SLOT_NOREF) {
256                                                         scanned_precisely += sizeof (gpointer);
257                                                 }
258                                         }
259                                 }
260                         }
261                 }
262
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);
266                 }
267         } else {
268                 // FIXME:
269                 if (!precise) {
270                         DEBUG (printf ("\tno context, scan area %p-%p.\n", stack_start, stack_end));
271                         mono_gc_conservatively_scan_area (stack_start, stack_end);
272                 }
273         }
274
275         DEBUG (printf ("Marked %d bytes, p=%d,c=%d out of %d.\n", scanned, scanned_precisely, scanned_conservatively, (int)(stack_end - stack_start)));
276
277         //mono_gc_conservatively_scan_area (stack_start, stack_end);
278 }
279
280 #define set_slot(slots, nslots, pos, val) do {  \
281                 g_assert ((pos) < (nslots));               \
282                 (slots) [(pos)] = (val);                           \
283         } while (0)
284
285 void
286 mini_gc_create_gc_map (MonoCompile *cfg)
287 {
288         GCMap *map;
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;
293
294         /*
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
305          * in them.
306          * - the live ranges of variables used in out-of-line bblocks also have holes in
307          * them.
308          */
309         NOT_IMPLEMENTED;
310
311         if (!(cfg->comp_done & MONO_COMP_LIVENESS))
312                 /* Without liveness info, the live ranges are not precise enough */
313                 return;
314
315 #ifdef TARGET_AMD64
316         min_offset = ALIGN_TO (cfg->locals_min_stack_offset, sizeof (gpointer));
317         max_offset = cfg->locals_max_stack_offset;
318 #else
319         /* min/max stack offset needs to be computed in mono_arch_allocate_vars () */
320         NOT_IMPLEMENTED;
321 #endif
322
323         for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
324                 MonoInst *ins = cfg->varinfo [i];
325                 MonoType *t = ins->inst_vtype;
326
327                 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references))
328                         break;
329                 if (MONO_TYPE_ISSTRUCT (t))
330                         break;
331                 if (t->byref || t->type == MONO_TYPE_PTR)
332                         break;
333                 if (ins && ins->opcode == OP_REGOFFSET && MONO_TYPE_IS_REFERENCE (ins->inst_vtype))
334                         break;
335         }
336
337         if (i == cfg->num_varinfo)
338                 norefs = TRUE;
339
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);
342
343         nslots = (max_offset - min_offset) / sizeof (gpointer);
344         if (!norefs) {
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;
350         }
351         live_range_start = g_new (guint32, nslots);
352         live_range_end = g_new (guint32, nslots);
353         loffset = 0;
354
355         for (i = 0; i < nslots; ++i) {
356                 live_range_start [i] = (guint32)-1;
357                 live_range_end [i] = 0;
358         }
359
360         for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
361                 MonoInst *ins = cfg->varinfo [i];
362                 MonoType *t = ins->inst_vtype;
363                 MonoMethodVar *vmv;
364                 guint32 pos;
365
366                 if (norefs)
367                         continue;
368
369                 vmv = MONO_VARINFO (cfg, i);
370
371                 if (ins->opcode != OP_REGOFFSET)
372                         continue;
373
374                 if (ins->inst_offset % sizeof (gpointer) != 0)
375                         continue;
376
377                 pos = (ins->inst_offset - min_offset) / sizeof (gpointer);
378
379                 if ((MONO_TYPE_ISSTRUCT (t) && !ins->klass->has_references))
380                         continue;
381
382                 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references)) {
383                         int numbits, j;
384                         gsize *bitmap;
385                         gboolean pin;
386
387                         if (ins->klass->generic_container || mono_class_is_open_constructed_type (t)) {
388                                 /* FIXME: Generic sharing */
389                                 pin = TRUE;
390                         } else {
391                                 mono_class_compute_gc_descriptor (ins->klass);
392
393                                 bitmap = mono_gc_get_bitmap_for_descr (ins->klass->gc_descr, &numbits);
394
395                                 if (bitmap) {
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);
400                                                 }
401                                         }
402                                         g_free (bitmap);
403
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);
406
407                                         // FIXME: These have no live range
408                                         pin = TRUE;
409                                 } else {
410                                         // FIXME:
411                                         pin = TRUE;
412                                 }
413                         }
414
415                         if (ins->backend.is_pinvoke)
416                                 pin = TRUE;
417
418                         if (pin) {
419                                 int size;
420
421                                 if (ins->backend.is_pinvoke)
422                                         size = mono_class_native_size (ins->klass, NULL);
423                                 else
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);
427                         }
428                         continue;
429                 }
430
431                 if (ins->inst_offset < min_offset || ins->inst_offset >= max_offset)
432                         /* Vret addr etc. */
433                         continue;
434
435                 if (t->byref || t->type == MONO_TYPE_PTR || t->type == MONO_TYPE_I || t->type == MONO_TYPE_U) {
436                         slots [pos] = SLOT_PIN;
437                         continue;
438                 }
439                 if (vmv && !vmv->live_range_start) {
440                         g_assert (pos >= 0 && pos < nslots);
441                         slots [pos] = SLOT_PIN;
442                         continue;
443                 }
444
445                 if (MONO_TYPE_IS_REFERENCE (ins->inst_vtype)) {
446                         set_slot (slots, nslots, pos, SLOT_REF);
447
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;
452
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);
455                 }
456         }
457
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;
461
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;
466         map->slots = slots;
467         loffset = 0;
468         if (!norefs) {
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];
473                         }
474                 }
475         }
476
477 #if 1
478         {
479                 static int precise_count;
480
481                 if (map->slots) {
482                         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;
489                                 }
490                         }
491                 }
492         }
493 #endif
494
495         cfg->jit_info->gc_info = map;
496
497         g_free (live_range_start);
498         g_free (live_range_end);
499 }
500
501 void
502 mini_gc_init (void)
503 {
504         MonoGCCallbacks cb;
505
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);
512
513         mono_counters_register ("GC Maps size",
514                                                         MONO_COUNTER_GC | MONO_COUNTER_INT, &gc_maps_size);
515 }
516
517 #else
518
519 void
520 mini_gc_init (void)
521 {
522 }
523
524 void
525 mini_gc_create_gc_map (MonoCompile *cfg)
526 {
527 }
528
529 #endif