2009-06-09 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
15 #include <mono/metadata/gc-internal.h>
16 #include <mono/utils/mono-counters.h>
17
18 #define ALIGN_TO(val,align) ((((guint64)val) + ((align) - 1)) & ~((align) - 1))
19
20 #if 0
21 #define DEBUG(s) do { s; } while (0)
22 #else
23 #define DEBUG(s)
24 #endif
25
26 #if 0
27 #define DEBUG_GC_MAP(s) do { s; fflush (stdout); } while (0)
28 #else
29 #define DEBUG_GC_MAP(s)
30 #endif
31
32 #define GC_BITS_PER_WORD (sizeof (gsize) * 8)
33
34 /*
35  * Per-thread data kept by this module. This is stored in the GC and passed to us as
36  * parameters, instead of being stored in a TLS variable, since during a collection,
37  * only the collection thread is active.
38  */
39 typedef struct {
40         MonoLMF *lmf;
41         MonoContext ctx;
42         gboolean has_context;
43         MonoJitTlsData *jit_tls;
44 } TlsData;
45
46 /* 
47  * Contains information needed to mark a stack frame.
48  * FIXME: Optimize the memory usage.
49  */
50 typedef struct {
51         /* The frame pointer register */
52         int frame_reg;
53         /* The offset of the local variable area in the stack frame relative to the frame pointer */
54         int locals_offset;
55         /* The size of the locals area. Can't use gc_refs->size as it includes padding */
56         int locals_size;
57         /* 
58          * If this is set, then the frame contains references which we can't
59          * process precisely.
60          */
61         guint8 pin;
62         /* A bitmap indicating which stack slots contain a GC ref */
63         /* If no stack slots contain GC refs, then this is NULL */
64         MonoBitSet *gc_refs;
65         /* A pair of low pc offset-high pc offset for each 1 bit in gc_refs */
66         guint32 live_ranges [MONO_ZERO_LEN_ARRAY];
67 } GCMap;
68
69 /* Statistics */
70 static guint32 gc_maps_size;
71
72 static gpointer
73 thread_attach_func (void)
74 {
75         return g_new0 (TlsData, 1);
76 }
77
78 static void
79 thread_suspend_func (gpointer user_data, void *sigctx)
80 {
81         TlsData *tls = user_data;
82
83         tls->lmf = mono_get_lmf ();
84         if (sigctx) {
85                 mono_arch_sigctx_to_monoctx (sigctx, &tls->ctx);
86                 tls->has_context = TRUE;
87         } else {
88                 tls->has_context = FALSE;
89         }
90         tls->jit_tls = TlsGetValue (mono_jit_tls_id);
91 }
92
93 static void
94 thread_mark_func (gpointer user_data, guint8 *stack_start, guint8 *stack_end, gboolean precise)
95 {
96         TlsData *tls = user_data;
97         MonoJitInfo *ji, res;
98         MonoContext ctx, new_ctx;
99         MonoLMF *lmf = tls->lmf;
100         guint8 *stack_limit;
101         gboolean last = TRUE, managed;
102         GCMap *map;
103         guint8* fp, *locals_start, *locals_end;
104         int i, pc_offset;
105
106         if (mono_thread_current () == NULL) {
107                 if (!precise)
108                         mono_gc_conservatively_scan_area (stack_start, stack_end);                      
109                 return;
110         }
111
112         /* FIXME: sgen-gc.c calls this multiple times for each major collection from pin_from_roots */
113
114         /* FIXME: Use real gc descriptors instead of bitmaps */
115
116         /* This is one past the last address which we have scanned */
117         stack_limit = stack_start;
118
119         //DEBUG (printf ("*** %s stack marking %p-%p ***\n", precise ? "Precise" : "Conservative", stack_start, stack_end));
120
121         if (!tls->has_context) {
122                 memset (&new_ctx, 0, sizeof (ctx));
123
124                 while (TRUE) {
125                         memcpy (&ctx, &new_ctx, sizeof (ctx));
126
127                         g_assert ((guint64)stack_limit % sizeof (gpointer) == 0);
128
129                         // FIXME: This doesn't work with appdomain transitions
130                         ji = mono_find_jit_info (mono_domain_get (), tls->jit_tls, &res, NULL,
131                                                                          &ctx, &new_ctx, NULL, &lmf, NULL, &managed);
132                         if (ji == (gpointer)-1)
133                                 break;
134
135                         /* The last frame can be in any state so mark conservatively */
136                         if (last) {
137                                 last = FALSE;
138                                 continue;
139                         }
140
141                         /* These frames are returned by mono_find_jit_info () two times */
142                         if (!managed)
143                                 continue;
144
145                         /* Scan the frame of this method */
146
147                         /*
148                          * A frame contains the following:
149                          * - saved registers
150                          * - saved args
151                          * - locals
152                          * - spill area
153                          * - localloc-ed memory
154                          * Currently, only the locals are scanned precisely.
155                          */
156
157                         map = ji->gc_info;
158 #ifdef __x86_64__
159                         if (map->frame_reg == AMD64_RSP)
160                                 fp = (guint8*)ctx.rsp;
161                         else if (map->frame_reg == AMD64_RBP)
162                                 fp = (guint8*)ctx.rbp;
163                         else
164                                 g_assert_not_reached ();
165 #else
166                         fp = NULL;
167                         g_assert_not_reached ();
168 #endif
169
170                         locals_start = fp + map->locals_offset;
171                         locals_end = locals_start + map->locals_size;
172
173                         pc_offset = (guint8*)MONO_CONTEXT_GET_IP (&ctx) - (guint8*)ji->code_start;
174                         g_assert (pc_offset >= 0);
175
176                         DEBUG (char *fname = mono_method_full_name (ji->method, TRUE); printf ("Mark: %s offset: 0x%x limit: %p fp: %p locals: %p-%p%s\n", fname, pc_offset, stack_limit, fp, locals_start, locals_end, map->pin ? ", conservative" : ""); g_free (fname));
177
178                         /* 
179                          * FIXME: Add a function to mark using a bitmap, to avoid doing a 
180                          * call for each object.
181                          */
182
183                         /* Pinning needs to be done first, then the precise scan later */
184
185                         if (!precise) {
186                                 g_assert (locals_start >= stack_limit);
187
188                                 if (locals_start > stack_limit) {
189                                         /* This scans the previously skipped frames as well */
190                                         if (!precise) {
191                                                 DEBUG (printf ("\tConservative scan of %p-%p.\n", stack_limit, locals_start));
192                                                 mono_gc_conservatively_scan_area (stack_limit, locals_start);
193                                         }
194                                 }
195
196                                 if (map->pin) {
197                                         DEBUG (printf ("\tConservative scan of %p-%p.\n", locals_start, locals_end));
198                                         mono_gc_conservatively_scan_area (locals_start, locals_end);
199                                 }
200
201                                 stack_limit = locals_end;
202                         } else {
203                                 if (!map->pin && map->gc_refs) {
204                                         int loffset = 0;
205
206                                         for (i = 0; i < mono_bitset_size (map->gc_refs); ++i) {
207                                                 if (mono_bitset_test_fast (map->gc_refs, i)) {
208                                                         MonoObject **ptr = (MonoObject**)(locals_start + (i * sizeof (gpointer)));
209                                                         MonoObject *obj = *ptr;
210
211                                                         if (pc_offset >= map->live_ranges [loffset] && pc_offset < map->live_ranges [loffset + 1]) {
212                                                                 if (obj) {
213                                                                         *ptr = mono_gc_scan_object (obj);
214                                                                         DEBUG (printf ("\tObjref at %p + 0x%x: %p -> %p.\n", locals_start, (int)(i * sizeof (gpointer)), obj, *ptr));
215                                                                 } else {
216                                                                         DEBUG (printf ("\tObjref at %p: %p.\n", ptr, obj));
217                                                                 }
218                                                         } else {
219                                                                 DEBUG (printf ("\tDead Objref at %p.\n", ptr));
220                                                         }
221
222                                                         loffset += 2;
223                                                 }
224                                         }
225                                 }
226                         }
227                 }
228
229                 if (stack_limit < stack_end && !precise) {
230                         DEBUG (printf ("\tConservative scan of %p-%p.\n", stack_limit, stack_end));
231                         mono_gc_conservatively_scan_area (stack_limit, stack_end);
232                 }
233         } else {
234                 // FIXME:
235                 if (!precise) {
236                         DEBUG (printf ("\tConservative scan of %p-%p.\n", stack_start, stack_end));
237                         mono_gc_conservatively_scan_area (stack_start, stack_end);
238                 }
239         }
240
241         //mono_gc_conservatively_scan_area (stack_start, stack_end);
242 }
243
244 void
245 mini_gc_create_gc_map (MonoCompile *cfg)
246 {
247         GCMap *map;
248         int i, nslots, alloc_size, loffset, min_offset, max_offset;
249         MonoBitSet *gc_refs = NULL;
250         gboolean pin = FALSE, norefs = FALSE;
251         guint32 *live_range_start, *live_range_end;
252
253         min_offset = ALIGN_TO (cfg->locals_min_stack_offset, sizeof (gpointer));
254         max_offset = cfg->locals_max_stack_offset;
255
256         for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
257                 MonoInst *ins = cfg->varinfo [i];
258                 MonoType *t = ins->inst_vtype;
259
260                 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references))
261                         break;
262                 if (t->byref || t->type == MONO_TYPE_PTR)
263                         break;
264                 if (ins && ins->opcode == OP_REGOFFSET && MONO_TYPE_IS_REFERENCE (ins->inst_vtype))
265                         break;
266         }
267
268         if (i == cfg->num_varinfo)
269                 norefs = TRUE;
270
271         DEBUG_GC_MAP (printf ("GC Map for %s: 0x%x-0x%x\n", mono_method_full_name (cfg->method, TRUE), min_offset, max_offset));
272
273         nslots = (max_offset - min_offset) / sizeof (gpointer);
274         if (!norefs) {
275                 alloc_size = mono_bitset_alloc_size (nslots, 0);
276                 gc_refs = mono_bitset_mem_new (mono_domain_alloc0 (cfg->domain, alloc_size), (max_offset - min_offset) / sizeof (gpointer), 0);
277                 gc_maps_size += alloc_size;
278         }
279         live_range_start = g_new (guint32, nslots);
280         live_range_end = g_new (guint32, nslots);
281         loffset = 0;
282
283         for (i = 0; i < nslots; ++i) {
284                 live_range_start [i] = (guint32)-1;
285                 live_range_end [i] = 0;
286         }
287
288         for (i = cfg->locals_start; i < cfg->num_varinfo; i++) {
289                 MonoInst *ins = cfg->varinfo [i];
290                 MonoType *t = ins->inst_vtype;
291                 MonoMethodVar *vmv;
292
293                 vmv = MONO_VARINFO (cfg, i);
294
295                 if ((MONO_TYPE_ISSTRUCT (t) && ins->klass->has_references)) {
296                         int numbits, j;
297                         gsize *bitmap;
298
299                         mono_class_compute_gc_descriptor (ins->klass);
300
301                         bitmap = mono_gc_get_bitmap_for_descr (ins->klass->gc_descr, &numbits);
302
303                         if (bitmap) {
304                                 int base_bit_offset = (ins->inst_offset - min_offset) / sizeof (gpointer);
305                                 for (j = 0; j < numbits; ++j) {
306                                         if (bitmap [j / GC_BITS_PER_WORD] & (1 << (j % GC_BITS_PER_WORD)))
307                                                 /* The descriptor is for the boxed object */
308                                                 mono_bitset_set_fast (gc_refs, base_bit_offset + j - (sizeof (MonoObject) / sizeof (gpointer)));
309                                 }
310                                 g_free (bitmap);
311
312                                 DEBUG_GC_MAP (printf ("\tVType: %s -> 0x%x\n", mono_type_full_name (ins->inst_vtype), (int)ins->inst_offset));
313
314                                 // FIXME: These have no live range
315                                 pin = TRUE;
316                         } else {
317                                 // FIXME:
318                                 pin = TRUE;
319                         }
320
321                         continue;
322                 }
323                 if (t->byref || t->type == MONO_TYPE_PTR || t->type == MONO_TYPE_I || t->type == MONO_TYPE_U)
324                         pin = TRUE;
325                 if (vmv && !vmv->live_range_start)
326                         pin = TRUE;
327                 if (pin)
328                         break;
329
330                 if (ins && ins->opcode == OP_REGOFFSET && MONO_TYPE_IS_REFERENCE (ins->inst_vtype)) {
331                         guint32 pos = (ins->inst_offset - min_offset) / sizeof (gpointer);
332
333                         g_assert (ins->inst_offset % sizeof (gpointer) == 0);
334                         g_assert (ins->inst_offset >= min_offset && ins->inst_offset < max_offset);
335                         mono_bitset_set_fast (gc_refs, pos);
336
337                         /* 
338                          * If stack slots are shared, the live range will be the union of
339                          * the live range of variables stored in it. This might cause some
340                          * objects to outlive their live ranges.
341                          */
342                         live_range_start [pos] = MIN (live_range_start [pos], vmv->live_range_start);
343                         live_range_end [pos] = MAX (live_range_end [pos], vmv->live_range_end);
344
345                         DEBUG_GC_MAP (printf ("\tRef: %s -> 0x%x [0x%x - 0x%x]\n", mono_type_full_name (ins->inst_vtype), (int)ins->inst_offset, vmv->live_range_start, vmv->live_range_end));
346                 }
347         }
348
349         alloc_size = sizeof (GCMap) + (norefs ? 0 : ((mono_bitset_count (gc_refs) - MONO_ZERO_LEN_ARRAY) * sizeof (guint32) * 2));
350         map = mono_domain_alloc0 (cfg->domain, alloc_size);
351         gc_maps_size += alloc_size;
352
353         map->frame_reg = cfg->frame_reg;
354         map->locals_offset = min_offset;
355         map->locals_size = ALIGN_TO (max_offset - min_offset, sizeof (gpointer));
356         map->gc_refs = gc_refs;
357         map->pin = pin;
358         loffset = 0;
359         if (!norefs) {
360                 for (i = 0; i < mono_bitset_size (gc_refs); ++i) {
361                         if (mono_bitset_test_fast (gc_refs, i)) {
362                                 map->live_ranges [loffset ++] = live_range_start [i];
363                                 map->live_ranges [loffset ++] = live_range_end [i];
364                         }
365                 }
366         }
367
368 #if 1
369         {
370                 static int precise_count;
371
372                 precise_count ++;
373                 if (getenv ("PRECISE_COUNT")) {
374                         if (precise_count == atoi (getenv ("PRECISE_COUNT")))
375                                 printf ("LAST: %s\n", mono_method_full_name (cfg->method, TRUE));
376                         if (precise_count > atoi (getenv ("PRECISE_COUNT")))
377                                 map->pin = TRUE;
378                 }
379         }
380 #endif
381
382         cfg->jit_info->gc_info = map;
383
384         g_free (live_range_start);
385         g_free (live_range_end);
386 }
387
388 void
389 mini_gc_init (void)
390 {
391         MonoGCCallbacks cb;
392
393         memset (&cb, 0, sizeof (cb));
394         cb.thread_attach_func = thread_attach_func;
395         cb.thread_suspend_func = thread_suspend_func;
396         /* Comment this out to disable precise stack marking */
397         //cb.thread_mark_func = thread_mark_func;
398         mono_gc_set_gc_callbacks (&cb);
399
400         mono_counters_register ("GC Maps size",
401                                                         MONO_COUNTER_GC | MONO_COUNTER_INT, &gc_maps_size);
402 }
403
404 #else
405
406 void
407 mini_gc_init (void)
408 {
409 }
410
411 void
412 mini_gc_create_gc_map (MonoCompile *cfg)
413 {
414 }
415
416 #endif