diff options
| author | 2026-03-17 13:22:00 +0100 | |
|---|---|---|
| committer | 2026-03-17 13:22:00 +0100 | |
| commit | a8d6f8bf30c07edb775e56889f568ca20240bedf (patch) | |
| tree | b5a452b2675b2400f15013617291fe6061180bbf /src/ir_abi0.c | |
| parent | 24f14b7ad1af08d872971d72ce089a529911f657 (diff) | |
REFACTOR: move sources to src/
Diffstat (limited to 'src/ir_abi0.c')
| -rw-r--r-- | src/ir_abi0.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/src/ir_abi0.c b/src/ir_abi0.c new file mode 100644 index 0000000..a3687d9 --- /dev/null +++ b/src/ir_abi0.c @@ -0,0 +1,462 @@ +#include "ir.h" + +/** This pass adds in ABI arguments/returns register mappings + ** and lowers aggregate params/args/returns into scalars + ** + ** invariant: all `call` instructions when doing this pass shall be preceded by + ** exactly narg `arg` instructions with no other instructions in between + **/ + +struct abiargsvec { vec_of(struct abiarg); }; + +static int +abiret(struct abiarg abiret[2], struct abiargsvec *abiargs, uchar *r2off, int *ni, union irtype retty) +{ + short r[2]; + uchar cls[2]; + int retreg = 0; + retreg = mctarg->abiret(r, cls, r2off, ni, retty); + if (retty.isagg) { + if (!retreg) { + vpush(abiargs, ((struct abiarg) { cls2type(KPTR), .reg = r[1] })); + if (r[0] == -1) { + memset(abiret, 0, 2*sizeof *abiret); + } else { + abiret[0].ty = cls2type(KPTR); + abiret[0].reg = r[0]; + } + } + } else if (retty.cls) { + assert(retreg == 1); + } + for (int i = 0; i < retreg; ++i) { + abiret[i].ty = cls2type(cls[i]); + abiret[i].isstk = 0; + abiret[i].reg = r[i]; + } + return retreg; +} + +static int +abiarg(struct abiargsvec *abiargs, uchar *r2off, int *ni, int *nf, int *ns, union irtype ty) +{ + short r[2]; + uchar cls[2]; + int ret = mctarg->abiarg(r, cls, r2off, ni, nf, ns, ty); + if (!ret) { /* in stack */ + vpush(abiargs, ((struct abiarg) { ty, .isstk = 1, .stk = r[0] })); + } else if (ret == 1 && ty.isagg && cls[0] == KPTR) { /* aggregate by pointer */ + vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] })); + } else { /* by regs */ + vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] })); + if (ret == 2) + vpush(abiargs, ((struct abiarg) { cls2type(cls[1]), .reg = r[1] })); + } + return ret; +} + +static struct instr +copyparam(struct function *fn, int *curi, int param, struct abiarg abi) +{ + struct instr par = mkinstr(Oparam, abi.ty.cls, mkref(RICON, param), mktyperef(abi.ty)); + if (!abi.isstk) { /* reg */ + assert(!abi.ty.isagg); + return par; + } + par.r = mktyperef((union irtype){.cls = KPTR}); + if (!abi.ty.isagg) { /* scalar in stack */ + enum op ld; + par.cls = KPTR; + if (abi.ty.cls == KPTR) abi.ty.cls = siz2intcls[cls2siz[abi.ty.cls]]; + switch (abi.ty.cls) { + default: assert(0); + case KI32: ld = Oloads32; break; + case KI64: ld = Oloadi64; break; + case KF32: ld = Oloadf32; break; + case KF64: ld = Oloadf64; break; + } + return mkinstr(ld, abi.ty.cls, insertinstr(fn->entry, (*curi)++, par)); + } else { /* aggregate in stack */ + par.cls = KPTR; + return par; + } +} + +static void +patchparam(struct function *fn, int *curi, int *param, int tydat, int nabi, struct abiarg abi[2], uchar r2off) +{ + struct block *blk = fn->entry; + assert(in_range(nabi,1,2)); + + for (; *curi < blk->ins.n; ++*curi) { + struct instr *ins = &instrtab[blk->ins.p[*curi]]; + if (ins->op != Oparam) continue; + assert(ins->r.t == RTYPE + && ins->r.i == (tydat < 0 ? abi[0].ty : (union irtype){.isagg=1, .dat=tydat}).bits); + if (abi[0].ty.isagg || tydat < 0) { + /* aggregate in stack or scalar, just copy */ + assert(nabi == 1); + *ins = copyparam(fn, curi, *param, abi[0]); + } else { /* aggregate in registers, materialize */ + union ref alloc, r[2]; + struct instr st; + const struct typedata *td; + uint nalloc; + uint align; + + assert(tydat >= 0); + td = &typedata[tydat]; + assert(td->siz <= 16 && td->align <= 16); + align = td->siz <= 4 ? 4 : alignup(td->align, 8); + nalloc = td->siz/align + (td->siz%align != 0); + *ins = mkinstr(Oalloca1 + ilog2(align), KPTR, mkref(RICON, nalloc)); + alloc = mkref(RTMP, ins - instrtab); + r[0] = insertinstr(blk, ++*curi, copyparam(fn, NULL, *param, abi[0])); + if (nabi > 1) + r[1] = insertinstr(blk, ++*curi, copyparam(fn, NULL, ++*param, abi[1])); + /* transform + * %x = copy %p + * into + * %x = alloca... + * store* %x, %a + * store* %x + N, %b + */ + st = mkinstr(cls2store[abi[0].ty.cls], 0, alloc, r[0]); + insertinstr(blk, ++*curi, st); + if (nabi > 1) { + struct instr tmp = mkinstr(Oadd, KPTR, alloc, mkref(RICON, r2off)); + st = mkinstr(cls2store[abi[1].ty.cls], 0, insertinstr(blk, ++*curi, tmp), r[1]); + insertinstr(blk, ++*curi, st); + } + } + ++*param; + ++*curi; + break; + } +} + +static void +load2regs(union ref out[2], union irtype typ, union ref src, int nabi, struct abiarg abi[2], uchar r2off, struct block *blk, int *curi) +{ + uint align = typedata[typ.dat].align; + uint siz = typedata[typ.dat].siz; + if (src.t == RTMP && oisalloca(instrtab[src.i].op)) { + /* use actual alignment as opposed to min required type alignment */ + uint aalign = 1 << (instrtab[src.i].op - Oalloca1); + assert(aalign >= align); + align = aalign; + } + /* deconstruct into + * %a = load* %x + * (%b = load* %x + N) + */ + /* XXX this generates pretty bad code for small-alignment structs even on platforms where unaligned loads are available.. */ + if (align >= 4) { + for (int i = 0; i < nabi; ++i) { + struct instr ins = {0}; + union ref temp; + switch (ins.cls = abi[i].ty.cls) { + default: assert(0); + case KI32: ins.op = Oloadu32; break; + case KI64: ins.op = Oloadi64; break; + case KF32: ins.op = Oloadf32; break; + case KF64: ins.op = Oloadf64; break; + } + if (i == 0) + ins.l = src; + else { + struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, r2off)); + ins.l = insertinstr(blk, (*curi)++, adr); + } + temp = insertinstr(blk, (*curi)++, ins); + //insertinstr(blk, (*curi)++, mkarginstr(abi[i].ty, temp)); + out[i] = temp; + } + } else { + for (int i = 0; i < nabi; ++i) { + struct instr ld = {0}; + union ref reg, temp; + uint n = cls2siz[abi[i].ty.cls] / align; + assert(n > 0); + ld.op = Oloadu8 + ilog2(align)*2; + ld.cls = abi[i].ty.cls; + for (int o = 0; o < n && (i*cls2siz[ld.cls])+o*align < siz; ++o) { + if (i+o == 0) + ld.l = src; + else { + struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, (i == 0 ? 0 : r2off) + o*align)); + ld.l = insertinstr(blk, (*curi)++, adr); + } + temp = insertinstr(blk, (*curi)++, ld); + if (o > 0) { + union ref t = insertinstr(blk, (*curi)++, mkinstr(Oshl, ld.cls, temp, mkref(RICON, o*align*8))); + reg = insertinstr(blk, (*curi)++, mkinstr(Oior, ld.cls, reg, t)); + } else { + reg = temp; + } + } + //insertinstr(blk, arginst++, mkarginstr(abi[i].ty, reg)); + out[i] = reg; + } + } +} + +static int +patcharg(struct block *blk, int *icall, struct call *call, + int argidx, int nabi, struct abiarg abi[2], uchar r2off) +{ + int arginst = *icall - (call->narg - argidx); + struct instr *arg = &instrtab[blk->ins.p[arginst]]; + assert(arg->op == Oarg && arg->l.t == RTYPE); + if (ref2type(arg->l).isagg) { /* aggregate argument */ + if (abi[0].ty.isagg) { /* aggregate in stack */ + /* XXX do this better.. */ + /* ptr %dst = arg <stk dst> */ + /* (blit %dst, %src) */ + union ref dst = mkref(RTMP, arg - instrtab); + uint align = typedata[abi->ty.dat].align, siz = typedata[abi->ty.dat].siz; + union ref src = arg->r; + if (src.t == RTMP && oisalloca(instrtab[src.i].op)) { + align = 1 << (instrtab[src.i].op - Oalloca1); + } + assert(align <= 8); + arg->cls = KPTR; + arg->r = mkref(RICON, abi->stk); + for (uint off = 0; off < siz; off += align) { + union ref sadr = off == 0 ? src : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, src, mkref(RICON, off))); + union ref tmp = insertinstr(blk, ++arginst, mkinstr(Oloads8+2*ilog2(align), align < 8 ? KI32 : KI64, sadr)); + union ref dadr = off == 0 ? dst : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, dst, mkref(RICON, off))); + insertinstr(blk, ++arginst, mkinstr(Ostorei8+ilog2(align), 0, dadr, tmp)); + } + *icall = arginst + (call->narg - argidx); + return 1; + } else if (abi[0].ty.cls == KPTR) { /* aggregate by pointer */ + arg->cls = KPTR; + return 1; + } else { /* aggregate in registers */ + union ref r[2]; + delinstr(blk, arginst); + load2regs(r, ref2type(arg->l), arg->r, nabi, abi, r2off, blk, &arginst); + for (int i = 0; i < nabi; ++i) + insertinstr(blk, arginst++, mkinstr(Oarg, 0, mktyperef(abi[i].ty), r[i])); + *icall = arginst + (call->narg - argidx - 1); + return nabi; + } + } else { /* normal scalar argument */ + return 1; + } +} +void +abi0_call(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + union ref retmem; + struct abiarg abiargsbuf[32]; + struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))}; + bool sretarghidden = 0; + int ni, nf, ns, vararg, nret = 0; + struct call *call = &calltab.p[ins->r.i]; + + vararg = call->vararg; + ni = nf = ns = 0; + assert(!ins->cls == !call->ret.bits); + nret = abiret(call->abiret, &abiargs, &call->r2off, &ni, call->ret); + if (call->ret.isagg) { /* adjust struct return */ + union irtype retty = call->ret; + struct typedata *td = &typedata[retty.dat]; + uint align = td->align, ralign; + struct instr alloca; + int ialloca; + for (int i = 0; i < nret; ++i) + align = align < (ralign = cls2siz[call->abiret[i].ty.cls]) ? ralign : align; + alloca = mkalloca(td->siz, align); + + sretarghidden = ni == 0; + /* swap alloca and call temps so users of original call point to alloca */ + retmem = insertinstr(blk, ialloca = (*curi)++ - call->narg, *ins); + *ins = alloca; + blk->ins.p[ialloca] = ins - instrtab; + blk->ins.p[*curi] = retmem.i; + ins = &instrtab[retmem.i]; + retmem.i = blk->ins.p[ialloca]; + + if (!nret) /* hidden pointer argument */ + insertinstr(blk, (*curi)++ - call->narg, + mkinstr(Oarg, 0, mktyperef((union irtype){.cls=KPTR}), retmem)); + } + + /* adjust args */ + for (int i = 0, i2 = ni + sretarghidden; i < call->narg; ++i) { + int arginst = *curi - (call->narg - i); + struct instr *arg = &instrtab[blk->ins.p[arginst]]; + union irtype pty = ref2type(arg->l); + uchar r2off; + int first = abiargs.n; + int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty); + ret = patcharg(blk, curi, call, i, ret, &abiargs.p[first], r2off); + if (call->vararg == i) vararg = i2; + i2 += ret; + } + call->argstksiz = ns; + /* adjust return */ + if (call->ret.isagg) { + ins->cls = 0; + if (!nret) { /* hidden pointer argument */ + ins->cls = 0; + if (!call->abiret[0].isstk) { + /* the result location pointer is also returned by the callee, e.g. in x86 */ + ins->cls = KPTR; + ++nret; + /* even if this is not used, the register copy + * must be emitted for the register allocator to know */ + } + } else { /* aggregate returned in regs */ + union ref r[2]; + struct instr ret2; + assert(in_range(nret, 1, 2)); + ins->cls = call->abiret[0].ty.cls; + r[0] = mkref(RTMP, ins - instrtab); + if (nret == 2) { + ret2 = mkinstr(Ocall2r, call->abiret[1].ty.cls, r[0]); + r[1] = insertinstr(blk, ++*curi, ret2); + } + for (int i = 0; i < nret; ++i) { + struct instr store = { cls2store[call->abiret[i].ty.cls] }; + if (i == 0) { + store.l = retmem; + } else { + struct instr addr = mkinstr(Oadd, KPTR, retmem, mkref(RICON, call->r2off)); + store.l = insertinstr(blk, ++*curi, addr); + } + store.r = r[i]; + insertinstr(blk, ++*curi, store); + } + } + } + + if (call->ret.isagg) call->ret = (union irtype){0}; + call->vararg = vararg; + call->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof(struct abiarg), 0); + call->narg = abiargs.n; + vfree(&abiargs); +} + +void +abi0(struct function *fn) +{ + struct abiarg abiargsbuf[32]; + uint nparam = typedata[fn->fnty.dat].nmemb; + const union type *paramty = typedata[fn->fnty.dat].param; + struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))}; + int rvovar = -1; + int ni = 0, nf = 0, ns = 0, istart = 0; + uchar r2off; + struct block *blk; + union ref sret = {0}; + + FREQUIRE(FNUSE); + + if (fn->retty.t == TYVOID) { + fn->nabiret = 0; + } else { + fn->nabiret = abiret(fn->abiret, &abiargs, &r2off, &ni, mkirtype(fn->retty)); + if (!fn->nabiret && isagg(fn->retty)) { /* ret agg by hidden pointer */ + struct instr param = copyparam(fn, NULL, 0, abiargs.p[0]); + sret = insertinstr(fn->entry, 0, param); + ++istart; + /* increment real param ordinals */ + for (int i = 1; i < fn->entry->ins.n; ++i) { + struct instr *ins = &instrtab[fn->entry->ins.p[i]]; + if (ins->op == Oparam) ++ins->l.i; + } + } + } + + /* adjust params */ + for (int i = 0, param = abiargs.n; i < nparam; ++i) { + union irtype pty = mkirtype(paramty[i]); + int first = abiargs.n; + uchar r2off; + int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty); + patchparam(fn, &istart, ¶m, pty.isagg ? pty.dat : -1, ret+!ret, &abiargs.p[first], r2off); + } + fn->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof *abiargs.p, 0); + fn->nabiarg = abiargs.n; + vfree(&abiargs); + + if (!fn->nabiret && isagg(fn->retty)) { + /* for structures returned by hidden pointer argument, + * if all return instrs return local var X, make X point to the result location, + * (return value optimization (RVO)) */ + blk = fn->entry; + do { + union ref arg = blk->jmp.arg[0]; + if (blk->jmp.t != Jret) continue; + if (!arg.bits) continue; + if (arg.t != RTMP || !oisalloca(instrtab[arg.i].op)) { + rvovar = -1; + break; + } + if (rvovar == -1) { + rvovar = arg.i; + } else if (arg.i != rvovar) { + rvovar = -1; + break; + } + } while ((blk = blk->lnext) != fn->entry); + if (rvovar != -1) + instrtab[rvovar] = mkinstr(Ocopy, KPTR, sret); + } + + blk = fn->entry->lnext; + int id = 1; + do { + /* adjust vaargs and calls */ + for (int iinstr = 0; iinstr < blk->ins.n; ++iinstr) { + struct instr *ins = &instrtab[blk->ins.p[iinstr]]; + if (ins->op == Ovastart) mctarg->vastart(fn, blk, &iinstr); + else if (ins->op == Ovaarg) mctarg->vaarg(fn, blk, &iinstr); + else if (ins->op == Ocall) abi0_call(fn, ins, blk, &iinstr); + } + + /* adjust returns */ + if (isagg(fn->retty) && blk->jmp.t == Jret && blk->jmp.arg[0].bits) { + assert(!blk->jmp.arg[1].bits); + if (fn->nabiret) { /* aggregate return in register(s) */ + union ref r[2]; + int curi = blk->ins.n; + load2regs(r, mkirtype(fn->retty), blk->jmp.arg[0], fn->nabiret, fn->abiret, r2off, blk, &curi); + for (int i = 0; i < fn->nabiret; ++i) { + blk->jmp.arg[i] = r[i]; + adduse(blk, USERJUMP, r[i]); + } + } else { + /* aggregate return (arg[0] is pointer to return value) */ + if (rvovar == -1) { + /* blit %sret, %arg */ + union irtype typ = mkirtype(fn->retty); + insertinstr(blk, blk->ins.n, mkarginstr(typ, sret)); + insertinstr(blk, blk->ins.n, mkarginstr(typ, blk->jmp.arg[0])); + insertinstr(blk, blk->ins.n, mkintrin(INstructcopy, 0, 2)); + } else assert(blk->jmp.arg[0].bits == mkref(RTMP, rvovar).bits); + if (fn->abiret[0].ty.cls) { + blk->jmp.arg[0] = rvovar == -1 ? sret : mkref(RTMP, rvovar); + adduse(blk, USERJUMP, blk->jmp.arg[0]); + } + else memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + } + } + blk->id = id++; + } while ((blk = blk->lnext) != fn->entry); + + + /* vaargs might break these */ + if (!(fn->prop & FNUSE)) filluses(fn); + fn->prop &= ~(FNBLKID | FNRPO); + + if (ccopt.dbg.a) { + bfmt(ccopt.dbgout, "<< After abi0 >>\n"); + irdump(fn); + } +} + +/* vim:set ts=3 sw=3 expandtab: */ |