aboutsummaryrefslogtreecommitdiff
path: root/rcc-legacy
diff options
context:
space:
mode:
Diffstat (limited to 'rcc-legacy')
-rw-r--r--rcc-legacy/src/mid/loop_idiom.cc401
-rw-r--r--rcc-legacy/src/mid/reduction_unroll.cc528
-rw-r--r--rcc-legacy/src/mid/scalar_repl.cc528
3 files changed, 1457 insertions, 0 deletions
diff --git a/rcc-legacy/src/mid/loop_idiom.cc b/rcc-legacy/src/mid/loop_idiom.cc
new file mode 100644
index 0000000..8cbbf00
--- /dev/null
+++ b/rcc-legacy/src/mid/loop_idiom.cc
@@ -0,0 +1,401 @@
+/* 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
+<http://www.gnu.org/licenses/>. */
+
+/// @file loop_idiom.cc
+/// @brief Recognize zero-fill / array-copy inner loops -> rewrite to runtime Ocall.
+///
+/// See docs/loop_idiom.md for architectural notes.
+
+#include "rcc/all.h"
+#include "rcc/loopinfo.h"
+
+#include <algorithm>
+#include <cstring>
+#include <unordered_set>
+#include <vector>
+
+namespace rcc {
+
+namespace {
+
+// store op -> byte width; non-store returns 0
+int store_bytes(int op) {
+ switch (op) {
+ case Ostoreb:
+ return 1;
+ case Ostoreh:
+ return 2;
+ case Ostorew:
+ case Ostores:
+ return 4;
+ case Ostorel:
+ case Ostored:
+ return 8;
+ default:
+ return 0;
+ }
+}
+
+int load_bytes(int op, int cls) {
+ switch (op) {
+ case Oloadsb:
+ case Oloadub:
+ return 1;
+ case Oloadsh:
+ case Oloaduh:
+ return 2;
+ case Oloadsw:
+ case Oloaduw:
+ return 4;
+ case Oload:
+ return (cls == Kl || cls == Kd) ? 8 : 4;
+ default:
+ return 0;
+ }
+}
+
+// Check byte splat.
+bool is_byte_splat(Fn* fn, Ref v, uint8_t* byte_out) {
+ if (rtype(v) != RCon)
+ return false;
+ Con* c = &fn->con[v.val];
+ if (c->type != Con::CBits || c->flt)
+ return false;
+ uint64_t bits = static_cast<uint64_t>(c->bits.i);
+ uint8_t b = bits & 0xff;
+ uint64_t splat = static_cast<uint64_t>(b);
+ splat |= splat << 8;
+ splat |= splat << 16;
+ splat |= splat << 32;
+ if (bits != splat)
+ return false;
+ *byte_out = b;
+ return true;
+}
+
+// Trace Ocopy chain back to a non-Ocopy root Ref.
+Ref base_root(Fn* fn, Ref r) {
+ for (int hop = 0; hop < 16; hop++) {
+ Ins* d = def_ins(fn, r);
+ if (!d || d->op != Ocopy)
+ break;
+ r = d->arg[0];
+ }
+ return r;
+}
+
+// Find IV increment index.
+int find_iv_inc_idx(Blk* latch, Ref next) {
+ for (uint k = 0; k < latch->nins; k++)
+ if (req(latch->ins[k].to, next))
+ return static_cast<int>(k);
+ return -1;
+}
+
+enum class Idiom { None, Memset, Memcpy, Memmove };
+
+struct Match {
+ Idiom kind;
+ Ref base_dst;
+ Ref base_src; // memcpy/memmove only
+ uint8_t value; // memset only
+ Ref end_dst; // for size = end - base
+ Blk* after;
+};
+
+bool try_match_loop(Fn* fn, const Loop& L, Match* out) {
+ // Validate loop structure.
+ if (L.body.size() > 2)
+ return false;
+ // Disallow nesting.
+
+ // Primary pointer IV (dst pointer for memset/memcpy)
+ PointerIV iv_dst{};
+ Phi* phi_dst = nullptr;
+ int found = 0;
+ for (Phi* ph = L.header->phi; ph; ph = ph->link) {
+ PointerIV cand{};
+ if (pick_pointer_iv(fn, L, ph, &cand)) {
+ iv_dst = cand;
+ phi_dst = ph;
+ found++;
+ }
+ }
+ if (found == 0)
+ return false;
+ // 1 IV -> memset candidate; 2 -> memcpy candidate; >2 -> reject
+ PointerIV iv_src{};
+ Phi* phi_src = nullptr;
+ if (found >= 2) {
+ if (found != 2)
+ return false;
+ // Rescan to get the two
+ std::vector<std::pair<PointerIV, Phi*>> ivs;
+ for (Phi* ph = L.header->phi; ph; ph = ph->link) {
+ PointerIV cand{};
+ if (pick_pointer_iv(fn, L, ph, &cand))
+ ivs.push_back({cand, ph});
+ }
+ if (ivs.size() != 2)
+ return false;
+ // Will decide which is dst when scanning store in body
+ iv_dst = ivs[0].first;
+ phi_dst = ivs[0].second;
+ iv_src = ivs[1].first;
+ phi_src = ivs[1].second;
+ }
+
+ // exit cmp must be based on iv_dst (memset) or either one (memcpy)
+ Ref end_dst;
+ Blk* after = nullptr;
+ if (!check_exit_cmp(fn, L, iv_dst.phi_to, &end_dst, &after)) {
+ if (phi_src && check_exit_cmp(fn, L, iv_src.phi_to, &end_dst, &after)) {
+ // Reversed: cmp based on src, swap dst/src naming
+ std::swap(iv_dst, iv_src);
+ std::swap(phi_dst, phi_src);
+ } else {
+ return false;
+ }
+ }
+ if (iv_dst.step != iv_src.step && phi_src)
+ return false;
+
+ // Find IV self-update ins (add in latch)
+ int iv_dst_inc = find_iv_inc_idx(L.latch, iv_dst.next);
+ int iv_src_inc = phi_src ? find_iv_inc_idx(L.latch, iv_src.next) : -1;
+ if (iv_dst_inc < 0)
+ return false;
+ if (phi_src && iv_src_inc < 0)
+ return false;
+
+ // Body scan.
+ Ins* load_ins = nullptr;
+ Ins* store_ins = nullptr;
+ for (Blk* bb : L.body) {
+ for (uint k = 0; k < bb->nins; k++) {
+ Ins* i = &bb->ins[k];
+ int op = i->op;
+ // Allowed: exit cmp (in header), IV self-update (in latch)
+ if (bb == L.header && req(i->to, L.header->jmp.arg))
+ continue; // exit cmp
+ if (bb == L.latch &&
+ (static_cast<int>(k) == iv_dst_inc || static_cast<int>(k) == iv_src_inc))
+ continue;
+ // Disallowed: call / alloc / FP / div / vastart / vaarg / dbgloc / sel / blit
+ if (op == Ocall || op == Oalloc4 || op == Oalloc8 || op == Oalloc16 || op == Ovaarg ||
+ op == Ovastart || op == Odbgloc || op == Osel0 || op == Osel1 || op == Oblit0 ||
+ op == Oblit1 || op == Osalloc)
+ return false;
+ if (op == Oudiv || op == Ourem || op == Odiv || op == Orem)
+ return false;
+ if (op == Ocopy && (i->cls == Ks || i->cls == Kd))
+ return false;
+ // load / store
+ int lb = load_bytes(op, static_cast<int>(i->cls));
+ int sb = store_bytes(op);
+ if (lb > 0) {
+ if (load_ins)
+ return false; // Reject multiple loads
+ load_ins = i;
+ continue;
+ }
+ if (sb > 0) {
+ if (store_ins)
+ return false;
+ store_ins = i;
+ continue;
+ }
+ // Other allowed.
+ if (op == Oadd || op == Osub || op == Omul || op == Oand || op == Oor || op == Oxor ||
+ op == Oshl || op == Oshr || op == Osar || op == Oextsw || op == Oextuw ||
+ op == Oextsh || op == Oextuh || op == Oextsb || op == Oextub || op == Ocopy ||
+ op == Onop)
+ continue;
+ // Other cmp (besides LFTR cmp): conservatively reject
+ return false;
+ }
+ }
+
+ if (!store_ins)
+ return false;
+ int sw = store_bytes(store_ins->op);
+ if (sw != iv_dst.step)
+ return false;
+ if (!req(store_ins->arg[1], iv_dst.phi_to))
+ return false;
+
+ if (has_outside_use(fn, L))
+ return false;
+
+ if (load_ins) {
+ // memcpy path
+ if (!phi_src)
+ return false;
+ int lw = load_bytes(load_ins->op, static_cast<int>(load_ins->cls));
+ if (lw != sw)
+ return false;
+ if (!req(load_ins->arg[0], iv_src.phi_to))
+ return false;
+ if (!req(store_ins->arg[0], load_ins->to))
+ return false;
+ // Noalias check.
+ Ref root_dst = base_root(fn, iv_dst.base);
+ Ref root_src = base_root(fn, iv_src.base);
+ out->kind = req(root_dst, root_src) ? Idiom::Memmove : Idiom::Memcpy;
+ out->base_dst = iv_dst.base;
+ out->base_src = iv_src.base;
+ out->value = 0;
+ out->end_dst = end_dst;
+ out->after = after;
+ return true;
+ }
+
+ // memset path
+ if (phi_src)
+ return false; // Second IV present but no load, abnormal shape
+ uint8_t bv = 0;
+ if (!is_byte_splat(fn, store_ins->arg[0], &bv))
+ return false;
+ out->kind = Idiom::Memset;
+ out->base_dst = iv_dst.base;
+ out->base_src = Ref::none();
+ out->value = bv;
+ out->end_dst = end_dst;
+ out->after = after;
+ return true;
+}
+
+const char* runtime_name(Idiom k) {
+ switch (k) {
+ case Idiom::Memset:
+ return "__rcc_memset";
+ case Idiom::Memcpy:
+ return "__rcc_memcpy";
+ case Idiom::Memmove:
+ return "__rcc_memmove";
+ default:
+ return "";
+ }
+}
+
+Ref make_runtime_callee(Ctx* ctx, Fn* fn, const char* name) {
+ Con c;
+ std::memset(&c, 0, sizeof(c));
+ c.type = Con::CAddr;
+ c.sym.type = Sym::SGlo;
+ c.sym.id = intern(const_cast<char*>(name));
+ return newcon(ctx, &c, fn);
+}
+
+void execute_rewrite(Ctx* ctx, Fn* fn, const Loop& L, const Match& m) {
+ Blk* pre = L.preheader;
+
+ // size = end - base (Osub Kl)
+ Ref size_ref = newtmp(ctx, "midiom", Kl, fn);
+ Ins sub_ins{};
+ sub_ins.op = Osub;
+ sub_ins.cls = Kl;
+ sub_ins.to = size_ref;
+ sub_ins.arg[0] = m.end_dst;
+ sub_ins.arg[1] = m.base_dst;
+ addins(ctx, &pre->ins, &pre->nins, &sub_ins);
+
+ // Oarg dst (Kl)
+ Ins arg0{};
+ arg0.op = Oarg;
+ arg0.cls = Kl;
+ arg0.to = Ref::none();
+ arg0.arg[0] = m.base_dst;
+ arg0.arg[1] = Ref::none();
+ addins(ctx, &pre->ins, &pre->nins, &arg0);
+
+ // Oarg val/src
+ Ins arg1{};
+ arg1.op = Oarg;
+ arg1.to = Ref::none();
+ arg1.arg[1] = Ref::none();
+ if (m.kind == Idiom::Memset) {
+ arg1.cls = Kw;
+ arg1.arg[0] = getcon(ctx, static_cast<int64_t>(m.value), fn);
+ } else {
+ arg1.cls = Kl;
+ arg1.arg[0] = m.base_src;
+ }
+ addins(ctx, &pre->ins, &pre->nins, &arg1);
+
+ // Oarg size (Kl)
+ Ins arg2{};
+ arg2.op = Oarg;
+ arg2.cls = Kl;
+ arg2.to = Ref::none();
+ arg2.arg[0] = size_ref;
+ arg2.arg[1] = Ref::none();
+ addins(ctx, &pre->ins, &pre->nins, &arg2);
+
+ // Ocall (returns void -> cls=Kw, to=none)
+ Ins call_ins{};
+ call_ins.op = Ocall;
+ call_ins.cls = Kw;
+ call_ins.to = Ref::none();
+ call_ins.arg[0] = make_runtime_callee(ctx, fn, runtime_name(m.kind));
+ call_ins.arg[1] = Ref::none();
+ addins(ctx, &pre->ins, &pre->nins, &call_ins);
+
+ fn->leaf = 0;
+
+ // change preheader to jump directly to after
+ pre->jmp.type = Jjmp;
+ pre->jmp.arg = Ref::none();
+ pre->jmp.arg2 = Ref::none();
+ pre->s1 = m.after;
+ pre->s2 = nullptr;
+}
+
+} // namespace
+
+void loop_idiom(Ctx* ctx, Fn* fn) {
+ std::vector<Loop> loops;
+ find_loops(fn, loops);
+ if (loops.empty())
+ return;
+
+ // Sort innermost first.
+ std::sort(loops.begin(), loops.end(),
+ [](const Loop& a, const Loop& b) { return a.body.size() < b.body.size(); });
+
+ std::unordered_set<uint> consumed_blk;
+ for (const Loop& L : loops) {
+ bool clash = false;
+ for (Blk* bb : L.body)
+ if (consumed_blk.count(bb->id)) {
+ clash = true;
+ break;
+ }
+ if (clash)
+ continue;
+ Match m{};
+ if (!try_match_loop(fn, L, &m))
+ continue;
+ execute_rewrite(ctx, fn, L, m);
+ for (Blk* bb : L.body)
+ consumed_blk.insert(bb->id);
+ }
+}
+
+} // namespace rcc
diff --git a/rcc-legacy/src/mid/reduction_unroll.cc b/rcc-legacy/src/mid/reduction_unroll.cc
new file mode 100644
index 0000000..08edb3d
--- /dev/null
+++ b/rcc-legacy/src/mid/reduction_unroll.cc
@@ -0,0 +1,528 @@
+/* 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
+<http://www.gnu.org/licenses/>. */
+
+/// @file reduction_unroll.cc
+/// @brief Identifies inner loop integer reductions and unrolls them into K-way parallel
+/// accumulators.
+///
+/// See docs/reduction_unroll.md for architectural notes.
+
+#include "rcc/all.h"
+#include "rcc/loopinfo.h"
+
+#include <algorithm>
+#include <cstring>
+#include <unordered_set>
+#include <vector>
+
+namespace rcc {
+
+namespace {
+
+constexpr int kUnrollK = 4;
+
+int load_bytes(int op, int cls) {
+ switch (op) {
+ case Oloadsb:
+ case Oloadub:
+ return 1;
+ case Oloadsh:
+ case Oloaduh:
+ return 2;
+ case Oloadsw:
+ case Oloaduw:
+ return 4;
+ case Oload:
+ return (cls == Kl || cls == Kd) ? 8 : 4;
+ default:
+ return 0;
+ }
+}
+
+bool is_int_reduce_op(int op) {
+ return op == Oadd || op == Omul || op == Oand || op == Oor || op == Oxor;
+}
+
+int64_t identity_for(int op) {
+ switch (op) {
+ case Oadd:
+ case Oor:
+ case Oxor:
+ return 0;
+ case Omul:
+ return 1;
+ case Oand:
+ return -1;
+ default:
+ return 0;
+ }
+}
+
+bool is_int_cls(int cls) {
+ return cls == Kw || cls == Kl;
+}
+
+struct Match {
+ PointerIV iv_p; // pointer IV
+ Phi* phi_p; // pointer phi
+ Phi* phi_s; // accumulator phi
+ Ref s_init; // accumulator init (preheader side)
+ Ref s_next; // accumulator next (latch side, = s_op.to)
+ Ins* load_ins; // load inside body
+ Ins* op_ins; // reduce op inside body (s.next = OP s, v)
+ Ins* p_inc_ins; // pointer increment inside body
+ Ref end; // end ref for exit cmp
+ Blk* after; // exit block
+ int op; // op_ins->op
+ int s_cls; // accumulator and op cls
+ int load_op; // load_ins->op
+ int load_cls; // load_ins->cls
+};
+
+// Check latch body.
+bool scan_body(Fn* /*fn*/, const Loop& L, const PointerIV& iv_p, Phi* phi_s, Match* m) {
+ m->load_ins = nullptr;
+ m->op_ins = nullptr;
+ m->p_inc_ins = nullptr;
+
+ Ref p = iv_p.phi_to;
+ Ref s = phi_s->to;
+ Ref s_next = Ref::none(), p_next = iv_p.next;
+ for (uint k = 0; k < phi_s->narg; k++)
+ if (phi_s->blk[k] == L.latch)
+ s_next = phi_s->arg[k];
+ if (rtype(s_next) != RTmp)
+ return false;
+
+ for (Blk* bb : L.body) {
+ for (uint k = 0; k < bb->nins; k++) {
+ Ins* i = &bb->ins[k];
+ int op = i->op;
+ // header allows exit cmp (guaranteed unique by check_exit_cmp)
+ if (bb == L.header && req(i->to, L.header->jmp.arg))
+ continue;
+ if (op == Onop)
+ continue;
+ if (op == Ocall || op == Oalloc4 || op == Oalloc8 || op == Oalloc16 || op == Ovaarg ||
+ op == Ovastart || op == Odbgloc || op == Osel0 || op == Osel1 || op == Oblit0 ||
+ op == Oblit1 || op == Osalloc || op == Oudiv || op == Ourem || op == Odiv ||
+ op == Orem)
+ return false;
+ // pointer increment?
+ if (req(i->to, p_next)) {
+ if (m->p_inc_ins)
+ return false;
+ m->p_inc_ins = i;
+ continue;
+ }
+ // accumulator increment?
+ if (req(i->to, s_next)) {
+ if (m->op_ins)
+ return false;
+ if (!is_int_reduce_op(op))
+ return false;
+ if (!is_int_cls(static_cast<int>(i->cls)))
+ return false;
+ // Must be OP(s, v) or OP(v, s)
+ if (!req(i->arg[0], s) && !req(i->arg[1], s))
+ return false;
+ m->op_ins = i;
+ continue;
+ }
+ // Is it a load?
+ int lb = load_bytes(op, static_cast<int>(i->cls));
+ if (lb > 0) {
+ if (m->load_ins)
+ return false;
+ if (!req(i->arg[0], p))
+ return false;
+ m->load_ins = i;
+ continue;
+ }
+ // Reject any other ins (reduction form requires a clean body)
+ return false;
+ }
+ }
+ if (!m->load_ins || !m->op_ins || !m->p_inc_ins)
+ return false;
+ // Loaded value must equal the other operand of the OP
+ Ref v_ref = m->load_ins->to;
+ if (!(req(m->op_ins->arg[0], v_ref) || req(m->op_ins->arg[1], v_ref)))
+ return false;
+ // class & width
+ int lb = load_bytes(m->load_ins->op, static_cast<int>(m->load_ins->cls));
+ if (lb != iv_p.step)
+ return false;
+ if (m->op_ins->cls != m->load_ins->cls)
+ return false;
+ m->s_next = s_next;
+ return true;
+}
+
+bool try_match_loop(Fn* fn, const Loop& L, Match* m) {
+ if (L.body.size() > 2)
+ return false;
+ if (!L.preheader)
+ return false;
+ if (L.preheader->jmp.type != Jjmp)
+ return false;
+ if (L.preheader->s1 != L.header || L.preheader->s2 != nullptr)
+ return false;
+
+ // Collect header phis.
+ PointerIV iv_p{};
+ Phi* phi_p = nullptr;
+ Phi* phi_s = nullptr;
+ int n_ptr = 0, n_acc = 0;
+ for (Phi* ph = L.header->phi; ph; ph = ph->link) {
+ PointerIV cand{};
+ if (pick_pointer_iv(fn, L, ph, &cand)) {
+ iv_p = cand;
+ phi_p = ph;
+ n_ptr++;
+ continue;
+ }
+ if (ph->narg == 2 && is_int_cls(static_cast<int>(ph->cls))) {
+ phi_s = ph;
+ n_acc++;
+ continue;
+ }
+ return false; // Reject any third type of phi
+ }
+ if (n_ptr != 1 || n_acc != 1)
+ return false;
+
+ // accumulator phi incoming edges
+ Ref s_init = Ref::none();
+ for (uint k = 0; k < phi_s->narg; k++) {
+ if (phi_s->blk[k] == L.preheader)
+ s_init = phi_s->arg[k];
+ else if (phi_s->blk[k] != L.latch)
+ return false;
+ }
+ if (rtype(s_init) == -1)
+ return false;
+
+ Ref end_ref;
+ Blk* after = nullptr;
+ if (!check_exit_cmp(fn, L, iv_p.phi_to, &end_ref, &after))
+ return false;
+
+ if (!scan_body(fn, L, iv_p, phi_s, m))
+ return false;
+
+ // accumulator cls == op cls, must match load cls (same bitwidth)
+ if (phi_s->cls != m->op_ins->cls)
+ return false;
+
+ if (has_outside_use(fn, L)) {
+ // Check outside loop uses strictly.
+ std::unordered_set<uint> loop_def;
+ for (Blk* bb : L.body) {
+ for (Phi* ph = bb->phi; ph; ph = ph->link)
+ if (rtype(ph->to) == RTmp)
+ loop_def.insert(ph->to.val);
+ for (uint k = 0; k < bb->nins; k++)
+ if (rtype(bb->ins[k].to) == RTmp)
+ loop_def.insert(bb->ins[k].to.val);
+ }
+ loop_def.erase(phi_p->to.val);
+ loop_def.erase(phi_s->to.val);
+ for (Blk* b = fn->start; b; b = b->link) {
+ if (L.body_bid.count(b->id))
+ continue;
+ for (Phi* ph = b->phi; ph; ph = ph->link)
+ for (uint k = 0; k < ph->narg; k++)
+ if (rtype(ph->arg[k]) == RTmp && loop_def.count(ph->arg[k].val))
+ return false;
+ for (uint k = 0; k < b->nins; k++) {
+ Ins* i = &b->ins[k];
+ for (int a = 0; a < 2; a++)
+ if (rtype(i->arg[a]) == RTmp && loop_def.count(i->arg[a].val))
+ return false;
+ }
+ if (rtype(b->jmp.arg) == RTmp && loop_def.count(b->jmp.arg.val))
+ return false;
+ if (rtype(b->jmp.arg2) == RTmp && loop_def.count(b->jmp.arg2.val))
+ return false;
+ }
+ }
+
+ m->iv_p = iv_p;
+ m->phi_p = phi_p;
+ m->phi_s = phi_s;
+ m->s_init = s_init;
+ m->end = end_ref;
+ m->after = after;
+ m->op = m->op_ins->op;
+ m->s_cls = m->op_ins->cls;
+ m->load_op = m->load_ins->op;
+ m->load_cls = static_cast<int>(m->load_ins->cls);
+ return true;
+}
+
+Phi* make_phi(Ctx* ctx, int cls, Ref to, Ref arg_pre, Blk* pre, Ref arg_back, Blk* back) {
+ Phi* p = static_cast<Phi*>(alloc(ctx, sizeof(Phi)));
+ p->cls = static_cast<short>(cls);
+ p->visit = 0;
+ p->to = to;
+ p->narg = 2;
+ p->arg = static_cast<Ref*>(vnew(ctx, 2, sizeof(Ref), PFn));
+ p->blk = static_cast<Blk**>(vnew(ctx, 2, sizeof(Blk*), PFn));
+ p->arg[0] = arg_pre;
+ p->blk[0] = pre;
+ p->arg[1] = arg_back;
+ p->blk[1] = back;
+ p->link = nullptr;
+ return p;
+}
+
+static int rd_seq = 0;
+
+void execute(Ctx* ctx, Fn* fn, const Loop& L, const Match& m) {
+ int K = kUnrollK;
+ int S = static_cast<int>(m.iv_p.step);
+ int KS = K * S;
+ int my_seq = rd_seq++;
+
+ Blk* pre = L.preheader;
+ Blk* tail_hdr = L.header;
+ int s_cls = m.s_cls;
+ int load_op = m.load_op;
+ int load_cls = m.load_cls;
+ int op = m.op;
+
+ // Append size / size_main / end_main to the end of preheader
+ Ref size_ref = newtmp(ctx, "rd.size", Kl, fn);
+ Ref size_main_ref = newtmp(ctx, "rd.szm", Kl, fn);
+ Ref end_main_ref = newtmp(ctx, "rd.endm", Kl, fn);
+
+ Ins sub_i{};
+ sub_i.op = Osub;
+ sub_i.cls = Kl;
+ sub_i.to = size_ref;
+ sub_i.arg[0] = m.end;
+ sub_i.arg[1] = m.iv_p.base;
+ addins(ctx, &pre->ins, &pre->nins, &sub_i);
+
+ Ins and_i{};
+ and_i.op = Oand;
+ and_i.cls = Kl;
+ and_i.to = size_main_ref;
+ and_i.arg[0] = size_ref;
+ and_i.arg[1] = getcon(ctx, -static_cast<int64_t>(KS), fn);
+ addins(ctx, &pre->ins, &pre->nins, &and_i);
+
+ Ins add_i{};
+ add_i.op = Oadd;
+ add_i.cls = Kl;
+ add_i.to = end_main_ref;
+ add_i.arg[0] = m.iv_p.base;
+ add_i.arg[1] = size_main_ref;
+ addins(ctx, &pre->ins, &pre->nins, &add_i);
+
+ // Create main_header / main_body / main_join
+ Blk* mh = newblk(ctx);
+ mh->name = strf(ctx, PFn, "rd%d.mh", my_seq);
+ mh->id = fn->nblk++;
+
+ Blk* mb = newblk(ctx);
+ mb->name = strf(ctx, PFn, "rd%d.mb", my_seq);
+ mb->id = fn->nblk++;
+
+ Blk* mj = newblk(ctx);
+ mj->name = strf(ctx, PFn, "rd%d.mj", my_seq);
+ mj->id = fn->nblk++;
+
+ // Pre-generate names (main_header phi.to, etc.)
+ Ref pm_ref = newtmp(ctx, "rd.pm", Kl, fn);
+ Ref pm_next_ref = newtmp(ctx, "rd.pm.n", Kl, fn);
+ std::vector<Ref> s_refs(static_cast<size_t>(K)), s_next_refs(static_cast<size_t>(K));
+ for (size_t i = 0; i < static_cast<size_t>(K); i++) {
+ s_refs[i] = newtmp(ctx, "rd.s", s_cls, fn);
+ s_next_refs[i] = newtmp(ctx, "rd.s.n", s_cls, fn);
+ }
+
+ // Identity ref
+ Ref ident_ref = getcon(ctx, identity_for(op), fn);
+
+ // main_header phi chain: pm + s0..K-1
+ Phi* phi_pm = make_phi(ctx, Kl, pm_ref, m.iv_p.base, pre, pm_next_ref, mb);
+ mh->phi = phi_pm;
+ Phi* tail_phi = phi_pm;
+ for (size_t i = 0; i < static_cast<size_t>(K); i++) {
+ Ref init = (i == 0) ? m.s_init : ident_ref;
+ Phi* ph = make_phi(ctx, s_cls, s_refs[i], init, pre, s_next_refs[i], mb);
+ tail_phi->link = ph;
+ tail_phi = ph;
+ }
+
+ // main_header cmp + jnz
+ Ref cmpm_ref = newtmp(ctx, "rd.cmpm", Kw, fn);
+ Ins cmpm_i{};
+ cmpm_i.op = Ocultl;
+ cmpm_i.cls = Kw;
+ cmpm_i.to = cmpm_ref;
+ cmpm_i.arg[0] = pm_ref;
+ cmpm_i.arg[1] = end_main_ref;
+ addins(ctx, &mh->ins, &mh->nins, &cmpm_i);
+ mh->jmp.type = Jjnz;
+ mh->jmp.arg = cmpm_ref;
+ mh->jmp.arg2 = Ref::none();
+ mh->s1 = mb;
+ mh->s2 = mj;
+
+ // main_body: K loads + K ops + 1 pointer step
+ std::vector<Ref> v_refs(static_cast<size_t>(K));
+ for (size_t i = 0; i < static_cast<size_t>(K); i++) {
+ Ref addr;
+ if (i == 0) {
+ addr = pm_ref;
+ } else {
+ addr = newtmp(ctx, "rd.adr", Kl, fn);
+ Ins ai{};
+ ai.op = Oadd;
+ ai.cls = Kl;
+ ai.to = addr;
+ ai.arg[0] = pm_ref;
+ ai.arg[1] = getcon(ctx, static_cast<int64_t>(i) * S, fn);
+ addins(ctx, &mb->ins, &mb->nins, &ai);
+ }
+ v_refs[i] = newtmp(ctx, "rd.v", load_cls, fn);
+ Ins li{};
+ li.op = static_cast<uint>(load_op);
+ li.cls = static_cast<uint>(load_cls) & 3u;
+ li.to = v_refs[i];
+ li.arg[0] = addr;
+ li.arg[1] = Ref::none();
+ addins(ctx, &mb->ins, &mb->nins, &li);
+ }
+ for (size_t i = 0; i < static_cast<size_t>(K); i++) {
+ Ins oi{};
+ oi.op = static_cast<uint>(op);
+ oi.cls = static_cast<uint>(s_cls) & 3u;
+ oi.to = s_next_refs[i];
+ oi.arg[0] = s_refs[i];
+ oi.arg[1] = v_refs[i];
+ addins(ctx, &mb->ins, &mb->nins, &oi);
+ }
+ Ins pn_i{};
+ pn_i.op = Oadd;
+ pn_i.cls = Kl;
+ pn_i.to = pm_next_ref;
+ pn_i.arg[0] = pm_ref;
+ pn_i.arg[1] = getcon(ctx, static_cast<int64_t>(KS), fn);
+ addins(ctx, &mb->ins, &mb->nins, &pn_i);
+ mb->jmp.type = Jjmp;
+ mb->jmp.arg = Ref::none();
+ mb->jmp.arg2 = Ref::none();
+ mb->s1 = mh;
+ mb->s2 = nullptr;
+
+ // main_join: log2(K) tree reduction
+ Ref s01_ref = newtmp(ctx, "rd.s01", s_cls, fn);
+ Ref s23_ref = newtmp(ctx, "rd.s23", s_cls, fn);
+ Ref s_main_ref = newtmp(ctx, "rd.sm", s_cls, fn);
+
+ Ins j1{};
+ j1.op = static_cast<uint>(op);
+ j1.cls = static_cast<uint>(s_cls) & 3u;
+ j1.to = s01_ref;
+ j1.arg[0] = s_refs[0];
+ j1.arg[1] = s_refs[1];
+ addins(ctx, &mj->ins, &mj->nins, &j1);
+
+ Ins j2{};
+ j2.op = static_cast<uint>(op);
+ j2.cls = static_cast<uint>(s_cls) & 3u;
+ j2.to = s23_ref;
+ j2.arg[0] = s_refs[2];
+ j2.arg[1] = s_refs[3];
+ addins(ctx, &mj->ins, &mj->nins, &j2);
+
+ Ins j3{};
+ j3.op = static_cast<uint>(op);
+ j3.cls = static_cast<uint>(s_cls) & 3u;
+ j3.to = s_main_ref;
+ j3.arg[0] = s01_ref;
+ j3.arg[1] = s23_ref;
+ addins(ctx, &mj->ins, &mj->nins, &j3);
+
+ mj->jmp.type = Jjmp;
+ mj->jmp.arg = Ref::none();
+ mj->jmp.arg2 = Ref::none();
+ mj->s1 = tail_hdr;
+ mj->s2 = nullptr;
+
+ // Change preheader.s1 -> main_header
+ pre->s1 = mh;
+
+ // Link new blocks.
+ mj->link = pre->link;
+ mb->link = mj;
+ mh->link = mb;
+ pre->link = mh;
+
+ // Update tail_header phis.
+ for (Phi* ph = tail_hdr->phi; ph; ph = ph->link) {
+ for (uint k = 0; k < ph->narg; k++) {
+ if (ph->blk[k] != pre)
+ continue;
+ ph->blk[k] = mj;
+ if (req(ph->to, m.phi_p->to))
+ ph->arg[k] = pm_ref;
+ else if (req(ph->to, m.phi_s->to))
+ ph->arg[k] = s_main_ref;
+ break;
+ }
+ }
+}
+
+} // namespace
+
+void reduction_unroll(Ctx* ctx, Fn* fn) {
+ std::vector<Loop> loops;
+ find_loops(fn, loops);
+ if (loops.empty())
+ return;
+
+ // Inner loops first (smaller body first)
+ std::sort(loops.begin(), loops.end(),
+ [](const Loop& a, const Loop& b) { return a.body.size() < b.body.size(); });
+
+ std::unordered_set<uint> consumed;
+ for (const Loop& L : loops) {
+ bool clash = false;
+ for (Blk* bb : L.body)
+ if (consumed.count(bb->id)) {
+ clash = true;
+ break;
+ }
+ if (clash)
+ continue;
+ Match m{};
+ if (!try_match_loop(fn, L, &m))
+ continue;
+ execute(ctx, fn, L, m);
+ for (Blk* bb : L.body)
+ consumed.insert(bb->id);
+ }
+}
+
+} // namespace rcc
diff --git a/rcc-legacy/src/mid/scalar_repl.cc b/rcc-legacy/src/mid/scalar_repl.cc
new file mode 100644
index 0000000..04c0a82
--- /dev/null
+++ b/rcc-legacy/src/mid/scalar_repl.cc
@@ -0,0 +1,528 @@
+/* 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
+<http://www.gnu.org/licenses/>. */
+
+/// @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 <cstring>
+#include <vector>
+
+namespace rcc {
+
+namespace {
+
+Ins make_nop_ins() {
+ Ins i = {};
+ i.op = Onop;
+ return i;
+}
+
+struct LoopInfo {
+ Blk* header;
+ std::vector<Blk*> 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<Blk*>& body) {
+ for (Blk* b : body)
+ if (b == x)
+ return true;
+ return false;
+}
+
+bool find_loops(Fn* fn, std::vector<LoopInfo>& 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<Blk*> 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<int>(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<int>(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<std::pair<uint, Ref>>& 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<uint>(r.val), v));
+ return v;
+ }
+ return Ref::none();
+ }
+ Ins* d = t->def;
+ if (!d || !is_pure_arith(static_cast<int>(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<int>(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<uint>(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<Ins*> 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<int>(iload->cls);
+
+ // Clone address chain to preheader, then issue load.
+ std::vector<std::pair<uint, Ref>> 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<uint>(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<Phi*>(alloc(ctx, sizeof(Phi)));
+ std::memset(p, 0, sizeof *p);
+ p->to = phi_ref;
+ p->cls = static_cast<short>(cls);
+ p->narg = 2;
+ p->arg = static_cast<Ref*>(vnew(ctx, 2, sizeof(Ref), PFn));
+ p->blk = static_cast<Blk**>(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<LoopInfo> 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