2 * liveness.c: liveness analysis
5 * Dietmar Maurer (dietmar@ximian.com)
7 * (C) 2002 Ximian, Inc.
11 #include <mono/metadata/debug-helpers.h>
15 static void mono_linear_scan2 (MonoCompile
*cfg
, GList
*vars
, GList
*regs
, regmask_t
*used_mask
);
18 mono_varlist_insert_sorted (MonoCompile
*cfg
, GList
*list
, MonoMethodVar
*mv
, int sort_type
)
23 return g_list_prepend (NULL
, mv
);
25 for (l
= list
; l
; l
= l
->next
) {
26 MonoMethodVar
*v1
= l
->data
;
29 if (mv
->spill_costs
>= v1
->spill_costs
) {
30 list
= g_list_insert_before (list
, l
, mv
);
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
);
39 if (mv
->range
.first_use
.abs_pos
<= v1
->range
.first_use
.abs_pos
) {
40 list
= g_list_insert_before (list
, l
, mv
);
46 list
= g_list_append (list
, mv
);
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
;
61 mono_varlist_sort (MonoCompile
*cfg
, GList
*list
, int sort_type
)
64 return g_list_sort (list
, compare_by_first_use_func
);
66 g_assert_not_reached ();
71 // #define DEBUG_LSCAN
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;
83 if (!cfg
->disable_reuse_registers
&& vars
&& (((MonoMethodVar
*)vars
->data
)->interval
!= NULL
)) {
84 mono_linear_scan2 (cfg
, vars
, regs
, used_mask
);
91 printf ("Linears scan for %s\n", mono_method_full_name (cfg
->method
, TRUE
));
95 for (l
= vars
; l
; l
= l
->next
) {
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
);
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
));
110 for (l
= vars
; l
; l
= l
->next
) {
114 printf ("START %2d %08x %08x\n", vmv
->idx
, vmv
->range
.first_use
.abs_pos
,
115 vmv
->range
.last_use
.abs_pos
);
117 /* expire old intervals in active */
118 if (!cfg
->disable_reuse_registers
) {
120 amv
= (MonoMethodVar
*)active
->data
;
122 if (amv
->range
.last_use
.abs_pos
> vmv
->range
.first_use
.abs_pos
)
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
);
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
) {
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
)) {
145 active
= g_list_delete_link (active
, a
);
148 active
= mono_varlist_insert_sorted (cfg
, active
, vmv
, 2);
150 active
= mono_varlist_insert_sorted (cfg
, active
, vmv
, 1);
153 printf ("SPILL0 %2d %08x %08x C%d\n", amv
->idx
,
154 amv
->range
.first_use
.abs_pos
, amv
->range
.last_use
.abs_pos
,
159 printf ("SPILL1 %2d %08x %08x C%d\n", vmv
->idx
,
160 vmv
->range
.first_use
.abs_pos
, vmv
->range
.last_use
.abs_pos
,
166 /* assign register */
170 vmv
->reg
= GPOINTER_TO_INT (regs
->data
);
172 used_regs
|= 1LL << vmv
->reg
;
174 regs
= g_list_delete_link (regs
, regs
);
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
);
181 active
= mono_varlist_insert_sorted (cfg
, active
, vmv
, TRUE
);
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
);
195 for (a
= active
; a
; a
= a
->next
) {
196 amv
= (MonoMethodVar
*)a
->data
;
197 gains
[amv
->reg
] += amv
->spill_costs
;
201 for (l
= vars
; l
; l
= l
->next
) {
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
;
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
));
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 */
229 for (l
= vars
; l
; l
= l
->next
) {
233 used_regs
|= 1LL << vmv
->reg
;
236 *used_mask
|= used_regs
;
239 if (cfg
->verbose_level
> 2)
240 printf ("EXIT: final used mask: %08x\n", *used_mask
);
244 g_list_free (active
);
249 compare_by_interval_start_pos_func (gconstpointer a
, gconstpointer b
)
251 MonoMethodVar
*v1
= (MonoMethodVar
*)a
;
252 MonoMethodVar
*v2
= (MonoMethodVar
*)b
;
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
)
265 #define LSCAN_DEBUG(a) do { a; } while (0)
267 #define LSCAN_DEBUG(a)
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;
281 mono_linear_scan2 (MonoCompile
*cfg
, GList
*vars
, GList
*regs
, regmask_t
*used_mask
)
283 GList
*unhandled
, *active
, *inactive
, *l
;
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
) {
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
);
305 MonoMethodVar
*current
= unhandled
->data
;
306 int pos
, reg
, max_free_pos
;
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
)
318 pos
= current
->interval
->range
->from
;
320 /* Check for intervals in active which expired or inactive */
322 /* FIXME: Optimize this */
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
));
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
));
344 /* Check for intervals in inactive which expired or active */
346 /* FIXME: Optimize this */
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
));
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
));
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
;
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
;
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
));
396 for (i
= 0; i
< n_regs
; ++i
)
397 if (free_pos
[i
] > max_free_pos
) {
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 */
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
;
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 */
424 GList
*min_spill_pos
;
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
) {
437 min_spill_value
= vmv
->spill_costs
;
441 /* Spill either the first active or the current interval */
442 min_spill_pos
= active
;
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
;
449 LSCAN_DEBUG (printf ("\tSpilled R%d\n", cfg
->varinfo
[vmv
->idx
]->dreg
));
450 active
= g_list_delete_link (active
, min_spill_pos
);
453 LSCAN_DEBUG (printf ("\tSpilled current (cost %d)\n", current
->spill_costs
));
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
) {
463 /* FIXME: This is x86 only */
464 gains
[i
] -= cfg
->method
->save_lmf
? 1 : 2;
470 /* Do the actual register assignment */
472 for (l
= vars
; l
; l
= l
->next
) {
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
;
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
));
496 cfg
->stat_n_regvars
= n_regvars
;
498 /* Compute used regs */
500 for (l
= vars
; l
; l
= l
->next
) {
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 */