diff options
Diffstat (limited to 'src/backend/rv64')
| -rw-r--r-- | src/backend/rv64/abi.cc | 690 | ||||
| -rw-r--r-- | src/backend/rv64/emit.cc | 949 | ||||
| -rw-r--r-- | src/backend/rv64/isel.cc | 518 | ||||
| -rw-r--r-- | src/backend/rv64/targ.cc | 142 |
4 files changed, 2299 insertions, 0 deletions
diff --git a/src/backend/rv64/abi.cc b/src/backend/rv64/abi.cc new file mode 100644 index 0000000..6456c21 --- /dev/null +++ b/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/src/backend/rv64/emit.cc b/src/backend/rv64/emit.cc new file mode 100644 index 0000000..4a0c87e --- /dev/null +++ b/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/src/backend/rv64/isel.cc b/src/backend/rv64/isel.cc new file mode 100644 index 0000000..c2ded02 --- /dev/null +++ b/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/src/backend/rv64/targ.cc b/src/backend/rv64/targ.cc new file mode 100644 index 0000000..4d0ab8c --- /dev/null +++ b/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 |
