[amd64] Remove the callee saved registers from MonoLMF, save/restore them normally...
[mono-project.git] / mono / mini / linear-scan.c
blobd778ab5fc73f700a880ab4f65618a59172e62a66
1 /*
2 * liveness.c: liveness analysis
4 * Author:
5 * Dietmar Maurer (dietmar@ximian.com)
7 * (C) 2002 Ximian, Inc.
8 */
10 #include "mini.h"
11 #include <mono/metadata/debug-helpers.h>
13 #ifndef DISABLE_JIT
15 static void mono_linear_scan2 (MonoCompile *cfg, GList *vars, GList *regs, regmask_t *used_mask);
17 GList *
18 mono_varlist_insert_sorted (MonoCompile *cfg, GList *list, MonoMethodVar *mv, int sort_type)
20 GList *l;
22 if (!list)
23 return g_list_prepend (NULL, mv);
25 for (l = list; l; l = l->next) {
26 MonoMethodVar *v1 = l->data;
28 if (sort_type == 2) {
29 if (mv->spill_costs >= v1->spill_costs) {
30 list = g_list_insert_before (list, l, mv);
31 break;
33 } else if (sort_type == 1) {
34 if (mv->range.last_use.abs_pos <= v1->range.last_use.abs_pos) {
35 list = g_list_insert_before (list, l, mv);
36 break;
38 } else {
39 if (mv->range.first_use.abs_pos <= v1->range.first_use.abs_pos) {
40 list = g_list_insert_before (list, l, mv);
41 break;
45 if (!l)
46 list = g_list_append (list, mv);
48 return list;
51 static gint
52 compare_by_first_use_func (gconstpointer a, gconstpointer b)
54 MonoMethodVar *v1 = (MonoMethodVar*)a;
55 MonoMethodVar *v2 = (MonoMethodVar*)b;
57 return v1->range.first_use.abs_pos - v2->range.first_use.abs_pos;
60 GList *
61 mono_varlist_sort (MonoCompile *cfg, GList *list, int sort_type)
63 if (sort_type == 0)
64 return g_list_sort (list, compare_by_first_use_func);
65 else
66 g_assert_not_reached ();
68 return NULL;
71 // #define DEBUG_LSCAN
73 void
74 mono_linear_scan (MonoCompile *cfg, GList *vars, GList *regs, regmask_t *used_mask)
76 GList *l, *a, *active = NULL;
77 MonoMethodVar *vmv, *amv;
78 int max_regs, n_regvars;
79 int gains [sizeof (regmask_t) * 8];
80 regmask_t used_regs = 0;
81 gboolean cost_driven;
83 if (!cfg->disable_reuse_registers && vars && (((MonoMethodVar*)vars->data)->interval != NULL)) {
84 mono_linear_scan2 (cfg, vars, regs, used_mask);
85 return;
88 cost_driven = TRUE;
90 #ifdef DEBUG_LSCAN
91 printf ("Linears scan for %s\n", mono_method_full_name (cfg->method, TRUE));
92 #endif
94 #ifdef DEBUG_LSCAN
95 for (l = vars; l; l = l->next) {
96 vmv = l->data;
97 printf ("VAR %d %08x %08x C%d\n", vmv->idx, vmv->range.first_use.abs_pos,
98 vmv->range.last_use.abs_pos, vmv->spill_costs);
100 #endif
101 max_regs = g_list_length (regs);
103 for (l = regs; l; l = l->next) {
104 int regnum = GPOINTER_TO_INT (l->data);
105 g_assert (regnum < G_N_ELEMENTS (gains));
106 gains [regnum] = 0;
109 /* linear scan */
110 for (l = vars; l; l = l->next) {
111 vmv = l->data;
113 #ifdef DEBUG_LSCAN
114 printf ("START %2d %08x %08x\n", vmv->idx, vmv->range.first_use.abs_pos,
115 vmv->range.last_use.abs_pos);
116 #endif
117 /* expire old intervals in active */
118 if (!cfg->disable_reuse_registers) {
119 while (active) {
120 amv = (MonoMethodVar *)active->data;
122 if (amv->range.last_use.abs_pos > vmv->range.first_use.abs_pos)
123 break;
125 #ifdef DEBUG_LSCAN
126 printf ("EXPIR %2d %08x %08x C%d R%d\n", amv->idx, amv->range.first_use.abs_pos,
127 amv->range.last_use.abs_pos, amv->spill_costs, amv->reg);
128 #endif
129 active = g_list_delete_link (active, active);
130 regs = g_list_prepend (regs, GINT_TO_POINTER (amv->reg));
131 gains [amv->reg] += amv->spill_costs;
135 if (active && g_list_length (active) == max_regs) {
136 /* Spill */
138 a = g_list_nth (active, max_regs - 1);
139 amv = (MonoMethodVar *)a->data;
141 if ((cost_driven && amv->spill_costs < vmv->spill_costs) ||
142 (!cost_driven && amv->range.last_use.abs_pos > vmv->range.last_use.abs_pos)) {
143 vmv->reg = amv->reg;
144 amv->reg = -1;
145 active = g_list_delete_link (active, a);
147 if (cost_driven)
148 active = mono_varlist_insert_sorted (cfg, active, vmv, 2);
149 else
150 active = mono_varlist_insert_sorted (cfg, active, vmv, 1);
152 #ifdef DEBUG_LSCAN
153 printf ("SPILL0 %2d %08x %08x C%d\n", amv->idx,
154 amv->range.first_use.abs_pos, amv->range.last_use.abs_pos,
155 amv->spill_costs);
156 #endif
157 } else {
158 #ifdef DEBUG_LSCAN
159 printf ("SPILL1 %2d %08x %08x C%d\n", vmv->idx,
160 vmv->range.first_use.abs_pos, vmv->range.last_use.abs_pos,
161 vmv->spill_costs);
162 #endif
163 vmv->reg = -1;
165 } else {
166 /* assign register */
168 g_assert (regs);
170 vmv->reg = GPOINTER_TO_INT (regs->data);
172 used_regs |= 1LL << vmv->reg;
174 regs = g_list_delete_link (regs, regs);
176 #ifdef DEBUG_LSCAN
177 printf ("ADD %2d %08x %08x C%d R%d\n", vmv->idx,
178 vmv->range.first_use.abs_pos, vmv->range.last_use.abs_pos,
179 vmv->spill_costs, vmv->reg);
180 #endif
181 active = mono_varlist_insert_sorted (cfg, active, vmv, TRUE);
185 #ifdef DEBUG_LSCAN
186 for (a = active; a; a = a->next) {
187 amv = (MonoMethodVar *)a->data;
188 printf ("ACT %2d %08x %08x C%d R%d\n", amv->idx, amv->range.first_use.abs_pos,
189 amv->range.last_use.abs_pos, amv->spill_costs, amv->reg);
191 printf ("NEXT\n");
192 #endif
195 for (a = active; a; a = a->next) {
196 amv = (MonoMethodVar *)a->data;
197 gains [amv->reg] += amv->spill_costs;
200 n_regvars = 0;
201 for (l = vars; l; l = l->next) {
202 vmv = l->data;
204 if (vmv->reg >= 0) {
205 if ((gains [vmv->reg] > mono_arch_regalloc_cost (cfg, vmv)) && (cfg->varinfo [vmv->idx]->opcode != OP_REGVAR)) {
206 if (cfg->verbose_level > 2) {
207 printf ("ALLOCATED R%d(%d) TO HREG %d COST %d\n", cfg->varinfo [vmv->idx]->dreg, vmv->idx, vmv->reg, vmv->spill_costs);
209 cfg->varinfo [vmv->idx]->opcode = OP_REGVAR;
210 cfg->varinfo [vmv->idx]->dreg = vmv->reg;
211 n_regvars ++;
212 } else {
213 if (cfg->verbose_level > 2)
214 printf ("COSTLY: R%d C%d C%d %s\n", vmv->idx, vmv->spill_costs, mono_arch_regalloc_cost (cfg, vmv), mono_arch_regname (vmv->reg));
215 vmv->reg = -1;
219 if (vmv->reg == -1) {
220 if (cfg->verbose_level > 2)
221 printf ("NOT REGVAR: %d\n", vmv->idx);
225 cfg->stat_n_regvars = n_regvars;
227 /* Compute used regs */
228 used_regs = 0;
229 for (l = vars; l; l = l->next) {
230 vmv = l->data;
232 if (vmv->reg >= 0)
233 used_regs |= 1LL << vmv->reg;
236 *used_mask |= used_regs;
238 #ifdef DEBUG_LSCAN
239 if (cfg->verbose_level > 2)
240 printf ("EXIT: final used mask: %08x\n", *used_mask);
241 #endif
243 g_list_free (regs);
244 g_list_free (active);
245 g_list_free (vars);
248 static gint
249 compare_by_interval_start_pos_func (gconstpointer a, gconstpointer b)
251 MonoMethodVar *v1 = (MonoMethodVar*)a;
252 MonoMethodVar *v2 = (MonoMethodVar*)b;
254 if (v1 == v2)
255 return 0;
256 else if (v1->interval->range && v2->interval->range)
257 return v1->interval->range->from - v2->interval->range->from;
258 else if (v1->interval->range)
259 return -1;
260 else
261 return 1;
264 #if 0
265 #define LSCAN_DEBUG(a) do { a; } while (0)
266 #else
267 #define LSCAN_DEBUG(a)
268 #endif
270 /* FIXME: This is x86 only */
271 static inline guint32
272 regalloc_cost (MonoCompile *cfg, MonoMethodVar *vmv)
274 MonoInst *ins = cfg->varinfo [vmv->idx];
276 /* Load if it is an argument */
277 return (ins->opcode == OP_ARG) ? 1 : 0;
280 void
281 mono_linear_scan2 (MonoCompile *cfg, GList *vars, GList *regs, regmask_t *used_mask)
283 GList *unhandled, *active, *inactive, *l;
284 MonoMethodVar *vmv;
285 gint32 free_pos [sizeof (regmask_t) * 8];
286 gint32 gains [sizeof (regmask_t) * 8];
287 regmask_t used_regs = 0;
288 int n_regs, n_regvars, i;
290 for (l = vars; l; l = l->next) {
291 vmv = l->data;
292 LSCAN_DEBUG (printf ("VAR R%d %08x %08x C%d\n", cfg->varinfo [vmv->idx]->dreg, vmv->range.first_use.abs_pos,
293 vmv->range.last_use.abs_pos, vmv->spill_costs));
296 LSCAN_DEBUG (printf ("Linear Scan 2 for %s:\n", mono_method_full_name (cfg->method, TRUE)));
298 n_regs = g_list_length (regs);
299 memset (gains, 0, n_regs * sizeof (gint32));
300 unhandled = g_list_sort (g_list_copy (vars), compare_by_interval_start_pos_func);
301 active = NULL;
302 inactive = NULL;
304 while (unhandled) {
305 MonoMethodVar *current = unhandled->data;
306 int pos, reg, max_free_pos;
307 gboolean changed;
309 unhandled = g_list_delete_link (unhandled, unhandled);
311 LSCAN_DEBUG (printf ("Processing R%d: ", cfg->varinfo [current->idx]->dreg));
312 LSCAN_DEBUG (mono_linterval_print (current->interval));
313 LSCAN_DEBUG (printf ("\n"));
315 if (!current->interval->range)
316 continue;
318 pos = current->interval->range->from;
320 /* Check for intervals in active which expired or inactive */
321 changed = TRUE;
322 /* FIXME: Optimize this */
323 while (changed) {
324 changed = FALSE;
325 for (l = active; l != NULL; l = l->next) {
326 MonoMethodVar *v = (MonoMethodVar*)l->data;
328 if (v->interval->last_range->to < pos) {
329 active = g_list_delete_link (active, l);
330 LSCAN_DEBUG (printf ("Interval R%d has expired\n", cfg->varinfo [v->idx]->dreg));
331 changed = TRUE;
332 break;
334 else if (!mono_linterval_covers (v->interval, pos)) {
335 inactive = g_list_append (inactive, v);
336 active = g_list_delete_link (active, l);
337 LSCAN_DEBUG (printf ("Interval R%d became inactive\n", cfg->varinfo [v->idx]->dreg));
338 changed = TRUE;
339 break;
344 /* Check for intervals in inactive which expired or active */
345 changed = TRUE;
346 /* FIXME: Optimize this */
347 while (changed) {
348 changed = FALSE;
349 for (l = inactive; l != NULL; l = l->next) {
350 MonoMethodVar *v = (MonoMethodVar*)l->data;
352 if (v->interval->last_range->to < pos) {
353 inactive = g_list_delete_link (inactive, l);
354 LSCAN_DEBUG (printf ("\tInterval R%d has expired\n", cfg->varinfo [v->idx]->dreg));
355 changed = TRUE;
356 break;
358 else if (mono_linterval_covers (v->interval, pos)) {
359 active = g_list_append (active, v);
360 inactive = g_list_delete_link (inactive, l);
361 LSCAN_DEBUG (printf ("\tInterval R%d became active\n", cfg->varinfo [v->idx]->dreg));
362 changed = TRUE;
363 break;
368 /* Find a register for the current interval */
369 for (i = 0; i < n_regs; ++i)
370 free_pos [i] = ((gint32)0x7fffffff);
372 for (l = active; l != NULL; l = l->next) {
373 MonoMethodVar *v = (MonoMethodVar*)l->data;
375 if (v->reg >= 0) {
376 free_pos [v->reg] = 0;
377 LSCAN_DEBUG (printf ("\threg %d is busy (cost %d)\n", v->reg, v->spill_costs));
381 for (l = inactive; l != NULL; l = l->next) {
382 MonoMethodVar *v = (MonoMethodVar*)l->data;
383 gint32 intersect_pos;
385 if (v->reg >= 0) {
386 intersect_pos = mono_linterval_get_intersect_pos (current->interval, v->interval);
387 if (intersect_pos != -1) {
388 free_pos [v->reg] = intersect_pos;
389 LSCAN_DEBUG (printf ("\threg %d becomes free at %d\n", v->reg, intersect_pos));
394 max_free_pos = -1;
395 reg = -1;
396 for (i = 0; i < n_regs; ++i)
397 if (free_pos [i] > max_free_pos) {
398 reg = i;
399 max_free_pos = free_pos [i];
402 g_assert (reg != -1);
404 if (free_pos [reg] >= current->interval->last_range->to) {
405 /* Register available for whole interval */
406 current->reg = reg;
407 LSCAN_DEBUG (printf ("\tAssigned hreg %d to R%d\n", reg, cfg->varinfo [current->idx]->dreg));
409 active = g_list_append (active, current);
410 gains [current->reg] += current->spill_costs;
412 else {
414 * free_pos [reg] > 0 means there is a register available for parts
415 * of the interval, so splitting it is possible. This is not yet
416 * supported, so we spill in this case too.
419 /* Spill an interval */
421 /* FIXME: Optimize the selection of the interval */
423 if (active) {
424 GList *min_spill_pos;
425 #if 0
427 * This favors registers with big spill costs, thus larger liveness ranges,
428 * thus actually leading to worse code size.
430 guint32 min_spill_value = G_MAXINT32;
432 for (l = active; l != NULL; l = l->next) {
433 vmv = (MonoMethodVar*)l->data;
435 if (vmv->spill_costs < min_spill_value) {
436 min_spill_pos = l;
437 min_spill_value = vmv->spill_costs;
440 #else
441 /* Spill either the first active or the current interval */
442 min_spill_pos = active;
443 #endif
444 vmv = (MonoMethodVar*)min_spill_pos->data;
445 if (vmv->spill_costs < current->spill_costs) {
446 // if (vmv->interval->last_range->to < current->interval->last_range->to) {
447 gains [vmv->reg] -= vmv->spill_costs;
448 vmv->reg = -1;
449 LSCAN_DEBUG (printf ("\tSpilled R%d\n", cfg->varinfo [vmv->idx]->dreg));
450 active = g_list_delete_link (active, min_spill_pos);
452 else
453 LSCAN_DEBUG (printf ("\tSpilled current (cost %d)\n", current->spill_costs));
455 else
456 LSCAN_DEBUG (printf ("\tSpilled current\n"));
460 /* Decrease the gains by the cost of saving+restoring the register */
461 for (i = 0; i < n_regs; ++i) {
462 if (gains [i]) {
463 /* FIXME: This is x86 only */
464 gains [i] -= cfg->method->save_lmf ? 1 : 2;
465 if (gains [i] < 0)
466 gains [i] = 0;
470 /* Do the actual register assignment */
471 n_regvars = 0;
472 for (l = vars; l; l = l->next) {
473 vmv = l->data;
475 if (vmv->reg >= 0) {
476 int reg_index = vmv->reg;
478 /* During allocation, vmv->reg is an index into the regs list */
479 vmv->reg = GPOINTER_TO_INT (g_list_nth_data (regs, vmv->reg));
481 if ((gains [reg_index] > regalloc_cost (cfg, vmv)) && (cfg->varinfo [vmv->idx]->opcode != OP_REGVAR)) {
482 if (cfg->verbose_level > 2)
483 printf ("REGVAR R%d G%d C%d %s\n", cfg->varinfo [vmv->idx]->dreg, gains [reg_index], regalloc_cost (cfg, vmv), mono_arch_regname (vmv->reg));
484 cfg->varinfo [vmv->idx]->opcode = OP_REGVAR;
485 cfg->varinfo [vmv->idx]->dreg = vmv->reg;
486 n_regvars ++;
488 else {
489 if (cfg->verbose_level > 2)
490 printf ("COSTLY: %s R%d G%d C%d %s\n", mono_method_full_name (cfg->method, TRUE), cfg->varinfo [vmv->idx]->dreg, gains [reg_index], regalloc_cost (cfg, vmv), mono_arch_regname (vmv->reg));
491 vmv->reg = -1;
496 cfg->stat_n_regvars = n_regvars;
498 /* Compute used regs */
499 used_regs = 0;
500 for (l = vars; l; l = l->next) {
501 vmv = l->data;
503 if (vmv->reg >= 0)
504 used_regs |= 1LL << vmv->reg;
507 *used_mask |= used_regs;
509 g_list_free (active);
510 g_list_free (inactive);
513 #endif /* #ifndef DISABLE_JIT */