/* 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 loopswap.cc
/// @brief Integer perfect 2-level nested loop interchange.
///
/// See docs/loopswap.md for architectural notes.
#include "rcc/all.h"
#include "rcc/loopinfo.h"
#include
#include
#include
namespace rcc {
namespace {
// Helper: reaches
bool reaches(Fn* fn, Ref r, Ref target, std::unordered_set& visiting, int budget) {
if (req(r, target))
return true;
if (budget <= 0)
return false;
if (rtype(r) != RTmp || r.val >= static_cast(fn->ntmp))
return false;
if (!visiting.insert(r.val).second)
return true;
Ins* def = fn->tmp[r.val].def;
if (def && def->op == Ocopy) {
bool ok = reaches(fn, def->arg[0], target, visiting, budget - 1);
visiting.erase(r.val);
return ok;
}
for (Blk* bb = fn->start; bb; bb = bb->link) {
for (Phi* p = bb->phi; p; p = p->link) {
if (!req(p->to, r))
continue;
bool ok = true;
for (uint k = 0; k < p->narg; k++) {
if (req(p->arg[k], r))
continue;
if (!reaches(fn, p->arg[k], target, visiting, budget - 1)) {
ok = false;
break;
}
}
visiting.erase(r.val);
return ok;
}
}
visiting.erase(r.val);
return false;
}
bool carries(Fn* fn, Ref r, Ref target) {
std::unordered_set v;
return reaches(fn, r, target, v, 64);
}
// Affine decomposition
struct Aff {
int64_t outer_c;
int64_t inner_c;
};
Aff affine(Fn* fn, Ref r, Ref outer_to, Ref inner_to, int depth) {
if (depth > 24)
return {0, 0};
if (rtype(r) == RCon)
return {0, 0};
if (rtype(r) != RTmp)
return {0, 0};
if (carries(fn, r, outer_to))
return {1, 0};
if (carries(fn, r, inner_to))
return {0, 1};
if (r.val >= static_cast(fn->ntmp))
return {0, 0};
Ins* def = fn->tmp[r.val].def;
if (!def)
return {0, 0};
switch (def->op) {
case Oadd: {
Aff a = affine(fn, def->arg[0], outer_to, inner_to, depth + 1);
Aff b = affine(fn, def->arg[1], outer_to, inner_to, depth + 1);
return {a.outer_c + b.outer_c, a.inner_c + b.inner_c};
}
case Osub: {
Aff a = affine(fn, def->arg[0], outer_to, inner_to, depth + 1);
Aff b = affine(fn, def->arg[1], outer_to, inner_to, depth + 1);
return {a.outer_c - b.outer_c, a.inner_c - b.inner_c};
}
case Omul: {
int64_t c;
if (ref_is_const(fn, def->arg[1], c)) {
Aff a = affine(fn, def->arg[0], outer_to, inner_to, depth + 1);
return {a.outer_c * c, a.inner_c * c};
}
if (ref_is_const(fn, def->arg[0], c)) {
Aff a = affine(fn, def->arg[1], outer_to, inner_to, depth + 1);
return {a.outer_c * c, a.inner_c * c};
}
return {0, 0};
}
case Oextsw:
case Ocopy:
return affine(fn, def->arg[0], outer_to, inner_to, depth + 1);
default:
return {0, 0};
}
}
// Perfect nest: L1.body \ L2.body \ {L1.header, L1.latch} must only contain empty Jjmp blocks.
bool perfect_nest(const Loop& L1, const Loop& L2) {
for (Blk* bb : L1.body) {
if (bb == L1.header || bb == L1.latch)
continue;
if (L2.body_bid.count(bb->id))
continue;
if (bb->nins != 0)
return false;
if (bb->jmp.type != Jjmp)
return false;
}
return true;
}
// Does the cmp ins for header.jmp.arg reference bv.to (or its carrier equiv)?
int find_header_cmp(Fn* fn, const Loop& L, const Biv& bv) {
Ref a = L.header->jmp.arg;
if (rtype(a) != RTmp)
return -1;
for (uint k = 0; k < L.header->nins; k++) {
Ins* ii = &L.header->ins[k];
if (!req(ii->to, a))
continue;
if (carries(fn, ii->arg[0], bv.to) || carries(fn, ii->arg[1], bv.to))
return static_cast(k);
return -1;
}
return -1;
}
// Find index of BIV's latch increment ins (the Oadd) in latch->ins array.
int find_latch_inc(const Loop& L, const Biv& bv) {
Phi* P = nullptr;
for (Phi* p = L.header->phi; p; p = p->link)
if (req(p->to, bv.to)) {
P = p;
break;
}
if (!P)
return -1;
Ref next = Ref::none();
for (uint k = 0; k < P->narg; k++)
if (P->blk[k] == L.latch) {
next = P->arg[k];
break;
}
if (rtype(next) != RTmp)
return -1;
for (uint k = 0; k < L.latch->nins; k++)
if (req(L.latch->ins[k].to, next) && L.latch->ins[k].op == Oadd)
return static_cast(k);
return -1;
}
Phi* find_phi(Blk* b, Ref to) {
for (Phi* p = b->phi; p; p = p->link)
if (req(p->to, to))
return p;
return nullptr;
}
void unlink_phi(Blk* b, Phi* t) {
for (Phi** cur = &b->phi; *cur; cur = &(*cur)->link)
if (*cur == t) {
*cur = t->link;
t->link = nullptr;
return;
}
}
void prepend_phi(Blk* b, Phi* p) {
p->link = b->phi;
b->phi = p;
}
void rewrite_phi_blk(Phi* p, Blk* from, Blk* to) {
for (uint k = 0; k < p->narg; k++)
if (p->blk[k] == from)
p->blk[k] = to;
}
void rewrite_uses(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 (uint k = 0; k < b->nins; k++) {
Ins* i = &b->ins[k];
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;
}
}
struct AccessRec {
Ref addr;
int64_t inner_s;
bool is_store;
};
bool try_swap(Ctx* ctx, Fn* fn, const Loop& L1, const Loop& L2) {
if (!perfect_nest(L1, L2))
return false;
std::vector bs1, bs2;
detect_bivs(fn, L1, bs1);
detect_bivs(fn, L2, bs2);
if (bs1.empty() || bs2.empty())
return false;
// Pick the BIV referenced by header.cmp (SysY loops typically compare one BIV).
Biv b1{}, b2{};
int cmp1 = -1, cmp2 = -1;
for (auto& bv : bs1) {
int idx = find_header_cmp(fn, L1, bv);
if (idx >= 0) {
b1 = bv;
cmp1 = idx;
break;
}
}
for (auto& bv : bs2) {
int idx = find_header_cmp(fn, L2, bv);
if (idx >= 0) {
b2 = bv;
cmp2 = idx;
break;
}
}
if (cmp1 < 0 || cmp2 < 0)
return false;
if ((b1.cls != Kw && b1.cls != Kl) || (b2.cls != Kw && b2.cls != Kl))
return false;
int inc1 = find_latch_inc(L1, b1);
int inc2 = find_latch_inc(L2, b2);
if (inc1 < 0 || inc2 < 0)
return false;
// Scan L2.body: integer only + decompose addr.
int64_t sum_outer = 0, sum_inner = 0;
std::vector mems;
for (Blk* bb : L2.body) {
for (uint k = 0; k < bb->nins; k++) {
Ins* ii = &bb->ins[k];
int op = ii->op;
if (op == Ocall)
return false;
if (ii->cls == Ks || ii->cls == Kd)
return false;
bool is_ld = isload(op);
bool is_st = isstore(op);
if (!is_ld && !is_st)
continue;
if (op == Ostores || op == Ostored)
return false;
Ref addr = is_st ? ii->arg[1] : ii->arg[0];
Aff a = affine(fn, addr, b1.to, b2.to, 0);
sum_outer += std::abs(a.outer_c);
sum_inner += std::abs(a.inner_c);
mems.push_back({addr, a.inner_c, is_st});
}
}
// accumulator pattern: if there are stores => every store has inner_s==0 and matches a load
// with same addr and inner_s==0.
bool has_store = false;
for (auto& m : mems)
if (m.is_store) {
has_store = true;
break;
}
if (has_store) {
for (auto& s : mems) {
if (!s.is_store)
continue;
if (s.inner_s != 0)
return false;
bool found = false;
for (auto& l : mems) {
if (!l.is_store && req(l.addr, s.addr) && l.inner_s == 0) {
found = true;
break;
}
}
if (!found)
return false;
}
}
if (sum_inner == 0)
return false;
if (sum_outer * 4 >= sum_inner)
return false;
// Execute
Blk* h1 = L1.header;
Blk* h2 = L2.header;
Phi* P1 = find_phi(h1, b1.to);
Phi* P2 = find_phi(h2, b2.to);
if (!P1 || !P2)
return false;
// Collect self-loop carrier of b1.to on L2.header (Braun artifact; invalid after swap, needs
// removal).
std::vector stale;
for (Phi* p = h2->phi; p; p = p->link) {
if (p->narg != 2)
continue;
Ref pre = Ref::none();
bool is_self = false;
for (uint k = 0; k < p->narg; k++) {
if (p->blk[k] == L2.preheader)
pre = p->arg[k];
else if (p->blk[k] == L2.latch && req(p->arg[k], p->to))
is_self = true;
}
if (is_self && carries(fn, pre, b1.to))
stale.push_back(p);
}
// Snapshot: copy 4 target ins before swap to avoid distortion.
Ins C1 = h1->ins[cmp1];
Ins C2 = h2->ins[cmp2];
Ins I1 = L1.latch->ins[inc1];
Ins I2 = L2.latch->ins[inc2];
// Swap phi: unlink + rewrite edge + prepend to the other.
unlink_phi(h1, P1);
unlink_phi(h2, P2);
rewrite_phi_blk(P1, L1.preheader, L2.preheader);
rewrite_phi_blk(P1, L1.latch, L2.latch);
rewrite_phi_blk(P2, L2.preheader, L1.preheader);
rewrite_phi_blk(P2, L2.latch, L1.latch);
prepend_phi(h1, P2);
prepend_phi(h2, P1);
// Swap cmp ins + jmp descriptor.
h1->ins[cmp1].op = Onop;
h2->ins[cmp2].op = Onop;
addins(ctx, &h1->ins, &h1->nins, &C2);
addins(ctx, &h2->ins, &h2->nins, &C1);
std::swap(h1->jmp.arg, h2->jmp.arg);
std::swap(h1->jmp.cls, h2->jmp.cls);
std::swap(h1->jmp.arg2, h2->jmp.arg2);
// Swap increment.
L1.latch->ins[inc1].op = Onop;
L2.latch->ins[inc2].op = Onop;
addins(ctx, &L1.latch->ins, &L1.latch->nins, &I2);
addins(ctx, &L2.latch->ins, &L2.latch->nins, &I1);
// Invalidate carriers: rewrite_uses to substitute with b1.to, then unlink phi.
for (Phi* s : stale) {
rewrite_uses(fn, s->to, b1.to);
unlink_phi(h2, s);
}
return true;
}
// L2 is immediate-child of L1: strict subset of body, with no L3 strictly in between.
bool is_immediate_child(const std::vector& loops, size_t i_par, size_t j_chl) {
const Loop& par = loops[i_par];
const Loop& chl = loops[j_chl];
if (chl.body_bid.size() >= par.body_bid.size())
return false;
for (uint id : chl.body_bid)
if (!par.body_bid.count(id))
return false;
for (size_t m = 0; m < loops.size(); m++) {
if (m == i_par || m == j_chl)
continue;
const Loop& mid = loops[m];
if (mid.body_bid.size() <= chl.body_bid.size())
continue;
if (mid.body_bid.size() >= par.body_bid.size())
continue;
bool chl_in_mid = true;
for (uint id : chl.body_bid)
if (!mid.body_bid.count(id)) {
chl_in_mid = false;
break;
}
if (!chl_in_mid)
continue;
bool mid_in_par = true;
for (uint id : mid.body_bid)
if (!par.body_bid.count(id)) {
mid_in_par = false;
break;
}
if (mid_in_par)
return false;
}
return true;
}
} // namespace
void loopinterchange(Ctx* ctx, Fn* fn) {
std::vector loops;
find_loops(fn, loops);
if (loops.size() < 2)
return;
for (size_t i = 0; i < loops.size(); i++) {
Loop& L1 = loops[i];
Loop* L2 = nullptr;
bool amb = false;
for (size_t j = 0; j < loops.size(); j++) {
if (i == j)
continue;
if (!is_immediate_child(loops, i, j))
continue;
if (L2) {
amb = true;
break;
}
L2 = &loops[j];
}
if (!L2 || amb)
continue;
if (try_swap(ctx, fn, L1, *L2))
return; // One swap per pass, CFG/loops are invalidated
}
}
} // namespace rcc