/* 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 reassoc.cc /// @brief Reassociation pass (LLVM rank-based style). /// /// See docs/reassoc.md for architectural notes. #include "rcc/all.h" #include #include #include #include #include #include #include namespace rcc { namespace { enum Family { FAdd, FMul, FAnd, FOr, FXor, FNone }; Family family_of(uint op) { switch (op) { case Oadd: case Osub: return FAdd; case Omul: return FMul; case Oand: return FAnd; case Oor: return FOr; case Oxor: return FXor; default: return FNone; } } // Binary op for the family. int family_binop(Family f) { switch (f) { case FAdd: return Oadd; case FMul: return Omul; case FAnd: return Oand; case FOr: return Oor; case FXor: return Oxor; default: return Onop; } } struct Leaf { Ref ref; int sign; // Only meaningful for FAdd, otherwise always +1 }; struct Reassoc { Ctx* ctx; Fn* fn; std::unordered_set absorbed; std::unordered_map> prelude; // root ins -> intermediate instructions in chain int n_trees = 0, n_folded = 0, n_addsub = 0, n_chainmax = 0; bool is_family_node(Ins* d, Family fam, int cls) { return d && family_of(d->op) == fam && static_cast(d->cls) == cls; } int64_t mask_val(int cls, int64_t v) { return (cls == Kw) ? static_cast(static_cast(v)) : v; } // Flatten nodes. void collect(Ins* root, Family fam, int cls, std::vector& out) { std::vector stk; if (fam == FAdd) { stk.push_back({root->arg[0], +1}); stk.push_back({root->arg[1], (root->op == Osub) ? -1 : +1}); } else { stk.push_back({root->arg[0], +1}); stk.push_back({root->arg[1], +1}); } while (!stk.empty()) { Leaf cur = stk.back(); stk.pop_back(); if (rtype(cur.ref) == RTmp && cur.ref.val != 0) { Tmp* t = &fn->tmp[cur.ref.val]; Ins* d = t->def; if (d && t->nuse == 1 && is_family_node(d, fam, cls) && !absorbed.count(d)) { absorbed.insert(d); if (fam == FAdd) { stk.push_back({d->arg[0], cur.sign}); stk.push_back({d->arg[1], (d->op == Osub) ? -cur.sign : cur.sign}); } else { stk.push_back({d->arg[0], cur.sign}); stk.push_back({d->arg[1], cur.sign}); } continue; } } out.push_back(cur); } } // rank nodes. static uint64_t rank_of(Ref r) { if (rtype(r) == RCon) return (static_cast(UINT32_MAX) << 32) | r.val; return (static_cast(r.val) << 32) | r.val; } void sort_terms(std::vector& v) { std::sort(v.begin(), v.end(), [](const Leaf& a, const Leaf& b) { return rank_of(a.ref) < rank_of(b.ref); }); } // Build left-leaning chain. Ref build_full(const std::vector& refs, int bop, int cls, std::vector& pre) { Ref acc = refs[0]; for (size_t k = 1; k < refs.size(); k++) { Ref t = newtmp(ctx, "rea", cls, fn); Ins ni; ni.op = static_cast(bop); ni.cls = static_cast(cls); ni.to = t; ni.arg[0] = acc; ni.arg[1] = refs[k]; pre.push_back(ni); acc = t; } return acc; } void make_copy(Ins* root, Ref r) { root->op = Ocopy; root->arg[0] = r; root->arg[1] = Ref::none(); } void make_bin(Ins* root, int op, Ref a0, Ref a1) { root->op = static_cast(op); root->arg[0] = a0; root->arg[1] = a1; } // Add/Sub Reconstruction void rebuild_addsub(Ins* root, int cls, std::vector& leaves) { // Constant folding. int64_t cacc = 0; std::vector terms; int nconst = 0; for (const Leaf& lf : leaves) { int64_t v; if (isconbits(fn, lf.ref, &v)) { cacc += lf.sign * v; nconst++; } else { terms.push_back(lf); } } cacc = mask_val(cls, cacc); if (nconst >= 2) n_folded++; sort_terms(terms); // Net sum of signs. std::vector canon; for (size_t k = 0; k < terms.size();) { size_t j = k; int64_t net = 0; while (j < terms.size() && req(terms[j].ref, terms[k].ref)) { net += terms[j].sign; j++; } int s = (net > 0) ? +1 : -1; for (int64_t c = 0; c < (net < 0 ? -net : net); c++) canon.push_back({terms[k].ref, s}); k = j; } // Merge constants. std::vector pos, neg; for (const Leaf& lf : canon) { if (lf.sign > 0) pos.push_back(lf.ref); else neg.push_back(lf.ref); } if (cacc != 0) pos.push_back(getcon(ctx, cacc, fn)); n_addsub++; if (pos.empty() && neg.empty()) { make_copy(root, getcon(ctx, cacc, fn)); // cacc==0 here -> copy 0 return; } std::vector& pre = prelude[root]; if (neg.empty()) { if (pos.size() == 1) { make_copy(root, pos[0]); } else { Ref acc = build_full(std::vector(pos.begin(), pos.end() - 1), Oadd, cls, pre); make_bin(root, Oadd, acc, pos.back()); } } else if (pos.empty()) { Ref negr = build_full(neg, Oadd, cls, pre); make_bin(root, Oneg, negr, Ref::none()); } else { Ref posr = build_full(pos, Oadd, cls, pre); Ref negr = build_full(neg, Oadd, cls, pre); make_bin(root, Osub, posr, negr); } size_t len = pos.size() + neg.size(); if (static_cast(len) > n_chainmax) n_chainmax = static_cast(len); } // Mul/And/Or/Xor Reconstruction void rebuild_logic(Ins* root, Family fam, int cls, std::vector& leaves) { int bop = family_binop(fam); int64_t mask = (cls == Kw) ? 0xffffffffLL : ~0LL; int64_t allones = mask; // Identity starting point int64_t acc; switch (fam) { case FMul: acc = 1; break; case FAnd: acc = allones; break; default: acc = 0; // Or / Xor break; } int nconst = 0; std::vector terms; for (const Leaf& lf : leaves) { int64_t v; if (isconbits(fn, lf.ref, &v)) { nconst++; v &= mask; switch (fam) { case FMul: acc *= v; break; case FAnd: acc &= v; break; case FOr: acc |= v; break; default: acc ^= v; break; } } else { terms.push_back(lf); } } acc &= mask; if (nconst >= 2) n_folded++; // Short circuit. if ((fam == FMul && acc == 0 && nconst > 0) || (fam == FAnd && acc == 0 && nconst > 0)) { make_copy(root, getcon(ctx, 0, fn)); return; } if (fam == FOr && acc == allones && nconst > 0) { make_copy(root, getcon(ctx, mask_val(cls, allones), fn)); return; } sort_terms(terms); // Cancellation. std::vector refs; if (fam == FAnd || fam == FOr) { for (size_t k = 0; k < terms.size(); k++) if (k == 0 || !req(terms[k].ref, terms[k - 1].ref)) refs.push_back(terms[k].ref); } else if (fam == FXor) { for (size_t k = 0; k < terms.size();) { size_t j = k; while (j < terms.size() && req(terms[j].ref, terms[k].ref)) j++; if ((j - k) & 1) refs.push_back(terms[k].ref); k = j; } } else { for (const Leaf& lf : terms) refs.push_back(lf.ref); } // Whether to merge constant: only add if not identity. bool const_is_id = (fam == FMul && acc == 1) || (fam == FAnd && acc == allones) || ((fam == FOr || fam == FXor) && acc == 0); if (!const_is_id || refs.empty()) refs.push_back(getcon(ctx, mask_val(cls, acc), fn)); if (refs.size() == 1) { make_copy(root, refs[0]); } else { std::vector& pre = prelude[root]; Ref acc_ref = build_full(std::vector(refs.begin(), refs.end() - 1), bop, cls, pre); make_bin(root, bop, acc_ref, refs.back()); } if (static_cast(refs.size()) > n_chainmax) n_chainmax = static_cast(refs.size()); } void process_root(Ins* root) { Family fam = family_of(root->op); int cls = static_cast(root->cls); size_t before = absorbed.size(); std::vector leaves; collect(root, fam, cls, leaves); size_t delta = absorbed.size() - before; // Act on trees. int nconst = 0; for (const Leaf& lf : leaves) { int64_t v; if (isconbits(fn, lf.ref, &v)) nconst++; } if (delta == 0 && nconst < 2) return; n_trees++; if (fam == FAdd) rebuild_addsub(root, cls, leaves); else rebuild_logic(root, fam, cls, leaves); } void run() { // Collect root candidates. std::vector roots; for (Blk* b = fn->start; b; b = b->link) { for (uint k = 0; k < b->nins; k++) { Ins* i = &b->ins[k]; if (rtype(i->to) != RTmp) continue; Family fam = family_of(i->op); if (fam == FNone) continue; if (KBASE(i->cls) != 0) continue; // integer only // Check root. Tmp* t = &fn->tmp[i->to.val]; if (t->nuse == 1 && t->use && t->use[0].type == Use::UIns) { Ins* parent = t->use[0].u.ins; if (is_family_node(parent, fam, static_cast(i->cls))) continue; } roots.push_back(i); } } // Process each root. for (Ins* r : roots) { if (absorbed.count(r)) continue; process_root(r); } // Mark absorbed intermediate nodes as Onop. for (Ins* a : absorbed) a->op = Onop; // Reconstruct. for (Blk* b = fn->start; b; b = b->link) { Ins* nins = static_cast(vnew(ctx, 0, sizeof(Ins), PFn)); uint cnt = 0; for (uint k = 0; k < b->nins; k++) { Ins* i = &b->ins[k]; if (i->op == Onop) continue; auto it = prelude.find(i); if (it != prelude.end()) for (Ins& p : it->second) addins(ctx, &nins, &cnt, &p); addins(ctx, &nins, &cnt, i); } b->ins = nins; b->nins = cnt; } if (debug['E']) { FILE* out = ctx->dbg_buf ? ctx->dbg_buf : stderr; fprintf(out, "> reassoc %s: trees=%d folded=%d addsub=%d chain_max=%d\n", fn->name, n_trees, n_folded, n_addsub, n_chainmax); } } }; } // namespace void reassoc(Ctx* ctx, Fn* fn) { // Pre/post conditions. Reassoc r; r.ctx = ctx; r.fn = fn; r.run(); } } // namespace rcc