/* 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 tre.cc
/// @brief Tail Recursion Elimination (TRE)
#include "rcc/all.h"
#include
#include
namespace rcc {
namespace {
struct Site {
Blk* b;
Ins* call; // Points to Ocall in b->ins
Ins* arg_first; // [arg_first..call) is Oarg sequence
std::vector[ args;
};
bool is_self_call(Fn* fn, Ins* call, uint32_t self_id) {
if (call->op != Ocall)
return false;
Ref tgt = call->arg[0];
if (rtype(tgt) != RCon)
return false;
Con* c = &fn->con[tgt.val];
if (c->type != Con::CAddr)
return false;
if (c->sym.type != Sym::SGlo)
return false;
return c->sym.id == self_id;
}
// Locate Ocall and arg-group start in b.
bool collect_site(Fn* fn, Blk* b, int npar, uint32_t self_id, Site& out) {
if (b->nins == 0)
return false;
Ins* last = &b->ins[b->nins - 1];
if (!is_self_call(fn, last, self_id))
return false;
// ret form matches call.to
if (b->jmp.type == Jret0) {
if (!req(last->to, Ref::none()))
return false;
} else if (b->jmp.type == Jretw || b->jmp.type == Jretl || b->jmp.type == Jrets ||
b->jmp.type == Jretd) {
if (!req(b->jmp.arg, last->to))
return false;
} else {
return false;
}
// Verify all instructions before call are Oarg and count matches.
Ins *i0 = nullptr, *i1 = nullptr;
igroup(b, last, &i0, &i1);
if (i1 != last + 1)
return false;
int argc = static_cast(last - i0);
if (argc != npar)
return false;
out.b = b;
out.call = last;
out.arg_first = i0;
out.args.clear();
out.args.reserve(static_cast(npar));
for (Ins* j = i0; j < last; j++) {
if (!isarg(j->op))
return false;
out.args.push_back(j->arg[0]);
}
return true;
}
} // namespace
void tre(Ctx* ctx, Fn* fn) {
if (fn->vararg)
return;
if (fn->name && std::strcmp(fn->name, "main") == 0)
return;
// exit if any block contains isalloc
for (Blk* b = fn->start; b; b = b->link)
for (Ins* i = b->ins; i < &b->ins[b->nins]; i++)
if (isalloc(i->op))
return;
// fn->start cannot already have phi
if (fn->start->phi)
return;
// Collect Opar from start block
std::vector par_inss;
Ins* ie0 = fn->start->ins + fn->start->nins;
for (Ins* i = fn->start->ins; i < ie0 && i->op == Opar; i++)
par_inss.push_back(i);
int npar = static_cast(par_inss.size());
// Reject theoretically allowed Opar variants (struct by-value) not used by SysY
for (Ins* i = fn->start->ins; i < ie0; i++) {
if (i->op == Oparc || i->op == Opare)
return;
}
uint32_t self_id = intern(fn->name);
// Scan qualified sites
std::vector sites;
for (Blk* b = fn->start; b; b = b->link) {
if (!isret(b->jmp.type))
continue;
Site s;
if (collect_site(fn, b, npar, self_id, s))
sites.push_back(std::move(s));
}
if (sites.empty())
return;
// Transformation
Blk* loop_header = fn->start;
// Prepare %p_init_i for each Opar, record original %p_i and cls
auto upar = static_cast(npar);
std::vector][ par_init_refs(upar);
std::vector][ par_orig_refs(upar);
std::vector par_clses(upar);
for (int i = 0; i < npar; i++) {
size_t ui = static_cast(i);
par_orig_refs[ui] = par_inss[ui]->to;
par_clses[ui] = static_cast(par_inss[ui]->cls);
par_init_refs[ui] = newtmp(ctx, "p_init", par_clses[ui], fn);
}
// Create @tre_entry
Blk* tre_entry = newblk(ctx);
tre_entry->name = strf(ctx, PFn, "tre_entry");
for (int i = 0; i < npar; i++) {
size_t ui = static_cast(i);
Ins ni = *par_inss[ui];
ni.to = par_init_refs[ui];
addins(ctx, &tre_entry->ins, &tre_entry->nins, &ni);
}
tre_entry->jmp.type = Jjmp;
tre_entry->jmp.arg = Ref::none();
tre_entry->s1 = loop_header;
tre_entry->s2 = nullptr;
tre_entry->link = loop_header;
fn->start = tre_entry;
// Remove head npar Opars from loop_header
int remaining = static_cast(loop_header->nins) - npar;
if (remaining > 0)
std::memmove(loop_header->ins, loop_header->ins + npar,
static_cast(remaining) * sizeof(Ins));
loop_header->nins = static_cast(remaining);
// Insert npar phis at loop_header head
int nphi_args = 1 + static_cast(sites.size());
for (int i = npar - 1; i >= 0; i--) {
size_t ui = static_cast(i);
auto* p = static_cast(alloc(ctx, sizeof(Phi)));
std::memset(p, 0, sizeof(Phi));
p->to = par_orig_refs[ui];
p->cls = static_cast(par_clses[ui]);
p->narg = static_cast(nphi_args);
p->arg = static_cast][(vnew(ctx, static_cast(nphi_args), sizeof(Ref), PFn));
p->blk = static_cast(vnew(ctx, static_cast(nphi_args), sizeof(Blk*), PFn));
p->arg[0] = par_init_refs[ui];
p->blk[0] = tre_entry;
for (size_t k = 0; k < sites.size(); k++) {
p->arg[1 + k] = sites[k].args[ui];
p->blk[1 + k] = sites[k].b;
}
p->link = loop_header->phi;
loop_header->phi = p;
}
// Truncate block at each site and redirect jmp to loop_header
for (auto& s : sites) {
int new_nins = static_cast(s.arg_first - s.b->ins);
s.b->nins = static_cast(new_nins);
s.b->jmp.type = Jjmp;
s.b->jmp.arg = Ref::none();
s.b->s1 = loop_header;
s.b->s2 = nullptr;
}
if (debug['T']) {
fprintf(stderr, "\n>>> after tre (%s) — %zu site(s):\n", fn->name, sites.size());
printfn(fn, stderr);
}
}
} // namespace rcc
]