/* 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 gcm.cc
/// @brief Global Code Motion (GCM, Click's algorithm) -- rearranges instructions into the
/// "shallowest loop" legal block on the dominator tree.
///
/// See docs/gcm.md for architectural notes.
#include "rcc/all.h"
namespace rcc {
namespace {
constexpr uint NOBID = ~0u;
bool isdivwl(Ins* i) {
return INRANGE(i->op, Odiv, Ourem) && KBASE(i->cls) == 0;
}
bool canelim(Ins* i) {
return isload(i->op) || isalloc(i->op) || isdivwl(i);
}
Ins nopins() {
Ins i = {};
i.op = Onop;
return i;
}
bool cheap(Ins* i) {
if (KBASE(i->cls) != 0)
return false;
switch (i->op) {
case Oneg:
case Oadd:
case Osub:
case Omul:
case Oand:
case Oor:
case Oxor:
case Osar:
case Oshr:
case Oshl:
return true;
default: {
int x;
return iscmp(i->op, &x, &x) != 0;
}
}
}
// schedearly
uint earlyins(Fn*, Blk*, Ins*);
uint schedearly(Fn* fn, Ref r) {
if (rtype(r) != RTmp)
return 0;
Tmp* t = &fn->tmp[r.val];
if (t->gcmbid != NOBID)
return t->gcmbid;
if (!t->def)
return t->gcmbid = t->bid; // phi doesn't move, pinned to def block
Blk* b = fn->rpo[t->bid];
assert(b->ins <= t->def && t->def < &b->ins[b->nins]);
t->gcmbid = 0; // Visiting sentinel (recursive backedge gets 0 = start)
return t->gcmbid = earlyins(fn, b, t->def);
}
uint earlyins(Fn* fn, Blk* b, Ins* i) {
uint ba = schedearly(fn, i->arg[0]);
uint bb = schedearly(fn, i->arg[1]);
assert(ba != NOBID && bb != NOBID);
// Take the deeper operand block in dominator tree (shallower dominates deeper)
if (fn->rpo[ba]->depth < fn->rpo[bb]->depth) {
assert(dom(fn->rpo[ba], fn->rpo[bb]));
ba = bb;
}
return pinned(i) ? b->id : ba;
}
void earlyblk(Fn* fn, uint bid) {
Blk* b = fn->rpo[bid];
for (Phi* p = b->phi; p; p = p->link)
for (uint n = 0; n < p->narg; n++)
schedearly(fn, p->arg[n]);
for (Ins* i = b->ins; i < &b->ins[b->nins]; i++)
if (pinned(i)) {
schedearly(fn, i->arg[0]);
schedearly(fn, i->arg[1]);
}
schedearly(fn, b->jmp.arg);
}
// schedlate + bestbid
uint lcabid(Fn* fn, uint b1, uint b2) {
if (b1 == NOBID)
return b2;
if (b2 == NOBID)
return b1;
Blk* b = lca(fn->rpo[b1], fn->rpo[b2]);
assert(b);
return b->id;
}
uint bestbid(Fn* fn, uint earlybid, uint latebid) {
if (latebid == NOBID)
return NOBID; // Unused
assert(earlybid != NOBID);
Blk* earlyb = fn->rpo[earlybid];
Blk* bestb = fn->rpo[latebid];
assert(dom(earlyb, bestb));
for (Blk* curb = bestb; curb != earlyb;) {
curb = curb->idom;
if (curb->loop < bestb->loop)
bestb = curb;
}
return bestb->id;
}
uint lateins(Fn*, Blk*, Ins*, Ref);
uint latephi(Fn*, Phi*, Ref);
uint latejmp(Blk*, Ref);
uint schedlate(Fn* fn, Ref r) {
if (rtype(r) != RTmp)
return NOBID;
Tmp* t = &fn->tmp[r.val];
if (t->visit)
return t->gcmbid;
t->visit = 1;
uint earlybid = t->gcmbid;
if (earlybid == NOBID)
return NOBID; // Unused
t->gcmbid = t->bid; // Reuse gcmbid field to temporarily store late
uint latebid = NOBID;
for (Use* u = t->use; u < &t->use[t->nuse]; u++) {
assert(u->bid < fn->nblk);
Blk* b = fn->rpo[u->bid];
uint usebid;
switch (u->type) {
case Use::UPhi:
usebid = latephi(fn, u->u.phi, r);
break;
case Use::UIns:
usebid = lateins(fn, b, u->u.ins, r);
break;
case Use::UJmp:
usebid = latejmp(b, r);
break;
default:
die("unreachable");
}
latebid = lcabid(fn, latebid, usebid);
}
// latebid might still be NOBID: temp is only indirectly used by "deletable pinned instructions"
if (t->def && !pinned(t->def))
t->gcmbid = bestbid(fn, earlybid, latebid);
// Otherwise keep early
return t->gcmbid;
}
uint lateins(Fn* fn, Blk* b, Ins* i, Ref r) {
assert(b->ins <= i && i < &b->ins[b->nins]);
assert(req(i->arg[0], r) || req(i->arg[1], r));
uint latebid = schedlate(fn, i->to);
if (pinned(i)) {
if (latebid == NOBID && canelim(i))
return NOBID;
return b->id;
}
return latebid;
}
uint latephi(Fn* fn, Phi* p, Ref r) {
if (!p->narg)
return NOBID; // Marked unused
uint latebid = NOBID;
for (uint n = 0; n < p->narg; n++)
if (req(p->arg[n], r))
latebid = lcabid(fn, latebid, p->blk[n]->id);
assert(latebid != NOBID);
return latebid;
}
uint latejmp(Blk* b, Ref r) {
if (req(b->jmp.arg, Ref::none()))
return NOBID;
assert(req(b->jmp.arg, r));
return b->id;
}
void lateblk(Fn* fn, uint bid) {
Blk* b = fn->rpo[bid];
for (Phi** pp = &b->phi; *pp;) {
if (schedlate(fn, (*pp)->to) == NOBID) {
(*pp)->narg = 0; // Mark unused
*pp = (*pp)->link; // Remove this phi
} else {
pp = &(*pp)->link;
}
}
for (Ins* i = b->ins; i < &b->ins[b->nins]; i++)
if (pinned(i))
schedlate(fn, i->to);
}
// gcmmove
void addgcmins(Ctx* ctx, Fn* fn, Ins* vins, uint nins) {
for (Ins* i = vins; i < &vins[nins]; i++) {
assert(rtype(i->to) == RTmp);
Tmp* t = &fn->tmp[i->to.val];
Blk* b = fn->rpo[t->gcmbid];
addins(ctx, &b->ins, &b->nins, i);
}
}
void gcmmove(Ctx* ctx, Fn* fn) {
uint nins = 0;
Ins* vins = static_cast(vnew(ctx, nins, sizeof vins[0], PFn));
for (Tmp* t = fn->tmp; t < &fn->tmp[fn->ntmp]; t++) {
if (!t->def || t->bid == t->gcmbid)
continue;
Ins* i = t->def;
if (pinned(i) && !canelim(i))
continue;
assert(rtype(i->to) == RTmp && t == &fn->tmp[i->to.val]);
if (t->gcmbid != NOBID)
addins(ctx, &vins, &nins, i);
*i = nopins();
}
addgcmins(ctx, fn, vins, nins);
}
// schedblk (intra-block DFS ordering)
Ins* schedins(Ctx* ctx, Fn* fn, Blk* b, Ins* i, Ins** pvins, uint* pnins) {
Ins *i0, *i1;
igroup(b, i, &i0, &i1);
// First recursively order the defs in this block that are depended on by operands in this group
for (i = i0; i < i1; i++)
for (uint n = 0; n < 2; n++) {
if (rtype(i->arg[n]) != RTmp)
continue;
Tmp* t = &fn->tmp[i->arg[n].val];
if (t->bid != b->id || !t->def)
continue;
schedins(ctx, fn, b, t->def, pvins, pnins);
}
// Then emit this group, wipe original to nop (prevent double emission)
for (i = i0; i < i1; i++) {
addins(ctx, pvins, pnins, i);
*i = nopins();
}
return i1;
}
void schedblk(Ctx* ctx, Fn* fn) {
Ins* vins = static_cast(vnew(ctx, 0, sizeof vins[0], PHeap));
for (Blk* b = fn->start; b; b = b->link) {
uint nins = 0;
for (Ins* i = b->ins; i < &b->ins[b->nins];)
i = schedins(ctx, fn, b, i, &vins, &nins);
idup(ctx, b, vins, nins);
}
vfree(vins);
}
// sink (trivial op sinking)
void sinkref(Ctx* ctx, Fn* fn, Blk* b, Ref* pr) {
if (rtype(*pr) != RTmp)
return;
Tmp* t = &fn->tmp[pr->val];
if (!t->def || t->bid == b->id || pinned(t->def) || !cheap(t->def))
return;
// Copy t->def and sink to b
Ins i = *t->def;
Ref r = newtmp(ctx, "snk", t->cls, fn);
t = nullptr; // newtmp might realloc fn->tmp, old pointer invalid
*pr = r;
i.to = r;
fn->tmp[r.val].gcmbid = b->id;
emiti(ctx, i);
sinkref(ctx, fn, b, &i.arg[0]);
sinkref(ctx, fn, b, &i.arg[1]);
}
void sink(Ctx* ctx, Fn* fn) {
for (Blk* b = fn->start; b; b = b->link) {
for (Ins* i = b->ins; i < &b->ins[b->nins]; i++)
if (isload(i->op))
sinkref(ctx, fn, b, &i->arg[0]);
else if (isstore(i->op))
sinkref(ctx, fn, b, &i->arg[1]);
sinkref(ctx, fn, b, &b->jmp.arg);
}
addgcmins(ctx, fn, ctx->curi, static_cast(&ctx->insb[NIns] - ctx->curi));
}
} // namespace
int pinned(Ins* i) {
return optab[i->op].pinned || isdivwl(i);
}
void gcm(Ctx* ctx, Fn* fn) {
filldepth(fn);
fillloop(fn);
for (Tmp* t = fn->tmp; t < &fn->tmp[fn->ntmp]; t++) {
t->visit = 0;
t->gcmbid = NOBID;
}
for (uint bid = 0; bid < fn->nblk; bid++)
earlyblk(fn, bid);
for (uint bid = 0; bid < fn->nblk; bid++)
lateblk(fn, bid);
gcmmove(ctx, fn);
filluse(ctx, fn);
ctx->curi = &ctx->insb[NIns];
sink(ctx, fn);
filluse(ctx, fn);
schedblk(ctx, fn);
if (debug['G']) {
fprintf(ctx->dbg_buf, "\n> After GCM:\n");
printfn(fn, ctx->dbg_buf);
}
}
} // namespace rcc