/* 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 lsr.cc /// @brief Loop Strength Reduction (LSR). /// /// See docs/lsr.md for architectural notes. #include "rcc/all.h" #include "rcc/loopinfo.h" #include #include #include #include #include #include namespace rcc { namespace { constexpr int kFixpointIters = 4; // IVs created by reduce_mul / insert_addr_iv. struct CreatedIV { Ref to; // header phi.to Ref init_ref; // phi preheader incoming edge Ref int64_t step; int cls; int src_biv; int64_t c; }; uint64_t ref_key(Ref r) { return (static_cast(static_cast(rtype(r))) << 32) | static_cast(r.val); } int find_biv(const std::vector& bivs, Ref r) { for (size_t k = 0; k < bivs.size(); k++) if (req(bivs[k].to, r)) return static_cast(k); return -1; } struct MulCand { Blk* b; uint idx; int biv; int64_t c; bool is_kl; }; // Returns number of hits. int reduce_mul(Ctx* ctx, Fn* fn, const Loop& L, std::vector& bivs, std::vector& created_ivs) { std::vector cands; for (Blk* b : L.body) { for (uint ix = 0; ix < b->nins; ix++) { Ins* m = &b->ins[ix]; if (m->op != Omul) continue; int64_t c = 0; Ref tref = Ref::none(); if (ref_is_const(fn, m->arg[1], c) && rtype(m->arg[0]) == RTmp) tref = m->arg[0]; else if (ref_is_const(fn, m->arg[0], c) && rtype(m->arg[1]) == RTmp) tref = m->arg[1]; else continue; if (c == 0 || c == 1) continue; int bi = -1; bool is_kl = false; int d = find_biv(bivs, tref); if (d >= 0 && static_cast(m->cls) == bivs[static_cast(d)].cls) { bi = d; is_kl = (m->cls == Kl); } if (bi < 0 && m->cls == Kl) { Ins* ext = def_ins(fn, tref); if (ext && ext->op == Oextsw) { int e = find_biv(bivs, ext->arg[0]); if (e >= 0 && bivs[static_cast(e)].cls == Kw) { bi = e; is_kl = true; } } } if (bi < 0) continue; cands.push_back({b, ix, bi, c, is_kl}); } } if (cands.empty()) return 0; struct Made { int biv; int64_t c; bool is_kl; Ref j; int64_t init; int64_t step; }; std::vector made; auto get_iv = [&](int bi, int64_t c, bool is_kl) -> Ref { for (auto& md : made) if (md.biv == bi && md.c == c && md.is_kl == is_kl) return md.j; const Biv& iv = bivs[static_cast(bi)]; int cls = is_kl ? Kl : Kw; int64_t j_init = iv.init * c; int64_t j_step = iv.step * c; Ref jt = newtmp(ctx, "lsr", cls, fn); Ref jn = newtmp(ctx, "lsr", cls, fn); Ins add{}; add.op = Oadd; add.cls = static_cast(cls) & 3u; add.to = jn; add.arg[0] = jt; add.arg[1] = getcon(ctx, j_step, fn); addins(ctx, &L.latch->ins, &L.latch->nins, &add); Phi* p = static_cast(alloc(ctx, sizeof(Phi))); std::memset(p, 0, sizeof(Phi)); p->to = jt; 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] = getcon(ctx, j_init, fn); p->blk[0] = L.preheader; p->arg[1] = jn; p->blk[1] = L.latch; p->link = L.header->phi; L.header->phi = p; made.push_back({bi, c, is_kl, jt, j_init, j_step}); // Fallback: add derived IV to bivs. bivs.push_back({jt, cls, j_init, j_step}); // Record for LFTR. Ref j_init_ref = getcon(ctx, j_init, fn); created_ivs.push_back({jt, j_init_ref, j_step, cls, bi, c}); return jt; }; for (auto& cd : cands) { Ref j = get_iv(cd.biv, cd.c, cd.is_kl); Ins* m = &cd.b->ins[cd.idx]; // get_iv might realloc latch->ins, re-fetch here m->op = Ocopy; m->arg[0] = j; m->arg[1] = Ref::none(); // m->cls unchanged (matches j) } return static_cast(cands.size()); } // Return phi input. Ref phi_arg_from(Phi* p, Blk* from) { for (uint k = 0; k < p->narg; k++) if (p->blk[k] == from) return p->arg[k]; return Ref::none(); } Phi* find_header_phi(Blk* hd, Ref to) { for (Phi* p = hd->phi; p; p = p->link) if (req(p->to, to)) return p; return nullptr; } bool is_pure_arith(int op) { return op == Oadd || op == Osub || op == Omul || op == Oextsw || op == Ocopy; } // Check load invariance. bool load_invariant_in_loop(Fn* fn, const Loop& L, Ins* load) { int lsz = loadsz(load); if (lsz <= 0) return false; for (Blk* bb : L.body) { for (uint i = 0; i < bb->nins; i++) { Ins* s = &bb->ins[i]; if (s->op == Ocall) return false; // call may clobber any global if (!isstore(s->op)) continue; int ssz = storesz(s); int delta_unused = 0; // storeX value, addr → arg[0]=value, arg[1]=addr int a = alias(load->arg[0], 0, lsz, s->arg[1], ssz, &delta_unused, fn); if (a != NoAlias) return false; } } return true; } // SCEV result. struct Scev { int64_t step; int src; }; int insert_addr_iv(Ctx* ctx, Fn* fn, const Loop& L, const std::vector& bivs, std::vector& created_ivs) { // Invariance check (memoized) std::unordered_map inv_memo; // -1 uncomputed, 0 false, 1 true std::function is_inv = [&](Ref r) -> bool { if (rtype(r) == RCon) return true; if (rtype(r) != RTmp) return false; uint64_t k = ref_key(r); auto it = inv_memo.find(k); if (it != inv_memo.end()) return it->second == 1; inv_memo[k] = -1; // Mark to prevent recursion cycles; non-self-loop phi returning here is false bool result = false; do { uint bid = fn->tmp[r.val].bid; if (L.body_bid.find(bid) == L.body_bid.end()) { // Def not in loop -> invariant result = true; break; } Ins* d = fn->tmp[r.val].def; if (!d) { // phi def check. if (bid != L.header->id) { result = false; break; } Phi* p = find_header_phi(L.header, r); if (!p) { result = false; break; } bool all_ok = true; for (uint i = 0; i < p->narg; i++) { if (req(p->arg[i], p->to)) continue; // self-loop if (!is_inv(p->arg[i])) { all_ok = false; break; } } result = all_ok; break; } if (isload(d->op)) { result = load_invariant_in_loop(fn, L, d); break; } if (!is_pure_arith(d->op)) { result = false; break; } bool a0_ok = req(d->arg[0], Ref::none()) || is_inv(d->arg[0]); bool a1_ok = req(d->arg[1], Ref::none()) || is_inv(d->arg[1]); result = a0_ok && a1_ok; } while (false); inv_memo[k] = result ? 1 : 0; return result; }; // SCEV returns {step, src_biv}. std::unordered_map> scev_memo; std::function(Ref)> scev = [&](Ref r) -> std::optional { if (rtype(r) == RCon) return Scev{0, -1}; if (rtype(r) != RTmp) return std::nullopt; uint64_t k = ref_key(r); auto it = scev_memo.find(k); if (it != scev_memo.end()) return it->second; scev_memo[k] = std::nullopt; // prevent recursion cycle std::optional ans = std::nullopt; auto combine = [](Scev a, Scev b, int sign) -> std::optional { int64_t step = a.step + sign * b.step; int src; if (a.step == 0 && b.step == 0) src = -1; else if (a.step == 0) src = b.src; else if (b.step == 0) src = a.src; else if (a.src == b.src) src = a.src; else return std::nullopt; if (step == 0) src = -1; return Scev{step, src}; }; do { int bi = find_biv(bivs, r); if (bi >= 0) { ans = Scev{bivs[static_cast(bi)].step, bi}; break; } if (is_inv(r)) { ans = Scev{0, -1}; break; } Ins* d = fn->tmp[r.val].def; if (!d) break; // non-BIV head phi / non-invariant phi -> fail int64_t cv = 0; switch (d->op) { case Ocopy: ans = scev(d->arg[0]); break; case Oextsw: { ans = scev(d->arg[0]); // sext passthrough, assumes no int32 wrap break; } case Oadd: { auto s0 = scev(d->arg[0]); auto s1 = scev(d->arg[1]); if (s0.has_value() && s1.has_value()) ans = combine(*s0, *s1, +1); break; } case Osub: { auto s0 = scev(d->arg[0]); auto s1 = scev(d->arg[1]); if (s0.has_value() && s1.has_value()) ans = combine(*s0, *s1, -1); break; } case Omul: { // One side linear, one side constant if (ref_is_const(fn, d->arg[1], cv)) { auto s = scev(d->arg[0]); if (s.has_value()) { int64_t step = s->step * cv; ans = Scev{step, step == 0 ? -1 : s->src}; } } else if (ref_is_const(fn, d->arg[0], cv)) { auto s = scev(d->arg[1]); if (s.has_value()) { int64_t step = s->step * cv; ans = Scev{step, step == 0 ? -1 : s->src}; } } break; } default: break; } } while (false); scev_memo[k] = ans; return ans; }; // Materialize init. std::unordered_map mat_memo; std::function materialize = [&](Ref r) -> Ref { if (rtype(r) == RCon) return r; uint64_t k = ref_key(r); auto it = mat_memo.find(k); if (it != mat_memo.end()) return it->second; // BIV -> init constant int bi = find_biv(bivs, r); if (bi >= 0) { Ref c = getcon(ctx, bivs[static_cast(bi)].init, fn); mat_memo[k] = c; return c; } // Decide based on def block. uint bid = fn->tmp[r.val].bid; if (L.body_bid.find(bid) == L.body_bid.end()) { mat_memo[k] = r; return r; } Ins* d = fn->tmp[r.val].def; if (!d) { // phi in body header. if (bid != L.header->id) { mat_memo[k] = r; // Fallback: shouldn't reach here (filtered by scev), but prevents crash return r; } Phi* p = find_header_phi(L.header, r); if (!p) { mat_memo[k] = r; return r; } Ref pe = phi_arg_from(p, L.preheader); Ref sub = materialize(pe); mat_memo[k] = sub; return sub; } // Ins in body: Ocopy/Oextsw/Oadd/Osub/Omul - copy one to preheader Ref a0 = req(d->arg[0], Ref::none()) ? Ref::none() : materialize(d->arg[0]); Ref a1 = req(d->arg[1], Ref::none()) ? Ref::none() : materialize(d->arg[1]); if (d->op == Ocopy) { mat_memo[k] = a0; return a0; } if (d->op == Oextsw && rtype(a0) == RCon) { mat_memo[k] = a0; // Kl constant itself is fine return a0; } int cls = static_cast(d->cls); Ref nt = newtmp(ctx, "lsr", cls, fn); Ins nins{}; nins.op = d->op; nins.cls = static_cast(cls) & 3u; nins.to = nt; nins.arg[0] = a0; nins.arg[1] = a1; addins(ctx, &L.preheader->ins, &L.preheader->nins, &nins); mat_memo[k] = nt; return nt; }; // Scan candidates. auto resolve_copy = [&](Ref r) -> Ref { // Trace Ocopy chain. for (int hop = 0; hop < 8; hop++) { Ins* d = def_ins(fn, r); if (!d || d->op != Ocopy) break; r = d->arg[0]; } return r; }; struct AddrCand { Blk* b; uint idx; int64_t step; int src_biv; uint64_t dedup_key; }; std::vector cands; for (Blk* b : L.body) { for (uint ix = 0; ix < b->nins; ix++) { Ins* m = &b->ins[ix]; if (m->op != Oadd || m->cls != Kl) continue; int64_t cb = 0; if (ref_is_const(fn, m->arg[0], cb) || ref_is_const(fn, m->arg[1], cb)) continue; // CBits step -> BIV self-increment, skip auto s = scev(m->to); if (!s.has_value() || s->step == 0) continue; Ref a0r = resolve_copy(m->arg[0]); Ref a1r = resolve_copy(m->arg[1]); uint64_t k0 = ref_key(a0r); uint64_t k1 = ref_key(a1r); if (k0 > k1) std::swap(k0, k1); uint64_t dk = (k0 * 1315423911ull) ^ (k1 + 0x9E3779B97F4A7C15ull); cands.push_back({b, ix, s->step, s->src, dk}); } } if (cands.empty()) return 0; // Deduplication + Rewrite struct IVRec { uint64_t key; Ref p; }; std::vector ivs; auto find_iv = [&](uint64_t key) -> Ref { for (auto& r : ivs) if (r.key == key) return r.p; return Ref::none(); }; int hits = 0; for (auto& cd : cands) { Ref p = find_iv(cd.dedup_key); if (req(p, Ref::none())) { // Materialize base (init at preheader) Ins* m0 = &cd.b->ins[cd.idx]; Ref orig_to = m0->to; Ref p_init = materialize(orig_to); int cls = Kl; Ref pt = newtmp(ctx, "lsr", cls, fn); Ref pn = newtmp(ctx, "lsr", cls, fn); // latch: pn = pt + step Ins add{}; add.op = Oadd; add.cls = static_cast(cls) & 3u; add.to = pn; add.arg[0] = pt; add.arg[1] = getcon(ctx, cd.step, fn); addins(ctx, &L.latch->ins, &L.latch->nins, &add); // header: pt = phi(p_init @ pre, pn @ latch) Phi* ph = static_cast(alloc(ctx, sizeof(Phi))); std::memset(ph, 0, sizeof(Phi)); ph->to = pt; ph->cls = static_cast(cls); ph->narg = 2; ph->arg = static_cast(vnew(ctx, 2, sizeof(Ref), PFn)); ph->blk = static_cast(vnew(ctx, 2, sizeof(Blk*), PFn)); ph->arg[0] = p_init; ph->blk[0] = L.preheader; ph->arg[1] = pn; ph->blk[1] = L.latch; ph->link = L.header->phi; L.header->phi = ph; ivs.push_back({cd.dedup_key, pt}); p = pt; // Record CreatedIV. int64_t c = 0; if (cd.src_biv >= 0) { int64_t bs = bivs[static_cast(cd.src_biv)].step; if (bs != 0 && cd.step % bs == 0) c = cd.step / bs; } created_ivs.push_back({pt, p_init, cd.step, Kl, cd.src_biv, c}); } // Re-fetch pointer. Ins* m = &cd.b->ins[cd.idx]; m->op = Ocopy; m->arg[0] = p; m->arg[1] = Ref::none(); // m->cls remains Kl hits++; } return hits; } // Exit cmp mapping. bool classify_cmp(int op, int& op_w, int& op_w_flip, int& op_l, int& op_l_flip) { auto setpair = [&](int w, int w_flip, int l, int l_flip) { op_w = w; op_w_flip = w_flip; op_l = l; op_l_flip = l_flip; }; switch (op) { case Oceqw: case Oceql: setpair(Oceqw, Oceqw, Oceql, Oceql); return true; case Ocnew: case Ocnel: setpair(Ocnew, Ocnew, Ocnel, Ocnel); return true; case Ocsltw: case Ocsltl: setpair(Ocsltw, Ocsgtw, Ocsltl, Ocsgtl); return true; case Ocslew: case Ocslel: setpair(Ocslew, Ocsgew, Ocslel, Ocsgel); return true; case Ocsgtw: case Ocsgtl: setpair(Ocsgtw, Ocsltw, Ocsgtl, Ocsltl); return true; case Ocsgew: case Ocsgel: setpair(Ocsgew, Ocslew, Ocsgel, Ocslel); return true; default: return false; } } int replace_exit_test(Ctx* ctx, Fn* fn, const Loop& L, const std::vector& bivs, const std::vector& created_ivs) { if (L.header->jmp.type != Jjnz) return 0; Ref cond = L.header->jmp.arg; if (rtype(cond) != RTmp) return 0; // cmp must be in header, and nuse==1 (otherwise modifying it affects other places) Ins* cmp = nullptr; uint cmp_idx = 0; for (uint i = 0; i < L.header->nins; i++) { if (req(L.header->ins[i].to, cond)) { cmp = &L.header->ins[i]; cmp_idx = i; break; } } if (!cmp) return 0; if (fn->tmp[cond.val].nuse != 1) return 0; int op_w, op_w_flip, op_l, op_l_flip; if (!classify_cmp(static_cast(cmp->op), op_w, op_w_flip, op_l, op_l_flip)) return 0; // Find BIV side + invariant side int bi = find_biv(bivs, cmp->arg[0]); Ref n_ref; bool biv_at_0; if (bi >= 0) { n_ref = cmp->arg[1]; biv_at_0 = true; } else { bi = find_biv(bivs, cmp->arg[1]); if (bi < 0) return 0; n_ref = cmp->arg[0]; biv_at_0 = false; } // If the other side is also a BIV, LFTR skips (comparison between two IVs) if (find_biv(bivs, n_ref) >= 0) return 0; // LFTR Guard. { Ref biv_to = bivs[static_cast(bi)].to; Phi* biv_phi = find_header_phi(L.header, biv_to); Ins* self_inc = nullptr; if (biv_phi) { for (uint k = 0; k < biv_phi->narg; k++) { if (biv_phi->blk[k] == L.latch) { Ref a = biv_phi->arg[k]; if (rtype(a) == RTmp && a.val < static_cast(fn->ntmp)) self_inc = fn->tmp[a.val].def; break; } } } if (biv_to.val >= static_cast(fn->ntmp)) return 0; Tmp* bt = &fn->tmp[biv_to.val]; int extra = 0; for (uint u = 0; u < bt->nuse; u++) { const Use& uu = bt->use[u]; if (uu.type == Use::UIns) { if (uu.u.ins == cmp) continue; if (uu.u.ins == self_inc) continue; // Orphans check. Ref user_to = uu.u.ins->to; if (rtype(user_to) == RTmp && user_to.val < static_cast(fn->ntmp) && fn->tmp[user_to.val].nuse == 0) continue; extra++; } else if (uu.type == Use::UPhi) { // Count phi use. extra++; } else if (uu.type == Use::UJmp) { extra++; } } if (extra > 0) return 0; } // Materialize to preheader. std::unordered_map inv_memo; std::function is_inv = [&](Ref r) -> bool { if (rtype(r) == RCon) return true; if (rtype(r) != RTmp) return false; uint64_t k = ref_key(r); auto it = inv_memo.find(k); if (it != inv_memo.end()) return it->second == 1; inv_memo[k] = -1; bool result = false; do { uint bid = fn->tmp[r.val].bid; if (L.body_bid.find(bid) == L.body_bid.end()) { result = true; break; } Ins* d = fn->tmp[r.val].def; if (!d) { if (bid != L.header->id) { result = false; break; } Phi* p = find_header_phi(L.header, r); if (!p) { result = false; break; } bool all_ok = true; for (uint i = 0; i < p->narg; i++) { if (req(p->arg[i], p->to)) continue; if (!is_inv(p->arg[i])) { all_ok = false; break; } } result = all_ok; break; } if (isload(d->op)) { result = load_invariant_in_loop(fn, L, d); break; } if (!is_pure_arith(d->op)) { result = false; break; } bool a0_ok = req(d->arg[0], Ref::none()) || is_inv(d->arg[0]); bool a1_ok = req(d->arg[1], Ref::none()) || is_inv(d->arg[1]); result = a0_ok && a1_ok; } while (false); inv_memo[k] = result ? 1 : 0; return result; }; if (!is_inv(n_ref)) return 0; // Materialize n_ref to preheader std::unordered_map mat_memo; std::function materialize = [&](Ref r) -> Ref { if (rtype(r) == RCon) return r; uint64_t k = ref_key(r); auto it = mat_memo.find(k); if (it != mat_memo.end()) return it->second; uint bid = fn->tmp[r.val].bid; if (L.body_bid.find(bid) == L.body_bid.end()) { mat_memo[k] = r; return r; } Ins* d = fn->tmp[r.val].def; if (!d) { // phi in body (invariant): take preheader edge if (bid != L.header->id) { mat_memo[k] = r; return r; } Phi* p = find_header_phi(L.header, r); if (!p) { mat_memo[k] = r; return r; } Ref pe = phi_arg_from(p, L.preheader); Ref sub = materialize(pe); mat_memo[k] = sub; return sub; } Ref a0 = req(d->arg[0], Ref::none()) ? Ref::none() : materialize(d->arg[0]); Ref a1 = req(d->arg[1], Ref::none()) ? Ref::none() : materialize(d->arg[1]); if (d->op == Ocopy) { mat_memo[k] = a0; return a0; } if (d->op == Oextsw && rtype(a0) == RCon) { mat_memo[k] = a0; return a0; } int cls = static_cast(d->cls); Ref nt = newtmp(ctx, "lsr.lftr", cls, fn); Ins ni{}; ni.op = d->op; ni.cls = static_cast(cls) & 3u; ni.to = nt; ni.arg[0] = a0; ni.arg[1] = a1; addins(ctx, &L.preheader->ins, &L.preheader->nins, &ni); mat_memo[k] = nt; return nt; }; Ref n_at_pre = materialize(n_ref); // Choose replacement IV: src_biv == bi and c != 0, pick smallest |c|. int best = -1; int64_t best_abs = 0; for (size_t i = 0; i < created_ivs.size(); i++) { const CreatedIV& iv = created_ivs[i]; if (iv.src_biv != bi) continue; if (iv.c == 0) continue; int64_t a = iv.c < 0 ? -iv.c : iv.c; if (best < 0 || a < best_abs) { best = static_cast(i); best_abs = a; } } if (best < 0) return 0; const CreatedIV& iv = created_ivs[static_cast(best)]; int64_t i0 = bivs[static_cast(bi)].init; int64_t cc = iv.c; int iv_cls = iv.cls; // Kw or Kl, determines cls of preheader arithmetic and new cmp // n_at_pre's cls must be compatible with IV: // - iv Kl: if n Kw -> extsw; if n Kl -> use directly // - iv Kw: if n Kw -> use directly; if n Kl -> unsafe (truncation), skip int n_cls = Kl; if (rtype(n_at_pre) == RTmp) n_cls = static_cast(fn->tmp[n_at_pre.val].cls); else n_cls = iv_cls; // RCon treated as target cls (CBits 64-bit constant, getcon reused directly) Ref n_use; if (iv_cls == Kl && n_cls == Kw) { n_use = newtmp(ctx, "lsr.lftr", Kl, fn); Ins ni{}; ni.op = Oextsw; ni.cls = static_cast(Kl) & 3u; ni.to = n_use; ni.arg[0] = n_at_pre; ni.arg[1] = Ref::none(); addins(ctx, &L.preheader->ins, &L.preheader->nins, &ni); } else if (iv_cls == n_cls || rtype(n_at_pre) == RCon) { n_use = n_at_pre; } else { return 0; // iv Kw, n Kl is unsafe } Ref i0_c = getcon(ctx, i0, fn); Ref c_c = getcon(ctx, cc, fn); auto emit_pre = [&](int op, int cls, Ref a0, Ref a1) -> Ref { Ref nt = newtmp(ctx, "lsr.lftr", cls, fn); Ins ni{}; ni.op = static_cast(op) & 0x3fffffffu; ni.cls = static_cast(cls) & 3u; ni.to = nt; ni.arg[0] = a0; ni.arg[1] = a1; addins(ctx, &L.preheader->ins, &L.preheader->nins, &ni); return nt; }; Ref diff = emit_pre(Osub, iv_cls, n_use, i0_c); Ref scaled = emit_pre(Omul, iv_cls, diff, c_c); Ref pend = emit_pre(Oadd, iv_cls, iv.init_ref, scaled); // Rewrite cmp (in-place; preheader's addins might realloc, so re-fetch is safe) cmp = &L.header->ins[cmp_idx]; int final_op = (iv_cls == Kl) ? (cc < 0 ? op_l_flip : op_l) : (cc < 0 ? op_w_flip : op_w); cmp->op = static_cast(final_op) & 0x3fffffffu; cmp->cls = static_cast(iv_cls) & 3u; if (biv_at_0) { cmp->arg[0] = iv.to; cmp->arg[1] = pend; } else { cmp->arg[0] = pend; cmp->arg[1] = iv.to; } return 1; } bool run_loop(Ctx* ctx, Fn* fn, const Loop& L, int& reduced_total, int& lifted_total, int& retested_total) { std::vector bivs; detect_bivs(fn, L, bivs); if (bivs.empty()) return false; std::vector created_ivs; int reduced = reduce_mul(ctx, fn, L, bivs, created_ivs); int lifted = insert_addr_iv(ctx, fn, L, bivs, created_ivs); int retested = 0; if (!created_ivs.empty()) { // tmp.nuse might be stale after insert_addr_iv rewrites mul->Ocopy. LFTR guard relies on // nuse, so update once. filluse(ctx, fn); retested = replace_exit_test(ctx, fn, L, bivs, created_ivs); } reduced_total += reduced; lifted_total += lifted; retested_total += retested; return (reduced + lifted + retested) > 0; } // LSR cleanup: resolve all RTmp references along Ocopy chains to their roots. // insert_addr_iv rewrites address additions as Ocopy %new_iv, leaving isolated Ocopys. // Downstream passes expect canonical IR. Walk uses and resolve RTmp defs if they are Ocopy. // Keep Ocopy itself; nuse becomes 0, and DCE cleans it up. void propagate_copies(Fn* fn) { auto resolve = [&](Ref r) -> Ref { for (int hop = 0; hop < 16; hop++) { if (rtype(r) != RTmp) break; uint v = r.val; if (v >= static_cast(fn->ntmp)) break; Ins* d = fn->tmp[v].def; if (!d || d->op != Ocopy) break; if (rtype(d->arg[0]) == -1) break; r = d->arg[0]; } return r; }; 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++) p->arg[k] = resolve(p->arg[k]); for (uint k = 0; k < b->nins; k++) { Ins* i = &b->ins[k]; if (i->op == Ocopy) continue; // Do not resolve Ocopy's own arg to preserve chain info for (int a = 0; a < 2; a++) i->arg[a] = resolve(i->arg[a]); } b->jmp.arg = resolve(b->jmp.arg); b->jmp.arg2 = resolve(b->jmp.arg2); } } } // namespace void lsr(Ctx* ctx, Fn* fn) { // alias info used by is_inv to check loop-invariant loads. // LSR only produces phi/add/mul/copy/sub, doesn't touch load/store, so no need to recalculate // inside fixpoint iter. fillalias(fn); int iter = 0; int reduced_total = 0; int lifted_total = 0; int retested_total = 0; for (; iter < kFixpointIters; iter++) { std::vector loops; find_loops(fn, loops); if (loops.empty()) break; bool changed = false; for (auto& L : loops) if (run_loop(ctx, fn, L, reduced_total, lifted_total, retested_total)) changed = true; if (!changed) break; // CFG unchanged; new Ins def requires filluse to write Tmp.def for next round's // scev/detect_bivs. filluse(ctx, fn); } // After fixpoint, resolve Ocopy chains left by insert_addr_iv globally. // Downstream passes won't see intermediate Ocopy references. Isolated Ocopy ins cleaned by DCE. propagate_copies(fn); if (debug['V']) { FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr; fprintf(out, "\n> LSR %s: reduced=%d mul, lifted=%d addr, retested=%d exit, in %d round%s\n", fn->name, reduced_total, lifted_total, retested_total, iter, iter == 1 ? "" : "s"); printfn(fn, out); } } } // namespace rcc