/* 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 divopt.cc /// @brief Constant integer division/modulo strength reduction: rewrite `x / c`, `x % c` /// into mul + shift + sign correction sequence when c is a compile-time constant. /// /// See docs/divopt.md for architectural notes. #include "rcc/all.h" #include namespace rcc { namespace { struct Magic { int32_t M; int s; bool need_add; }; Magic compute_magic_s32(uint32_t ad) { const uint32_t t = 0x80000000u; // 2^31 uint32_t anc = t - 1 - t % ad; uint32_t p = 31; uint32_t q1 = t / anc, r1 = t - q1 * anc; uint32_t q2 = t / ad, r2 = t - q2 * ad; uint32_t delta; do { ++p; q1 *= 2; r1 *= 2; if (r1 >= anc) { ++q1; r1 -= anc; } q2 *= 2; r2 *= 2; if (r2 >= ad) { ++q2; r2 -= ad; } delta = ad - r2; } while (q1 < delta || (q1 == delta && r1 == 0)); Magic m; uint32_t M_u = q2 + 1; m.M = static_cast(M_u); m.s = static_cast(p - 32); m.need_add = (M_u >= 0x80000000u); return m; } void emit_pow2_div_into(Ctx* ctx, Fn* fn, Ref dst, Ref x, int k) { Ref sign = newtmp(ctx, "divopt", Kw, fn); Ref mask = newtmp(ctx, "divopt", Kw, fn); Ref tmp = newtmp(ctx, "divopt", Kw, fn); emit(ctx, Osar, Kw, dst, tmp, getcon(ctx, k, fn)); emit(ctx, Oadd, Kw, tmp, x, mask); emit(ctx, Oshr, Kw, mask, sign, getcon(ctx, 32 - k, fn)); emit(ctx, Osar, Kw, sign, x, getcon(ctx, 31, fn)); } void emit_magic_div_into(Ctx* ctx, Fn* fn, Ref dst, Ref x, int32_t d_abs, bool d_neg) { Magic m = compute_magic_s32(static_cast(d_abs)); int64_t M64 = static_cast(m.M); // Sign extend i32 to i64 Ref xl = newtmp(ctx, "divopt", Kl, fn); Ref prod = newtmp(ctx, "divopt", Kl, fn); Ref hi = newtmp(ctx, "divopt", Kl, fn); Ref hi2 = m.need_add ? newtmp(ctx, "divopt", Kl, fn) : hi; Ref q1 = (m.s > 0) ? newtmp(ctx, "divopt", Kl, fn) : hi2; Ref sg = newtmp(ctx, "divopt", Kl, fn); Ref qpre = d_neg ? newtmp(ctx, "divopt", Kw, fn) : dst; // Reverse emit: if (d_neg) emit(ctx, Oneg, Kw, dst, qpre, Ref::none()); emit(ctx, Osub, Kw, qpre, q1, sg); emit(ctx, Osar, Kl, sg, xl, getcon(ctx, 63, fn)); if (m.s > 0) emit(ctx, Osar, Kl, q1, hi2, getcon(ctx, m.s, fn)); if (m.need_add) emit(ctx, Oadd, Kl, hi2, hi, xl); emit(ctx, Osar, Kl, hi, prod, getcon(ctx, 32, fn)); emit(ctx, Omul, Kl, prod, xl, getcon(ctx, M64, fn)); emit(ctx, Oextsw, Kl, xl, x, Ref::none()); } bool lower_div(Ctx* ctx, Fn* fn, Ins* i, Blk* b, bool* rebuilt) { if (i->op != Odiv && i->op != Orem) return false; if (i->cls != Kw) 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; int32_t d = static_cast(c->bits.i); if (d == 0 || d == static_cast(0x80000000)) return false; // Skip d=0 (leave to hardware) and d=INT_MIN (|d| overflows i32) bool is_rem = (i->op == Orem); Ref dst = i->to; Ref x = i->arg[0]; // First hit: move unprocessed tail to end of insb (same as simpl.cc) 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; } // d = +-1 degenerate: rem -> 0; div(1)=x; div(-1)=-x. if (d == 1 || d == -1) { if (is_rem) emit(ctx, Ocopy, Kw, dst, getcon(ctx, 0, fn), Ref::none()); else if (d == 1) emit(ctx, Ocopy, Kw, dst, x, Ref::none()); else emit(ctx, Oneg, Kw, dst, x, Ref::none()); return true; } // For rem: emit tail r = x - q*d first (reverse order), then div sequence. Ref q_dst = is_rem ? newtmp(ctx, "divopt", Kw, fn) : dst; if (is_rem) { Ref qd = newtmp(ctx, "divopt", Kw, fn); emit(ctx, Osub, Kw, dst, x, qd); emit(ctx, Omul, Kw, qd, q_dst, getcon(ctx, static_cast(d), fn)); } int32_t ad = (d < 0) ? -d : d; if ((ad & (ad - 1)) == 0) { // |d| is power of 2, k >= 1 int k = __builtin_ctz(static_cast(ad)); if (d < 0) { Ref q_pos = newtmp(ctx, "divopt", Kw, fn); emit(ctx, Oneg, Kw, q_dst, q_pos, Ref::none()); emit_pow2_div_into(ctx, fn, q_pos, x, k); } else { emit_pow2_div_into(ctx, fn, q_dst, x, k); } } else { emit_magic_div_into(ctx, fn, q_dst, x, ad, d < 0); } return true; } } // namespace void divopt(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_div(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