/* 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 backend/simpl.cc
/// @brief Frontend simplification pass.
#include "rcc/all.h"
#include
#include
#include
namespace rcc {
// Memory block copy expansion: Oblit pair -> explicit load/store chain.
namespace {
struct BlitTier {
int store_op;
int load_op;
int load_cls;
int width;
};
constexpr BlitTier kBlitTiers[] = {
{Ostorel, Oload, Kl, 8},
{Ostorew, Oload, Kw, 4},
{Ostoreh, Oloaduh, Kw, 2},
{Ostoreb, Oloadub, Kw, 1},
};
void blit(Ctx* ctx, Ref sd[2], int signed_sz, Fn* fn) {
bool fwd = signed_sz >= 0;
int sz = std::abs(signed_sz);
int off = fwd ? sz : 0;
for (const BlitTier& t : kBlitTiers) {
while (sz >= t.width) {
if (fwd)
off -= t.width;
Ref val = newtmp(ctx, "blt", Kl, fn);
Ref dst_addr = newtmp(ctx, "blt", Kl, fn);
Ref off_con = getcon(ctx, off, fn);
// Reverse emit: store <- dst_addr <- load <- src_addr.
emit(ctx, t.store_op, 0, Ref::none(), val, dst_addr);
emit(ctx, Oadd, Kl, dst_addr, sd[1], off_con);
Ref src_addr = newtmp(ctx, "blt", Kl, fn);
emit(ctx, t.load_op, t.load_cls, val, src_addr, Ref::none());
emit(ctx, Oadd, Kl, src_addr, sd[0], off_con);
if (!fwd)
off += t.width;
sz -= t.width;
}
}
}
inline bool is_pow2(uint64_t v) {
return v != 0 && (v & (v - 1)) == 0;
}
// Single instruction simplification: Oblit1 / Oudiv / Ourem.
void simplify_one(Ctx* ctx, Ins** pi, bool* rebuilt, Blk* b, Fn* fn) {
Ins* i = *pi;
switch (i->op) {
case Oblit1: {
assert(i > b->ins);
assert((i - 1)->op == Oblit0);
// First rebuild trigger: move unprocessed tail to insb.
if (!*rebuilt) {
ctx->curi = ctx->insb + NIns;
ulong tail = static_cast((b->ins + b->nins) - (i + 1));
ctx->curi -= tail;
icpy(ctx->curi, i + 1, tail);
*rebuilt = true;
}
blit(ctx, (i - 1)->arg, rsval(i->arg[0]), fn);
*pi = i - 1; // Retreat pointer to Oblit0.
return;
}
case Oudiv:
case Ourem: {
// Unsigned integer + constant divisor + pow2.
if (KBASE(i->cls) != 0)
break;
if (rtype(i->arg[1]) != RCon)
break;
Con* c = &fn->con[i->arg[1].val];
if (c->type != Con::CBits)
break;
if (!is_pow2(static_cast(c->bits.i)))
break;
int n = lowest_bit(static_cast(c->bits.i));
if (i->op == Ourem) {
i->op = Oand;
i->arg[1] = getcon(ctx, (1LL << n) - 1, fn);
} else {
i->op = Oshr;
i->arg[1] = getcon(ctx, n, fn);
}
break;
}
default:
break;
}
if (*rebuilt)
emiti(ctx, *i);
}
} // namespace
void simpl(Ctx* ctx, Fn* fn) {
for (Blk* b = fn->start; b; b = b->link) {
bool rebuilt = false;
for (Ins* i = b->ins + b->nins; i != b->ins;) {
--i;
simplify_one(ctx, &i, &rebuilt, b, fn);
}
if (rebuilt)
idup(ctx, b, ctx->curi, static_cast(ctx->insb + NIns - ctx->curi));
}
}
// salloc: Dynamic alloca expanded on stack. Called by rv64_isel.
void salloc(Ctx* ctx, Ref rt, Ref rs, Fn* fn) {
fn->dynalloc = 1;
if (rtype(rs) == RCon) {
int64_t sz = fn->con[rs.val].bits.i;
if (sz < 0 || sz >= INT_MAX - 15)
err(const_cast("invalid alloc size %" PRId64), sz);
sz = (sz + 15) & -16;
emit(ctx, Osalloc, Kl, rt, getcon(ctx, sz, fn), Ref::none());
return;
}
// Non-constant size path.
if (rtype(rs) == RTmp && fn->tmp[rs.val].slot != -1)
err(const_cast("unlikely alloc argument %%%s for %%%s"), fn->tmp[rs.val].name,
fn->tmp[rt.val].name);
Ref r0 = newtmp(ctx, "isel", Kl, fn);
Ref r1 = newtmp(ctx, "isel", Kl, fn);
emit(ctx, Osalloc, Kl, rt, r0, Ref::none());
emit(ctx, Oand, Kl, r0, r1, getcon(ctx, -16, fn));
emit(ctx, Oadd, Kl, r1, rs, getcon(ctx, 15, fn));
}
} // namespace rcc