Compare commits

..

1 Commits

Author SHA1 Message Date
Roland Paterson-Jones
95541ccfb0 Simple Inner Loop Optimzation
Two simple loop optimizations.

1. Strength reduction of mul[tiplication] by loop induction
variable.

2. Hoisting of (address) base into phi where loop induction
variable is used only as a base (address) offset.

Limited to loops with a single body block, which happily
is always innermost loops. This restriction would not be
very hard to lift - it would require detecting the set of
loop blocks (and ensuring reducibility?)

Limited to loop induction variables with 0 initial value
and increment of 1 (for mul strength reduction). This
limitation is trivial to lift; however all of the
cproc/hare[c]/coremark opportunity is with 0/1 loops for
mul reduction, and 0 initial value for base-offset opt.
2025-03-21 16:02:12 +01:00
39 changed files with 303 additions and 2171 deletions

View File

@ -1,4 +1,4 @@
© 2015-2026 Quentin Carbonneaux <quentin@c9x.me>
© 2015-2025 Quentin Carbonneaux <quentin@c9x.me>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@ -5,9 +5,9 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
COMMOBJ = main.o util.o parse.o abi.o cfg.o mem.o ssa.o alias.o load.o \
copy.o fold.o gvn.o gcm.o loopopt.o simpl.o ifopt.o live.o \
spill.o rega.o emit.o
AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o amd64/winabi.o
copy.o fold.o gvn.o gcm.o loopopt.o simpl.o live.o spill.o rega.o \
emit.o
AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o
ARM64OBJ = arm64/targ.o arm64/abi.o arm64/isel.o arm64/emit.o
RV64OBJ = rv64/targ.o rv64/abi.o rv64/isel.o rv64/emit.o
OBJ = $(COMMOBJ) $(AMD64OBJ) $(ARM64OBJ) $(RV64OBJ)
@ -81,9 +81,6 @@ check-arm64: qbe
check-rv64: qbe
TARGET=rv64 tools/test.sh all
check-amd64_win: qbe
TARGET=amd64_win tools/test.sh all
src:
@echo $(SRCALL)

22
all.h
View File

@ -44,7 +44,6 @@ enum {
struct Target {
char name[16];
char apple;
char windows;
int gpr0; /* first general purpose reg */
int ngpr;
int fpr0; /* first floating point reg */
@ -63,7 +62,6 @@ struct Target {
void (*emitfin)(FILE *);
char asloc[4];
char assym[4];
uint cansel:1;
};
#define BIT(n) ((bits)1 << (n))
@ -185,8 +183,6 @@ enum {
Oalloc1 = Oalloc16,
Oflag = Oflagieq,
Oflag1 = Oflagfuo,
Oxsel = Oxselieq,
Oxsel1 = Oxselfuo,
NPubOp = Onop,
Jjf = Jjfieq,
Jjf1 = Jjffuo,
@ -203,7 +199,6 @@ enum {
#define isparbh(o) INRANGE(o, Oparsb, Oparuh)
#define isargbh(o) INRANGE(o, Oargsb, Oarguh)
#define isretbh(j) INRANGE(j, Jretsb, Jretuh)
#define isxsel(o) INRANGE(o, Oxsel, Oxsel1)
enum {
Kx = -1, /* "top" class (see usecheck() and clsmerge()) */
@ -293,10 +288,8 @@ struct Use {
struct Sym {
enum {
SGlo = 0, /* direct access */
SThr = 1, /* local-exec TLS */
SExt = 2, /* GOT/PLT access */
SExtThr = SExt|SThr, /* initial-exec TLS */
SGlo,
SThr,
} type;
uint32_t id;
};
@ -489,7 +482,7 @@ void *vnew(ulong, size_t, Pool);
void vfree(void *);
void vgrow(void *, ulong);
void addins(Ins **, uint *, Ins *);
void addbins(Ins **, uint *, Blk *);
void addbins(Blk *, Ins **, uint *);
void strf(char[NString], char *, ...);
uint32_t intern(char *);
char *str(uint32_t);
@ -502,6 +495,7 @@ void emiti(Ins);
void idup(Blk *, Ins *, ulong);
Ins *icpy(Ins *, Ins *, ulong);
int cmpop(int);
int cmpneg(int);
int cmpwlneg(int);
int clsmerge(short *, short);
int phicls(int, Tmp *);
@ -561,8 +555,6 @@ void fillloop(Fn *);
void simpljmp(Fn *);
int reaches(Fn *, Blk *, Blk *);
int reachesnotvia(Fn *, Blk *, Blk *, Blk *);
int ifgraph(Blk *, Blk **, Blk **, Blk **);
void simplcfg(Fn *);
/* mem.c */
void promote(Fn *);
@ -603,9 +595,6 @@ void gvn(Fn *);
int pinned(Ins *);
void gcm(Fn *);
/* ifopt.c */
void ifconvert(Fn *fn);
/* loopopt.c */
void loopopt(Fn *fn);
@ -628,8 +617,7 @@ void emitfnlnk(char *, Lnk *, FILE *);
void emitdat(Dat *, FILE *);
void emitdbgfile(char *, FILE *);
void emitdbgloc(uint, uint, FILE *);
int stashbits(bits, int);
int stashbits(void *, int);
void elf_emitfnfin(char *, FILE *);
void elf_emitfin(FILE *);
void macho_emitfin(FILE *);
void pe_emitfin(FILE *);

View File

@ -4,14 +4,14 @@ typedef struct Amd64Op Amd64Op;
enum Amd64Reg {
RAX = RXX+1, /* caller-save */
RCX, /* caller-save */
RDX, /* caller-save */
RSI, /* caller-save on sysv, callee-save on win */
RDI, /* caller-save on sysv, callee-save on win */
R8, /* caller-save */
R9, /* caller-save */
R10, /* caller-save */
R11, /* caller-save */
RCX,
RDX,
RSI,
RDI,
R8,
R9,
R10,
R11,
RBX, /* callee-save */
R12,
@ -41,13 +41,9 @@ enum Amd64Reg {
NFPR = XMM14 - XMM0 + 1, /* reserve XMM15 */
NGPR = RSP - RAX + 1,
NGPS = R11 - RAX + 1,
NFPS = NFPR,
NGPS_SYSV = R11 - RAX + 1,
NCLR_SYSV = R15 - RBX + 1,
NGPS_WIN = R11 - RAX + 1 - 2, /* -2 for RDI/RDI */
NCLR_WIN = R15 - RBX + 1 + 2, /* +2 for RDI/RDI */
NCLR = R15 - RBX + 1,
};
MAKESURE(reg_not_tmp, XMM15 < (int)Tmp0);
@ -67,16 +63,8 @@ bits amd64_sysv_retregs(Ref, int[2]);
bits amd64_sysv_argregs(Ref, int[2]);
void amd64_sysv_abi(Fn *);
/* winabi.c */
extern int amd64_winabi_rsave[];
extern int amd64_winabi_rclob[];
bits amd64_winabi_retregs(Ref, int[2]);
bits amd64_winabi_argregs(Ref, int[2]);
void amd64_winabi_abi(Fn *);
/* isel.c */
void amd64_isel(Fn *);
/* emit.c */
void amd64_sysv_emitfn(Fn *, FILE *);
void amd64_winabi_emitfn(Fn *, FILE *);
void amd64_emitfn(Fn *, FILE *);

View File

@ -12,22 +12,24 @@ struct E {
};
#define CMP(X) \
X(Ciule, "be", "a") \
X(Ciult, "b", "ae") \
X(Cisle, "le", "g") \
X(Cislt, "l", "ge") \
X(Cisgt, "g", "le") \
X(Cisge, "ge", "l") \
X(Ciugt, "a", "be") \
X(Ciuge, "ae", "b") \
X(Cieq, "z", "nz") \
X(Cine, "nz", "z") \
X(NCmpI+Cfle, "?" , "?") \
X(NCmpI+Cflt, "?", "?") \
X(NCmpI+Cfgt, "a", "be") \
X(NCmpI+Cfge, "ae", "b") \
X(NCmpI+Cfo, "np", "p") \
X(NCmpI+Cfuo, "p", "np")
X(Ciule, "be") \
X(Ciult, "b") \
X(Cisle, "le") \
X(Cislt, "l") \
X(Cisgt, "g") \
X(Cisge, "ge") \
X(Ciugt, "a") \
X(Ciuge, "ae") \
X(Cieq, "z") \
X(Cine, "nz") \
X(NCmpI+Cfle, "be") \
X(NCmpI+Cflt, "b") \
X(NCmpI+Cfgt, "a") \
X(NCmpI+Cfge, "ae") \
X(NCmpI+Cfeq, "z") \
X(NCmpI+Cfne, "nz") \
X(NCmpI+Cfo, "np") \
X(NCmpI+Cfuo, "p")
enum {
SLong = 0,
@ -70,77 +72,65 @@ static struct {
short cls;
char *fmt;
} omap[] = {
{ Oadd, Ka, "+add%k %1, %=" },
{ Osub, Ka, "-sub%k %1, %=" },
{ Oand, Ki, "+and%k %1, %=" },
{ Oor, Ki, "+or%k %1, %=" },
{ Oxor, Ki, "+xor%k %1, %=" },
{ Osar, Ki, "-sar%k %B1, %=" },
{ Oshr, Ki, "-shr%k %B1, %=" },
{ Oshl, Ki, "-shl%k %B1, %=" },
{ Omul, Ki, "+imul%k %1, %=" },
{ Omul, Ks, "+mulss %1, %=" },
{ Omul, Kd, "+mulsd %1, %=" },
{ Odiv, Ka, "-div%k %1, %=" },
{ Ostorel, Ka, "movq %L0, %M1" },
{ Ostorew, Ka, "movl %W0, %M1" },
{ Ostoreh, Ka, "movw %H0, %M1" },
{ Ostoreb, Ka, "movb %B0, %M1" },
{ Ostores, Ka, "movss %S0, %M1" },
{ Ostored, Ka, "movsd %D0, %M1" },
{ Oload, Ka, "mov%k %M0, %=" },
{ Oloadsw, Kl, "movslq %M0, %L=" },
{ Oloadsw, Kw, "movl %M0, %W=" },
{ Oloaduw, Ki, "movl %M0, %W=" },
{ Oloadsh, Ki, "movsw%k %M0, %=" },
{ Oloaduh, Ki, "movzw%k %M0, %=" },
{ Oloadsb, Ki, "movsb%k %M0, %=" },
{ Oloadub, Ki, "movzb%k %M0, %=" },
{ Oextsw, Kl, "movslq %W0, %L=" },
{ Oextuw, Kl, "movl %W0, %W=" },
{ Oextsh, Ki, "movsw%k %H0, %=" },
{ Oextuh, Ki, "movzw%k %H0, %=" },
{ Oextsb, Ki, "movsb%k %B0, %=" },
{ Oextub, Ki, "movzb%k %B0, %=" },
{ Oadd, Ka, "+add%k %1, %=" },
{ Osub, Ka, "-sub%k %1, %=" },
{ Oand, Ki, "+and%k %1, %=" },
{ Oor, Ki, "+or%k %1, %=" },
{ Oxor, Ki, "+xor%k %1, %=" },
{ Osar, Ki, "-sar%k %B1, %=" },
{ Oshr, Ki, "-shr%k %B1, %=" },
{ Oshl, Ki, "-shl%k %B1, %=" },
{ Omul, Ki, "+imul%k %1, %=" },
{ Omul, Ks, "+mulss %1, %=" },
{ Omul, Kd, "+mulsd %1, %=" },
{ Odiv, Ka, "-div%k %1, %=" },
{ Ostorel, Ka, "movq %L0, %M1" },
{ Ostorew, Ka, "movl %W0, %M1" },
{ Ostoreh, Ka, "movw %H0, %M1" },
{ Ostoreb, Ka, "movb %B0, %M1" },
{ Ostores, Ka, "movss %S0, %M1" },
{ Ostored, Ka, "movsd %D0, %M1" },
{ Oload, Ka, "mov%k %M0, %=" },
{ Oloadsw, Kl, "movslq %M0, %L=" },
{ Oloadsw, Kw, "movl %M0, %W=" },
{ Oloaduw, Ki, "movl %M0, %W=" },
{ Oloadsh, Ki, "movsw%k %M0, %=" },
{ Oloaduh, Ki, "movzw%k %M0, %=" },
{ Oloadsb, Ki, "movsb%k %M0, %=" },
{ Oloadub, Ki, "movzb%k %M0, %=" },
{ Oextsw, Kl, "movslq %W0, %L=" },
{ Oextuw, Kl, "movl %W0, %W=" },
{ Oextsh, Ki, "movsw%k %H0, %=" },
{ Oextuh, Ki, "movzw%k %H0, %=" },
{ Oextsb, Ki, "movsb%k %B0, %=" },
{ Oextub, Ki, "movzb%k %B0, %=" },
{ Oexts, Kd, "cvtss2sd %0, %=" },
{ Otruncd, Ks, "cvtsd2ss %0, %=" },
{ Ostosi, Ki, "cvttss2si%k %0, %=" },
{ Odtosi, Ki, "cvttsd2si%k %0, %=" },
{ Oswtof, Ka, "cvtsi2%k %W0, %=" },
{ Osltof, Ka, "cvtsi2%k %L0, %=" },
{ Ocast, Ki, "movq %D0, %L=" },
{ Ocast, Ka, "movq %L0, %D=" },
{ Oexts, Kd, "cvtss2sd %0, %=" },
{ Otruncd, Ks, "cvtsd2ss %0, %=" },
{ Ostosi, Ki, "cvttss2si%k %0, %=" },
{ Odtosi, Ki, "cvttsd2si%k %0, %=" },
{ Oswtof, Ka, "cvtsi2%k %W0, %=" },
{ Osltof, Ka, "cvtsi2%k %L0, %=" },
{ Ocast, Ki, "movq %D0, %L=" },
{ Ocast, Ka, "movq %L0, %D=" },
{ Oaddr, Ki, "lea%k %M0, %=" },
{ Oswap, Ki, "xchg%k %0, %1" },
{ Osign, Kl, "cqto" },
{ Osign, Kw, "cltd" },
{ Oxdiv, Ki, "div%k %0" },
{ Oxidiv, Ki, "idiv%k %0" },
{ Oxcmp, Ks, "ucomiss %S0, %S1" },
{ Oxcmp, Kd, "ucomisd %D0, %D1" },
{ Oxcmp, Ki, "cmp%k %0, %1" },
{ Oxtest, Ki, "test%k %0, %1" },
#define X(c, s, _) \
{ Oflag+c, Ki, "set" s " %B=\n\tmovzb%k %B=, %=" },
{ Oaddr, Ki, "lea%k %M0, %=" },
{ Oswap, Ki, "xchg%k %0, %1" },
{ Osign, Kl, "cqto" },
{ Osign, Kw, "cltd" },
{ Oxdiv, Ki, "div%k %0" },
{ Oxidiv, Ki, "idiv%k %0" },
{ Oxcmp, Ks, "ucomiss %S0, %S1" },
{ Oxcmp, Kd, "ucomisd %D0, %D1" },
{ Oxcmp, Ki, "cmp%k %0, %1" },
{ Oxtest, Ki, "test%k %0, %1" },
#define X(c, s) \
{ Oflag+c, Ki, "set" s " %B=\n\tmovzb%k %B=, %=" },
CMP(X)
#undef X
{ Oflagfeq, Ki, "setz %B=\n\tmovzb%k %B=, %=" },
{ Oflagfne, Ki, "setnz %B=\n\tmovzb%k %B=, %=" },
{ NOp, 0, 0 }
};
static char cmov[][2][16] = {
#define X(c, s0, s1) \
[c] = { \
"cmov" s0 " %0, %=", \
"cmov" s1 " %1, %=", \
},
CMP(X)
#undef X
};
static char *rname[][4] = {
[RAX] = {"rax", "eax", "ax", "al"},
[RBX] = {"rbx", "ebx", "bx", "bl"},
@ -177,12 +167,9 @@ slot(Ref r, E *e)
}
else if (e->fp == RSP)
return 4*s + e->nclob*8;
else if (e->fn->vararg) {
if (T.windows)
return -4 * (e->fn->slot - s);
else
return -176 + -4 * (e->fn->slot - s);
} else
else if (e->fn->vararg)
return -176 + -4 * (e->fn->slot - s);
else
return -4 * (e->fn->slot - s);
}
@ -196,12 +183,12 @@ emitcon(Con *con, E *e)
l = str(con->sym.id);
p = l[0] == '"' ? "" : T.assym;
if (con->sym.type == SThr) {
assert(!T.apple);
fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else {
assert((con->sym.type & ~SExt) == SGlo);
if (T.apple)
fprintf(e->f, "%s%s@TLVP", p, l);
else
fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else
fprintf(e->f, "%s%s", p, l);
}
if (con->bits.i)
fprintf(e->f, "%+"PRId64, con->bits.i);
break;
@ -380,7 +367,7 @@ Next:
off = e->fn->con[ref.val];
emitcon(&off, e);
if (off.type == CAddr)
if (off.sym.type != SThr)
if (off.sym.type != SThr || T.apple)
fprintf(e->f, "(%%rip)");
break;
case RTmp:
@ -397,9 +384,9 @@ Next:
goto Next;
}
static bits negmask[4] = {
[Ks] = 0x80000000,
[Kd] = 0x8000000000000000,
static void *negmask[4] = {
[Ks] = (uint32_t[4]){ 0x80000000 },
[Kd] = (uint64_t[2]){ 0x8000000000000000 },
};
static void
@ -414,8 +401,6 @@ emitins(Ins i, E *e)
switch (i.op) {
default:
if (isxsel(i.op))
goto case_Oxsel;
Table:
/* most instructions are just pulled out of
* the table omap[], some special cases are
@ -532,22 +517,14 @@ emitins(Ins i, E *e)
emitf("mov%k %0, %=", &i, e);
break;
case Oaddr:
if (rtype(i.arg[0]) != RCon)
goto Table;
con = &e->fn->con[i.arg[0].val];
assert(isreg(i.to) && con->type == CAddr);
sym = str(con->sym.id);
if (T.apple && (con->sym.type & SThr)) {
fprintf(e->f,
"\tmovq %s%s@tlvp(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
}
switch (con->sym.type) {
case SThr:
if (!T.apple
&& rtype(i.arg[0]) == RCon
&& e->fn->con[i.arg[0].val].sym.type == SThr) {
/* derive the symbol address from the TCB
* address at offset 0 of %fs */
assert(isreg(i.to));
con = &e->fn->con[i.arg[0].val];
sym = str(con->sym.id);
emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f, "\tleaq %s%s@tpoff",
sym[0] == '"' ? "" : T.assym, sym);
@ -558,40 +535,15 @@ emitins(Ins i, E *e)
regtoa(i.to.val, SLong),
regtoa(i.to.val, SLong));
break;
case SExtThr:
/* initial-exec TLS: load offset from
* GOT, add to thread-base register */
assert(!con->bits.i);
emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f,
"\taddq %s%s@gottpoff(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
case SExt:
/* load address from the GOT */
assert(!con->bits.i);
fprintf(e->f,
"\tmovq %s%s@gotpcrel(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
default:
goto Table;
}
break;
goto Table;
case Ocall:
/* calls simply have a weird syntax in AT&T
* assembly... */
switch (rtype(i.arg[0])) {
case RCon:
con = &e->fn->con[i.arg[0].val];
fprintf(e->f, "\tcallq ");
emitcon(con, e);
if (con->type == CAddr
&& (con->sym.type & SExt)
&& !T.apple)
fprintf(e->f, "@plt");
emitcon(&e->fn->con[i.arg[0].val], e);
fprintf(e->f, "\n");
break;
case RTmp:
@ -624,27 +576,18 @@ emitins(Ins i, E *e)
case Odbgloc:
emitdbgloc(i.arg[0].val, i.arg[1].val, e->f);
break;
case_Oxsel:
if (req(i.to, i.arg[1]))
emitf(cmov[i.op-Oxsel][0], &i, e);
else {
if (!req(i.to, i.arg[0]))
emitf("mov %0, %=", &i, e);
emitf(cmov[i.op-Oxsel][1], &i, e);
}
break;
}
}
static void
sysv_framesz(E *e)
framesz(E *e)
{
uint64_t i, o, f;
/* specific to NAlign == 3 */
o = 0;
if (!e->fn->leaf) {
for (i=0, o=0; i<NCLR_SYSV; i++)
for (i=0, o=0; i<NCLR; i++)
o ^= e->fn->reg >> amd64_sysv_rclob[i];
o &= 1;
}
@ -658,10 +601,10 @@ sysv_framesz(E *e)
}
void
amd64_sysv_emitfn(Fn *fn, FILE *f)
amd64_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
static char *ctoa[] = {
#define X(c, s) [c] = s,
CMP(X)
#undef X
};
@ -680,7 +623,7 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f);
} else
e->fp = RSP;
sysv_framesz(e);
framesz(e);
if (e->fsz)
fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", e->fsz);
if (fn->vararg) {
@ -690,7 +633,7 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
for (n=0; n<8; ++n, o+=16)
fprintf(f, "\tmovaps %%xmm%d, %d(%%rbp)\n", n, o);
}
for (r=amd64_sysv_rclob; r<&amd64_sysv_rclob[NCLR_SYSV]; r++)
for (r=amd64_sysv_rclob; r<&amd64_sysv_rclob[NCLR]; r++)
if (fn->reg & BIT(*r)) {
itmp.arg[0] = TMP(*r);
emitf("pushq %L0", &itmp, e);
@ -719,7 +662,7 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
"\tmovq %%rbp, %%rsp\n"
"\tsubq $%"PRIu64", %%rsp\n",
e->fsz + e->nclob * 8);
for (r=&amd64_sysv_rclob[NCLR_SYSV]; r>amd64_sysv_rclob;)
for (r=&amd64_sysv_rclob[NCLR]; r>amd64_sysv_rclob;)
if (fn->reg & BIT(*--r)) {
itmp.arg[0] = TMP(*r);
emitf("popq %L0", &itmp, e);
@ -747,10 +690,9 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
s = b->s1;
b->s1 = b->s2;
b->s2 = s;
n = 0;
} else
n = 1;
fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n],
c = cmpneg(c);
fprintf(f, "\tj%s %sbb%d\n", ctoa[c],
T.asloc, id0+b->s2->id);
goto Jmp;
}
@ -761,119 +703,3 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
if (!T.apple)
elf_emitfnfin(fn->name, f);
}
static void
winabi_framesz(E *e)
{
uint64_t i, o, f;
/* specific to NAlign == 3 */
o = 0;
if (!e->fn->leaf) {
for (i=0, o=0; i<NCLR_WIN; i++)
o ^= e->fn->reg >> amd64_winabi_rclob[i];
o &= 1;
}
f = e->fn->slot;
f = (f + 3) & -4;
if (f > 0
&& e->fp == RSP
&& e->fn->salign == 4)
f += 2;
e->fsz = 4*f + 8*o;
}
void
amd64_winabi_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
CMP(X)
#undef X
};
static int id0;
Blk *b, *s;
Ins *i, itmp;
int *r, c, n, lbl;
E *e;
e = &(E){.f = f, .fn = fn};
emitfnlnk(fn->name, &fn->lnk, f);
fputs("\tendbr64\n", f);
if (fn->vararg) {
fprintf(f, "\tmovq %%rcx, 0x8(%%rsp)\n");
fprintf(f, "\tmovq %%rdx, 0x10(%%rsp)\n");
fprintf(f, "\tmovq %%r8, 0x18(%%rsp)\n");
fprintf(f, "\tmovq %%r9, 0x20(%%rsp)\n");
}
if (!fn->leaf || fn->vararg || fn->dynalloc) {
e->fp = RBP;
fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f);
} else
e->fp = RSP;
winabi_framesz(e);
if (e->fsz)
fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", e->fsz);
for (r=amd64_winabi_rclob; r<&amd64_winabi_rclob[NCLR_WIN]; r++)
if (fn->reg & BIT(*r)) {
itmp.arg[0] = TMP(*r);
emitf("pushq %L0", &itmp, e);
e->nclob++;
}
for (lbl=0, b=fn->start; b; b=b->link) {
if (lbl || b->npred > 1)
fprintf(f, "%sbb%d:\n", T.asloc, id0+b->id);
for (i=b->ins; i!=&b->ins[b->nins]; i++)
emitins(*i, e);
lbl = 1;
switch (b->jmp.type) {
case Jhlt:
fprintf(f, "\tud2\n");
break;
case Jret0:
if (fn->dynalloc)
fprintf(f,
"\tmovq %%rbp, %%rsp\n"
"\tsubq $%"PRIu64", %%rsp\n",
e->fsz + e->nclob * 8);
for (r=&amd64_winabi_rclob[NCLR_WIN]; r>amd64_winabi_rclob;)
if (fn->reg & BIT(*--r)) {
itmp.arg[0] = TMP(*r);
emitf("popq %L0", &itmp, e);
}
if (e->fp == RBP)
fputs("\tleave\n", f);
else if (e->fsz)
fprintf(f,
"\taddq $%"PRIu64", %%rsp\n",
e->fsz);
fputs("\tret\n", f);
break;
case Jjmp:
Jmp:
if (b->s1 != b->link)
fprintf(f, "\tjmp %sbb%d\n",
T.asloc, id0+b->s1->id);
else
lbl = 0;
break;
default:
c = b->jmp.type - Jjf;
if (0 <= c && c <= NCmp) {
if (b->link == b->s2 || c >= NCmpI) {
s = b->s1;
b->s1 = b->s2;
b->s2 = s;
n = 0;
} else
n = 1;
fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n],
T.asloc, id0+b->s2->id);
goto Jmp;
}
die("unhandled jump %d", b->jmp.type);
}
}
id0 += fn->nblk;
}

View File

@ -87,7 +87,7 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
vgrow(&fn->mem, ++fn->nmem);
memset(&a, 0, sizeof a);
a.offset.type = CAddr;
n = stashbits(fn->con[r0.val].bits.i, KWIDE(k) ? 8 : 4);
n = stashbits(&fn->con[r0.val].bits, KWIDE(k) ? 8 : 4);
/* quote the name so that we do not
* add symbol prefixes on the apple
* target variant
@ -96,14 +96,6 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
a.offset.sym.id = intern(buf);
fn->mem[fn->nmem-1] = a;
}
else if (op == Ocall && r == &i->arg[0]
&& rtype(r0) == RCon && fn->con[r0.val].type != CAddr) {
/* use a temporary register so that we
* produce an indirect call
*/
r1 = newtmp("isel", Kl, fn);
emit(Ocopy, Kl, r1, r0, R);
}
else if (op != Ocopy && k == Kl && noimm(r0, fn)) {
/* load constants that do not fit in
* a 32bit signed integer into a
@ -120,9 +112,8 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
r1 = newtmp("isel", Kl, fn);
emit(Oaddr, Kl, r1, SLOT(s), R);
}
else if (op != Ocall && hascon(r0, &c, fn)
&& c->type == CAddr && ((c->sym.type & SExt)
|| (T.apple && c->sym.type == SThr))) {
else if (T.apple && hascon(r0, &c, fn)
&& c->type == CAddr && c->sym.type == SThr) {
r1 = newtmp("isel", Kl, fn);
if (c->bits.i) {
r2 = newtmp("isel", Kl, fn);
@ -132,18 +123,16 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
emit(Oadd, Kl, r1, r2, r3);
} else
r2 = r1;
if (T.apple && (c->sym.type & SThr)) {
emit(Ocopy, Kl, r2, TMP(RAX), R);
r2 = newtmp("isel", Kl, fn);
r3 = newtmp("isel", Kl, fn);
emit(Ocall, 0, R, r3, CALL(17));
emit(Ocopy, Kl, TMP(RDI), r2, R);
emit(Oload, Kl, r3, r2, R);
}
emit(Ocopy, Kl, r2, TMP(RAX), R);
r2 = newtmp("isel", Kl, fn);
r3 = newtmp("isel", Kl, fn);
emit(Ocall, 0, R, r3, CALL(17));
emit(Ocopy, Kl, TMP(RDI), r2, R);
emit(Oload, Kl, r3, r2, R);
cc = *c;
cc.bits.i = 0;
r3 = newcon(&cc, fn);
emit(Oaddr, Kl, r2, r3, R);
emit(Oload, Kl, r2, r3, R);
if (rtype(r0) == RMem) {
m = &fn->mem[r0.val];
m->offset.type = CUndef;
@ -154,8 +143,9 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
else if (!(isstore(op) && r == &i->arg[1])
&& !isload(op) && op != Ocall && rtype(r0) == RCon
&& fn->con[r0.val].type == CAddr) {
/* turn address operands into
* lea/mov instructions
/* apple as does not support 32-bit
* absolute addressing, use a rip-
* relative leaq instead
*/
r1 = newtmp("isel", Kl, fn);
emit(Oaddr, Kl, r1, r0, R);
@ -173,10 +163,6 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
m->base = r0;
}
}
else if (isxsel(op) && rtype(*r) == RCon) {
r1 = newtmp("isel", i->cls, fn);
emit(Ocopy, i->cls, r1, *r, R);
}
*r = r1;
}
@ -439,8 +425,7 @@ sel(Ins i, Num *tn, Fn *fn)
case Oexts:
case Otruncd:
case Ocast:
case_Oxsel:
case_Oext:
case_OExt:
Emit:
emiti(i);
i1 = curi; /* fixarg() can change curi */
@ -454,9 +439,7 @@ Emit:
break;
default:
if (isext(i.op))
goto case_Oext;
if (isxsel(i.op))
goto case_Oxsel;
goto case_OExt;
if (isload(i.op))
goto case_Oload;
if (iscmp(i.op, &kc, &x)) {
@ -510,88 +493,6 @@ flagi(Ins *i0, Ins *i)
return 0;
}
static Ins*
selsel(Fn *fn, Blk *b, Ins *i, Num *tn)
{
Ref r, cr[2];
int c, k, swap, gencmp, gencpy;
Ins *isel0, *isel1, *fi;
Tmp *t;
assert(i->op == Osel1);
for (isel0=i; b->ins<isel0; isel0--) {
if (isel0->op == Osel0)
break;
assert(isel0->op == Osel1);
}
assert(isel0->op == Osel0);
r = isel0->arg[0];
assert(rtype(r) == RTmp);
t = &fn->tmp[r.val];
fi = flagi(b->ins, isel0);
cr[0] = cr[1] = R;
gencmp = gencpy = swap = 0;
k = Kw;
c = Cine;
if (!fi || !req(fi->to, r)) {
gencmp = 1;
cr[0] = r;
cr[1] = CON_Z;
}
else if (iscmp(fi->op, &k, &c)) {
if (c == NCmpI+Cfeq
|| c == NCmpI+Cfne) {
/* these are selected as 'and'
* or 'or', so we check their
* result with Cine
*/
c = Cine;
goto Other;
}
swap = cmpswap(fi->arg, c);
if (swap)
c = cmpop(c);
if (t->nuse == 1) {
gencmp = 1;
cr[0] = fi->arg[0];
cr[1] = fi->arg[1];
*fi = (Ins){.op = Onop};
}
}
else if (fi->op == Oand && t->nuse == 1
&& (rtype(fi->arg[0]) == RTmp ||
rtype(fi->arg[1]) == RTmp)) {
fi->op = Oxtest;
fi->to = R;
if (rtype(fi->arg[1]) == RCon) {
r = fi->arg[1];
fi->arg[1] = fi->arg[0];
fi->arg[0] = r;
}
}
else {
Other:
/* since flags are not tracked in liveness,
* the result of the flag-setting instruction
* has to be marked as live
*/
if (t->nuse == 1)
gencpy = 1;
}
/* generate conditional moves */
for (isel1=i; isel0<isel1; --isel1) {
isel1->op = Oxsel+c;
sel(*isel1, tn, fn);
}
assert(!gencmp || !gencpy);
if (gencmp)
selcmp(cr, k, swap, fn);
if (gencpy)
emit(Ocopy, Kw, R, r, R);
*isel0 = (Ins){.op = Onop};
return isel0;
}
static void
seljmp(Blk *b, Fn *fn)
{
@ -621,7 +522,7 @@ seljmp(Blk *b, Fn *fn)
b->jmp.type = Jjf + Cine;
}
else if (iscmp(fi->op, &k, &c)
&& c != NCmpI+Cfeq /* see sel(), selsel() */
&& c != NCmpI+Cfeq /* see sel() */
&& c != NCmpI+Cfne) {
swap = cmpswap(fi->arg, c);
if (swap)
@ -925,14 +826,8 @@ amd64_isel(Fn *fn)
memset(num, 0, n * sizeof num[0]);
anumber(num, b, fn->con);
seljmp(b, fn);
for (i=&b->ins[b->nins]; i!=b->ins;) {
--i;
assert(i->op != Osel0);
if (i->op == Osel1)
i = selsel(fn, b, i, num);
else
sel(*i, num, fn);
}
for (i=&b->ins[b->nins]; i!=b->ins;)
sel(*--i, num, fn);
idup(b, curi, &insb[NIns]-curi);
}
free(num);

View File

@ -228,8 +228,8 @@ int amd64_sysv_rsave[] = {
int amd64_sysv_rclob[] = {RBX, R12, R13, R14, R15, -1};
MAKESURE(sysv_arrays_ok,
sizeof amd64_sysv_rsave == (NGPS_SYSV+NFPS+1) * sizeof(int) &&
sizeof amd64_sysv_rclob == (NCLR_SYSV+1) * sizeof(int)
sizeof amd64_sysv_rsave == (NGPS+NFPS+1) * sizeof(int) &&
sizeof amd64_sysv_rclob == (NCLR+1) * sizeof(int)
);
/* layout of call's second argument (RCall)

View File

@ -19,21 +19,20 @@ amd64_memargs(int op)
.nfpr = NFPR, \
.rglob = BIT(RBP) | BIT(RSP), \
.nrglob = 2, \
.rsave = amd64_sysv_rsave, \
.nrsave = {NGPS, NFPS}, \
.retregs = amd64_sysv_retregs, \
.argregs = amd64_sysv_argregs, \
.memargs = amd64_memargs, \
.abi0 = elimsb, \
.abi1 = amd64_sysv_abi, \
.isel = amd64_isel, \
.cansel = 1,
.emitfn = amd64_emitfn, \
Target T_amd64_sysv = {
.name = "amd64_sysv",
.emitfin = elf_emitfin,
.asloc = ".L",
.abi1 = amd64_sysv_abi,
.rsave = amd64_sysv_rsave,
.nrsave = {NGPS_SYSV, NFPS},
.retregs = amd64_sysv_retregs,
.argregs = amd64_sysv_argregs,
.emitfn = amd64_sysv_emitfn,
AMD64_COMMON
};
@ -43,25 +42,5 @@ Target T_amd64_apple = {
.emitfin = macho_emitfin,
.asloc = "L",
.assym = "_",
.abi1 = amd64_sysv_abi,
.rsave = amd64_sysv_rsave,
.nrsave = {NGPS_SYSV, NFPS},
.retregs = amd64_sysv_retregs,
.argregs = amd64_sysv_argregs,
.emitfn = amd64_sysv_emitfn,
AMD64_COMMON
};
Target T_amd64_win = {
.name = "amd64_win",
.windows = 1,
.emitfin = pe_emitfin,
.asloc = "L",
.abi1 = amd64_winabi_abi,
.rsave = amd64_winabi_rsave,
.nrsave = {NGPS_WIN, NFPS},
.retregs = amd64_winabi_retregs,
.argregs = amd64_winabi_argregs,
.emitfn = amd64_winabi_emitfn,
AMD64_COMMON
};

View File

@ -1,763 +0,0 @@
#include "all.h"
#include <stdbool.h>
typedef enum ArgPassStyle {
APS_Invalid = 0,
APS_Register,
APS_InlineOnStack,
APS_CopyAndPointerInRegister,
APS_CopyAndPointerOnStack,
APS_VarargsTag,
APS_EnvTag,
} ArgPassStyle;
typedef struct ArgClass {
Typ* type;
ArgPassStyle style;
int align;
uint size;
int cls;
Ref ref;
} ArgClass;
typedef struct ExtraAlloc ExtraAlloc;
struct ExtraAlloc {
Ins instr;
ExtraAlloc* link;
};
#define ALIGN_DOWN(n, a) ((n) & ~((a)-1))
#define ALIGN_UP(n, a) ALIGN_DOWN((n) + (a)-1, (a))
// Number of stack bytes required be reserved for the callee.
#define SHADOW_SPACE_SIZE 32
int amd64_winabi_rsave[] = {RCX, RDX, R8, R9, R10, R11, RAX, XMM0,
XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8,
XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, -1};
int amd64_winabi_rclob[] = {RBX, R12, R13, R14, R15, RSI, RDI, -1};
MAKESURE(winabi_arrays_ok,
sizeof amd64_winabi_rsave == (NGPS_WIN + NFPS + 1) * sizeof(int) &&
sizeof amd64_winabi_rclob == (NCLR_WIN + 1) * sizeof(int));
// layout of call's second argument (RCall)
//
// bit 0: rax returned
// bit 1: xmm0 returned
// bits 23: 0
// bits 4567: rcx, rdx, r8, r9 passed
// bits 89ab: xmm0,1,2,3 passed
// bit c: env call (rax passed)
// bits d..1f: 0
bits amd64_winabi_retregs(Ref r, int p[2]) {
assert(rtype(r) == RCall);
bits b = 0;
int num_int_returns = r.val & 1;
int num_float_returns = r.val & 2;
if (num_int_returns == 1) {
b |= BIT(RAX);
} else {
b |= BIT(XMM0);
}
if (p) {
p[0] = num_int_returns;
p[1] = num_float_returns;
}
return b;
}
static uint popcnt(bits b) {
b = (b & 0x5555555555555555) + ((b >> 1) & 0x5555555555555555);
b = (b & 0x3333333333333333) + ((b >> 2) & 0x3333333333333333);
b = (b & 0x0f0f0f0f0f0f0f0f) + ((b >> 4) & 0x0f0f0f0f0f0f0f0f);
b += (b >> 8);
b += (b >> 16);
b += (b >> 32);
return b & 0xff;
}
bits amd64_winabi_argregs(Ref r, int p[2]) {
assert(rtype(r) == RCall);
// On SysV, these are counts. Here, a count isn't sufficient, we actually need
// to know which ones are in use because they're not necessarily contiguous.
int int_passed = (r.val >> 4) & 15;
int float_passed = (r.val >> 8) & 15;
bool env_param = (r.val >> 12) & 1;
bits b = 0;
b |= (int_passed & 1) ? BIT(RCX) : 0;
b |= (int_passed & 2) ? BIT(RDX) : 0;
b |= (int_passed & 4) ? BIT(R8) : 0;
b |= (int_passed & 8) ? BIT(R9) : 0;
b |= (float_passed & 1) ? BIT(XMM0) : 0;
b |= (float_passed & 2) ? BIT(XMM1) : 0;
b |= (float_passed & 4) ? BIT(XMM2) : 0;
b |= (float_passed & 8) ? BIT(XMM3) : 0;
b |= env_param ? BIT(RAX) : 0;
if (p) {
// TODO: The only place this is used is live.c. I'm not sure what should be
// returned here wrt to using the same counter for int/float regs on win.
// For now, try the number of registers in use even though they're not
// contiguous.
p[0] = popcnt(int_passed);
p[1] = popcnt(float_passed);
}
return b;
}
typedef struct RegisterUsage {
// Counter for both int/float as they're counted together. Only if the bool's
// set in regs_passed is the given register *actually* needed for a value
// (i.e. needs to be saved, etc.).
int num_regs_passed;
// Indexed first by 0=int, 1=float, use KBASE(cls).
// Indexed second by register index in calling convention, so for integer,
// 0=RCX, 1=RDX, 2=R8, 3=R9, and for float XMM0, XMM1, XMM2, XMM3.
bool regs_passed[2][4];
bool rax_returned;
bool xmm0_returned;
// This is also used as where the va_start will start for varargs functions
// (there's no 'Oparv', so we need to keep track of a count here.)
int num_named_args_passed;
// This is set when classifying the arguments for a call (but not when
// classifying the parameters of a function definition).
bool is_varargs_call;
bool has_env;
} RegisterUsage;
static int register_usage_to_call_arg_value(RegisterUsage reg_usage) {
return (reg_usage.rax_returned << 0) | //
(reg_usage.xmm0_returned << 1) | //
(reg_usage.regs_passed[0][0] << 4) | //
(reg_usage.regs_passed[0][1] << 5) | //
(reg_usage.regs_passed[0][2] << 6) | //
(reg_usage.regs_passed[0][3] << 7) | //
(reg_usage.regs_passed[1][0] << 8) | //
(reg_usage.regs_passed[1][1] << 9) | //
(reg_usage.regs_passed[1][2] << 10) | //
(reg_usage.regs_passed[1][3] << 11) | //
(reg_usage.has_env << 12);
}
// Assigns the argument to a register if there's any left according to the
// calling convention, and updates the regs_passed bools. Otherwise marks the
// value as needing stack space to be passed.
static void assign_register_or_stack(RegisterUsage* reg_usage,
ArgClass* arg,
bool is_float,
bool by_copy) {
if (reg_usage->num_regs_passed == 4) {
arg->style = by_copy ? APS_CopyAndPointerOnStack : APS_InlineOnStack;
} else {
reg_usage->regs_passed[is_float][reg_usage->num_regs_passed] = true;
++reg_usage->num_regs_passed;
arg->style = by_copy ? APS_CopyAndPointerInRegister : APS_Register;
}
++reg_usage->num_named_args_passed;
}
static bool type_is_by_copy(Typ* type) {
// Note that only these sizes are passed by register, even though e.g. a
// 5 byte struct would "fit", it still is passed by copy-and-pointer.
return type->isdark || (type->size != 1 && type->size != 2 &&
type->size != 4 && type->size != 8);
}
// This function is used for both arguments and parameters.
// begin_instr should either point at the first Oarg or Opar, and end_instr
// should point past the last one (so to the Ocall for arguments, or to the
// first 'real' instruction of the function for parameters).
static void classify_arguments(RegisterUsage* reg_usage,
Ins* begin_instr,
Ins* end_instr,
ArgClass* arg_classes,
Ref* env) {
ArgClass* arg = arg_classes;
// For each argument, determine how it will be passed (int, float, stack)
// and update the `reg_usage` counts. Additionally, fill out arg_classes for
// each argument.
for (Ins* instr = begin_instr; instr < end_instr; ++instr, ++arg) {
switch (instr->op) {
case Oarg:
case Opar:
assign_register_or_stack(reg_usage, arg, KBASE(instr->cls),
/*by_copy=*/false);
arg->cls = instr->cls;
arg->align = 3;
arg->size = 8;
break;
case Oargc:
case Oparc: {
int typ_index = instr->arg[0].val;
Typ* type = &typ[typ_index];
bool by_copy = type_is_by_copy(type);
assign_register_or_stack(reg_usage, arg, /*is_float=*/false, by_copy);
arg->cls = Kl;
if (!by_copy && type->size <= 4) {
arg->cls = Kw;
}
arg->align = 3;
arg->size = type->size;
break;
}
case Oarge:
*env = instr->arg[0];
arg->style = APS_EnvTag;
reg_usage->has_env = true;
break;
case Opare:
*env = instr->to;
arg->style = APS_EnvTag;
reg_usage->has_env = true;
break;
case Oargv:
reg_usage->is_varargs_call = true;
arg->style = APS_VarargsTag;
break;
}
}
if (reg_usage->has_env && reg_usage->is_varargs_call) {
die("can't use env with varargs");
}
// During a varargs call, float arguments have to be duplicated to their
// associated integer register, so mark them as in-use too.
if (reg_usage->is_varargs_call) {
for (int i = 0; i < 4; ++i) {
if (reg_usage->regs_passed[/*float*/ 1][i]) {
reg_usage->regs_passed[/*int*/ 0][i] = true;
}
}
}
}
static bool is_integer_type(int ty) {
assert(ty >= 0 && ty < 4 && "expecting Kw Kl Ks Kd");
return KBASE(ty) == 0;
}
static Ref register_for_arg(int cls, int counter) {
assert(counter < 4);
if (is_integer_type(cls)) {
return TMP(amd64_winabi_rsave[counter]);
} else {
return TMP(XMM0 + counter);
}
}
static Ins* lower_call(Fn* func,
Blk* block,
Ins* call_instr,
ExtraAlloc** pextra_alloc) {
// Call arguments are instructions. Walk through them to find the end of the
// call+args that we need to process (and return the instruction past the body
// of the instruction for continuing processing).
Ins* instr_past_args = call_instr - 1;
for (; instr_past_args >= block->ins; --instr_past_args) {
if (!isarg(instr_past_args->op)) {
break;
}
}
Ins* earliest_arg_instr = instr_past_args + 1;
// Don't need an ArgClass for the call itself, so one less than the total
// number of instructions we're dealing with.
uint num_args = call_instr - earliest_arg_instr;
ArgClass* arg_classes = alloc(num_args * sizeof(ArgClass));
RegisterUsage reg_usage = {0};
ArgClass ret_arg_class = {0};
// Ocall's two arguments are the the function to be called in 0, and, if the
// the function returns a non-basic type, then arg[1] is a reference to the
// type of the return. req checks if Refs are equal; `R` is 0.
bool il_has_struct_return = !req(call_instr->arg[1], R);
bool is_struct_return = false;
if (il_has_struct_return) {
Typ* ret_type = &typ[call_instr->arg[1].val];
is_struct_return = type_is_by_copy(ret_type);
if (is_struct_return) {
assign_register_or_stack(&reg_usage, &ret_arg_class, /*is_float=*/false,
/*by_copy=*/true);
}
ret_arg_class.size = ret_type->size;
}
Ref env = R;
classify_arguments(&reg_usage, earliest_arg_instr, call_instr, arg_classes,
&env);
// We now know which arguments are on the stack and which are in registers, so
// we can allocate the correct amount of space to stash the stack-located ones
// into.
uint stack_usage = 0;
for (uint i = 0; i < num_args; ++i) {
ArgClass* arg = &arg_classes[i];
// stack_usage only accounts for pushes that are for values that don't have
// enough registers. Large struct copies are alloca'd separately, and then
// only have (potentially) 8 bytes to add to stack_usage here.
if (arg->style == APS_InlineOnStack) {
if (arg->align > 4) {
err("win abi cannot pass alignments > 16");
}
stack_usage += arg->size;
} else if (arg->style == APS_CopyAndPointerOnStack) {
stack_usage += 8;
}
}
stack_usage = ALIGN_UP(stack_usage, 16);
// Note that here we're logically 'after' the call (due to emitting
// instructions in reverse order), so we're doing a negative stack
// allocation to clean up after the call.
Ref stack_size_ref =
getcon(-(int64_t)(stack_usage + SHADOW_SPACE_SIZE), func);
emit(Osalloc, Kl, R, stack_size_ref, R);
ExtraAlloc* return_pad = NULL;
if (is_struct_return) {
return_pad = alloc(sizeof(ExtraAlloc));
Ref ret_pad_ref = newtmp("abi.ret_pad", Kl, func);
return_pad->instr =
(Ins){Oalloc8, Kl, ret_pad_ref, {getcon(ret_arg_class.size, func)}};
return_pad->link = (*pextra_alloc);
*pextra_alloc = return_pad;
reg_usage.rax_returned = true;
emit(Ocopy, call_instr->cls, call_instr->to, TMP(RAX), R);
} else {
if (il_has_struct_return) {
// In the case that at the IL level, a struct return was specified, but as
// far as the calling convention is concerned it's not actually by
// pointer, we need to store the return value into an alloca because
// subsequent IL will still be treating the function return as a pointer.
ExtraAlloc* return_copy = alloc(sizeof(ExtraAlloc));
return_copy->instr =
(Ins){Oalloc8, Kl, call_instr->to, {getcon(8, func)}};
return_copy->link = (*pextra_alloc);
*pextra_alloc = return_copy;
Ref copy = newtmp("abi.copy", Kl, func);
emit(Ostorel, 0, R, copy, call_instr->to);
emit(Ocopy, Kl, copy, TMP(RAX), R);
reg_usage.rax_returned = true;
} else if (is_integer_type(call_instr->cls)) {
// Only a basic type returned from the call, integer.
emit(Ocopy, call_instr->cls, call_instr->to, TMP(RAX), R);
reg_usage.rax_returned = true;
} else {
// Basic type, floating point.
emit(Ocopy, call_instr->cls, call_instr->to, TMP(XMM0), R);
reg_usage.xmm0_returned = true;
}
}
// Emit the actual call instruction. There's no 'to' value by this point
// because we've lowered it into register manipulation (that's the `R`),
// arg[0] of the call is the function, and arg[1] is register usage is
// documented as above (copied from SysV).
emit(Ocall, call_instr->cls, R, call_instr->arg[0],
CALL(register_usage_to_call_arg_value(reg_usage)));
if (!req(R, env)) {
// If there's an env arg to be passed, it gets stashed in RAX.
emit(Ocopy, Kl, TMP(RAX), env, R);
}
if (reg_usage.is_varargs_call) {
// Any float arguments need to be duplicated to integer registers. This is
// required by the calling convention so that dumping to shadow space can be
// done without a prototype and for varargs.
#define DUP_IF_USED(index, floatreg, intreg) \
if (reg_usage.regs_passed[/*float*/ 1][index]) { \
emit(Ocast, Kl, TMP(intreg), TMP(floatreg), R); \
}
DUP_IF_USED(0, XMM0, RCX);
DUP_IF_USED(1, XMM1, RDX);
DUP_IF_USED(2, XMM2, R8);
DUP_IF_USED(3, XMM3, R9);
#undef DUP_IF_USED
}
int reg_counter = 0;
if (is_struct_return) {
Ref first_reg = register_for_arg(Kl, reg_counter++);
emit(Ocopy, Kl, first_reg, return_pad->instr.to, R);
}
// This is where we actually do the load of values into registers or into
// stack slots.
Ref arg_stack_slots = newtmp("abi.args", Kl, func);
uint slot_offset = SHADOW_SPACE_SIZE;
ArgClass* arg = arg_classes;
for (Ins* instr = earliest_arg_instr; instr != call_instr; ++instr, ++arg) {
switch (arg->style) {
case APS_Register: {
Ref into = register_for_arg(arg->cls, reg_counter++);
if (instr->op == Oargc) {
// If this is a small struct being passed by value. The value in the
// instruction in this case is a pointer, but it needs to be loaded
// into the register.
emit(Oload, arg->cls, into, instr->arg[1], R);
} else {
// Otherwise, a normal value passed in a register.
emit(Ocopy, instr->cls, into, instr->arg[0], R);
}
break;
}
case APS_InlineOnStack: {
Ref slot = newtmp("abi.off", Kl, func);
if (instr->op == Oargc) {
// This is a small struct, so it's not passed by copy, but the
// instruction is a pointer. So we need to copy it into the stack
// slot. (And, remember that these are emitted backwards, so store,
// then load.)
Ref smalltmp = newtmp("abi.smalltmp", arg->cls, func);
emit(Ostorel, 0, R, smalltmp, slot);
emit(Oload, arg->cls, smalltmp, instr->arg[1], R);
} else {
// Stash the value into the stack slot.
emit(Ostorel, 0, R, instr->arg[0], slot);
}
emit(Oadd, Kl, slot, arg_stack_slots, getcon(slot_offset, func));
slot_offset += arg->size;
break;
}
case APS_CopyAndPointerInRegister:
case APS_CopyAndPointerOnStack: {
// Alloca a space to copy into, and blit the value from the instr to the
// copied location.
ExtraAlloc* arg_copy = alloc(sizeof(ExtraAlloc));
Ref copy_ref = newtmp("abi.copy", Kl, func);
arg_copy->instr =
(Ins){Oalloc8, Kl, copy_ref, {getcon(arg->size, func)}};
arg_copy->link = (*pextra_alloc);
*pextra_alloc = arg_copy;
emit(Oblit1, 0, R, INT(arg->size), R);
emit(Oblit0, 0, R, instr->arg[1], copy_ref);
// Now load the pointer into the correct register or stack slot.
if (arg->style == APS_CopyAndPointerInRegister) {
Ref into = register_for_arg(arg->cls, reg_counter++);
emit(Ocopy, Kl, into, copy_ref, R);
} else {
assert(arg->style == APS_CopyAndPointerOnStack);
Ref slot = newtmp("abi.off", Kl, func);
emit(Ostorel, 0, R, copy_ref, slot);
emit(Oadd, Kl, slot, arg_stack_slots, getcon(slot_offset, func));
slot_offset += 8;
}
break;
}
case APS_EnvTag:
case APS_VarargsTag:
// Nothing to do here, see right before the call for reg dupe.
break;
case APS_Invalid:
die("unreachable");
}
}
if (stack_usage) {
// The last (first in call order) thing we do is allocate the the stack
// space we're going to fill with temporaries.
emit(Osalloc, Kl, arg_stack_slots,
getcon(stack_usage + SHADOW_SPACE_SIZE, func), R);
} else {
// When there's no usage for temporaries, we can add this into the other
// alloca, but otherwise emit it separately (not storing into a reference)
// so that it doesn't get removed later for being useless.
emit(Osalloc, Kl, R, getcon(SHADOW_SPACE_SIZE, func), R);
}
return instr_past_args;
}
static void lower_block_return(Fn* func, Blk* block) {
int jmp_type = block->jmp.type;
if (!isret(jmp_type) || jmp_type == Jret0) {
return;
}
// Save the argument, and set the block to be a void return because once it's
// lowered it's handled by the the register/stack manipulation.
Ref ret_arg = block->jmp.arg;
block->jmp.type = Jret0;
RegisterUsage reg_usage = {0};
if (jmp_type == Jretc) {
Typ* type = &typ[func->retty];
if (type_is_by_copy(type)) {
assert(rtype(func->retr) == RTmp);
emit(Ocopy, Kl, TMP(RAX), func->retr, R);
emit(Oblit1, 0, R, INT(type->size), R);
emit(Oblit0, 0, R, ret_arg, func->retr);
} else {
emit(Oload, Kl, TMP(RAX), ret_arg, R);
}
reg_usage.rax_returned = true;
} else {
int k = jmp_type - Jretw;
if (is_integer_type(k)) {
emit(Ocopy, k, TMP(RAX), ret_arg, R);
reg_usage.rax_returned = true;
} else {
emit(Ocopy, k, TMP(XMM0), ret_arg, R);
reg_usage.xmm0_returned = true;
}
}
block->jmp.arg = CALL(register_usage_to_call_arg_value(reg_usage));
}
static void lower_vastart(Fn* func,
RegisterUsage* param_reg_usage,
Ref valist) {
assert(func->vararg);
// In varargs functions:
// 1. the int registers are already dumped to the shadow stack space;
// 2. any parameters passed in floating point registers have
// been duplicated to the integer registers
// 3. we ensure (later) that for varargs functions we're always using an rbp
// frame pointer.
// So, the ... argument is just indexed past rbp by the number of named values
// that were actually passed.
Ref offset = newtmp("abi.vastart", Kl, func);
emit(Ostorel, 0, R, offset, valist);
// *8 for sizeof(u64), +16 because the return address and rbp have been pushed
// by the time we get to the body of the function.
emit(Oadd, Kl, offset, TMP(RBP),
getcon(param_reg_usage->num_named_args_passed * 8 + 16, func));
}
static void lower_vaarg(Fn* func, Ins* vaarg_instr) {
// va_list is just a void** on winx64, so load the pointer, then load the
// argument from that pointer, then increment the pointer to the next arg.
// (All emitted backwards as usual.)
Ref inc = newtmp("abi.vaarg.inc", Kl, func);
Ref ptr = newtmp("abi.vaarg.ptr", Kl, func);
emit(Ostorel, 0, R, inc, vaarg_instr->arg[0]);
emit(Oadd, Kl, inc, ptr, getcon(8, func));
emit(Oload, vaarg_instr->cls, vaarg_instr->to, ptr, R);
emit(Oload, Kl, ptr, vaarg_instr->arg[0], R);
}
static void lower_args_for_block(Fn* func,
Blk* block,
RegisterUsage* param_reg_usage,
ExtraAlloc** pextra_alloc) {
// global temporary buffer used by emit. Reset to the end, and predecremented
// when adding to it.
curi = &insb[NIns];
lower_block_return(func, block);
if (block->nins) {
// Work backwards through the instructions, either copying them unchanged,
// or modifying as necessary.
for (Ins* instr = &block->ins[block->nins - 1]; instr >= block->ins;) {
switch (instr->op) {
case Ocall:
instr = lower_call(func, block, instr, pextra_alloc);
break;
case Ovastart:
lower_vastart(func, param_reg_usage, instr->arg[0]);
--instr;
break;
case Ovaarg:
lower_vaarg(func, instr);
--instr;
break;
case Oarg:
case Oargc:
die("unreachable");
default:
emiti(*instr);
--instr;
break;
}
}
}
// This it the start block, which is processed last. Add any allocas that
// other blocks needed.
bool is_start_block = block == func->start;
if (is_start_block) {
for (ExtraAlloc* ea = *pextra_alloc; ea; ea = ea->link) {
emiti(ea->instr);
}
}
// emit/emiti add instructions from the end to the beginning of the temporary
// global buffer. dup the final version into the final block storage.
block->nins = &insb[NIns] - curi;
idup(block, curi, block->nins);
}
static Ins* find_end_of_func_parameters(Blk* start_block) {
Ins* i;
for (i = start_block->ins; i < &start_block->ins[start_block->nins]; ++i) {
if (!ispar(i->op)) {
break;
}
}
return i;
}
// Copy from registers/stack into values.
static RegisterUsage lower_func_parameters(Fn* func) {
// This is half-open, so end points after the last Opar.
Blk* start_block = func->start;
Ins* start_of_params = start_block->ins;
Ins* end_of_params = find_end_of_func_parameters(start_block);
size_t num_params = end_of_params - start_of_params;
ArgClass* arg_classes = alloc(num_params * sizeof(ArgClass));
ArgClass arg_ret = {0};
// global temporary buffer used by emit. Reset to the end, and predecremented
// when adding to it.
curi = &insb[NIns];
int reg_counter = 0;
RegisterUsage reg_usage = {0};
if (func->retty >= 0) {
bool by_copy = type_is_by_copy(&typ[func->retty]);
if (by_copy) {
assign_register_or_stack(&reg_usage, &arg_ret, /*is_float=*/false,
by_copy);
Ref ret_ref = newtmp("abi.ret", Kl, func);
emit(Ocopy, Kl, ret_ref, TMP(RCX), R);
func->retr = ret_ref;
++reg_counter;
}
}
Ref env = R;
classify_arguments(&reg_usage, start_of_params, end_of_params, arg_classes,
&env);
func->reg = amd64_winabi_argregs(
CALL(register_usage_to_call_arg_value(reg_usage)), NULL);
// Copy from the registers or stack slots into the named parameters. Depending
// on how they're passed, they either need to be copied or loaded.
ArgClass* arg = arg_classes;
uint slot_offset = SHADOW_SPACE_SIZE / 4 + 4;
for (Ins* instr = start_of_params; instr < end_of_params; ++instr, ++arg) {
switch (arg->style) {
case APS_Register: {
Ref from = register_for_arg(arg->cls, reg_counter++);
// If it's a struct at the IL level, we need to copy the register into
// an alloca so we have something to point at (same for InlineOnStack).
if (instr->op == Oparc) {
arg->ref = newtmp("abi", Kl, func);
emit(Ostorel, 0, R, arg->ref, instr->to);
emit(Ocopy, instr->cls, arg->ref, from, R);
emit(Oalloc8, Kl, instr->to, getcon(arg->size, func), R);
} else {
emit(Ocopy, instr->cls, instr->to, from, R);
}
break;
}
case APS_InlineOnStack:
if (instr->op == Oparc) {
arg->ref = newtmp("abi", Kl, func);
emit(Ostorel, 0, R, arg->ref, instr->to);
emit(Ocopy, instr->cls, arg->ref, SLOT(-slot_offset), R);
emit(Oalloc8, Kl, instr->to, getcon(arg->size, func), R);
} else {
emit(Ocopy, Kl, instr->to, SLOT(-slot_offset), R);
}
slot_offset += 2;
break;
case APS_CopyAndPointerOnStack:
emit(Oload, Kl, instr->to, SLOT(-slot_offset), R);
slot_offset += 2;
break;
case APS_CopyAndPointerInRegister: {
// Because this has to be a copy (that we own), it is sufficient to just
// copy the register to the target.
Ref from = register_for_arg(Kl, reg_counter++);
emit(Ocopy, Kl, instr->to, from, R);
break;
}
case APS_EnvTag:
break;
case APS_VarargsTag:
case APS_Invalid:
die("unreachable");
}
}
// If there was an `env`, it was passed in RAX, so copy it into the env ref.
if (!req(R, env)) {
emit(Ocopy, Kl, env, TMP(RAX), R);
}
int num_created_instrs = &insb[NIns] - curi;
int num_other_after_instrs = (int)(start_block->nins - num_params);
int new_total_instrs = num_other_after_instrs + num_created_instrs;
Ins* new_instrs = vnew(new_total_instrs, sizeof(Ins), PFn);
Ins* instr_p = icpy(new_instrs, curi, num_created_instrs);
icpy(instr_p, end_of_params, num_other_after_instrs);
start_block->nins = new_total_instrs;
start_block->ins = new_instrs;
return reg_usage;
}
// The main job of this function is to lower generic instructions into the
// specific details of how arguments are passed, and parameters are
// interpreted for win x64. A useful reference is
// https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention .
//
// Some of the major differences from SysV if you're comparing the code
// (non-exhaustive):
// - only 4 int and 4 float regs are used
// - when an int register is assigned a value, its associated float register is
// left unused (and vice versa). i.e. there's only one counter as you assign
// arguments to registers.
// - any structs that aren't 1/2/4/8 bytes in size are passed by pointer, not
// by copying them into the stack. So e.g. if you pass something like
// `struct { void*, int64_t }` by value, it first needs to be copied to
// another alloca (in order to maintain value semantics at the language
// level), then the pointer to that copy is treated as a regular integer
// argument (which then itself may *also* be copied to the stack in the case
// there's no integer register remaining.)
// - when calling a varargs functions, floating point values must be duplicated
// integer registers. Along with the above restrictions, this makes varargs
// handling simpler for the callee than SysV.
void amd64_winabi_abi(Fn* func) {
// The first thing to do is lower incoming parameters to this function.
RegisterUsage param_reg_usage = lower_func_parameters(func);
// This is the second larger part of the job. We walk all blocks, and rewrite
// instructions returns, calls, and handling of varargs into their win x64
// specific versions. Any other instructions are just passed through unchanged
// by using `emiti`.
// Skip over the entry block, and do it at the end so that our later
// modifications can add allocations to the start block. In particular, we
// need to add stack allocas for copies when structs are passed or returned by
// value.
ExtraAlloc* extra_alloc = NULL;
for (Blk* block = func->start->link; block; block = block->link) {
lower_args_for_block(func, block, &param_reg_usage, &extra_alloc);
}
lower_args_for_block(func, func->start, &param_reg_usage, &extra_alloc);
if (debug['A']) {
fprintf(stderr, "\n> After ABI lowering:\n");
printfn(func, stderr);
}
}

View File

@ -429,7 +429,7 @@ selcall(Fn *fn, Ins *i0, Ins *i1, Insl **ilp)
for (i=i0, c=ca; i<i1; i++, c++) {
if ((c->class & Cstk) != 0)
continue;
if (i->op == Oarg || i->op == Oarge || isargbh(i->op))
if (i->op == Oarg || i->op == Oarge)
emit(Ocopy, *c->cls, TMP(*c->reg), i->arg[0], R);
if (i->op == Oargc)
ldregs(c->reg, c->cls, c->nreg, i->arg[1], fn);

View File

@ -10,24 +10,24 @@ struct E {
};
#define CMP(X) \
X(Cieq, "eq", "ne") \
X(Cine, "ne", "eq") \
X(Cisge, "ge", "lt") \
X(Cisgt, "gt", "le") \
X(Cisle, "le", "gt") \
X(Cislt, "lt", "ge") \
X(Ciuge, "cs", "cc") \
X(Ciugt, "hi", "ls") \
X(Ciule, "ls", "hi") \
X(Ciult, "cc", "cs") \
X(NCmpI+Cfeq, "eq", "ne") \
X(NCmpI+Cfge, "ge", "lt") \
X(NCmpI+Cfgt, "gt", "le") \
X(NCmpI+Cfle, "ls", "hi") \
X(NCmpI+Cflt, "mi", "pl") \
X(NCmpI+Cfne, "ne", "eq") \
X(NCmpI+Cfo, "vc", "vs") \
X(NCmpI+Cfuo, "vs", "vc")
X(Cieq, "eq") \
X(Cine, "ne") \
X(Cisge, "ge") \
X(Cisgt, "gt") \
X(Cisle, "le") \
X(Cislt, "lt") \
X(Ciuge, "cs") \
X(Ciugt, "hi") \
X(Ciule, "ls") \
X(Ciult, "cc") \
X(NCmpI+Cfeq, "eq") \
X(NCmpI+Cfge, "ge") \
X(NCmpI+Cfgt, "gt") \
X(NCmpI+Cfle, "ls") \
X(NCmpI+Cflt, "mi") \
X(NCmpI+Cfne, "ne") \
X(NCmpI+Cfo, "vc") \
X(NCmpI+Cfuo, "vs")
enum {
Ki = -1, /* matches Kw and Kl */
@ -102,17 +102,13 @@ static struct {
{ Oacmn, Ki, "cmn %0, %1" },
{ Oafcmp, Ka, "fcmpe %0, %1" },
#define X(c, str, _) \
#define X(c, str) \
{ Oflag+c, Ki, "cset %=, " str },
CMP(X)
#undef X
{ NOp, 0, 0 }
};
enum {
V31 = 0x1fffffff, /* local name for V31 */
};
static char *
rname(int r, int k)
{
@ -136,12 +132,6 @@ rname(int r, int k)
case Kx:
case Kd: sprintf(buf, "d%d", r-V0); break;
}
else if (r == V31)
switch (k) {
default: die("invalid class");
case Ks: sprintf(buf, "s31"); break;
case Kd: sprintf(buf, "d31"); break;
}
else
die("invalid register");
return buf;
@ -182,7 +172,7 @@ emitf(char *s, Ins *i, E *e)
if (c == ' ' && !sp) {
fputc('\t', e->f);
sp = 1;
} else if (!c) {
} else if ( !c) {
fputc('\n', e->f);
return;
} else
@ -207,12 +197,12 @@ emitf(char *s, Ins *i, E *e)
if (KBASE(k) == 0)
fputs(rname(IP1, k), e->f);
else
fputs(rname(V31, k), e->f);
fputs(k==Ks ? "s31" : "d31", e->f);
break;
case '=':
case '0':
r = c == '=' ? i->to : i->arg[0];
assert(isreg(r) || req(r, TMP(V31)));
assert(isreg(r));
fputs(rname(r.val, k), e->f);
break;
case '1':
@ -278,10 +268,6 @@ loadaddr(Con *c, char *rn, E *e)
s = "\tadrp\tR, SO\n"
"\tadd\tR, R, #:lo12:SO\n";
break;
case SExtThr:
if (!T.apple)
die("extern thread unavailable on arm64");
/* fall through */
case SThr:
if (T.apple)
s = "\tadrp\tR, S@tlvppage\n"
@ -291,14 +277,6 @@ loadaddr(Con *c, char *rn, E *e)
"\tadd\tR, R, #:tprel_hi12:SO, lsl #12\n"
"\tadd\tR, R, #:tprel_lo12_nc:SO\n";
break;
case SExt:
if (T.apple)
s = "\tadrp\tR, S@gotpageO\n"
"\tldr\tR, [R, S@gotpageoffO]\n";
else
s = "\tadrp\tR, :got:SO\n"
"\tldr\tR, [R, #:got_lo12:SO]\n";
break;
}
l = str(c->sym.id);
@ -357,8 +335,8 @@ loadcon(Con *c, int r, int k, E *e)
static void emitins(Ins *, E *);
static int
fixarg(Ref *pr, int sz, int t, E *e)
static void
fixarg(Ref *pr, int sz, E *e)
{
Ins *i;
Ref r;
@ -368,14 +346,11 @@ fixarg(Ref *pr, int sz, int t, E *e)
if (rtype(r) == RSlot) {
s = slot(r, e);
if (s > sz * 4095u) {
if (t < 0)
return 1;
i = &(Ins){Oaddr, Kl, TMP(t), {r}};
i = &(Ins){Oaddr, Kl, TMP(IP1), {r}};
emitins(i, e);
*pr = TMP(t);
*pr = TMP(IP1);
}
}
return 0;
}
static void
@ -383,28 +358,16 @@ emitins(Ins *i, E *e)
{
char *l, *p, *rn;
uint64_t s;
int o, t;
int o;
Ref r;
Con *c;
switch (i->op) {
default:
if (isload(i->op))
fixarg(&i->arg[0], loadsz(i), IP1, e);
if (isstore(i->op)) {
t = T.apple ? -1 : R18;
if (fixarg(&i->arg[1], storesz(i), t, e)) {
if (req(i->arg[0], TMP(IP1))) {
fprintf(e->f,
"\tfmov\t%c31, %c17\n",
"ds"[i->cls == Kw],
"xw"[i->cls == Kw]);
i->arg[0] = TMP(V31);
i->op = Ostores + (i->cls-Kw);
}
fixarg(&i->arg[1], storesz(i), IP1, e);
}
}
fixarg(&i->arg[0], loadsz(i), e);
if (isstore(i->op))
fixarg(&i->arg[1], storesz(i), e);
Table:
/* most instructions are just pulled out of
* the table omap[], some special cases are
@ -480,7 +443,7 @@ emitins(Ins *i, E *e)
goto Table;
c = &e->fn->con[i->arg[0].val];
if (c->type != CAddr
|| (c->sym.type & SThr)
|| c->sym.type != SGlo
|| c->bits.i)
die("invalid call argument");
l = str(c->sym.id);
@ -544,8 +507,8 @@ framelayout(E *e)
void
arm64_emitfn(Fn *fn, FILE *out)
{
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
static char *ctoa[] = {
#define X(c, s) [c] = s,
CMP(X)
#undef X
};
@ -676,12 +639,11 @@ arm64_emitfn(Fn *fn, FILE *out)
t = b->s1;
b->s1 = b->s2;
b->s2 = t;
n = 0;
} else
n = 1;
c = cmpneg(c);
fprintf(e->f,
"\tb%s\t%s%d\n",
ctoa[c][n], T.asloc, id0+b->s2->id
ctoa[c], T.asloc, id0+b->s2->id
);
goto Jmp;
}

View File

@ -80,7 +80,7 @@ fixarg(Ref *pr, int k, int phi, Fn *fn)
c = &fn->con[r0.val];
if (T.apple
&& c->type == CAddr
&& (c->sym.type & SThr)) {
&& c->sym.type == SThr) {
r1 = newtmp("isel", Kl, fn);
*pr = r1;
if (c->bits.i) {
@ -109,7 +109,7 @@ fixarg(Ref *pr, int k, int phi, Fn *fn)
if (KBASE(k) == 0) {
emit(Ocopy, k, r1, r0, R);
} else {
n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4);
n = stashbits(&c->bits, KWIDE(k) ? 8 : 4);
vgrow(&fn->con, ++fn->ncon);
c = &fn->con[fn->ncon-1];
sprintf(buf, "\"%sfp%d\"", T.asloc, n);

View File

@ -40,7 +40,6 @@ arm64_memargs(int op)
.isel = arm64_isel, \
.abi1 = arm64_abi, \
.emitfn = arm64_emitfn, \
.cansel = 0, \
Target T_arm64 = {
.name = "arm64",

177
cfg.c
View File

@ -16,22 +16,19 @@ newblk()
static void
fixphis(Fn *f)
{
Blk *b, *bp;
Blk *b;
Phi *p;
uint n, n0;
for (b=f->start; b; b=b->link) {
assert(b->id < f->nblk);
for (p=b->phi; p; p=p->link) {
for (n=n0=0; n<p->narg; n++) {
bp = p->blk[n];
if (bp->id != -1u)
if (bp->s1 == b || bp->s2 == b) {
p->blk[n0] = bp;
for (n=n0=0; n<p->narg; n++)
if (p->blk[n]->id != -1u) {
p->blk[n0] = p->blk[n];
p->arg[n0] = p->arg[n];
n0++;
}
}
assert(n0 > 0);
p->narg = n0;
}
@ -399,169 +396,3 @@ reachesnotvia(Fn *fn, Blk *b, Blk *to, Blk *excl)
excl->visit = 1;
return reaches(fn, b, to);
}
int
ifgraph(Blk *ifb, Blk **pthenb, Blk **pelseb, Blk **pjoinb)
{
Blk *s1, *s2, **t;
if (ifb->jmp.type != Jjnz)
return 0;
s1 = ifb->s1;
s2 = ifb->s2;
if (s1->id > s2->id) {
s1 = ifb->s2;
s2 = ifb->s1;
t = pthenb;
pthenb = pelseb;
pelseb = t;
}
if (s1 == s2)
return 0;
if (s1->jmp.type != Jjmp || s1->npred != 1)
return 0;
if (s1->s1 == s2) {
/* if-then / if-else */
if (s2->npred != 2)
return 0;
*pthenb = s1;
*pelseb = ifb;
*pjoinb = s2;
return 1;
}
if (s2->jmp.type != Jjmp || s2->npred != 1)
return 0;
if (s1->s1 != s2->s1 || s1->s1->npred != 2)
return 0;
assert(s1->s1 != ifb);
*pthenb = s1;
*pelseb = s2;
*pjoinb = s1->s1;
return 1;
}
typedef struct Jmp Jmp;
struct Jmp {
int type;
Ref arg;
Blk *s1, *s2;
};
static int
jmpeq(Jmp *a, Jmp *b)
{
return a->type == b->type && req(a->arg, b->arg)
&& a->s1 == b->s1 && a->s2 == b->s2;
}
static int
jmpnophi(Jmp *j)
{
if (j->s1 && j->s1->phi)
return 0;
if (j->s2 && j->s2->phi)
return 0;
return 1;
}
/* require cfg rpo, breaks use */
void
simplcfg(Fn *fn)
{
Ins cpy, *i;
Blk *b, *bb, **pb;
Jmp *jmp, *j, *jj;
Phi *p;
int *empty, done;
uint n;
if (debug['C']) {
fprintf(stderr, "\n> Before CFG simplification:\n");
printfn(fn, stderr);
}
cpy = (Ins){.op = Ocopy};
for (b=fn->start; b; b=b->link)
if (b->npred == 1) {
bb = b->pred[0];
for (p=b->phi; p; p=p->link) {
cpy.cls = p->cls;
cpy.to = p->to;
cpy.arg[0] = phiarg(p, bb);
addins(&bb->ins, &bb->nins, &cpy);
}
b->phi = 0;
}
jmp = emalloc(fn->nblk * sizeof jmp[0]);
empty = emalloc(fn->nblk * sizeof empty[0]);
for (b=fn->start; b; b=b->link) {
jmp[b->id].type = b->jmp.type;
jmp[b->id].arg = b->jmp.arg;
jmp[b->id].s1 = b->s1;
jmp[b->id].s2 = b->s2;
empty[b->id] = !b->phi;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (i->op != Onop && i->op != Odbgloc) {
empty[b->id] = 0;
break;
}
}
do {
done = 1;
for (b=fn->start; b; b=b->link) {
if (b->id == -1u)
continue;
j = &jmp[b->id];
if (j->type == Jjmp && j->s1->npred == 1) {
assert(!j->s1->phi);
addbins(&b->ins, &b->nins, j->s1);
empty[b->id] &= empty[j->s1->id];
jj = &jmp[j->s1->id];
pb = (Blk*[]){jj->s1, jj->s2, 0};
for (; (bb=*pb); pb++)
for (p=bb->phi; p; p=p->link) {
n = phiargn(p, j->s1);
p->blk[n] = b;
}
j->s1->id = -1u;
*j = *jj;
done = 0;
}
else if (j->type == Jjnz
&& empty[j->s1->id] && empty[j->s2->id]
&& jmpeq(&jmp[j->s1->id], &jmp[j->s2->id])
&& jmpnophi(&jmp[j->s1->id])) {
*j = jmp[j->s1->id];
done = 0;
}
}
} while (!done);
for (b=fn->start; b; b=b->link)
if (b->id != -1u) {
j = &jmp[b->id];
b->jmp.type = j->type;
b->jmp.arg = j->arg;
b->s1 = j->s1;
b->s2 = j->s2;
assert(!j->s1 || j->s1->id != -1u);
assert(!j->s2 || j->s2->id != -1u);
}
fillcfg(fn);
free(empty);
free(jmp);
if (debug['C']) {
fprintf(stderr, "\n> After CFG simplification:\n");
printfn(fn, stderr);
}
}

35
copy.c
View File

@ -41,8 +41,9 @@ bitwidth(uint64_t v)
return n+v;
}
/* no more than w bits are used */
static int
uwl(Fn *fn, Ref r, int w)
usewidthle(Fn *fn, Ref r, int w)
{
Ext e;
Tmp *t;
@ -51,6 +52,7 @@ uwl(Fn *fn, Ref r, int w)
Ins *i;
Ref rc;
int64_t v;
int b;
assert(rtype(r) == RTmp);
t = &fn->tmp[r.val];
@ -58,28 +60,24 @@ uwl(Fn *fn, Ref r, int w)
switch (u->type) {
case UPhi:
p = u->u.phi;
/* during gvn, phi nodes may be
* replaced by other temps; in
* this case, the replaced phi
* uses are added to the
* replacement temp uses and
* Phi.to is set to R */
if (p->visit || req(p->to, R))
if (p->visit)
continue;
p->visit = 1;
if (uwl(fn, p->to, w))
b = usewidthle(fn, p->to, w);
p->visit = 0;
if (b)
continue;
break;
case UIns:
i = u->u.ins;
assert(i != 0);
if (i->op == Ocopy)
if (uwl(fn, i->to, w))
if (usewidthle(fn, i->to, w))
continue;
if (ext(i, &e)) {
if (e.usew <= w)
continue;
if (uwl(fn, i->to, w))
if (usewidthle(fn, i->to, w))
continue;
}
if (i->op == Oand) {
@ -103,21 +101,6 @@ uwl(Fn *fn, Ref r, int w)
return 1;
}
/* no more than w bits are used */
static int
usewidthle(Fn *fn, Ref r, int w)
{
Blk *b;
Phi *p;
int ret;
ret = uwl(fn, r, w);
for (b=fn->start; b; b=b->link)
for (p=b->phi; p; p=p->link)
p->visit = 0;
return ret;
}
static int
min(int v1, int v2)
{

View File

@ -180,9 +180,7 @@ by zero-extension, or by sign-extension.
DYNCONST :=
CONST
| 'thread' $IDENT # Thread-local symbol
| 'extern' $IDENT # Extern symbol (GOT)
| 'extern' 'thread' $IDENT # Extern thread-local (initial-exec)
| 'thread' $IDENT # Thread-local symbol
VAL :=
DYNCONST
@ -227,15 +225,6 @@ When the `thread` keyword prefixes a symbol name, the
symbol's numeric value is resolved at runtime in the
thread-local storage.
When the `extern` keyword prefixes a symbol name, the
symbol is accessed indirectly through a table edited
by the dynamic linker (e.g., GOT/PLT). This enables
PIE/PIC code generation. When `extern` is combined
with `thread`, the symbol is accessed using the
initial-exec TLS model, suitable for thread-local
variables defined in shared objects available at
startup time (i.e., not loaded through dlopen).
Vals are used as arguments in regular, phi, and jump
instructions within function definitions. They are
either constants or function-scope temporaries.
@ -987,7 +976,6 @@ is possible to conservatively use the maximum size and
alignment required by all the targets.
type :valist = align 8 { 24 } # For amd64_sysv
type :valist = align 8 { 8 } # For amd64_win
type :valist = align 8 { 32 } # For arm64
type :valist = align 8 { 8 } # For rv64

View File

@ -1,15 +0,0 @@
There is an experimental amd64_win (native Windows ABI and calling
convention).
In tree, this is currently only tested via cross-compilation from a
Linux host, and using wine to run the tests.
You'll need something like:
sudo apt install mingw64-w64 dos2unix wine
and then
make check-amd64_win
should pass.

69
emit.c
View File

@ -61,14 +61,11 @@ emitfnlnk(char *n, Lnk *l, FILE *f)
void
emitdat(Dat *d, FILE *f)
{
static struct {
char decl[8];
int64_t mask;
} di[] = {
[DB] = {"\t.byte", 0xffL},
[DH] = {"\t.short", 0xffffL},
[DW] = {"\t.int", 0xffffffffL},
[DL] = {"\t.quad", -1L},
static char *dtoa[] = {
[DB] = "\t.byte",
[DH] = "\t.short",
[DW] = "\t.int",
[DL] = "\t.quad"
};
static int64_t zero;
char *p;
@ -114,13 +111,12 @@ emitdat(Dat *d, FILE *f)
else if (d->isref) {
p = d->u.ref.name[0] == '"' ? "" : T.assym;
fprintf(f, "%s %s%s%+"PRId64"\n",
di[d->type].decl, p, d->u.ref.name,
dtoa[d->type], p, d->u.ref.name,
d->u.ref.off);
}
else {
fprintf(f, "%s %"PRId64"\n",
di[d->type].decl,
d->u.num & di[d->type].mask);
dtoa[d->type], d->u.num);
}
break;
}
@ -129,7 +125,7 @@ emitdat(Dat *d, FILE *f)
typedef struct Asmbits Asmbits;
struct Asmbits {
bits n;
char bits[16];
int size;
Asmbits *link;
};
@ -137,17 +133,18 @@ struct Asmbits {
static Asmbits *stash;
int
stashbits(bits n, int size)
stashbits(void *bits, int size)
{
Asmbits **pb, *b;
int i;
assert(size == 4 || size == 8 || size == 16);
for (pb=&stash, i=0; (b=*pb); pb=&b->link, i++)
if (size <= b->size && b->n == n)
if (size <= b->size)
if (memcmp(bits, b->bits, size) == 0)
return i;
b = emalloc(sizeof *b);
b->n = n;
memcpy(b->bits, bits, size);
b->size = size;
b->link = 0;
*pb = b;
@ -158,8 +155,9 @@ static void
emitfin(FILE *f, char *sec[3])
{
Asmbits *b;
char *p;
int lg, i;
union { int32_t i; float f; } u;
double d;
if (!stash)
return;
@ -173,24 +171,17 @@ emitfin(FILE *f, char *sec[3])
"%sfp%d:",
sec[lg-2], lg, T.asloc, i
);
if (lg == 4)
fprintf(f,
"\n\t.quad %"PRId64
"\n\t.quad 0\n\n",
(int64_t)b->n);
else if (lg == 3)
fprintf(f,
"\n\t.quad %"PRId64
" /* %f */\n\n",
(int64_t)b->n,
*(double *)&b->n);
else if (lg == 2) {
u.i = b->n;
fprintf(f,
"\n\t.int %"PRId32
" /* %f */\n\n",
u.i, (double)u.f);
}
for (p=b->bits; p<&b->bits[b->size]; p+=4)
fprintf(f, "\n\t.int %"PRId32,
*(int32_t *)p);
if (lg <= 3) {
if (lg == 2)
d = *(float *)b->bits;
else
d = *(double *)b->bits;
fprintf(f, " /* %f */\n\n", d);
} else
fprintf(f, "\n\n");
}
}
while ((b=stash)) {
@ -221,20 +212,12 @@ macho_emitfin(FILE *f)
static char *sec[3] = {
"__TEXT,__literal4,4byte_literals",
"__TEXT,__literal8,8byte_literals",
"__TEXT,__literal16,16byte_literals",
".abort \"unreachable\"",
};
emitfin(f, sec);
}
void
pe_emitfin(FILE *f)
{
static char *sec[3] = { ".rodata", ".rodata", ".rodata" };
emitfin(f, sec);
}
static uint32_t *file;
static uint nfile;
static uint curfile;

8
gvn.c
View File

@ -43,11 +43,11 @@ static uint gvntbln;
static Ins *
gvndup(Ins *i, int insert)
{
uint idx;
uint idx, n;
Ins *ii;
idx = ihash(i) % gvntbln;
for (;;) {
for (n=1;; n++) {
ii = gvntbl[idx];
if (!ii)
break;
@ -247,10 +247,6 @@ dedupins(Fn *fn, Blk *b, Ins *i)
if (i->op == Onop || pinned(i))
return;
/* when sel instructions are inserted
* before gvn, we may want to optimize
* them here */
assert(i->op != Osel0);
assert(!req(i->to, R));
assoccon(fn, b, i);

121
ifopt.c
View File

@ -1,121 +0,0 @@
#include "all.h"
enum {
MaxIns = 2,
MaxPhis = 2,
};
static int
okbranch(Blk *b)
{
Ins *i;
int n;
n = 0;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (i->op != Odbgloc) {
if (pinned(i))
return 0;
if (i->op != Onop)
n++;
}
return n <= MaxIns;
}
static int
okjoin(Blk *b)
{
Phi *p;
int n;
n = 0;
for (p=b->phi; p; p=p->link) {
if (KBASE(p->cls) != 0)
return 0;
n++;
}
return n <= MaxPhis;
}
static int
okgraph(Blk *ifb, Blk *thenb, Blk *elseb, Blk *joinb)
{
if (joinb->npred != 2 || !okjoin(joinb))
return 0;
assert(thenb != elseb);
if (thenb != ifb && !okbranch(thenb))
return 0;
if (elseb != ifb && !okbranch(elseb))
return 0;
return 1;
}
static void
convert(Blk *ifb, Blk *thenb, Blk *elseb, Blk *joinb)
{
Ins *ins, sel;
Phi *p;
uint nins;
ins = vnew(0, sizeof ins[0], PHeap);
nins = 0;
addbins(&ins, &nins, ifb);
if (thenb != ifb)
addbins(&ins, &nins, thenb);
if (elseb != ifb)
addbins(&ins, &nins, elseb);
assert(joinb->npred == 2);
if (joinb->phi) {
sel = (Ins){
.op = Osel0, .cls = Kw,
.arg = {ifb->jmp.arg},
};
addins(&ins, &nins, &sel);
}
sel = (Ins){.op = Osel1};
for (p=joinb->phi; p; p=p->link) {
sel.to = p->to;
sel.cls = p->cls;
sel.arg[0] = phiarg(p, thenb);
sel.arg[1] = phiarg(p, elseb);
addins(&ins, &nins, &sel);
}
idup(ifb, ins, nins);
ifb->jmp.type = Jjmp;
ifb->jmp.arg = R;
ifb->s1 = joinb;
ifb->s2 = 0;
joinb->npred = 1;
joinb->pred[0] = ifb;
joinb->phi = 0;
vfree(ins);
}
/* eliminate if-then[-else] graphlets
* using sel instructions
* needs rpo pred use; breaks cfg use
*/
void
ifconvert(Fn *fn)
{
Blk *ifb, *thenb, *elseb, *joinb;
if (debug['K'])
fputs("\n> If-conversion:\n", stderr);
for (ifb=fn->start; ifb; ifb=ifb->link)
if (ifgraph(ifb, &thenb, &elseb, &joinb))
if (okgraph(ifb, thenb, elseb, joinb)) {
if (debug['K'])
fprintf(stderr,
" @%s -> @%s, @%s -> @%s\n",
ifb->name, thenb->name, elseb->name,
joinb->name);
convert(ifb, thenb, elseb, joinb);
}
if (debug['K']) {
fprintf(stderr, "\n> After if-conversion:\n");
printfn(fn, stderr);
}
}

13
main.c
View File

@ -10,8 +10,7 @@ char debug['Z'+1] = {
['M'] = 0, /* memory optimization */
['N'] = 0, /* ssa construction */
['C'] = 0, /* copy elimination */
['G'] = 0, /* gvn/gcm */
['K'] = 0, /* if-conversion */
['F'] = 0, /* constant folding */
['A'] = 0, /* abi lowering */
['I'] = 0, /* instruction selection */
['L'] = 0, /* liveness */
@ -21,7 +20,6 @@ char debug['Z'+1] = {
extern Target T_amd64_sysv;
extern Target T_amd64_apple;
extern Target T_amd64_win;
extern Target T_arm64;
extern Target T_arm64_apple;
extern Target T_rv64;
@ -29,7 +27,6 @@ extern Target T_rv64;
static Target *tlist[] = {
&T_amd64_sysv,
&T_amd64_apple,
&T_amd64_win,
&T_arm64,
&T_arm64_apple,
&T_rv64,
@ -79,7 +76,6 @@ func(Fn *fn)
ssacheck(fn);
gvn(fn);
fillcfg(fn);
simplcfg(fn);
filluse(fn);
filldom(fn);
gcm(fn);
@ -87,13 +83,6 @@ func(Fn *fn)
ssacheck(fn);
loopopt(fn);
filluse(fn);
if (T.cansel) {
ifconvert(fn);
fillcfg(fn);
filluse(fn);
filldom(fn);
ssacheck(fn);
}
T.abi1(fn);
simpl(fn);
fillcfg(fn);

22
ops.h
View File

@ -145,8 +145,6 @@ O(nop, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(addr, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(blit0, T(m,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,1,0) V(0)
O(blit1, T(w,e,e,e, x,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,1,0) V(0)
O(sel0, T(w,e,e,e, x,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(sel1, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(swap, T(w,l,s,d, w,l,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0)
O(sign, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(salloc, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
@ -198,26 +196,6 @@ O(flagfne, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfo, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfuo, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
/* Backend Flag Select (Condition Move) */
O(xselieq, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseline, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisge, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisgt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisle, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselislt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliuge, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliugt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliule, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliult, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfeq, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfge, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfgt, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfle, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselflt, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfne, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfo, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfuo, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
#undef T
#undef X
#undef V

35
parse.c
View File

@ -57,7 +57,6 @@ enum Token {
Thlt,
Texport,
Tthread,
Textern,
Tcommon,
Tfunc,
Ttype,
@ -117,7 +116,6 @@ static char *kwmap[Ntok] = {
[Thlt] = "hlt",
[Texport] = "export",
[Tthread] = "thread",
[Textern] = "extern",
[Tcommon] = "common",
[Tfunc] = "function",
[Ttype] = "type",
@ -217,15 +215,12 @@ getint()
n = 0;
c = fgetc(inf);
m = (c == '-');
if (m) {
if (m)
c = fgetc(inf);
if (!isdigit(c))
err("integer expected");
}
do {
n = 10*n + (c - '0');
c = fgetc(inf);
} while (isdigit(c));
} while ('0' <= c && c <= '9');
ungetc(c, inf);
if (m)
n = 1 + ~n;
@ -429,10 +424,11 @@ static Ref
parseref()
{
Con c;
int tok;
memset(&c, 0, sizeof c);
switch ((tok = next())) {
switch (next()) {
default:
return R;
case Ttmp:
return tmpref(tokval.str);
case Tint:
@ -449,20 +445,9 @@ parseref()
c.bits.d = tokval.fltd;
c.flt = 2;
break;
default:
for (;; tok=next()) {
switch (tok) {
case Textern:
c.sym.type |= SExt;
continue;
case Tthread:
c.sym.type |= SThr;
continue;
}
break;
}
if (tok != Tglo)
return R;
case Tthread:
c.sym.type = SThr;
expect(Tglo);
/* fall through */
case Tglo:
c.type = CAddr;
@ -1267,9 +1252,7 @@ printcon(Con *c, FILE *f)
case CUndef:
break;
case CAddr:
if (c->sym.type & SExt)
fprintf(f, "extern ");
if (c->sym.type & SThr)
if (c->sym.type == SThr)
fprintf(f, "thread ");
fprintf(f, "$%s", str(c->sym.id));
if (c->bits.i)

View File

@ -131,7 +131,7 @@ slot(Ref r, Fn *fn)
static void
emitaddr(Con *c, FILE *f)
{
assert((c->sym.type & ~SExt) == SGlo);
assert(c->sym.type == SGlo);
fputs(str(c->sym.id), f);
if (c->bits.i)
fprintf(f, "+%"PRIi64, c->bits.i);
@ -231,20 +231,7 @@ loadaddr(Con *c, char *rn, FILE *f)
{
char off[32];
switch (c->sym.type) {
case SGlo:
fprintf(f, "\tlui %s, %%hi(", rn);
emitaddr(c, f);
fprintf(f, ")\n\taddi %s, %s, %%lo(", rn, rn);
emitaddr(c, f);
fputs(")\n", f);
break;
case SExt:
fprintf(f, "\tla %s, ", rn);
emitaddr(c, f);
fputc('\n', f);
break;
case SThr:
if (c->sym.type == SThr) {
if (c->bits.i)
sprintf(off, "+%"PRIi64, c->bits.i);
else
@ -255,9 +242,10 @@ loadaddr(Con *c, char *rn, FILE *f)
rn, rn, str(c->sym.id), off);
fprintf(f, "\taddi %s, %s, %%tprel_lo(%s)%s\n",
rn, rn, str(c->sym.id), off);
break;
case SExtThr:
die("extern thread unavailable on rv64");
} else {
fprintf(f, "\tla %s, ", rn);
emitaddr(c, f);
fputc('\n', f);
}
}
@ -294,7 +282,7 @@ fixmem(Ref *pr, Fn *fn, FILE *f)
if (rtype(r) == RCon) {
c = &fn->con[r.val];
if (c->type == CAddr)
if (c->sym.type != SGlo) {
if (c->sym.type == SThr) {
loadcon(c, T6, Kl, f);
*pr = TMP(T6);
}
@ -399,7 +387,7 @@ emitins(Ins *i, Fn *fn, FILE *f)
case RCon:
con = &fn->con[i->arg[0].val];
if (con->type != CAddr
|| (con->sym.type & SThr)
|| con->sym.type != SGlo
|| con->bits.i)
goto Invalid;
fprintf(f, "\tcall %s\n", str(con->sym.id));
@ -456,7 +444,7 @@ rv64_emitfn(Fn *fn, FILE *f)
static int id0;
int lbl, neg, off, frame, *pr, r;
Blk *b, *s;
Ins *i, ii;
Ins *i;
emitfnlnk(fn->name, &fn->lnk, f);
@ -561,11 +549,6 @@ rv64_emitfn(Fn *fn, FILE *f)
b->s2 = s;
neg = 1;
}
if (rtype(b->jmp.arg) == RSlot) {
ii.arg[0] = b->jmp.arg;
emitf("lw t6, %M0", &ii, fn, f);
b->jmp.arg = TMP(T6);
}
assert(isreg(b->jmp.arg));
fprintf(f,
"\tb%sz %s, .L%d\n",

View File

@ -31,7 +31,6 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
c = &fn->con[r0.val];
if (c->type == CAddr && memarg(r, op, i))
break;
if (KBASE(k) == 0)
if (c->type == CBits && immarg(r, op, i))
if (-2048 <= c->bits.i && c->bits.i < 2048)
break;
@ -42,7 +41,7 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
* immediates
*/
assert(c->type == CBits);
n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4);
n = stashbits(&c->bits, KWIDE(k) ? 8 : 4);
vgrow(&fn->con, ++fn->ncon);
c = &fn->con[fn->ncon-1];
sprintf(buf, "\"%sfp%d\"", T.asloc, n);

View File

@ -50,7 +50,6 @@ Target T_rv64 = {
.emitfn = rv64_emitfn,
.emitfin = elf_emitfin,
.asloc = ".L",
.cansel = 0,
};
MAKESURE(rsave_size_ok, sizeof rv64_rsave == (NGPS+NFPS+1) * sizeof(int));

22
spill.c
View File

@ -406,20 +406,26 @@ spill(Fn *fn)
if (rtype(b->jmp.arg) == RCall)
v->t[0] |= T.retregs(b->jmp.arg, 0);
}
if (rtype(b->jmp.arg) == RTmp) {
t = b->jmp.arg.val;
assert(KBASE(tmp[t].cls) == 0);
bsset(v, t);
limit2(v, 0, 0, NULL);
if (!bshas(v, t))
b->jmp.arg = slot(t);
}
for (t=Tmp0; bsiter(b->out, &t); t++)
if (!bshas(v, t))
slot(t);
bscopy(b->out, v);
/* 2. process the block instructions */
if (rtype(b->jmp.arg) == RTmp) {
t = b->jmp.arg.val;
assert(KBASE(tmp[t].cls) == 0);
lvarg[0] = bshas(v, t);
bsset(v, t);
bscopy(u, v);
limit2(v, 0, 0, NULL);
if (!bshas(v, t)) {
if (!lvarg[0])
bsclr(u, t);
b->jmp.arg = slot(t);
}
reloads(u, v);
}
curi = &insb[NIns];
for (i=&b->ins[b->nins]; i!=b->ins;) {
i--;

View File

@ -28,7 +28,7 @@ function $test() {
# >>> driver
# #include <stdio.h>
# struct four { long long l; char c; int i; };
# struct four { long l; char c; int i; };
# extern void test(void);
# int F(int a0, int a1, int a2, int a3, struct four s, int a6) {
# printf("%d %d %d %d %d %d %d\n",

View File

@ -107,7 +107,7 @@ function $test() {
# typedef struct { int i; } st2;
# typedef struct { float f; int i; } st3;
# typedef struct { int i; double d; } st4;
# typedef struct { float f; long long l; } st5;
# typedef struct { float f; long l; } st5;
# typedef struct { char t[16]; } st6;
# typedef struct { float f; double d; } st7;
# typedef struct { int i[4]; } st8;

View File

@ -150,7 +150,7 @@ function w $main() {
# typedef struct { float s0, s1; } Sss;
# typedef struct { float s; double d; } Ssd;
# typedef struct { int w0, w1; } Sww;
# typedef struct { long long l; char b; } Slb;
# typedef struct { long l; char b; } Slb;
# typedef struct { char b[17]; } Sbig;
# typedef struct { double d0, d1, d2; } Sddd;
# Sfi1 zfi1, fi1 = { -123, 4.56 };
@ -168,11 +168,11 @@ function w $main() {
# void pss(Sss *s) { printf(" { %g, %g }", s->s0, s->s1); }
# void psd(Ssd *s) { printf(" { %g, %g }", s->s, s->d); }
# void pww(Sww *s) { printf(" { %d, %d }", s->w0, s->w1); }
# void plb(Slb *s) { printf(" { %lld, '%c' }", s->l, s->b); }
# void plb(Slb *s) { printf(" { %ld, '%c' }", s->l, s->b); }
# void pbig(Sbig *s) { printf(" \"%.17s\"", s->b); }
# void pddd(Sddd *s) { printf(" { %g, %g, %g }", s->d0, s->d1, s->d2); }
# void pw(int w) { printf(" %d", w); }
# void pl(long long l) { printf(" %lld", l); }
# void pl(long l) { printf(" %ld", l); }
# void ps(float s) { printf(" %g", s); }
# void pd(double d) { printf(" %g", d); }
# /* --------------------------- */
@ -206,8 +206,8 @@ function w $main() {
# pss(&p0); puts("");
# qfn4(p0);
# }
# extern void qfn5(double, double, double, double, double, double, double, Sss, float, long long);
# void cfn5(double p0, double p1, double p2, double p3, double p4, double p5, double p6, Sss p7, float p8, long long p9) {
# extern void qfn5(double, double, double, double, double, double, double, Sss, float, long);
# void cfn5(double p0, double p1, double p2, double p3, double p4, double p5, double p6, Sss p7, float p8, long p9) {
# printf("qbe->c(%d)", 5);
# pss(&p7); ps(p8); pl(p9); puts("");
# qfn5(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
@ -236,8 +236,8 @@ function w $main() {
# pbig(&p0); puts("");
# qfn9(p0);
# }
# extern void qfn10(int, int, int, int, int, int, int, int, Sbig, float, long long);
# void cfn10(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, Sbig p8, float p9, long long p10) {
# extern void qfn10(int, int, int, int, int, int, int, int, Sbig, float, long);
# void cfn10(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, Sbig p8, float p9, long p10) {
# printf("qbe->c(%d)", 10);
# pbig(&p8); ps(p9); pl(p10); puts("");
# qfn10(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);

View File

@ -1,20 +0,0 @@
type :obj = { l, l, l, l }
export
function :obj $f(l %self) {
@_0
%_1 =l alloc8 16
storel 77, %_1
ret %_1
}
# >>> driver
# #include <stdio.h>
# typedef struct { long long a, b, c, d; } obj;
# extern obj f();
# int main() { obj ret = f(); printf("%lld\n", ret.a); return 0; }
# <<<
# >>> output
# 77
# <<<

View File

@ -1,4 +1,3 @@
# skip amd64_win (no signals on win32)
# test amd64 addressing modes
export

View File

@ -1,4 +1,4 @@
# skip arm64 arm64_apple rv64 amd64_win
# skip arm64 arm64_apple rv64
# a hack example,
# we use a dark type to get
# a pointer to the stack.

View File

@ -1,238 +0,0 @@
export
function l $ifc1(l %v0, l %v1, w %c) {
@start
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifc2(l %v0, l %v1, w %p) {
@start
%c =w cnew %p, 42
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifc3(l %v0, l %v1, w %p) {
@start
%c =w cugtw %p, 42
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifclts(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w clts %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcles(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cles %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcgts(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cgts %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcges(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cges %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifceqs(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w ceqs %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcnes(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cnes %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcos(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cos %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcuos(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cuos %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
# >>> driver
# extern long ifc1(long, long, int);
# extern long ifc2(long, long, int);
# extern long ifc3(long, long, int);
# extern long ifclts(float, float, long, long);
# extern long ifcles(float, float, long, long);
# extern long ifcgts(float, float, long, long);
# extern long ifcges(float, float, long, long);
# extern long ifceqs(float, float, long, long);
# extern long ifcnes(float, float, long, long);
# extern long ifcos(float, float, long, long);
# extern long ifcuos(float, float, long, long);
# int main() {
# return
# ifc1(7, 5, 0) != 7
# || ifc1(7, 5, 1) != 5
# || ifc1(7, 5, 33) != 5
# || ifc2(7, 5, 42) != 7
# || ifc2(7, 5, 41) != 5
# || ifc2(7, 5, 43) != 5
# || ifc3(7, 5, 42) != 7
# || ifc3(7, 5, 41) != 7
# || ifc3(7, 5, 43) != 5
# || ifclts(5.0f, 6.0f, 7, 5) != 5
# || ifclts(5.0f, 5.0f, 7, 5) != 7
# || ifclts(5.0f, 4.0f, 7, 5) != 7
# || ifclts(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifclts(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifclts(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcles(5.0f, 6.0f, 7, 5) != 5
# || ifcles(5.0f, 5.0f, 7, 5) != 5
# || ifcles(5.0f, 4.0f, 7, 5) != 7
# || ifcles(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcles(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcles(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcgts(5.0f, 6.0f, 7, 5) != 7
# || ifcgts(5.0f, 5.0f, 7, 5) != 7
# || ifcgts(5.0f, 4.0f, 7, 5) != 5
# || ifcgts(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcgts(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcgts(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcges(5.0f, 6.0f, 7, 5) != 7
# || ifcges(5.0f, 5.0f, 7, 5) != 5
# || ifcges(5.0f, 4.0f, 7, 5) != 5
# || ifcges(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcges(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcges(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifceqs(5.0f, 6.0f, 7, 5) != 7
# || ifceqs(5.0f, 5.0f, 7, 5) != 5
# || ifceqs(5.0f, 4.0f, 7, 5) != 7
# || ifceqs(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifceqs(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifceqs(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcnes(5.0f, 6.0f, 7, 5) != 5
# || ifcnes(5.0f, 5.0f, 7, 5) != 7
# || ifcnes(5.0f, 4.0f, 7, 5) != 5
# || ifcnes(5.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcnes(0.0f/0.0f, 5.0f, 7, 5) != 5
# || ifcnes(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcos(5.0f, 6.0f, 7, 5) != 5
# || ifcos(5.0f, 5.0f, 7, 5) != 5
# || ifcos(5.0f, 4.0f, 7, 5) != 5
# || ifcos(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcos(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcos(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcuos(5.0f, 6.0f, 7, 5) != 7
# || ifcuos(5.0f, 5.0f, 7, 5) != 7
# || ifcuos(5.0f, 4.0f, 7, 5) != 7
# || ifcuos(5.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcuos(0.0f/0.0f, 5.0f, 7, 5) != 5
# || ifcuos(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 5
# ;
# }
# <<<

View File

@ -1,38 +0,0 @@
# make sure large consts are lowered
# without an offset
# i.e. not movq $9223372036854775807, 64(%rax)
export function w $main() {
@_0
%_1 =w call $myfunc(l 1, l 2, l 3, l 4, l 5, l 6, l 7, l 8, l 9223372036854775807)
ret 0
}
# >>> driver
# #include <stdio.h>
# #include <stdint.h>
# #include <inttypes.h>
# void myfunc(int64_t a, int64_t b, int64_t c, int64_t d, int64_t e, int64_t f, int64_t g, int64_t h, int64_t i) {
# printf("%" PRId64 "\n", a);
# printf("%" PRId64 "\n", b);
# printf("%" PRId64 "\n", c);
# printf("%" PRId64 "\n", d);
# printf("%" PRId64 "\n", e);
# printf("%" PRId64 "\n", f);
# printf("%" PRId64 "\n", g);
# printf("%" PRId64 "\n", h);
# printf("%" PRId64 "\n", i);
# }
# <<<
# >>> output
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9223372036854775807
# <<<

View File

@ -1,4 +1,3 @@
# skip amd64_win (pthread and tls not implemented)
thread data $i = align 4 {w 42}
data $fmti = align 1 {b "i%d==%d\n", b 0}

View File

@ -29,7 +29,7 @@ char *tok[] = {
"function", "type", "data", "section", "align", "dbgfile",
"blit", "l", "w", "sh", "uh", "h", "sb", "ub", "b",
"d", "s", "z", "loadw", "loadl", "loads", "loadd",
"alloc1", "alloc2", "thread", "extern", "common",
"alloc1", "alloc2", "thread", "common",
};
enum {

View File

@ -31,23 +31,17 @@ find_cc_and_qemu() {
cc=$candidate_cc
echo "cc: $cc"
if [ "$target" = "$(uname -m)" ]
then
if [ "$target" = "$(uname -m)" ]; then
qemu=qemu_not_needed
echo "qemu: not needed, testing native architecture"
else
qemu="$3"
if $qemu -version >/dev/null 2>&1
then
if $qemu -version >/dev/null 2>&1; then
sysroot=$($candidate_cc -print-sysroot)
if [ -n "$sysroot" ]; then
qemu="$qemu -L $sysroot"
fi
echo "qemu: $qemu"
elif $qemu --version >/dev/null 2>&1
then
# wine
:
else
qemu=
echo "qemu: not found"
@ -96,19 +90,6 @@ init() {
fi
bin="$bin -t amd64_sysv"
;;
amd64_win)
for p in x86_64-w64-mingw32
do
find_cc_and_qemu x86_64-w64 "$p-gcc -static" "wine"
done
if test -z "$cc"
then
echo "Cannot find windows compiler or wine."
exit 1
fi
export WINEDEBUG=-all
bin="$bin -t amd64_win"
;;
"")
case `uname` in
*Darwin*)
@ -204,7 +185,7 @@ once() {
if test -s $out
then
$qemu $exe a b c | tr -d '\r' | diff -u - $out
$qemu $exe a b c | diff -u - $out
ret=$?
reason="output"
else

62
util.c
View File

@ -164,7 +164,7 @@ addins(Ins **pvins, uint *pnins, Ins *i)
}
void
addbins(Ins **pvins, uint *pnins, Blk *b)
addbins(Blk *b, Ins **pvins, uint *pnins)
{
Ins *i;
@ -281,17 +281,6 @@ igroup(Blk *b, Ins *i, Ins **i0, Ins **i1)
assert(i < ie);
*i1 = i + 1;
return;
case Osel1:
for (; i>ib && (i-1)->op == Osel1; i--)
;
assert(i->op == Osel0);
/* fall through */
case Osel0:
*i0 = i++;
for (; i<ie && i->op == Osel1; i++)
;
*i1 = i;
return;
default:
if (ispar(i->op))
goto case_Opar;
@ -343,27 +332,34 @@ icpy(Ins *d, Ins *s, ulong n)
}
static int cmptab[][2] ={
/* negation swap */
[Ciule] = {Ciugt, Ciuge},
[Ciult] = {Ciuge, Ciugt},
[Ciugt] = {Ciule, Ciult},
[Ciuge] = {Ciult, Ciule},
[Cisle] = {Cisgt, Cisge},
[Cislt] = {Cisge, Cisgt},
[Cisgt] = {Cisle, Cislt},
[Cisge] = {Cislt, Cisle},
[Cieq] = {Cine, Cieq},
[Cine] = {Cieq, Cine},
[NCmpI+Cfle] = {-1, NCmpI+Cfge},
[NCmpI+Cflt] = {-1, NCmpI+Cfgt},
[NCmpI+Cfgt] = {-1, NCmpI+Cflt},
[NCmpI+Cfge] = {-1, NCmpI+Cfle},
[NCmpI+Cfeq] = {-1, NCmpI+Cfeq},
[NCmpI+Cfne] = {-1, NCmpI+Cfne},
[NCmpI+Cfo] = {-1, NCmpI+Cfo},
[NCmpI+Cfuo] = {-1, NCmpI+Cfuo},
/* negation swap */
[Ciule] = {Ciugt, Ciuge},
[Ciult] = {Ciuge, Ciugt},
[Ciugt] = {Ciule, Ciult},
[Ciuge] = {Ciult, Ciule},
[Cisle] = {Cisgt, Cisge},
[Cislt] = {Cisge, Cisgt},
[Cisgt] = {Cisle, Cislt},
[Cisge] = {Cislt, Cisle},
[Cieq] = {Cine, Cieq},
[Cine] = {Cieq, Cine},
[NCmpI+Cfle] = {NCmpI+Cfgt, NCmpI+Cfge},
[NCmpI+Cflt] = {NCmpI+Cfge, NCmpI+Cfgt},
[NCmpI+Cfgt] = {NCmpI+Cfle, NCmpI+Cflt},
[NCmpI+Cfge] = {NCmpI+Cflt, NCmpI+Cfle},
[NCmpI+Cfeq] = {NCmpI+Cfne, NCmpI+Cfeq},
[NCmpI+Cfne] = {NCmpI+Cfeq, NCmpI+Cfne},
[NCmpI+Cfo] = {NCmpI+Cfuo, NCmpI+Cfo},
[NCmpI+Cfuo] = {NCmpI+Cfo, NCmpI+Cfuo},
};
int
cmpneg(int c)
{
assert(0 <= c && c < NCmp);
return cmptab[c][0];
}
int
cmpop(int c)
{
@ -375,9 +371,9 @@ int
cmpwlneg(int op)
{
if (INRANGE(op, Ocmpw, Ocmpw1))
return cmptab[op - Ocmpw][0] + Ocmpw;
return cmpneg(op - Ocmpw) + Ocmpw;
if (INRANGE(op, Ocmpl, Ocmpl1))
return cmptab[op - Ocmpl][0] + Ocmpl;
return cmpneg(op - Ocmpl) + Ocmpl;
die("not a wl comparison");
}