/* 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 loopinfo.cc
/// @brief find_loops + detect_bivs + ref_is_const (shared).
#include "rcc/loopinfo.h"
#include "rcc/all.h"
namespace rcc {
bool ref_is_const(Fn* fn, Ref r, int64_t& out) {
if (rtype(r) != RCon)
return false;
if (r.val >= static_cast(fn->ncon))
return false;
Con* c = &fn->con[r.val];
if (c->type != Con::CBits || c->flt)
return false;
out = c->bits.i;
return true;
}
Ins* def_ins(Fn* fn, Ref r) {
if (rtype(r) != RTmp || r.val >= static_cast(fn->ntmp))
return nullptr;
return fn->tmp[r.val].def;
}
void 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];
if (hd->npred != 2)
continue;
Blk* pre = nullptr;
Blk* latch = nullptr;
for (uint k = 0; k < hd->npred; k++) {
Blk* p = hd->pred[k];
if (p->id != ~0u && p->id >= hd->id)
latch = p;
else
pre = p;
}
if (!pre || !latch)
continue;
Loop L;
L.header = hd;
L.preheader = pre;
L.latch = latch;
L.body.push_back(hd);
L.body_bid.insert(hd->id);
hd->visit = hd->id;
std::vector stk = {latch};
while (!stk.empty()) {
Blk* n = stk.back();
stk.pop_back();
if (n->visit == hd->id)
continue;
n->visit = hd->id;
L.body.push_back(n);
L.body_bid.insert(n->id);
for (uint k = 0; k < n->npred; k++)
stk.push_back(n->pred[k]);
}
out.push_back(std::move(L));
}
}
void detect_bivs(Fn* fn, const Loop& L, std::vector& out) {
for (Phi* p = L.header->phi; p; p = p->link) {
if (p->narg != 2)
continue;
Ref init_ref = Ref::none(), next_ref = Ref::none();
for (uint k = 0; k < p->narg; k++) {
if (p->blk[k] == L.preheader)
init_ref = p->arg[k];
else if (p->blk[k] == L.latch)
next_ref = p->arg[k];
}
int64_t init_v = 0;
if (!ref_is_const(fn, init_ref, init_v))
continue;
Ins* nx = def_ins(fn, next_ref);
if (!nx || nx->op != Oadd)
continue;
int64_t step_v = 0;
if (req(nx->arg[0], p->to) && ref_is_const(fn, nx->arg[1], step_v)) {
} else if (req(nx->arg[1], p->to) && ref_is_const(fn, nx->arg[0], step_v)) {
} else {
continue;
}
if (step_v == 0)
continue;
out.push_back({p->to, static_cast(p->cls), init_v, step_v});
}
}
bool pick_pointer_iv(Fn* fn, const Loop& L, Phi* p, PointerIV* out) {
if (p->cls != Kl)
return false;
if (p->narg != 2)
return false;
Ref base = Ref::none(), next = Ref::none();
for (uint k = 0; k < p->narg; k++) {
if (p->blk[k] == L.preheader)
base = p->arg[k];
else if (p->blk[k] == L.latch)
next = p->arg[k];
else
return false;
}
if (rtype(base) == -1 || rtype(next) == -1)
return false;
Ins* nx = def_ins(fn, next);
if (!nx || nx->op != Oadd)
return false;
int64_t step = 0;
if (req(nx->arg[0], p->to) && ref_is_const(fn, nx->arg[1], step)) {
} else if (req(nx->arg[1], p->to) && ref_is_const(fn, nx->arg[0], step)) {
} else {
return false;
}
if (step <= 0)
return false;
out->phi_to = p->to;
out->base = base;
out->next = next;
out->step = step;
return true;
}
bool check_exit_cmp(Fn* fn, const Loop& L, Ref p, Ref* end_out, Blk** after_out) {
Blk* hd = L.header;
if (hd->jmp.type != Jjnz)
return false;
Ref cmp_r = hd->jmp.arg;
if (rtype(cmp_r) != RTmp)
return false;
Ins* cmp_def = nullptr;
for (uint k = 0; k < hd->nins; k++) {
Ins* ii = &hd->ins[k];
if (req(ii->to, cmp_r)) {
cmp_def = ii;
break;
}
}
if (!cmp_def)
return false;
if (cmp_def->op != Ocultl && cmp_def->op != Ocsltl)
return false;
if (!req(cmp_def->arg[0], p))
return false;
Ref end = cmp_def->arg[1];
if (rtype(end) == RTmp) {
Ins* d = def_ins(fn, end);
if (d) {
for (Blk* bb : L.body)
if (bb->ins <= d && d < bb->ins + bb->nins)
return false;
}
}
Blk* s1 = hd->s1;
Blk* s2 = hd->s2;
bool s1_in = L.body_bid.count(s1->id) != 0;
bool s2_in = L.body_bid.count(s2->id) != 0;
if (s1_in == s2_in)
return false;
*end_out = end;
*after_out = s1_in ? s2 : s1;
return true;
}
bool has_outside_use(Fn* fn, const Loop& L) {
std::unordered_set 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);
}
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 true;
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 true;
}
if (rtype(b->jmp.arg) == RTmp && loop_def.count(b->jmp.arg.val))
return true;
if (rtype(b->jmp.arg2) == RTmp && loop_def.count(b->jmp.arg2.val))
return true;
}
return false;
}
} // namespace rcc