diff options
Diffstat (limited to 'src/backend/rv64/abi.cc')
| -rw-r--r-- | src/backend/rv64/abi.cc | 690 |
1 files changed, 690 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 |
