kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 70d7ac3b3443aaf7afc32adeb979cbcfd4953444
parent 13beba6de38f1389ec3217e78f27ddacfabb57bc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 10 Jun 2026 08:28:02 -0700

cleanup: I.4 — route addr-CSE + SSA use-rewrite through central operand walk

pass_addr_fold's addr_cse_apply_to_inst and pass_ssa's reg_replace_inst_uses
each re-hand-rolled the main-opnds + IR_CALL/RET/ASM/INTRINSIC aux traversal.
Both are uniform over the operands the central opt_walk_inst_operands already
visits (it decomposes OPK_INDIRECT to base/index), so route them through it:
addr-CSE via a per-register remap callback, SSA via a use-only callback (defs
are renamed separately). Drops the now-dead reg_replace_abivalue_uses.

reg_define_inst_defs (needs per-def index + version stack) and
opt_collect_inst_uses (needs operand index + IR_PHI preds) stay hand-rolled —
the generic (Operand*, is_def) callback can't carry that context — as does
opt_hard_inst_use_def. Documented in TODO.

No behavior change: test-opt (cg_control/cg_switch/strength_reduce/
panic_recovery/cg_fp_cmp all 0 fail), test-cg-api 211/0, test-toy 1392/0/35.

Diffstat:
Mdoc/plan/TODO.md | 9+++++++--
Msrc/opt/pass_addr_fold.c | 89+++++++++++++++++++++----------------------------------------------------------
Msrc/opt/pass_ssa.c | 70++++++++++++++--------------------------------------------------------
3 files changed, 44 insertions(+), 124 deletions(-)

diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md @@ -91,8 +91,13 @@ Add new deferred fixes below as they are discovered. flag-parse blocks were assessed and are not byte-identical — different option structs.) - **I.2 — two parallel C-6.7.9 initializer walkers** (`lang/c/parse/parse_init.c:648-777`) re-encode the same traversal grammar. Factor a single grammar driver + leaf vtable. -- **I.4 — opt passes re-hand-roll the centralized operand-walk.** (The `ranges_overlap` - trampoline part is fixed.) +- **I.4 — two opt walks still hand-roll operand iteration that the central + `opt_walk_inst_operands` cannot express.** `reg_define_inst_defs` (pass_ssa) needs a + per-def index + version-stack (`pushed`) context, and `opt_collect_inst_uses` + (pass_analysis) needs the operand index + IR_PHI pred-value handling — neither fits + the generic `(Operand*, is_def)` callback without losing information. (`addr_cse_apply_to_inst` + and the SSA use-rewrite `reg_replace_inst_uses` now route through the central walk; + `opt_hard_inst_use_def` stays intentionally bespoke per its hardcoded opcode dispatch.) ## God-function decompositions (highest risk / lowest ROI — do when next touching) diff --git a/src/opt/pass_addr_fold.c b/src/opt/pass_addr_fold.c @@ -551,72 +551,29 @@ static u32 addr_cse_find_or_add(AddrCseEntry** entries, u32* n, u32* cap, return idx; } -static void addr_cse_apply_to_operand(Operand* op, const PReg* remap) { - /* remap is zero-initialized; 0 means "no remap" (preg 0 is reserved as - * unused). PREG_NONE = 0xffffffff and would be a valid remap target but - * we never produce that. */ - if (!op) return; - if (op->kind == OPK_REG) { - PReg p = (PReg)op->v.reg; - if (p != PREG_NONE && p != 0 && remap[p] != 0) op->v.reg = remap[p]; - } else if (op->kind == OPK_INDIRECT) { - PReg p = (PReg)op->v.ind.base; - if (p != PREG_NONE && p != 0 && remap[p] != 0) op->v.ind.base = remap[p]; - if (op->v.ind.index != (Reg)REG_NONE) { - PReg pi = (PReg)op->v.ind.index; - if (pi != PREG_NONE && pi != 0 && remap[pi] != 0) - op->v.ind.index = remap[pi]; - } - } +/* Per-register remap callback for the centralized operand walk. The walk + * decomposes OPK_INDIRECT into its OPK_REG base/index and writes them back, so + * a plain register remap here covers indirects too. `remap` is zero-init; 0 + * means "no remap" (preg 0 is reserved unused; we never produce PREG_NONE as a + * target). */ +static void addr_cse_remap_op(Func* f, Inst* in, Operand* op, int is_def, + void* ctx) { + const PReg* remap = (const PReg*)ctx; + PReg p; + (void)f; + (void)in; + (void)is_def; + if (!op || op->kind != OPK_REG) return; + p = (PReg)op->v.reg; + if (p != PREG_NONE && p != 0 && remap[p] != 0) op->v.reg = remap[p]; } -static void addr_cse_apply_to_inst(Inst* in, const PReg* remap) { - for (u32 o = 0; o < in->nopnds; ++o) - addr_cse_apply_to_operand(&in->opnds[o], remap); - /* IR_CALL aux carries operands too; rewrite both replay variants. */ - if ((IROp)in->op == IR_CALL) { - IRCallAux* aux = (IRCallAux*)in->extra.aux; - if (!aux) return; - if (aux->use_plan_replay) { - addr_cse_apply_to_operand(&aux->plan.callee, remap); - for (u32 i = 0; i < aux->plan.nargs; ++i) - addr_cse_apply_to_operand(&aux->plan.args[i].src, remap); - for (u32 i = 0; i < aux->plan.nrets; ++i) - addr_cse_apply_to_operand(&aux->plan.rets[i].dst, remap); - } else { - addr_cse_apply_to_operand(&aux->desc.callee, remap); - for (u32 i = 0; i < aux->desc.nargs; ++i) { - CGABIValue* v = (CGABIValue*)&aux->desc.args[i]; - addr_cse_apply_to_operand(&v->storage, remap); - for (u32 k = 0; k < v->nparts; ++k) - addr_cse_apply_to_operand((Operand*)&v->parts[k].op, remap); - } - addr_cse_apply_to_operand(&aux->desc.ret.storage, remap); - for (u32 k = 0; k < aux->desc.ret.nparts; ++k) - addr_cse_apply_to_operand((Operand*)&aux->desc.ret.parts[k].op, remap); - } - } else if ((IROp)in->op == IR_RET) { - IRRetAux* aux = (IRRetAux*)in->extra.aux; - if (aux && aux->present) { - addr_cse_apply_to_operand(&aux->val.storage, remap); - for (u32 k = 0; k < aux->val.nparts; ++k) - addr_cse_apply_to_operand((Operand*)&aux->val.parts[k].op, remap); - } - } else if ((IROp)in->op == IR_ASM_BLOCK) { - IRAsmAux* aux = (IRAsmAux*)in->extra.aux; - if (!aux) return; - for (u32 i = 0; i < aux->nin; ++i) - addr_cse_apply_to_operand(&aux->in_ops[i], remap); - for (u32 i = 0; i < aux->nout; ++i) - addr_cse_apply_to_operand(&aux->out_ops[i], remap); - } else if ((IROp)in->op == IR_INTRINSIC) { - IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; - if (!aux) return; - for (u32 i = 0; i < aux->narg; ++i) - addr_cse_apply_to_operand(&aux->args[i], remap); - for (u32 i = 0; i < aux->ndst; ++i) - addr_cse_apply_to_operand(&aux->dsts[i], remap); - } +static void addr_cse_apply_to_inst(Func* f, Inst* in, const PReg* remap) { + /* The centralized walk visits every operand the bespoke walk did — main + * opnds plus IR_CALL/RET/ASM/INTRINSIC aux — so a uniform per-register remap + * is equivalent. (It also visits an IR_PARAM_DECL's def, which is never a + * CSE target, so remap leaves it untouched.) */ + opt_walk_inst_operands(f, in, addr_cse_remap_op, (void*)remap); } Inst* opt_block_insert_at(Func* f, Block* bl, u32 at, u32 k) { @@ -742,7 +699,7 @@ void opt_addr_of_global_cse(Func* f) { for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; for (u32 i = 0; i < bl->ninsts; ++i) { - addr_cse_apply_to_inst(&bl->insts[i], remap); + addr_cse_apply_to_inst(f, &bl->insts[i], remap); } } @@ -918,7 +875,7 @@ void opt_hoist_loop_consts(Func* f) { for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; for (u32 i = 0; i < bl->ninsts; ++i) - addr_cse_apply_to_inst(&bl->insts[i], remap); + addr_cse_apply_to_inst(f, &bl->insts[i], remap); } opt_analysis_invalidate( diff --git a/src/opt/pass_ssa.c b/src/opt/pass_ssa.c @@ -303,13 +303,6 @@ static void reg_replace_use(RegRenameCtx* ctx, Operand* op) { } } -static void reg_replace_abivalue_uses(RegRenameCtx* ctx, CGABIValue* v) { - if (!v) return; - reg_replace_use(ctx, &v->storage); - for (u32 i = 0; i < v->nparts; ++i) - reg_replace_use(ctx, (Operand*)&v->parts[i].op); -} - static Val reg_define_operand(RegRenameCtx* ctx, u32 b, u32 i, Inst* in, Operand* op, u32 def_index, u32* pushed) { if (!op || op->kind != OPK_REG) return VAL_NONE; @@ -342,56 +335,21 @@ static u32 reg_def_index(const Inst* in, Reg r, u32 ordinal) { return 0; } -static void reg_replace_inst_uses(RegRenameCtx* ctx, Inst* in) { - for (u32 o = 0; o < in->nopnds; ++o) { - Operand* op = &in->opnds[o]; - int is_def = 0; - if (op->kind == OPK_REG) { - if ((IROp)in->op == IR_ATOMIC_CAS) - is_def = (o == 0 || o == 1) && reg_in_defs(in, op->v.reg); - else - is_def = o == 0 && reg_in_defs(in, op->v.reg); - } - if (!is_def) reg_replace_use(ctx, op); - } +static void reg_replace_use_cb(Func* f, Inst* in, Operand* op, int is_def, + void* ctx) { + (void)f; + (void)in; + if (!is_def) reg_replace_use((RegRenameCtx*)ctx, op); +} - switch ((IROp)in->op) { - case IR_CALL: { - IRCallAux* aux = (IRCallAux*)in->extra.aux; - if (!aux) break; - if (aux->use_plan_replay) { - reg_replace_use(ctx, &aux->plan.callee); - for (u32 i = 0; i < aux->plan.nargs; ++i) - reg_replace_use(ctx, &aux->plan.args[i].src); - } else { - reg_replace_use(ctx, &aux->desc.callee); - for (u32 i = 0; i < aux->desc.nargs; ++i) - reg_replace_abivalue_uses(ctx, (CGABIValue*)&aux->desc.args[i]); - } - break; - } - case IR_RET: { - IRRetAux* aux = (IRRetAux*)in->extra.aux; - if (aux && aux->present) reg_replace_abivalue_uses(ctx, &aux->val); - break; - } - case IR_SCOPE_BEGIN: - break; - case IR_ASM_BLOCK: { - IRAsmAux* aux = (IRAsmAux*)in->extra.aux; - if (!aux) break; - for (u32 i = 0; i < aux->nin; ++i) reg_replace_use(ctx, &aux->in_ops[i]); - break; - } - case IR_INTRINSIC: { - IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux; - if (!aux) break; - for (u32 i = 0; i < aux->narg; ++i) reg_replace_use(ctx, &aux->args[i]); - break; - } - default: - break; - } +static void reg_replace_inst_uses(RegRenameCtx* ctx, Inst* in) { + /* Route through the centralized operand walk: it applies the same use/def + * classification (incl. the IR_ATOMIC_CAS two-def case) and visits the same + * IR_CALL/RET/ASM/INTRINSIC aux operands, decomposing OPK_INDIRECT into its + * base/index — exactly what the bespoke walk did. The callback rewrites only + * uses; defs are renamed separately (reg_define_inst_defs, which needs the + * per-def index/version-stack context the generic callback can't carry). */ + opt_walk_inst_operands(ctx->f, in, reg_replace_use_cb, ctx); } static void reg_define_abivalue(RegRenameCtx* ctx, u32 b, u32 i, Inst* in,