Skip to content

Commit 26fd44c

Browse files
committed
Enhance register allocation with usage-based spill
Building on the VReg abstraction, this commit adds intelligent spill decisions based on variable usage patterns and context. - Track variable lifetime with first_use, last_use, and use_count - Add loop_depth field for identifying loop-resident variables - Transform spill cost from simple distance to multi-factor heuristic: * Loop variables: +200 cost per nesting level * Usage frequency: +5 cost per use * Live range length: +20 for long ranges * Constants: -50 (prefer spilling for rematerialization)
1 parent ee6d834 commit 26fd44c

File tree

5 files changed

+88
-21
lines changed

5 files changed

+88
-21
lines changed

src/arm-codegen.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -485,16 +485,18 @@ void code_generate(void)
485485
emit_ph2_ir(ph2_ir);
486486

487487
/* prepare 'argc' and 'argv', then proceed to 'main' function */
488-
emit(__movw(__AL, __r8, GLOBAL_FUNC->stack_size));
489-
emit(__movt(__AL, __r8, GLOBAL_FUNC->stack_size));
490-
emit(__add_r(__AL, __r8, __r12, __r8));
491-
emit(__lw(__AL, __r0, __r8, 0));
492-
emit(__add_i(__AL, __r1, __r8, 4));
493-
emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size));
488+
if (MAIN_BB) {
489+
emit(__movw(__AL, __r8, GLOBAL_FUNC->stack_size));
490+
emit(__movt(__AL, __r8, GLOBAL_FUNC->stack_size));
491+
emit(__add_r(__AL, __r8, __r12, __r8));
492+
emit(__lw(__AL, __r0, __r8, 0));
493+
emit(__add_i(__AL, __r1, __r8, 4));
494+
emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size));
494495

495-
/* exit with main's return value - r0 already has the return value */
496-
emit(__mov_i(__AL, __r7, 1));
497-
emit(__svc());
496+
/* exit with main's return value - r0 already has the return value */
497+
emit(__mov_i(__AL, __r7, 1));
498+
emit(__svc());
499+
}
498500

499501
for (int i = 0; i < ph2_ir_idx; i++) {
500502
ph2_ir = PH2_IR_FLATTEN[i];

src/defs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ struct var {
375375
int vreg_id; /* Virtual register ID */
376376
int phys_reg; /* Physical register assignment (-1 if unassigned) */
377377
int vreg_flags; /* VReg flags */
378+
int first_use; /* First instruction index where variable is used */
379+
int last_use; /* Last instruction index where variable is used */
380+
int loop_depth; /* Nesting depth if variable is in a loop */
381+
int use_count; /* Number of times variable is used */
378382
};
379383

380384
typedef struct {

src/parser.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ var_t *require_var(block_t *blk)
6464
var_list->elements[var_list->size++] = var;
6565
var->consumed = -1;
6666
var->phys_reg = -1;
67+
var->first_use = -1;
68+
var->last_use = -1;
69+
var->loop_depth = 0;
70+
var->use_count = 0;
6771
var->base = var;
6872
var->type = TY_int;
6973
return var;

src/reg-alloc.c

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ bool check_live_out(basic_block_t *bb, var_t *var)
5454
return false;
5555
}
5656

57+
void track_var_use(var_t *var, int insn_idx)
58+
{
59+
if (!var)
60+
return;
61+
62+
var->use_count++;
63+
64+
if (var->first_use < 0)
65+
var->first_use = insn_idx;
66+
67+
var->last_use = insn_idx;
68+
}
69+
5770
void refresh(basic_block_t *bb, insn_t *insn)
5871
{
5972
for (int i = 0; i < REG_CNT; i++) {
@@ -93,18 +106,57 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op)
93106
return n;
94107
}
95108

109+
/* Calculate the cost of spilling a variable from a register.
110+
* Higher cost means the variable is more valuable to keep in a register.
111+
* The cost is computed based on multiple factors that affect performance.
112+
*/
96113
int calculate_spill_cost(var_t *var, basic_block_t *bb, int current_idx)
97114
{
115+
int cost = 0;
116+
117+
/* Variables that are live-out of the basic block must be spilled anyway,
118+
* so give them a high cost to prefer spilling them over others
119+
*/
98120
if (check_live_out(bb, var))
99-
return 1000;
121+
cost += 1000;
100122

123+
/* Variables that will be used soon should have higher cost.
124+
* The closer the next use, the higher the penalty for spilling
125+
*/
101126
if (var->consumed > current_idx) {
102127
int distance = var->consumed - current_idx;
103128
if (distance < 10)
104-
return 100 - distance * 10;
129+
cost += 100 - distance * 10; /* Max 100 points for immediate use */
130+
}
131+
132+
/* Frequently used variables should stay in registers.
133+
* Each use adds 5 points to the cost
134+
*/
135+
if (var->use_count > 0)
136+
cost += var->use_count * 5;
137+
138+
/* Variables inside loops are accessed repeatedly, so they should have much
139+
* higher priority to stay in registers (200 points per level)
140+
*/
141+
if (var->loop_depth > 0)
142+
cost += var->loop_depth * 200;
143+
144+
/* Constants can be easily reloaded, so prefer spilling them by reducing
145+
* their cost
146+
*/
147+
if (var->is_const)
148+
cost -= 50;
149+
150+
/* Variables with long live ranges may benefit from spilling to free up
151+
* registers for other variables
152+
*/
153+
if (var->first_use >= 0 && var->last_use >= 0) {
154+
int range_length = var->last_use - var->first_use;
155+
if (range_length > 100)
156+
cost += 20; /* Small penalty for very long live ranges */
105157
}
106158

107-
return 0;
159+
return cost;
108160
}
109161

110162
int find_best_spill(basic_block_t *bb,
@@ -269,9 +321,8 @@ int prepare_dest(basic_block_t *bb, var_t *var, int operand_0, int operand_1)
269321
}
270322
}
271323

272-
if (REGS[spilled].var) {
324+
if (REGS[spilled].var)
273325
vreg_clear_phys(REGS[spilled].var);
274-
}
275326

276327
spill_var(bb, REGS[spilled].var, spilled);
277328
REGS[spilled].var = var;
@@ -512,6 +563,7 @@ void reg_alloc(void)
512563

513564
switch (insn->opcode) {
514565
case OP_unwound_phi:
566+
track_var_use(insn->rs1, insn->idx);
515567
src0 = prepare_operand(bb, insn->rs1, -1);
516568

517569
if (!insn->rd->offset) {
@@ -610,6 +662,7 @@ void reg_alloc(void)
610662
if (insn->rd->consumed == -1)
611663
break;
612664

665+
track_var_use(insn->rs1, insn->idx);
613666
src0 = find_in_regs(insn->rs1);
614667

615668
/* If operand is loaded from stack, clear the original slot
@@ -754,6 +807,8 @@ void reg_alloc(void)
754807
case OP_bit_and:
755808
case OP_bit_or:
756809
case OP_bit_xor:
810+
track_var_use(insn->rs1, insn->idx);
811+
track_var_use(insn->rs2, insn->idx);
757812
src0 = prepare_operand(bb, insn->rs1, -1);
758813
src1 = prepare_operand(bb, insn->rs2, src0);
759814
dest = prepare_dest(bb, insn->rd, src0, src1);

src/riscv-codegen.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,14 +467,16 @@ void code_generate(void)
467467

468468
/* prepare 'argc' and 'argv', then proceed to 'main' function */
469469
/* use original sp saved in s0 to get argc/argv */
470-
emit(__addi(__t0, __s0, 0));
471-
emit(__lw(__a0, __t0, 0));
472-
emit(__addi(__a1, __t0, 4));
473-
emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size));
470+
if (MAIN_BB) {
471+
emit(__addi(__t0, __s0, 0));
472+
emit(__lw(__a0, __t0, 0));
473+
emit(__addi(__a1, __t0, 4));
474+
emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size));
474475

475-
/* exit with main's return value in a0 */
476-
emit(__addi(__a7, __zero, 93));
477-
emit(__ecall());
476+
/* exit with main's return value in a0 */
477+
emit(__addi(__a7, __zero, 93));
478+
emit(__ecall());
479+
}
478480

479481
for (int i = 0; i < ph2_ir_idx; i++) {
480482
ph2_ir = PH2_IR_FLATTEN[i];

0 commit comments

Comments
 (0)