/* 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 mulopt.cc /// @brief Constant integer multiplication strength reduction. /// /// See docs/mulopt.md for architectural notes. #include "rcc/all.h" #include namespace rcc { namespace { enum Kind { KZero, KCopy, KShl, KShlAddShl, KShlSubShl, }; struct Rewrite { Kind kind; int sh1; // Primary shift for KShl / KShlAddShl / KShlSubShl int sh2; // Secondary shift for KShlAddShl / KShlSubShl (0 means use x itself) }; // Classify patterns. bool classify(uint64_t ac, int max_shift, Rewrite& out) { if (ac == 0) { out.kind = KZero; return true; } if (ac == 1) { out.kind = KCopy; return true; } if ((ac & (ac - 1)) == 0) { int k = __builtin_ctzll(ac); if (k >= max_shift) return false; out.kind = KShl; out.sh1 = k; return true; } int a = 63 - __builtin_clzll(ac); // floor log2(ac), 0 < ac, ac is not pow2 if (a >= max_shift) return false; uint64_t hi = static_cast(1) << a; // pow2 + pow2. uint64_t low = ac - hi; if ((low & (low - 1)) == 0) { int b = __builtin_ctzll(low); out.kind = KShlAddShl; out.sh1 = a; out.sh2 = b; return true; } // pow2 - pow2. if (a + 1 < max_shift) { uint64_t hi2 = hi << 1; uint64_t diff = hi2 - ac; // Check diff. if ((diff & (diff - 1)) == 0) { int b = __builtin_ctzll(diff); out.kind = KShlSubShl; out.sh1 = a + 1; out.sh2 = b; return true; } } return false; } // emit() prepends. bool lower_mul(Ctx* ctx, Fn* fn, Ins* i, Blk* b, bool* rebuilt) { if (i->op != Omul) return false; if (i->cls != Kw && i->cls != Kl) return false; if (rtype(i->arg[1]) != RCon) return false; Con* c = &fn->con[i->arg[1].val]; if (c->type != Con::CBits) return false; int cls = i->cls; int64_t cv; int max_shift; if (cls == Kw) { cv = static_cast( c->bits.i); // Narrow to i32 to avoid interference from Kl residual bits max_shift = 32; } else { cv = c->bits.i; max_shift = 64; } bool neg = (cv < 0); uint64_t ac = static_cast(cv); if (neg) ac = -ac; // wrap-around produces 2^31 / 2^63 for INT_MIN / INT64_MIN respectively Rewrite r; if (!classify(ac, max_shift, r)) return false; Ref dst = i->to; Ref x = i->arg[0]; // Move unprocessed tail. 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; } switch (r.kind) { case KZero: // 0 * x = 0. emit(ctx, Ocopy, cls, dst, getcon(ctx, 0, fn), Ref::none()); break; case KCopy: if (neg) emit(ctx, Oneg, cls, dst, x, Ref::none()); else emit(ctx, Ocopy, cls, dst, x, Ref::none()); break; case KShl: { if (neg) { Ref t = newtmp(ctx, "mulopt", cls, fn); emit(ctx, Oneg, cls, dst, t, Ref::none()); emit(ctx, Oshl, cls, t, x, getcon(ctx, r.sh1, fn)); } else { emit(ctx, Oshl, cls, dst, x, getcon(ctx, r.sh1, fn)); } break; } case KShlAddShl: { // sum. Ref sum = neg ? newtmp(ctx, "mulopt", cls, fn) : dst; Ref t1 = newtmp(ctx, "mulopt", cls, fn); Ref t2 = (r.sh2 == 0) ? x : newtmp(ctx, "mulopt", cls, fn); if (neg) emit(ctx, Oneg, cls, dst, sum, Ref::none()); emit(ctx, Oadd, cls, sum, t1, t2); if (r.sh2 != 0) emit(ctx, Oshl, cls, t2, x, getcon(ctx, r.sh2, fn)); emit(ctx, Oshl, cls, t1, x, getcon(ctx, r.sh1, fn)); break; } case KShlSubShl: { Ref diff = neg ? newtmp(ctx, "mulopt", cls, fn) : dst; Ref t1 = newtmp(ctx, "mulopt", cls, fn); Ref t2 = (r.sh2 == 0) ? x : newtmp(ctx, "mulopt", cls, fn); if (neg) emit(ctx, Oneg, cls, dst, diff, Ref::none()); emit(ctx, Osub, cls, diff, t1, t2); if (r.sh2 != 0) emit(ctx, Oshl, cls, t2, x, getcon(ctx, r.sh2, fn)); emit(ctx, Oshl, cls, t1, x, getcon(ctx, r.sh1, fn)); break; } } return true; } } // namespace void mulopt(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; if (lower_mul(ctx, fn, i, b, &rebuilt)) continue; if (rebuilt) emiti(ctx, *i); } if (rebuilt) idup(ctx, b, ctx->curi, static_cast(ctx->insb + NIns - ctx->curi)); } } } // namespace rcc