aboutsummaryrefslogtreecommitdiff
path: root/rcc-legacy
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-06-08 17:07:31 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-06-08 17:07:31 +0800
commit189d77ba3a50a515c0b687ecc2766e1052a03795 (patch)
treee682e6992a75544d319be88e8bc3a27c5df557b1 /rcc-legacy
parenta4a5ad2786a1937addc09d72a375158877fb3b80 (diff)
downloadrcc-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.cc304
-rw-r--r--rcc-legacy/src/backend/peephole.cc146
-rw-r--r--rcc-legacy/src/backend/rega.cc684
-rw-r--r--rcc-legacy/src/backend/rv64/abi.cc690
-rw-r--r--rcc-legacy/src/backend/rv64/emit.cc949
-rw-r--r--rcc-legacy/src/backend/rv64/isel.cc518
-rw-r--r--rcc-legacy/src/backend/rv64/targ.cc142
-rw-r--r--rcc-legacy/src/backend/simpl.cc176
-rw-r--r--rcc-legacy/src/backend/spill.cc600
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