commit 0dcabba636a542050faf4f2e60e3755b217dac35
parent 6a675ba495996db615fb007194c6c42ed0bafb7c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 10 May 2026 13:38:47 -0700
arch/cg: thread RegClass through free_reg
Make CGTarget.free_reg take (Reg, RegClass) so backends pick the
right pool by class instead of probing int-then-fp by numeric range.
The probe-by-range strategy mis-routed when pools had overlapping
register numbers — rv64 set both int and fp pools at base=18, so
freeing fs2 found s2 first and panicked "already free".
Per-arch free_reg now switches on cls to pick the pool. All call
sites have the class on hand: Operand.cls for storage releases,
class_of_sv for SValue releases, and statically RC_INT for INDIRECT
bases (always pointer-typed) and indirect callees (function ptrs).
aa64 had the same latent overlap in regs 19..23 but the cg corpus
never allocated fp index >= 11; this closes that gap too.
rv64 parse: 153/10/1 → 163/0/1 (the 10 FP-allocating cases that hit
the rv64 overlap now pass at L0 and L1, R and E paths). aa64 cg and
parse suites unchanged.
Diffstat:
6 files changed, 48 insertions(+), 34 deletions(-)
diff --git a/src/arch/aarch64.c b/src/arch/aarch64.c
@@ -403,7 +403,7 @@ static AASlot* slot_get(AAImpl* a, FrameSlot fs);
static u32 force_reg_int(CGTarget* t, Operand op, u32 sf, u32 scratch);
static void aa_load(CGTarget* t, Operand dst, Operand addr, MemAccess ma);
static void aa_store(CGTarget* t, Operand addr, Operand src, MemAccess ma);
-static void aa_free_reg(CGTarget* t, Reg r);
+static void aa_free_reg(CGTarget* t, Reg r, RegClass cls);
/* ---- helpers ---- */
@@ -820,19 +820,25 @@ static Reg aa_alloc_reg(CGTarget* t, RegClass cls, const Type* ty) {
compiler_panic(t->c, a->loc, "aarch64 alloc_reg: class %d unimpl", (int)cls);
}
-static void aa_free_reg(CGTarget* t, Reg r) {
+static void aa_free_reg(CGTarget* t, Reg r, RegClass cls) {
AAImpl* a = impl_of(t);
- RegPool* pools[2] = {&a->int_pool, &a->fp_pool};
- for (u32 i = 0; i < 2; ++i) {
- int rc = regpool_free(pools[i], r);
- if (rc == 1) return;
- if (rc == -1) {
- compiler_panic(t->c, a->loc, "aarch64 free_reg: reg %u already free",
- (unsigned)r);
- }
+ RegPool* p;
+ switch (cls) {
+ case RC_INT: p = &a->int_pool; break;
+ case RC_FP: p = &a->fp_pool; break;
+ default:
+ compiler_panic(t->c, a->loc, "aarch64 free_reg: class %d unimpl",
+ (int)cls);
+ }
+ int rc = regpool_free(p, r);
+ if (rc == 1) return;
+ if (rc == -1) {
+ compiler_panic(t->c, a->loc,
+ "aarch64 free_reg: reg %u already free in %s pool",
+ (unsigned)r, cls == RC_FP ? "fp" : "int");
}
- compiler_panic(t->c, a->loc, "aarch64 free_reg: reg %u not a scratch reg",
- (unsigned)r);
+ compiler_panic(t->c, a->loc, "aarch64 free_reg: reg %u not in %s pool",
+ (unsigned)r, cls == RC_FP ? "fp" : "int");
}
static FrameSlot aa_frame_slot(CGTarget* t, const FrameSlotDesc* d) {
@@ -971,7 +977,7 @@ static void aa_spill_reg(CGTarget* t, Operand src, FrameSlot slot,
addr.type = ma.type;
addr.v.frame_slot = slot;
aa_store(t, addr, src, ma);
- aa_free_reg(t, src.v.reg);
+ aa_free_reg(t, src.v.reg, src.cls);
}
static void aa_reload_reg(CGTarget* t, Operand dst, FrameSlot slot,
diff --git a/src/arch/arch.h b/src/arch/arch.h
@@ -454,7 +454,7 @@ struct CGTarget {
* Real targets return physical scratch registers and implement spill/reload
* mechanics; opt_cgtarget returns fresh virtual regs and ignores spills. */
Reg (*alloc_reg)(CGTarget*, RegClass, const Type*);
- void (*free_reg)(CGTarget*, Reg); /* hint; opt_cgtarget ignores */
+ void (*free_reg)(CGTarget*, Reg, RegClass); /* hint; opt_cgtarget ignores */
FrameSlot (*frame_slot)(CGTarget*, const FrameSlotDesc*);
void (*param)(CGTarget*, const CGParamDesc*);
const Reg* (*clobbers)(CGTarget*, RegClass, u32* nregs);
diff --git a/src/arch/rv64.c b/src/arch/rv64.c
@@ -546,19 +546,23 @@ static Reg rv_alloc_reg(CGTarget* t, RegClass cls, const Type* ty) {
compiler_panic(t->c, a->loc, "rv64 alloc_reg: class %d unimpl", (int)cls);
}
-static void rv_free_reg(CGTarget* t, Reg r) {
+static void rv_free_reg(CGTarget* t, Reg r, RegClass cls) {
RImpl* a = impl_of(t);
- RegPool* pools[2] = {&a->int_pool, &a->fp_pool};
- for (u32 i = 0; i < 2; ++i) {
- int rc = regpool_free(pools[i], r);
- if (rc == 1) return;
- if (rc == -1) {
- compiler_panic(t->c, a->loc, "rv64 free_reg: reg %u already free",
- (unsigned)r);
- }
+ RegPool* p;
+ switch (cls) {
+ case RC_INT: p = &a->int_pool; break;
+ case RC_FP: p = &a->fp_pool; break;
+ default:
+ compiler_panic(t->c, a->loc, "rv64 free_reg: class %d unimpl", (int)cls);
+ }
+ int rc = regpool_free(p, r);
+ if (rc == 1) return;
+ if (rc == -1) {
+ compiler_panic(t->c, a->loc, "rv64 free_reg: reg %u already free in %s pool",
+ (unsigned)r, cls == RC_FP ? "fp" : "int");
}
- compiler_panic(t->c, a->loc, "rv64 free_reg: reg %u not a scratch reg",
- (unsigned)r);
+ compiler_panic(t->c, a->loc, "rv64 free_reg: reg %u not in %s pool",
+ (unsigned)r, cls == RC_FP ? "fp" : "int");
}
static FrameSlot rv_frame_slot(CGTarget* t, const FrameSlotDesc* d) {
@@ -730,7 +734,7 @@ static void rv_spill_reg(CGTarget* t, Operand src, FrameSlot slot,
addr.type = ma.type;
addr.v.frame_slot = slot;
rv_store(t, addr, src, ma);
- rv_free_reg(t, src.v.reg);
+ rv_free_reg(t, src.v.reg, src.cls);
}
static void rv_reload_reg(CGTarget* t, Operand dst, FrameSlot slot,
diff --git a/src/arch/x64.c b/src/arch/x64.c
@@ -32,8 +32,9 @@ static Reg xx_alloc_reg(CGTarget* t, RegClass cls, const Type* ty) {
(void)ty;
xx_panic(t, "alloc_reg");
}
-static void xx_free_reg(CGTarget* t, Reg r) {
+static void xx_free_reg(CGTarget* t, Reg r, RegClass cls) {
(void)r;
+ (void)cls;
xx_panic(t, "free_reg");
}
static FrameSlot xx_frame_slot(CGTarget* t, const FrameSlotDesc* d) {
diff --git a/src/cg/cg.c b/src/cg/cg.c
@@ -399,7 +399,7 @@ static SValue* pick_victim(CG* g, u8 cls) {
* runtime ownership and need nothing. */
static void release_arg_storage(CG* g, const Operand* st) {
if (st->kind == OPK_REG) {
- g->target->free_reg(g->target, st->v.reg);
+ g->target->free_reg(g->target, st->v.reg, st->cls);
} else if (st->kind == OPK_LOCAL) {
return_spill_slot(g, st->v.frame_slot, st->cls);
}
@@ -500,7 +500,7 @@ static void ensure_reg(CG* g, SValue* sv) {
* stack and not consumed by a downstream operation. */
static void release(CG* g, SValue* sv) {
if (sv->res == RES_REG) {
- g->target->free_reg(g->target, (Reg)reg_of_sv(sv));
+ g->target->free_reg(g->target, (Reg)reg_of_sv(sv), class_of_sv(sv));
} else if (sv->res == RES_SPILLED) {
return_spill_slot(g, sv->spill_slot, class_of_sv(sv));
sv->spill_slot = FRAME_SLOT_NONE;
@@ -897,9 +897,10 @@ static Operand force_reg(CG* g, SValue* v, const Type* ty) {
T->load_imm(T, dst, v->op.v.imm);
} else if (is_lvalue(&v->op)) {
T->load(T, dst, v->op, mem_for_lvalue(g, &v->op, ty));
- /* Old INDIRECT base reg is no longer referenced — release it. */
+ /* Old INDIRECT base reg is no longer referenced — release it.
+ * INDIRECT bases are always pointer-typed (RC_INT). */
if (v->op.kind == OPK_INDIRECT) {
- T->free_reg(T, v->op.v.ind.base);
+ T->free_reg(T, v->op.v.ind.base, RC_INT);
}
} else {
compiler_panic(g->c, g->cur_loc, "cg: cannot force operand to register");
@@ -982,7 +983,7 @@ void cg_inc_dec(CG* g, BinOp op, int post) {
/* Free whichever register is NOT being returned, plus any base reg the
* lvalue owned. */
- T->free_reg(T, post ? r_new : r_old);
+ T->free_reg(T, post ? r_new : r_old, type_class(ty));
release(g, &lv);
push(g, make_sv(post ? o_old : o_new, ty));
}
@@ -1155,7 +1156,8 @@ void cg_call(CG* g, u32 nargs, const Type* fn_type) {
g->avs_in_flight_n = 0;
if (callee.op.kind != OPK_GLOBAL) {
- T->free_reg(T, callee_op.v.reg);
+ /* Indirect callees are function pointers; they live in int regs. */
+ T->free_reg(T, callee_op.v.reg, RC_INT);
}
if (has_result) {
push(g, make_sv(desc.ret.storage, ret_ty));
diff --git a/src/opt/opt.c b/src/opt/opt.c
@@ -112,9 +112,10 @@ static Reg w_alloc_reg(CGTarget* t, RegClass cls, const Type* ty) {
return (Reg)v;
}
-static void w_free_reg(CGTarget* t, Reg r) {
+static void w_free_reg(CGTarget* t, Reg r, RegClass cls) {
(void)t;
(void)r;
+ (void)cls;
}
static FrameSlot w_frame_slot(CGTarget* t, const FrameSlotDesc* d) {