/* 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 sccp.cc
/// @brief Sparse Conditional Constant Propagation (Wegman-Zadeck).
///
/// See docs/sccp.md for architectural notes.
#include "rcc/all.h"
#include
#include
#include
#include
#include
namespace rcc {
namespace {
enum LatState : uchar { Top = 0, Const = 1, Bot = 2 };
struct Lat {
LatState state;
Ref con; // Only valid when state==Const, type==RCon
};
constexpr Lat kTop = {Top, {RTmp, 0}};
constexpr Lat kBot = {Bot, {RTmp, 0}};
bool con_eq(Fn* fn, Ref a, Ref b) {
if (req(a, b))
return true;
if (rtype(a) != RCon || rtype(b) != RCon)
return false;
if (a.val >= static_cast(fn->ncon) || b.val >= static_cast(fn->ncon))
return false;
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 && ca->flt == cb->flt;
}
bool lat_eq(Fn* fn, Lat a, Lat b) {
if (a.state != b.state)
return false;
if (a.state == Const)
return con_eq(fn, a.con, b.con);
return true;
}
// Check if unfoldable type.
bool op_is_unfoldable(uint op) {
if (isstore(op))
return true; // store does not define tmp, but safe
if (isload(op))
return true;
if (isalloc(op))
return true;
if (ispar(op))
return true;
if (isarg(op))
return true;
if (op == Ocall)
return true;
if (op == Oblit0 || op == Oblit1)
return true;
if (op == Ovastart || op == Ovaarg)
return true;
if (op == Odbgloc)
return true;
if (op == Osel0 || op == Osel1)
return true;
if (op == Onop)
return true;
return false;
}
struct Sccp {
Ctx* ctx;
Fn* fn;
std::vector lat; // ntmp
std::vector blk_reachable; // nblk
// edge_exec pairs.
std::set> edge_exec;
std::vector cfg_wl;
std::vector ssa_wl; // tmp val queue
Lat arg_lat(Ref r) {
if (rtype(r) == RCon)
return {Const, r};
if (rtype(r) == RTmp) {
if (r.val >= lat.size())
return kBot;
return lat[r.val];
}
// RSlot / RType / RCall / RMem etc: do not participate in constant propagation
return kBot;
}
void set_lat(uint t, Lat nv) {
if (t >= lat.size() || t == 0)
return;
if (lat_eq(fn, lat[t], nv))
return;
// lattice monotonically descends: Top -> Const -> Bot
lat[t] = nv;
ssa_wl.push_back(t);
}
// Evaluate instruction using lattice.
Lat eval_ins(Ins* i) {
if (rtype(i->to) != RTmp)
return kBot;
if (op_is_unfoldable(i->op))
return kBot;
Lat a0 = arg_lat(i->arg[0]);
Lat a1;
if (req(i->arg[1], Ref::none()))
a1 = {Const, Ref::con_z()}; // phantom arg[1] for unary op
else
a1 = arg_lat(i->arg[1]);
// Ocopy special case: lattice equals arg[0]
if (i->op == Ocopy)
return a0;
if (a0.state == Top || a1.state == Top)
return kTop;
if (a0.state == Bot || a1.state == Bot)
return kBot;
// Both sides are Const: try to fold
Ins tmp = *i;
tmp.arg[0] = a0.con;
tmp.arg[1] = a1.con;
Ref r = foldref(ctx, fn, &tmp);
if (req(r, Ref::none()))
return kBot;
return {Const, r};
}
Lat eval_phi(Phi* p, Blk* in_blk) {
Lat acc = kTop;
for (uint k = 0; k < p->narg; k++) {
Blk* pred = p->blk[k];
if (!pred)
continue;
if (!edge_exec.count({pred->id, in_blk->id}))
continue; // Incoming edge unreachable - skip
Lat a = arg_lat(p->arg[k]);
if (a.state == Bot)
return kBot;
if (a.state == Top)
continue;
// Const
if (acc.state == Top) {
acc = a;
} else if (!con_eq(fn, acc.con, a.con)) {
return kBot;
}
}
return acc;
}
// Activate CFG edge.
void activate_edge(Blk* pred, Blk* succ) {
if (!succ)
return;
auto key = std::make_pair(pred->id, succ->id);
if (!edge_exec.insert(key).second)
return; // Already activated
// Enqueue succ if needed.
cfg_wl.push_back(succ);
if (!blk_reachable[succ->id])
blk_reachable[succ->id] = 1;
}
void eval_jmp(Blk* b) {
int t = b->jmp.type;
if (isret(t) || t == Jhlt)
return;
if (t == Jjmp) {
activate_edge(b, b->s1);
return;
}
if (INRANGE(t, Jjf, Jjf1)) {
// Conditional jumps.
activate_edge(b, b->s1);
activate_edge(b, b->s2);
return;
}
if (t == Jjnz) {
Lat c = arg_lat(b->jmp.arg);
if (c.state == Top)
return;
if (c.state == Bot) {
activate_edge(b, b->s1);
activate_edge(b, b->s2);
return;
}
// Const: check if non-zero
if (rtype(c.con) != RCon || c.con.val >= static_cast(fn->ncon)) {
activate_edge(b, b->s1);
activate_edge(b, b->s2);
return;
}
Con* cc = &fn->con[c.con.val];
if (cc->type != Con::CBits) {
// CAddr is treated as truthy (non-null address)
activate_edge(b, b->s1);
return;
}
if (cc->bits.i != 0)
activate_edge(b, b->s1);
else
activate_edge(b, b->s2);
return;
}
// Other unknown jmp types: activate both sides
activate_edge(b, b->s1);
activate_edge(b, b->s2);
}
void visit_block(Blk* b) {
// Re-eval phis.
for (Phi* p = b->phi; p; p = p->link) {
if (rtype(p->to) != RTmp)
continue;
Lat nv = eval_phi(p, b);
set_lat(p->to.val, nv);
}
for (uint k = 0; k < b->nins; k++) {
Ins* i = &b->ins[k];
if (rtype(i->to) != RTmp)
continue;
Lat nv = eval_ins(i);
set_lat(i->to.val, nv);
}
eval_jmp(b);
}
// Process tmp uses.
void visit_uses(uint t) {
if (t >= static_cast(fn->ntmp))
return;
Tmp* tt = &fn->tmp[t];
for (uint u = 0; u < tt->nuse; u++) {
Use& uu = tt->use[u];
if (uu.bid >= blk_reachable.size())
continue;
if (!blk_reachable[uu.bid])
continue; // In unreachable block, ignore for now
Blk* ub = fn->rpo[uu.bid];
if (uu.type == Use::UPhi) {
Phi* p = uu.u.phi;
if (rtype(p->to) == RTmp) {
Lat nv = eval_phi(p, ub);
set_lat(p->to.val, nv);
}
} else if (uu.type == Use::UIns) {
Ins* i = uu.u.ins;
if (rtype(i->to) == RTmp) {
Lat nv = eval_ins(i);
set_lat(i->to.val, nv);
}
} else if (uu.type == Use::UJmp) {
eval_jmp(ub);
}
}
}
void solve() {
// Init state.
lat.assign(static_cast(fn->ntmp), kTop);
blk_reachable.assign(static_cast(fn->nblk), 0);
edge_exec.clear();
cfg_wl.clear();
ssa_wl.clear();
blk_reachable[fn->start->id] = 1;
cfg_wl.push_back(fn->start);
// Enqueue start.
while (!cfg_wl.empty() || !ssa_wl.empty()) {
// Process worklists.
while (!cfg_wl.empty()) {
Blk* b = cfg_wl.back();
cfg_wl.pop_back();
visit_block(b);
}
while (!ssa_wl.empty()) {
uint t = ssa_wl.back();
ssa_wl.pop_back();
visit_uses(t);
}
}
}
// Apply: Commit lattice to IR.
Ref maybe_const(Ref r) {
if (rtype(r) != RTmp)
return r;
if (r.val >= lat.size())
return r;
Lat& l = lat[r.val];
if (l.state == Const)
return l.con;
return r;
}
int apply() {
int n_ins = 0, n_phi = 0, n_jmp = 0, n_arg = 0;
for (Blk* b = fn->start; b; b = b->link) {
if (b->id >= blk_reachable.size() || !blk_reachable[b->id])
continue;
// Phi: Remove incoming edges from unreachable preds; replace arg operands
for (Phi* p = b->phi; p; p = p->link) {
uint w = 0;
for (uint k = 0; k < p->narg; k++) {
Blk* pred = p->blk[k];
if (!pred || !edge_exec.count({pred->id, b->id})) {
// This incoming edge was not activated during SCCP -> unreachable -> delete
continue;
}
Ref na = maybe_const(p->arg[k]);
if (!req(na, p->arg[k]))
n_arg++;
if (w != k) {
p->arg[w] = na;
p->blk[w] = pred;
} else {
p->arg[w] = na;
}
w++;
}
p->narg = w;
// Keep constant phi.to for DCE.
if (rtype(p->to) == RTmp && p->to.val < lat.size() && lat[p->to.val].state == Const)
n_phi++;
}
// Ins: Change Const def to Ocopy; replace arg operands
for (uint k = 0; k < b->nins; k++) {
Ins* i = &b->ins[k];
bool is_const_def = (rtype(i->to) == RTmp && i->to.val < lat.size() &&
lat[i->to.val].state == Const);
if (is_const_def && i->op != Ocopy && !op_is_unfoldable(i->op)) {
Ref cr = lat[i->to.val].con;
i->op = Ocopy;
i->arg[0] = cr;
i->arg[1] = Ref::none();
n_ins++;
continue;
}
// Otherwise just replace args
Ref na0 = maybe_const(i->arg[0]);
Ref na1 = maybe_const(i->arg[1]);
if (!req(na0, i->arg[0])) {
i->arg[0] = na0;
n_arg++;
}
if (!req(na1, i->arg[1])) {
i->arg[1] = na1;
n_arg++;
}
}
// jmp: Jjnz cond Const -> Jjmp; replace args
if (b->jmp.type == Jjnz) {
Lat c = arg_lat(b->jmp.arg);
if (c.state == Const && rtype(c.con) == RCon &&
c.con.val < static_cast(fn->ncon)) {
Con* cc = &fn->con[c.con.val];
if (cc->type == Con::CBits) {
// Choose succ
Blk* taken = (cc->bits.i != 0) ? b->s1 : b->s2;
b->jmp.type = Jjmp;
b->jmp.arg = Ref::none();
b->s1 = taken;
b->s2 = nullptr;
n_jmp++;
} else if (cc->type == Con::CAddr) {
// Address is always truthy
b->jmp.type = Jjmp;
b->jmp.arg = Ref::none();
b->s1 = b->s1;
b->s2 = nullptr;
n_jmp++;
}
} else {
Ref na = maybe_const(b->jmp.arg);
if (!req(na, b->jmp.arg)) {
b->jmp.arg = na;
n_arg++;
}
}
} else if (!isret(b->jmp.type) && b->jmp.type != Jhlt && b->jmp.type != Jjmp) {
Ref na = maybe_const(b->jmp.arg);
if (!req(na, b->jmp.arg)) {
b->jmp.arg = na;
n_arg++;
}
}
}
if (debug['S']) {
FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr;
fprintf(out, "> SCCP %s: const_ins=%d const_phi=%d jjnz_collapse=%d arg_subst=%d\n",
fn->name, n_ins, n_phi, n_jmp, n_arg);
}
return n_ins + n_phi + n_jmp + n_arg;
}
};
} // namespace
void sccp(Ctx* ctx, Fn* fn) {
// Prerequisites: filluse and rpo ready.
Sccp s;
s.ctx = ctx;
s.fn = fn;
s.solve();
s.apply();
}
} // namespace rcc