diff options
| author | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-06-08 17:07:31 +0800 |
|---|---|---|
| committer | Richard-Qin-X <richard.qin.001@gmail.com> | 2026-06-08 17:07:31 +0800 |
| commit | 189d77ba3a50a515c0b687ecc2766e1052a03795 (patch) | |
| tree | e682e6992a75544d319be88e8bc3a27c5df557b1 /rcc-legacy | |
| parent | a4a5ad2786a1937addc09d72a375158877fb3b80 (diff) | |
| download | rcc-project-189d77ba3a50a515c0b687ecc2766e1052a03795.tar.gz rcc-project-189d77ba3a50a515c0b687ecc2766e1052a03795.tar.xz | |
[legacy/backend] Add RISC-V 64 instruction selection, regalloc, and emission
Diffstat (limited to 'rcc-legacy')
| -rw-r--r-- | rcc-legacy/src/backend/emit.cc | 304 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/peephole.cc | 146 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/rega.cc | 684 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/rv64/abi.cc | 690 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/rv64/emit.cc | 949 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/rv64/isel.cc | 518 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/rv64/targ.cc | 142 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/simpl.cc | 176 | ||||
| -rw-r--r-- | rcc-legacy/src/backend/spill.cc | 600 |
9 files changed, 4209 insertions, 0 deletions
diff --git a/rcc-legacy/src/backend/emit.cc b/rcc-legacy/src/backend/emit.cc new file mode 100644 index 0000000..6e1f971 --- /dev/null +++ b/rcc-legacy/src/backend/emit.cc @@ -0,0 +1,304 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file backend/emit.cc +/// @brief Target-independent assembly/data/debug section output tools. + +#include "rcc/all.h" + +#include <cinttypes> +#include <cstring> +#include <vector> + +namespace rcc { + +// Section types. + +namespace { + +enum Sec : int { kSecText = 0, kSecData = 1, kSecBss = 2 }; + +// Given (sec, thread), return the name/instruction string after .section. +// When thread=true, SecText is unreachable. +const char* section_name(int sec, bool thread) { + if (!thread) { + switch (sec) { + case kSecText: + return ".text"; + case kSecData: + return ".data"; + case kSecBss: + return ".bss"; + } + } else { + switch (sec) { + case kSecText: + return ".abort \"unreachable\""; + case kSecData: + return ".section .tdata,\"awT\""; + case kSecBss: + return ".section .tbss,\"awT\""; + } + } + return nullptr; // unreachable +} + +// Write ".globl / label / section switch" header. Apple branch has TLV trampolines. +void write_lnk(char* n, Lnk* l, int sec_kind, FILE* f) { + const char* pfx = (n[0] == '"') ? "" : T.assym; + const char* sfx = ""; + + if (T.apple && l->thread) { + l->sec = const_cast<char*>("__DATA"); + l->secf = const_cast<char*>("__thread_data,thread_local_regular"); + sfx = "$tlv$init"; + fputs(".section __DATA,__thread_vars,thread_local_variables\n", f); + fprintf(f, "%s%s:\n", pfx, n); + fprintf(f, + "\t.quad __tlv_bootstrap\n" + "\t.quad 0\n" + "\t.quad %s%s%s\n\n", + pfx, n, sfx); + } + + if (l->sec) { + fprintf(f, ".section %s", l->sec); + if (l->secf) + fprintf(f, ",%s", l->secf); + } else { + fputs(section_name(sec_kind, l->thread != 0), f); + } + fputc('\n', f); + + if (l->align) + fprintf(f, ".balign %d\n", l->align); + if (l->export_) + fprintf(f, ".globl %s%s\n", pfx, n); + fprintf(f, "%s%s%s:\n", pfx, n, sfx); +} + +} // namespace + +void emitfnlnk(char* n, Lnk* l, FILE* f) { + write_lnk(n, l, kSecText, f); +} + +// Data sections. + +namespace { +int64_t g_data_zero = 0; + +struct DataDecl { + const char* decl; + int64_t mask; +}; + +constexpr DataDecl kDataDecl[] = { + /* DStart */ {nullptr, 0}, + /* DEnd */ {nullptr, 0}, + /* DB */ {"\t.byte", 0xffLL}, + /* DH */ {"\t.short", 0xffffLL}, + /* DW */ {"\t.int", 0xffffffffLL}, + /* DL */ {"\t.quad", -1LL}, + /* DZ */ {nullptr, 0}, +}; + +} // namespace + +void emitdat(Dat* d, FILE* f) { + switch (d->type) { + case Dat::DStart: + g_data_zero = 0; + return; + + case Dat::DEnd: + if (d->lnk->common) { + if (g_data_zero == -1) + die("invalid common data definition"); + const char* p = (d->name[0] == '"') ? "" : T.assym; + fprintf(f, ".comm %s%s,%" PRId64, p, d->name, g_data_zero); + if (d->lnk->align) + fprintf(f, ",%d", d->lnk->align); + fputc('\n', f); + } else if (g_data_zero != -1) { + write_lnk(d->name, d->lnk, kSecBss, f); + fprintf(f, "\t.fill %" PRId64 ",1,0\n", g_data_zero); + } + return; + + case Dat::DZ: + if (g_data_zero != -1) + g_data_zero += d->u.num; + else + fprintf(f, "\t.fill %" PRId64 ",1,0\n", d->u.num); + return; + + case Dat::DB: + case Dat::DH: + case Dat::DW: + case Dat::DL: + break; + } + + // Switch to .data and flush accumulated zeros on first non-zero content. + if (g_data_zero != -1) { + write_lnk(d->name, d->lnk, kSecData, f); + if (g_data_zero > 0) + fprintf(f, "\t.fill %" PRId64 ",1,0\n", g_data_zero); + g_data_zero = -1; + } + + const DataDecl& dd = kDataDecl[d->type]; + if (d->isstr) { + if (d->type != Dat::DB) + err(const_cast<char*>("strings only supported for 'b' currently")); + fprintf(f, "\t.ascii %s\n", d->u.str); + } else if (d->isref) { + const char* p = (d->u.ref.name[0] == '"') ? "" : T.assym; + fprintf(f, "%s %s%s%+" PRId64 "\n", dd.decl, p, d->u.ref.name, d->u.ref.off); + } else { + fprintf(f, "%s %" PRId64 "\n", dd.decl, d->u.num & dd.mask); + } +} + +// FP constant pool (stashbits + emitfin). + +struct Asmbits { + bits n; + int size; + Asmbits* link; +}; + +int stashbits(Ctx* ctx, bits n, int size) { + assert(size == 4 || size == 8 || size == 16); + Asmbits** pb; + Asmbits* b; + int i = 0; + for (pb = &ctx->emit_stash; (b = *pb); pb = &b->link, ++i) { + if (size <= b->size && b->n == n) + return i; + } + b = static_cast<Asmbits*>(emalloc(sizeof(Asmbits))); + b->n = n; + b->size = size; + b->link = nullptr; + *pb = b; + return i; +} + +namespace { + +// Layout by .quad / .quad 0 (16B) / .quad (8B) / .int (4B). +void emit_fp_pool(Ctx* ctx, FILE* f, const char* sec[3]) { + if (!ctx->emit_stash) + return; + fprintf(f, "/* floating point constants */\n"); + + union { + int32_t i; + float f; + } u; + + // Three passes from largest to smallest (16, 8, 4). + for (int lg = 4; lg >= 2; --lg) { + int width = 1 << lg; + int i = 0; + for (Asmbits* b = ctx->emit_stash; b; b = b->link, ++i) { + if (b->size != width) + continue; + fprintf(f, + ".section %s\n" + ".p2align %d\n" + "%sfp%d:", + sec[lg - 2], lg, T.asloc, i); + if (lg == 4) { + fprintf(f, "\n\t.quad %" PRId64 "\n\t.quad 0\n\n", static_cast<int64_t>(b->n)); + } else if (lg == 3) { + double dv; + memcpy(&dv, &b->n, sizeof(dv)); + fprintf(f, "\n\t.quad %" PRId64 " /* %f */\n\n", static_cast<int64_t>(b->n), dv); + } else /* lg == 2 */ { + u.i = static_cast<int32_t>(b->n); + fprintf(f, "\n\t.int %" PRId32 " /* %f */\n\n", u.i, static_cast<double>(u.f)); + } + } + } + + // Free linked list. + while (Asmbits* b = ctx->emit_stash) { + ctx->emit_stash = b->link; + free(b); + } +} + +} // namespace + +void elf_emitfin(Ctx* ctx, FILE* f) { + static const char* sec[3] = {".rodata", ".rodata", ".rodata"}; + emit_fp_pool(ctx, f, sec); + fprintf(f, ".section .note.GNU-stack,\"\",@progbits\n"); +} + +void elf_emitfnfin(char* fn, FILE* f) { + fprintf(f, ".type %s, @function\n", fn); + fprintf(f, ".size %s, .-%s\n", fn, fn); +} + +void macho_emitfin(Ctx* ctx, FILE* f) { + static const char* sec[3] = { + "__TEXT,__literal4,4byte_literals", + "__TEXT,__literal8,8byte_literals", + "__TEXT,__literal16,16byte_literals", + }; + emit_fp_pool(ctx, f, sec); +} + +void pe_emitfin(Ctx* ctx, FILE* f) { + static const char* sec[3] = {".rodata", ".rodata", ".rodata"}; + emit_fp_pool(ctx, f, sec); +} + +// DWARF .file / .loc. + +namespace { +std::vector<uint32_t> g_dbg_files; // intern id sequence. +uint32_t g_dbg_curfile = 0; // 1-based (gas requires > 0). +} // namespace + +void emitdbgfile(char* fn, FILE* f) { + uint32_t id = intern(fn); + for (size_t i = 0; i < g_dbg_files.size(); ++i) { + if (g_dbg_files[i] == id) { + g_dbg_curfile = static_cast<uint32_t>(i + 1); + return; + } + } + g_dbg_files.push_back(id); + g_dbg_curfile = static_cast<uint32_t>(g_dbg_files.size()); + fprintf(f, ".file %u %s\n", g_dbg_curfile, fn); +} + +void emitdbgloc(uint line, uint col, FILE* f) { + if (col != 0) + fprintf(f, "\t.loc %u %u %u\n", g_dbg_curfile, line, col); + else + fprintf(f, "\t.loc %u %u\n", g_dbg_curfile, line); +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/peephole.cc b/rcc-legacy/src/backend/peephole.cc new file mode 100644 index 0000000..0c5e95f --- /dev/null +++ b/rcc-legacy/src/backend/peephole.cc @@ -0,0 +1,146 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file peephole.cc +/// @brief Backend sliding-window peephole pass. + +#include "rcc/all.h" + +namespace rcc { + +namespace { + +// Store op to byte width. +int store_size(int op) { + switch (op) { + case Ostoreb: + return 1; + case Ostoreh: + return 2; + case Ostorew: + case Ostores: + return 4; + case Ostorel: + case Ostored: + return 8; + default: + return 0; + } +} + +// Check if store and load are compatible for forwarding. +bool store_load_compatible(int sop, int lcls) { + switch (sop) { + case Ostorew: + return lcls == Kw; + case Ostorel: + return lcls == Kl; + case Ostores: + return lcls == Ks; + case Ostored: + return lcls == Kd; + default: + return false; + } +} + +void peep_blk(Blk* b) { + uint n = b->nins; + if (n == 0) + return; + + // Self Ocopy elimination. + for (uint k = 0; k < n; k++) { + Ins* i = &b->ins[k]; + if (i->op == Ocopy && req(i->to, i->arg[0]) && rtype(i->to) != -1) + i->op = Onop; + } + + // Window 2 mode. + auto next_live = [&](uint from) -> int { + for (uint k = from; k < n; k++) + if (b->ins[k].op != Onop) + return static_cast<int>(k); + return -1; + }; + + int p = next_live(0); + while (p >= 0) { + int q = next_live(static_cast<uint>(p) + 1); + if (q < 0) + break; + Ins* pi = &b->ins[p]; + Ins* qi = &b->ins[q]; + + // Store + Load to same slot: keep store, change load to copy. + if (isstore(pi->op) && qi->op == Oload && rtype(pi->arg[1]) == RSlot && + rtype(qi->arg[0]) == RSlot && req(pi->arg[1], qi->arg[0]) && + store_load_compatible(pi->op, qi->cls) && rtype(pi->arg[0]) != RCon) { + // Skip forwarding if value is a constant. + qi->op = Ocopy; + qi->arg[0] = pi->arg[0]; + qi->arg[1] = Ref::none(); + // Keep cls and to. + p = q; + continue; + } + + // Consecutive stores to same slot: drop first. + if (isstore(pi->op) && pi->op == qi->op && rtype(pi->arg[1]) == RSlot && + rtype(qi->arg[1]) == RSlot && req(pi->arg[1], qi->arg[1]) && store_size(pi->op) > 0) { + pi->op = Onop; + p = q; + continue; + } + + p = q; + } + + // Redundant Oextsw elimination. + for (uint pidx = 0; pidx < n; pidx++) { + Ins* pi = &b->ins[pidx]; + if (pi->op != Oextsw) + continue; + if (!isreg(pi->to) || !isreg(pi->arg[0])) + continue; + for (uint qidx = pidx + 1; qidx < n; qidx++) { + Ins* qi = &b->ins[qidx]; + if (qi->op == Onop) + continue; + // Identical Oextsw. + if (qi->op == pi->op && req(qi->to, pi->to) && req(qi->arg[0], pi->arg[0]) && + req(qi->arg[1], pi->arg[1])) { + qi->op = Onop; + continue; + } + // Anchor invalidated. + if (isreg(qi->to) && (req(qi->to, pi->to) || req(qi->to, pi->arg[0]))) + break; + } + } +} + +} // namespace + +void peephole(Ctx* /*ctx*/, Fn* fn) { + for (Blk* b = fn->start; b; b = b->link) + peep_blk(b); +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/rega.cc b/rcc-legacy/src/backend/rega.cc new file mode 100644 index 0000000..e4f9da1 --- /dev/null +++ b/rcc-legacy/src/backend/rega.cc @@ -0,0 +1,684 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file backend/rega.cc +/// @brief Register allocation pass. + +#include "rcc/all.h" + +#include <algorithm> +#include <climits> +#include <cstdio> +#include <cstring> + +namespace rcc { + +namespace { + +#ifdef TEST_PMOV +#undef assert +#define assert(x) assert_test(#x, x) +#endif + +struct RMap { + int t[Tmp0]; + int r[Tmp0]; + int w[Tmp0]; /* wait list, for unmatched hints */ + BSet b[1]; + int n; +}; + +int* hint(Ctx* ctx, int t) { + return &ctx->rega_tmp[phicls(t, ctx->rega_tmp)].hint.r; +} + +void sethint(Ctx* ctx, int t, int r) { + Tmp* p; + + p = &ctx->rega_tmp[phicls(t, ctx->rega_tmp)]; + if (p->hint.r == -1 || p->hint.w > ctx->rega_loop) { + p->hint.r = r; + p->hint.w = ctx->rega_loop; + ctx->rega_tmp[t].visit = -1; + } +} + +void rcopy(RMap* ma, RMap* mb) { + memcpy(ma->t, mb->t, sizeof ma->t); + memcpy(ma->r, mb->r, sizeof ma->r); + memcpy(ma->w, mb->w, sizeof ma->w); + bscopy(ma->b, mb->b); + ma->n = mb->n; +} + +int rfind(RMap* m, int t) { + int i; + + for (i = 0; i < m->n; i++) + if (m->t[i] == t) + return m->r[i]; + return -1; +} + +Ref rref(Ctx* ctx, RMap* m, int t) { + int r, s; + + r = rfind(m, t); + if (r == -1) { + s = ctx->rega_tmp[t].slot; + assert(s != -1 && "should have spilled"); + return Ref::slot(s); + } else + return Ref::tmp(static_cast<uint>(r)); +} + +void radd(Ctx* ctx, RMap* m, int t, int r) { + assert((t >= Tmp0 || t == r) && "invalid temporary"); + assert(((T.gpr0 <= r && r < T.gpr0 + T.ngpr) || (T.fpr0 <= r && r < T.fpr0 + T.nfpr)) && + "invalid register"); + assert(!bshas(m->b, static_cast<uint>(t)) && "temporary has mapping"); + assert(!bshas(m->b, static_cast<uint>(r)) && "register already allocated"); + assert(m->n <= T.ngpr + T.nfpr && "too many mappings"); + bsset(m->b, static_cast<uint>(t)); + bsset(m->b, static_cast<uint>(r)); + m->t[m->n] = t; + m->r[m->n] = r; + m->n++; + ctx->rega_regu |= BIT(r); +} + +Ref ralloctry(Ctx* ctx, RMap* m, int t, int try_) { + bits regs; + int h, r, r0, r1; + + if (t < Tmp0) { + assert(bshas(m->b, static_cast<uint>(t))); + return Ref::tmp(static_cast<uint>(t)); + } + if (bshas(m->b, static_cast<uint>(t))) { + r = rfind(m, t); + assert(r != -1); + return Ref::tmp(static_cast<uint>(r)); + } + r = ctx->rega_tmp[t].visit; + if (r == -1 || bshas(m->b, static_cast<uint>(r))) + r = *hint(ctx, t); + if (r == -1 || bshas(m->b, static_cast<uint>(r))) { + if (try_) + return Ref::none(); + regs = ctx->rega_tmp[phicls(t, ctx->rega_tmp)].hint.m; + regs |= m->b->t[0]; + if (KBASE(ctx->rega_tmp[t].cls) == 0) { + r0 = T.gpr0; + r1 = r0 + T.ngpr; + } else { + r0 = T.fpr0; + r1 = r0 + T.nfpr; + } + for (r = r0; r < r1; r++) + if (!(regs & BIT(r))) + goto Found; + for (r = r0; r < r1; r++) + if (!bshas(m->b, static_cast<uint>(r))) + goto Found; + fprintf(stderr, "DBG no-more-regs: t=%d cls=%d range=[R%d,R%d) m->n=%d\n", t, + ctx->rega_tmp[t].cls, r0, r1, m->n); + fprintf(stderr, " m->t/r entries:\n"); + for (int idx = 0; idx < m->n; ++idx) + fprintf(stderr, " [%d] t=%d -> r=R%d\n", idx, m->t[idx], m->r[idx]); + fprintf(stderr, " m->b set bits:"); + for (int rr = 0; rr < 64; ++rr) + if (bshas(m->b, static_cast<uint>(rr))) + fprintf(stderr, " %d", rr); + fprintf(stderr, "\n"); + err(const_cast<char*>("no more regs")); + } +Found: + radd(ctx, m, t, r); + sethint(ctx, t, r); + ctx->rega_tmp[t].visit = r; + h = *hint(ctx, t); + if (h != -1 && h != r) + m->w[h] = t; + return Ref::tmp(static_cast<uint>(r)); +} + +inline Ref ralloc(Ctx* ctx, RMap* m, int t) { + return ralloctry(ctx, m, t, 0); +} + +int rfree(RMap* m, int t) { + int i, r; + + assert(t >= Tmp0 || !(BIT(t) & T.rglob)); + if (!bshas(m->b, static_cast<uint>(t))) + return -1; + for (i = 0; m->t[i] != t; i++) + assert(i + 1 < m->n); + r = m->r[i]; + bsclr(m->b, static_cast<uint>(t)); + bsclr(m->b, static_cast<uint>(r)); + m->n--; + memmove(&m->t[i], &m->t[i + 1], static_cast<size_t>(m->n - i) * sizeof m->t[0]); + memmove(&m->r[i], &m->r[i + 1], static_cast<size_t>(m->n - i) * sizeof m->r[0]); + assert(t >= Tmp0 || t == r); + return r; +} + +void mdump(Ctx* ctx, RMap* m) { + int i; + + for (i = 0; i < m->n; i++) + if (m->t[i] >= Tmp0) + fprintf(ctx->dbg_buf, " (%s, R%d)", ctx->rega_tmp[m->t[i]].name, m->r[i]); + fprintf(ctx->dbg_buf, "\n"); +} + +void pmadd(Ctx* ctx, Ref src, Ref dst, int k) { + if (ctx->rega_npm == NPm) + err(const_cast<char*>("no more pm slots")); + ctx->rega_pm[ctx->rega_npm].src = src; + ctx->rega_pm[ctx->rega_npm].dst = dst; + ctx->rega_pm[ctx->rega_npm].cls = k; + ctx->rega_npm++; +} + +enum PMStat { ToMove, Moving, Moved }; + +int pmrec(Ctx* ctx, enum PMStat* status, int i, int* k) { + int j, c; + + /* note, this routine might emit + * too many large instructions + */ + if (req(ctx->rega_pm[i].src, ctx->rega_pm[i].dst)) { + status[i] = Moved; + return -1; + } + assert(KBASE(ctx->rega_pm[i].cls) == KBASE(*k)); + assert((Kw | Kl) == Kl && (Ks | Kd) == Kd); + *k |= ctx->rega_pm[i].cls; + for (j = 0; j < ctx->rega_npm; j++) + if (req(ctx->rega_pm[j].dst, ctx->rega_pm[i].src)) + break; + switch (j == ctx->rega_npm ? Moved : status[j]) { + case Moving: + c = j; /* start of cycle */ + emit(ctx, Oswap, *k, Ref::none(), ctx->rega_pm[i].src, ctx->rega_pm[i].dst); + break; + case ToMove: + status[i] = Moving; + c = pmrec(ctx, status, j, k); + if (c == i) { + c = -1; /* end of cycle */ + break; + } + if (c != -1) { + emit(ctx, Oswap, *k, Ref::none(), ctx->rega_pm[i].src, ctx->rega_pm[i].dst); + break; + } + [[fallthrough]]; + case Moved: + c = -1; + emit(ctx, Ocopy, ctx->rega_pm[i].cls, ctx->rega_pm[i].dst, ctx->rega_pm[i].src, + Ref::none()); + break; + default: + die("unreachable"); + } + status[i] = Moved; + return c; +} + +void pmgen(Ctx* ctx) { + int i; + enum PMStat* status; + + status = static_cast<enum PMStat*>( + alloc(ctx, static_cast<size_t>(ctx->rega_npm) * sizeof status[0])); + assert(!ctx->rega_npm || status[ctx->rega_npm - 1] == ToMove); + for (i = 0; i < ctx->rega_npm; i++) + if (status[i] == ToMove) { + int k = ctx->rega_pm[i].cls; + pmrec(ctx, status, i, &k); + } +} + +void move(Ctx* ctx, int r, Ref to, RMap* m) { + int n, t, r1; + + r1 = req(to, Ref::none()) ? -1 : rfree(m, static_cast<int>(to.val)); + if (bshas(m->b, static_cast<uint>(r))) { + /* r is used and not by to */ + assert(r1 != r); + for (n = 0; m->r[n] != r; n++) + assert(n + 1 < m->n); + t = m->t[n]; + rfree(m, t); + bsset(m->b, static_cast<uint>(r)); + ralloc(ctx, m, t); + bsclr(m->b, static_cast<uint>(r)); + } + t = req(to, Ref::none()) ? r : static_cast<int>(to.val); + radd(ctx, m, t, r); +} + +int regcpy(Ins* i) { + return i->op == Ocopy && isreg(i->arg[0]); +} + +Ins* dopm(Ctx* ctx, Blk* b, Ins* i, RMap* m) { + RMap m0; + int n, r, r1, t, s; + Ins *i1, *ip; + bits def; + + m0 = *m; /* okay since we don't use m0.b */ + m0.b->t = 0; + i1 = ++i; + do { + i--; + move(ctx, static_cast<int>(i->arg[0].val), i->to, m); + } while (i != b->ins && regcpy(i - 1)); + assert(m0.n <= m->n); + if (i != b->ins && (i - 1)->op == Ocall) { + def = T.retregs((i - 1)->arg[1], 0) | T.rglob; + for (r = 0; T.rsave[r] >= 0; r++) + if (!(BIT(T.rsave[r]) & def)) + move(ctx, T.rsave[r], Ref::none(), m); + } + for (ctx->rega_npm = 0, n = 0; n < m->n; n++) { + t = m->t[n]; + s = ctx->rega_tmp[t].slot; + r1 = m->r[n]; + r = rfind(&m0, t); + if (r != -1) + pmadd(ctx, Ref::tmp(static_cast<uint>(r1)), Ref::tmp(static_cast<uint>(r)), + ctx->rega_tmp[t].cls); + else if (s != -1) + pmadd(ctx, Ref::tmp(static_cast<uint>(r1)), Ref::slot(s), ctx->rega_tmp[t].cls); + } + for (ip = i; ip < i1; ip++) { + if (!req(ip->to, Ref::none())) + rfree(m, static_cast<int>(ip->to.val)); + r = static_cast<int>(ip->arg[0].val); + if (rfind(m, r) == -1) + radd(ctx, m, r, r); + } + pmgen(ctx); + return i; +} + +int prio1(Ctx* ctx, Ref r1, Ref r2) { + /* trivial heuristic to begin with, + * later we can use the distance to + * the definition instruction + */ + (void)r2; + return *hint(ctx, static_cast<int>(r1.val)) != -1; +} + +void insert(Ctx* ctx, Ref* r, Ref** rs, int p) { + int i; + + rs[i = p] = r; + while (i-- > 0 && prio1(ctx, *r, *rs[i])) { + rs[i + 1] = rs[i]; + rs[i] = r; + } +} + +void doblk(Ctx* ctx, Blk* b, RMap* cur) { + int t, x, r, rf, rt, nr; + bits rs; + Ins *i, *i1; + Mem* m; + Ref* ra[4]; + + if (rtype(b->jmp.arg) == RTmp) + b->jmp.arg = ralloc(ctx, cur, static_cast<int>(b->jmp.arg.val)); + if (rtype(b->jmp.arg2) == RTmp) + b->jmp.arg2 = ralloc(ctx, cur, static_cast<int>(b->jmp.arg2.val)); + ctx->curi = &ctx->insb[NIns]; + for (i1 = &b->ins[b->nins]; i1 != b->ins;) { + emiti(ctx, *--i1); + i = ctx->curi; + rf = -1; + switch (i->op) { + case Ocall: + rs = T.argregs(i->arg[1], 0) | T.rglob; + for (r = 0; T.rsave[r] >= 0; r++) + if (!(BIT(T.rsave[r]) & rs)) + rfree(cur, T.rsave[r]); + break; + case Ocopy: + if (regcpy(i)) { + ctx->curi++; + i1 = dopm(ctx, b, i1, cur); + ctx->rega_stmov += static_cast<uint>(i + 1 - ctx->curi); + continue; + } + if (isreg(i->to)) + if (rtype(i->arg[0]) == RTmp) + sethint(ctx, static_cast<int>(i->arg[0].val), static_cast<int>(i->to.val)); + [[fallthrough]]; + default: + if (!req(i->to, Ref::none())) { + assert(rtype(i->to) == RTmp); + r = static_cast<int>(i->to.val); + if (r < Tmp0 && (BIT(r) & T.rglob)) + break; + rf = rfree(cur, r); + if (rf == -1) { + assert(!isreg(i->to)); + ctx->curi++; + continue; + } + i->to = Ref::tmp(static_cast<uint>(rf)); + } + break; + } + for (x = 0, nr = 0; x < 2; x++) + switch (rtype(i->arg[x])) { + case RMem: + m = &ctx->rega_mem[i->arg[x].val]; + if (rtype(m->base) == RTmp) + insert(ctx, &m->base, ra, nr++); + if (rtype(m->index) == RTmp) + insert(ctx, &m->index, ra, nr++); + break; + case RTmp: + insert(ctx, &i->arg[x], ra, nr++); + break; + } + for (r = 0; r < nr; r++) + *ra[r] = ralloc(ctx, cur, static_cast<int>(ra[r]->val)); + if (i->op == Ocopy && req(i->to, i->arg[0])) + ctx->curi++; + + /* try to change the register of a hinted + * temporary if rf is available */ + if (rf != -1 && (t = cur->w[rf]) != 0) + if (!bshas(cur->b, static_cast<uint>(rf)) && *hint(ctx, t) == rf && + (rt = rfree(cur, t)) != -1) { + ctx->rega_tmp[t].visit = -1; + ralloc(ctx, cur, t); + assert(bshas(cur->b, static_cast<uint>(rf))); + emit(ctx, Ocopy, ctx->rega_tmp[t].cls, Ref::tmp(static_cast<uint>(rt)), + Ref::tmp(static_cast<uint>(rf)), Ref::none()); + ctx->rega_stmov += 1; + cur->w[rf] = 0; + for (r = 0; r < nr; r++) + if (req(*ra[r], Ref::tmp(static_cast<uint>(rt)))) + *ra[r] = Ref::tmp(static_cast<uint>(rf)); + /* one could iterate this logic with + * the newly freed rt, but in this case + * the above loop must be changed */ + } + } + idup(ctx, b, ctx->curi, static_cast<ulong>(&ctx->insb[NIns] - ctx->curi)); +} + +/* comparison function to order temporaries + * for allocation at the end of blocks */ +int prio2(Ctx* ctx, int t1, int t2) { + if ((ctx->rega_tmp[t1].visit ^ ctx->rega_tmp[t2].visit) < 0) /* != signs */ + return ctx->rega_tmp[t1].visit != -1 ? +1 : -1; + if ((*hint(ctx, t1) ^ *hint(ctx, t2)) < 0) + return *hint(ctx, t1) != -1 ? +1 : -1; + return static_cast<int>(ctx->rega_tmp[t1].cost) - static_cast<int>(ctx->rega_tmp[t2].cost); +} + +} // namespace + +/* register allocation + * depends on rpo, phi, cost, (and obviously spill) + */ +void rega(Ctx* ctx, Fn* fn) { + int j, t, r, x, rl[Tmp0]; + Blk *b, *b1, *s, ***ps, *blist, **blk, **bp; + RMap *end, *beg, cur, old, *m; + Ins* i; + Phi* p; + uint u, n; + Ref src, dst; + + /* setup */ + ctx->rega_stmov = 0; + ctx->rega_stblk = 0; + ctx->rega_regu = 0; + ctx->rega_tmp = fn->tmp; + ctx->rega_mem = fn->mem; + blk = static_cast<Blk**>(alloc(ctx, fn->nblk * sizeof blk[0])); + end = static_cast<RMap*>(alloc(ctx, fn->nblk * sizeof end[0])); + beg = static_cast<RMap*>(alloc(ctx, fn->nblk * sizeof beg[0])); + for (n = 0; n < fn->nblk; n++) { + bsinit(ctx, end[n].b, static_cast<uint>(fn->ntmp)); + bsinit(ctx, beg[n].b, static_cast<uint>(fn->ntmp)); + } + bsinit(ctx, cur.b, static_cast<uint>(fn->ntmp)); + bsinit(ctx, old.b, static_cast<uint>(fn->ntmp)); + (void)old; /* declares but doesn't use this, suppress -Wunused here */ + + ctx->rega_loop = INT_MAX; + for (t = 0; t < fn->ntmp; t++) { + ctx->rega_tmp[t].hint.r = t < Tmp0 ? t : -1; + ctx->rega_tmp[t].hint.w = ctx->rega_loop; + ctx->rega_tmp[t].visit = -1; + } + for (bp = blk, b = fn->start; b; b = b->link) + *bp++ = b; + /* qsort() comparison function to peel + * loop nests from inside out */ + std::sort(blk, blk + fn->nblk, [](Blk* ba, Blk* bb) { + /* todo, evaluate if this order is really + * better than the simple postorder */ + if (ba->loop == bb->loop) + return ba->id > bb->id; + return ba->loop > bb->loop; + }); + for (b = fn->start, i = b->ins; i < &b->ins[b->nins]; i++) + if (i->op != Ocopy || !isreg(i->arg[0])) + break; + else { + assert(rtype(i->to) == RTmp); + sethint(ctx, static_cast<int>(i->to.val), static_cast<int>(i->arg[0].val)); + } + + /* assign registers */ + for (bp = blk; bp < &blk[fn->nblk]; bp++) { + b = *bp; + n = b->id; + ctx->rega_loop = b->loop; + cur.n = 0; + bszero(cur.b); + memset(cur.w, 0, sizeof cur.w); + for (x = 0, t = Tmp0; bsiter(b->out, &t); t++) { + j = x++; + rl[j] = t; + while (j-- > 0 && prio2(ctx, t, rl[j]) > 0) { + rl[j + 1] = rl[j]; + rl[j] = t; + } + } + for (r = 0; bsiter(b->out, &r) && r < Tmp0; r++) + radd(ctx, &cur, r, r); + for (j = 0; j < x; j++) + ralloctry(ctx, &cur, rl[j], 1); + for (j = 0; j < x; j++) + ralloc(ctx, &cur, rl[j]); + rcopy(&end[n], &cur); + doblk(ctx, b, &cur); + bscopy(b->in, cur.b); + for (p = b->phi; p; p = p->link) + if (rtype(p->to) == RTmp) + bsclr(b->in, p->to.val); + rcopy(&beg[n], &cur); + } + + /* emit copies shared by multiple edges + * to the same block */ + for (s = fn->start; s; s = s->link) { + if (s->npred <= 1) + continue; + m = &beg[s->id]; + + /* rl maps a register that is live at the + * beginning of s to the one used in all + * predecessors (if any, -1 otherwise) */ + memset(rl, 0, sizeof rl); + + /* to find the register of a phi in a + * predecessor, we have to find the + * corresponding argument */ + for (p = s->phi; p; p = p->link) { + if (rtype(p->to) != RTmp || (r = rfind(m, static_cast<int>(p->to.val))) == -1) + continue; + for (u = 0; u < p->narg; u++) { + b = p->blk[u]; + src = p->arg[u]; + if (rtype(src) != RTmp) + continue; + x = rfind(&end[b->id], static_cast<int>(src.val)); + if (x == -1) /* spilled */ + continue; + rl[r] = (!rl[r] || rl[r] == x) ? x : -1; + } + if (rl[r] == 0) + rl[r] = -1; + } + + /* process non-phis temporaries */ + for (j = 0; j < m->n; j++) { + t = m->t[j]; + r = m->r[j]; + if (rl[r] || t < Tmp0 /* todo, remove this */) + continue; + for (bp = s->pred; bp < &s->pred[s->npred]; bp++) { + x = rfind(&end[(*bp)->id], t); + if (x == -1) /* spilled */ + continue; + rl[r] = (!rl[r] || rl[r] == x) ? x : -1; + } + if (rl[r] == 0) + rl[r] = -1; + } + + ctx->rega_npm = 0; + for (j = 0; j < m->n; j++) { + t = m->t[j]; + r = m->r[j]; + x = rl[r]; + assert(x != 0 || t < Tmp0 /* todo, ditto */); + if (x > 0 && !bshas(m->b, static_cast<uint>(x))) { + pmadd(ctx, Ref::tmp(static_cast<uint>(x)), Ref::tmp(static_cast<uint>(r)), + ctx->rega_tmp[t].cls); + m->r[j] = x; + bsset(m->b, static_cast<uint>(x)); + } + } + ctx->curi = &ctx->insb[NIns]; + pmgen(ctx); + j = static_cast<int>(&ctx->insb[NIns] - ctx->curi); + if (j == 0) + continue; + ctx->rega_stmov += static_cast<uint>(j); + s->nins += static_cast<uint>(j); + i = static_cast<Ins*>(alloc(ctx, s->nins * sizeof(Ins))); + icpy(icpy(i, ctx->curi, static_cast<ulong>(j)), s->ins, s->nins - static_cast<uint>(j)); + s->ins = i; + } + + if (debug['R']) { + fprintf(ctx->dbg_buf, "\n> Register mappings:\n"); + for (n = 0; n < fn->nblk; n++) { + b = fn->rpo[n]; + fprintf(ctx->dbg_buf, "\t%-10s beg", b->name); + mdump(ctx, &beg[n]); + fprintf(ctx->dbg_buf, "\t end"); + mdump(ctx, &end[n]); + } + fprintf(ctx->dbg_buf, "\n"); + } + + /* emit remaining copies in new blocks */ + blist = 0; + for (b = fn->start;; b = b->link) { + Blk* null_blk = nullptr; + Blk** ps_arr[3] = {&b->s1, &b->s2, &null_blk}; + ps = ps_arr; + for (; (s = **ps); ps++) { + ctx->rega_npm = 0; + for (p = s->phi; p; p = p->link) { + dst = p->to; + assert(rtype(dst) == RSlot || rtype(dst) == RTmp); + if (rtype(dst) == RTmp) { + r = rfind(&beg[s->id], static_cast<int>(dst.val)); + if (r == -1) + continue; + dst = Ref::tmp(static_cast<uint>(r)); + } + for (u = 0; p->blk[u] != b; u++) + assert(u + 1 < p->narg); + src = p->arg[u]; + if (rtype(src) == RTmp) + src = rref(ctx, &end[b->id], static_cast<int>(src.val)); + pmadd(ctx, src, dst, p->cls); + } + for (t = Tmp0; bsiter(s->in, &t); t++) { + src = rref(ctx, &end[b->id], t); + dst = rref(ctx, &beg[s->id], t); + pmadd(ctx, src, dst, ctx->rega_tmp[t].cls); + } + ctx->curi = &ctx->insb[NIns]; + pmgen(ctx); + if (ctx->curi == &ctx->insb[NIns]) + continue; + b1 = newblk(ctx); + b1->loop = (b->loop + s->loop) / 2; + b1->link = blist; + blist = b1; + fn->nblk++; + b1->name = strf(ctx, PFn, "%s_%s", b->name, s->name); + ctx->rega_stmov += static_cast<uint>(&ctx->insb[NIns] - ctx->curi); + ctx->rega_stblk += 1; + idup(ctx, b1, ctx->curi, static_cast<ulong>(&ctx->insb[NIns] - ctx->curi)); + b1->jmp.type = Jjmp; + b1->s1 = s; + **ps = b1; + } + if (!b->link) { + b->link = blist; + break; + } + } + for (b = fn->start; b; b = b->link) + b->phi = 0; + fn->reg = ctx->rega_regu; + + if (debug['R']) { + fprintf(ctx->dbg_buf, "\n> Register allocation statistics:\n"); + fprintf(ctx->dbg_buf, "\tnew moves: %d\n", ctx->rega_stmov); + fprintf(ctx->dbg_buf, "\tnew blocks: %d\n", ctx->rega_stblk); + fprintf(ctx->dbg_buf, "\n> After register allocation:\n"); + printfn(fn, ctx->dbg_buf); + } +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/rv64/abi.cc b/rcc-legacy/src/backend/rv64/abi.cc new file mode 100644 index 0000000..6456c21 --- /dev/null +++ b/rcc-legacy/src/backend/rv64/abi.cc @@ -0,0 +1,690 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file abi.cc +/// @brief RISC-V lp64d ABI lowering. + +#include "rcc/rv64.h" + +namespace rcc { + +namespace { + +// ABI classification flags (OR-able). +enum : int { + kCptr = 1, // Pointer to large struct. + kCstk1 = 2, // First XLEN on stack. + kCstk2 = 4, // Second XLEN on stack. + kCstk = kCstk1 | kCstk2, + kCfpint = 8, // Float in GP reg. +}; + +// Argument class. +struct ArgClass { + int flags; // OR of kCptr / kCstk1 / kCstk2 / kCfpint + Typ* type; + int reg[2]; + int cls[2]; + int off[2]; + int ngp; // Written by typclass + int nfp; + int nreg; +}; + +struct Insl { + Insl* link; + Ins i; +}; + +struct Params { + int ngp; + int nfp; + int stk; +}; + +// Registers. Sentinels of 0 are used to prevent out-of-bounds reads. +constexpr int kGpReg[10] = {A0, A1, A2, A3, A4, A5, A6, A7, 0, 0}; +constexpr int kFpReg[10] = {FA0, FA1, FA2, FA3, FA4, FA5, FA6, FA7, 0, 0}; + +} // namespace + +// RCall encoding. + +bits rv64_retregs(Ref r, int p[2]) { + assert(rtype(r) == RCall); + int ngp = static_cast<int>(r.val & 3); + int nfp = static_cast<int>((r.val >> 2) & 3); + if (p) { + p[0] = ngp; + p[1] = nfp; + } + bits b = 0; + for (int i = 0; i < ngp; ++i) + b |= BIT(A0 + i); + for (int i = 0; i < nfp; ++i) + b |= BIT(FA0 + i); + return b; +} + +bits rv64_argregs(Ref r, int p[2]) { + assert(rtype(r) == RCall); + int ngp = static_cast<int>((r.val >> 4) & 15); + int nfp = static_cast<int>((r.val >> 8) & 15); + int t5 = static_cast<int>((r.val >> 12) & 1); + if (p) { + p[0] = ngp + t5; + p[1] = nfp; + } + bits b = 0; + for (int i = 0; i < ngp; ++i) + b |= BIT(A0 + i); + for (int i = 0; i < nfp; ++i) + b |= BIT(FA0 + i); + if (t5) + b |= BIT(T5); + return b; +} + +namespace { + +// HFA-style floating-point struct classification. + +int classify_fpstruct(Typ* t, int off, ArgClass* c) { + if (t->isunion) + return -1; + + for (Field* f = *t->fields; f->type != Field::FEnd; ++f) { + if (f->type == Field::FPad) { + off += static_cast<int>(f->len); + continue; + } + if (f->type == Field::FTyp) { + if (classify_fpstruct(&typ[f->len], off, c) == -1) + return -1; + continue; + } + + int slot = c->nfp + c->ngp; + if (slot == 2) + return -1; + + switch (f->type) { + case Field::Fb: + case Field::Fh: + case Field::Fw: + c->cls[slot] = Kw; + ++c->ngp; + break; + case Field::Fl: + c->cls[slot] = Kl; + ++c->ngp; + break; + case Field::Fs: + c->cls[slot] = Ks; + ++c->nfp; + break; + case Field::Fd: + c->cls[slot] = Kd; + ++c->nfp; + break; + default: + die("classify_fpstruct: unexpected field type"); + } + c->off[slot] = off; + off += static_cast<int>(f->len); + } + return c->nfp; +} + +// Determine ABI class for struct t. +void typclass(ArgClass* c, Typ* t, int fpabi, const int* gp, const int* fp) { + c->type = t; + c->flags = 0; + c->ngp = 0; + c->nfp = 0; + + if (t->align > 4) + err(const_cast<char*>("alignments larger than 16 are not supported")); + + if (t->isdark || t->size > 16 || t->size == 0) { + // Large or dark: pass by pointer. + c->flags |= kCptr; + c->cls[0] = Kl; + c->off[0] = 0; + c->ngp = 1; + } else if (!fpabi || classify_fpstruct(t, 0, c) <= 0) { + // GP-only word packing. + uint n = 0; + while (8 * n < t->size) { + c->cls[n] = Kl; + c->off[n] = static_cast<int>(8 * n); + ++n; + } + c->nfp = 0; + c->ngp = static_cast<int>(n); + } + + c->nreg = c->nfp + c->ngp; + for (int i = 0; i < c->nreg; ++i) { + if (KBASE(c->cls[i]) == 0) + c->reg[i] = *gp++; + else + c->reg[i] = *fp++; + } +} + +// Store tmp to mem+off. +void sttmps(Ctx* ctx, Ref tmp[], int ntmp, ArgClass* c, Ref mem, Fn* fn) { + static constexpr auto kStOp = [] { + std::array<int, 4> a{}; + a[Kw] = Ostorew; + a[Kl] = Ostorel; + a[Ks] = Ostores; + a[Kd] = Ostored; + return a; + }(); + assert(ntmp > 0 && ntmp <= 2); + for (int i = 0; i < ntmp; ++i) { + tmp[i] = newtmp(ctx, "abi", c->cls[i], fn); + Ref addr = newtmp(ctx, "abi", Kl, fn); + emit(ctx, kStOp[static_cast<size_t>(c->cls[i])], 0, Ref::none(), tmp[i], addr); + emit(ctx, Oadd, Kl, addr, mem, getcon(ctx, c->off[i], fn)); + } +} + +// Load mem+off to regs. +void ldregs(Ctx* ctx, ArgClass* c, Ref mem, Fn* fn) { + for (int i = 0; i < c->nreg; ++i) { + Ref addr = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Oload, c->cls[i], Ref::tmp(static_cast<uint>(c->reg[i])), addr, Ref::none()); + emit(ctx, Oadd, Kl, addr, mem, getcon(ctx, c->off[i], fn)); + } +} + +// Return value lowering. + +void selret(Ctx* ctx, Blk* b, Fn* fn) { + int j = b->jmp.type; + if (!isret(j) || j == Jret0) + return; + + Ref r = b->jmp.arg; + b->jmp.type = Jret0; + + int cty; + if (j == Jretc) { + ArgClass cr{}; + typclass(&cr, &typ[fn->retty], 1, kGpReg, kFpReg); + if (cr.flags & kCptr) { + assert(rtype(fn->retr) == RTmp); + emit(ctx, Oblit1, 0, Ref::none(), Ref::integer(static_cast<int>(cr.type->size)), + Ref::none()); + emit(ctx, Oblit0, 0, Ref::none(), r, fn->retr); + cty = 0; + } else { + ldregs(ctx, &cr, r, fn); + cty = (cr.nfp << 2) | cr.ngp; + } + } else { + int k = j - Jretw; + if (KBASE(k) == 0) { + emit(ctx, Ocopy, k, Ref::tmp(A0), r, Ref::none()); + cty = 1; + } else { + emit(ctx, Ocopy, k, Ref::tmp(FA0), r, Ref::none()); + cty = 1 << 2; + } + } + + b->jmp.arg = Ref::call(static_cast<uint>(cty)); +} + +// Arg/par classification. + +int argsclass(Ins* i0, Ins* i1, ArgClass* carg, int retptr) { + const int* gp = kGpReg; + const int* fp = kFpReg; + int ngp = 8, nfp = 8; + bool vararg = false, envc = false; + + if (retptr) { + ++gp; + --ngp; + } + + ArgClass* c = carg; + for (Ins* i = i0; i < i1; ++i, ++c) { + switch (i->op) { + case Opar: + case Oarg: + c->cls[0] = static_cast<int>(i->cls); + if (!vararg && KBASE(i->cls) == 1 && nfp > 0) { + --nfp; + c->reg[0] = *fp++; + } else if (ngp > 0) { + if (KBASE(i->cls) == 1) + c->flags |= kCfpint; + --ngp; + c->reg[0] = *gp++; + } else { + c->flags |= kCstk1; + } + break; + + case Oargv: + vararg = true; + break; + + case Oparc: + case Oargc: { + Typ* t = &typ[i->arg[0].val]; + typclass(c, t, 1, gp, fp); + if (c->nfp > 0 && (c->nfp >= nfp || c->ngp >= ngp)) { + // Downgrade to GP-only. + typclass(c, t, 0, gp, fp); + } + assert(c->nfp <= nfp); + if (c->ngp <= ngp) { + ngp -= c->ngp; + nfp -= c->nfp; + gp += c->ngp; + fp += c->nfp; + } else if (ngp > 0) { + // First word in GP, second on stack. + assert(c->ngp == 2); + assert(c->flags == 0); + c->flags |= kCstk2; + c->nreg = 1; + --ngp; + ++gp; + } else { + c->flags |= kCstk1; + if (c->nreg > 1) + c->flags |= kCstk2; + c->nreg = 0; + } + break; + } + + case Opare: + case Oarge: + c->reg[0] = T5; + c->cls[0] = Kl; + envc = true; + break; + } + } + + int passgp = static_cast<int>(gp - kGpReg); + int passfp = static_cast<int>(fp - kFpReg); + return ((envc ? 1 : 0) << 12) | (passgp << 4) | (passfp << 8); +} + +// Prepend Oalloc to start block. +void stkblob(Ctx* ctx, Ref r, Typ* t, Fn* fn, Insl** ilp) { + Insl* il = static_cast<Insl*>(alloc(ctx, sizeof(*il))); + int al = t->align - 2; // alignment logic. + if (al < 0) + al = 0; + uint64_t sz = (t->size + 7) & ~static_cast<uint64_t>(7); + + Ins ins = {}; + ins.op = static_cast<uint>(Oalloc + al); + ins.cls = Kl; + ins.to = r; + ins.arg[0] = getcon(ctx, static_cast<int64_t>(sz), fn); + il->i = ins; + il->link = *ilp; + *ilp = il; +} + +// Call lowering. + +void selcall(Ctx* ctx, Fn* fn, Ins* i0, Ins* i1, Insl** ilp) { + ArgClass* ca = + static_cast<ArgClass*>(alloc(ctx, static_cast<size_t>(i1 - i0) * sizeof(ArgClass))); + for (Ins* z = i0; z < i1; ++z) + ca[z - i0] = {}; + ArgClass cr{}; + + if (!req(i1->arg[1], Ref::none())) { + typclass(&cr, &typ[i1->arg[1].val], 1, kGpReg, kFpReg); + } + + int cty = argsclass(i0, i1, ca, cr.flags & kCptr); + + // compute stk and replace large struct args with pointer. + uint64_t stk = 0; + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (i->op == Oargv) + continue; + if (c->flags & kCptr) { + i->arg[0] = newtmp(ctx, "abi", Kl, fn); + stkblob(ctx, i->arg[0], c->type, fn, ilp); + i->op = Oarg; + } + if (c->flags & kCstk1) + stk += 8; + if (c->flags & kCstk2) + stk += 8; + } + stk += stk & 15; // 16B align tail + } + if (stk) + emit(ctx, Osalloc, Kl, Ref::none(), getcon(ctx, -static_cast<int64_t>(stk), fn), + Ref::none()); + + // Emit instructions backwards. + Ref tmp[2]; + if (!req(i1->arg[1], Ref::none())) { + stkblob(ctx, i1->to, cr.type, fn, ilp); + cty |= (cr.nfp << 2) | cr.ngp; + if (cr.flags & kCptr) { + // Dummy Ocopy. + emit(ctx, Ocopy, Kw, Ref::none(), Ref::tmp(A0), Ref::none()); + } else { + sttmps(ctx, tmp, cr.nreg, &cr, i1->to, fn); + for (int j = 0; j < cr.nreg; ++j) { + Ref rreg = Ref::tmp(static_cast<uint>(cr.reg[j])); + emit(ctx, Ocopy, cr.cls[j], tmp[j], rreg, Ref::none()); + } + } + } else if (KBASE(i1->cls) == 0) { + emit(ctx, Ocopy, i1->cls, i1->to, Ref::tmp(A0), Ref::none()); + cty |= 1; + } else { + emit(ctx, Ocopy, i1->cls, i1->to, Ref::tmp(FA0), Ref::none()); + cty |= 1 << 2; + } + + emit(ctx, Ocall, 0, Ref::none(), i1->arg[0], Ref::call(static_cast<uint>(cty))); + + if (cr.flags & kCptr) { + // Cptr return logic. + emit(ctx, Ocopy, Kl, Ref::tmp(A0), i1->to, Ref::none()); + } + + // move args to regs. + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (i->op == Oargv || (c->flags & kCstk1)) + continue; + if (i->op == Oargc) { + ldregs(ctx, c, i->arg[1], fn); + } else if (c->flags & kCfpint) { + int k = KWIDE(c->cls[0]) ? Kl : Kw; + Ref r = newtmp(ctx, "abi", k, fn); + emit(ctx, Ocopy, k, Ref::tmp(static_cast<uint>(c->reg[0])), r, Ref::none()); + c->reg[0] = static_cast<int>(r.val); + } else { + emit(ctx, Ocopy, c->cls[0], Ref::tmp(static_cast<uint>(c->reg[0])), i->arg[0], + Ref::none()); + } + } + } + + // casts and blits. + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (c->flags & kCfpint) { + int k = KWIDE(c->cls[0]) ? Kl : Kw; + emit(ctx, Ocast, k, Ref::tmp(static_cast<uint>(c->reg[0])), i->arg[0], Ref::none()); + } + if (c->flags & kCptr) { + emit(ctx, Oblit1, 0, Ref::none(), Ref::integer(static_cast<int>(c->type->size)), + Ref::none()); + emit(ctx, Oblit0, 0, Ref::none(), i->arg[1], i->arg[0]); + } + } + } + + if (!stk) + return; + + // move stack args to slot. + uint64_t off = 0; + Ref sp = newtmp(ctx, "abi", Kl, fn); + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (i->op == Oargv || !(c->flags & kCstk)) + continue; + if (i->op == Oarg) { + Ref addr = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ostorew + i->cls, Kw, Ref::none(), i->arg[0], addr); + if (i->cls == Kw) { + // TODO: check if sext is needed. + ctx->curi->op = Ostorel; + ctx->curi->arg[0] = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Oextsw, Kl, ctx->curi->arg[0], i->arg[0], Ref::none()); + } + emit(ctx, Oadd, Kl, addr, sp, getcon(ctx, static_cast<int64_t>(off), fn)); + off += 8; + } + if (i->op == Oargc) { + if (c->flags & kCstk1) { + Ref addr = newtmp(ctx, "abi", Kl, fn); + Ref val = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ostorel, 0, Ref::none(), val, addr); + emit(ctx, Oadd, Kl, addr, sp, getcon(ctx, static_cast<int64_t>(off), fn)); + emit(ctx, Oload, Kl, val, i->arg[1], Ref::none()); + off += 8; + } + if (c->flags & kCstk2) { + Ref addr = newtmp(ctx, "abi", Kl, fn); + Ref val = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ostorel, 0, Ref::none(), val, addr); + emit(ctx, Oadd, Kl, addr, sp, getcon(ctx, static_cast<int64_t>(off), fn)); + Ref addr2 = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Oload, Kl, val, addr2, Ref::none()); + emit(ctx, Oadd, Kl, addr2, i->arg[1], getcon(ctx, 8, fn)); + off += 8; + } + } + } + } + emit(ctx, Osalloc, Kl, sp, getcon(ctx, static_cast<int64_t>(stk), fn), Ref::none()); +} + +// Par lowering. + +Params selpar(Ctx* ctx, Fn* fn, Ins* i0, Ins* i1) { + ArgClass* ca = + static_cast<ArgClass*>(alloc(ctx, static_cast<size_t>(i1 - i0) * sizeof(ArgClass))); + for (Ins* z = i0; z < i1; ++z) + ca[z - i0] = {}; + ArgClass cr{}; + ctx->curi = &ctx->insb[NIns]; + + if (fn->retty >= 0) { + typclass(&cr, &typ[fn->retty], 1, kGpReg, kFpReg); + if (cr.flags & kCptr) { + fn->retr = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ocopy, Kl, fn->retr, Ref::tmp(A0), Ref::none()); + } + } + + int cty = argsclass(i0, i1, ca, cr.flags & kCptr); + fn->reg = rv64_argregs(Ref::call(static_cast<uint>(cty)), nullptr); + + // slot allocations and Ocast. + Insl* il = nullptr; + Ref tmp[17]; + Ref* t = tmp; + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (c->flags & kCfpint) { + Ref to_orig = i->to; + int k = c->cls[0]; + c->cls[0] = KWIDE(k) ? Kl : Kw; + i->to = newtmp(ctx, "abi", k, fn); + emit(ctx, Ocast, k, to_orig, i->to, Ref::none()); + } + if (i->op == Oparc && !(c->flags & kCptr) && c->nreg != 0) { + int nt = c->nreg; + if (c->flags & kCstk2) { + c->cls[1] = Kl; + c->off[1] = 8; + assert(nt == 1); + nt = 2; + } + sttmps(ctx, t, nt, c, i->to, fn); + stkblob(ctx, i->to, c->type, fn, &il); + t += nt; + } + } + } + for (; il; il = il->link) + emiti(ctx, il->i); + + // copy from regs or load from slot. + t = tmp; + int s = 2 + 8 * fn->vararg; + { + ArgClass* c = ca; + for (Ins* i = i0; i < i1; ++i, ++c) { + if (i->op == Oparc && !(c->flags & kCptr)) { + if (c->nreg == 0) { + fn->tmp[i->to.val].slot = -s; + s += (c->flags & kCstk2) ? 2 : 1; + continue; + } + for (int j = 0; j < c->nreg; ++j) { + Ref rreg = Ref::tmp(static_cast<uint>(c->reg[j])); + emit(ctx, Ocopy, c->cls[j], *t++, rreg, Ref::none()); + } + if (c->flags & kCstk2) { + emit(ctx, Oload, Kl, *t, Ref::slot(-s), Ref::none()); + ++t; + ++s; + } + } else if (c->flags & kCstk1) { + emit(ctx, Oload, c->cls[0], i->to, Ref::slot(-s), Ref::none()); + ++s; + } else { + emit(ctx, Ocopy, c->cls[0], i->to, Ref::tmp(static_cast<uint>(c->reg[0])), + Ref::none()); + } + } + } + + Params p; + p.stk = s; + p.ngp = (cty >> 4) & 15; + p.nfp = (cty >> 8) & 15; + return p; +} + +// Varargs. + +void selvaarg(Ctx* ctx, Fn* fn, Ins* i) { + Ref loc = newtmp(ctx, "abi", Kl, fn); + Ref newloc = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ostorel, Kw, Ref::none(), newloc, i->arg[0]); + emit(ctx, Oadd, Kl, newloc, loc, getcon(ctx, 8, fn)); + emit(ctx, Oload, i->cls, i->to, loc, Ref::none()); + emit(ctx, Oload, Kl, loc, i->arg[0], Ref::none()); +} + +void selvastart(Ctx* ctx, Fn* fn, Params p, Ref ap) { + Ref rsave = newtmp(ctx, "abi", Kl, fn); + emit(ctx, Ostorel, Kw, Ref::none(), rsave, ap); + int s = p.stk > 2 + 8 * fn->vararg ? p.stk : 2 + p.ngp; + emit(ctx, Oaddr, Kl, rsave, Ref::slot(-s), Ref::none()); +} + +} // namespace + +// Entry point. + +void rv64_abi(Ctx* ctx, Fn* fn) { + for (Blk* b = fn->start; b; b = b->link) + b->visit = 0; + + // Lower parameters. + Blk* b = fn->start; + Ins* end = &b->ins[b->nins]; + Ins* i = b->ins; + while (i < end && ispar(i->op)) + ++i; + Params p = selpar(ctx, fn, b->ins, i); + + int n0 = static_cast<int>(&ctx->insb[NIns] - ctx->curi); + int ioff = static_cast<int>(i - b->ins); + int n1 = static_cast<int>(b->nins) - ioff; + vgrow(ctx, &b->ins, static_cast<ulong>(n0 + n1)); + icpy(b->ins + n0, b->ins + ioff, static_cast<ulong>(n1)); + icpy(b->ins, ctx->curi, static_cast<ulong>(n0)); + b->nins = static_cast<uint>(n0 + n1); + + // Lower calls/returns/varargs. + Insl* il = nullptr; + b = fn->start; + do { + b = b->link ? b->link : fn->start; + if (b->visit) + continue; + ctx->curi = &ctx->insb[NIns]; + selret(ctx, b, fn); + + for (Ins* it = &b->ins[b->nins]; it != b->ins;) { + --it; + switch (it->op) { + default: + emiti(ctx, *it); + break; + case Ocall: { + Ins* lo = it; + while (lo > b->ins && isarg((lo - 1)->op)) + --lo; + selcall(ctx, fn, lo, it, &il); + it = lo; + break; + } + case Ovastart: + selvastart(ctx, fn, p, it->arg[0]); + break; + case Ovaarg: + selvaarg(ctx, fn, it); + break; + case Oarg: + case Oargc: + die("rv64_abi: Oarg/Oargc unreachable"); + } + } + + if (b == fn->start) { + for (; il; il = il->link) + emiti(ctx, il->i); + } + idup(ctx, b, ctx->curi, static_cast<ulong>(&ctx->insb[NIns] - ctx->curi)); + } while (b != fn->start); + + if (debug['A']) { + fprintf(ctx->dbg_buf, "\n> After ABI lowering:\n"); + printfn(fn, ctx->dbg_buf); + } +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/rv64/emit.cc b/rcc-legacy/src/backend/rv64/emit.cc new file mode 100644 index 0000000..4a0c87e --- /dev/null +++ b/rcc-legacy/src/backend/rv64/emit.cc @@ -0,0 +1,949 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file backend/rv64/emit.cc +/// @brief RISC-V 64 (lp64d) assembly emission. + +#include "rcc/rv64.h" + +#include <cinttypes> +#include <cstdio> + +namespace rcc { + +// Pseudo K values for omap wildcards. + +namespace { + +enum : short { + Ki = -1, // Matches Kw / Kl + Ka = -2, // Matches all cls +}; + +// Single instruction format templates. +struct AsmFmt { + short op; + short cls; + const char* fmt; +}; + +constexpr AsmFmt kAsmFmt[] = { + {Oadd, Ki, "add%k %=, %0, %1"}, + {Oadd, Ka, "fadd.%k %=, %0, %1"}, + {Osub, Ki, "sub%k %=, %0, %1"}, + {Osub, Ka, "fsub.%k %=, %0, %1"}, + {Oneg, Ki, "neg%k %=, %0"}, + {Oneg, Ka, "fneg.%k %=, %0"}, + {Odiv, Ki, "div%k %=, %0, %1"}, + {Odiv, Ka, "fdiv.%k %=, %0, %1"}, + {Orem, Ki, "rem%k %=, %0, %1"}, + {Orem, Kl, "rem %=, %0, %1"}, + {Oudiv, Ki, "divu%k %=, %0, %1"}, + {Ourem, Ki, "remu%k %=, %0, %1"}, + {Omul, Ki, "mul%k %=, %0, %1"}, + {Omul, Ka, "fmul.%k %=, %0, %1"}, + {Oand, Ki, "and %=, %0, %1"}, + {Oor, Ki, "or %=, %0, %1"}, + {Oxor, Ki, "xor %=, %0, %1"}, + {Osar, Ki, "sra%k %=, %0, %1"}, + {Oshr, Ki, "srl%k %=, %0, %1"}, + {Oshl, Ki, "sll%k %=, %0, %1"}, + {Ocsltl, Ki, "slt %=, %0, %1"}, + {Ocultl, Ki, "sltu %=, %0, %1"}, + {Oceqs, Ki, "feq.s %=, %0, %1"}, + {Ocges, Ki, "fge.s %=, %0, %1"}, + {Ocgts, Ki, "fgt.s %=, %0, %1"}, + {Ocles, Ki, "fle.s %=, %0, %1"}, + {Oclts, Ki, "flt.s %=, %0, %1"}, + {Oceqd, Ki, "feq.d %=, %0, %1"}, + {Ocged, Ki, "fge.d %=, %0, %1"}, + {Ocgtd, Ki, "fgt.d %=, %0, %1"}, + {Ocled, Ki, "fle.d %=, %0, %1"}, + {Ocltd, Ki, "flt.d %=, %0, %1"}, + {Ostoreb, Kw, "sb %0, %M1"}, + {Ostoreh, Kw, "sh %0, %M1"}, + {Ostorew, Kw, "sw %0, %M1"}, + {Ostorel, Ki, "sd %0, %M1"}, + {Ostores, Kw, "fsw %0, %M1"}, + {Ostored, Kw, "fsd %0, %M1"}, + {Oloadsb, Ki, "lb %=, %M0"}, + {Oloadub, Ki, "lbu %=, %M0"}, + {Oloadsh, Ki, "lh %=, %M0"}, + {Oloaduh, Ki, "lhu %=, %M0"}, + {Oloadsw, Ki, "lw %=, %M0"}, + // rv64 sign-extends 32-bit values into 64-bit registers. + {Oloaduw, Kw, "lw %=, %M0"}, + {Oloaduw, Kl, "lwu %=, %M0"}, + {Oload, Kw, "lw %=, %M0"}, + {Oload, Kl, "ld %=, %M0"}, + {Oload, Ks, "flw %=, %M0"}, + {Oload, Kd, "fld %=, %M0"}, + {Oextsb, Ki, "sext.b %=, %0"}, + {Oextub, Ki, "zext.b %=, %0"}, + {Oextsh, Ki, "sext.h %=, %0"}, + {Oextuh, Ki, "zext.h %=, %0"}, + {Oextsw, Kl, "sext.w %=, %0"}, + {Oextuw, Kl, "zext.w %=, %0"}, + {Otruncd, Ks, "fcvt.s.d %=, %0"}, + {Oexts, Kd, "fcvt.d.s %=, %0"}, + {Ostosi, Kw, "fcvt.w.s %=, %0, rtz"}, + {Ostosi, Kl, "fcvt.l.s %=, %0, rtz"}, + {Ostoui, Kw, "fcvt.wu.s %=, %0, rtz"}, + {Ostoui, Kl, "fcvt.lu.s %=, %0, rtz"}, + {Odtosi, Kw, "fcvt.w.d %=, %0, rtz"}, + {Odtosi, Kl, "fcvt.l.d %=, %0, rtz"}, + {Odtoui, Kw, "fcvt.wu.d %=, %0, rtz"}, + {Odtoui, Kl, "fcvt.lu.d %=, %0, rtz"}, + {Oswtof, Ka, "fcvt.%k.w %=, %0"}, + {Ouwtof, Ka, "fcvt.%k.wu %=, %0"}, + {Osltof, Ka, "fcvt.%k.l %=, %0"}, + {Oultof, Ka, "fcvt.%k.lu %=, %0"}, + {Ocast, Kw, "fmv.x.w %=, %0"}, + {Ocast, Kl, "fmv.x.d %=, %0"}, + {Ocast, Ks, "fmv.w.x %=, %0"}, + {Ocast, Kd, "fmv.d.x %=, %0"}, + {Ocopy, Ki, "mv %=, %0"}, + {Ocopy, Ka, "fmv.%k %=, %0"}, + {Oswap, Ki, "mv %?, %0\n\tmv %0, %1\n\tmv %1, %?"}, + {Oswap, Ka, "fmv.%k %?, %0\n\tfmv.%k %0, %1\n\tfmv.%k %1, %?"}, + {Oreqz, Ki, "seqz %=, %0"}, + {Ornez, Ki, "snez %=, %0"}, + {Ocall, Kw, "jalr %0"}, + {NOp, 0, nullptr}, +}; + +// Register names. + +constexpr auto kRegName = [] { + std::array<const char*, FT11 + 1> a{}; + a[T0] = "t0"; + a[T1] = "t1"; + a[T2] = "t2"; + a[T3] = "t3"; + a[T4] = "t4"; + a[T5] = "t5"; + a[A0] = "a0"; + a[A1] = "a1"; + a[A2] = "a2"; + a[A3] = "a3"; + a[A4] = "a4"; + a[A5] = "a5"; + a[A6] = "a6"; + a[A7] = "a7"; + a[S1] = "s1"; + a[S2] = "s2"; + a[S3] = "s3"; + a[S4] = "s4"; + a[S5] = "s5"; + a[S6] = "s6"; + a[S7] = "s7"; + a[S8] = "s8"; + a[S9] = "s9"; + a[S10] = "s10"; + a[S11] = "s11"; + a[FP] = "fp"; + a[SP] = "sp"; + a[GP] = "gp"; + a[TP] = "tp"; + a[RA] = "ra"; + a[FT0] = "ft0"; + a[FT1] = "ft1"; + a[FT2] = "ft2"; + a[FT3] = "ft3"; + a[FT4] = "ft4"; + a[FT5] = "ft5"; + a[FT6] = "ft6"; + a[FT7] = "ft7"; + a[FT8] = "ft8"; + a[FT9] = "ft9"; + a[FT10] = "ft10"; + a[FA0] = "fa0"; + a[FA1] = "fa1"; + a[FA2] = "fa2"; + a[FA3] = "fa3"; + a[FA4] = "fa4"; + a[FA5] = "fa5"; + a[FA6] = "fa6"; + a[FA7] = "fa7"; + a[FS0] = "fs0"; + a[FS1] = "fs1"; + a[FS2] = "fs2"; + a[FS3] = "fs3"; + a[FS4] = "fs4"; + a[FS5] = "fs5"; + a[FS6] = "fs6"; + a[FS7] = "fs7"; + a[FS8] = "fs8"; + a[FS9] = "fs9"; + a[FS10] = "fs10"; + a[FS11] = "fs11"; + a[T6] = "t6"; + a[FT11] = "ft11"; + return a; +}(); + +// Register name access helper. +static inline const char* regname(int r) { + return kRegName[static_cast<size_t>(r)]; +} + +constexpr char kClsChar[] = {'w', 'l', 's', 'd'}; + +// Slot offsets. +int64_t slot_offset(Ref r, Fn* fn) { + int s = rsval(r); + assert(s <= fn->slot); + if (s < 0) + return 8 * -s; + return -4 * (fn->slot - s); +} + +// CAddr symbol literals. + +void emit_addr_sym(Con* c, FILE* f) { + assert((c->sym.type & ~Sym::SExt) == Sym::SGlo); + fputs(str(c->sym.id), f); + if (c->bits.i) + fprintf(f, "+%" PRIi64, c->bits.i); +} + +// Load into specific register. + +void load_sym_into(Con* c, const char* rn, FILE* f) { + char off[32]; + switch (c->sym.type) { + case Sym::SGlo: + case Sym::SExt: + // medany compatibility. + fprintf(f, "\tla %s, ", rn); + emit_addr_sym(c, f); + fputc('\n', f); + return; + case Sym::SThr: + if (c->bits.i) + snprintf(off, sizeof(off), "+%" PRIi64, c->bits.i); + else + off[0] = 0; + fprintf(f, "\tlui %s, %%tprel_hi(%s)%s\n", rn, str(c->sym.id), off); + fprintf(f, "\tadd %s, %s, tp, %%tprel_add(%s)%s\n", rn, rn, str(c->sym.id), off); + fprintf(f, "\taddi %s, %s, %%tprel_lo(%s)%s\n", rn, rn, str(c->sym.id), off); + return; + case Sym::SExtThr: + die("extern thread unavailable on rv64"); + } +} + +void load_con_into(Con* c, int r, int k, FILE* f) { + const char* rn = regname(r); + switch (c->type) { + case Con::CAddr: + load_sym_into(c, rn, f); + return; + case Con::CBits: { + int64_t n = c->bits.i; + if (!KWIDE(k)) + n = static_cast<int32_t>(n); + fprintf(f, "\tli %s, %" PRIi64 "\n", rn, n); + return; + } + default: + die("invalid constant"); + } +} + +// Materialize memory operand into t6. + +void materialize_mem(Ref* pr, Fn* fn, FILE* f) { + Ref r = *pr; + if (rtype(r) == RCon) { + Con* c = &fn->con[r.val]; + if (c->type == Con::CAddr && c->sym.type != Sym::SGlo) { + load_con_into(c, T6, Kl, f); + *pr = Ref::tmp(static_cast<uint>(T6)); + } + } + if (rtype(r) == RSlot) { + int64_t s = slot_offset(r, fn); + if (s < -2048 || s > 2047) { + fprintf(f, "\tli t6, %" PRId64 "\n", s); + fputs("\tadd t6, fp, t6\n", f); + *pr = Ref::tmp(static_cast<uint>(T6)); + } + } +} + +// Format string expansion. + +void emit_reg_arg(Ref r, FILE* f) { + assert(isreg(r)); + fputs(kRegName[r.val], f); +} + +void emit_imm_or_reg(Ref r, Ins* /*i*/, Fn* fn, FILE* f) { + switch (rtype(r)) { + case RTmp: + emit_reg_arg(r, f); + return; + case RCon: { + Con* pc = &fn->con[r.val]; + assert(pc->type == Con::CBits); + assert(pc->bits.i >= -2048 && pc->bits.i < 2048); + fprintf(f, "%d", static_cast<int>(pc->bits.i)); + return; + } + default: + die("invalid second argument"); + } +} + +void emit_mem_arg(Ref r, Ins* i, Fn* fn, FILE* f) { + switch (rtype(r)) { + case RTmp: + fprintf(f, "0(%s)", kRegName[r.val]); + return; + case RCon: { + Con* pc = &fn->con[r.val]; + assert(pc->type == Con::CAddr); + emit_addr_sym(pc, f); + // Store / float load pseudo-instructions need a scratch register. + if (isstore(i->op) || (isload(i->op) && KBASE(i->cls) == 1)) + fputs(", t6", f); + return; + } + case RSlot: { + int64_t offset = slot_offset(r, fn); + assert(offset >= -2048 && offset <= 2047); + fprintf(f, "%d(fp)", static_cast<int>(offset)); + return; + } + case RMem: { + // Base+const addressing from isel. + Mem* m = &fn->mem[r.val]; + assert(isreg(m->base)); + assert(req(m->index, Ref::none())); + int64_t off = m->offset.type == Con::CBits ? m->offset.bits.i : 0; + assert(off >= -2048 && off <= 2047); + fprintf(f, "%d(%s)", static_cast<int>(off), kRegName[m->base.val]); + return; + } + default: + die("invalid address argument"); + } +} + +void emit_fmt(const char* s, Ins* i, Fn* fn, FILE* f) { + int k = i->cls; + fputc('\t', f); + for (;;) { + // Process characters or escape sequences. + char c; + while ((c = *s++) != '%') { + if (!c) { + fputc('\n', f); + return; + } + fputc(c, f); + } + char e = *s++; + switch (e) { + case '?': + fputs(KBASE(k) == 0 ? "t6" : "ft11", f); + break; + case 'k': + if (k != Kl) + fputc(kClsChar[k], f); + break; + case '=': + emit_reg_arg(i->to, f); + break; + case '0': + emit_reg_arg(i->arg[0], f); + break; + case '1': + emit_imm_or_reg(i->arg[1], i, fn, f); + break; + case 'M': { + char sel = *s++; + assert(sel == '0' || sel == '1'); + emit_mem_arg(i->arg[sel - '0'], i, fn, f); + break; + } + default: + die("invalid escape"); + } + } +} + +// Table-driven emit dispatch. + +void emit_from_table(Ins* i, Fn* fn, FILE* f) { + for (int o = 0;; ++o) { + if (kAsmFmt[o].op == NOp) + die("no match for %s(%c) to=t%d/v%u a0=t%d/v%u a1=t%d/v%u || %s", optab[i->op].name, + "wlsd"[i->cls], rtype(i->to), i -> to.val, rtype(i->arg[0]), i->arg[0].val, + rtype(i->arg[1]), i->arg[1].val, die_ctx_str(nullptr, i)); + if (kAsmFmt[o].op != static_cast<short>(i->op)) + continue; + short tc = kAsmFmt[o].cls; + if (tc == static_cast<short>(i->cls) || tc == Ka || (tc == Ki && KBASE(i->cls) == 0)) { + emit_fmt(kAsmFmt[o].fmt, i, fn, f); + return; + } + } +} + +// Single instruction dispatch. + +void emit_ins(Ins* i, Fn* fn, FILE* f) { + // Store with constant 0. + if (INRANGE(i->op, Ostoreb, Ostorel) && rtype(i->arg[0]) == RCon) { + Con* c = &fn->con[i->arg[0].val]; + if (c->type == Con::CBits && c->bits.i == 0) { + materialize_mem(&i->arg[1], fn, f); + const char* mn; + switch (i->op) { + case Ostoreb: + mn = "sb"; + break; + case Ostoreh: + mn = "sh"; + break; + case Ostorew: + mn = "sw"; + break; + case Ostorel: + mn = "sd"; + break; + default: + die("unreachable"); + } + fprintf(f, "\t%s zero, ", mn); + emit_mem_arg(i->arg[1], i, fn, f); + fputc('\n', f); + return; + } + } + + switch (i->op) { + default: + if (isload(i->op)) + materialize_mem(&i->arg[0], fn, f); + else if (isstore(i->op)) + materialize_mem(&i->arg[1], fn, f); + emit_from_table(i, fn, f); + return; + + case Ocopy: { + if (req(i->to, i->arg[0])) + return; + + if (rtype(i->to) == RSlot) { + int rt = rtype(i->arg[0]); + if (rt == RSlot) { + // SLOT -> SLOT bridge. + int64_t off_src = slot_offset(i->arg[0], fn); + int64_t off_dst = slot_offset(i->to, fn); + const char* scratch = KBASE(i->cls) ? "ft11" : "t6"; + const char* ld; + const char* st; + switch (i->cls) { + case Kw: + ld = "lw"; + st = "sw"; + break; + case Kl: + ld = "ld"; + st = "sd"; + break; + case Ks: + ld = "flw"; + st = "fsw"; + break; + case Kd: + ld = "fld"; + st = "fsd"; + break; + default: + die("Ocopy SLOT->SLOT illegal cls=%d", i->cls); + } + // Check for large offsets. + if (off_src < -2048 || off_src > 2047 || off_dst < -2048 || off_dst > 2047) + die("Ocopy SLOT->SLOT large offset NYI off_src=%" PRId64 " off_dst=%" PRId64 + " || %s", + off_src, off_dst, die_ctx_str(nullptr, i)); + fprintf(f, "\t%s %s, %" PRId64 "(fp)\n", ld, scratch, off_src); + fprintf(f, "\t%s %s, %" PRId64 "(fp)\n", st, scratch, off_dst); + return; + } + if (rt == RCon) + die("unimplemented Ocopy SLOT-dst cls=%c to=t%d/v%u a0=t%d/v%u || %s", + "wlsd"[i->cls], rtype(i->to), i -> to.val, rtype(i->arg[0]), i->arg[0].val, + die_ctx_str(nullptr, i)); + assert(isreg(i->arg[0])); + Ins ii = *i; + ii.arg[1] = ii.to; + ii.to = Ref::none(); + switch (ii.cls) { + case Kw: + ii.op = Ostorew; + break; + case Kl: + ii.op = Ostorel; + break; + case Ks: + ii.op = Ostores; + break; + case Kd: + ii.op = Ostored; + break; + } + // Revert dopm Ks/Kd to Kw for Ostore*. + ii.cls = Kw; + materialize_mem(&ii.arg[1], fn, f); + emit_from_table(&ii, fn, f); + return; + } + + assert(isreg(i->to)); + switch (rtype(i->arg[0])) { + case RCon: + load_con_into(&fn->con[i->arg[0].val], static_cast<int>(i->to.val), + static_cast<int>(i->cls), f); + return; + case RSlot: { + Ins ii = *i; + ii.op = Oload; + materialize_mem(&ii.arg[0], fn, f); + emit_from_table(&ii, fn, f); + return; + } + default: + assert(isreg(i->arg[0])); + emit_from_table(i, fn, f); + return; + } + } + + case Onop: + return; + + case Oaddr: { + const char* rn = kRegName[i->to.val]; + switch (rtype(i->arg[0])) { + case RSlot: { + int64_t s = slot_offset(i->arg[0], fn); + if (-s < 2048) { + fprintf(f, "\tadd %s, fp, %" PRId64 "\n", rn, s); + } else { + fprintf(f, "\tli %s, %" PRId64 "\n", rn, s); + fprintf(f, "\tadd %s, fp, %s\n", rn, rn); + } + return; + } + case RCon: { + // addr $sym generated from IRGen. + Con* c = &fn->con[i->arg[0].val]; + assert(c->type == Con::CAddr); + load_sym_into(c, rn, f); + return; + } + default: + die("invalid Oaddr argument"); + } + } + + case Ocall: + switch (rtype(i->arg[0])) { + case RCon: { + Con* con = &fn->con[i->arg[0].val]; + if (con->type != Con::CAddr || (con->sym.type & Sym::SThr) || con->bits.i) + die("invalid call argument"); + fprintf(f, "\tcall %s\n", str(con->sym.id)); + return; + } + case RTmp: + emit_fmt("jalr %0", i, fn, f); + return; + default: + die("invalid call argument"); + } + + case Osalloc: + emit_fmt("sub sp, sp, %0", i, fn, f); + if (!req(i->to, Ref::none())) + emit_fmt("mv %=, sp", i, fn, f); + return; + + case Odbgloc: + emitdbgloc(i->arg[0].val, i->arg[1].val, f); + return; + } +} + +// Function prologue / epilogue helpers. + +void save_clobbers(Fn* fn, FILE* f) { + int off = 0; + for (int* pr = rv64_rclob; *pr >= 0; ++pr) { + if (!(fn->reg & BIT(*pr))) + continue; + fprintf(f, "\t%s %s, %d(sp)\n", *pr < FT0 ? "sd" : "fsd", regname(*pr), off); + off += 8; + } +} + +void restore_clobbers(Fn* fn, FILE* f) { + int off = 0; + for (int* pr = rv64_rclob; *pr >= 0; ++pr) { + if (!(fn->reg & BIT(*pr))) + continue; + fprintf(f, "\t%s %s, %d(sp)\n", *pr < FT0 ? "ld" : "fld", regname(*pr), off); + off += 8; + } +} + +// Compute total stack frame size. +int compute_frame_size(Fn* fn) { + int frame = (16 + 4 * fn->slot + 15) & ~15; + for (int* pr = rv64_rclob; *pr >= 0; ++pr) { + if (fn->reg & BIT(*pr)) + frame += 8; + } + return (frame + 15) & ~15; +} + +// Adjust sp by delta. +void emit_sp_adjust(const char* op, const char* base, int delta, FILE* f) { + if (delta <= 2048) { + fprintf(f, "\t%s sp, %s, -%d\n", op, base, delta); + } else { + fprintf(f, "\tli t6, -%d\n\t%s sp, %s, t6\n", delta, op, base); + } +} + +// Fused cmp+jnz -> single conditional branch. + +struct BrCfg { + const char* mn; // rv64 branch mnemonic. + bool swap; // Swap operands. +}; + +// Branch mnemonic for each condition code. +constexpr BrCfg kBrCfg[NCmpI] = { + /* Cieq */ {"beq", false}, /* Cine */ {"bne", false}, + /* Cisge */ {"bge", false}, /* Cisgt */ {"blt", true}, + /* Cisle */ {"bge", true}, /* Cislt */ {"blt", false}, + /* Ciuge */ {"bgeu", false}, /* Ciugt */ {"bltu", true}, + /* Ciule */ {"bgeu", true}, /* Ciult */ {"bltu", false}, +}; + +// Logical negation of condition codes. +constexpr int kCmpNeg[NCmpI] = { + /* Cieq */ Cine, /* Cine */ Cieq, /* Cisge */ Cislt, /* Cisgt */ Cisle, + /* Cisle */ Cisgt, /* Cislt */ Cisge, /* Ciuge */ Ciult, /* Ciugt */ Ciule, + /* Ciule */ Ciugt, /* Ciult */ Ciuge, +}; + +// Resolve fused branch operand. +const char* branch_operand(Ref r, int ck, Fn* fn, FILE* f, bool* slot_used, Blk* b) { + if (rtype(r) == RSlot) { + if (*slot_used) + die("fused branch: both operands spilled @%d", static_cast<int>(b->id)); + Ins ii = {}; + ii.arg[0] = r; + emit_fmt(KWIDE(ck) ? "ld t6, %M0" : "lw t6, %M0", &ii, fn, f); + *slot_used = true; + return kRegName[T6]; + } + assert(isreg(r)); + return kRegName[r.val]; +} + +} // namespace + +// rv64_emitfn: Function entry point. + +namespace { +// Global block ID base. +int g_blkid_base = 0; +} // namespace + +void rv64_emitfn(Fn* fn, FILE* f) { + emitfnlnk(fn->name, &fn->lnk, f); + + // Prologue. + if (fn->vararg) { + // TODO: only allocate for a* unused by named arguments. + fputs("\tadd sp, sp, -64\n", f); + for (int r = A0; r <= A7; ++r) + fprintf(f, "\tsd %s, %d(sp)\n", regname(r), 8 * (r - A0)); + } + fputs("\tsd fp, -16(sp)\n", f); + fputs("\tsd ra, -8(sp)\n", f); + fputs("\tadd fp, sp, -16\n", f); + + int frame = compute_frame_size(fn); + emit_sp_adjust("add", "sp", frame, f); + save_clobbers(fn, f); + + // Body. + bool need_label = false; + for (Blk* b = fn->start; b; b = b->link) { + if (need_label || b->npred > 1) + fprintf(f, ".L%d:\n", g_blkid_base + static_cast<int>(b->id)); + for (Ins* i = b->ins; i != b->ins + b->nins; ++i) + emit_ins(i, fn, f); + need_label = true; + + switch (b->jmp.type) { + case Jhlt: + fputs("\tebreak\n", f); + break; + + case Jret0: + if (fn->dynalloc) + emit_sp_adjust("add", "fp", frame - 16, f); + restore_clobbers(fn, f); + fprintf(f, + "\tadd sp, fp, %d\n" + "\tld ra, 8(fp)\n" + "\tld fp, 0(fp)\n" + "\tret\n", + 16 + fn->vararg * 64); + break; + + case Jjmp: + emit_jmp: + if (b->s1 != b->link) + fprintf(f, "\tj .L%d\n", g_blkid_base + static_cast<int>(b->s1->id)); + else + need_label = false; + break; + + case Jjnz: { + // Ordering optimization for fall-through. + bool neg = false; + if (b->link == b->s2) { + Blk* s = b->s1; + b->s1 = b->s2; + b->s2 = s; + neg = true; + } + if (rtype(b->jmp.arg) == RSlot) { + Ins ii = {}; + ii.arg[0] = b->jmp.arg; + emit_fmt("lw t6, %M0", &ii, fn, f); + b->jmp.arg = Ref::tmp(static_cast<uint>(T6)); + } + assert(isreg(b->jmp.arg)); + fprintf(f, "\tb%sz %s, .L%d\n", neg ? "ne" : "eq", kRegName[b->jmp.arg.val], + g_blkid_base + static_cast<int>(b->s2->id)); + goto emit_jmp; + } + + default: + // Fused integer cmp+jnz. + if (b->jmp.type >= Jjfieq && b->jmp.type <= Jjfiult) { + int cc = static_cast<int>(b->jmp.type) - Jjfieq; + int ck = b->jmp.cls; + + bool neg = false; + if (b->link == b->s2) { + Blk* s = b->s1; + b->s1 = b->s2; + b->s2 = s; + neg = true; + } + BrCfg cfg = kBrCfg[neg ? cc : kCmpNeg[cc]]; + + bool slot_used = false; + const char* r0 = branch_operand(b->jmp.arg, ck, fn, f, &slot_used, b); + const char* r1 = branch_operand(b->jmp.arg2, ck, fn, f, &slot_used, b); + if (cfg.swap) { + const char* t = r0; + r0 = r1; + r1 = t; + } + fprintf(f, "\t%s %s, %s, .L%d\n", cfg.mn, r0, r1, + g_blkid_base + static_cast<int>(b->s2->id)); + goto emit_jmp; + } + break; + } + } + + g_blkid_base += static_cast<int>(fn->nblk); + elf_emitfnfin(fn->name, f); +} + +// Built-in runtime. + +namespace { + +void emit_runtime_memset(FILE* f) { + fprintf(f, + ".globl __rcc_memset\n" + "__rcc_memset:\n" + "\tandi a1, a1, 0xff\n" + "\tslli t0, a1, 8\n" + "\tor a1, a1, t0\n" + "\tslli t0, a1, 16\n" + "\tor a1, a1, t0\n" + "\tslli t0, a1, 32\n" + "\tor a1, a1, t0\n" + "\tmv t1, a0\n" + "\tli t2, 64\n" + ".Lrcc_ms64:\n" + "\tblt a2, t2, .Lrcc_ms8\n" + "\tsd a1, 0(t1)\n" + "\tsd a1, 8(t1)\n" + "\tsd a1, 16(t1)\n" + "\tsd a1, 24(t1)\n" + "\tsd a1, 32(t1)\n" + "\tsd a1, 40(t1)\n" + "\tsd a1, 48(t1)\n" + "\tsd a1, 56(t1)\n" + "\taddi t1, t1, 64\n" + "\taddi a2, a2, -64\n" + "\tj .Lrcc_ms64\n" + ".Lrcc_ms8:\n" + "\tli t2, 8\n" + ".Lrcc_ms8l:\n" + "\tblt a2, t2, .Lrcc_ms1\n" + "\tsd a1, 0(t1)\n" + "\taddi t1, t1, 8\n" + "\taddi a2, a2, -8\n" + "\tj .Lrcc_ms8l\n" + ".Lrcc_ms1:\n" + "\tbeqz a2, .Lrcc_msd\n" + "\tsb a1, 0(t1)\n" + "\taddi t1, t1, 1\n" + "\taddi a2, a2, -1\n" + "\tj .Lrcc_ms1\n" + ".Lrcc_msd:\n" + "\tret\n" + ".type __rcc_memset, @function\n" + ".size __rcc_memset, .-__rcc_memset\n\n"); +} + +void emit_runtime_memcpy(FILE* f) { + fprintf(f, + ".globl __rcc_memcpy\n" + "__rcc_memcpy:\n" + "\tmv t1, a0\n" + ".Lrcc_mc32:\n" + "\tli t2, 32\n" + "\tblt a2, t2, .Lrcc_mc8\n" + "\tld t3, 0(a1)\n" + "\tld t4, 8(a1)\n" + "\tld t5, 16(a1)\n" + "\tld t6, 24(a1)\n" + "\tsd t3, 0(t1)\n" + "\tsd t4, 8(t1)\n" + "\tsd t5, 16(t1)\n" + "\tsd t6, 24(t1)\n" + "\taddi a1, a1, 32\n" + "\taddi t1, t1, 32\n" + "\taddi a2, a2, -32\n" + "\tj .Lrcc_mc32\n" + ".Lrcc_mc8:\n" + "\tli t2, 8\n" + ".Lrcc_mc8l:\n" + "\tblt a2, t2, .Lrcc_mc1\n" + "\tld t3, 0(a1)\n" + "\tsd t3, 0(t1)\n" + "\taddi a1, a1, 8\n" + "\taddi t1, t1, 8\n" + "\taddi a2, a2, -8\n" + "\tj .Lrcc_mc8l\n" + ".Lrcc_mc1:\n" + "\tbeqz a2, .Lrcc_mcd\n" + "\tlb t3, 0(a1)\n" + "\tsb t3, 0(t1)\n" + "\taddi a1, a1, 1\n" + "\taddi t1, t1, 1\n" + "\taddi a2, a2, -1\n" + "\tj .Lrcc_mc1\n" + ".Lrcc_mcd:\n" + "\tret\n" + ".type __rcc_memcpy, @function\n" + ".size __rcc_memcpy, .-__rcc_memcpy\n\n"); +} + +void emit_runtime_memmove(FILE* f) { + fprintf(f, + ".globl __rcc_memmove\n" + "__rcc_memmove:\n" + "\tbgeu a0, a1, .Lrcc_mm_chk\n" + "\tj __rcc_memcpy\n" + ".Lrcc_mm_chk:\n" + "\tadd t0, a1, a2\n" + "\tbgeu a0, t0, __rcc_memcpy\n" + "\tadd t1, a0, a2\n" + "\tadd a1, a1, a2\n" + ".Lrcc_mmb32:\n" + "\tli t2, 32\n" + "\tblt a2, t2, .Lrcc_mmb8\n" + "\taddi t1, t1, -32\n" + "\taddi a1, a1, -32\n" + "\tld t3, 0(a1)\n" + "\tld t4, 8(a1)\n" + "\tld t5, 16(a1)\n" + "\tld t6, 24(a1)\n" + "\tsd t3, 0(t1)\n" + "\tsd t4, 8(t1)\n" + "\tsd t5, 16(t1)\n" + "\tsd t6, 24(t1)\n" + "\taddi a2, a2, -32\n" + "\tj .Lrcc_mmb32\n" + ".Lrcc_mmb8:\n" + "\tli t2, 8\n" + ".Lrcc_mmb8l:\n" + "\tblt a2, t2, .Lrcc_mmb1\n" + "\taddi t1, t1, -8\n" + "\taddi a1, a1, -8\n" + "\tld t3, 0(a1)\n" + "\tsd t3, 0(t1)\n" + "\taddi a2, a2, -8\n" + "\tj .Lrcc_mmb8l\n" + ".Lrcc_mmb1:\n" + "\tbeqz a2, .Lrcc_mmd\n" + "\taddi t1, t1, -1\n" + "\taddi a1, a1, -1\n" + "\tlb t3, 0(a1)\n" + "\tsb t3, 0(t1)\n" + "\taddi a2, a2, -1\n" + "\tj .Lrcc_mmb1\n" + ".Lrcc_mmd:\n" + "\tret\n" + ".type __rcc_memmove, @function\n" + ".size __rcc_memmove, .-__rcc_memmove\n\n"); +} + +} // namespace + +void rv64_emitfin(Ctx* ctx, FILE* f) { + fprintf(f, ".text\n"); + emit_runtime_memset(f); + emit_runtime_memcpy(f); + emit_runtime_memmove(f); + elf_emitfin(ctx, f); +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/rv64/isel.cc b/rcc-legacy/src/backend/rv64/isel.cc new file mode 100644 index 0000000..c2ded02 --- /dev/null +++ b/rcc-legacy/src/backend/rv64/isel.cc @@ -0,0 +1,518 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file isel.cc +/// @brief RISC-V 64 instruction selection. + +#include "rcc/rv64.h" + +#include <cinttypes> +#include <climits> +#include <cstdio> + +namespace rcc { +namespace { + +// Operand role classification. + +// is_mem_arg: Does the arg slot pointed to by r have "memory operand" semantics? +bool is_mem_arg(Ref* r, int op, Ins* i) { + if (isload(op) || op == Ocall) + return r == &i->arg[0]; + if (isstore(op)) + return r == &i->arg[1]; + return false; +} + +// is_imm_arg: Is r the immediate slot for this op? +bool is_imm_arg(Ref* r, int op, Ins* i) { + return rv64_op[op].imm && r == &i->arg[1]; +} + +// Compare op -> (sign, swap, neg) tuple. + +struct CmpCfg { + uint8_t sign; // 1=signed (Ocsltl), 0=unsigned (Ocultl) + uint8_t swap; // 1=swap arg0/arg1 + uint8_t neg; // 1=negate i.to +}; + +constexpr CmpCfg kIntOrderCmp[8] = { + /* Cisge */ {1, 0, 1}, // a >= b ⇔ !(a < b) + /* Cisgt */ {1, 1, 0}, // a > b ⇔ (b < a) + /* Cisle */ {1, 1, 1}, // a <= b ⇔ !(b < a) + /* Cislt */ {1, 0, 0}, // a < b + /* Ciuge */ {0, 0, 1}, + /* Ciugt */ {0, 1, 0}, + /* Ciule */ {0, 1, 1}, + /* Ciult */ {0, 0, 0}, +}; + +// Utilities. + +// boolean negate: emit `xor pr, src, 1` +void negate_to(Ctx* ctx, Ref* pr, Fn* fn) { + Ref src = newtmp(ctx, "isel", Kw, fn); + emit(ctx, Oxor, Kw, *pr, src, getcon(ctx, 1, fn)); + *pr = src; +} + +// fixarg: operand fixup. + +// Fix constant operand. +void fix_con_arg(Ctx* ctx, Ref* r, int k, Ins* i, int op, Fn* fn) { + Con* c = &fn->con[r->val]; + + // CAddr as mem operand: no materialization needed. + if (c->type == Con::CAddr && is_mem_arg(r, op, i)) + return; + + // Integer immediate within I-form range. + if (KBASE(k) == 0 && c->type == Con::CBits && is_imm_arg(r, op, i)) { + if (-2048 <= c->bits.i && c->bits.i < 2048) + return; + } + + // Integer store value of 0: emit `s* zero, ...` without materialization. + if (i && c->type == Con::CBits && c->bits.i == 0 && r == &i->arg[0] && + INRANGE(op, Ostoreb, Ostorel)) { + return; + } + + Ref dst = newtmp(ctx, "isel", k, fn); + + if (KBASE(k) == 1) { + // Floating-point constants must be stashed to ro data section. + assert(c->type == Con::CBits); + int slot_id = stashbits(ctx, static_cast<bits>(c->bits.i), KWIDE(k) ? 8 : 4); + + vgrow(ctx, &fn->con, static_cast<ulong>(++fn->ncon)); + Con* nc = &fn->con[fn->ncon - 1]; + *nc = {}; + nc->type = Con::CAddr; + + char buf[32]; + snprintf(buf, sizeof(buf), "\"%sfp%d\"", T.asloc, slot_id); + nc->sym.id = intern(buf); + + emit(ctx, Oload, k, dst, Ref::con(static_cast<uint>(nc - fn->con)), Ref::none()); + } else { + // Integer constants out of imm range use Ocopy. + emit(ctx, Ocopy, k, dst, *r, Ref::none()); + } + + *r = dst; +} + +// Operations that do not require sext.w when Kw is input. +bool skip_sext_for_kw(int op) { + switch (op) { + // *w-form arithmetic. + case Oadd: + case Osub: + case Omul: + case Odiv: + case Orem: + case Oudiv: + case Ourem: + case Osar: + case Oshr: + case Oshl: + // Narrow stores. + case Ostoreb: + case Ostoreh: + case Ostorew: + // Extensions. + case Oextsb: + case Oextub: + case Oextsh: + case Oextuh: + case Oextsw: + case Oextuw: + // int->float conversions. + case Oswtof: + case Ouwtof: + return true; + default: + return false; + } +} + +// Fix temporary operand. +void fix_tmp_arg(Ctx* ctx, Ref* r, int k, Ins* i, int op, Fn* fn) { + if (isreg(*r)) + return; + + int s = fn->tmp[r->val].slot; + if (s != -1) { + // Tmp with slot allocated. + if (is_mem_arg(r, op, i)) { + *r = Ref::slot(s); + } else { + Ref dst = newtmp(ctx, "isel", k, fn); + emit(ctx, Oaddr, k, dst, Ref::slot(s), Ref::none()); + *r = dst; + } + return; + } + + // Tmp is Kl but used as Kw. + if (k == Kw && fn->tmp[r->val].cls == Kl) { + if (!skip_sext_for_kw(op)) { + Ref dst = newtmp(ctx, "isel", k, fn); + emit(ctx, Oextsw, Kl, dst, *r, Ref::none()); + *r = dst; + } + // Skip sext.w. + return; + } + + assert(k == fn->tmp[r->val].cls); +} + +void fixarg(Ctx* ctx, Ref* r, int k, Ins* i, Fn* fn) { + int op = i ? static_cast<int>(i->op) : Ocopy; + switch (rtype(*r)) { + case RCon: + fix_con_arg(ctx, r, k, i, op, fn); + break; + case RTmp: + fix_tmp_arg(ctx, r, k, i, op, fn); + break; + default: + // RSlot/RType/RCall/RMem/RInt/RNone don't need fixing. + break; + } +} + +// Fixarg both operands of the instruction. +void fixarg2(Ctx* ctx, int k, Fn* fn) { + Ins* h = ctx->curi; + fixarg(ctx, &h->arg[0], k, h, fn); + fixarg(ctx, &h->arg[1], k, h, fn); +} + +// Compare selection. + +void selcmp(Ctx* ctx, Ins i, int k, int op, Fn* fn) { + assert(op >= 0 && op < NCmp); + + // (1) Integer eq/ne. + if (op == Cieq || op == Cine) { + Ref r = newtmp(ctx, "isel", k, fn); + emit(ctx, op == Cieq ? Oreqz : Ornez, static_cast<int>(i.cls), i.to, r, Ref::none()); + emit(ctx, Oxor, k, r, i.arg[0], i.arg[1]); + fixarg2(ctx, k, fn); + return; + } + + // (2) Integer ordered comparisons. + if (op < NCmpI) { + const CmpCfg cfg = kIntOrderCmp[op - Cisge]; + i.op = static_cast<uint>(cfg.sign ? Ocsltl : Ocultl); + if (cfg.swap) { + Ref t = i.arg[0]; + i.arg[0] = i.arg[1]; + i.arg[1] = t; + } + if (cfg.neg) + negate_to(ctx, &i.to, fn); + emiti(ctx, i); + fixarg2(ctx, k, fn); + return; + } + + // (3) Floating point comparisons. + int fop = op - NCmpI; + + // Cfo (ordered) / Cfuo (unordered). + if (fop == Cfo || fop == Cfuo) { + if (fop == Cfuo) + negate_to(ctx, &i.to, fn); + Ref r0 = newtmp(ctx, "isel", static_cast<int>(i.cls), fn); + Ref r1 = newtmp(ctx, "isel", static_cast<int>(i.cls), fn); + int ceq_op = KWIDE(k) ? Oceqd : Oceqs; + emit(ctx, Oand, static_cast<int>(i.cls), i.to, r0, r1); + emit(ctx, ceq_op, static_cast<int>(i.cls), r0, i.arg[0], i.arg[0]); + fixarg2(ctx, k, fn); + emit(ctx, ceq_op, static_cast<int>(i.cls), r1, i.arg[1], i.arg[1]); + fixarg2(ctx, k, fn); + return; + } + + // Cfne: a != b -> !(a == b). + if (fop == Cfne) { + i.op = static_cast<uint>(KWIDE(k) ? Oceqd : Oceqs); + negate_to(ctx, &i.to, fn); + } + // Remaining Cfeq/Cfge/Cfgt/Cfle/Cflt. + + emiti(ctx, i); + fixarg2(ctx, k, fn); +} + +// Load/store constant offset folding. + +Ref new_offset_mem(Ctx* ctx, Fn* fn, Ref base, int64_t off) { + vensure(ctx, &fn->mem, static_cast<ulong>(fn->nmem + 1), sizeof(Mem), PFn); + Mem* m = &fn->mem[fn->nmem]; + *m = {}; + m->offset.type = Con::CBits; + m->offset.bits.i = off; + m->base = base; + m->index = Ref::none(); + m->scale = 1; + return Ref::mem(static_cast<uint>(fn->nmem++)); +} + +// Reverse fold Oaddr -> CAddr. +[[maybe_unused]] void fold_addr_into_mem(Ctx* ctx, Ref* paddr, uint bid, Fn* fn) { + if (rtype(*paddr) == RTmp) { + Tmp* t = &fn->tmp[paddr->val]; + Ins* def = t->def; + if (!def || static_cast<int>(def->op) != Oaddr) + return; + if (t->nuse != 1 || t->bid != bid) + return; + if (rtype(def->arg[0]) != RCon) + return; + Con* c = &fn->con[def->arg[0].val]; + if (c->type != Con::CAddr) + return; + *paddr = def->arg[0]; + def->op = Onop; + return; + } + if (rtype(*paddr) == RMem) { + Mem* m = &fn->mem[paddr->val]; + if (rtype(m->base) != RTmp) + return; + if (!req(m->index, Ref::none())) + return; + if (m->offset.type != Con::CBits) + return; + Tmp* t = &fn->tmp[m->base.val]; + Ins* def = t->def; + if (!def || static_cast<int>(def->op) != Oaddr) + return; + if (t->nuse != 1 || t->bid != bid) + return; + if (rtype(def->arg[0]) != RCon) + return; + Con* c = &fn->con[def->arg[0].val]; + if (c->type != Con::CAddr) + return; + // Create new CAddr combining bits. + vgrow(ctx, &fn->con, static_cast<ulong>(fn->ncon + 1)); + Con* nc = &fn->con[fn->ncon]; + *nc = *c; + nc->bits.i = c->bits.i + m->offset.bits.i; + uint cidx = static_cast<uint>(fn->ncon++); + *paddr = Ref::con(cidx); + def->op = Onop; + return; + } +} + +void fold_mem_offset(Ctx* ctx, Ref* paddr, uint bid, Fn* fn) { + if (rtype(*paddr) != RTmp) + return; + Tmp* t = &fn->tmp[paddr->val]; + Ins* def = t->def; + if (!def || static_cast<int>(def->op) != Oadd || static_cast<int>(def->cls) != Kl) + return; + if (t->nuse != 1 || t->bid != bid) + return; + + // Oadd is commutative. + Ref base, koff; + if (rtype(def->arg[0]) == RTmp && rtype(def->arg[1]) == RCon) { + base = def->arg[0]; + koff = def->arg[1]; + } else if (rtype(def->arg[0]) == RCon && rtype(def->arg[1]) == RTmp) { + base = def->arg[1]; + koff = def->arg[0]; + } else { + return; + } + + Con* c = &fn->con[koff.val]; + if (c->type != Con::CBits) + return; + int64_t off = c->bits.i; + if (off < -2048 || off > 2047) + return; + + // base must be a normal tmp (value is address). + if (fn->tmp[base.val].slot != -1) + return; + + *paddr = new_offset_mem(ctx, fn, base, off); + def->op = Onop; +} + +// Single instruction selection. + +void sel(Ctx* ctx, Ins i, Blk* b, Fn* fn) { + // Oalloc{4,8,16} non-constant sizes. + if (INRANGE(i.op, Oalloc, Oalloc1)) { + Ins* head = ctx->curi - 1; + salloc(ctx, i.to, i.arg[0], fn); + fixarg(ctx, &head->arg[0], Kl, head, fn); + return; + } + + int ck, cc; + if (iscmp(static_cast<int>(i.op), &ck, &cc)) { + selcmp(ctx, i, ck, cc, fn); + return; + } + + // Consume Onop. + if (i.op == Onop) + return; + + // Oaddr from IRGen. + if (i.op == Oaddr) { + emiti(ctx, i); + return; + } + + // Load/store constant offset folding. + int op = static_cast<int>(i.op); + Ref* paddr = nullptr; + if (isload(op)) + paddr = &i.arg[0]; + else if (isstore(op)) + paddr = &i.arg[1]; + if (paddr) { + fold_mem_offset(ctx, paddr, b->id, fn); + } + + emiti(ctx, i); + Ins* h = ctx->curi; // capture head. + fixarg(ctx, &h->arg[0], argcls(&i, 0), h, fn); + fixarg(ctx, &h->arg[1], argcls(&i, 1), h, fn); +} + +// Jump selection. + +// cmp+jnz fusion. +inline short cmp_to_jjfi(int cc) { + assert(cc >= 0 && cc < NCmpI); + return static_cast<short>(Jjfieq + cc); +} + +void seljmp(Ctx* ctx, Blk* b, Fn* fn) { + if (b->jmp.type != Jjnz) + return; + + if (rtype(b->jmp.arg) == RTmp) { + Tmp* t = &fn->tmp[b->jmp.arg.val]; + Ins* def = t->def; + if (t->nuse == 1 && def && t->bid == b->id) { + int ck, cc; + if (iscmp(static_cast<int>(def->op), &ck, &cc) && cc < NCmpI) { + b->jmp.type = cmp_to_jjfi(cc); + b->jmp.cls = static_cast<short>(ck); + b->jmp.arg = def->arg[0]; + b->jmp.arg2 = def->arg[1]; + def->op = Onop; + fixarg(ctx, &b->jmp.arg, ck, nullptr, fn); + fixarg(ctx, &b->jmp.arg2, ck, nullptr, fn); + return; + } + } + } + fixarg(ctx, &b->jmp.arg, Kw, nullptr, fn); +} + +// Pass A: fast alloc -> slot. + +void assign_fast_alloc_slots(Fn* fn) { + Blk* b = fn->start; + + int align_bytes = 4; + for (int op = Oalloc; op <= Oalloc1; ++op, align_bytes *= 2) { + Ins* ie = b->ins + b->nins; + for (Ins* i = b->ins; i < ie; ++i) { + if (static_cast<int>(i->op) != op) + continue; + if (rtype(i->arg[0]) != RCon) + continue; + + int64_t sz = fn->con[i->arg[0].val].bits.i; + if (sz < 0 || sz >= INT_MAX - 15) + err(const_cast<char*>("invalid alloc size %" PRId64), sz); + + sz = (sz + align_bytes - 1) & -align_bytes; + sz /= 4; + if (sz > INT_MAX - fn->slot) + die("alloc too large"); + + fn->tmp[i->to.val].slot = fn->slot; + fn->slot += static_cast<int>(sz); + + *i = Ins{}; + i->op = Onop; + } + } +} + +} // namespace + +// rv64_isel: Main entry point. + +void rv64_isel(Ctx* ctx, Fn* fn) { + assign_fast_alloc_slots(fn); + + for (Blk* b = fn->start; b; b = b->link) { + ctx->curi = ctx->insb + NIns; + + // Phi nodes of successors. + Blk* succ[3] = {b->s1, b->s2, nullptr}; + for (Blk** ps = succ; *ps; ++ps) { + for (Phi* p = (*ps)->phi; p; p = p->link) { + uint n = 0; + while (p->blk[n] != b) { + ++n; + assert(n < p->narg); + } + fixarg(ctx, &p->arg[n], static_cast<int>(p->cls), nullptr, fn); + } + } + + seljmp(ctx, b, fn); + + // Reverse iteration for emit prepending. + for (Ins* i = b->ins + b->nins; i != b->ins;) { + --i; + sel(ctx, *i, b, fn); + } + + idup(ctx, b, ctx->curi, static_cast<ulong>(ctx->insb + NIns - ctx->curi)); + } + + if (debug['I']) { + fprintf(ctx->dbg_buf, "\n> After instruction selection:\n"); + printfn(fn, ctx->dbg_buf); + } +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/rv64/targ.cc b/rcc-legacy/src/backend/rv64/targ.cc new file mode 100644 index 0000000..4d0ab8c --- /dev/null +++ b/rcc-legacy/src/backend/rv64/targ.cc @@ -0,0 +1,142 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file targ.cc +/// @brief RISC-V 64 lp64d target descriptor. + +#include "rcc/rv64.h" + +namespace rcc { + +// Per-op attribute table. +Rv64Op rv64_op[NOp]; + +namespace { +struct Rv64OpInit { + Rv64OpInit() { + for (size_t i = 0; i < NOp; ++i) + rv64_op[i].imm = kops[i].imm; + } +} rv64_op_init_; +} // namespace + +// Caller-save list for rega/spill scanning. + +int rv64_rsave[] = { + // GP segment (NGPS = 14): args + temps + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + T0, + T1, + T2, + T3, + T4, + T5, + + // FP segment (NFPS = 19): temps + args + FT0, + FT1, + FT2, + FT3, + FT4, + FT5, + FT6, + FT7, + FT8, + FT9, + FT10, + FA0, + FA1, + FA2, + FA3, + FA4, + FA5, + FA6, + FA7, + + -1, +}; + +// Callee-save list for prologue/epilogue generation. + +int rv64_rclob[] = { + S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, FS0, + FS1, FS2, FS3, FS4, FS5, FS6, FS7, FS8, FS9, FS10, FS11, -1, +}; + +// Global live registers bitmask. + +namespace { +constexpr bits kRv64GlobLive = BIT(FP) | BIT(SP) | BIT(GP) | BIT(TP) | BIT(RA); + +// RV64 ISA has no memory operand instructions. +int memargs_none(int /*op*/) { + return 0; +} +} // namespace + +// Target descriptor. + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++20-designator" +#endif + +Target T_rv64 = { + .name = "rv64", + .apple = 0, + .windows = 0, + .gpr0 = T0, + .ngpr = NGPR, + .fpr0 = FT0, + .nfpr = NFPR, + .rglob = kRv64GlobLive, + .nrglob = 5, + .rsave = rv64_rsave, + .nrsave = {NGPS, NFPS}, + .retregs = rv64_retregs, + .argregs = rv64_argregs, + .memargs = memargs_none, + .abi0 = elimsb, + .abi1 = rv64_abi, + .isel = rv64_isel, + .emitfn = rv64_emitfn, + .emitfin = rv64_emitfin, + .asloc = ".L", + .assym = "", + .cansel = 0, +}; + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + +// Table size consistency checks. + +MAKESURE(rv64_rsave_size_ok, + sizeof(rv64_rsave) / sizeof(int) == static_cast<size_t>(NGPS + NFPS + 1)); +MAKESURE(rv64_rclob_size_ok, sizeof(rv64_rclob) / sizeof(int) == static_cast<size_t>(NCLR + 1)); + +} // namespace rcc diff --git a/rcc-legacy/src/backend/simpl.cc b/rcc-legacy/src/backend/simpl.cc new file mode 100644 index 0000000..0ec2082 --- /dev/null +++ b/rcc-legacy/src/backend/simpl.cc @@ -0,0 +1,176 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file backend/simpl.cc +/// @brief Frontend simplification pass. + +#include "rcc/all.h" + +#include <cinttypes> +#include <climits> +#include <cstdlib> + +namespace rcc { + +// Memory block copy expansion: Oblit pair -> explicit load/store chain. +namespace { + +struct BlitTier { + int store_op; + int load_op; + int load_cls; + int width; +}; + +constexpr BlitTier kBlitTiers[] = { + {Ostorel, Oload, Kl, 8}, + {Ostorew, Oload, Kw, 4}, + {Ostoreh, Oloaduh, Kw, 2}, + {Ostoreb, Oloadub, Kw, 1}, +}; + +void blit(Ctx* ctx, Ref sd[2], int signed_sz, Fn* fn) { + bool fwd = signed_sz >= 0; + int sz = std::abs(signed_sz); + int off = fwd ? sz : 0; + + for (const BlitTier& t : kBlitTiers) { + while (sz >= t.width) { + if (fwd) + off -= t.width; + + Ref val = newtmp(ctx, "blt", Kl, fn); + Ref dst_addr = newtmp(ctx, "blt", Kl, fn); + Ref off_con = getcon(ctx, off, fn); + + // Reverse emit: store <- dst_addr <- load <- src_addr. + emit(ctx, t.store_op, 0, Ref::none(), val, dst_addr); + emit(ctx, Oadd, Kl, dst_addr, sd[1], off_con); + + Ref src_addr = newtmp(ctx, "blt", Kl, fn); + emit(ctx, t.load_op, t.load_cls, val, src_addr, Ref::none()); + emit(ctx, Oadd, Kl, src_addr, sd[0], off_con); + + if (!fwd) + off += t.width; + sz -= t.width; + } + } +} + +inline bool is_pow2(uint64_t v) { + return v != 0 && (v & (v - 1)) == 0; +} + +// Single instruction simplification: Oblit1 / Oudiv / Ourem. +void simplify_one(Ctx* ctx, Ins** pi, bool* rebuilt, Blk* b, Fn* fn) { + Ins* i = *pi; + + switch (i->op) { + case Oblit1: { + assert(i > b->ins); + assert((i - 1)->op == Oblit0); + + // First rebuild trigger: move unprocessed tail to insb. + if (!*rebuilt) { + ctx->curi = ctx->insb + NIns; + ulong tail = static_cast<ulong>((b->ins + b->nins) - (i + 1)); + ctx->curi -= tail; + icpy(ctx->curi, i + 1, tail); + *rebuilt = true; + } + + blit(ctx, (i - 1)->arg, rsval(i->arg[0]), fn); + *pi = i - 1; // Retreat pointer to Oblit0. + return; + } + + case Oudiv: + case Ourem: { + // Unsigned integer + constant divisor + pow2. + if (KBASE(i->cls) != 0) + break; + if (rtype(i->arg[1]) != RCon) + break; + Con* c = &fn->con[i->arg[1].val]; + if (c->type != Con::CBits) + break; + if (!is_pow2(static_cast<uint64_t>(c->bits.i))) + break; + + int n = lowest_bit(static_cast<bits>(c->bits.i)); + if (i->op == Ourem) { + i->op = Oand; + i->arg[1] = getcon(ctx, (1LL << n) - 1, fn); + } else { + i->op = Oshr; + i->arg[1] = getcon(ctx, n, fn); + } + break; + } + + default: + break; + } + + if (*rebuilt) + emiti(ctx, *i); +} + +} // namespace + +void simpl(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; + simplify_one(ctx, &i, &rebuilt, b, fn); + } + if (rebuilt) + idup(ctx, b, ctx->curi, static_cast<ulong>(ctx->insb + NIns - ctx->curi)); + } +} + +// salloc: Dynamic alloca expanded on stack. Called by rv64_isel. + +void salloc(Ctx* ctx, Ref rt, Ref rs, Fn* fn) { + fn->dynalloc = 1; + + if (rtype(rs) == RCon) { + int64_t sz = fn->con[rs.val].bits.i; + if (sz < 0 || sz >= INT_MAX - 15) + err(const_cast<char*>("invalid alloc size %" PRId64), sz); + sz = (sz + 15) & -16; + emit(ctx, Osalloc, Kl, rt, getcon(ctx, sz, fn), Ref::none()); + return; + } + + // Non-constant size path. + if (rtype(rs) == RTmp && fn->tmp[rs.val].slot != -1) + err(const_cast<char*>("unlikely alloc argument %%%s for %%%s"), fn->tmp[rs.val].name, + fn->tmp[rt.val].name); + + Ref r0 = newtmp(ctx, "isel", Kl, fn); + Ref r1 = newtmp(ctx, "isel", Kl, fn); + emit(ctx, Osalloc, Kl, rt, r0, Ref::none()); + emit(ctx, Oand, Kl, r0, r1, getcon(ctx, -16, fn)); + emit(ctx, Oadd, Kl, r1, rs, getcon(ctx, 15, fn)); +} + +} // namespace rcc diff --git a/rcc-legacy/src/backend/spill.cc b/rcc-legacy/src/backend/spill.cc new file mode 100644 index 0000000..360dcb3 --- /dev/null +++ b/rcc-legacy/src/backend/spill.cc @@ -0,0 +1,600 @@ +/* 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 +<http://www.gnu.org/licenses/>. */ + +/// @file backend/spill.cc +/// @brief Register spilling pass. + +#include "rcc/all.h" + +#include <algorithm> +#include <climits> +#include <cstdio> +#include <vector> + +namespace rcc { + +// fillcost + +namespace { + +// loopiter callback: aggregate gen/nlive of loop body to loop header. +void aggreg(Blk* hd, Blk* b) { + bsunion(hd->gen, b->gen); + for (int k = 0; k < 2; ++k) + if (b->nlive[k] > hd->nlive[k]) + hd->nlive[k] = b->nlive[k]; +} + +// Single Ref use accumulation. +void tmpuse(Ref r, int use, int loop, Fn* fn) { + if (rtype(r) == RMem) { + Mem* m = &fn->mem[r.val]; + tmpuse(m->base, 1, loop, fn); + tmpuse(m->index, 1, loop, fn); + return; + } + if (rtype(r) == RTmp && r.val >= Tmp0) { + Tmp* t = &fn->tmp[r.val]; + t->nuse += static_cast<uint>(use); + t->ndef += static_cast<uint>(!use); + t->cost += static_cast<uint>(loop); + } +} + +void accumulate_phi_costs(Blk* b, Fn* fn) { + for (Phi* p = b->phi; p; p = p->link) { + Tmp* t = &fn->tmp[p->to.val]; + tmpuse(p->to, 0, 0, fn); + for (uint a = 0; a < p->narg; ++a) { + int n = p->blk[a]->loop; + t->cost += static_cast<uint>(n); + tmpuse(p->arg[a], 1, n, fn); + } + } +} + +void accumulate_ins_costs(Blk* b, Fn* fn) { + int n = b->loop; + for (Ins* i = b->ins; i < &b->ins[b->nins]; ++i) { + tmpuse(i->to, 0, n, fn); + tmpuse(i->arg[0], 1, n, fn); + tmpuse(i->arg[1], 1, n, fn); + } + tmpuse(b->jmp.arg, 1, n, fn); + tmpuse(b->jmp.arg2, 1, n, fn); // Second operand for Jjfi/Jjff. +} + +void dump_loop_info(Ctx* ctx, Fn* fn) { + FILE* f = ctx->dbg_buf; + fprintf(f, "\n> Loop information:\n"); + for (Blk* b = fn->start; b; b = b->link) { + uint a; + for (a = 0; a < b->npred; ++a) + if (b->id <= b->pred[a]->id) + break; + if (a != b->npred) { + fprintf(f, "\t%-10s", b->name); + fprintf(f, " (% 3d ", b->nlive[0]); + fprintf(f, "% 3d) ", b->nlive[1]); + dumpts(b->gen, fn->tmp, f); + } + } +} + +} // namespace + +void fillcost(Ctx* ctx, Fn* fn) { + loopiter(fn, aggreg); + + if (debug['S']) + dump_loop_info(ctx, fn); + + // Register cost = UINT_MAX (never spill); normal tmp accumulates from 0. + for (Tmp* t = fn->tmp; t - fn->tmp < fn->ntmp; ++t) { + t->cost = (t - fn->tmp < Tmp0) ? UINT_MAX : 0; + t->nuse = 0; + t->ndef = 0; + } + + for (Blk* b = fn->start; b; b = b->link) { + accumulate_phi_costs(b, fn); + accumulate_ins_costs(b, fn); + } + + if (debug['S']) { + FILE* f = ctx->dbg_buf; + fprintf(f, "\n> Spill costs:\n"); + for (int n = Tmp0; n < fn->ntmp; ++n) + fprintf(f, "\t%-10s %d\n", fn->tmp[n].name, fn->tmp[n].cost); + fprintf(f, "\n"); + } +} + +// spill + +namespace { + +// Per-call state. +struct SpillCtx { + Ctx* ctx; // Back reference to ctx. + Tmp* tmp; // Cache of fn->tmp. + int ntmp; + int locs; // fn->slot entrance snapshot. + int slot4; // Next 4B slot. + int slot8; // Next 8B slot. + BSet mask[2]; // GPR and FPR bitsets. + BSet b2_scratch; // Scratch buffer for limit2. + std::vector<int> tarr; // Scratch buffer for limit. +}; + +// Allocate stack slot on demand. +Ref alloc_spill_slot(SpillCtx& s, int t) { + assert(t >= Tmp0 && "cannot spill register"); + int existing = s.tmp[t].slot; + if (existing != -1) + return Ref::slot(existing); + + int pos; + if (KWIDE(s.tmp[t].cls)) { + pos = s.slot8; + if (s.slot4 == s.slot8) + s.slot4 += 2; + s.slot8 += 2; + } else { + pos = s.slot4; + if (s.slot4 == s.slot8) { + s.slot8 += 2; + s.slot4 += 1; + } else { + // 4B hole filled, jump to slot8. + s.slot4 = s.slot8; + } + } + pos += s.locs; + s.tmp[t].slot = pos; + return Ref::slot(pos); +} + +// Limit bitset to at most k elements. +void limit_one(SpillCtx& s, BSet* b, int k, BSet* f) { + int nt = static_cast<int>(bscount(b)); + if (nt <= k) + return; + + s.tarr.resize(static_cast<size_t>(nt)); + + // Pull elements from b to tarr, clear b. + int collected = 0; + int t = 0; + while (bsiter(b, &t)) { + bsclr(b, static_cast<uint>(t)); + s.tarr[static_cast<size_t>(collected++)] = t; + ++t; + } + assert(collected == nt); + + if (nt > 1) { + Tmp* tmp = s.tmp; + if (f) { + BSet* fst = f; + std::stable_sort(s.tarr.begin(), s.tarr.begin() + nt, [fst, tmp](int x, int y) { + int fx = bshas(fst, static_cast<uint>(x)); + int fy = bshas(fst, static_cast<uint>(y)); + if (fx != fy) + return fx > fy; + return tmp[x].cost > tmp[y].cost; + }); + } else { + std::stable_sort(s.tarr.begin(), s.tarr.begin() + nt, + [tmp](int x, int y) { return tmp[x].cost > tmp[y].cost; }); + } + } + + int i = 0; + for (; i < k && i < nt; ++i) + bsset(b, static_cast<uint>(s.tarr[static_cast<size_t>(i)])); + for (; i < nt; ++i) + alloc_spill_slot(s, s.tarr[static_cast<size_t>(i)]); +} + +// Limit GPR/FPR separately. +void limit_two(SpillCtx& s, BSet* b1, int k1, int k2, BSet* f) { + BSet* b2 = &s.b2_scratch; + bscopy(b2, b1); + bsinter(b1, &s.mask[0]); + bsinter(b2, &s.mask[1]); + limit_one(s, b1, T.ngpr - k1, f); + { + int cnt = static_cast<int>(bscount(b2)); + if (cnt > s.ctx->spill_fp_live_peak) + s.ctx->spill_fp_live_peak = cnt; + } + limit_one(s, b2, T.nfpr - k2, f); + bsunion(b1, b2); +} + +// Set register hint for tmp in u. +void sethint(SpillCtx& s, BSet* u, bits r) { + int t = Tmp0; + while (bsiter(u, &t)) { + s.tmp[phicls(t, s.tmp)].hint.m |= r; + ++t; + } +} + +// Emit Oload for tmp in u \ v. +void reloads(Ctx* ctx, SpillCtx& s, BSet* u, BSet* v) { + int t = Tmp0; + while (bsiter(u, &t)) { + if (!bshas(v, static_cast<uint>(t))) + emit(ctx, Oload, s.tmp[t].cls, Ref::tmp(static_cast<uint>(t)), alloc_spill_slot(s, t), + Ref::none()); + ++t; + } +} + +// Emit Ostore to SLOT(s) if s != -1. +void store_to_slot(Ctx* ctx, SpillCtx& spc, Ref r, int slot_id) { + if (slot_id == -1) + return; + int cls = spc.tmp[r.val].cls; + emit(ctx, Ostorew + cls, 0, Ref::none(), r, Ref::slot(slot_id)); +} + +inline bool is_regcopy(Ins* i) { + return i->op == Ocopy && isreg(i->arg[0]); +} + +// Process parallel moves (contiguous reg->tmp copies). +Ins* spill_par_moves(Ctx* ctx, SpillCtx& s, Blk* b, Ins* i, BSet* v) { + BSet u[1]; + bsinit(ctx, u, static_cast<uint>(s.ntmp)); + + // Backward scan of contiguous regcpy segment. + Ins* i1 = ++i; + do { + --i; + int t = i->to.val; + if (!req(i->to, Ref::none())) + if (bshas(v, static_cast<uint>(t))) { + bsclr(v, static_cast<uint>(t)); + store_to_slot(ctx, s, i->to, s.tmp[t].slot); + } + bsset(v, i->arg[0].val); + } while (i != b->ins && is_regcopy(i - 1)); + + bscopy(u, v); + + bits r; + if (i != b->ins && (i - 1)->op == Ocall) { + // Call site: return registers are dead, argregs must be preserved. + Ins* call = i - 1; + v->t[0] &= ~T.retregs(call->arg[1], nullptr); + limit_two(s, v, T.nrsave[0], T.nrsave[1], nullptr); + r = 0; + for (int n = 0; T.rsave[n] >= 0; ++n) + r |= BIT(T.rsave[n]); + v->t[0] |= T.argregs(call->arg[1], nullptr); + } else { + limit_two(s, v, 0, 0, nullptr); + r = v->t[0]; + } + sethint(s, v, r); + reloads(ctx, s, u, v); + + // Re-emit original regcpy in the segment. + do + emiti(ctx, *--i1); + while (i1 != i); + return i; +} + +// Loop header path merge. +void merge_pred(SpillCtx& s, BSet* u, Blk* bu, BSet* v, Blk* bv) { + if (bu->loop <= bv->loop) { + bsunion(u, v); + return; + } + int t = 0; + while (bsiter(v, &t)) { + if (s.tmp[t].slot == -1) + bsset(u, static_cast<uint>(t)); + ++t; + } +} + +// Compute exit set v. +void compute_exit_set(SpillCtx& s, Blk* b, BSet* u, BSet* v, BSet* w) { + Blk* s1 = b->s1; + Blk* s2 = b->s2; + Blk* hd = nullptr; + if (s1 && s1->id <= b->id) + hd = s1; + if (s2 && s2->id <= b->id) + if (!hd || s2->id >= hd->id) + hd = s2; + + if (hd) { + // Back-edge handling. + bszero(v); + hd->gen->t[0] |= T.rglob; // Global registers never spill. + for (int k = 0; k < 2; ++k) { + uint n = (k == 0) ? static_cast<uint>(T.ngpr) : static_cast<uint>(T.nfpr); + bscopy(u, b->out); + bsinter(u, &s.mask[k]); + bscopy(w, u); + bsinter(u, hd->gen); + bsdiff(w, hd->gen); + if (bscount(u) < n) { + int j = static_cast<int>(bscount(w)); // Live-through. + int l = hd->nlive[k]; + if (k == 1 && static_cast<int>(bscount(w)) > s.ctx->spill_fp_live_peak) + s.ctx->spill_fp_live_peak = static_cast<int>(bscount(w)); + limit_one(s, w, static_cast<int>(n) - (l - j), nullptr); + bsunion(u, w); + } else { + if (k == 1 && static_cast<int>(bscount(u)) > s.ctx->spill_fp_live_peak) + s.ctx->spill_fp_live_peak = static_cast<int>(bscount(u)); + limit_one(s, u, static_cast<int>(n), nullptr); + } + bsunion(v, u); + } + } else if (s1) { + // Forward edge handling. + bszero(v); + liveon(w, b, s1); + merge_pred(s, v, b, w, s1); + if (s2) { + liveon(u, b, s2); + merge_pred(s, v, b, u, s2); + bsinter(w, u); + } + limit_two(s, v, 0, 0, w); + } else { + bscopy(v, b->out); + if (rtype(b->jmp.arg) == RCall) + v->t[0] |= T.retregs(b->jmp.arg, nullptr); + } +} + +// Single instruction processing in main loop. + +// Process i->to. +void process_def(Ins* i, BSet* v, BSet* w) { + if (req(i->to, Ref::none())) + return; + assert(rtype(i->to) == RTmp); + uint t = i->to.val; + if (bshas(v, t)) { + bsclr(v, t); + } else { + // Dead def. + assert(t >= Tmp0 && "dead reg"); + bsset(v, t); + bsset(w, t); + } +} + +// Process args. +void process_args(Ins* i, BSet* v, BSet* w, int lvarg[2], Fn* fn) { + int memquota = T.memargs(static_cast<int>(i->op)); + for (int n = 0; n < 2; ++n) + if (rtype(i->arg[n]) == RMem) + --memquota; + + for (int n = 0; n < 2; ++n) { + switch (rtype(i->arg[n])) { + case RMem: { + uint t = i->arg[n].val; + Mem* m = &fn->mem[t]; + if (rtype(m->base) == RTmp) { + bsset(v, m->base.val); + bsset(w, m->base.val); + } + if (rtype(m->index) == RTmp) { + bsset(v, m->index.val); + bsset(w, m->index.val); + } + break; + } + case RTmp: { + uint t = i->arg[n].val; + lvarg[n] = bshas(v, t); + bsset(v, t); + if (memquota-- <= 0) + bsset(w, t); + break; + } + default: + break; + } + } +} + +// Rewrite evicted args after limit2. +void rewrite_evicted_args(SpillCtx& s, Ins* i, BSet* u, BSet* v, const int lvarg[2]) { + for (int n = 0; n < 2; ++n) { + if (rtype(i->arg[n]) != RTmp) + continue; + uint t = i->arg[n].val; + if (!bshas(v, t)) { + if (!lvarg[n]) + bsclr(u, t); + i->arg[n] = alloc_spill_slot(s, static_cast<int>(t)); + } + } +} + +// Process jmp args. +void process_jmp_arg(SpillCtx& s, Blk* b, BSet* v) { + bool has1 = rtype(b->jmp.arg) == RTmp; + bool has2 = rtype(b->jmp.arg2) == RTmp; + if (!has1 && !has2) + return; + int t1 = has1 ? static_cast<int>(b->jmp.arg.val) : -1; + int t2 = has2 ? static_cast<int>(b->jmp.arg2.val) : -1; + if (has1) { + assert(KBASE(s.tmp[t1].cls) == 0); + bsset(v, static_cast<uint>(t1)); + } + if (has2) { + assert(KBASE(s.tmp[t2].cls) == 0); + bsset(v, static_cast<uint>(t2)); + } + limit_two(s, v, 0, 0, nullptr); + if (has1 && !bshas(v, static_cast<uint>(t1))) + b->jmp.arg = alloc_spill_slot(s, t1); + if (has2 && !bshas(v, static_cast<uint>(t2))) + b->jmp.arg2 = alloc_spill_slot(s, t2); +} + +// Slot unselected outs. +void slot_unselected_outs(SpillCtx& s, Blk* b, BSet* v) { + int t = Tmp0; + while (bsiter(b->out, &t)) { + if (!bshas(v, static_cast<uint>(t))) + alloc_spill_slot(s, t); + ++t; + } +} + +// Process block phis. +void process_block_phis(Ctx* ctx, SpillCtx& s, Blk* b, BSet* v) { + for (Phi* p = b->phi; p; p = p->link) { + assert(rtype(p->to) == RTmp); + uint t = p->to.val; + if (bshas(v, t)) { + bsclr(v, t); + store_to_slot(ctx, s, p->to, s.tmp[t].slot); + } else if (bshas(b->in, t)) { + p->to = alloc_spill_slot(s, static_cast<int>(t)); + } + } +} + +void dump_spill_result(Ctx* ctx, Fn* fn) { + FILE* f = ctx->dbg_buf; + fprintf(f, "\n> Block information:\n"); + for (Blk* b = fn->start; b; b = b->link) { + fprintf(f, "\t%-10s (% 5d) ", b->name, b->loop); + dumpts(b->out, fn->tmp, f); + } + fprintf(f, "\n> After spilling:\n"); + printfn(fn, f); +} + +} // namespace + +// spill main entry. + +void spill(Ctx* ctx, Fn* fn) { + SpillCtx s{}; + s.ctx = ctx; + s.tmp = fn->tmp; + s.ntmp = fn->ntmp; + s.locs = fn->slot; + s.slot4 = 0; + s.slot8 = 0; + + uint n = static_cast<uint>(s.ntmp); + bsinit(ctx, &s.mask[0], n); + bsinit(ctx, &s.mask[1], n); + bsinit(ctx, &s.b2_scratch, n); + + BSet u[1], v[1], w[1]; + bsinit(ctx, u, n); + bsinit(ctx, v, n); + bsinit(ctx, w, n); + + // Mask initialization. + for (int t = 0; t < s.ntmp; ++t) { + int k = 0; + if (t >= T.fpr0 && t < T.fpr0 + T.nfpr) + k = 1; + if (t >= Tmp0) + k = KBASE(s.tmp[t].cls); + bsset(&s.mask[k], static_cast<uint>(t)); + } + + // RPO reverse order iteration. + for (Blk** bp = &fn->rpo[fn->nblk]; bp != fn->rpo;) { + Blk* b = *--bp; + + // Determine exit set v. + ctx->curi = nullptr; // emit/reloads disabled here. + compute_exit_set(s, b, u, v, w); + process_jmp_arg(s, b, v); + slot_unselected_outs(s, b, v); + bscopy(b->out, v); + + // Reverse process instructions, fill insb. + ctx->curi = ctx->insb + NIns; + for (Ins* i = &b->ins[b->nins]; i != b->ins;) { + --i; + if (is_regcopy(i)) { + i = spill_par_moves(ctx, s, b, i, v); + continue; + } + + bszero(w); + int lvarg[2] = {0, 0}; + process_def(i, v, w); + process_args(i, v, w, lvarg, fn); + + bscopy(u, v); + limit_two(s, v, 0, 0, w); + rewrite_evicted_args(s, i, u, v, lvarg); + reloads(ctx, s, u, v); + + if (!req(i->to, Ref::none())) { + uint t = i->to.val; + store_to_slot(ctx, s, i->to, s.tmp[t].slot); + if (t >= Tmp0) + bsclr(v, t); + } + emiti(ctx, *i); + + bits r = v->t[0]; // Low 64 bits are physical registers. + if (r) + sethint(s, v, r); + } + + // Entry handling. + if (b == fn->start) { + assert(v->t[0] == (T.rglob | fn->reg)); + } else { + assert(v->t[0] == T.rglob); + } + + // Block entry phis. + process_block_phis(ctx, s, b, v); + bscopy(b->in, v); + + idup(ctx, b, ctx->curi, static_cast<ulong>(ctx->insb + NIns - ctx->curi)); + } + + // 16-byte alignment. + s.slot8 += s.slot8 & 3; + fn->slot += s.slot8; + + if (debug['S']) + dump_spill_result(ctx, fn); +} + +} // namespace rcc |
