/* 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 util.cc /// @brief Infrastructure: Memory management, growable arrays. #include "rcc/all.h" #include #include namespace rcc { // Global Read-Only Resources Typ* typ; Target T = {}; char debug['Z' + 1] = {}; Op optab[NOp]; namespace { struct OptabInit { OptabInit() { for (size_t i = 0; i < NOp; ++i) { const OpInfo& info = kops[i]; Op& o = optab[i]; o.name = const_cast(info.name); for (int a = 0; a < 2; ++a) for (int b = 0; b < 4; ++b) o.argcls[a][b] = info.argcls[a][b]; o.canfold = (info.flags & Fold) != 0; o.hasid = (info.flags & Id) != 0; o.idval = (info.flags & IdV) != 0; o.commutes = (info.flags & Comm) != 0; o.assoc = (info.flags & Assoc) != 0; o.idemp = (info.flags & Idemp) != 0; o.cmpeqwl = (info.flags & CmpEW) != 0; o.cmplgtewl = (info.flags & CmpLW) != 0; o.eqval = (info.flags & EqV) != 0; o.pinned = (info.flags & Pin) != 0; } } } optab_init_; } // namespace // SubPool: per-Fn Memory Pool void* subpool_alloc(SubPool* sp, size_t n) { if (n == 0) return nullptr; void* p = emalloc(n); sp->chunks.push_back(p); return p; } void subpool_free(SubPool* sp) { for (void* p : sp->chunks) free(p); sp->chunks.clear(); } // Ctx Lifecycle void* emalloc(size_t n) { void* p = calloc(1, n); if (!p) die("emalloc, out of memory"); return p; } void Ctx::init() { assert(!insb && "Ctx::init called twice"); insb = static_cast(emalloc(NIns * sizeof(Ins))); curi = insb + NIns; } void Ctx::bind(Fn* f) { assert(f && f->_subpool && "bind requires fn with attached SubPool"); subpool = f->_subpool; curi = insb + NIns; load_curf = nullptr; load_inum = 0; load_ilog = nullptr; load_nlog = 0; ssa_namel = nullptr; gvn_con01[0] = Ref::none(); gvn_con01[1] = Ref::none(); gvn_tbl = nullptr; gvn_tbln = 0; rega_regu = 0; rega_tmp = nullptr; rega_npm = 0; rega_loop = 0; rega_stmov = 0; rega_stblk = 0; emit_stash = nullptr; spill_fp_live_peak = 0; fn = f; } void Ctx::release() { if (subpool) { subpool_free(subpool); delete subpool; subpool = nullptr; } fn = nullptr; } Ctx::~Ctx() { if (subpool) { subpool_free(subpool); delete subpool; } if (insb) free(insb); if (out_buf) fclose(out_buf); if (dbg_buf) fclose(dbg_buf); // ssa.cc Name pool is allocated via emalloc and reused across functions, ~Ctx must drain it. ssa_drain_namel(this); } // alloc / freeall void* alloc(Ctx* ctx, size_t n) { assert(ctx && ctx->subpool); return subpool_alloc(ctx->subpool, n); } void freeall(Ctx* ctx) { assert(ctx && ctx->subpool); subpool_free(ctx->subpool); } // Growable Arrays (vnew / vgrow / vfree) struct ArrayHdr { ulong magic; ulong cap; size_t esz; Pool pool; }; static constexpr ulong AHDR_MAGIC = 0xA77A7007u; static constexpr ulong VMIN_CAP = 2; /// Retreat from user pointer to header static ArrayHdr* hdr_of(void* p) { auto* h = static_cast(p) - 1; assert(h->magic == AHDR_MAGIC); return h; } void* vnew(Ctx* ctx, ulong len, size_t esz, Pool pl) { ulong cap = VMIN_CAP; while (cap < len) cap *= 2; size_t total = sizeof(ArrayHdr) + cap * esz; void* raw = (pl == PHeap) ? emalloc(total) : alloc(ctx, total); auto* h = static_cast(raw); h->magic = AHDR_MAGIC; h->cap = cap; h->esz = esz; h->pool = pl; return h + 1; } void vfree(void* p) { ArrayHdr* h = hdr_of(p); if (h->pool == PHeap) { h->magic = 0; free(h); } } void vgrow(Ctx* ctx, void* vp, ulong len) { void** pp = static_cast(vp); ArrayHdr* h = hdr_of(*pp); if (h->cap >= len) return; void* nv = vnew(ctx, len, h->esz, h->pool); memcpy(nv, *pp, h->cap * h->esz); vfree(*pp); *pp = nv; } // String Pool (intern / str) uint32_t hash(const char* s) { uint32_t h = 5381; for (; *s; s++) h = ((h << 5) + h) ^ static_cast(*s); // djb2 xor variant return h; } /// Hash table slot struct StrSlot { uint32_t hash; uint32_t id; }; static constexpr uint32_t SLOT_EMPTY = UINT32_MAX; static constexpr size_t HTAB_INIT = 1024; static std::vector char_buf; // Continuous character storage static std::vector id_to_off; // ID -> char_buf offset static std::vector htab; // Open addressing hash table static uint32_t htab_used; // Number of occupied slots /// Initialize hash table (triggered automatically on first intern call) static void htab_init() { htab.assign(HTAB_INIT, {SLOT_EMPTY, 0}); htab_used = 0; } /// Grow and rehash when load factor exceeds 70% static void htab_grow() { size_t new_sz = htab.size() * 2; std::vector old; old.swap(htab); htab.assign(new_sz, {SLOT_EMPTY, 0}); uint32_t mask = static_cast(new_sz - 1); for (auto& s : old) { if (s.hash == SLOT_EMPTY) continue; uint32_t pos = s.hash & mask; while (htab[pos].hash != SLOT_EMPTY) pos = (pos + 1) & mask; htab[pos] = s; } } uint32_t intern(const char* s) { if (htab.empty()) htab_init(); uint32_t h = hash(s); uint32_t mask = static_cast(htab.size() - 1); uint32_t pos = h & mask; // Linear probing search while (htab[pos].hash != SLOT_EMPTY) { if (htab[pos].hash == h) { // hash identical, compare actual string uint32_t off = id_to_off[htab[pos].id]; if (strcmp(s, char_buf.data() + off) == 0) return htab[pos].id; } pos = (pos + 1) & mask; } // Not found, insert new string uint32_t new_id = static_cast(id_to_off.size()); uint32_t off = static_cast(char_buf.size()); size_t len = strlen(s); char_buf.insert(char_buf.end(), s, s + len + 1); // Includes '\0' id_to_off.push_back(off); htab[pos] = {h, new_id}; htab_used++; // Load factor check if (htab_used * 10 > htab.size() * 7) htab_grow(); return new_id; } char* str(uint32_t id) { assert(id < id_to_off.size()); return char_buf.data() + id_to_off[id]; } // strf: Format string and allocate to specified memory pool char* strf(Ctx* ctx, Pool pl, const char* fmt, ...) { va_list ap, ap2; va_start(ap, fmt); va_copy(ap2, ap); int n = vsnprintf(nullptr, 0, fmt, ap); va_end(ap); char* p = static_cast((pl == PFn) ? alloc(ctx, static_cast(n + 1)) : emalloc(static_cast(n + 1))); vsnprintf(p, static_cast(n + 1), fmt, ap2); va_end(ap2); return p; } // die // die context handover (emit phase has no explicit Ctx*, bypassed via thread_local) static thread_local Ctx* tl_die_ctx = nullptr; void set_die_ctx(Ctx* c) { tl_die_ctx = c; } const char* die_ctx_str(Ctx* ctx, Ins* cur) { static thread_local char buf[2048]; char* p = buf; char* end = buf + sizeof(buf); if (!ctx) ctx = tl_die_ctx; if (!ctx || !ctx->fn) { snprintf(buf, sizeof(buf), "no_fn"); return buf; } Fn* fn = ctx->fn; int fp_par = 0, gp_par = 0; if (fn->start) for (uint j = 0; j < fn->start->nins; ++j) { Ins* ii = &fn->start->ins[j]; if (ii->op != Ocopy || !isreg(ii->arg[0])) break; if (KBASE(ii->cls) == 1) ++fp_par; else ++gp_par; } int ncall = 0, max_loop = 0; for (Blk* b = fn->start; b; b = b->link) { if (b->loop > max_loop) max_loop = b->loop; for (uint j = 0; j < b->nins; ++j) if (b->ins[j].op == Ocall) ++ncall; } p += snprintf(p, static_cast(end - p), "nblk=%u ntmp=%d ncon=%d slot=%d fpar=%d gpar=%d ncall=%d mxloop=%d fplive=%d", fn->nblk, fn->ntmp, fn->ncon, fn->slot, fp_par, gp_par, ncall, max_loop, ctx->spill_fp_live_peak); if (!cur) return buf; Blk* host = nullptr; uint pos = 0; for (Blk* b = fn->start; b; b = b->link) { if (cur >= b->ins && cur < b->ins + b->nins) { host = b; pos = static_cast(cur - b->ins); break; } } if (!host) { snprintf(p, static_cast(end - p), " host=none"); return buf; } p += snprintf(p, static_cast(end - p), " blk%u/L%d nins=%u idx=%u ", host->id, host->loop, host->nins, pos); uint lo = pos > 10 ? pos - 10 : 0; uint hi = pos + 10 < host->nins ? pos + 10 : host->nins - 1; for (uint j = lo; j <= hi && p < end - 32; ++j) { Ins* ii = &host->ins[j]; p += snprintf(p, static_cast(end - p), "%s%d/%c,", j == pos ? "*" : "", ii->op, "wlsd"[ii->cls]); } return buf; } [[noreturn]] void die_(const char* file, const char* s, ...) { va_list ap; fprintf(stderr, "%s: dying: ", file); va_start(ap, s); vfprintf(stderr, s, ap); va_end(ap); fputc('\n', stderr); abort(); } [[noreturn]] void err(char* s, ...) { va_list ap; fprintf(stderr, "rcc: error: "); va_start(ap, s); vfprintf(stderr, s, ap); va_end(ap); fputc('\n', stderr); abort(); } // BSet Bitset Operations void bsinit(Ctx* ctx, BSet* bs, uint n) { n = (n + NBit - 1) / NBit; bs->nt = n; bs->t = static_cast(alloc(ctx, n * sizeof(bits))); } void bszero(BSet* bs) { memset(bs->t, 0, bs->nt * sizeof(bits)); } uint bscount(BSet* bs) { uint c = 0; for (uint i = 0; i < bs->nt; i++) { bits b = bs->t[i]; // Bitwise count: Hamming Weight (divide and conquer) b = b - ((b >> 1) & 0x5555555555555555ULL); b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL); b = (b + (b >> 4)) & 0x0f0f0f0f0f0f0f0fULL; c += static_cast((b * 0x0101010101010101ULL) >> 56); } return c; } void bsset(BSet* bs, uint elt) { assert(elt < bs->nt * NBit); bs->t[elt / NBit] |= BIT(elt % NBit); } void bsclr(BSet* bs, uint elt) { assert(elt < bs->nt * NBit); bs->t[elt / NBit] &= ~BIT(elt % NBit); } void bscopy(BSet* a, BSet* b) { assert(a->nt == b->nt); memcpy(a->t, b->t, a->nt * sizeof(bits)); } void bsunion(BSet* a, BSet* b) { assert(a->nt == b->nt); for (uint i = 0; i < a->nt; i++) a->t[i] |= b->t[i]; } void bsinter(BSet* a, BSet* b) { assert(a->nt == b->nt); for (uint i = 0; i < a->nt; i++) a->t[i] &= b->t[i]; } void bsdiff(BSet* a, BSet* b) { assert(a->nt == b->nt); for (uint i = 0; i < a->nt; i++) a->t[i] &= ~b->t[i]; } int bsequal(BSet* a, BSet* b) { assert(a->nt == b->nt); for (uint i = 0; i < a->nt; i++) if (a->t[i] != b->t[i]) return 0; return 1; } /// Find index of lowest set bit in 64-bit word. For single bit (pow2) input, equivalent to log2. int lowest_bit(bits b) { // De Bruijn sequence method static const int debruijn[64] = { 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, }; return debruijn[((b & (~b + 1)) * 0x022fdd63cc95386dULL) >> 58]; } int bsiter(BSet* bs, int* elt) { uint i = static_cast(*elt); uint t = i / NBit; if (t >= bs->nt) return 0; bits b = bs->t[t] & ~(BIT(i % NBit) - 1); while (!b) { if (++t >= bs->nt) return 0; b = bs->t[t]; } *elt = static_cast(NBit * t + static_cast(lowest_bit(b))); return 1; } // Instruction Operation Helpers void addins(Ctx* ctx, Ins** pi, uint* ni, Ins* ins) { if (ins->op == Onop) return; vgrow(ctx, pi, *ni + 1); (*pi)[*ni] = *ins; (*ni)++; } void addbins(Ctx* ctx, Ins** pi, uint* ni, Blk* b) { for (uint j = 0; j < b->nins; j++) addins(ctx, pi, ni, &b->ins[j]); } void emit(Ctx* ctx, int op, int k, Ref to, Ref a0, Ref a1) { if (ctx->curi == ctx->insb) die("emit, too many instructions (NIns=%d) op=%d cls=%d " "to=t%d/v%u a0=t%d/v%u a1=t%d/v%u || %s", NIns, op, k, rtype(to), to.val, rtype(a0), a0.val, rtype(a1), a1.val, die_ctx_str(ctx, nullptr)); --ctx->curi; ctx->curi->op = static_cast(op); ctx->curi->cls = static_cast(k); ctx->curi->to = to; ctx->curi->arg[0] = a0; ctx->curi->arg[1] = a1; } void emiti(Ctx* ctx, Ins i) { emit(ctx, i.op, i.cls, i.to, i.arg[0], i.arg[1]); } Ins* icpy(Ins* dst, Ins* src, ulong n) { if (n) memmove(dst, src, n * sizeof(Ins)); return dst + n; } void idup(Ctx* ctx, Blk* b, Ins* src, ulong n) { vgrow(ctx, &b->ins, n); icpy(b->ins, src, n); b->nins = static_cast(n); } void igroup(Blk* b, Ins* i, Ins** i0, Ins** i1) { Ins* ib = b->ins; Ins* ie = ib + b->nins; switch (i->op) { case Oblit0: *i0 = i; *i1 = i + 2; return; case Oblit1: *i0 = i - 1; *i1 = i + 1; return; case Osel0: case Osel1: // Backtrack to the leading sel0 from anywhere in the group, then consume consecutive // sel1s forward while (i->op == Osel1) --i; assert(i->op == Osel0); *i0 = i; for (++i; i < ie && i->op == Osel1; ++i) ; *i1 = i; return; } if (ispar(i->op)) { while (i > ib && ispar((i - 1)->op)) --i; *i0 = i; while (i < ie && ispar(i->op)) ++i; *i1 = i; } else if (i->op == Ocall || isarg(i->op)) { while (i > ib && isarg((i - 1)->op)) --i; *i0 = i; while (i < ie && i->op != Ocall) ++i; assert(i < ie); *i1 = i + 1; } else { *i0 = i; *i1 = i + 1; } } int argcls(Ins* i, int n) { return optab[i->op].argcls[n][i->cls]; } // Tmp / Con / Comparison Helpers Ref newtmp(Ctx* ctx, const char* prfx, int k, Fn* fn) { static int cnt; int t = fn->ntmp++; vgrow(ctx, &fn->tmp, static_cast(fn->ntmp)); memset(&fn->tmp[t], 0, sizeof(Tmp)); if (prfx) fn->tmp[t].name = strf(ctx, PFn, "%s.%d", prfx, ++cnt); fn->tmp[t].cls = static_cast(k); fn->tmp[t].slot = -1; fn->tmp[t].nuse = 1; fn->tmp[t].ndef = 1; return Ref::tmp(static_cast(t)); } void chuse(Ctx*, Ref r, int du, Fn* fn) { if (rtype(r) == RTmp) fn->tmp[r.val].nuse += static_cast(du); } int symeq(Sym a, Sym b) { return a.type == b.type && a.id == b.id; } Ref newcon(Ctx* ctx, Con* c0, Fn* fn) { for (int i = 1; i < fn->ncon; i++) { Con* c1 = &fn->con[i]; if (c0->type == c1->type && symeq(c0->sym, c1->sym) && c0->bits.i == c1->bits.i) return Ref::con(static_cast(i)); } int i = fn->ncon++; vgrow(ctx, &fn->con, static_cast(fn->ncon)); fn->con[i] = *c0; return Ref::con(static_cast(i)); } Ref getcon(Ctx* ctx, int64_t val, Fn* fn) { for (int i = 1; i < fn->ncon; i++) if (fn->con[i].type == Con::CBits && fn->con[i].bits.i == val) return Ref::con(static_cast(i)); int i = fn->ncon++; vgrow(ctx, &fn->con, static_cast(fn->ncon)); memset(&fn->con[i], 0, sizeof(Con)); fn->con[i].type = Con::CBits; fn->con[i].bits.i = val; return Ref::con(static_cast(i)); } int addcon(Con* c0, Con* c1, int m) { if (m != 1 && c1->type == Con::CAddr) return 0; if (c0->type == Con::CUndef) { *c0 = *c1; c0->bits.i *= m; } else { if (c1->type == Con::CAddr) { if (c0->type == Con::CAddr) return 0; c0->type = Con::CAddr; c0->sym = c1->sym; } c0->bits.i += c1->bits.i * m; } return 1; } int isconbits(Fn* fn, Ref r, int64_t* v) { if (rtype(r) == RCon) { Con* c = &fn->con[r.val]; if (c->type == Con::CBits) { *v = c->bits.i; return 1; } } return 0; } int iscmp(int op, int* pk, int* pc) { if (Ocmpw <= op && op <= Ocmpw1) { *pc = op - Ocmpw; *pk = Kw; } else if (Ocmpl <= op && op <= Ocmpl1) { *pc = op - Ocmpl; *pk = Kl; } else if (Ocmps <= op && op <= Ocmps1) { *pc = NCmpI + op - Ocmps; *pk = Ks; } else if (Ocmpd <= op && op <= Ocmpd1) { *pc = NCmpI + op - Ocmpd; *pk = Kd; } else return 0; return 1; } /// Negation / Swap table for comparison operations static const int cmptab[][2] = { /* negate swap */ /* Cieq */ {Cine, Cieq}, /* Cine */ {Cieq, Cine}, /* Cisge */ {Cislt, Cisle}, /* Cisgt */ {Cisle, Cislt}, /* Cisle */ {Cisgt, Cisge}, /* Cislt */ {Cisge, Cisgt}, /* Ciuge */ {Ciult, Ciule}, /* Ciugt */ {Ciule, Ciult}, /* Ciule */ {Ciugt, Ciuge}, /* Ciult */ {Ciuge, Ciugt}, }; static const int fcmptab[][2] = { /* negate swap */ /* Cfeq */ {-1, Cfeq}, /* Cfge */ {-1, Cfle}, /* Cfgt */ {-1, Cflt}, /* Cfle */ {-1, Cfge}, /* Cflt */ {-1, Cfgt}, /* Cfne */ {-1, Cfne}, /* Cfo */ {-1, Cfo}, /* Cfuo */ {-1, Cfuo}, }; int cmpop(int c) { assert(0 <= c && c < NCmp); if (c < NCmpI) return cmptab[c][1]; return NCmpI + fcmptab[c - NCmpI][1]; } int cmpwlneg(int op) { if (INRANGE(op, Ocmpw, Ocmpw1)) return cmptab[op - Ocmpw][0] + Ocmpw; if (INRANGE(op, Ocmpl, Ocmpl1)) return cmptab[op - Ocmpl][0] + Ocmpl; die("not a wl comparison"); } int clsmerge(short* pk, short k) { short k1 = *pk; if (k1 == Kx) { *pk = k; return 0; } if ((k1 == Kw && k == Kl) || (k1 == Kl && k == Kw)) { *pk = Kw; return 0; } return k1 != k; } int phicls(int t, Tmp* tmp) { int t1 = tmp[t].phi; if (!t1) return t; t1 = phicls(t1, tmp); tmp[t].phi = t1; return t1; } uint phiargn(Phi* p, Blk* b) { if (p) for (uint n = 0; n < p->narg; n++) if (p->blk[n] == b) return n; return ~0u; } Ref phiarg(Phi* p, Blk* b) { uint n = phiargn(p, b); assert(n != ~0u && "block not found"); return p->arg[n]; } void dumpts(BSet* bs, Tmp* tmp, FILE* f) { int t; fprintf(f, "["); for (t = Tmp0; bsiter(bs, &t); t++) fprintf(f, " %s", tmp[t].name); fprintf(f, " ]\n"); } } // namespace rcc