/* 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 fold.cc /// @brief Constant folding utilities -- called by GVN (dedupins / assoccon), no standalone driver. /// /// See docs/fold.md for architectural notes. #include "rcc/all.h" #include #include namespace rcc { namespace { inline bool iscon(const Con* c, int w, uint64_t k) { if (c->type != Con::CBits) return false; int64_t v = w ? c->bits.i : static_cast(c->bits.i); return static_cast(v) == (w ? k : static_cast(k)); } union Bits64 { int64_t s; uint64_t u; float fs; double fd; }; inline Bits64 read_bits(const Con* c) { Bits64 b; b.s = c->bits.i; return b; } int addr_combine(int op, const Con* cl, const Con* cr, int* typ_out, Sym* sym_out) { int ctyp = Con::CBits; Sym sym; std::memset(&sym, 0, sizeof sym); bool al = (cl->type == Con::CAddr); bool ar = (cr->type == Con::CAddr); if (op == Oadd) { if (al && ar) return 1; if (al || ar) { ctyp = Con::CAddr; sym = al ? cl->sym : cr->sym; } } else if (op == Osub) { if (al && ar) { if (!symeq(cl->sym, cr->sym)) return 1; // Subtract same sym. } else if (al) { ctyp = Con::CAddr; sym = cl->sym; } else if (ar) { return 1; // Number - address is invalid } } else if (op == Ocast) { if (al) { ctyp = Con::CAddr; sym = cl->sym; } } else if (al || ar) { return 1; } *typ_out = ctyp; *sym_out = sym; return 0; } uint64_t foldcmp_int(int cmp, int w, Bits64 l, Bits64 r) { if (!w) { l.u = static_cast(static_cast(l.u)); r.u = static_cast(static_cast(r.u)); } switch (cmp) { case Cieq: return l.u == r.u; case Cine: return l.u != r.u; case Ciule: return l.u <= r.u; case Ciult: return l.u < r.u; case Ciuge: return l.u >= r.u; case Ciugt: return l.u > r.u; case Cisle: return l.s <= r.s; case Cislt: return l.s < r.s; case Cisge: return l.s >= r.s; case Cisgt: return l.s > r.s; default: die("foldcmp_int: bad cmp %d", cmp); } } template uint64_t foldcmp_flt(int cmp, F l, F r) { switch (cmp) { case Cfeq: return l == r; case Cfne: return l != r; case Cfle: return l <= r; case Cflt: return l < r; case Cfge: return l >= r; case Cfgt: return l > r; case Cfo: return (l < r) || (l >= r); case Cfuo: return !((l < r) || (l >= r)); default: die("foldcmp_flt: bad cmp %d", cmp); } } void foldflt(Con* res, int op, int w, const Con* cl, const Con* cr) { if (cl->type != Con::CBits || cr->type != Con::CBits) err(const_cast("invalid address operand for '%s'"), optab[op].name); res->type = Con::CBits; res->sym = Sym{}; std::memset(&res->bits, 0, sizeof res->bits); res->flt = 0; if (w) { double ld = cl->bits.d, rd = cr->bits.d, xd; switch (op) { case Oadd: xd = ld + rd; break; case Osub: xd = ld - rd; break; case Oneg: xd = -ld; break; case Odiv: xd = ld / rd; break; case Omul: xd = ld * rd; break; case Oswtof: xd = static_cast(static_cast(cl->bits.i)); break; case Ouwtof: xd = static_cast(static_cast(cl->bits.i)); break; case Osltof: xd = static_cast(static_cast(cl->bits.i)); break; case Oultof: xd = static_cast(static_cast(cl->bits.i)); break; case Oexts: xd = static_cast(cl->bits.s); break; case Ocast: xd = ld; break; default: die("foldflt: bad op Kd %d", op); } res->bits.d = xd; res->flt = 2; } else { float ls = cl->bits.s, rs = cr->bits.s, xs; switch (op) { case Oadd: xs = ls + rs; break; case Osub: xs = ls - rs; break; case Oneg: xs = -ls; break; case Odiv: xs = ls / rs; break; case Omul: xs = ls * rs; break; case Oswtof: xs = static_cast(static_cast(cl->bits.i)); break; case Ouwtof: xs = static_cast(static_cast(cl->bits.i)); break; case Osltof: xs = static_cast(static_cast(cl->bits.i)); break; case Oultof: xs = static_cast(static_cast(cl->bits.i)); break; case Otruncd: xs = static_cast(cl->bits.d); break; case Ocast: xs = ls; break; default: die("foldflt: bad op Ks %d", op); } res->bits.s = xs; res->flt = 1; } } } // namespace int foldint(Con* res, int op, int w, Con* cl, Con* cr) { // Address combination check. int ctyp; Sym sym; if (addr_combine(op, cl, cr, &ctyp, &sym)) return 1; Bits64 l = read_bits(cl); Bits64 r = read_bits(cr); // Div / rem. if (op == Odiv || op == Orem || op == Oudiv || op == Ourem) { if (iscon(cr, w, 0)) return 1; if (op == Odiv || op == Orem) { uint64_t intmin = w ? static_cast(INT64_MIN) : static_cast(static_cast(INT32_MIN)); if (iscon(cr, w, static_cast(-1)) && iscon(cl, w, intmin)) return 1; } } // Dispatch. uint64_t x; if (op >= Ocmpw && op <= Ocmpw1) { x = foldcmp_int(op - Ocmpw, /*w=*/0, l, r); } else if (op >= Ocmpl && op <= Ocmpl1) { x = foldcmp_int(op - Ocmpl, /*w=*/1, l, r); } else if (op >= Ocmps && op <= Ocmps1) { x = foldcmp_flt(op - Ocmps, cl->bits.s, cr->bits.s); } else if (op >= Ocmpd && op <= Ocmpd1) { x = foldcmp_flt(op - Ocmpd, cl->bits.d, cr->bits.d); } else { // Shift count mask. const uint64_t shmask = static_cast(31 | (w << 5)); switch (op) { case Oadd: x = l.u + r.u; break; case Osub: x = l.u - r.u; break; case Oneg: x = -l.u; break; case Omul: x = l.u * r.u; break; case Odiv: x = w ? static_cast(l.s / r.s) : static_cast(static_cast(l.s) / static_cast(r.s)); break; case Orem: x = w ? static_cast(l.s % r.s) : static_cast(static_cast(l.s) % static_cast(r.s)); break; case Oudiv: x = w ? l.u / r.u : static_cast(l.u) / static_cast(r.u); break; case Ourem: x = w ? l.u % r.u : static_cast(l.u) % static_cast(r.u); break; case Oand: x = l.u & r.u; break; case Oor: x = l.u | r.u; break; case Oxor: x = l.u ^ r.u; break; // Signed right shift. case Osar: x = static_cast((w ? l.s : static_cast(l.s)) >> (r.u & shmask)); break; case Oshr: x = (w ? l.u : static_cast(l.u)) >> (r.u & shmask); break; case Oshl: x = l.u << (r.u & shmask); break; case Oextsb: x = static_cast(static_cast(l.u)); break; case Oextub: x = static_cast(l.u); break; case Oextsh: x = static_cast(static_cast(l.u)); break; case Oextuh: x = static_cast(l.u); break; case Oextsw: x = static_cast(static_cast(l.u)); break; case Oextuw: x = static_cast(l.u); break; case Ostosi: x = w ? static_cast(static_cast(cl->bits.s)) : static_cast(static_cast(cl->bits.s)); break; case Ostoui: x = w ? static_cast(cl->bits.s) : static_cast(static_cast(cl->bits.s)); break; case Odtosi: x = w ? static_cast(static_cast(cl->bits.d)) : static_cast(static_cast(cl->bits.d)); break; case Odtoui: x = w ? static_cast(cl->bits.d) : static_cast(static_cast(cl->bits.d)); break; case Ocast: x = l.u; break; default: die("foldint: unsupported op %d", op); } } // Write back. res->type = static_casttype)>(ctyp); res->sym = sym; std::memset(&res->bits, 0, sizeof res->bits); res->bits.i = static_cast(x); res->flt = 0; return 0; } namespace { Ref opfold(Ctx* ctx, int op, int cls, Con* cl, Con* cr, Fn* fn) { Con c; std::memset(&c, 0, sizeof c); if (cls == Kw || cls == Kl) { if (foldint(&c, op, cls == Kl, cl, cr)) return Ref::none(); } else { foldflt(&c, op, cls == Kd, cl, cr); } if (!KWIDE(cls)) c.bits.i &= 0xffffffff; Ref r = newcon(ctx, &c, fn); assert((cls != Ks && cls != Kd) || c.flt); return r; } } // namespace Ref foldref(Ctx* ctx, Fn* fn, Ins* i) { if (rtype(i->to) != RTmp) return Ref::none(); if (!optab[i->op].canfold) return Ref::none(); if (rtype(i->arg[0]) != RCon) return Ref::none(); Ref rr = i->arg[1]; if (req(rr, Ref::none())) rr = Ref::con_z(); if (rtype(rr) != RCon) return Ref::none(); Con* cl = &fn->con[i->arg[0].val]; Con* cr = &fn->con[rr.val]; return opfold(ctx, i->op, i->cls, cl, cr, fn); } } // namespace rcc