/* Source code for RCC-Legacy. Copyright (C) 2026 Richard Qin This file is part of RCC. RCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. RCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with RCC; see the file LICENSE. If not see . */ /// @file scalar_repl.cc /// @brief Converts in-loop memory accumulators into a register + phi node. /// /// See docs/scalar_repl.md for architectural notes. #include "rcc/all.h" #include #include namespace rcc { namespace { Ins make_nop_ins() { Ins i = {}; i.op = Onop; return i; } struct LoopInfo { Blk* header; std::vector body; Blk* preheader; Blk* latch; Blk* exit_blk; Blk* exit_pred; // exit_blk's only pred inside the body }; bool blk_in(Blk* x, const std::vector& body) { for (Blk* b : body) if (b == x) return true; return false; } bool find_loops(Fn* fn, std::vector& out) { out.clear(); for (Blk* b = fn->start; b; b = b->link) b->visit = ~0u; for (uint i = 0; i < fn->nblk; i++) { Blk* hd = fn->rpo[i]; bool has_back = false; for (uint k = 0; k < hd->npred; k++) if (hd->pred[k]->id >= i) { has_back = true; break; } if (!has_back) continue; LoopInfo L{hd, {hd}, nullptr, nullptr, nullptr, nullptr}; std::vector stack; for (uint k = 0; k < hd->npred; k++) if (hd->pred[k]->id >= i) stack.push_back(hd->pred[k]); while (!stack.empty()) { Blk* n = stack.back(); stack.pop_back(); if (n->id < hd->id || n->visit == hd->id) continue; n->visit = hd->id; L.body.push_back(n); for (uint k = 0; k < n->npred; k++) stack.push_back(n->pred[k]); } Blk* phdr = nullptr; Blk* latch = nullptr; bool unique = true; for (uint k = 0; k < hd->npred; k++) { Blk* p = hd->pred[k]; if (blk_in(p, L.body)) { if (latch) { unique = false; break; } latch = p; } else { if (phdr) { unique = false; break; } phdr = p; } } if (!unique || !phdr || !latch) continue; L.preheader = phdr; L.latch = latch; Blk* exit_blk = nullptr; bool exit_unique = true; for (Blk* b : L.body) { Blk* succs[2] = {b->s1, b->s2}; for (Blk* s : succs) { if (!s || blk_in(s, L.body)) continue; if (exit_blk && exit_blk != s) { exit_unique = false; break; } exit_blk = s; } if (!exit_unique) break; } if (!exit_unique || !exit_blk) continue; int from_loop = 0; Blk* exit_pred = nullptr; for (uint k = 0; k < exit_blk->npred; k++) if (blk_in(exit_blk->pred[k], L.body)) { from_loop++; exit_pred = exit_blk->pred[k]; } if (from_loop != 1) continue; L.exit_blk = exit_blk; L.exit_pred = exit_pred; out.push_back(std::move(L)); } return !out.empty(); } // Recursively check address invariance. bool is_pure_arith(int op) { switch (op) { case Oadd: case Osub: case Omul: case Oand: case Oor: case Oxor: case Oshl: case Oshr: case Osar: case Oextsw: case Oextuw: case Oextsh: case Oextuh: case Oextsb: case Oextub: case Ocopy: return true; default: return false; } } // Find phi node for t in a blk (t->def doesn't point to phi, must scan). Phi* find_phi(Fn* fn, Ref r) { if (rtype(r) != RTmp) return nullptr; Tmp* t = &fn->tmp[r.val]; if (t->bid == ~0u) return nullptr; Blk* b = fn->rpo[t->bid]; for (Phi* p = b->phi; p; p = p->link) if (req(p->to, r)) return p; return nullptr; } // Each incoming phi arg must be invariant. Cannot do optimistic cycle back-edge. bool phi_invariant(Fn* fn, Phi* p, const LoopInfo& L) { for (uint k = 0; k < p->narg; k++) { Ref a = p->arg[k]; if (req(a, p->to)) continue; if (rtype(a) == RCon) continue; if (rtype(a) != RTmp) return false; Tmp* t = &fn->tmp[a.val]; if (t->bid == ~0u) continue; if (blk_in(fn->rpo[t->bid], L.body)) return false; } return true; } bool addr_invariant_rec(Fn* fn, Ref addr, const LoopInfo& L, int depth) { if (rtype(addr) == RCon) return true; if (rtype(addr) != RTmp) return false; Tmp* t = &fn->tmp[addr.val]; if (t->bid == ~0u) return true; if (!blk_in(fn->rpo[t->bid], L.body)) return true; if (depth <= 0) return false; if (Phi* p = find_phi(fn, addr)) return phi_invariant(fn, p, L); Ins* d = t->def; if (!d || !is_pure_arith(static_cast(d->op))) return false; if (!addr_invariant_rec(fn, d->arg[0], L, depth - 1)) return false; if (!req(d->arg[1], Ref::none()) && !addr_invariant_rec(fn, d->arg[1], L, depth - 1)) return false; return true; } bool addr_invariant(Fn* fn, Ref addr, const LoopInfo& L) { return addr_invariant_rec(fn, addr, L, 8); } // Structural equivalence: two Ref chains compute the same value. bool addr_same_value(Fn* fn, Ref a, Ref b, int depth = 8) { if (req(a, b)) return true; if (rtype(a) == RCon && rtype(b) == RCon) { Con* ca = &fn->con[a.val]; Con* cb = &fn->con[b.val]; if (ca->type != cb->type) return false; if (ca->type == Con::CAddr && !symeq(ca->sym, cb->sym)) return false; return ca->bits.i == cb->bits.i; } if (rtype(a) != RTmp || rtype(b) != RTmp) return false; if (depth <= 0) return false; Tmp* ta = &fn->tmp[a.val]; Tmp* tb = &fn->tmp[b.val]; Ins* da = ta->def; Ins* db = tb->def; if (!da || !db) return false; if (da->op != db->op) return false; if (!is_pure_arith(static_cast(da->op))) return false; if (!addr_same_value(fn, da->arg[0], db->arg[0], depth - 1)) return false; if (req(da->arg[1], Ref::none()) && req(db->arg[1], Ref::none())) return true; return addr_same_value(fn, da->arg[1], db->arg[1], depth - 1); } // Clone invariant address chain to preheader. Ref clone_invariant_to_preheader(Ctx* ctx, Fn* fn, Ref r, const LoopInfo& L, std::vector>& cache, int depth = 8) { if (rtype(r) == RCon) return r; if (rtype(r) != RTmp) return Ref::none(); Tmp* t = &fn->tmp[r.val]; if (t->bid == ~0u || !blk_in(fn->rpo[t->bid], L.body)) return r; // Already defined outside loop, reuse directly if (depth <= 0) return Ref::none(); for (auto& kv : cache) if (kv.first == r.val) return kv.second; Phi* p = find_phi(fn, r); if (p) { // Look for preheader-side arg in phi.blk[] for (uint k = 0; k < p->narg; k++) if (p->blk[k] == L.preheader) { Ref v = clone_invariant_to_preheader(ctx, fn, p->arg[k], L, cache, depth - 1); if (rtype(v) == -1) return Ref::none(); cache.push_back(std::make_pair(static_cast(r.val), v)); return v; } return Ref::none(); } Ins* d = t->def; if (!d || !is_pure_arith(static_cast(d->op))) return Ref::none(); Ref a0 = clone_invariant_to_preheader(ctx, fn, d->arg[0], L, cache, depth - 1); if (rtype(a0) == -1) return Ref::none(); Ref a1 = Ref::none(); if (!req(d->arg[1], Ref::none())) { a1 = clone_invariant_to_preheader(ctx, fn, d->arg[1], L, cache, depth - 1); if (rtype(a1) == -1) return Ref::none(); } Ref nt = newtmp(ctx, "sr.addr", static_cast(d->cls), fn); Ins ni{}; ni.op = d->op; ni.cls = d->cls; ni.to = nt; ni.arg[0] = a0; ni.arg[1] = a1; addins(ctx, &L.preheader->ins, &L.preheader->nins, &ni); cache.push_back(std::make_pair(static_cast(r.val), nt)); return nt; } // Globally replace all ref==from with to (in phi.arg / ins.arg / jmp.arg / jmp.arg2). void rewrite_ref(Fn* fn, Ref from, Ref to) { for (Blk* b = fn->start; b; b = b->link) { for (Phi* p = b->phi; p; p = p->link) for (uint k = 0; k < p->narg; k++) if (req(p->arg[k], from)) p->arg[k] = to; for (Ins* i = b->ins; i < &b->ins[b->nins]; i++) { if (req(i->arg[0], from)) i->arg[0] = to; if (req(i->arg[1], from)) i->arg[1] = to; } if (req(b->jmp.arg, from)) b->jmp.arg = to; if (req(b->jmp.arg2, from)) b->jmp.arg2 = to; } } void prepend_ins(Ctx* ctx, Blk* b, const Ins& ins) { vgrow(ctx, &b->ins, b->nins + 1); std::memmove(b->ins + 1, b->ins, b->nins * sizeof(Ins)); b->ins[0] = ins; b->nins += 1; } // Try one promotion on L. Returns true on hit; outer loop recomputes use/dom/alias. bool try_promote(Ctx* ctx, Fn* fn, const LoopInfo& L) { for (Blk* b : L.body) { for (Ins* iload = b->ins; iload < &b->ins[b->nins]; iload++) { if (!isload(iload->op)) continue; Ref addr = iload->arg[0]; if (!addr_invariant(fn, addr, L)) continue; int sz = loadsz(iload); std::vector matched_loads; Blk* store_blk = nullptr; Ins* matched_store = nullptr; int store_count = 0; bool ok = true; // Three-tier classification: structurally identical -> matched, NoAlias -> skip, // MayAlias -> bail. auto classify = [&](Ref jaddr, int jsz) -> int { // ret: 0=matched, 1=skip(NoAlias), 2=bail if (jsz != sz) return 2; // Different size cannot be promoted (conservative) if (addr_same_value(fn, addr, jaddr)) return 0; int delta; int r = alias(addr, 0, sz, jaddr, jsz, &delta, fn); if (r == NoAlias) return 1; if (r == MustAlias && delta == 0) return 0; return 2; }; for (Blk* bb : L.body) { for (Ins* j = bb->ins; j < &bb->ins[bb->nins]; j++) { if (j->op == Ocall || j->op == Oblit0 || j->op == Oblit1) { ok = false; break; } if (isload(j->op)) { int jsz = loadsz(j); // Different size but NoAlias can still be skipped if (jsz != sz) { int delta; int r = alias(addr, 0, sz, j->arg[0], jsz, &delta, fn); if (r != NoAlias) { ok = false; break; } continue; } int c = classify(j->arg[0], jsz); if (c == 0) matched_loads.push_back(j); else if (c == 2) { ok = false; break; } } else if (isstore(j->op)) { int jsz = storesz(j); if (jsz != sz) { int delta; int r = alias(addr, 0, sz, j->arg[1], jsz, &delta, fn); if (r != NoAlias) { ok = false; break; } continue; } int c = classify(j->arg[1], jsz); if (c == 0) { store_count++; if (store_count > 1) { ok = false; break; } matched_store = j; store_blk = bb; } else if (c == 2) { ok = false; break; } } } if (!ok) break; } if (!ok || store_count != 1) continue; // Store must be must-execute. if (!dom(store_blk, L.latch)) continue; if (!dom(store_blk, L.exit_pred)) continue; int cls = static_cast(iload->cls); // Clone address chain to preheader, then issue load. std::vector> addr_cache; Ref pre_addr = clone_invariant_to_preheader(ctx, fn, addr, L, addr_cache); if (rtype(pre_addr) == -1) continue; // Clone failed (rare corner case) Ref init_ref = newtmp(ctx, "sr.init", cls, fn); Ins pre_ld{}; pre_ld.op = iload->op; pre_ld.cls = static_cast(cls); pre_ld.to = init_ref; pre_ld.arg[0] = pre_addr; pre_ld.arg[1] = Ref::none(); addins(ctx, &L.preheader->ins, &L.preheader->nins, &pre_ld); // Insert phi at header top. Ref back_val = matched_store->arg[0]; Ref phi_ref = newtmp(ctx, "sr.phi", cls, fn); Phi* p = static_cast(alloc(ctx, sizeof(Phi))); std::memset(p, 0, sizeof *p); p->to = phi_ref; p->cls = static_cast(cls); p->narg = 2; p->arg = static_cast(vnew(ctx, 2, sizeof(Ref), PFn)); p->blk = static_cast(vnew(ctx, 2, sizeof(Blk*), PFn)); p->arg[0] = init_ref; p->blk[0] = L.preheader; p->arg[1] = back_val; p->blk[1] = L.latch; p->link = L.header->phi; L.header->phi = p; // Change all matched_loads def tmps to phi_ref; original loads become Onop. for (Ins* ml : matched_loads) { rewrite_ref(fn, ml->to, phi_ref); *ml = make_nop_ins(); } // Sink store at top of exit_blk. Ins sink{}; sink.op = matched_store->op; sink.cls = matched_store->cls; sink.to = Ref::none(); sink.arg[0] = phi_ref; sink.arg[1] = pre_addr; prepend_ins(ctx, L.exit_blk, sink); // Original store becomes Onop. *matched_store = make_nop_ins(); return true; } } return false; } } // namespace void scalar_repl(Ctx* ctx, Fn* fn) { int total = 0; for (int iter = 0; iter < 16; iter++) { std::vector loops; if (!find_loops(fn, loops)) break; bool changed = false; for (const LoopInfo& L : loops) { if (try_promote(ctx, fn, L)) { changed = true; total++; break; } } if (!changed) break; filluse(ctx, fn); filldom(fn); fillalias(fn); } if (debug['L']) { FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr; fprintf(out, "\n> scalar-repl promoted %d location(s) in %s\n", total, fn->name); } } } // namespace rcc