aboutsummaryrefslogtreecommitdiffhomepage
path: root/x86_64/sysv.c
diff options
context:
space:
mode:
authorlemon <lsof@mailbox.org>2025-12-12 17:40:35 +0100
committerlemon <lsof@mailbox.org>2025-12-12 17:40:35 +0100
commit24bcc929477751b056e81e7772dc2bb3d11ce4a5 (patch)
treef83eb0c32df505f25c828d0a62f17806dc2736b1 /x86_64/sysv.c
parent3cd8e39ff61217a37b41cee47f2682f5291317d6 (diff)
s/amd64/x86_64/
Diffstat (limited to 'x86_64/sysv.c')
-rw-r--r--x86_64/sysv.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/x86_64/sysv.c b/x86_64/sysv.c
new file mode 100644
index 0000000..32cc9e5
--- /dev/null
+++ b/x86_64/sysv.c
@@ -0,0 +1,313 @@
+#include "all.h"
+
+static int classify(uchar cls[2], const struct typedata *td, uint off);
+
+static void
+clsscalar(uchar cls[2], uint off, union type ty)
+{
+ enum irclass k = type2cls[scalartypet(ty)];
+ uchar *fcls = &cls[off/8];
+ if (isflt(ty)) { /* SSE */
+ if (!*fcls || (*fcls == KF32 && k > *fcls))
+ *fcls = k;
+ } else { /* INTEGER */
+ assert(isint(ty) || ty.t == TYPTR);
+ if (cls2siz[*fcls] < cls2siz[k])
+ *fcls = k == KPTR ? KI64 : k;
+ }
+ if (off % 8 >= 4 && cls2siz[*fcls] < 8)
+ *fcls = kisint(*fcls) ? KI64 : KF64;
+}
+
+static int
+classifyarr(uchar cls[2], union type ty, uint off)
+{
+ union type chld = typechild(ty);
+ uint n = typearrlen(ty), siz = typesize(chld);
+ assert(n > 0);
+ for (uint i = 0; i < n; ++i) {
+ uint offx = off + i * siz;
+ if (isagg(chld)) {
+ if (!classify(cls, &typedata[chld.dat], offx))
+ return cls[0] = cls[1] = 0;
+ } else if (chld.t == TYARRAY) {
+ if (!classifyarr(cls, chld, offx))
+ return cls[0] = cls[1] = 0;
+ } else {
+ clsscalar(cls, offx, chld);
+ }
+ }
+ return !!cls[0] + !!cls[1];
+}
+
+static int
+classify(uchar cls[2], const struct typedata *td, uint off)
+{
+ uint siz = alignup(td->siz, 4);
+ if (siz > 16) /* MEMORY */
+ return 0;
+ for (int i = 0; i < td->nmemb; ++i) {
+ struct fielddata *fld = &td->fld[i].f;
+ uint align = typealign(fld->t);
+ if (alignup(fld->off, align) != fld->off) /* unaligned field -> MEMORY */
+ return cls[0] = cls[1] = 0;
+ if (isagg(fld->t)) {
+ if (!classify(cls, &typedata[fld->t.dat], off + fld->off))
+ return cls[0] = cls[1] = 0;
+ } else if (fld->t.t == TYARRAY) {
+ if (isincomplete(fld->t)) continue;
+ if (!classifyarr(cls, fld->t, off + fld->off))
+ return cls[0] = cls[1] = 0;
+ } else {
+ clsscalar(cls, fld->off + off, fld->t);
+ }
+ }
+ return !!cls[0] + !!cls[1];
+}
+
+static int
+abiarg(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype typ)
+{
+ static const uchar intregs[] = { RDI, RSI, RDX, RCX, R8, R9 };
+ enum { NINT = countof(intregs), NFLT = 8 };
+ int ret, ni_save, nf_save;
+
+ if (!typ.isagg) {
+ if (kisflt(cls[0] = typ.cls) && *nf < NFLT) {
+ r[0] = XMM0 + (*nf)++;
+ } else if (kisint(cls[0]) && *ni < NINT) {
+ r[0] = intregs[(*ni)++];
+ } else {
+ r[0] = *ns;
+ *ns += 8;
+ return 0; /* MEMORY */
+ }
+ return 1;
+ }
+ cls[0] = cls[1] = 0;
+ ret = classify(cls, &typedata[typ.dat], 0);
+ if (!ret) { /*MEMORY*/
+ r[0] = *ns;
+ *ns = alignup(*ns + typedata[typ.dat].siz, 8);
+ return 0;
+ }
+ assert(ret <= 2);
+ ni_save = *ni, nf_save = *nf;
+ *r2off = 8;
+ for (int i = 0; i < ret; ++i) {
+ assert(cls[i]);
+ if (kisflt(cls[i]) && *nf < NFLT)
+ r[i] = XMM0 + (*nf)++;
+ else if (kisint(cls[i]) && *ni < NINT)
+ r[i] = intregs[(*ni)++];
+ else { /* MEMORY */
+ *ni = ni_save, *nf = nf_save;
+ r[0] = *ns;
+ *ns = alignup(*ns + typedata[typ.dat].siz, 8);
+ r[1] = -1;
+ return cls[0] = cls[1] = 0;
+ }
+ }
+ return ret;
+}
+
+static int
+abiret(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype typ)
+{
+ int ret;
+
+ if (!typ.isagg) {
+ r[0] = kisflt(cls[0] = typ.cls) ? XMM0 : RAX;
+ return 1;
+ }
+
+ cls[0] = cls[1] = 0;
+ ret = classify(cls, &typedata[typ.dat], 0);
+ if (!ret) { /* MEMORY */
+ assert(*ni == 0);
+ r[0] = RAX; /* on return should contain result location address */
+ r[1] = RDI; /* register for caller-owned result location argument */
+ ++*ni;
+ return 0;
+ }
+ assert(ret <= 2);
+ *r2off = 8;
+ for (int i = 0, ni = 0, nf = 0; i < ret; ++i) {
+ assert(cls[i]);
+ if (kisflt(cls[i])) /* SSE (XMM0, XMM1) */
+ r[i] = XMM0 + nf++;
+ else if (kisint(cls[i])) /* INTEGER (RAX, RDX) */
+ r[i] = ni++ == 0 ? RAX : RDX;
+ else assert(0);
+ }
+ return ret;
+}
+
+/* Layout of va_list:
+ * struct {
+ * ( 0) unsigned int gp_offset;
+ * ( 4) unsigned int fp_offset;
+ * ( 8) void *overflow_arg_area;
+ * (16) void *reg_save_area;
+ * }
+ * Layout of register save area (align 16):
+ * reg off
+ * rdi 0
+ * rsi 8
+ * rdx 16
+ * rcx 24
+ * r8 32
+ * r9 40
+ * xmm0 48
+ * xmm1 64
+ * ...
+ * in x86_64/emit xvaprologue generates the code to save the registers to a stack slot
+ * there only needs to be one xvaprologue if there's any vastart instrs, and it has to be
+ * at the beginning of the function (before IR generated by regalloc can touch any registers)
+ * then vastart can initialize va_list.reg_save_area with a pointer to that
+ */
+
+static void
+vastart(struct function *fn, struct block *blk, int *curi)
+{
+ union ref rsave; /* register save area */
+ int gpr0 = 0, fpr0 = 0, stk0 = 0;
+ struct instr *ins = &instrtab[blk->ins.p[*curi]];
+ union ref ap = ins->l, src, dst;
+ assert(ins->op == Ovastart);
+ /* add xvaprologue if not there yet, which must be the first
+ * real instruction in the function (following alloca) */
+ if (fn->entry->ins.n > 1 && instrtab[fn->entry->ins.p[1]].op == Oxvaprologue) {
+ rsave = mkref(RTMP, fn->entry->ins.p[0]); /* alloca instruction */
+ assert(instrtab[rsave.i].op == Oalloca16);
+ } else {
+ rsave = insertinstr(fn->entry, 0, mkalloca(192, 16));
+ insertinstr(fn->entry, 1, mkinstr(Oxvaprologue, 0, rsave, .keep=1));
+ }
+ /* find first unnamed gpr and fpr */
+ for (int i = 0; i < fn->nabiarg; ++i) {
+ struct abiarg abi = fn->abiarg[i];
+ if (!abi.isstk){
+ if (abi.reg < XMM0) ++gpr0;
+ else ++fpr0;
+ } else {
+ stk0 = abi.stk+8;
+ }
+ }
+ /* set ap->reg_save_area */
+ *ins = mkinstr(Oadd, KPTR, ap, mkref(RICON, 16));
+ dst = mkref(RTMP, ins - instrtab);
+ int i = *curi + 1;
+ insertinstr(blk, i++, mkinstr(Ostore64, 0, dst, rsave));
+ /* set ap->overflow_arg_area */
+ src = insertinstr(blk, i++, mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, 16+stk0)));
+ dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 8)));
+ insertinstr(blk, i++, mkinstr(Ostore64, 0, dst, src));
+ /* set ap->gp_offset */
+ insertinstr(blk, i++, mkinstr(Ostore32, 0, ap, mkref(RICON, gpr0*8)));
+ /* set ap->fp_offset */
+ dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4)));
+ insertinstr(blk, i++, mkinstr(Ostore32, 0, dst, mkref(RICON, 6*8 + fpr0*16)));
+ *curi = i-1;
+}
+
+static void
+vaarg(struct function *fn, struct block *blk, int *curi)
+{
+ short r[2];
+ uchar cls[2];
+ union ref tmp;
+ int ni = 0, nf = 0, ns = 0;
+ uchar r2off;
+ int var = blk->ins.p[*curi];
+ union ref ap = instrtab[var].l;
+ union irtype ty = ref2type(instrtab[var].r);
+
+ assert(instrtab[var].op == Ovaarg);
+ blk->ins.p[*curi] = newinstr(blk, (struct instr){Onop});
+
+ int ret = abiarg(r, cls, &r2off, &ni, &nf, &ns, ty);
+
+ if (ret == 2) assert(!"nyi");
+ else if (ret == 1) {
+ struct block *merge;
+ union ref phi, phiargs[2];
+ /* int: l->gp_offset < 48 - num_gp * 8 */
+ /* sse: l->fp_offset < 304 - num_gp * 16 (why 304? ... 176) */
+ tmp = ni ? ap : insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4)));
+ tmp = insertinstr(blk, (*curi)++, mkinstr(Oloadu32, KI32, tmp));
+ tmp = insertinstr(blk, (*curi)++, mkinstr(Oulte, KI32, tmp, mkref(RICON, ni ? 48 - ni*8 : 176 - nf*16)));
+ merge = blksplitafter(fn, blk, *curi);
+ blk->jmp.t = 0;
+ useblk(fn, blk);
+ putcondbranch(fn, tmp, newblk(fn), newblk(fn));
+ useblk(fn, blk->s1);
+ {
+ /* phi0: &l->reg_save_area[l->gp/fp_offset] */
+ union ref sav = addinstr(fn, mkinstr(Oloadi64, KPTR, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 16))));
+ union ref roff = addinstr(fn, mkinstr(Oloadu32, KI32, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4))));
+ phiargs[0] = irbinop(fn, Oadd, KPTR, sav, roff);
+ /* l->gp/fp_offset += num_gp/fp * 8(16) */
+ roff = irbinop(fn, Oadd, KI32, roff, mkref(RICON, ni ? ni * 8 : nf * 16));
+ addinstr(fn, mkinstr(Ostore32, 0, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4)), roff));
+ assert(merge->npred == 1);
+ blkpred(merge, 0) = blk->s1;
+ blk->s1->jmp.t = Jb;
+ blk->s1->s1 = merge;
+ }
+ useblk(fn, blk->s2);
+ {
+ /* phi1: l->overflow_arg_area */
+ union ref adr = irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 8));
+ union ref ovf = addinstr(fn, mkinstr(Oloadi64, KPTR, adr));
+ /* align no-op */
+
+ phiargs[1] = ovf;
+ /* update l->overflow_arg_area += size */
+ int siz = 8;
+ addinstr(fn, mkinstr(Ostore64, 0, adr, irbinop(fn, Oadd, KPTR, ovf, mkref(RICON, siz))));
+ putbranch(fn, merge);
+ }
+ assert(merge->npred == 2);
+ vpush(&merge->ins, 0);
+ memmove(merge->ins.p+1, merge->ins.p, (merge->ins.n-1)*sizeof *merge->ins.p);
+ merge->ins.p[0] = var;
+ phi = insertphi(merge, KPTR);
+ memcpy(phitab.p[instrtab[phi.i].l.i], phiargs, sizeof phiargs);
+ if (!ty.isagg) {
+ instrtab[var] = mkinstr(cls[0] == KI32 ? Oloads32 : Oloadi64, cls[0], phi);
+ } else {
+ instrtab[var] = mkalloca(8, 8);
+ tmp = insertinstr(merge, 1, mkinstr(Oloadi64, KI64, phi));
+ insertinstr(merge, 2, mkinstr(Ostore64, 0, mkref(RTMP, var), tmp));
+ }
+ fn->prop &= ~FNUSE;
+ } else {
+ assert(!"nyi");
+ }
+}
+
+static const char x86_64_rnames[][6] = {
+#define R(r) #r,
+ LIST_REGS(R)
+#undef R
+};
+
+const struct mctarg t_x86_64_sysv = {
+ .gpr0 = RAX, .ngpr = R15 - RAX + 1,
+ .bpr = RBP,
+ .gprscratch = R11, .fprscratch = XMM15,
+ .fpr0 = XMM0, .nfpr = XMM15 - XMM0 + 1,
+ .rcallee = 1<<RBX | 1<<R12 | 1<<R13 | 1<<R14 | 1<<R15,
+ .rglob = 1<<RSP | 1<<RBP,
+ .rnames = x86_64_rnames,
+ .objkind = OBJELF,
+ .abiret = abiret,
+ .abiarg = abiarg,
+ .vastart = vastart,
+ .vaarg = vaarg,
+ .isel = x86_64_isel,
+ .emit = x86_64_emit
+};
+
+/* vim:set ts=3 sw=3 expandtab: */