diff --git a/vendor/qbe/LICENSE b/vendor/qbe/LICENSE deleted file mode 100644 index e9a7c48..0000000 --- a/vendor/qbe/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -© 2015-2023 Quentin Carbonneaux - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/vendor/qbe/Makefile b/vendor/qbe/Makefile index f5e8a76..a7c4cfe 100644 --- a/vendor/qbe/Makefile +++ b/vendor/qbe/Makefile @@ -5,8 +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 simpl.o live.o spill.o rega.o emit.o -AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o + copy.o fold.o gvn.o gcm.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 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) @@ -71,12 +72,18 @@ clean-gen: clean check: qbe tools/test.sh all +check-x86_64: qbe + TARGET=x86_64 tools/test.sh all + check-arm64: qbe TARGET=arm64 tools/test.sh all check-rv64: qbe TARGET=rv64 tools/test.sh all +check-amd64_win: qbe + TARGET=amd64_win tools/test.sh all + src: @echo $(SRCALL) @@ -90,4 +97,7 @@ src: }" < $$F; \ done -.PHONY: clean clean-gen check check-arm64 src 80 install uninstall +wc: + @wc -l $(SRCALL) + +.PHONY: clean clean-gen check check-arm64 check-rv64 check-amd64_win src 80 wc install uninstall diff --git a/vendor/qbe/README b/vendor/qbe/README index 69d0aa9..1f51d7c 100644 --- a/vendor/qbe/README +++ b/vendor/qbe/README @@ -15,4 +15,4 @@ The LICENSE file applies to all files distributed. Invoke make in this directory to create the executable file qbe. Install using 'make install', the standard DESTDIR and PREFIX environment variables are supported. -Alternatively, you may simply copy the qbe binary manually. +Alternatively, you may simply copy the qbe binary. diff --git a/vendor/qbe/alias.c b/vendor/qbe/alias.c index 3885115..9b41084 100644 --- a/vendor/qbe/alias.c +++ b/vendor/qbe/alias.c @@ -132,7 +132,7 @@ store(Ref r, int sz, Fn *fn) void fillalias(Fn *fn) { - uint n, m; + uint n; int t, sz; int64_t x; Blk *b; @@ -146,8 +146,6 @@ fillalias(Fn *fn) for (n=0; nnblk; ++n) { b = fn->rpo[n]; for (p=b->phi; p; p=p->link) { - for (m=0; mnarg; m++) - esc(p->arg[m], fn); assert(rtype(p->to) == RTmp); a = &fn->tmp[p->to.val].alias; assert(a->type == ABot); @@ -217,4 +215,8 @@ fillalias(Fn *fn) if (b->jmp.type != Jretc) esc(b->jmp.arg, fn); } + for (b=fn->start; b; b=b->link) + for (p=b->phi; p; p=p->link) + for (n=0; nnarg; n++) + esc(p->arg[n], fn); } diff --git a/vendor/qbe/all.h b/vendor/qbe/all.h index 9870003..7e67bc0 100644 --- a/vendor/qbe/all.h +++ b/vendor/qbe/all.h @@ -21,6 +21,7 @@ typedef struct Phi Phi; typedef struct Blk Blk; typedef struct Use Use; typedef struct Sym Sym; +typedef struct Num Num; typedef struct Alias Alias; typedef struct Tmp Tmp; typedef struct Con Con; @@ -33,7 +34,6 @@ typedef struct Lnk Lnk; typedef struct Target Target; enum { - NString = 160, NIns = 1 << 20, NAlign = 3, NField = 32, @@ -43,6 +43,7 @@ enum { struct Target { char name[16]; char apple; + char windows; int gpr0; /* first general purpose reg */ int ngpr; int fpr0; /* first floating point reg */ @@ -61,6 +62,7 @@ struct Target { void (*emitfin)(FILE *); char asloc[4]; char assym[4]; + uint cansel:1; }; #define BIT(n) ((bits)1 << (n)) @@ -115,7 +117,7 @@ static inline int rtype(Ref r) static inline int rsval(Ref r) { - return (int32_t)((int64_t)r.val << 3) >> 3; + return ((int)r.val ^ 0x10000000) - 0x10000000; } enum CmpI { @@ -182,6 +184,8 @@ enum { Oalloc1 = Oalloc16, Oflag = Oflagieq, Oflag1 = Oflagfuo, + Oxsel = Oxselieq, + Oxsel1 = Oxselfuo, NPubOp = Onop, Jjf = Jjfieq, Jjf1 = Jjffuo, @@ -190,6 +194,7 @@ enum { #define INRANGE(x, l, u) ((unsigned)(x) - l <= u - l) /* linear in x */ #define isstore(o) INRANGE(o, Ostoreb, Ostored) #define isload(o) INRANGE(o, Oloadsb, Oload) +#define isalloc(o) INRANGE(o, Oalloc4, Oalloc16) #define isext(o) INRANGE(o, Oextsb, Oextuw) #define ispar(o) INRANGE(o, Opar, Opare) #define isarg(o) INRANGE(o, Oarg, Oargv) @@ -197,6 +202,7 @@ 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()) */ @@ -212,7 +218,16 @@ enum { struct Op { char *name; short argcls[2][4]; - int canfold; + uint canfold:1; + uint hasid:1; /* op identity value? */ + uint idval:1; /* identity value 0/1 */ + uint commutes:1; /* commutative op? */ + uint assoc:1; /* associative op? */ + uint idemp:1; /* idempotent op? */ + uint cmpeqwl:1; /* Kl/Kw cmp eq/ne? */ + uint cmplgtewl:1; /* Kl/Kw cmp lt/gt/le/ge? */ + uint eqval:1; /* 1 for eq; 0 for ne */ + uint pinned:1; /* GCM pinned op? */ }; struct Ins { @@ -224,10 +239,11 @@ struct Ins { struct Phi { Ref to; + short cls; + int visit; + uint narg; Ref *arg; Blk **blk; - uint narg; - int cls; Phi *link; }; @@ -250,13 +266,14 @@ struct Blk { Blk *dom, *dlink; Blk **fron; uint nfron; + int depth; Blk **pred; uint npred; BSet in[1], out[1], gen[1]; int nlive[2]; int loop; - char name[NString]; + char *name; }; struct Use { @@ -275,12 +292,20 @@ struct Use { struct Sym { enum { - SGlo, - SThr, + SGlo = 0, /* direct access */ + SThr = 1, /* local-exec TLS */ + SExt = 2, /* GOT/PLT access */ + SExtThr = SExt|SThr, /* initial-exec TLS */ } type; uint32_t id; }; +struct Num { + uchar n; + uchar nl, nr; + Ref l, r; +}; + enum { NoAlias, MayAlias, @@ -310,7 +335,7 @@ struct Alias { }; struct Tmp { - char name[NString]; + char *name; Ins *def; Use *use; uint ndef, nuse; @@ -335,6 +360,7 @@ struct Tmp { Wuw } width; int visit; + uint gcmbid; }; struct Con { @@ -364,6 +390,7 @@ struct Addr { /* amd64 addressing */ struct Lnk { char export; char thread; + char common; char align; char *sec; char *secf; @@ -383,14 +410,16 @@ struct Fn { Blk **rpo; bits reg; int slot; + int salign; char vararg; char dynalloc; - char name[NString]; + char leaf; + char *name; Lnk lnk; }; struct Typ { - char name[NString]; + char *name; char isdark; char isunion; int align; @@ -458,29 +487,35 @@ void freeall(void); void *vnew(ulong, size_t, Pool); void vfree(void *); void vgrow(void *, ulong); -void strf(char[NString], char *, ...); +void addins(Ins **, uint *, Ins *); +void addbins(Ins **, uint *, Blk *); +char *strf(Pool, char *, ...); uint32_t intern(char *); char *str(uint32_t); int argcls(Ins *, int); int isreg(Ref); int iscmp(int, int *, int *); +void igroup(Blk *, Ins *, Ins **, Ins **); void emit(int, int, Ref, Ref, Ref); void emiti(Ins); -void idup(Ins **, Ins *, ulong); +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 *); +uint phiargn(Phi *, Blk *); +Ref phiarg(Phi *, Blk *); Ref newtmp(char *, int, Fn *); void chuse(Ref, int, Fn *); int symeq(Sym, Sym); Ref newcon(Con *, Fn *); Ref getcon(int64_t, Fn *); -int addcon(Con *, Con *); +int addcon(Con *, Con *, int); +int isconbits(Fn *fn, Ref r, int64_t *v); void salloc(Ref, Ref, Fn *); void dumpts(BSet *, Tmp *, FILE *); - +void runmatch(uchar *, Num *, Ref, Ref *); void bsinit(BSet *, uint); void bszero(BSet *); uint bscount(BSet *); @@ -512,16 +547,21 @@ void elimsb(Fn *); /* cfg.c */ Blk *newblk(void); -void edgedel(Blk *, Blk **); void fillpreds(Fn *); -void fillrpo(Fn *); +void fillcfg(Fn *); void filldom(Fn *); int sdom(Blk *, Blk *); int dom(Blk *, Blk *); void fillfron(Fn *); void loopiter(Fn *, void (*)(Blk *, Blk *)); +void filldepth(Fn *); +Blk *lca(Blk *, Blk *); 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 *); @@ -539,17 +579,31 @@ int storesz(Ins *); void loadopt(Fn *); /* ssa.c */ +void adduse(Tmp *, int, Blk *, ...); void filluse(Fn *); -void fillpreds(Fn *); -void fillrpo(Fn *); void ssa(Fn *); void ssacheck(Fn *); /* copy.c */ -void copy(Fn *); +void narrowpars(Fn *fn); +Ref copyref(Fn *, Blk *, Ins *); +Ref phicopyref(Fn *, Blk *, Phi *); /* fold.c */ -void fold(Fn *); +int foldint(Con *, int, int, Con *, Con *); +Ref foldref(Fn *, Ins *); + +/* gvn.c */ +extern Ref con01[2]; /* 0 and 1 */ +int zeroval(Fn *, Blk *, Ref, int, int *); +void gvn(Fn *); + +/* gcm.c */ +int pinned(Ins *); +void gcm(Fn *); + +/* ifopt.c */ +void ifconvert(Fn *fn); /* simpl.c */ void simpl(Fn *); @@ -570,7 +624,8 @@ void emitfnlnk(char *, Lnk *, FILE *); void emitdat(Dat *, FILE *); void emitdbgfile(char *, FILE *); void emitdbgloc(uint, uint, FILE *); -int stashbits(void *, int); +int stashbits(bits, int); void elf_emitfnfin(char *, FILE *); void elf_emitfin(FILE *); void macho_emitfin(FILE *); +void pe_emitfin(FILE *); diff --git a/vendor/qbe/amd64/all.h b/vendor/qbe/amd64/all.h index 3a2db0e..8946dbb 100644 --- a/vendor/qbe/amd64/all.h +++ b/vendor/qbe/amd64/all.h @@ -4,14 +4,14 @@ typedef struct Amd64Op Amd64Op; enum Amd64Reg { RAX = RXX+1, /* caller-save */ - RCX, - RDX, - RSI, - RDI, - R8, - R9, - R10, - R11, + 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 */ RBX, /* callee-save */ R12, @@ -41,9 +41,13 @@ enum Amd64Reg { NFPR = XMM14 - XMM0 + 1, /* reserve XMM15 */ NGPR = RSP - RAX + 1, - NGPS = R11 - RAX + 1, NFPS = NFPR, - NCLR = R15 - RBX + 1, + + 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 */ }; MAKESURE(reg_not_tmp, XMM15 < (int)Tmp0); @@ -63,8 +67,16 @@ 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_emitfn(Fn *, FILE *); +void amd64_sysv_emitfn(Fn *, FILE *); +void amd64_winabi_emitfn(Fn *, FILE *); diff --git a/vendor/qbe/amd64/emit.c b/vendor/qbe/amd64/emit.c index 51d1a5c..7d3c3ea 100644 --- a/vendor/qbe/amd64/emit.c +++ b/vendor/qbe/amd64/emit.c @@ -1,25 +1,33 @@ #include "all.h" +typedef struct E E; + +struct E { + FILE *f; + Fn *fn; + int fp; + uint64_t fsz; + int nclob; +}; + #define CMP(X) \ - 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") + 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") enum { SLong = 0, @@ -60,67 +68,79 @@ enum { static struct { short op; short cls; - char *asm; + 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"}, @@ -142,23 +162,32 @@ static char *rname[][4] = { static int -slot(Ref r, Fn *fn) +slot(Ref r, E *e) { int s; s = rsval(r); - assert(s <= fn->slot); + assert(s <= e->fn->slot); /* specific to NAlign == 3 */ - if (s < 0) - return -4 * s; - else if (fn->vararg) - return -176 + -4 * (fn->slot - s); - else - return -4 * (fn->slot - s); + if (s < 0) { + if (e->fp == RSP) + return 4*-s - 8 + e->fsz + e->nclob*8; + else + return 4*-s; + } + 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 + return -4 * (e->fn->slot - s); } static void -emitcon(Con *con, FILE *f) +emitcon(Con *con, E *e) { char *p, *l; @@ -167,17 +196,17 @@ emitcon(Con *con, FILE *f) l = str(con->sym.id); p = l[0] == '"' ? "" : T.assym; if (con->sym.type == SThr) { - if (T.apple) - fprintf(f, "%s%s@TLVP", p, l); - else - fprintf(f, "%%fs:%s%s@tpoff", p, l); - } else - fprintf(f, "%s%s", p, l); + assert(!T.apple); + fprintf(e->f, "%%fs:%s%s@tpoff", p, l); + } else { + assert((con->sym.type & ~SExt) == SGlo); + fprintf(e->f, "%s%s", p, l); + } if (con->bits.i) - fprintf(f, "%+"PRId64, con->bits.i); + fprintf(e->f, "%+"PRId64, con->bits.i); break; case CBits: - fprintf(f, "%"PRId64, con->bits.i); + fprintf(e->f, "%"PRId64, con->bits.i); break; default: die("unreachable"); @@ -212,10 +241,10 @@ getarg(char c, Ins *i) } } -static void emitins(Ins, Fn *, FILE *); +static void emitins(Ins, E *); static void -emitcopy(Ref r1, Ref r2, int k, Fn *fn, FILE *f) +emitcopy(Ref r1, Ref r2, int k, E *e) { Ins icp; @@ -223,11 +252,11 @@ emitcopy(Ref r1, Ref r2, int k, Fn *fn, FILE *f) icp.arg[0] = r2; icp.to = r1; icp.cls = k; - emitins(icp, fn, f); + emitins(icp, e); } static void -emitf(char *s, Ins *i, Fn *fn, FILE *f) +emitf(char *s, Ins *i, E *e) { static char clstoa[][3] = {"l", "q", "ss", "sd"}; char c; @@ -247,25 +276,25 @@ emitf(char *s, Ins *i, Fn *fn, FILE *f) case '-': assert((!req(i->arg[1], i->to) || req(i->arg[0], i->to)) && "cannot convert to 2-address"); - emitcopy(i->to, i->arg[0], i->cls, fn, f); + emitcopy(i->to, i->arg[0], i->cls, e); s++; break; } - fputc('\t', f); + fputc('\t', e->f); Next: while ((c = *s++) != '%') if (!c) { - fputc('\n', f); + fputc('\n', e->f); return; } else - fputc(c, f); + fputc(c, e->f); switch ((c = *s++)) { case '%': - fputc('%', f); + fputc('%', e->f); break; case 'k': - fputs(clstoa[i->cls], f); + fputs(clstoa[i->cls], e->f); break; case '0': case '1': @@ -282,37 +311,42 @@ Next: switch (rtype(ref)) { case RTmp: assert(isreg(ref)); - fprintf(f, "%%%s", regtoa(ref.val, sz)); + fprintf(e->f, "%%%s", regtoa(ref.val, sz)); break; case RSlot: - fprintf(f, "%d(%%rbp)", slot(ref, fn)); + fprintf(e->f, "%d(%%%s)", + slot(ref, e), + regtoa(e->fp, SLong) + ); break; case RMem: Mem: - m = &fn->mem[ref.val]; + m = &e->fn->mem[ref.val]; if (rtype(m->base) == RSlot) { off.type = CBits; - off.bits.i = slot(m->base, fn); - addcon(&m->offset, &off); - m->base = TMP(RBP); + off.bits.i = slot(m->base, e); + addcon(&m->offset, &off, 1); + m->base = TMP(e->fp); } if (m->offset.type != CUndef) - emitcon(&m->offset, f); - fputc('(', f); + emitcon(&m->offset, e); + fputc('(', e->f); if (!req(m->base, R)) - fprintf(f, "%%%s", regtoa(m->base.val, SLong)); + fprintf(e->f, "%%%s", + regtoa(m->base.val, SLong) + ); else if (m->offset.type == CAddr) - fprintf(f, "%%rip"); + fprintf(e->f, "%%rip"); if (!req(m->index, R)) - fprintf(f, ", %%%s, %d", + fprintf(e->f, ", %%%s, %d", regtoa(m->index.val, SLong), m->scale ); - fputc(')', f); + fputc(')', e->f); break; case RCon: - fputc('$', f); - emitcon(&fn->con[ref.val], f); + fputc('$', e->f); + emitcon(&e->fn->con[ref.val], e); break; default: die("unreachable"); @@ -337,18 +371,21 @@ Next: case RMem: goto Mem; case RSlot: - fprintf(f, "%d(%%rbp)", slot(ref, fn)); + fprintf(e->f, "%d(%%%s)", + slot(ref, e), + regtoa(e->fp, SLong) + ); break; case RCon: - off = fn->con[ref.val]; - emitcon(&off, f); + off = e->fn->con[ref.val]; + emitcon(&off, e); if (off.type == CAddr) - if (off.sym.type != SThr || T.apple) - fprintf(f, "(%%rip)"); + if (off.sym.type != SThr) + fprintf(e->f, "(%%rip)"); break; case RTmp: assert(isreg(ref)); - fprintf(f, "(%%%s)", regtoa(ref.val, SLong)); + fprintf(e->f, "(%%%s)", regtoa(ref.val, SLong)); break; default: die("unreachable"); @@ -360,13 +397,13 @@ Next: goto Next; } -static void *negmask[4] = { - [Ks] = (uint32_t[4]){ 0x80000000 }, - [Kd] = (uint64_t[2]){ 0x8000000000000000 }, +static bits negmask[4] = { + [Ks] = 0x80000000, + [Kd] = 0x8000000000000000, }; static void -emitins(Ins i, Fn *fn, FILE *f) +emitins(Ins i, E *e) { Ref r; int64_t val; @@ -377,6 +414,8 @@ emitins(Ins i, Fn *fn, FILE *f) 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 @@ -393,7 +432,7 @@ emitins(Ins i, Fn *fn, FILE *f) || (omap[o].cls == Ka)) break; } - emitf(omap[o].asm, &i, fn, f); + emitf(omap[o].fmt, &i, e); break; case Onop: /* just do nothing for nops, they are inserted @@ -410,7 +449,7 @@ emitins(Ins i, Fn *fn, FILE *f) if (KBASE(i.cls) == 0 /* only available for ints */ && rtype(i.arg[0]) == RCon && rtype(i.arg[1]) == RTmp) { - emitf("imul%k %0, %1, %=", &i, fn, f); + emitf("imul%k %0, %1, %=", &i, e); break; } goto Table; @@ -419,18 +458,18 @@ emitins(Ins i, Fn *fn, FILE *f) * some 3-address subtractions */ if (req(i.to, i.arg[1]) && !req(i.arg[0], i.to)) { ineg = (Ins){Oneg, i.cls, i.to, {i.to}}; - emitins(ineg, fn, f); - emitf("add%k %0, %=", &i, fn, f); + emitins(ineg, e); + emitf("add%k %0, %=", &i, e); break; } goto Table; case Oneg: if (!req(i.to, i.arg[0])) - emitf("mov%k %0, %=", &i, fn, f); + emitf("mov%k %0, %=", &i, e); if (KBASE(i.cls) == 0) - emitf("neg%k %=", &i, fn, f); + emitf("neg%k %=", &i, e); else - fprintf(f, + fprintf(e->f, "\txorp%c %sfp%d(%%rip), %%%s\n", "xxsd"[i.cls], T.asloc, @@ -443,8 +482,8 @@ emitins(Ins i, Fn *fn, FILE *f) * conversion to 2-address in emitf() would fail */ if (req(i.to, i.arg[1])) { i.arg[1] = TMP(XMM0+15); - emitf("mov%k %=, %1", &i, fn, f); - emitf("mov%k %0, %=", &i, fn, f); + emitf("mov%k %=, %1", &i, e); + emitf("mov%k %0, %=", &i, e); i.arg[0] = i.to; } goto Table; @@ -460,69 +499,105 @@ emitins(Ins i, Fn *fn, FILE *f) t0 = rtype(i.arg[0]); if (i.cls == Kl && t0 == RCon - && fn->con[i.arg[0].val].type == CBits) { - val = fn->con[i.arg[0].val].bits.i; + && e->fn->con[i.arg[0].val].type == CBits) { + val = e->fn->con[i.arg[0].val].bits.i; if (isreg(i.to)) if (val >= 0 && val <= UINT32_MAX) { - emitf("movl %W0, %W=", &i, fn, f); + emitf("movl %W0, %W=", &i, e); break; } if (rtype(i.to) == RSlot) if (val < INT32_MIN || val > INT32_MAX) { - emitf("movl %0, %=", &i, fn, f); - emitf("movl %0>>32, 4+%=", &i, fn, f); + emitf("movl %0, %=", &i, e); + emitf("movl %0>>32, 4+%=", &i, e); break; } } if (isreg(i.to) && t0 == RCon - && fn->con[i.arg[0].val].type == CAddr) { - emitf("lea%k %M0, %=", &i, fn, f); + && e->fn->con[i.arg[0].val].type == CAddr) { + emitf("lea%k %M0, %=", &i, e); break; } if (rtype(i.to) == RSlot && (t0 == RSlot || t0 == RMem)) { i.cls = KWIDE(i.cls) ? Kd : Ks; i.arg[1] = TMP(XMM0+15); - emitf("mov%k %0, %1", &i, fn, f); - emitf("mov%k %1, %=", &i, fn, f); + emitf("mov%k %0, %1", &i, e); + emitf("mov%k %1, %=", &i, e); break; } /* conveniently, the assembler knows if it * should use movabsq when reading movq */ - emitf("mov%k %0, %=", &i, fn, f); + emitf("mov%k %0, %=", &i, e); break; case Oaddr: - if (!T.apple - && rtype(i.arg[0]) == RCon - && 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 = &fn->con[i.arg[0].val]; - sym = str(con->sym.id); - emitf("movq %%fs:0, %L=", &i, fn, f); - fprintf(f, "\tleaq %s%s@tpoff", - sym[0] == '"' ? "" : T.assym, sym); - if (con->bits.i) - fprintf(f, "%+"PRId64, con->bits.i); - fprintf(f, "(%%%s), %%%s\n", - regtoa(i.to.val, SLong), + 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; } - goto Table; + if (T.windows && con->sym.type != SGlo) + die("extern/thread unsupported on amd64_win"); + switch (con->sym.type) { + case SThr: + /* derive the symbol address from the TCB + * address at offset 0 of %fs */ + emitf("movq %%fs:0, %L=", &i, e); + fprintf(e->f, "\tleaq %s%s@tpoff", + sym[0] == '"' ? "" : T.assym, sym); + if (con->bits.i) + fprintf(e->f, "%+"PRId64, + con->bits.i); + fprintf(e->f, "(%%%s), %%%s\n", + 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; case Ocall: /* calls simply have a weird syntax in AT&T * assembly... */ switch (rtype(i.arg[0])) { case RCon: - fprintf(f, "\tcallq "); - emitcon(&fn->con[i.arg[0].val], f); - fprintf(f, "\n"); + 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"); + fprintf(e->f, "\n"); break; case RTmp: - emitf("callq *%L0", &i, fn, f); + emitf("callq *%L0", &i, e); break; default: die("invalid call argument"); @@ -533,9 +608,10 @@ emitins(Ins i, Fn *fn, FILE *f) * maybe we should split Osalloc in 2 different * instructions depending on the result */ - emitf("subq %L0, %%rsp", &i, fn, f); + assert(e->fp == RBP); + emitf("subq %L0, %%rsp", &i, e); if (!req(i.to, R)) - emitcopy(i.to, TMP(RSP), Kl, fn, f); + emitcopy(i.to, TMP(RSP), Kl, e); break; case Oswap: if (KBASE(i.cls) == 0) @@ -543,34 +619,51 @@ emitins(Ins i, Fn *fn, FILE *f) /* for floats, there is no swap instruction * so we use xmm15 as a temporary */ - emitcopy(TMP(XMM0+15), i.arg[0], i.cls, fn, f); - emitcopy(i.arg[0], i.arg[1], i.cls, fn, f); - emitcopy(i.arg[1], TMP(XMM0+15), i.cls, fn, f); + emitcopy(TMP(XMM0+15), i.arg[0], i.cls, e); + emitcopy(i.arg[0], i.arg[1], i.cls, e); + emitcopy(i.arg[1], TMP(XMM0+15), i.cls, e); break; case Odbgloc: - emitdbgloc(i.arg[0].val, i.arg[1].val, f); + 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 uint64_t -framesz(Fn *fn) +static void +sysv_framesz(E *e) { uint64_t i, o, f; /* specific to NAlign == 3 */ - for (i=0, o=0; ireg >> amd64_sysv_rclob[i]); - f = fn->slot; + o = 0; + if (!e->fn->leaf) { + for (i=0, o=0; ifn->reg >> amd64_sysv_rclob[i]; + o &= 1; + } + f = e->fn->slot; f = (f + 3) & -4; - return 4*f + 8*o + 176*fn->vararg; + if (f > 0 + && e->fp == RSP + && e->fn->salign == 4) + f += 2; + e->fsz = 4*f + 8*o + 176*e->fn->vararg; } void -amd64_emitfn(Fn *fn, FILE *f) +amd64_sysv_emitfn(Fn *fn, FILE *f) { - static char *ctoa[] = { - #define X(c, s) [c] = s, + static char *ctoa[][2] = { + #define X(c, s, n) [c] = {s, n}, CMP(X) #undef X }; @@ -578,13 +671,20 @@ amd64_emitfn(Fn *fn, FILE *f) Blk *b, *s; Ins *i, itmp; int *r, c, o, n, lbl; - uint64_t fs; + uint p; + E *e; + e = &(E){.f = f, .fn = fn}; emitfnlnk(fn->name, &fn->lnk, f); - fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f); - fs = framesz(fn); - if (fs) - fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", fs); + fputs("\tendbr64\n", f); + if (!fn->leaf || fn->vararg || fn->dynalloc) { + e->fp = RBP; + fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f); + } else + e->fp = RSP; + sysv_framesz(e); + if (e->fsz) + fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", e->fsz); if (fn->vararg) { o = -176; for (r=amd64_sysv_rsave; r<&amd64_sysv_rsave[6]; r++, o+=8) @@ -592,18 +692,24 @@ amd64_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]; r++) + for (r=amd64_sysv_rclob; r<&amd64_sysv_rclob[NCLR_SYSV]; r++) if (fn->reg & BIT(*r)) { itmp.arg[0] = TMP(*r); - emitf("pushq %L0", &itmp, fn, f); - fs += 8; + emitf("pushq %L0", &itmp, e); + e->nclob++; } for (lbl=0, b=fn->start; b; b=b->link) { - if (lbl || b->npred > 1) + if (lbl || b->npred > 1) { + for (p=0; pnpred; p++) + if (b->pred[p]->id >= b->id) + break; + if (p != b->npred) + fprintf(f, ".p2align 4\n"); fprintf(f, "%sbb%d:\n", T.asloc, id0+b->id); + } for (i=b->ins; i!=&b->ins[b->nins]; i++) - emitins(*i, fn, f); + emitins(*i, e); lbl = 1; switch (b->jmp.type) { case Jhlt: @@ -614,17 +720,19 @@ amd64_emitfn(Fn *fn, FILE *f) fprintf(f, "\tmovq %%rbp, %%rsp\n" "\tsubq $%"PRIu64", %%rsp\n", - fs - ); - for (r=&amd64_sysv_rclob[NCLR]; r>amd64_sysv_rclob;) + e->fsz + e->nclob * 8); + for (r=&amd64_sysv_rclob[NCLR_SYSV]; r>amd64_sysv_rclob;) if (fn->reg & BIT(*--r)) { itmp.arg[0] = TMP(*r); - emitf("popq %L0", &itmp, fn, f); + emitf("popq %L0", &itmp, e); } - fprintf(f, - "\tleave\n" - "\tret\n" - ); + 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: @@ -641,9 +749,10 @@ amd64_emitfn(Fn *fn, FILE *f) s = b->s1; b->s1 = b->s2; b->s2 = s; + n = 0; } else - c = cmpneg(c); - fprintf(f, "\tj%s %sbb%d\n", ctoa[c], + n = 1; + fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n], T.asloc, id0+b->s2->id); goto Jmp; } @@ -654,3 +763,119 @@ amd64_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; ifn->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; +} diff --git a/vendor/qbe/amd64/isel.c b/vendor/qbe/amd64/isel.c index e29c8bf..379244a 100644 --- a/vendor/qbe/amd64/isel.c +++ b/vendor/qbe/amd64/isel.c @@ -18,14 +18,7 @@ * dce should be moved out... */ -typedef struct ANum ANum; - -struct ANum { - char n, l, r; - Ins *i; -}; - -static int amatch(Addr *, Ref, int, ANum *, Fn *); +static int amatch(Addr *, Num *, Ref, Fn *); static int noimm(Ref r, Fn *fn) @@ -94,11 +87,23 @@ 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, KWIDE(k) ? 8 : 4); + n = stashbits(fn->con[r0.val].bits.i, KWIDE(k) ? 8 : 4); + /* quote the name so that we do not + * add symbol prefixes on the apple + * target variant + */ sprintf(buf, "\"%sfp%d\"", T.asloc, n); 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 @@ -115,8 +120,9 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn) r1 = newtmp("isel", Kl, fn); emit(Oaddr, Kl, r1, SLOT(s), R); } - else if (T.apple && hascon(r0, &c, fn) - && c->type == CAddr && c->sym.type == SThr) { + else if (op != Ocall && hascon(r0, &c, fn) + && c->type == CAddr && ((c->sym.type & SExt) + || (T.apple && c->sym.type == SThr))) { r1 = newtmp("isel", Kl, fn); if (c->bits.i) { r2 = newtmp("isel", Kl, fn); @@ -126,16 +132,18 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn) emit(Oadd, Kl, r1, r2, r3); } else r2 = r1; - 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); + 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); + } cc = *c; cc.bits.i = 0; r3 = newcon(&cc, fn); - emit(Oload, Kl, r2, r3, R); + emit(Oaddr, Kl, r2, r3, R); if (rtype(r0) == RMem) { m = &fn->mem[r0.val]; m->offset.type = CUndef; @@ -146,9 +154,8 @@ 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) { - /* apple as does not support 32-bit - * absolute addressing, use a rip- - * relative leaq instead + /* turn address operands into + * lea/mov instructions */ r1 = newtmp("isel", Kl, fn); emit(Oaddr, Kl, r1, r0, R); @@ -166,11 +173,15 @@ 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; } static void -seladdr(Ref *r, ANum *an, Fn *fn) +seladdr(Ref *r, Num *tn, Fn *fn) { Addr a; Ref r0; @@ -178,7 +189,7 @@ seladdr(Ref *r, ANum *an, Fn *fn) r0 = *r; if (rtype(r0) == RTmp) { memset(&a, 0, sizeof a); - if (!amatch(&a, r0, an[r0.val].n, an, fn)) + if (!amatch(&a, tn, r0, fn)) return; if (!req(a.base, R)) if (a.offset.type == CAddr) { @@ -242,7 +253,7 @@ selcmp(Ref arg[2], int k, int swap, Fn *fn) } static void -sel(Ins i, ANum *an, Fn *fn) +sel(Ins i, Num *tn, Fn *fn) { Ref r0, r1, tmp[7]; int x, j, k, kc, sh, swap; @@ -404,10 +415,10 @@ sel(Ins i, ANum *an, Fn *fn) if (i.op == Ostores) i.op = Ostorew; } - seladdr(&i.arg[1], an, fn); + seladdr(&i.arg[1], tn, fn); goto Emit; case_Oload: - seladdr(&i.arg[0], an, fn); + seladdr(&i.arg[0], tn, fn); goto Emit; case Odbgloc: case Ocall: @@ -428,7 +439,8 @@ sel(Ins i, ANum *an, Fn *fn) case Oexts: case Otruncd: case Ocast: - case_OExt: + case_Oxsel: + case_Oext: Emit: emiti(i); i1 = curi; /* fixarg() can change curi */ @@ -442,7 +454,9 @@ Emit: break; default: if (isext(i.op)) - goto case_OExt; + goto case_Oext; + if (isxsel(i.op)) + goto case_Oxsel; if (isload(i.op)) goto case_Oload; if (iscmp(i.op, &kc, &x)) { @@ -496,6 +510,88 @@ 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->insop == 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; isel0op = 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) { @@ -525,7 +621,7 @@ seljmp(Blk *b, Fn *fn) b->jmp.type = Jjf + Cine; } else if (iscmp(fi->op, &k, &c) - && c != NCmpI+Cfeq /* see sel() */ + && c != NCmpI+Cfeq /* see sel(), selsel() */ && c != NCmpI+Cfne) { swap = cmpswap(fi->arg, c); if (swap) @@ -559,155 +655,225 @@ seljmp(Blk *b, Fn *fn) } } +enum { + Pob, + Pbis, + Pois, + Pobis, + Pbi1, + Pobi1, +}; + +/* mgen generated code + * + * (with-vars (o b i s) + * (patterns + * (ob (add (con o) (tmp b))) + * (bis (add (tmp b) (mul (tmp i) (con s 1 2 4 8)))) + * (ois (add (con o) (mul (tmp i) (con s 1 2 4 8)))) + * (obis (add (con o) (tmp b) (mul (tmp i) (con s 1 2 4 8)))) + * (bi1 (add (tmp b) (tmp i))) + * (obi1 (add (con o) (tmp b) (tmp i))) + * )) + */ + static int -aref(Ref r, ANum *ai) +opn(int op, int l, int r) { - switch (rtype(r)) { - case RCon: + static uchar Oaddtbl[91] = { + 2, + 2,2, + 4,4,5, + 6,6,8,8, + 4,4,9,10,9, + 7,7,5,8,9,5, + 4,4,12,10,12,12,12, + 4,4,9,10,9,9,12,9, + 11,11,5,8,9,5,12,9,5, + 7,7,5,8,9,5,12,9,5,5, + 11,11,5,8,9,5,12,9,5,5,5, + 4,4,9,10,9,9,12,9,9,9,9,9, + 7,7,5,8,9,5,12,9,5,5,5,9,5, + }; + int t; + + if (l < r) + t = l, l = r, r = t; + switch (op) { + case Omul: + if (2 <= l) + if (r == 0) { + return 3; + } return 2; - case RTmp: - return ai[r.val].n; + case Oadd: + return Oaddtbl[(l + l*l)/2 + r]; default: - die("constant or temporary expected"); + return 2; } } static int -ascale(Ref r, Con *con) +refn(Ref r, Num *tn, Con *con) { int64_t n; - if (rtype(r) != RCon) - return 0; - if (con[r.val].type != CBits) - return 0; - n = con[r.val].bits.i; - return n == 1 || n == 2 || n == 4 || n == 8; + switch (rtype(r)) { + case RTmp: + if (!tn[r.val].n) + tn[r.val].n = 2; + return tn[r.val].n; + case RCon: + if (con[r.val].type != CBits) + return 1; + n = con[r.val].bits.i; + if (n == 8 || n == 4 || n == 2 || n == 1) + return 0; + return 1; + default: + return INT_MIN; + } } +static bits match[13] = { + [4] = BIT(Pob), + [5] = BIT(Pbi1), + [6] = BIT(Pob) | BIT(Pois), + [7] = BIT(Pob) | BIT(Pobi1), + [8] = BIT(Pbi1) | BIT(Pbis), + [9] = BIT(Pbi1) | BIT(Pobi1), + [10] = BIT(Pbi1) | BIT(Pbis) | BIT(Pobi1) | BIT(Pobis), + [11] = BIT(Pob) | BIT(Pobi1) | BIT(Pobis), + [12] = BIT(Pbi1) | BIT(Pobi1) | BIT(Pobis), +}; + +static uchar *matcher[] = { + [Pbi1] = (uchar[]){ + 1,3,1,3,2,0 + }, + [Pbis] = (uchar[]){ + 5,1,8,5,27,1,5,1,2,5,13,3,1,1,3,3,3,2,0,1, + 3,3,3,2,3,1,0,1,29 + }, + [Pob] = (uchar[]){ + 1,3,0,3,1,0 + }, + [Pobi1] = (uchar[]){ + 5,3,9,9,10,33,12,35,45,1,5,3,11,9,7,9,4,9, + 17,1,3,0,3,1,3,2,0,3,1,1,3,0,34,1,37,1,5,2, + 5,7,2,7,8,37,29,1,3,0,1,32 + }, + [Pobis] = (uchar[]){ + 5,2,10,7,11,19,49,1,1,3,3,3,2,1,3,0,3,1,0, + 1,3,0,5,1,8,5,25,1,5,1,2,5,13,3,1,1,3,3,3, + 2,0,1,3,3,3,2,26,1,51,1,5,1,6,5,9,1,3,0,51, + 3,1,1,3,0,45 + }, + [Pois] = (uchar[]){ + 1,3,0,1,3,3,3,2,0 + }, +}; + +/* end of generated code */ + static void -anumber(ANum *ai, Blk *b, Con *con) +anumber(Num *tn, Blk *b, Con *con) { - /* This should be made obsolete by a proper - * reassoc pass. - * - * Rules: - * - * RTmp(_) -> 0 tmp - * ( RTmp(_) -> 1 slot ) - * RCon(_) -> 2 con - * 0 * 2 -> 3 s * i (when constant is 1,2,4,8) - */ - static char add[10][10] = { - [2] [4] = 4, [4] [2] = 4, - [2] [6] = 6, [6] [2] = 6, - [2] [7] = 7, [7] [2] = 7, - [0] [2] = 4, [2] [0] = 4, /* 4: o + b */ - [0] [0] = 5, /* 5: b + s * i */ - [0] [3] = 5, [3] [0] = 5, - [2] [3] = 6, [3] [2] = 6, /* 6: o + s * i */ - [2] [5] = 7, [5] [2] = 7, /* 7: o + b + s * i */ - [0] [6] = 7, [6] [0] = 7, - [4] [3] = 7, [3] [4] = 7, - }; - int a, a1, a2, n1, n2, t1, t2; Ins *i; + Num *n; for (i=b->ins; i<&b->ins[b->nins]; i++) { - if (rtype(i->to) == RTmp) - ai[i->to.val].i = i; - if (i->op != Oadd && i->op != Omul) + if (rtype(i->to) != RTmp) continue; - a1 = aref(i->arg[0], ai); - a2 = aref(i->arg[1], ai); - t1 = a1 != 1 && a1 != 2; - t2 = a2 != 1 && a2 != 2; - if (i->op == Oadd) { - a = add[n1 = a1][n2 = a2]; - if (t1 && a < add[0][a2]) - a = add[n1 = 0][n2 = a2]; - if (t2 && a < add[a1][0]) - a = add[n1 = a1][n2 = 0]; - if (t1 && t2 && a < add[0][0]) - a = add[n1 = 0][n2 = 0]; - } else { - n1 = n2 = a = 0; - if (ascale(i->arg[0], con) && t2) - a = 3, n1 = 2, n2 = 0; - if (t1 && ascale(i->arg[1], con)) - a = 3, n1 = 0, n2 = 2; - } - ai[i->to.val].n = a; - ai[i->to.val].l = n1; - ai[i->to.val].r = n2; + n = &tn[i->to.val]; + n->l = i->arg[0]; + n->r = i->arg[1]; + n->nl = refn(n->l, tn, con); + n->nr = refn(n->r, tn, con); + n->n = opn(i->op, n->nl, n->nr); } } +static Ref +adisp(Con *c, Num *tn, Ref r, Fn *fn, int s) +{ + Ref v[2]; + int n; + + while (!req(r, R)) { + assert(rtype(r) == RTmp); + n = refn(r, tn, fn->con); + if (!(match[n] & BIT(Pob))) + break; + runmatch(matcher[Pob], tn, r, v); + assert(rtype(v[0]) == RCon); + addcon(c, &fn->con[v[0].val], s); + r = v[1]; + } + return r; +} + static int -amatch(Addr *a, Ref r, int n, ANum *ai, Fn *fn) +amatch(Addr *a, Num *tn, Ref r, Fn *fn) { - Ins *i; - int nl, nr, t, s; - Ref al, ar; + static int pat[] = {Pobis, Pobi1, Pbis, Pois, Pbi1, -1}; + Ref ro, rb, ri, rs, v[4]; + Con *c, co; + int s, n, *p; - if (rtype(r) == RCon) { - if (!addcon(&a->offset, &fn->con[r.val])) - err("unlikely sum of $%s and $%s", - str(a->offset.sym.id), - str(fn->con[r.val].sym.id)); - return 1; - } - assert(rtype(r) == RTmp); - i = ai[r.val].i; - nl = ai[r.val].l; - nr = ai[r.val].r; - if (i) { - if (nl > nr) { - al = i->arg[1]; - ar = i->arg[0]; - t = nl, nl = nr, nr = t; - } else { - al = i->arg[0]; - ar = i->arg[1]; - } - } - switch (n) { - case 3: /* s * i */ - a->index = al; - a->scale = fn->con[ar.val].bits.i; + if (rtype(r) != RTmp) return 0; - case 5: /* b + s * i */ - switch (nr) { - case 0: - if (fn->tmp[ar.val].slot != -1) { - al = i->arg[1]; - ar = i->arg[0]; - } - a->index = ar; - a->scale = 1; - break; - case 3: - amatch(a, ar, nr, ai, fn); + + n = refn(r, tn, fn->con); + memset(v, 0, sizeof v); + for (p=pat; *p>=0; p++) + if (match[n] & BIT(*p)) { + runmatch(matcher[*p], tn, r, v); break; } - r = al; - /* fall through */ - case 0: - s = fn->tmp[r.val].slot; - if (s != -1) - r = SLOT(s); - a->base = r; - return n || s != -1; - case 2: /* constants */ - case 4: /* o + b */ - case 6: /* o + s * i */ - case 7: /* o + b + s * i */ - amatch(a, ar, nr, ai, fn); - amatch(a, al, nl, ai, fn); - return 1; - default: - die("unreachable"); + if (*p < 0) + v[1] = r; + + memset(&co, 0, sizeof co); + ro = v[0]; + rb = adisp(&co, tn, v[1], fn, 1); + ri = v[2]; + rs = v[3]; + s = 1; + + if (*p < 0 && co.type != CUndef) + if (amatch(a, tn, rb, fn)) + return addcon(&a->offset, &co, 1); + if (!req(ro, R)) { + assert(rtype(ro) == RCon); + c = &fn->con[ro.val]; + if (!addcon(&co, c, 1)) + return 0; } + if (!req(rs, R)) { + assert(rtype(rs) == RCon); + c = &fn->con[rs.val]; + assert(c->type == CBits); + s = c->bits.i; + } + ri = adisp(&co, tn, ri, fn, s); + *a = (Addr){co, rb, ri, s}; + + if (rtype(ri) == RTmp) + if (fn->tmp[ri.val].slot != -1) { + if (a->scale != 1 + || fn->tmp[rb.val].slot != -1) + return 0; + a->base = ri; + a->index = rb; + } + if (!req(a->base, R)) { + assert(rtype(a->base) == RTmp); + s = fn->tmp[a->base.val].slot; + if (s != -1) + a->base = SLOT(s); + } + return 1; } /* instruction selection @@ -722,7 +888,7 @@ amd64_isel(Fn *fn) uint a; int n, al; int64_t sz; - ANum *ainfo; + Num *num; /* assign slots to fast allocs */ b = fn->start; @@ -741,12 +907,13 @@ amd64_isel(Fn *fn) die("alloc too large"); fn->tmp[i->to.val].slot = fn->slot; fn->slot += sz; + fn->salign = 2 + al - Oalloc; *i = (Ins){.op = Onop}; } /* process basic blocks */ n = fn->ntmp; - ainfo = emalloc(n * sizeof ainfo[0]); + num = emalloc(n * sizeof num[0]); for (b=fn->start; b; b=b->link) { curi = &insb[NIns]; for (sb=(Blk*[3]){b->s1, b->s2, 0}; *sb; sb++) @@ -755,15 +922,20 @@ amd64_isel(Fn *fn) assert(a+1 < p->narg); fixarg(&p->arg[a], p->cls, 0, fn); } - memset(ainfo, 0, n * sizeof ainfo[0]); - anumber(ainfo, b, fn->con); + memset(num, 0, n * sizeof num[0]); + anumber(num, b, fn->con); seljmp(b, fn); - for (i=&b->ins[b->nins]; i!=b->ins;) - sel(*--i, ainfo, fn); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + 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); + } + idup(b, curi, &insb[NIns]-curi); } - free(ainfo); + free(num); if (debug['I']) { fprintf(stderr, "\n> After instruction selection:\n"); diff --git a/vendor/qbe/amd64/sysv.c b/vendor/qbe/amd64/sysv.c index 5b559c1..68f548c 100644 --- a/vendor/qbe/amd64/sysv.c +++ b/vendor/qbe/amd64/sysv.c @@ -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+NFPS+1) * sizeof(int) && - sizeof amd64_sysv_rclob == (NCLR+1) * sizeof(int) + sizeof amd64_sysv_rsave == (NGPS_SYSV+NFPS+1) * sizeof(int) && + sizeof amd64_sysv_rclob == (NCLR_SYSV+1) * sizeof(int) ); /* layout of call's second argument (RCall) @@ -508,11 +508,10 @@ split(Fn *fn, Blk *b) ++fn->nblk; bn = newblk(); - bn->nins = &insb[NIns] - curi; - idup(&bn->ins, curi, bn->nins); + idup(bn, curi, &insb[NIns]-curi); curi = &insb[NIns]; bn->visit = ++b->visit; - strf(bn->name, "%s.%d", b->name, b->visit); + bn->name = strf(PFn, "%s.%d", b->name, b->visit); bn->loop = b->loop; bn->link = b->link; b->link = bn; @@ -657,9 +656,9 @@ void amd64_sysv_abi(Fn *fn) { Blk *b; - Ins *i, *i0, *ip; + Ins *i, *i0; RAlloc *ral; - int n, fa; + int n0, n1, ioff, fa; for (b=fn->start; b; b=b->link) b->visit = 0; @@ -669,12 +668,13 @@ amd64_sysv_abi(Fn *fn) if (!ispar(i->op)) break; fa = selpar(fn, b->ins, i); - n = b->nins - (i - b->ins) + (&insb[NIns] - curi); - i0 = alloc(n * sizeof(Ins)); - ip = icpy(ip = i0, curi, &insb[NIns] - curi); - ip = icpy(ip, i, &b->ins[b->nins] - i); - b->nins = n; - b->ins = i0; + n0 = &insb[NIns] - curi; + ioff = i - b->ins; + n1 = b->nins - ioff; + vgrow(&b->ins, n0+n1); + icpy(b->ins+n0, b->ins+ioff, n1); + icpy(b->ins, curi, n0); + b->nins = n0+n1; /* lower calls, returns, and vararg instructions */ ral = 0; @@ -711,8 +711,7 @@ amd64_sysv_abi(Fn *fn) if (b == fn->start) for (; ral; ral=ral->link) emiti(ral->i); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } while (b != fn->start); if (debug['A']) { diff --git a/vendor/qbe/amd64/targ.c b/vendor/qbe/amd64/targ.c index fba9144..3edaf8b 100644 --- a/vendor/qbe/amd64/targ.c +++ b/vendor/qbe/amd64/targ.c @@ -19,20 +19,21 @@ 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, \ - .emitfn = amd64_emitfn, \ + .cansel = 1, 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 }; @@ -42,5 +43,25 @@ 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 }; diff --git a/vendor/qbe/amd64/winabi.c b/vendor/qbe/amd64/winabi.c new file mode 100755 index 0000000..5136e62 --- /dev/null +++ b/vendor/qbe/amd64/winabi.c @@ -0,0 +1,763 @@ +#include "all.h" + +#include + +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(®_usage, &ret_arg_class, /*is_float=*/false, + /*by_copy=*/true); + } + ret_arg_class.size = ret_type->size; + } + Ref env = R; + classify_arguments(®_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(®_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(®_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, ¶m_reg_usage, &extra_alloc); + } + lower_args_for_block(func, func->start, ¶m_reg_usage, &extra_alloc); + + if (debug['A']) { + fprintf(stderr, "\n> After ABI lowering:\n"); + printfn(func, stderr); + } +} diff --git a/vendor/qbe/arm64/abi.c b/vendor/qbe/arm64/abi.c index abc02d9..c258948 100644 --- a/vendor/qbe/arm64/abi.c +++ b/vendor/qbe/arm64/abi.c @@ -90,7 +90,7 @@ isfloatv(Typ *t, char *cls) static void typclass(Class *c, Typ *t, int *gp, int *fp) { - uint64_t sz; + uint64_t sz, hfasz; uint n; sz = (t->size + 7) & -8; @@ -103,7 +103,21 @@ typclass(Class *c, Typ *t, int *gp, int *fp) if (t->align > 3) err("alignments larger than 8 are not supported"); - if (t->isdark || sz > 16 || sz == 0) { + c->size = sz; + c->hfa.base = Kx; + c->ishfa = isfloatv(t, &c->hfa.base); + hfasz = t->size/(KWIDE(c->hfa.base) ? 8 : 4); + c->ishfa &= !t->isdark && hfasz <= 4; + c->hfa.size = hfasz; + + if (c->ishfa) { + for (n=0; nnfp++) { + c->reg[n] = *fp++; + c->cls[n] = c->hfa.base; + } + c->nreg = n; + } + else if (t->isdark || sz > 16 || sz == 0) { /* large structs are replaced by a * pointer to some caller-allocated * memory */ @@ -112,26 +126,14 @@ typclass(Class *c, Typ *t, int *gp, int *fp) c->ngp = 1; *c->reg = *gp; *c->cls = Kl; - return; } - - c->size = sz; - c->hfa.base = Kx; - c->ishfa = isfloatv(t, &c->hfa.base); - c->hfa.size = t->size/(KWIDE(c->hfa.base) ? 8 : 4); - - if (c->ishfa) - for (n=0; nhfa.size; n++, c->nfp++) { - c->reg[n] = *fp++; - c->cls[n] = c->hfa.base; - } - else + else { for (n=0; nngp++) { c->reg[n] = *gp++; c->cls[n] = Kl; } - - c->nreg = n; + c->nreg = n; + } } static void @@ -427,7 +429,7 @@ selcall(Fn *fn, Ins *i0, Ins *i1, Insl **ilp) for (i=i0, c=ca; iclass & Cstk) != 0) continue; - if (i->op == Oarg || i->op == Oarge) + if (i->op == Oarg || i->op == Oarge || isargbh(i->op)) 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); @@ -543,11 +545,10 @@ split(Fn *fn, Blk *b) ++fn->nblk; bn = newblk(); - bn->nins = &insb[NIns] - curi; - idup(&bn->ins, curi, bn->nins); + idup(bn, curi, &insb[NIns]-curi); curi = &insb[NIns]; bn->visit = ++b->visit; - strf(bn->name, "%s.%d", b->name, b->visit); + bn->name = strf(PFn, "%s.%d", b->name, b->visit); bn->loop = b->loop; bn->link = b->link; b->link = bn; @@ -728,9 +729,9 @@ void arm64_abi(Fn *fn) { Blk *b; - Ins *i, *i0, *ip; + Ins *i, *i0; Insl *il; - int n; + int n0, n1, ioff; Params p; for (b=fn->start; b; b=b->link) @@ -741,12 +742,13 @@ arm64_abi(Fn *fn) if (!ispar(i->op)) break; p = selpar(fn, b->ins, i); - n = b->nins - (i - b->ins) + (&insb[NIns] - curi); - i0 = alloc(n * sizeof(Ins)); - ip = icpy(ip = i0, curi, &insb[NIns] - curi); - ip = icpy(ip, i, &b->ins[b->nins] - i); - b->nins = n; - b->ins = i0; + n0 = &insb[NIns] - curi; + ioff = i - b->ins; + n1 = b->nins - ioff; + vgrow(&b->ins, n0+n1); + icpy(b->ins+n0, b->ins+ioff, n1); + icpy(b->ins, curi, n0); + b->nins = n0+n1; /* lower calls, returns, and vararg instructions */ il = 0; @@ -789,8 +791,7 @@ arm64_abi(Fn *fn) if (b == fn->start) for (; il; il=il->link) emiti(il->i); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } while (b != fn->start); if (debug['A']) { @@ -841,8 +842,7 @@ apple_extsb(Fn *fn) emit(op, Kw, i->to, i->arg[0], R); } } - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } if (debug['A']) { diff --git a/vendor/qbe/arm64/emit.c b/vendor/qbe/arm64/emit.c index 990d839..64d85f7 100644 --- a/vendor/qbe/arm64/emit.c +++ b/vendor/qbe/arm64/emit.c @@ -10,24 +10,24 @@ struct E { }; #define CMP(X) \ - 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") + 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") enum { Ki = -1, /* matches Kw and Kl */ @@ -37,7 +37,7 @@ enum { static struct { short op; short cls; - char *asm; + char *fmt; } omap[] = { { Oadd, Ki, "add %=, %0, %1" }, { Oadd, Ka, "fadd %=, %0, %1" }, @@ -102,13 +102,17 @@ 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) { @@ -132,6 +136,12 @@ 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; @@ -160,7 +170,8 @@ emitf(char *s, Ins *i, E *e) Ref r; int k, c; Con *pc; - uint n, sp; + uint64_t n; + uint sp; fputc('\t', e->f); @@ -171,7 +182,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 @@ -194,14 +205,14 @@ emitf(char *s, Ins *i, E *e) goto Switch; case '?': if (KBASE(k) == 0) - fputs(rname(R18, k), e->f); + fputs(rname(IP1, k), e->f); else - fputs(k==Ks ? "s31" : "d31", e->f); + fputs(rname(V31, k), e->f); break; case '=': case '0': r = c == '=' ? i->to : i->arg[0]; - assert(isreg(r)); + assert(isreg(r) || req(r, TMP(V31))); fputs(rname(r.val, k), e->f); break; case '1': @@ -217,10 +228,17 @@ emitf(char *s, Ins *i, E *e) pc = &e->fn->con[r.val]; n = pc->bits.i; assert(pc->type == CBits); - if (n & 0xfff000) - fprintf(e->f, "#%u, lsl #12", n>>12); - else - fprintf(e->f, "#%u", n); + if (n >> 24) { + assert(arm64_logimm(n, k)); + fprintf(e->f, "#%"PRIu64, n); + } else if (n & 0xfff000) { + assert(!(n & ~0xfff000ull)); + fprintf(e->f, "#%"PRIu64", lsl #12", + n>>12); + } else { + assert(!(n & ~0xfffull)); + fprintf(e->f, "#%"PRIu64, n); + } break; } break; @@ -260,6 +278,10 @@ 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" @@ -269,6 +291,14 @@ 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); @@ -304,6 +334,7 @@ loadcon(Con *c, int r, int k, E *e) rn = rname(r, k); n = c->bits.i; if (c->type == CAddr) { + rn = rname(r, Kl); loadaddr(c, rn, e); return; } @@ -326,8 +357,8 @@ loadcon(Con *c, int r, int k, E *e) static void emitins(Ins *, E *); -static void -fixarg(Ref *pr, int sz, E *e) +static int +fixarg(Ref *pr, int sz, int t, E *e) { Ins *i; Ref r; @@ -337,11 +368,14 @@ fixarg(Ref *pr, int sz, E *e) if (rtype(r) == RSlot) { s = slot(r, e); if (s > sz * 4095u) { - i = &(Ins){Oaddr, Kl, TMP(IP0), {r}}; + if (t < 0) + return 1; + i = &(Ins){Oaddr, Kl, TMP(t), {r}}; emitins(i, e); - *pr = TMP(IP0); + *pr = TMP(t); } } + return 0; } static void @@ -349,16 +383,28 @@ emitins(Ins *i, E *e) { char *l, *p, *rn; uint64_t s; - int o; + int o, t; Ref r; Con *c; switch (i->op) { default: if (isload(i->op)) - fixarg(&i->arg[0], loadsz(i), e); - if (isstore(i->op)) - fixarg(&i->arg[1], storesz(i), e); + 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); + } + } Table: /* most instructions are just pulled out of * the table omap[], some special cases are @@ -374,7 +420,7 @@ emitins(Ins *i, E *e) || (omap[o].cls == Ki && KBASE(i->cls) == 0)) break; } - emitf(omap[o].asm, i, e); + emitf(omap[o].fmt, i, e); break; case Onop: break; @@ -384,7 +430,7 @@ emitins(Ins *i, E *e) if (rtype(i->to) == RSlot) { r = i->to; if (!isreg(i->arg[0])) { - i->to = TMP(R18); + i->to = TMP(IP1); emitins(i, e); i->arg[0] = i->to; } @@ -405,7 +451,7 @@ emitins(Ins *i, E *e) emitins(i, e); break; default: - assert(i->to.val != R18); + assert(i->to.val != IP1); goto Table; } break; @@ -434,7 +480,7 @@ emitins(Ins *i, E *e) goto Table; c = &e->fn->con[i->arg[0].val]; if (c->type != CAddr - || c->sym.type != SGlo + || (c->sym.type & SThr) || c->bits.i) die("invalid call argument"); l = str(c->sym.id); @@ -498,8 +544,8 @@ framelayout(E *e) void arm64_emitfn(Fn *fn, FILE *out) { - static char *ctoa[] = { - #define X(c, s) [c] = s, + static char *ctoa[][2] = { + #define X(c, s, n) [c] = {s, n}, CMP(X) #undef X }; @@ -514,6 +560,7 @@ arm64_emitfn(Fn *fn, FILE *out) if (T.apple) e->fn->lnk.align = 4; emitfnlnk(e->fn->name, &e->fn->lnk, e->f); + fputs("\thint\t#34\n", e->f); framelayout(e); if (e->fn->vararg && !T.apple) { @@ -629,11 +676,12 @@ arm64_emitfn(Fn *fn, FILE *out) t = b->s1; b->s1 = b->s2; b->s2 = t; + n = 0; } else - c = cmpneg(c); + n = 1; fprintf(e->f, "\tb%s\t%s%d\n", - ctoa[c], T.asloc, id0+b->s2->id + ctoa[c][n], T.asloc, id0+b->s2->id ); goto Jmp; } diff --git a/vendor/qbe/arm64/isel.c b/vendor/qbe/arm64/isel.c index 062beb3..7b78202 100644 --- a/vendor/qbe/arm64/isel.c +++ b/vendor/qbe/arm64/isel.c @@ -24,7 +24,7 @@ imm(Con *c, int k, int64_t *pn) i = Iplo12; if (n < 0) { i = Inlo12; - n = -n; + n = -(uint64_t)n; } *pn = n; if ((n & 0x000fff) == n) @@ -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, KWIDE(k) ? 8 : 4); + n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4); vgrow(&fn->con, ++fn->ncon); c = &fn->con[fn->ncon-1]; sprintf(buf, "\"%sfp%d\"", T.asloc, n); @@ -306,8 +306,7 @@ arm64_isel(Fn *fn) seljmp(b, fn); for (i=&b->ins[b->nins]; i!=b->ins;) sel(*--i, fn); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } if (debug['I']) { diff --git a/vendor/qbe/arm64/targ.c b/vendor/qbe/arm64/targ.c index 232376d..8f1e149 100644 --- a/vendor/qbe/arm64/targ.c +++ b/vendor/qbe/arm64/targ.c @@ -16,7 +16,7 @@ int arm64_rclob[] = { -1 }; -#define RGLOB (BIT(FP) | BIT(SP) | BIT(R18)) +#define RGLOB (BIT(FP) | BIT(SP) | BIT(IP1) | BIT(R18)) static int arm64_memargs(int op) @@ -31,7 +31,7 @@ arm64_memargs(int op) .fpr0 = V0, \ .nfpr = NFPR, \ .rglob = RGLOB, \ - .nrglob = 3, \ + .nrglob = 4, \ .rsave = arm64_rsave, \ .nrsave = {NGPS, NFPS}, \ .retregs = arm64_retregs, \ @@ -40,6 +40,7 @@ arm64_memargs(int op) .isel = arm64_isel, \ .abi1 = arm64_abi, \ .emitfn = arm64_emitfn, \ + .cansel = 0, \ Target T_arm64 = { .name = "arm64", diff --git a/vendor/qbe/cfg.c b/vendor/qbe/cfg.c index 406c307..6d160f0 100644 --- a/vendor/qbe/cfg.c +++ b/vendor/qbe/cfg.c @@ -8,66 +8,50 @@ newblk() b = alloc(sizeof *b); *b = z; + b->ins = vnew(0, sizeof b->ins[0], PFn); + b->pred = vnew(0, sizeof b->pred[0], PFn); return b; } -void -edgedel(Blk *bs, Blk **pbd) +static void +fixphis(Fn *f) { - Blk *bd; + Blk *b, *bp; Phi *p; - uint a; - int mult; + uint n, n0; - bd = *pbd; - mult = 1 + (bs->s1 == bs->s2); - *pbd = 0; - if (!bd || mult > 1) - return; - for (p=bd->phi; p; p=p->link) { - for (a=0; p->blk[a]!=bs; a++) - assert(a+1narg); - p->narg--; - memmove(&p->blk[a], &p->blk[a+1], - sizeof p->blk[0] * (p->narg-a)); - memmove(&p->arg[a], &p->arg[a+1], - sizeof p->arg[0] * (p->narg-a)); - } - if (bd->npred != 0) { - for (a=0; bd->pred[a]!=bs; a++) - assert(a+1npred); - bd->npred--; - memmove(&bd->pred[a], &bd->pred[a+1], - sizeof bd->pred[0] * (bd->npred-a)); + 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; nnarg; n++) { + bp = p->blk[n]; + if (bp->id != -1u) + if (bp->s1 == b || bp->s2 == b) { + p->blk[n0] = bp; + p->arg[n0] = p->arg[n]; + n0++; + } + } + assert(n0 > 0); + p->narg = n0; + } } } static void -addpred(Blk *bp, Blk *bc) +addpred(Blk *bp, Blk *b) { - if (!bc->pred) { - bc->pred = alloc(bc->npred * sizeof bc->pred[0]); - bc->visit = 0; - } - bc->pred[bc->visit++] = bp; + vgrow(&b->pred, ++b->npred); + b->pred[b->npred-1] = bp; } -/* fill predecessors information in blocks */ void fillpreds(Fn *f) { Blk *b; - for (b=f->start; b; b=b->link) { + for (b=f->start; b; b=b->link) b->npred = 0; - b->pred = 0; - } - for (b=f->start; b; b=b->link) { - if (b->s1) - b->s1->npred++; - if (b->s2 && b->s2 != b->s1) - b->s2->npred++; - } for (b=f->start; b; b=b->link) { if (b->s1) addpred(b, b->s1); @@ -76,52 +60,55 @@ fillpreds(Fn *f) } } -static int -rporec(Blk *b, uint x) +static void +porec(Blk *b, uint *npo) { Blk *s1, *s2; if (!b || b->id != -1u) - return x; - b->id = 1; + return; + b->id = 0; /* marker */ s1 = b->s1; s2 = b->s2; if (s1 && s2 && s1->loop > s2->loop) { s1 = b->s2; s2 = b->s1; } - x = rporec(s1, x); - x = rporec(s2, x); - b->id = x; - assert(x != -1u); - return x - 1; + porec(s1, npo); + porec(s2, npo); + b->id = (*npo)++; } -/* fill the rpo information */ -void +static void fillrpo(Fn *f) { - uint n; Blk *b, **p; for (b=f->start; b; b=b->link) b->id = -1u; - n = 1 + rporec(f->start, f->nblk-1); - f->nblk -= n; - f->rpo = alloc(f->nblk * sizeof f->rpo[0]); + f->nblk = 0; + porec(f->start, &f->nblk); + vgrow(&f->rpo, f->nblk); for (p=&f->start; (b=*p);) { if (b->id == -1u) { - edgedel(b, &b->s1); - edgedel(b, &b->s2); *p = b->link; } else { - b->id -= n; + b->id = f->nblk-b->id-1; f->rpo[b->id] = b; p = &b->link; } } } +/* fill rpo, preds; prune dead blks */ +void +fillcfg(Fn *f) +{ + fillrpo(f); + fillpreds(f); + fixphis(f); +} + /* for dominators computation, read * "A Simple, Fast Dominance Algorithm" * by K. Cooper, T. Harvey, and K. Kennedy. @@ -262,6 +249,50 @@ loopiter(Fn *fn, void f(Blk *, Blk *)) } } +/* dominator tree depth */ +void +filldepth(Fn *fn) +{ + Blk *b, *d; + int depth; + + for (b=fn->start; b; b=b->link) + b->depth = -1; + + fn->start->depth = 0; + + for (b=fn->start; b; b=b->link) { + if (b->depth != -1) + continue; + depth = 1; + for (d=b->idom; d->depth==-1; d=d->idom) + depth++; + depth += d->depth; + b->depth = depth; + for (d=b->idom; d->depth==-1; d=d->idom) + d->depth = --depth; + } +} + +/* least common ancestor in dom tree */ +Blk * +lca(Blk *b1, Blk *b2) +{ + if (!b1) + return b2; + if (!b2) + return b1; + while (b1->depth > b2->depth) + b1 = b1->idom; + while (b2->depth > b1->depth) + b2 = b2->idom; + while (b1 != b2) { + b1 = b1->idom; + b2 = b2->idom; + } + return b1; +} + void multloop(Blk *hd, Blk *b) { @@ -329,3 +360,208 @@ simpljmp(Fn *fn) *p = ret; free(uf); } + +static int +reachrec(Blk *b, Blk *to) +{ + if (b == to) + return 1; + if (!b || b->visit) + return 0; + + b->visit = 1; + if (reachrec(b->s1, to)) + return 1; + if (reachrec(b->s2, to)) + return 1; + + return 0; +} + +/* Blk.visit needs to be clear at entry */ +int +reaches(Fn *fn, Blk *b, Blk *to) +{ + int r; + + assert(to); + r = reachrec(b, to); + for (b=fn->start; b; b=b->link) + b->visit = 0; + return r; +} + +/* can b reach 'to' not through excl + * Blk.visit needs to be clear at entry */ +int +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); + } +} diff --git a/vendor/qbe/copy.c b/vendor/qbe/copy.c index 77a8f06..975d1df 100644 --- a/vendor/qbe/copy.c +++ b/vendor/qbe/copy.c @@ -1,16 +1,294 @@ #include "all.h" +typedef struct Ext Ext; + +struct Ext { + char zext; + char nopw; /* is a no-op if arg width is <= nopw */ + char usew; /* uses only the low usew bits of arg */ +}; + static int -iscon(Ref r, int64_t bits, Fn *fn) +ext(Ins *i, Ext *e) { - return rtype(r) == RCon - && fn->con[r.val].type == CBits - && fn->con[r.val].bits.i == bits; + static Ext tbl[] = { + /*extsb*/ {0, 7, 8}, + /*extub*/ {1, 8, 8}, + /*extsh*/ {0, 15, 16}, + /*extuh*/ {1, 16, 16}, + /*extsw*/ {0, 31, 32}, + /*extuw*/ {1, 32, 32}, + }; + + if (!isext(i->op)) + return 0; + *e = tbl[i->op - Oextsb]; + return 1; } static int -iscopy(Ins *i, Ref r, Fn *fn) +bitwidth(uint64_t v) { + int n; + + n = 0; + if (v >> 32) { n += 32; v >>= 32; } + if (v >> 16) { n += 16; v >>= 16; } + if (v >> 8) { n += 8; v >>= 8; } + if (v >> 4) { n += 4; v >>= 4; } + if (v >> 2) { n += 2; v >>= 2; } + if (v >> 1) { n += 1; v >>= 1; } + return n+v; +} + +static int +visit(Fn *fn, Ref r, int w, int f(Fn *, Ref, int)) +{ + Blk *b; + Phi *p; + int ret; + + ret = f(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 +uwl(Fn *fn, Ref r, int w) +{ + Ext e; + Tmp *t; + Use *u; + Phi *p; + Ins *i; + Ref rc; + int64_t v; + + assert(rtype(r) == RTmp); + t = &fn->tmp[r.val]; + for (u=t->use; u<&t->use[t->nuse]; u++) { + 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)) + continue; + p->visit = 1; + if (uwl(fn, p->to, w)) + continue; + break; + case UIns: + i = u->u.ins; + assert(i != 0); + if (i->op == Ocopy) + if (uwl(fn, i->to, w)) + continue; + if (ext(i, &e)) { + if (e.usew <= w) + continue; + if (uwl(fn, i->to, w)) + continue; + } + if (i->op == Oand) { + if (req(r, i->arg[0])) + rc = i->arg[1]; + else { + assert(req(r, i->arg[1])); + rc = i->arg[0]; + } + if (isconbits(fn, rc, &v) + && bitwidth(v) <= w) + continue; + break; + } + break; + default: + break; + } + return 0; + } + return 1; +} + +/* no more than w bits are used */ +static int +usewidthle(Fn *fn, Ref r, int w) +{ + return visit(fn, r, w, uwl); +} + +static int +min(int v1, int v2) +{ + return v1 < v2 ? v1 : v2; +} + +static int +dwl(Fn *fn, Ref r, int w) +{ + Ext e; + Tmp *t; + Phi *p; + Ins *i; + uint n; + int64_t v; + int x; + + if (isconbits(fn, r, &v) + && bitwidth(v) <= w) + return 1; + if (w <= 0) + return 0; + if (rtype(r) != RTmp) + return 0; + t = &fn->tmp[r.val]; + if (t->cls != Kw) + return 0; + + if (!t->def) { + /* phi def */ + for (p=fn->rpo[t->bid]->phi; p; p=p->link) + if (req(p->to, r)) + break; + assert(p); + if (p->visit && p->visit <= w) + return 1; + p->visit = w; + for (n=0; nnarg; n++) + if (!dwl(fn, p->arg[n], w)) + return 0; + return 1; + } + + i = t->def; + if (i->op == Ocopy) + return dwl(fn, i->arg[0], w); + if (i->op == Oshr || i->op == Osar) { + if (isconbits(fn, i->arg[1], &v)) + if (0 < v && v <= 32) { + if (i->op == Oshr && w+v >= 32) + return 1; + if (w < 32) { + if (i->op == Osar) + w = min(31, w+v); + else + w = min(32, w+v); + } + } + return dwl(fn, i->arg[0], w); + } + if (iscmp(i->op, &x, &x)) + return w >= 1; + if (i->op == Oand) { + if (dwl(fn, i->arg[0], w) + || dwl(fn, i->arg[1], w)) + return 1; + return 0; + } + if (i->op == Oor || i->op == Oxor) { + if (dwl(fn, i->arg[0], w) + && dwl(fn, i->arg[1], w)) + return 1; + return 0; + } + if (ext(i, &e)) { + if (e.zext && e.usew <= w) + return 1; + w = min(w, e.nopw); + return dwl(fn, i->arg[0], w); + } + + return 0; +} + +/* is the ref narrower than w bits */ +static int +defwidthle(Fn *fn, Ref r, int w) +{ + return visit(fn, r, w, dwl); +} + +static int +isw1(Fn *fn, Ref r) +{ + return defwidthle(fn, r, 1); +} + +/* insert early extub/extuh instructions + * for pars used only narrowly; this + * helps factoring extensions out of + * loops + * + * needs use; breaks use + */ +void +narrowpars(Fn *fn) +{ + Blk *b; + int loop; + Ins ext, *i, *ins; + uint npar, nins; + Ref r; + + /* only useful for functions with loops */ + loop = 0; + for (b=fn->start; b; b=b->link) + if (b->loop > 1) { + loop = 1; + break; + } + if (!loop) + return; + + b = fn->start; + + npar = 0; + for (i=b->ins; i<&b->ins[b->nins]; i++) { + if (!ispar(i->op)) + break; + npar++; + } + if (npar == 0) + return; + + nins = b->nins + npar; + ins = vnew(nins, sizeof ins[0], PFn); + icpy(ins, b->ins, npar); + icpy(ins + 2*npar, b->ins+npar, b->nins-npar); + b->ins = ins; + b->nins = nins; + + for (i=b->ins; i<&b->ins[b->nins]; i++) { + if (!ispar(i->op)) + break; + ext = (Ins){.op = Onop}; + if (i->cls == Kw) + if (usewidthle(fn, i->to, 16)) { + ext.op = Oextuh; + if (usewidthle(fn, i->to, 8)) + ext.op = Oextub; + r = newtmp("vw", i->cls, fn); + ext.cls = i->cls; + ext.to = i->to; + ext.arg[0] = r; + i->to = r; + } + *(i+npar) = ext; + } +} + +Ref +copyref(Fn *fn, Blk *b, Ins *i) +{ + /* which extensions are copies for a given + * argument width */ static bits extcpy[] = { [WFull] = 0, [Wsb] = BIT(Wsb) | BIT(Wsh) | BIT(Wsw), @@ -20,210 +298,139 @@ iscopy(Ins *i, Ref r, Fn *fn) [Wsw] = BIT(Wsw), [Wuw] = BIT(Wuw), }; - bits b; + Ext e; Tmp *t; + int64_t v; + int w, z; - switch (i->op) { - case Ocopy: - return 1; - case Omul: - case Odiv: - case Oudiv: - return iscon(i->arg[1], 1, fn); - case Oadd: - case Osub: - case Oor: - case Oxor: - case Osar: - case Oshl: - case Oshr: - return iscon(i->arg[1], 0, fn); - default: - break; + if (i->op == Ocopy) + return i->arg[0]; + + /* op identity value */ + if (optab[i->op].hasid + && KBASE(i->cls) == 0 /* integer only - fp NaN! */ + && req(i->arg[1], con01[optab[i->op].idval]) + && (!optab[i->op].cmpeqwl || isw1(fn, i->arg[0]))) + return i->arg[0]; + + /* idempotent op with identical args */ + if (optab[i->op].idemp + && req(i->arg[0], i->arg[1])) + return i->arg[0]; + + /* integer cmp with identical args */ + if ((optab[i->op].cmpeqwl || optab[i->op].cmplgtewl) + && req(i->arg[0], i->arg[1])) + return con01[optab[i->op].eqval]; + + /* cmpeq/ne 0 with 0/non-0 inference */ + if (optab[i->op].cmpeqwl + && req(i->arg[1], CON_Z) + && zeroval(fn, b, i->arg[0], argcls(i, 0), &z)) + return con01[optab[i->op].eqval^z^1]; + + /* redundant and mask */ + if (i->op == Oand + && isconbits(fn, i->arg[1], &v) + && (v > 0 && ((v+1) & v) == 0) + && defwidthle(fn, i->arg[0], bitwidth(v))) + return i->arg[0]; + + if (i->cls == Kw + && (i->op == Oextsw || i->op == Oextuw)) + return i->arg[0]; + + if (ext(i, &e) && rtype(i->arg[0]) == RTmp) { + t = &fn->tmp[i->arg[0].val]; + assert(KBASE(t->cls) == 0); + + /* do not break typing by returning + * a narrower temp */ + if (KWIDE(i->cls) > KWIDE(t->cls)) + return R; + + w = Wsb + (i->op - Oextsb); + if (BIT(w) & extcpy[t->width]) + return i->arg[0]; + + /* avoid eliding extensions of params + * inserted in the start block; their + * point is to make further extensions + * redundant */ + if ((!t->def || !ispar(t->def->op)) + && usewidthle(fn, i->to, e.usew)) + return i->arg[0]; + + if (defwidthle(fn, i->arg[0], e.nopw)) + return i->arg[0]; } - if (!isext(i->op) || rtype(r) != RTmp) - return 0; - if (i->op == Oextsw || i->op == Oextuw) - if (i->cls == Kw) - return 1; - t = &fn->tmp[r.val]; - assert(KBASE(t->cls) == 0); - if (i->cls == Kl && t->cls == Kw) - return 0; - b = extcpy[t->width]; - return (BIT(Wsb + (i->op-Oextsb)) & b) != 0; + return R; } -static Ref -copyof(Ref r, Ref *cpy) +static int +phieq(Phi *pa, Phi *pb) { - if (rtype(r) == RTmp && !req(cpy[r.val], R)) - return cpy[r.val]; - return r; + Ref r; + uint n; + + assert(pa->narg == pb->narg); + for (n=0; nnarg; n++) { + r = phiarg(pb, pa->blk[n]); + if (!req(pa->arg[n], r)) + return 0; + } + return 1; } -/* detects a cluster of phis/copies redundant with 'r'; - * the algorithm is inspired by Section 3.2 of "Simple - * and Efficient SSA Construction" by Braun M. et al. - */ -static void -phisimpl(Phi *p, Ref r, Ref *cpy, Use ***pstk, BSet *ts, BSet *as, Fn *fn) +Ref +phicopyref(Fn *fn, Blk *b, Phi *p) { - Use **stk, *u, *u1; - uint nstk, a; - int t; - Ref r1; - Phi *p0; + Blk *d, **s; + Phi *p1; + Ref r; + uint n, c; - bszero(ts); - bszero(as); - p0 = &(Phi){.narg = 0}; - stk = *pstk; - nstk = 1; - stk[0] = &(Use){.type = UPhi, .u.phi = p}; - while (nstk) { - u = stk[--nstk]; - if (u->type == UIns && iscopy(u->u.ins, r, fn)) { - p = p0; - t = u->u.ins->to.val; + /* identical args */ + r = R; + for (n=0; nnarg; n++) + if (!req(p->arg[n], p->to)) { + if (req(r, R)) + r = p->arg[n]; + else if (!req(p->arg[n], r)) + break; } - else if (u->type == UPhi) { - p = u->u.phi; - t = p->to.val; - } - else - continue; - if (bshas(ts, t)) - continue; - bsset(ts, t); - for (a=0; anarg; a++) { - r1 = copyof(p->arg[a], cpy); - if (req(r1, r)) - continue; - if (rtype(r1) != RTmp) - return; - bsset(as, r1.val); - } - u = fn->tmp[t].use; - u1 = &u[fn->tmp[t].nuse]; - vgrow(pstk, nstk+(u1-u)); - stk = *pstk; - for (; unarg) + return r; + + /* same as a previous phi */ + for (p1=b->phi; p1!=p; p1=p1->link) { + assert(p1); + if (phieq(p1, p)) + return p1->to; } - bsdiff(as, ts); - if (!bscount(as)) - for (t=0; bsiter(ts, &t); t++) - cpy[t] = r; -} - -static void -subst(Ref *pr, Ref *cpy) -{ - assert(rtype(*pr) != RTmp || !req(cpy[pr->val], R)); - *pr = copyof(*pr, cpy); -} - -/* requires use and dom, breaks use */ -void -copy(Fn *fn) -{ - BSet ts[1], as[1]; - Use **stk; - Phi *p, **pp; - Ins *i; - Blk *b; - uint n, a, eq; - Ref *cpy, r, r1; - int t; - - bsinit(ts, fn->ntmp); - bsinit(as, fn->ntmp); - cpy = emalloc(fn->ntmp * sizeof cpy[0]); - stk = vnew(10, sizeof stk[0], PHeap); - - /* 1. build the copy-of map */ - for (n=0; nnblk; n++) { - b = fn->rpo[n]; - for (p=b->phi; p; p=p->link) { - assert(rtype(p->to) == RTmp); - if (!req(cpy[p->to.val], R)) - continue; - eq = 0; - r = R; - for (a=0; anarg; a++) - if (p->blk[a]->id < n) { - r1 = copyof(p->arg[a], cpy); - if (req(r, R) || req(r, UNDEF)) - r = r1; - if (req(r1, r) || req(r1, UNDEF)) - eq++; - } - assert(!req(r, R)); - if (rtype(r) == RTmp - && !dom(fn->rpo[fn->tmp[r.val].bid], b)) - cpy[p->to.val] = p->to; - else if (eq == p->narg) - cpy[p->to.val] = r; - else { - cpy[p->to.val] = p->to; - phisimpl(p, r, cpy, &stk, ts, as, fn); - } - } - for (i=b->ins; i<&b->ins[b->nins]; i++) { - assert(rtype(i->to) <= RTmp); - if (!req(cpy[i->to.val], R)) - continue; - r = copyof(i->arg[0], cpy); - if (iscopy(i, r, fn)) - cpy[i->to.val] = r; - else - cpy[i->to.val] = i->to; - } - } - - /* 2. remove redundant phis/copies - * and rewrite their uses */ - for (b=fn->start; b; b=b->link) { - for (pp=&b->phi; (p=*pp);) { - r = cpy[p->to.val]; - if (!req(r, p->to)) { - *pp = p->link; - continue; - } - for (a=0; anarg; a++) - subst(&p->arg[a], cpy); - pp=&p->link; - } - for (i=b->ins; i<&b->ins[b->nins]; i++) { - r = cpy[i->to.val]; - if (!req(r, i->to)) { - *i = (Ins){.op = Onop}; - continue; - } - subst(&i->arg[0], cpy); - subst(&i->arg[1], cpy); - } - subst(&b->jmp.arg, cpy); - } - - if (debug['C']) { - fprintf(stderr, "\n> Copy information:"); - for (t=Tmp0; tntmp; t++) { - if (req(cpy[t], R)) { - fprintf(stderr, "\n%10s not seen!", - fn->tmp[t].name); - } - else if (!req(cpy[t], TMP(t))) { - fprintf(stderr, "\n%10s copy of ", - fn->tmp[t].name); - printref(cpy[t], fn, stderr); - } - } - fprintf(stderr, "\n\n> After copy elimination:\n"); - printfn(fn, stderr); - } - vfree(stk); - free(cpy); + + /* can be replaced by a + * dominating jnz arg */ + d = b->idom; + if (p->narg != 2 + || d->jmp.type != Jjnz + || !isw1(fn, d->jmp.arg)) + return R; + + s = (Blk*[]){0, 0}; + for (n=0; n<2; n++) + for (c=0; c<2; c++) + if (req(p->arg[n], con01[c])) + s[c] = p->blk[n]; + + /* if s1 ends with a jnz on either b + * or s2; the inference below is wrong + * without the jump type checks */ + if (d->s1 == s[1] && d->s2 == s[0] + && d->s1->jmp.type == Jjmp + && d->s2->jmp.type == Jjmp) + return d->jmp.arg; + + return R; } diff --git a/vendor/qbe/doc/il.txt b/vendor/qbe/doc/il.txt index 2d488dc..6d1e979 100644 --- a/vendor/qbe/doc/il.txt +++ b/vendor/qbe/doc/il.txt @@ -180,7 +180,9 @@ by zero-extension, or by sign-extension. DYNCONST := CONST - | 'thread' $IDENT # Thread-local symbol + | 'thread' $IDENT # Thread-local symbol + | 'extern' $IDENT # Extern symbol (GOT) + | 'extern' 'thread' $IDENT # Extern thread-local (initial-exec) VAL := DYNCONST @@ -225,6 +227,15 @@ 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. @@ -439,7 +450,7 @@ return type of the function. All return values of this function must have this return type. If the return type is missing, the function must not return any value. -The parameter list is a comma separated list of +The parameter list is a comma separated list of distinct temporary names prefixed by types. The types are used to correctly implement C compatibility. When an argument has an aggregate type, a pointer to the aggregate is passed @@ -976,6 +987,7 @@ 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 diff --git a/vendor/qbe/doc/native_win.txt b/vendor/qbe/doc/native_win.txt new file mode 100644 index 0000000..bc88f05 --- /dev/null +++ b/vendor/qbe/doc/native_win.txt @@ -0,0 +1,15 @@ +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. diff --git a/vendor/qbe/emit.c b/vendor/qbe/emit.c index 490628e..45a6e5a 100644 --- a/vendor/qbe/emit.c +++ b/vendor/qbe/emit.c @@ -61,11 +61,14 @@ emitfnlnk(char *n, Lnk *l, FILE *f) void emitdat(Dat *d, FILE *f) { - static char *dtoa[] = { - [DB] = "\t.byte", - [DH] = "\t.short", - [DW] = "\t.int", - [DL] = "\t.quad" + 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 int64_t zero; char *p; @@ -75,7 +78,17 @@ emitdat(Dat *d, FILE *f) zero = 0; break; case DEnd: - if (zero != -1) { + if (d->lnk->common) { + if (zero == -1) + die("invalid common data definition"); + p = d->name[0] == '"' ? "" : T.assym; + fprintf(f, ".comm %s%s,%"PRId64, + p, d->name, zero); + if (d->lnk->align) + fprintf(f, ",%d", d->lnk->align); + fputc('\n', f); + } + else if (zero != -1) { emitlnk(d->name, d->lnk, SecBss, f); fprintf(f, "\t.fill %"PRId64",1,0\n", zero); } @@ -101,12 +114,13 @@ emitdat(Dat *d, FILE *f) else if (d->isref) { p = d->u.ref.name[0] == '"' ? "" : T.assym; fprintf(f, "%s %s%s%+"PRId64"\n", - dtoa[d->type], p, d->u.ref.name, + di[d->type].decl, p, d->u.ref.name, d->u.ref.off); } else { fprintf(f, "%s %"PRId64"\n", - dtoa[d->type], d->u.num); + di[d->type].decl, + d->u.num & di[d->type].mask); } break; } @@ -115,7 +129,7 @@ emitdat(Dat *d, FILE *f) typedef struct Asmbits Asmbits; struct Asmbits { - char bits[16]; + bits n; int size; Asmbits *link; }; @@ -123,18 +137,17 @@ struct Asmbits { static Asmbits *stash; int -stashbits(void *bits, int size) +stashbits(bits n, 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) - if (memcmp(bits, b->bits, size) == 0) + if (size <= b->size && b->n == n) return i; b = emalloc(sizeof *b); - memcpy(b->bits, bits, size); + b->n = n; b->size = size; b->link = 0; *pb = b; @@ -145,9 +158,8 @@ static void emitfin(FILE *f, char *sec[3]) { Asmbits *b; - char *p; int lg, i; - double d; + union { int32_t i; float f; } u; if (!stash) return; @@ -161,17 +173,24 @@ emitfin(FILE *f, char *sec[3]) "%sfp%d:", sec[lg-2], lg, T.asloc, i ); - 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"); + 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); + } } } while ((b=stash)) { @@ -202,12 +221,20 @@ macho_emitfin(FILE *f) static char *sec[3] = { "__TEXT,__literal4,4byte_literals", "__TEXT,__literal8,8byte_literals", - ".abort \"unreachable\"", + "__TEXT,__literal16,16byte_literals", }; 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; diff --git a/vendor/qbe/fold.c b/vendor/qbe/fold.c index 3ff0bc7..7d0b04c 100644 --- a/vendor/qbe/fold.c +++ b/vendor/qbe/fold.c @@ -1,22 +1,6 @@ #include "all.h" -enum { - Bot = -1, /* lattice bottom */ - Top = 0, /* lattice top (matches UNDEF) */ -}; - -typedef struct Edge Edge; - -struct Edge { - int dest; - int dead; - Edge *work; -}; - -static int *val; -static Edge *flowrk, (*edge)[2]; -static Use **usewrk; -static uint nuse; +/* boring folding code */ static int iscon(Con *c, int w, uint64_t k) @@ -29,305 +13,7 @@ iscon(Con *c, int w, uint64_t k) return (uint32_t)c->bits.i == (uint32_t)k; } -static int -latval(Ref r) -{ - switch (rtype(r)) { - case RTmp: - return val[r.val]; - case RCon: - return r.val; - default: - die("unreachable"); - } -} - -static int -latmerge(int v, int m) -{ - return m == Top ? v : (v == Top || v == m) ? m : Bot; -} - -static void -update(int t, int m, Fn *fn) -{ - Tmp *tmp; - uint u; - - m = latmerge(val[t], m); - if (m != val[t]) { - tmp = &fn->tmp[t]; - for (u=0; unuse; u++) { - vgrow(&usewrk, ++nuse); - usewrk[nuse-1] = &tmp->use[u]; - } - val[t] = m; - } -} - -static int -deadedge(int s, int d) -{ - Edge *e; - - e = edge[s]; - if (e[0].dest == d && !e[0].dead) - return 0; - if (e[1].dest == d && !e[1].dead) - return 0; - return 1; -} - -static void -visitphi(Phi *p, int n, Fn *fn) -{ - int v; - uint a; - - v = Top; - for (a=0; anarg; a++) - if (!deadedge(p->blk[a]->id, n)) - v = latmerge(v, latval(p->arg[a])); - update(p->to.val, v, fn); -} - -static int opfold(int, int, Con *, Con *, Fn *); - -static void -visitins(Ins *i, Fn *fn) -{ - int v, l, r; - - if (rtype(i->to) != RTmp) - return; - if (optab[i->op].canfold) { - l = latval(i->arg[0]); - if (!req(i->arg[1], R)) - r = latval(i->arg[1]); - else - r = CON_Z.val; - if (l == Bot || r == Bot) - v = Bot; - else if (l == Top || r == Top) - v = Top; - else - v = opfold(i->op, i->cls, &fn->con[l], &fn->con[r], fn); - } else - v = Bot; - /* fprintf(stderr, "\nvisiting %s (%p)", optab[i->op].name, (void *)i); */ - update(i->to.val, v, fn); -} - -static void -visitjmp(Blk *b, int n, Fn *fn) -{ - int l; - - switch (b->jmp.type) { - case Jjnz: - l = latval(b->jmp.arg); - if (l == Bot) { - edge[n][1].work = flowrk; - edge[n][0].work = &edge[n][1]; - flowrk = &edge[n][0]; - } - else if (iscon(&fn->con[l], 0, 0)) { - assert(edge[n][0].dead); - edge[n][1].work = flowrk; - flowrk = &edge[n][1]; - } - else { - assert(edge[n][1].dead); - edge[n][0].work = flowrk; - flowrk = &edge[n][0]; - } - break; - case Jjmp: - edge[n][0].work = flowrk; - flowrk = &edge[n][0]; - break; - case Jhlt: - break; - default: - if (isret(b->jmp.type)) - break; - die("unreachable"); - } -} - -static void -initedge(Edge *e, Blk *s) -{ - if (s) - e->dest = s->id; - else - e->dest = -1; - e->dead = 1; - e->work = 0; -} - -static int -renref(Ref *r) -{ - int l; - - if (rtype(*r) == RTmp) - if ((l=val[r->val]) != Bot) { - *r = CON(l); - return 1; - } - return 0; -} - -/* require rpo, use, pred */ -void -fold(Fn *fn) -{ - Edge *e, start; - Use *u; - Blk *b, **pb; - Phi *p, **pp; - Ins *i; - int t, d; - uint n, a; - - val = emalloc(fn->ntmp * sizeof val[0]); - edge = emalloc(fn->nblk * sizeof edge[0]); - usewrk = vnew(0, sizeof usewrk[0], PHeap); - - for (t=0; tntmp; t++) - val[t] = Top; - for (n=0; nnblk; n++) { - b = fn->rpo[n]; - b->visit = 0; - initedge(&edge[n][0], b->s1); - initedge(&edge[n][1], b->s2); - } - initedge(&start, fn->start); - flowrk = &start; - nuse = 0; - - /* 1. find out constants and dead cfg edges */ - for (;;) { - e = flowrk; - if (e) { - flowrk = e->work; - e->work = 0; - if (e->dest == -1 || !e->dead) - continue; - e->dead = 0; - n = e->dest; - b = fn->rpo[n]; - for (p=b->phi; p; p=p->link) - visitphi(p, n, fn); - if (b->visit == 0) { - for (i=b->ins; i<&b->ins[b->nins]; i++) - visitins(i, fn); - visitjmp(b, n, fn); - } - b->visit++; - assert(b->jmp.type != Jjmp - || !edge[n][0].dead - || flowrk == &edge[n][0]); - } - else if (nuse) { - u = usewrk[--nuse]; - n = u->bid; - b = fn->rpo[n]; - if (b->visit == 0) - continue; - switch (u->type) { - case UPhi: - visitphi(u->u.phi, u->bid, fn); - break; - case UIns: - visitins(u->u.ins, fn); - break; - case UJmp: - visitjmp(b, n, fn); - break; - default: - die("unreachable"); - } - } - else - break; - } - - if (debug['F']) { - fprintf(stderr, "\n> SCCP findings:"); - for (t=Tmp0; tntmp; t++) { - if (val[t] == Bot) - continue; - fprintf(stderr, "\n%10s: ", fn->tmp[t].name); - if (val[t] == Top) - fprintf(stderr, "Top"); - else - printref(CON(val[t]), fn, stderr); - } - fprintf(stderr, "\n dead code: "); - } - - /* 2. trim dead code, replace constants */ - d = 0; - for (pb=&fn->start; (b=*pb);) { - if (b->visit == 0) { - d = 1; - if (debug['F']) - fprintf(stderr, "%s ", b->name); - edgedel(b, &b->s1); - edgedel(b, &b->s2); - *pb = b->link; - continue; - } - for (pp=&b->phi; (p=*pp);) - if (val[p->to.val] != Bot) - *pp = p->link; - else { - for (a=0; anarg; a++) - if (!deadedge(p->blk[a]->id, b->id)) - renref(&p->arg[a]); - pp = &p->link; - } - for (i=b->ins; i<&b->ins[b->nins]; i++) - if (renref(&i->to)) - *i = (Ins){.op = Onop}; - else { - for (n=0; n<2; n++) - renref(&i->arg[n]); - if (isstore(i->op)) - if (req(i->arg[0], UNDEF)) - *i = (Ins){.op = Onop}; - } - renref(&b->jmp.arg); - if (b->jmp.type == Jjnz && rtype(b->jmp.arg) == RCon) { - if (iscon(&fn->con[b->jmp.arg.val], 0, 0)) { - edgedel(b, &b->s1); - b->s1 = b->s2; - b->s2 = 0; - } else - edgedel(b, &b->s2); - b->jmp.type = Jjmp; - b->jmp.arg = R; - } - pb = &b->link; - } - - if (debug['F']) { - if (!d) - fprintf(stderr, "(none)"); - fprintf(stderr, "\n\n> After constant folding:\n"); - printfn(fn, stderr); - } - - free(val); - free(edge); - vfree(usewrk); -} - -/* boring folding code */ - -static int +int foldint(Con *res, int op, int w, Con *cl, Con *cr) { union { @@ -516,7 +202,7 @@ foldflt(Con *res, int op, int w, Con *cl, Con *cr) } } -static int +static Ref opfold(int op, int cls, Con *cl, Con *cr, Fn *fn) { Ref r; @@ -524,12 +210,37 @@ opfold(int op, int cls, Con *cl, Con *cr, Fn *fn) if (cls == Kw || cls == Kl) { if (foldint(&c, op, cls == Kl, cl, cr)) - return Bot; + return R; } else foldflt(&c, op, cls == Kd, cl, cr); if (!KWIDE(cls)) c.bits.i &= 0xffffffff; r = newcon(&c, fn); assert(!(cls == Ks || cls == Kd) || c.flt); - return r.val; + return r; +} + +/* used by GVN */ +Ref +foldref(Fn *fn, Ins *i) +{ + Ref rr; + Con *cl, *cr; + + if (rtype(i->to) != RTmp) + return R; + if (optab[i->op].canfold) { + if (rtype(i->arg[0]) != RCon) + return R; + cl = &fn->con[i->arg[0].val]; + rr = i->arg[1]; + if (req(rr, R)) + rr = CON_Z; + if (rtype(rr) != RCon) + return R; + cr = &fn->con[rr.val]; + + return opfold(i->op, i->cls, cl, cr, fn); + } + return R; } diff --git a/vendor/qbe/gcm.c b/vendor/qbe/gcm.c new file mode 100644 index 0000000..e685d2a --- /dev/null +++ b/vendor/qbe/gcm.c @@ -0,0 +1,460 @@ +#include "all.h" + +#define NOBID (-1u) + +static int +isdivwl(Ins *i) +{ + switch (i->op) { + case Odiv: + case Orem: + case Oudiv: + case Ourem: + return KBASE(i->cls) == 0; + default: + return 0; + } +} + +int +pinned(Ins *i) +{ + return optab[i->op].pinned || isdivwl(i); +} + +/* pinned ins that can be eliminated if unused */ +static int +canelim(Ins *i) +{ + return isload(i->op) || isalloc(i->op) || isdivwl(i); +} + +static uint earlyins(Fn *, Blk *, Ins *); + +static uint +schedearly(Fn *fn, Ref r) +{ + Tmp *t; + Blk *b; + + if (rtype(r) != RTmp) + return 0; + + t = &fn->tmp[r.val]; + if (t->gcmbid != NOBID) + return t->gcmbid; + + b = fn->rpo[t->bid]; + if (t->def) { + assert(b->ins <= t->def && t->def < &b->ins[b->nins]); + t->gcmbid = 0; /* mark as visiting */ + t->gcmbid = earlyins(fn, b, t->def); + } else { + /* phis do not move */ + t->gcmbid = t->bid; + } + + return t->gcmbid; +} + +static uint +earlyins(Fn *fn, Blk *b, Ins *i) +{ + uint b0, b1; + + b0 = schedearly(fn, i->arg[0]); + assert(b0 != NOBID); + b1 = schedearly(fn, i->arg[1]); + assert(b1 != NOBID); + if (fn->rpo[b0]->depth < fn->rpo[b1]->depth) { + assert(dom(fn->rpo[b0], fn->rpo[b1])); + b0 = b1; + } + return pinned(i) ? b->id : b0; +} + +static void +earlyblk(Fn *fn, uint bid) +{ + Blk *b; + Phi *p; + Ins *i; + uint n; + + b = fn->rpo[bid]; + for (p=b->phi; p; p=p->link) + for (n=0; nnarg; n++) + schedearly(fn, p->arg[n]); + for (i=b->ins; i<&b->ins[b->nins]; i++) + if (pinned(i)) { + schedearly(fn, i->arg[0]); + schedearly(fn, i->arg[1]); + } + schedearly(fn, b->jmp.arg); +} + +/* least common ancestor in dom tree */ +static uint +lcabid(Fn *fn, uint bid1, uint bid2) +{ + Blk *b; + + if (bid1 == NOBID) + return bid2; + if (bid2 == NOBID) + return bid1; + + b = lca(fn->rpo[bid1], fn->rpo[bid2]); + assert(b); + return b->id; +} + +static uint +bestbid(Fn *fn, uint earlybid, uint latebid) +{ + Blk *curb, *earlyb, *bestb; + + if (latebid == NOBID) + return NOBID; /* unused */ + + assert(earlybid != NOBID); + + earlyb = fn->rpo[earlybid]; + bestb = curb = fn->rpo[latebid]; + assert(dom(earlyb, curb)); + + while (curb != earlyb) { + curb = curb->idom; + if (curb->loop < bestb->loop) + bestb = curb; + } + return bestb->id; +} + +static uint lateins(Fn *, Blk *, Ins *, Ref r); +static uint latephi(Fn *, Phi *, Ref r); +static uint latejmp(Blk *, Ref r); + +/* return lca bid of ref uses */ +static uint +schedlate(Fn *fn, Ref r) +{ + Tmp *t; + Blk *b; + Use *u; + uint earlybid; + uint latebid; + uint uselatebid; + + if (rtype(r) != RTmp) + return NOBID; + + t = &fn->tmp[r.val]; + if (t->visit) + return t->gcmbid; + + t->visit = 1; + earlybid = t->gcmbid; + if (earlybid == NOBID) + return NOBID; /* not used */ + + /* reuse gcmbid for late bid */ + t->gcmbid = t->bid; + latebid = NOBID; + for (u=t->use; u<&t->use[t->nuse]; u++) { + assert(u->bid < fn->nblk); + b = fn->rpo[u->bid]; + switch (u->type) { + case UXXX: + die("unreachable"); + break; + case UPhi: + uselatebid = latephi(fn, u->u.phi, r); + break; + case UIns: + uselatebid = lateins(fn, b, u->u.ins, r); + break; + case UJmp: + uselatebid = latejmp(b, r); + break; + } + latebid = lcabid(fn, latebid, uselatebid); + } + /* latebid may be NOBID if the temp is used + * in fixed instructions that may be eliminated + * and are themselves unused transitively */ + + if (t->def && !pinned(t->def)) + t->gcmbid = bestbid(fn, earlybid, latebid); + /* else, keep the early one */ + + /* now, gcmbid is the best bid */ + return t->gcmbid; +} + +/* returns lca bid of uses or NOBID if + * the definition can be eliminated */ +static uint +lateins(Fn *fn, Blk *b, Ins *i, Ref r) +{ + uint latebid; + + assert(b->ins <= i && i < &b->ins[b->nins]); + assert(req(i->arg[0], r) || req(i->arg[1], r)); + + latebid = schedlate(fn, i->to); + if (pinned(i)) { + if (latebid == NOBID) + if (canelim(i)) + return NOBID; + return b->id; + } + + return latebid; +} + +static uint +latephi(Fn *fn, Phi *p, Ref r) +{ + uint n; + uint latebid; + + if (!p->narg) + return NOBID; /* marked as unused */ + + latebid = NOBID; + for (n = 0; n < p->narg; n++) + if (req(p->arg[n], r)) + latebid = lcabid(fn, latebid, p->blk[n]->id); + + assert(latebid != NOBID); + return latebid; +} + +static uint +latejmp(Blk *b, Ref r) +{ + if (req(b->jmp.arg, R)) + return NOBID; + else { + assert(req(b->jmp.arg, r)); + return b->id; + } +} + +static void +lateblk(Fn *fn, uint bid) +{ + Blk *b; + Phi **pp; + Ins *i; + + b = fn->rpo[bid]; + for (pp=&b->phi; *(pp);) + if (schedlate(fn, (*pp)->to) == NOBID) { + (*pp)->narg = 0; /* mark unused */ + *pp = (*pp)->link; /* remove phi */ + } else + pp = &(*pp)->link; + + for (i=b->ins; i<&b->ins[b->nins]; i++) + if (pinned(i)) + schedlate(fn, i->to); +} + +static void +addgcmins(Fn *fn, Ins *vins, uint nins) +{ + Ins *i; + Tmp *t; + Blk *b; + + for (i=vins; i<&vins[nins]; i++) { + assert(rtype(i->to) == RTmp); + t = &fn->tmp[i->to.val]; + b = fn->rpo[t->gcmbid]; + addins(&b->ins, &b->nins, i); + } +} + +/* move live instructions to the + * end of their target block; use- + * before-def errors are fixed by + * schedblk */ +static void +gcmmove(Fn *fn) +{ + Tmp *t; + Ins *vins, *i; + uint nins; + + nins = 0; + vins = vnew(nins, sizeof vins[0], PFn); + + for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) { + if (t->def == 0) + continue; + if (t->bid == t->gcmbid) + continue; + i = t->def; + if (pinned(i) && !canelim(i)) + continue; + assert(rtype(i->to) == RTmp); + assert(t == &fn->tmp[i->to.val]); + if (t->gcmbid != NOBID) + addins(&vins, &nins, i); + *i = (Ins){.op = Onop}; + } + addgcmins(fn, vins, nins); +} + +/* dfs ordering */ +static Ins * +schedins(Fn *fn, Blk *b, Ins *i, Ins **pvins, uint *pnins) +{ + Ins *i0, *i1; + Tmp *t; + uint n; + + igroup(b, i, &i0, &i1); + for (i=i0; iarg[n]) != RTmp) + continue; + t = &fn->tmp[i->arg[n].val]; + if (t->bid != b->id || !t->def) + continue; + schedins(fn, b, t->def, pvins, pnins); + } + for (i=i0; istart; b; b=b->link) { + nins = 0; + for (i=b->ins; i<&b->ins[b->nins];) + i = schedins(fn, b, i, &vins, &nins); + idup(b, vins, nins); + } + vfree(vins); +} + +static int +cheap(Ins *i) +{ + int x; + + if (KBASE(i->cls) != 0) + return 0; + switch (i->op) { + case Oneg: + case Oadd: + case Osub: + case Omul: + case Oand: + case Oor: + case Oxor: + case Osar: + case Oshr: + case Oshl: + return 1; + default: + return iscmp(i->op, &x, &x); + } +} + +static void +sinkref(Fn *fn, Blk *b, Ref *pr) +{ + Ins i; + Tmp *t; + Ref r; + + if (rtype(*pr) != RTmp) + return; + t = &fn->tmp[pr->val]; + if (!t->def + || t->bid == b->id + || pinned(t->def) + || !cheap(t->def)) + return; + + /* sink t->def to b */ + i = *t->def; + r = newtmp("snk", t->cls, fn); + t = 0; /* invalidated */ + *pr = r; + i.to = r; + fn->tmp[r.val].gcmbid = b->id; + emiti(i); + sinkref(fn, b, &i.arg[0]); + sinkref(fn, b, &i.arg[1]); +} + +/* redistribute trivial ops to point of + * use to reduce register pressure + * requires rpo, use; breaks use + */ +static void +sink(Fn *fn) +{ + Blk *b; + Ins *i; + + for (b=fn->start; b; b=b->link) { + for (i=b->ins; i<&b->ins[b->nins]; i++) + if (isload(i->op)) + sinkref(fn, b, &i->arg[0]); + else if (isstore(i->op)) + sinkref(fn, b, &i->arg[1]); + sinkref(fn, b, &b->jmp.arg); + } + addgcmins(fn, curi, &insb[NIns] - curi); +} + +/* requires use dom + * maintains rpo pred dom + * breaks use + */ +void +gcm(Fn *fn) +{ + Tmp *t; + uint bid; + + filldepth(fn); + fillloop(fn); + + for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) { + t->visit = 0; + t->gcmbid = NOBID; + } + for (bid=0; bidnblk; bid++) + earlyblk(fn, bid); + for (bid=0; bidnblk; bid++) + lateblk(fn, bid); + + gcmmove(fn); + filluse(fn); + curi = &insb[NIns]; + sink(fn); + filluse(fn); + schedblk(fn); + + if (debug['G']) { + fprintf(stderr, "\n> After GCM:\n"); + printfn(fn, stderr); + } +} diff --git a/vendor/qbe/gvn.c b/vendor/qbe/gvn.c new file mode 100644 index 0000000..2417150 --- /dev/null +++ b/vendor/qbe/gvn.c @@ -0,0 +1,508 @@ +#include "all.h" + +Ref con01[2]; + +static inline uint +mix(uint x0, uint x1) +{ + return x0 + 17*x1; +} + +static inline uint +rhash(Ref r) +{ + return mix(r.type, r.val); +} + +static uint +ihash(Ins *i) +{ + uint h; + + h = mix(i->op, i->cls); + h = mix(h, rhash(i->arg[0])); + h = mix(h, rhash(i->arg[1])); + + return h; +} + +static int +ieq(Ins *ia, Ins *ib) +{ + if (ia->op == ib->op) + if (ia->cls == ib->cls) + if (req(ia->arg[0], ib->arg[0])) + if (req(ia->arg[1], ib->arg[1])) + return 1; + return 0; +} + +static Ins **gvntbl; +static uint gvntbln; + +static Ins * +gvndup(Ins *i, int insert) +{ + uint idx; + Ins *ii; + + idx = ihash(i) % gvntbln; + for (;;) { + ii = gvntbl[idx]; + if (!ii) + break; + if (ieq(i, ii)) + return ii; + + idx++; + if (gvntbln <= idx) + idx = 0; + } + if (insert) + gvntbl[idx] = i; + return 0; +} + +static void +replaceuse(Fn *fn, Use *u, Ref r1, Ref r2) +{ + Blk *b; + Ins *i; + Phi *p; + Ref *pr; + Tmp *t2; + int n; + + t2 = 0; + if (rtype(r2) == RTmp) + t2 = &fn->tmp[r2.val]; + b = fn->rpo[u->bid]; + switch (u->type) { + case UPhi: + p = u->u.phi; + for (pr=p->arg; pr<&p->arg[p->narg]; pr++) + if (req(*pr, r1)) + *pr = r2; + if (t2) + adduse(t2, UPhi, b, p); + break; + case UIns: + i = u->u.ins; + for (n=0; n<2; n++) + if (req(i->arg[n], r1)) + i->arg[n] = r2; + if (t2) + adduse(t2, UIns, b, i); + break; + case UJmp: + if (req(b->jmp.arg, r1)) + b->jmp.arg = r2; + if (t2) + adduse(t2, UJmp, b); + break; + case UXXX: + die("unreachable"); + } +} + +static void +replaceuses(Fn *fn, Ref r1, Ref r2) +{ + Tmp *t1; + Use *u; + + assert(rtype(r1) == RTmp); + t1 = &fn->tmp[r1.val]; + for (u=t1->use; u<&t1->use[t1->nuse]; u++) + replaceuse(fn, u, r1, r2); + t1->nuse = 0; +} + +static void +dedupphi(Fn *fn, Blk *b) +{ + Phi *p, **pp; + Ref r; + + for (pp=&b->phi; (p=*pp);) { + r = phicopyref(fn, b, p); + if (!req(r, R)) { + replaceuses(fn, p->to, r); + p->to = R; + *pp = p->link; + } else + pp = &p->link; + } +} + +static int +rcmp(Ref a, Ref b) +{ + if (rtype(a) != rtype(b)) + return rtype(a) - rtype(b); + return a.val - b.val; +} + +static void +normins(Fn *fn, Ins *i) +{ + uint n; + int64_t v; + Ref r; + + /* truncate constant bits to + * 32 bits for s/w uses */ + for (n=0; n<2; n++) { + if (!KWIDE(argcls(i, n))) + if (isconbits(fn, i->arg[n], &v)) + if ((v & 0xffffffff) != v) + i->arg[n] = getcon(v & 0xffffffff, fn); + } + /* order arg[0] <= arg[1] for + * commutative ops, preferring + * RTmp in arg[0] */ + if (optab[i->op].commutes) + if (rcmp(i->arg[0], i->arg[1]) > 0) { + r = i->arg[1]; + i->arg[1] = i->arg[0]; + i->arg[0] = r; + } +} + +static int +negcon(int cls, Con *c) +{ + static Con z = {.type = CBits, .bits.i = 0}; + + return foldint(c, Osub, cls, &z, c); +} + +static void +assoccon(Fn *fn, Blk *b, Ins *i1) +{ + Tmp *t2; + Ins *i2; + int op, fail; + Con c, c1, c2; + + op = i1->op; + if (op == Osub) + op = Oadd; + + if (!optab[op].assoc + || KBASE(i1->cls) != 0 + || rtype(i1->arg[0]) != RTmp + || rtype(i1->arg[1]) != RCon) + return; + c1 = fn->con[i1->arg[1].val]; + + t2 = &fn->tmp[i1->arg[0].val]; + if (t2->def == 0) + return; + i2 = t2->def; + + if (op != (i2->op == Osub ? Oadd : i2->op) + || rtype(i2->arg[1]) != RCon) + return; + c2 = fn->con[i2->arg[1].val]; + + assert(KBASE(i2->cls) == 0); + assert(KWIDE(i2->cls) >= KWIDE(i1->cls)); + + if (i1->op == Osub && negcon(i1->cls, &c1)) + return; + if (i2->op == Osub && negcon(i2->cls, &c2)) + return; + if (foldint(&c, op, i1->cls, &c1, &c2)) + return; + + if (op == Oadd && c.type == CBits) + if ((i1->cls == Kl && c.bits.i < 0) + || (i1->cls == Kw && (int32_t)c.bits.i < 0)) { + fail = negcon(i1->cls, &c); + assert(fail == 0); + op = Osub; + } + + i1->op = op; + i1->arg[0] = i2->arg[0]; + i1->arg[1] = newcon(&c, fn); + adduse(&fn->tmp[i1->arg[0].val], UIns, b, i1); +} + +static void +killins(Fn *fn, Ins *i, Ref r) +{ + replaceuses(fn, i->to, r); + *i = (Ins){.op = Onop}; +} + +static void +dedupins(Fn *fn, Blk *b, Ins *i) +{ + Ref r; + Ins *i1; + + normins(fn, 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); + + r = copyref(fn, b, i); + if (!req(r, R)) { + killins(fn, i, r); + return; + } + r = foldref(fn, i); + if (!req(r, R)) { + killins(fn, i, r); + return; + } + i1 = gvndup(i, 1); + if (i1) { + killins(fn, i, i1->to); + return; + } +} + +int +cmpeqz(Fn *fn, Ref r, Ref *arg, int *cls, int *eqval) +{ + Ins *i; + + if (rtype(r) != RTmp) + return 0; + i = fn->tmp[r.val].def; + if (i) + if (optab[i->op].cmpeqwl) + if (req(i->arg[1], CON_Z)) { + *arg = i->arg[0]; + *cls = argcls(i, 0); + *eqval = optab[i->op].eqval; + return 1; + } + return 0; +} + +static int +branchdom(Fn *fn, Blk *bif, Blk *bbr1, Blk *bbr2, Blk *b) +{ + assert(bif->jmp.type == Jjnz); + + if (b != bif + && dom(bbr1, b) + && !reachesnotvia(fn, bbr2, b, bif)) + return 1; + + return 0; +} + +static int +domzero(Fn *fn, Blk *d, Blk *b, int *z) +{ + if (branchdom(fn, d, d->s1, d->s2, b)) { + *z = 0; + return 1; + } + if (branchdom(fn, d, d->s2, d->s1, b)) { + *z = 1; + return 1; + } + return 0; +} + +/* infer 0/non-0 value from dominating jnz */ +int +zeroval(Fn *fn, Blk *b, Ref r, int cls, int *z) +{ + Blk *d; + Ref arg; + int cls1, eqval; + + for (d=b->idom; d; d=d->idom) { + if (d->jmp.type != Jjnz) + continue; + if (req(r, d->jmp.arg) + && cls == Kw + && domzero(fn, d, b, z)) { + return 1; + } + if (cmpeqz(fn, d->jmp.arg, &arg, &cls1, &eqval) + && req(r, arg) + && cls == cls1 + && domzero(fn, d, b, z)) { + *z ^= eqval; + return 1; + } + } + return 0; +} + +static int +usecls(Use *u, Ref r, int cls) +{ + int k; + + switch (u->type) { + case UIns: + k = Kx; /* widest use */ + if (req(u->u.ins->arg[0], r)) + k = argcls(u->u.ins, 0); + if (req(u->u.ins->arg[1], r)) + if (k == Kx || !KWIDE(k)) + k = argcls(u->u.ins, 1); + return k == Kx ? cls : k; + case UPhi: + if (req(u->u.phi->to, R)) + return cls; /* eliminated */ + return u->u.phi->cls; + case UJmp: + return Kw; + default: + break; + } + die("unreachable"); +} + +static void +propjnz0(Fn *fn, Blk *bif, Blk *s0, Blk *snon0, Ref r, int cls) +{ + Blk *b; + Tmp *t; + Use *u; + + if (s0->npred != 1 || rtype(r) != RTmp) + return; + t = &fn->tmp[r.val]; + for (u=t->use; u<&t->use[t->nuse]; u++) { + b = fn->rpo[u->bid]; + /* we may compare an l temp with a w + * comparison; so check that the use + * does not involve high bits */ + if (usecls(u, r, cls) == cls) + if (branchdom(fn, bif, s0, snon0, b)) + replaceuse(fn, u, r, CON_Z); + } +} + +static void +dedupjmp(Fn *fn, Blk *b) +{ + Blk **ps; + int64_t v; + Ref arg; + int cls, eqval, z; + + if (b->jmp.type != Jjnz) + return; + + /* propagate jmp arg as 0 through s2 */ + propjnz0(fn, b, b->s2, b->s1, b->jmp.arg, Kw); + /* propagate cmp eq/ne 0 def of jmp arg as 0 */ + if (cmpeqz(fn, b->jmp.arg, &arg, &cls, &eqval)) { + ps = (Blk*[]){b->s1, b->s2}; + propjnz0(fn, b, ps[eqval^1], ps[eqval], arg, cls); + } + + /* collapse trivial/constant jnz to jmp */ + v = 1; + z = 0; + if (b->s1 == b->s2 + || isconbits(fn, b->jmp.arg, &v) + || zeroval(fn, b, b->jmp.arg, Kw, &z)) { + if (v == 0 || z) + b->s1 = b->s2; + /* we later move active ins out of dead blks */ + b->s2 = 0; + b->jmp.type = Jjmp; + b->jmp.arg = R; + } +} + +static void +rebuildcfg(Fn *fn) +{ + uint n, nblk; + Blk *b, *s, **rpo; + Ins *i; + + nblk = fn->nblk; + rpo = emalloc(nblk * sizeof rpo[0]); + memcpy(rpo, fn->rpo, nblk * sizeof rpo[0]); + + fillcfg(fn); + + /* move instructions that were in + * killed blocks and may be active + * in the computation in the start + * block */ + s = fn->start; + for (n=0; nid != -1u) + continue; + /* blk unreachable after GVN */ + assert(b != s); + for (i=b->ins; i<&b->ins[b->nins]; i++) + if (!optab[i->op].pinned) + if (gvndup(i, 0) == i) + addins(&s->ins, &s->nins, i); + } + free(rpo); +} + +/* requires rpo pred ssa use + * recreates rpo preds + * breaks pred use dom ssa (GCM fixes ssa) + */ +void +gvn(Fn *fn) +{ + Blk *b; + Phi *p; + Ins *i; + uint n, nins; + + con01[0] = getcon(0, fn); + con01[1] = getcon(1, fn); + + /* copy.c uses the visit bit */ + for (b=fn->start; b; b=b->link) + for (p=b->phi; p; p=p->link) + p->visit = 0; + + fillloop(fn); + narrowpars(fn); + filluse(fn); + ssacheck(fn); + + nins = 0; + for (b=fn->start; b; b=b->link) { + b->visit = 0; + nins += b->nins; + } + + gvntbln = nins + nins/2; + gvntbl = emalloc(gvntbln * sizeof gvntbl[0]); + for (n=0; nnblk; n++) { + b = fn->rpo[n]; + dedupphi(fn, b); + for (i=b->ins; i<&b->ins[b->nins]; i++) + dedupins(fn, b, i); + dedupjmp(fn, b); + } + rebuildcfg(fn); + free(gvntbl); + gvntbl = 0; + + if (debug['G']) { + fprintf(stderr, "\n> After GVN:\n"); + printfn(fn, stderr); + } +} diff --git a/vendor/qbe/ifopt.c b/vendor/qbe/ifopt.c new file mode 100644 index 0000000..3e45f52 --- /dev/null +++ b/vendor/qbe/ifopt.c @@ -0,0 +1,121 @@ +#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); + } +} diff --git a/vendor/qbe/load.c b/vendor/qbe/load.c index 6f40cc7..d7eaccb 100644 --- a/vendor/qbe/load.c +++ b/vendor/qbe/load.c @@ -166,6 +166,23 @@ load(Slice sl, bits msk, Loc *l) return r; } +static void +rebase(Slice *sl) +{ + Alias *a; + + if (rtype(sl->ref) != RTmp) + return; + a = &curf->tmp[sl->ref.val].alias; + if (a->offset == (short)a->offset) + if (a->type == ALoc + || a->type == AEsc + || a->type == AUnk) { + sl->ref = TMP(a->base); + sl->off = a->offset; + } +} + static int killsl(Ref r, Slice sl) { @@ -380,6 +397,7 @@ def(Slice sl, bits msk, Blk *b, Ins *i, Loc *il) goto Load; p->arg[np] = r1; p->blk[np] = bp; + /* XXX - multiplicity in predecessors!!! */ } if (msk != msks) mask(cls, &r, msk, il); @@ -430,6 +448,7 @@ loadopt(Fn *fn) sz = loadsz(i); sl = (Slice){i->arg[0], 0, sz, i->cls}; l = (Loc){LRoot, i-b->ins, b}; + rebase(&sl); i->arg[1] = def(sl, MASK(sz), b, i, &l); } qsort(ilog, nlog, sizeof ilog[0], icmp); @@ -481,8 +500,7 @@ loadopt(Fn *fn) vgrow(&ib, ++nt); ib[nt-1] = *i; } - b->nins = nt; - idup(&b->ins, ib, nt); + idup(b, ib, nt); } vfree(ib); vfree(ilog); diff --git a/vendor/qbe/main.c b/vendor/qbe/main.c index 5ecb4d0..c413dc2 100644 --- a/vendor/qbe/main.c +++ b/vendor/qbe/main.c @@ -10,7 +10,8 @@ char debug['Z'+1] = { ['M'] = 0, /* memory optimization */ ['N'] = 0, /* ssa construction */ ['C'] = 0, /* copy elimination */ - ['F'] = 0, /* constant folding */ + ['G'] = 0, /* gvn/gcm */ + ['K'] = 0, /* if-conversion */ ['A'] = 0, /* abi lowering */ ['I'] = 0, /* instruction selection */ ['L'] = 0, /* liveness */ @@ -20,6 +21,7 @@ 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; @@ -27,6 +29,7 @@ extern Target T_rv64; static Target *tlist[] = { &T_amd64_sysv, &T_amd64_apple, + &T_amd64_win, &T_arm64, &T_arm64_apple, &T_rv64, @@ -59,8 +62,7 @@ func(Fn *fn) printfn(fn, stderr); } T.abi0(fn); - fillrpo(fn); - fillpreds(fn); + fillcfg(fn); filluse(fn); promote(fn); filluse(fn); @@ -73,25 +75,37 @@ func(Fn *fn) fillalias(fn); coalesce(fn); filluse(fn); + filldom(fn); ssacheck(fn); - copy(fn); + gvn(fn); + fillcfg(fn); + simplcfg(fn); filluse(fn); - fold(fn); + filldom(fn); + gcm(fn); + filluse(fn); + ssacheck(fn); + if (T.cansel) { + ifconvert(fn); + fillcfg(fn); + filluse(fn); + filldom(fn); + ssacheck(fn); + } T.abi1(fn); simpl(fn); - fillpreds(fn); + fillcfg(fn); filluse(fn); T.isel(fn); - fillrpo(fn); + fillcfg(fn); filllive(fn); fillloop(fn); fillcost(fn); spill(fn); rega(fn); - fillrpo(fn); + fillcfg(fn); simpljmp(fn); - fillpreds(fn); - fillrpo(fn); + fillcfg(fn); assert(fn->rpo[0] == fn->start); for (n=0;; n++) if (n == fn->nblk-1) { diff --git a/vendor/qbe/mem.c b/vendor/qbe/mem.c index 944cd2f..3265b39 100644 --- a/vendor/qbe/mem.c +++ b/vendor/qbe/mem.c @@ -296,7 +296,7 @@ coalesce(Fn *fn) if (s->l) { radd(&s->r, ip); if (b->loop != -1) { - assert(b->loop > n); + assert(b->loop >= n); radd(&s->r, br[b->loop].b - 1); } } @@ -447,10 +447,15 @@ coalesce(Fn *fn) if (i->op == Oblit0) if (slot(&s, &off0, i->arg[0], fn, sl)) if (slot(&s0, &off1, i->arg[1], fn, sl)) - if (s->s == s0->s && off0 < off1) { - sz = rsval((i+1)->arg[0]); - assert(sz >= 0); - (i+1)->arg[0] = INT(-sz); + if (s->s == s0->s) { + if (off0 < off1) { + sz = rsval((i+1)->arg[0]); + assert(sz >= 0); + (i+1)->arg[0] = INT(-sz); + } else if (off0 == off1) { + *i = (Ins){.op = Onop}; + *(i+1) = (Ins){.op = Onop}; + } } } vfree(bl); diff --git a/vendor/qbe/minic/minic.y b/vendor/qbe/minic/minic.y index eeef900..448f38a 100644 --- a/vendor/qbe/minic/minic.y +++ b/vendor/qbe/minic/minic.y @@ -70,7 +70,7 @@ struct Stmt { int yylex(void), yyerror(char *); Symb expr(Node *), lval(Node *); -void bool(Node *, int, int); +void branch(Node *, int, int); FILE *of; int line; @@ -329,7 +329,7 @@ expr(Node *n) case 'a': l = lbl; lbl += 3; - bool(n, l, l+1); + branch(n, l, l+1); fprintf(of, "@l%d\n", l); fprintf(of, "\tjmp @l%d\n", l+2); fprintf(of, "@l%d\n", l+1); @@ -468,7 +468,7 @@ lval(Node *n) } void -bool(Node *n, int lt, int lf) +branch(Node *n, int lt, int lf) { Symb s; int l; @@ -483,16 +483,16 @@ bool(Node *n, int lt, int lf) case 'o': l = lbl; lbl += 1; - bool(n->l, lt, l); + branch(n->l, lt, l); fprintf(of, "@l%d\n", l); - bool(n->r, lt, lf); + branch(n->r, lt, lf); break; case 'a': l = lbl; lbl += 1; - bool(n->l, l, lf); + branch(n->l, l, lf); fprintf(of, "@l%d\n", l); - bool(n->r, lt, lf); + branch(n->r, lt, lf); break; } } @@ -526,7 +526,7 @@ stmt(Stmt *s, int b) case If: l = lbl; lbl += 3; - bool(s->p1, l, l+1); + branch(s->p1, l, l+1); fprintf(of, "@l%d\n", l); if (!(r=stmt(s->p2, b))) if (s->p3) @@ -540,7 +540,7 @@ stmt(Stmt *s, int b) l = lbl; lbl += 3; fprintf(of, "@l%d\n", l); - bool(s->p1, l+1, l+2); + branch(s->p1, l+1, l+2); fprintf(of, "@l%d\n", l+1); if (!stmt(s->p2, l+2)) fprintf(of, "\tjmp @l%d\n", l); diff --git a/vendor/qbe/ops.h b/vendor/qbe/ops.h index 8ccf80d..f59e757 100644 --- a/vendor/qbe/ops.h +++ b/vendor/qbe/ops.h @@ -6,184 +6,217 @@ #define V(Imm) #endif +#ifndef F +#define F(a,b,c,d,e,f,g,h,i,j) +#endif #define T(a,b,c,d,e,f,g,h) { \ {[Kw]=K##a, [Kl]=K##b, [Ks]=K##c, [Kd]=K##d}, \ {[Kw]=K##e, [Kl]=K##f, [Ks]=K##g, [Kd]=K##h} \ } - /*********************/ /* PUBLIC OPERATIONS */ /*********************/ -/* Arithmetic and Bits */ -O(add, T(w,l,s,d, w,l,s,d), 1) X(2, 1, 0) V(1) -O(sub, T(w,l,s,d, w,l,s,d), 1) X(2, 1, 0) V(0) -O(neg, T(w,l,s,d, x,x,x,x), 1) X(1, 1, 0) V(0) -O(div, T(w,l,s,d, w,l,s,d), 1) X(0, 0, 0) V(0) -O(rem, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0) -O(udiv, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0) -O(urem, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0) -O(mul, T(w,l,s,d, w,l,s,d), 1) X(2, 0, 0) V(0) -O(and, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1) -O(or, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1) -O(xor, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1) -O(sar, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1) -O(shr, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1) -O(shl, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1) +/* can fold */ +/* | has identity */ +/* | | identity value for arg[1] */ +/* | | | commutative */ +/* | | | | associative */ +/* | | | | | idempotent */ +/* | | | | | | c{eq,ne}[wl] */ +/* | | | | | | | c[us][gl][et][wl] */ +/* | | | | | | | | value if = args */ +/* | | | | | | | | | pinned */ +/* Arithmetic and Bits v v v v v v v v v v */ +O(add, T(w,l,s,d, w,l,s,d), F(1,1,0,1,1,0,0,0,0,0)) X(2,1,0) V(1) +O(sub, T(w,l,s,d, w,l,s,d), F(1,1,0,0,0,0,0,0,0,0)) X(2,1,0) V(0) +O(neg, T(w,l,s,d, x,x,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0) +O(div, T(w,l,s,d, w,l,s,d), F(1,1,1,0,0,0,0,0,0,0)) X(0,0,0) V(0) +O(rem, T(w,l,e,e, w,l,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0) +O(udiv, T(w,l,e,e, w,l,e,e), F(1,1,1,0,0,0,0,0,0,0)) X(0,0,0) V(0) +O(urem, T(w,l,e,e, w,l,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0) +O(mul, T(w,l,s,d, w,l,s,d), F(1,1,1,1,0,0,0,0,0,0)) X(2,0,0) V(0) +O(and, T(w,l,e,e, w,l,e,e), F(1,0,0,1,1,1,0,0,0,0)) X(2,1,0) V(1) +O(or, T(w,l,e,e, w,l,e,e), F(1,1,0,1,1,1,0,0,0,0)) X(2,1,0) V(1) +O(xor, T(w,l,e,e, w,l,e,e), F(1,1,0,1,1,0,0,0,0,0)) X(2,1,0) V(1) +O(sar, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1) +O(shr, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1) +O(shl, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1) /* Comparisons */ -O(ceqw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(cnew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(csgew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(csgtw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(cslew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(csltw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(1) -O(cugew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(cugtw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(culew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0) -O(cultw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(1) +O(ceqw, T(w,w,e,e, w,w,e,e), F(1,1,1,1,0,0,1,0,1,0)) X(0,1,0) V(0) +O(cnew, T(w,w,e,e, w,w,e,e), F(1,1,0,1,0,0,1,0,0,0)) X(0,1,0) V(0) +O(csgew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(csgtw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0) +O(cslew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(csltw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1) +O(cugew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(cugtw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0) +O(culew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(cultw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1) -O(ceql, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(cnel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(csgel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(csgtl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(cslel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(csltl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(1) -O(cugel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(cugtl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(culel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0) -O(cultl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(1) +O(ceql, T(l,l,e,e, l,l,e,e), F(1,0,0,1,0,0,1,0,1,0)) X(0,1,0) V(0) +O(cnel, T(l,l,e,e, l,l,e,e), F(1,0,0,1,0,0,1,0,0,0)) X(0,1,0) V(0) +O(csgel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(csgtl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0) +O(cslel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(csltl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1) +O(cugel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(cugtl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0) +O(culel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0) +O(cultl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1) -O(ceqs, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cges, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cgts, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cles, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(clts, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cnes, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cos, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) -O(cuos, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0) +O(ceqs, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cges, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cgts, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cles, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(clts, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cnes, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cos, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cuos, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) -O(ceqd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cged, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cgtd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cled, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cltd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cned, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cod, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) -O(cuod, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0) +O(ceqd, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cged, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cgtd, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cled, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cltd, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cned, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cod, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) +O(cuod, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0) /* Memory */ -O(storeb, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) -O(storeh, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) -O(storew, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) -O(storel, T(l,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) -O(stores, T(s,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) -O(stored, T(d,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0) +O(storeb, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(storeh, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(storew, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(storel, T(l,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(stores, T(s,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(stored, T(d,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) -O(loadsb, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(loadub, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(loadsh, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(loaduh, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(loadsw, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(loaduw, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(load, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 1) V(0) +O(loadsb, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(loadub, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(loadsh, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(loaduh, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(loadsw, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(loaduw, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) +O(load, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) /* Extensions and Truncations */ -O(extsb, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(extub, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(extsh, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(extuh, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(extsw, T(e,w,e,e, e,x,e,e), 1) X(0, 0, 1) V(0) -O(extuw, T(e,w,e,e, e,x,e,e), 1) X(0, 0, 1) V(0) +O(extsb, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(extub, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(extsh, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(extuh, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(extsw, T(e,w,e,e, e,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(extuw, T(e,w,e,e, e,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) -O(exts, T(e,e,e,s, e,e,e,x), 1) X(0, 0, 1) V(0) -O(truncd, T(e,e,d,e, e,e,x,e), 1) X(0, 0, 1) V(0) -O(stosi, T(s,s,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(stoui, T(s,s,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(dtosi, T(d,d,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(dtoui, T(d,d,e,e, x,x,e,e), 1) X(0, 0, 1) V(0) -O(swtof, T(e,e,w,w, e,e,x,x), 1) X(0, 0, 1) V(0) -O(uwtof, T(e,e,w,w, e,e,x,x), 1) X(0, 0, 1) V(0) -O(sltof, T(e,e,l,l, e,e,x,x), 1) X(0, 0, 1) V(0) -O(ultof, T(e,e,l,l, e,e,x,x), 1) X(0, 0, 1) V(0) -O(cast, T(s,d,w,l, x,x,x,x), 1) X(0, 0, 1) V(0) +O(exts, T(e,e,e,s, e,e,e,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(truncd, T(e,e,d,e, e,e,x,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(stosi, T(s,s,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(stoui, T(s,s,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(dtosi, T(d,d,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(dtoui, T(d,d,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(swtof, T(e,e,w,w, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(uwtof, T(e,e,w,w, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(sltof, T(e,e,l,l, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(ultof, T(e,e,l,l, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) +O(cast, T(s,d,w,l, x,x,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) /* Stack Allocation */ -O(alloc4, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(alloc8, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(alloc16, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) +O(alloc4, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(alloc8, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(alloc16, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) /* Variadic Function Helpers */ -O(vaarg, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 0) V(0) -O(vastart, T(m,e,e,e, x,e,e,e), 0) X(0, 0, 0) V(0) +O(vaarg, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(vastart, T(m,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(copy, T(w,l,s,d, x,x,x,x), 0) X(0, 0, 1) V(0) +O(copy, T(w,l,s,d, x,x,x,x), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0) /* Debug */ -O(dbgloc, T(w,e,e,e, w,e,e,e), 0) X(0, 0, 1) V(0) +O(dbgloc, T(w,e,e,e, w,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0) /****************************************/ /* INTERNAL OPERATIONS (keep nop first) */ /****************************************/ /* Miscellaneous and Architecture-Specific Operations */ -O(nop, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 1) V(0) -O(addr, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(blit0, T(m,e,e,e, m,e,e,e), 0) X(0, 1, 0) V(0) -O(blit1, T(w,e,e,e, x,e,e,e), 0) X(0, 1, 0) V(0) -O(swap, T(w,l,s,d, w,l,s,d), 0) X(1, 0, 0) V(0) -O(sign, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0) -O(salloc, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(xidiv, T(w,l,e,e, x,x,e,e), 0) X(1, 0, 0) V(0) -O(xdiv, T(w,l,e,e, x,x,e,e), 0) X(1, 0, 0) V(0) -O(xcmp, T(w,l,s,d, w,l,s,d), 0) X(1, 1, 0) V(0) -O(xtest, T(w,l,e,e, w,l,e,e), 0) X(1, 1, 0) V(0) -O(acmp, T(w,l,e,e, w,l,e,e), 0) X(0, 0, 0) V(0) -O(acmn, T(w,l,e,e, w,l,e,e), 0) X(0, 0, 0) V(0) -O(afcmp, T(e,e,s,d, e,e,s,d), 0) X(0, 0, 0) V(0) -O(reqz, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0) -O(rnez, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0) +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) +O(xidiv, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0) +O(xdiv, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0) +O(xcmp, T(w,l,s,d, w,l,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0) +O(xtest, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0) +O(acmp, 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(acmn, 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(afcmp, 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(reqz, 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(rnez, 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) /* Arguments, Parameters, and Calls */ -O(par, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(parsb, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(parub, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(parsh, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(paruh, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(parc, T(e,x,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(pare, T(e,x,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(arg, T(w,l,s,d, x,x,x,x), 0) X(0, 0, 0) V(0) -O(argsb, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0) -O(argub, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0) -O(argsh, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0) -O(arguh, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0) -O(argc, T(e,x,e,e, e,l,e,e), 0) X(0, 0, 0) V(0) -O(arge, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0) -O(argv, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0) -O(call, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 0) V(0) +O(par, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(parsb, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(parub, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(parsh, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(paruh, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(parc, T(e,x,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(pare, T(e,x,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(arg, T(w,l,s,d, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(argsb, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(argub, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(argsh, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(arguh, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(argc, T(e,x,e,e, e,l,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(arge, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(argv, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) +O(call, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0) /* Flags Setting */ -O(flagieq, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagine, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagisge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagisgt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagisle, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagislt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagiuge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagiugt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagiule, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagiult, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfeq, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfgt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfle, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagflt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfne, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfo, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) -O(flagfuo, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0) +O(flagieq, 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(flagine, 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(flagisge, 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(flagisgt, 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(flagisle, 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(flagislt, 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(flagiuge, 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(flagiugt, 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(flagiule, 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(flagiult, 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(flagfeq, 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(flagfge, 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(flagfgt, 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(flagfle, 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(flagflt, 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(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 diff --git a/vendor/qbe/parse.c b/vendor/qbe/parse.c index 738ec5b..5d8d039 100644 --- a/vendor/qbe/parse.c +++ b/vendor/qbe/parse.c @@ -15,8 +15,17 @@ enum { }; Op optab[NOp] = { -#define O(op, t, cf) [O##op]={#op, t, cf}, +#undef F +#define F(cf, hi, id, co, as, im, ic, lg, cv, pn) \ + .canfold = cf, \ + .hasid = hi, .idval = id, \ + .commutes = co, .assoc = as, \ + .idemp = im, \ + .cmpeqwl = ic, .cmplgtewl = lg, .eqval = cv, \ + .pinned = pn +#define O(op, k, flags) [O##op]={.name = #op, .argcls = k, flags}, #include "ops.h" +#undef F }; typedef enum { @@ -48,6 +57,8 @@ enum Token { Thlt, Texport, Tthread, + Textern, + Tcommon, Tfunc, Ttype, Tdata, @@ -106,6 +117,8 @@ static char *kwmap[Ntok] = { [Thlt] = "hlt", [Texport] = "export", [Tthread] = "thread", + [Textern] = "extern", + [Tcommon] = "common", [Tfunc] = "function", [Ttype] = "type", [Tdata] = "data", @@ -132,7 +145,7 @@ enum { TMask = 16383, /* for temps hash */ BMask = 8191, /* for blocks hash */ - K = 9583425, /* found using tools/lexh.c */ + K = 11183273, /* found using tools/lexh.c */ M = 23, }; @@ -150,7 +163,8 @@ static struct { static int lnum; static Fn *curf; -static int tmph[TMask+1]; +static int *tmph; +static int tmphcap; static Phi **plink; static Blk *curb; static Blk **blink; @@ -203,12 +217,15 @@ getint() n = 0; c = fgetc(inf); m = (c == '-'); - if (m || c == '+') + if (m) { c = fgetc(inf); + if (!isdigit(c)) + err("integer expected"); + } do { n = 10*n + (c - '0'); c = fgetc(inf); - } while ('0' <= c && c <= '9'); + } while (isdigit(c)); ungetc(c, inf); if (m) n = 1 + ~n; @@ -218,7 +235,6 @@ getint() static int lex() { - static char tok[NString]; int c, i, esc; int t; @@ -277,7 +293,7 @@ lex() lnum++; return Tnl; } - if (isdigit(c) || c == '-' || c == '+') { + if (isdigit(c) || c == '-') { ungetc(c, inf); tokval.num = getint(); return Tint; @@ -285,7 +301,6 @@ lex() if (c == '"') { t = Tstr; Quoted: - tokval.str = vnew(2, 1, PFn); tokval.str[0] = c; esc = 0; for (i=1;; i++) { @@ -306,20 +321,18 @@ Alpha: err("invalid character %c (%d)", c, c); i = 0; do { - if (i >= NString-1) - err("identifier too long"); - tok[i++] = c; + vgrow(&tokval.str, i+2); + tokval.str[i++] = c; c = fgetc(inf); } while (isalpha(c) || c == '$' || c == '.' || c == '_' || isdigit(c)); - tok[i] = 0; + tokval.str[i] = 0; ungetc(c, inf); - tokval.str = tok; if (t != Txxx) { return t; } - t = lexh[hash(tok)*K >> M]; - if (t == Txxx || strcmp(kwmap[t], tok) != 0) { - err("unknown keyword %s", tok); + t = lexh[hash(tokval.str)*K >> M]; + if (t == Txxx || strcmp(kwmap[t], tokval.str) != 0) { + err("unknown keyword %s", tokval.str); return Txxx; } return t; @@ -380,23 +393,31 @@ expect(int t) } static Ref -tmpref(char *v) +tmpref() { - int t, *h; + int t, i; - h = &tmph[hash(v) & TMask]; - t = *h; - if (t) { - if (strcmp(curf->tmp[t].name, v) == 0) + if (tmphcap/2 <= curf->ntmp-Tmp0) { + free(tmph); + tmphcap = tmphcap ? tmphcap*2 : TMask+1; + tmph = emalloc(tmphcap * sizeof tmph[0]); + for (t=Tmp0; tntmp; t++) { + i = hash(curf->tmp[t].name) & (tmphcap-1); + for (; tmph[i]; i=(i+1) & (tmphcap-1)) + ; + tmph[i] = t; + } + } + i = hash(tokval.str) & (tmphcap-1); + for (; tmph[i]; i=(i+1) & (tmphcap-1)) { + t = tmph[i]; + if (strcmp(curf->tmp[t].name, tokval.str) == 0) return TMP(t); - for (t=curf->ntmp-1; t>=Tmp0; t--) - if (strcmp(curf->tmp[t].name, v) == 0) - return TMP(t); } t = curf->ntmp; - *h = t; + tmph[i] = t; newtmp(0, Kx, curf); - strcpy(curf->tmp[t].name, v); + curf->tmp[t].name = strf(PFn, "%s", tokval.str); return TMP(t); } @@ -404,13 +425,12 @@ static Ref parseref() { Con c; + int tok; memset(&c, 0, sizeof c); - switch (next()) { - default: - return R; + switch ((tok = next())) { case Ttmp: - return tmpref(tokval.str); + return tmpref(); case Tint: c.type = CBits; c.bits.i = tokval.num; @@ -425,9 +445,20 @@ parseref() c.bits.d = tokval.fltd; c.flt = 2; break; - case Tthread: - c.sym.type = SThr; - expect(Tglo); + 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; /* fall through */ case Tglo: c.type = CAddr; @@ -548,18 +579,18 @@ parserefl(int arg) } static Blk * -findblk(char *name) +findblk() { Blk *b; uint32_t h; - h = hash(name) & BMask; + h = hash(tokval.str) & BMask; for (b=blkh[h]; b; b=b->dlink) - if (strcmp(b->name, name) == 0) + if (strcmp(b->name, tokval.str) == 0) return b; b = newblk(); b->id = nblk++; - strcpy(b->name, name); + b->name = strf(PFn, "%s", tokval.str); b->dlink = blkh[h]; blkh[h] = b; return b; @@ -568,8 +599,7 @@ findblk(char *name) static void closeblk() { - curb->nins = curi - insb; - idup(&curb->ins, insb, curb->nins); + idup(curb, insb, curi-insb); blink = &curb->link; curi = insb; } @@ -590,7 +620,7 @@ parseline(PState ps) err("label or } expected"); switch (t) { case Ttmp: - r = tmpref(tokval.str); + r = tmpref(); expect(Teq); k = parsecls(&ty); op = next(); @@ -610,7 +640,7 @@ parseline(PState ps) case Trbrace: return PEnd; case Tlbl: - b = findblk(tokval.str); + b = findblk(); if (curb && curb->jmp.type == Jxxx) { closeblk(); curb->jmp.type = Jjmp; @@ -646,11 +676,11 @@ parseline(PState ps) expect(Tcomma); Jump: expect(Tlbl); - curb->s1 = findblk(tokval.str); + curb->s1 = findblk(); if (curb->jmp.type != Jjmp) { expect(Tcomma); expect(Tlbl); - curb->s2 = findblk(tokval.str); + curb->s2 = findblk(); } if (curb->s1 == curf->start || curb->s2 == curf->start) err("invalid jump to the start block"); @@ -680,6 +710,7 @@ parseline(PState ps) goto Ins; } if (op == Tcall) { + curf->leaf = 0; arg[0] = parseref(); parserefl(1); op = Ocall; @@ -709,7 +740,7 @@ parseline(PState ps) err("too many arguments"); if (op == Tphi) { expect(Tlbl); - blk[i] = findblk(tokval.str); + blk[i] = findblk(); } arg[i] = parseref(); if (req(arg[i], R)) @@ -896,6 +927,7 @@ parsefn(Lnk *lnk) curf->con[0].bits.i = 0xdeaddead; /* UNDEF */ curf->con[1].type = CBits; curf->lnk = *lnk; + curf->leaf = 1; blink = &curf->start; curf->retty = Kx; if (peek() != Tglo) @@ -904,7 +936,7 @@ parsefn(Lnk *lnk) rcls = K0; if (next() != Tglo) err("function name expected"); - strncpy(curf->name, tokval.str, NString-1); + curf->name = strf(PFn, "%s", tokval.str); curf->vararg = parserefl(0); if (nextnl() != Tlbrace) err("function body must start with {"); @@ -919,12 +951,12 @@ parsefn(Lnk *lnk) curf->mem = vnew(0, sizeof curf->mem[0], PFn); curf->nmem = 0; curf->nblk = nblk; - curf->rpo = 0; - for (b=0; b; b=b->link) + curf->rpo = vnew(nblk, sizeof curf->rpo[0], PFn); + for (b=curf->start; b; b=b->link) b->dlink = 0; /* was trashed by findblk() */ for (i=0; isize = 0; if (nextnl() != Ttyp || nextnl() != Teq) err("type name and then = expected"); - strcpy(ty->name, tokval.str); + ty->name = strf(PHeap, "%s", tokval.str); t = nextnl(); if (t == Talign) { if (nextnl() != Tint) @@ -1058,7 +1090,7 @@ parsedatref(Dat *d) int t; d->isref = 1; - d->u.ref.name = tokval.str; + d->u.ref.name = strf(PFn, "%s", tokval.str); d->u.ref.off = 0; t = peek(); if (t == Tplus) { @@ -1073,24 +1105,27 @@ static void parsedatstr(Dat *d) { d->isstr = 1; - d->u.str = tokval.str; + d->u.str = strf(PFn, "%s", tokval.str); } static void parsedat(void cb(Dat *), Lnk *lnk) { - char name[NString] = {0}; + char *name; int t; Dat d; if (nextnl() != Tglo || nextnl() != Teq) err("data name, then = expected"); - strncpy(name, tokval.str, NString-1); + name = strf(PFn, "%s", tokval.str); t = nextnl(); lnk->align = 8; if (t == Talign) { if (nextnl() != Tint) err("alignment expected"); + if (tokval.num <= 0 || tokval.num > CHAR_MAX + || (tokval.num & (tokval.num-1)) != 0) + err("invalid alignment"); lnk->align = tokval.num; t = nextnl(); } @@ -1156,15 +1191,18 @@ parselnk(Lnk *lnk) case Tthread: lnk->thread = 1; break; + case Tcommon: + lnk->common = 1; + break; case Tsection: if (lnk->sec) err("only one section allowed"); if (next() != Tstr) err("section \"name\" expected"); - lnk->sec = tokval.str; + lnk->sec = strf(PFn, "%s", tokval.str); if (peek() == Tstr) { next(); - lnk->secf = tokval.str; + lnk->secf = strf(PFn, "%s", tokval.str); } break; default: @@ -1189,6 +1227,7 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn thead = Txxx; ntyp = 0; typ = vnew(0, sizeof typ[0], PHeap); + tokval.str = vnew(128, 1, PHeap); for (;;) { lnk = (Lnk){0}; switch (parselnk(&lnk)) { @@ -1199,6 +1238,7 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn dbgfile(tokval.str); break; case Tfunc: + lnk.align = 16; func(parsefn(&lnk)); break; case Tdata: @@ -1208,9 +1248,11 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn parsetyp(); break; case Teof: - for (n=0; nsym.type == SThr) + if (c->sym.type & SExt) + fprintf(f, "extern "); + if (c->sym.type & SThr) fprintf(f, "thread "); fprintf(f, "$%s", str(c->sym.id)); if (c->bits.i) @@ -1294,6 +1338,9 @@ printref(Ref r, Fn *fn, FILE *f) case RInt: fprintf(f, "%d", rsval(r)); break; + case -1: + fprintf(f, "R"); + break; } } diff --git a/vendor/qbe/rega.c b/vendor/qbe/rega.c index 8e601c9..4427142 100644 --- a/vendor/qbe/rega.c +++ b/vendor/qbe/rega.c @@ -15,13 +15,17 @@ struct RMap { int n; }; +enum { + NPm = 64, /* max copies in a parallel move */ +}; + static bits regu; /* registers used */ static Tmp *tmp; /* function temporaries */ static Mem *mem; /* function mem references */ static struct { Ref src, dst; int cls; -} pm[Tmp0]; /* parallel move constructed */ +} pm[NPm]; /* parallel move constructed */ static int npm; /* size of pm */ static int loop; /* current loop level */ @@ -190,8 +194,8 @@ mdump(RMap *m) static void pmadd(Ref src, Ref dst, int k) { - if (npm == Tmp0) - die("cannot have more moves than registers"); + if (npm == NPm) + die("no more pm slots"); pm[npm].src = src; pm[npm].dst = dst; pm[npm].cls = k; @@ -439,8 +443,7 @@ doblk(Blk *b, RMap *cur) * the above loop must be changed */ } } - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } /* qsort() comparison function to peel @@ -670,11 +673,10 @@ rega(Fn *fn) b1->link = blist; blist = b1; fn->nblk++; - strf(b1->name, "%s_%s", b->name, s->name); - b1->nins = &insb[NIns] - curi; - stmov += b1->nins; + b1->name = strf(PFn, "%s_%s", b->name, s->name); + stmov += &insb[NIns]-curi; stblk += 1; - idup(&b1->ins, curi, b1->nins); + idup(b1, curi, &insb[NIns]-curi); b1->jmp.type = Jjmp; b1->s1 = s; **ps = b1; diff --git a/vendor/qbe/rv64/abi.c b/vendor/qbe/rv64/abi.c index e31425c..1c51bfc 100644 --- a/vendor/qbe/rv64/abi.c +++ b/vendor/qbe/rv64/abi.c @@ -587,9 +587,9 @@ void rv64_abi(Fn *fn) { Blk *b; - Ins *i, *i0, *ip; + Ins *i, *i0; Insl *il; - int n; + int n0, n1, ioff; Params p; for (b=fn->start; b; b=b->link) @@ -600,12 +600,13 @@ rv64_abi(Fn *fn) if (!ispar(i->op)) break; p = selpar(fn, b->ins, i); - n = b->nins - (i - b->ins) + (&insb[NIns] - curi); - i0 = alloc(n * sizeof(Ins)); - ip = icpy(ip = i0, curi, &insb[NIns] - curi); - ip = icpy(ip, i, &b->ins[b->nins] - i); - b->nins = n; - b->ins = i0; + n0 = &insb[NIns] - curi; + ioff = i - b->ins; + n1 = b->nins - ioff; + vgrow(&b->ins, n0+n1); + icpy(b->ins+n0, b->ins+ioff, n1); + icpy(b->ins, curi, n0); + b->nins = n0+n1; /* lower calls, returns, and vararg instructions */ il = 0; @@ -642,8 +643,7 @@ rv64_abi(Fn *fn) if (b == fn->start) for (; il; il=il->link) emiti(il->i); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } while (b != fn->start); if (debug['A']) { diff --git a/vendor/qbe/rv64/emit.c b/vendor/qbe/rv64/emit.c index a410ddf..f4b3236 100644 --- a/vendor/qbe/rv64/emit.c +++ b/vendor/qbe/rv64/emit.c @@ -8,7 +8,7 @@ enum { static struct { short op; short cls; - char *asm; + char *fmt; } omap[] = { { Oadd, Ki, "add%k %=, %0, %1" }, { Oadd, Ka, "fadd.%k %=, %0, %1" }, @@ -131,7 +131,7 @@ slot(Ref r, Fn *fn) static void emitaddr(Con *c, FILE *f) { - assert(c->sym.type == SGlo); + assert((c->sym.type & ~SExt) == SGlo); fputs(str(c->sym.id), f); if (c->bits.i) fprintf(f, "+%"PRIi64, c->bits.i); @@ -231,7 +231,20 @@ loadaddr(Con *c, char *rn, FILE *f) { char off[32]; - if (c->sym.type == SThr) { + 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->bits.i) sprintf(off, "+%"PRIi64, c->bits.i); else @@ -242,10 +255,9 @@ 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); - } else { - fprintf(f, "\tla %s, ", rn); - emitaddr(c, f); - fputc('\n', f); + break; + case SExtThr: + die("extern thread unavailable on rv64"); } } @@ -282,7 +294,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 == SThr) { + if (c->sym.type != SGlo) { loadcon(c, T6, Kl, f); *pr = TMP(T6); } @@ -326,7 +338,7 @@ emitins(Ins *i, Fn *fn, FILE *f) || (omap[o].cls == Ki && KBASE(i->cls) == 0)) break; } - emitf(omap[o].asm, i, fn, f); + emitf(omap[o].fmt, i, fn, f); break; case Ocopy: if (req(i->to, i->arg[0])) @@ -387,7 +399,7 @@ emitins(Ins *i, Fn *fn, FILE *f) case RCon: con = &fn->con[i->arg[0].val]; if (con->type != CAddr - || con->sym.type != SGlo + || (con->sym.type & SThr) || con->bits.i) goto Invalid; fprintf(f, "\tcall %s\n", str(con->sym.id)); @@ -444,7 +456,7 @@ rv64_emitfn(Fn *fn, FILE *f) static int id0; int lbl, neg, off, frame, *pr, r; Blk *b, *s; - Ins *i; + Ins *i, ii; emitfnlnk(fn->name, &fn->lnk, f); @@ -549,6 +561,11 @@ 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", diff --git a/vendor/qbe/rv64/isel.c b/vendor/qbe/rv64/isel.c index 8921a07..ca62359 100644 --- a/vendor/qbe/rv64/isel.c +++ b/vendor/qbe/rv64/isel.c @@ -31,6 +31,7 @@ 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; @@ -41,7 +42,7 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn) * immediates */ assert(c->type == CBits); - n = stashbits(&c->bits, KWIDE(k) ? 8 : 4); + n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4); vgrow(&fn->con, ++fn->ncon); c = &fn->con[fn->ncon-1]; sprintf(buf, "\"%sfp%d\"", T.asloc, n); @@ -244,8 +245,7 @@ rv64_isel(Fn *fn) seljmp(b, fn); for (i=&b->ins[b->nins]; i!=b->ins;) sel(*--i, fn); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } if (debug['I']) { diff --git a/vendor/qbe/rv64/targ.c b/vendor/qbe/rv64/targ.c index c0e5e18..fc6632c 100644 --- a/vendor/qbe/rv64/targ.c +++ b/vendor/qbe/rv64/targ.c @@ -50,6 +50,7 @@ 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)); diff --git a/vendor/qbe/simpl.c b/vendor/qbe/simpl.c index 7001301..3774f29 100644 --- a/vendor/qbe/simpl.c +++ b/vendor/qbe/simpl.c @@ -30,16 +30,43 @@ blit(Ref sd[2], int sz, Fn *fn) } } +static int +ulog2_tab64[64] = { + 63, 0, 1, 41, 37, 2, 16, 42, + 38, 29, 32, 3, 12, 17, 43, 55, + 39, 35, 30, 53, 33, 21, 4, 23, + 13, 9, 18, 6, 25, 44, 48, 56, + 62, 40, 36, 15, 28, 31, 11, 54, + 34, 52, 20, 22, 8, 5, 24, 47, + 61, 14, 27, 10, 51, 19, 7, 46, + 60, 26, 50, 45, 59, 49, 58, 57, +}; + +static int +ulog2(uint64_t pow2) +{ + return ulog2_tab64[(pow2 * 0x5b31ab928877a7e) >> 58]; +} + +static int +ispow2(uint64_t v) +{ + return v && (v & (v - 1)) == 0; +} + static void ins(Ins **pi, int *new, Blk *b, Fn *fn) { ulong ni; + Con *c; Ins *i; + Ref r; + int n; i = *pi; /* simplify more instructions here; - * copy 0 into xor, mul 2^n into shift, - * bit rotations, ... */ + * copy 0 into xor, bit rotations, + * etc. */ switch (i->op) { case Oblit1: assert(i > b->ins); @@ -53,12 +80,29 @@ ins(Ins **pi, int *new, Blk *b, Fn *fn) } blit((i-1)->arg, rsval(i->arg[0]), fn); *pi = i-1; - break; - default: - if (*new) - emiti(*i); + return; + case Oudiv: + case Ourem: + r = i->arg[1]; + if (KBASE(i->cls) == 0) + if (rtype(r) == RCon) { + c = &fn->con[r.val]; + if (c->type == CBits) + if (ispow2(c->bits.i)) { + n = ulog2(c->bits.i); + if (i->op == Ourem) { + i->op = Oand; + i->arg[1] = getcon((1ull<op = Oshr; + i->arg[1] = getcon(n, fn); + } + } + } break; } + if (*new) + emiti(*i); } void @@ -74,9 +118,7 @@ simpl(Fn *fn) --i; ins(&i, &new, b, fn); } - if (new) { - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); - } + if (new) + idup(b, curi, &insb[NIns]-curi); } } diff --git a/vendor/qbe/spill.c b/vendor/qbe/spill.c index 2ce1d4f..4fb7a29 100644 --- a/vendor/qbe/spill.c +++ b/vendor/qbe/spill.c @@ -406,26 +406,20 @@ 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--; @@ -517,8 +511,7 @@ spill(Fn *fn) p->to = slot(p->to.val); } bscopy(b->in, v); - b->nins = &insb[NIns] - curi; - idup(&b->ins, curi, b->nins); + idup(b, curi, &insb[NIns]-curi); } /* align the locals to a 16 byte boundary */ diff --git a/vendor/qbe/ssa.c b/vendor/qbe/ssa.c index 356135b..89cde44 100644 --- a/vendor/qbe/ssa.c +++ b/vendor/qbe/ssa.c @@ -1,7 +1,7 @@ #include "all.h" #include -static void +void adduse(Tmp *tmp, int ty, Blk *b, ...) { Use *u; @@ -40,11 +40,10 @@ filluse(Fn *fn) Blk *b; Phi *p; Ins *i; - int m, t, tp, w; + int m, t, tp, w, x; uint a; Tmp *tmp; - /* todo, is this the correct file? */ tmp = fn->tmp; for (t=Tmp0; tntmp; t++) { tmp[t].def = 0; @@ -84,6 +83,8 @@ filluse(Fn *fn) w = Wsb + (i->op - Oloadsb); if (isext(i->op)) w = Wsb + (i->op - Oextsb); + if (iscmp(i->op, &x, &x)) + w = Wub; if (w == Wsw || w == Wuw) if (i->cls == Kw) w = WFull; @@ -143,7 +144,7 @@ phiins(Fn *fn) continue; } bszero(u); - k = -1; + k = Kx; bp = be; for (b=fn->start; b; b=b->link) { b->visit = 0; diff --git a/vendor/qbe/test/_gcm1.ssa b/vendor/qbe/test/_gcm1.ssa new file mode 100644 index 0000000..719cddb --- /dev/null +++ b/vendor/qbe/test/_gcm1.ssa @@ -0,0 +1,48 @@ +export +function w $ifmv(w %p1, w %p2, w %p3) { +@start +@entry + %rt =w add %p2, %p3 # gcm moves to @true + %rf =w sub %p2, %p3 # gcm moves to @false + jnz %p1, @true, @false +@true + %r =w copy %rt + jmp @exit +@false + %r =w copy %rf + jmp @exit +@exit + ret %r +} + +export +function w $hoist1(w %p1, w %p2, w %p3) { +@start +@entry + %n =w copy 0 + %i =w copy %p1 +@loop + %base =w add %p2, %p3 # gcm moves to @exit + %i =w sub %i, 1 + %n =w add %n, 1 + jnz %i, @loop, @exit +@exit + %r =w add %base, %n + ret %r +} + +export +function w $hoist2(w %p1, w %p2, w %p3) { +@start +@entry + %n =w copy 0 + %i =w copy %p1 +@loop + %base =w add %p2, %p3 # gcm moves to @entry + %i =w sub %i, 1 + %n =w add %n, %base + jnz %i, @loop, @exit +@exit + %r =w add %base, %n + ret %r +} diff --git a/vendor/qbe/test/_gcm2.ssa b/vendor/qbe/test/_gcm2.ssa new file mode 100644 index 0000000..baeb4a4 --- /dev/null +++ b/vendor/qbe/test/_gcm2.ssa @@ -0,0 +1,43 @@ +# Programs from "Global Code Motion Global Value Numbering" by Cliff Click +# https://courses.cs.washington.edu/courses/cse501/06wi/reading/click-pldi95.pdf + +# GCM program in Figure 1 + +function w $gcm_test(w %a){ +@start + %i.0 =w copy 0 +@loop + %i.1 =w phi @start %i.0, @loop %i.2 + %b =w add %a, 1 # early schedule moves to @start + %i.2 =w add %i.1, %b + %c =w mul %i.2, 2 # late schedule moves to @end + %x =w csltw %i.2, 10 + jnz %x, @loop, @end +@end + ret %c +} + +# GCM program in "Figure 3 x's definition does not dominate it's use" +# +# SSA contruction will insert phi instruction for "x" in @if_false +# preventing the "add" in @if_false from being moved to @if_true + +function $gcm_test2 (w %a){ +@start + %f =w copy 1 + %x =w copy 0 + %s.0 =w copy 0 +@loop + %s.1 = w phi @start %s.0, @if_false %s.2 + jnz %a, @if, @end +@if + jnz %f, @if_true, @if_false +@if_true + %f =w copy 0 + %x =w add %x, 1 +@if_false + %s.2 =w add %s.1, %x + jmp @loop +@end + ret +} diff --git a/vendor/qbe/test/_load-elim.ssa b/vendor/qbe/test/_load-elim.ssa new file mode 100644 index 0000000..faae478 --- /dev/null +++ b/vendor/qbe/test/_load-elim.ssa @@ -0,0 +1,17 @@ +# GCM can eliminate unused add/load instructions + +export +function w $f(l %p, w %c) { +@start + jnz %c, @true, @false +@true + %p1 =l add %p, 4 + %v1 =w loaduw %p1 + jmp @end +@false + %p2 =l add %p, 4 + %v2 =w loaduw %p2 + jmp @end +@end + ret 0 +} diff --git a/vendor/qbe/test/abi3.ssa b/vendor/qbe/test/abi3.ssa index cc263c2..5ca71f4 100644 --- a/vendor/qbe/test/abi3.ssa +++ b/vendor/qbe/test/abi3.ssa @@ -28,7 +28,7 @@ function $test() { # >>> driver # #include -# struct four { long l; char c; int i; }; +# struct four { long 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", diff --git a/vendor/qbe/test/abi5.ssa b/vendor/qbe/test/abi5.ssa index 65b702c..cd786cc 100644 --- a/vendor/qbe/test/abi5.ssa +++ b/vendor/qbe/test/abi5.ssa @@ -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 l; } st5; +# typedef struct { float f; long long l; } st5; # typedef struct { char t[16]; } st6; # typedef struct { float f; double d; } st7; # typedef struct { int i[4]; } st8; diff --git a/vendor/qbe/test/abi8.ssa b/vendor/qbe/test/abi8.ssa index 85c3b73..b6bc941 100644 --- a/vendor/qbe/test/abi8.ssa +++ b/vendor/qbe/test/abi8.ssa @@ -10,6 +10,7 @@ type :sd = { s, d } # ditto type :ww = { w, w } # in a single gp reg type :lb = { l, b } # in two gp regs type :big = { b 17 } # by reference +type :ddd = { d, d, d} # big hfa on arm64 data $ctoqbestr = { b "c->qbe(%d)", b 0 } data $emptystr = { b 0 } @@ -112,6 +113,14 @@ function $qfn10(w %p0, w %p1, w %p2, w %p3, w %p4, w %p5, w %p6, w %p7, :big %p8 %r1 =w call $puts(l $emptystr) ret } +export +function $qfn11(:ddd %p0) { +@start + %r0 =w call $printf(l $ctoqbestr, ..., w 11) + call $pddd(l %p0) + %r1 =w call $puts(l $emptystr) + ret +} export function w $main() { @@ -128,6 +137,7 @@ function w $main() { call $cfn8(w 0, w 0, w 0, w 0, w 0, w 0, w 0, w 0, :lb $lb) call $cfn9(:big $big) call $cfn10(w 0, w 0, w 0, w 0, w 0, w 0, w 0, w 0, :big $big, s s_10.10, l 11) + call $cfn11(:ddd $ddd) ret 0 } @@ -140,8 +150,9 @@ 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 l; char b; } Slb; +# typedef struct { long long l; char b; } Slb; # typedef struct { char b[17]; } Sbig; +# typedef struct { double d0, d1, d2; } Sddd; # Sfi1 zfi1, fi1 = { -123, 4.56 }; # Sfi2 zfi2, fi2 = { 1.23, 456 }; # Sfi3 zfi3, fi3 = { 3.45, 567 }; @@ -150,16 +161,18 @@ function w $main() { # Sww zww, ww = { -123, -456 }; # Slb zlb, lb = { 123, 'z' }; # Sbig zbig, big = { "abcdefhijklmnopqr" }; +# Sddd zddd, ddd = { 1.23, 45.6, 7.89 }; # void pfi1(Sfi1 *s) { printf(" { %d, %g }", s->h, s->s); } # void pfi2(Sfi2 *s) { printf(" { %g, %d }", s->s, s->w); } # void pfi3(Sfi3 *s) { printf(" { %g, %d }", s->s, s->u.w); } # 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(" { %ld, '%c' }", s->l, s->b); } +# void plb(Slb *s) { printf(" { %lld, '%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 l) { printf(" %ld", l); } +# void pl(long long l) { printf(" %lld", l); } # void ps(float s) { printf(" %g", s); } # void pd(double d) { printf(" %g", d); } # /* --------------------------- */ @@ -193,8 +206,8 @@ function w $main() { # pss(&p0); puts(""); # qfn4(p0); # } -# 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) { +# 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) { # printf("qbe->c(%d)", 5); # pss(&p7); ps(p8); pl(p9); puts(""); # qfn5(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); @@ -223,12 +236,18 @@ function w $main() { # pbig(&p0); puts(""); # qfn9(p0); # } -# 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) { +# 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) { # printf("qbe->c(%d)", 10); # pbig(&p8); ps(p9); pl(p10); puts(""); # qfn10(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10); # } +# extern void qfn11(Sddd); +# void cfn11(Sddd p0) { +# printf("qbe->c(%d)", 11); +# pddd(&p0); puts(""); +# qfn11(p0); +# } # <<< # >>> output @@ -254,4 +273,6 @@ function w $main() { # c->qbe(9) "abcdefhijklmnopqr" # qbe->c(10) "abcdefhijklmnopqr" 10.1 11 # c->qbe(10) "abcdefhijklmnopqr" 10.1 11 +# qbe->c(11) { 1.23, 45.6, 7.89 } +# c->qbe(11) { 1.23, 45.6, 7.89 } # <<< diff --git a/vendor/qbe/test/abi9.ssa b/vendor/qbe/test/abi9.ssa new file mode 100644 index 0000000..2fc1036 --- /dev/null +++ b/vendor/qbe/test/abi9.ssa @@ -0,0 +1,20 @@ +type :obj = { l, l, l, l } + +export +function :obj $f(l %self) { +@_0 + %_1 =l alloc8 16 + storel 77, %_1 + ret %_1 +} + +# >>> driver +# #include +# 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 +# <<< diff --git a/vendor/qbe/test/alias1.ssa b/vendor/qbe/test/alias1.ssa new file mode 100644 index 0000000..a064fac --- /dev/null +++ b/vendor/qbe/test/alias1.ssa @@ -0,0 +1,21 @@ +export function w $main() { +@start + %a =l alloc4 4 + %b =l alloc4 4 + storew 4, %a + storew 5, %b + +@loop + # %mem will be %a and %b successively, + # but we do not know it when processing + # the phi because %b goes through a cpy + %mem =l phi @start %a, @loop %bcpy + + %w =w load %mem + %eq5 =w ceqw %w, 5 + %bcpy =l copy %b + jnz %eq5, @exit, @loop + +@exit + ret 0 +} diff --git a/vendor/qbe/test/conaddr.ssa b/vendor/qbe/test/conaddr.ssa index 9e24d49..0ded18e 100644 --- a/vendor/qbe/test/conaddr.ssa +++ b/vendor/qbe/test/conaddr.ssa @@ -1,3 +1,4 @@ +# skip amd64_win (no signals on win32) # test amd64 addressing modes export diff --git a/vendor/qbe/test/copy.ssa b/vendor/qbe/test/copy.ssa new file mode 100644 index 0000000..83f9ec7 --- /dev/null +++ b/vendor/qbe/test/copy.ssa @@ -0,0 +1,30 @@ +export function w $f() { +@start + %x0 =w loadsb $a + # the extension must not be eliminated + # even though the load already extended + %x1 =l extsb %x0 + %c =w ceql %x1, -1 + ret %c +} + +export function w $g(w %arg) { +@start + %a0 =w extuh %arg # 1 + %a1 =w or 65536, %a0 # 65537 +@loop + %a2 =w phi @loop %a2, @start %a1 # 65537 + jnz %arg, @end, @loop +@end + %a3 =w shr %a2, 1 # 32768 + %a4 =w or %a2, 1 # 65537 + %a5 =w or %a3, %a4 # 98305 + %ret =w extuh %a5 # 32769 + ret %ret +} + +# >>> driver +# char a = -1; +# extern int f(), g(int); +# int main() { return !(f() == 1 && g(1) == 32769); } +# <<< diff --git a/vendor/qbe/test/dark.ssa b/vendor/qbe/test/dark.ssa index ed9ec21..c508e48 100644 --- a/vendor/qbe/test/dark.ssa +++ b/vendor/qbe/test/dark.ssa @@ -1,4 +1,4 @@ -# skip arm64 arm64_apple rv64 +# skip arm64 arm64_apple rv64 amd64_win # a hack example, # we use a dark type to get # a pointer to the stack. diff --git a/vendor/qbe/test/fpcnv.ssa b/vendor/qbe/test/fpcnv.ssa index 3466ed2..3fe078f 100644 --- a/vendor/qbe/test/fpcnv.ssa +++ b/vendor/qbe/test/fpcnv.ssa @@ -72,6 +72,7 @@ function l $dtol(d %f) { # >>> driver +# #include # #include # # extern float fneg(float); @@ -113,15 +114,19 @@ function l $dtol(d %f) { # return 7; # } # for (i=0; i= 1LL << DBL_MANT_DIG) +# break; # if (dtol(fin[i]) != (unsigned long long)fin[i]) -# return 9; +# return 8; # if((unsigned long long)fin[i] > UINT_MAX) # continue; -# if (stow((float)fin[i]) != (unsigned int)(float)fin[i]) -# return 10; # if (dtow(fin[i]) != (unsigned int)fin[i]) +# return 9; +# if (fin[i] >= 1LL << FLT_MANT_DIG) +# continue; +# if (stol((float)fin[i]) != (unsigned long long)(float)fin[i]) +# return 10; +# if (stow((float)fin[i]) != (unsigned int)(float)fin[i]) # return 11; # } # return 0; diff --git a/vendor/qbe/test/gvn1.ssa b/vendor/qbe/test/gvn1.ssa new file mode 100644 index 0000000..d47f05b --- /dev/null +++ b/vendor/qbe/test/gvn1.ssa @@ -0,0 +1,19 @@ +export +function w $test(w %p1, w %p2) { +@start +@entry + %t1 =w copy 1 + jnz %t1, @live, @dead1 +@live + %t2 =w add %p1, %p2 + ret %t2 +@dead1 + %t2 =w add %p1, %p2 # live ins in dead blk +@dead2 + jnz %t1, @live, @dead1 +} + +# >>> driver +# extern int test(int p1, int p2); +# int main() { return test(1, 2) != 3; } +# <<< diff --git a/vendor/qbe/test/gvn2.ssa b/vendor/qbe/test/gvn2.ssa new file mode 100644 index 0000000..33f9a96 --- /dev/null +++ b/vendor/qbe/test/gvn2.ssa @@ -0,0 +1,31 @@ +# GVN 0/non-0 inference removes @yesyes, @yesno, @noyes, @nono + +export +function w $test(w %c) { +@start + jnz %c, @yes, @no +@yes + %c0 =w cnew %c, 0 + jnz %c0, @yesyes, @yesno +@yesyes + %rc =w copy 1 + jmp @end +@yesno + %rc =w copy 111 + jmp @end +@no + %c1 =w cnew %c, 0 + jnz %c1, @noyes, @nono +@noyes + %rc =w copy 222 + jmp @end +@nono + %rc =w copy 0 +@end + ret %rc +} + +# >>> driver +# int test(int); +# int main(void) { return test(0); } +# <<< diff --git a/vendor/qbe/test/ifc.ssa b/vendor/qbe/test/ifc.ssa new file mode 100644 index 0000000..29f4457 --- /dev/null +++ b/vendor/qbe/test/ifc.ssa @@ -0,0 +1,238 @@ +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 +# ; +# } +# <<< diff --git a/vendor/qbe/test/isel4.ssa b/vendor/qbe/test/isel4.ssa new file mode 100644 index 0000000..874807e --- /dev/null +++ b/vendor/qbe/test/isel4.ssa @@ -0,0 +1,64 @@ +# amd64 address-folding stress + +export function w $f0(l %a, l %b) { +@start + %c =l add %b, 2 + %d =l mul %c, 4 + %e =l add %a, %d + %q =l loadw %e + ret %q +} + +export function w $f1(l %a, l %b) { +@start + %c =l add 1, %b + %f =l add %c, 1 + %d =l mul %f, 4 + %e =l add %d, %a + %q =l loadw %e + ret %q +} + +export function w $f2(l %a, l %b) { +@start + %l =l mul %b, 4 + %d =l add 8, %l + %e =l add %a, %d + %q =l loadw %e + ret %q +} + +# fixme: folding is not good here +export function w $f3(l %a, l %b) { +@start + %l =l mul %b, 4 + %d =l add 4, %l + %f =l add 4, %d + %e =l add %a, %f + %q =l loadw %e + ret %q +} + +export function w $f4(l %a, l %b) { +@start + %c =l add 1, %b + %d =l mul %c, 4 + %e =l add 4, %d + %f =l add %e, %a + %q =l loadw %f + ret %q +} + +# >>> driver +# int a[] = {1, 2, 3, 4}; +# typedef int loadf(int *, long long); +# extern loadf f0, f1, f2, f3, f4; +# loadf *fns[] = {&f0, &f1, &f2, &f3, &f4, 0}; +# int main() { +# loadf **f; +# int n; +# for (n=1,f=fns; *f; f++,n++) +# if ((*f)(a, 1) != 4) return n; +# return 0; +# } +# <<< diff --git a/vendor/qbe/test/isel5.ssa b/vendor/qbe/test/isel5.ssa new file mode 100644 index 0000000..9c546d7 --- /dev/null +++ b/vendor/qbe/test/isel5.ssa @@ -0,0 +1,16 @@ +# make sure the local symbols used for +# fp constants do not get a _ prefix +# on apple arm hardware + +export function w $main() { +@start + %r =d copy d_1.2 + %x =w call $printf(l $fmt, ..., d %r) + ret 0 +} + +data $fmt = { b "%.06f\n", b 0 } + +# >>> output +# 1.200000 +# <<< diff --git a/vendor/qbe/test/isel6.ssa b/vendor/qbe/test/isel6.ssa new file mode 100644 index 0000000..1846571 --- /dev/null +++ b/vendor/qbe/test/isel6.ssa @@ -0,0 +1,38 @@ +# 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 +# #include +# #include +# 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 +# <<< diff --git a/vendor/qbe/test/tls.ssa b/vendor/qbe/test/tls.ssa index a17dda9..381279f 100644 --- a/vendor/qbe/test/tls.ssa +++ b/vendor/qbe/test/tls.ssa @@ -1,3 +1,4 @@ +# 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} diff --git a/vendor/qbe/tools/lexh.c b/vendor/qbe/tools/lexh.c index 1b4a7fb..82f3826 100644 --- a/vendor/qbe/tools/lexh.c +++ b/vendor/qbe/tools/lexh.c @@ -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", + "alloc1", "alloc2", "thread", "extern", "common", }; enum { diff --git a/vendor/qbe/tools/log2.c b/vendor/qbe/tools/log2.c new file mode 100644 index 0000000..05f97c9 --- /dev/null +++ b/vendor/qbe/tools/log2.c @@ -0,0 +1,64 @@ +#include +#include + +typedef unsigned long long ullong; + +char seen[64]; +ullong rbg = 0x1e0298f7a7e; + +int +bit() +{ + int bit; + + bit = rbg & 1; + rbg >>= 1; + return bit; +} + +int +search(ullong n, int b, ullong *out) +{ + int i, x; + ullong y, z; + + if (b == 64) { + *out = n; + return 1; + } + + x = 63 & ((n << (63 - b)) >> 58); + assert(!(x & 0) && x <= 62); + y = bit(); + + for (i=0; i<2; i++) { + z = x | (y << 5); + if (!seen[z]) { + seen[z] = (63-b)+1; + if (search(n | (y << b), b+1, out)) + return 1; + seen[z] = 0; + } + y ^= 1; + } + + return 0; +} + +int +main() +{ + ullong out; + int i; + + if (search(0, 0, &out)) { + printf("0x%llx\n", out); + for (i=0; i<64; i++) { + printf((i&7) == 0 ? "\t" : " "); + printf("%2d,", seen[i]-1); + if ((i&7) == 7) + printf("\n"); + } + } else + puts("not found"); +} diff --git a/vendor/qbe/tools/mgen/.gitignore b/vendor/qbe/tools/mgen/.gitignore new file mode 100644 index 0000000..1bb5834 --- /dev/null +++ b/vendor/qbe/tools/mgen/.gitignore @@ -0,0 +1,3 @@ +*.cm[iox] +*.o +mgen diff --git a/vendor/qbe/tools/mgen/.ocp-indent b/vendor/qbe/tools/mgen/.ocp-indent new file mode 100644 index 0000000..28b3b28 --- /dev/null +++ b/vendor/qbe/tools/mgen/.ocp-indent @@ -0,0 +1 @@ +match_clause=4 diff --git a/vendor/qbe/tools/mgen/Makefile b/vendor/qbe/tools/mgen/Makefile new file mode 100644 index 0000000..1b94436 --- /dev/null +++ b/vendor/qbe/tools/mgen/Makefile @@ -0,0 +1,16 @@ +BIN = mgen +SRC = \ + match.ml \ + fuzz.ml \ + cgen.ml \ + sexp.ml \ + test.ml \ + main.ml + +$(BIN): $(SRC) + ocamlopt -o $(BIN) -g str.cmxa $(SRC) + +clean: + rm -f *.cm? *.o $(BIN) + +.PHONY: clean diff --git a/vendor/qbe/tools/mgen/cgen.ml b/vendor/qbe/tools/mgen/cgen.ml new file mode 100644 index 0000000..297265c --- /dev/null +++ b/vendor/qbe/tools/mgen/cgen.ml @@ -0,0 +1,420 @@ +open Match + +type options = + { pfx: string + ; static: bool + ; oc: out_channel } + +type side = L | R + +type id_pred = + | InBitSet of Int64.t + | Ge of int + | Eq of int + +and id_test = + | Pred of (side * id_pred) + | And of id_test * id_test + +type case_code = + | Table of ((int * int) * int) list + | IfThen of + { test: id_test + ; cif: case_code + ; cthen: case_code option } + | Return of int + +type case = + { swap: bool + ; code: case_code } + +let cgen_case tmp nstates map = + let cgen_test ids = + match ids with + | [id] -> Eq id + | _ -> + let min_id = + List.fold_left min max_int ids in + if List.length ids = nstates - min_id + then Ge min_id + else begin + assert (nstates <= 64); + InBitSet + (List.fold_left (fun bs id -> + Int64.logor bs + (Int64.shift_left 1L id)) + 0L ids) + end + in + let symmetric = + let inverse ((l, r), x) = ((r, l), x) in + setify map = setify (List.map inverse map) in + let map = + let ordered ((l, r), _) = r <= l in + if symmetric then + List.filter ordered map + else map + in + let exception BailToTable in + try + let st = + match setify (List.map snd map) with + | [st] -> st + | _ -> raise BailToTable + in + (* the operation considered can only + * generate a single state *) + let pairs = List.map fst map in + let ls, rs = List.split pairs in + let ls = setify ls and rs = setify rs in + if List.length ls > 1 && List.length rs > 1 then + raise BailToTable; + { swap = symmetric + ; code = + let pl = Pred (L, cgen_test ls) + and pr = Pred (R, cgen_test rs) in + IfThen + { test = And (pl, pr) + ; cif = Return st + ; cthen = Some (Return tmp) } } + with BailToTable -> + { swap = symmetric + ; code = Table map } + +let show_op (_cls, op) = + "O" ^ show_op_base op + +let indent oc i = + Printf.fprintf oc "%s" (String.sub "\t\t\t\t\t" 0 i) + +let emit_swap oc i = + let pf m = Printf.fprintf oc m in + let pfi n m = indent oc n; pf m in + pfi i "if (l < r)\n"; + pfi (i+1) "t = l, l = r, r = t;\n" + +let gen_tables oc tmp pfx nstates (op, c) = + let i = 1 in + let pf m = Printf.fprintf oc m in + let pfi n m = indent oc n; pf m in + let ntables = ref 0 in + (* we must follow the order in which + * we visit code in emit_case, or + * else ntables goes out of sync *) + let base = pfx ^ show_op op in + let swap = c.swap in + let rec gen c = + match c with + | Table map -> + let name = + if !ntables = 0 then base else + base ^ string_of_int !ntables + in + assert (nstates <= 256); + if swap then + let n = nstates * (nstates + 1) / 2 in + pfi i "static uchar %stbl[%d] = {\n" name n + else + pfi i "static uchar %stbl[%d][%d] = {\n" + name nstates nstates; + for l = 0 to nstates - 1 do + pfi (i+1) ""; + for r = 0 to nstates - 1 do + if not swap || r <= l then + begin + pf "%d" + (try List.assoc (l,r) map + with Not_found -> tmp); + pf ","; + end + done; + pf "\n"; + done; + pfi i "};\n" + | IfThen {cif; cthen} -> + gen cif; + Option.iter gen cthen + | Return _ -> () + in + gen c.code + +let emit_case oc pfx no_swap (op, c) = + let fpf = Printf.fprintf in + let pf m = fpf oc m in + let pfi n m = indent oc n; pf m in + let rec side oc = function + | L -> fpf oc "l" + | R -> fpf oc "r" + in + let pred oc (s, pred) = + match pred with + | InBitSet bs -> fpf oc "BIT(%a) & %#Lx" side s bs + | Eq id -> fpf oc "%a == %d" side s id + | Ge id -> fpf oc "%d <= %a" id side s + in + let base = pfx ^ show_op op in + let swap = c.swap in + let ntables = ref 0 in + let rec code i c = + match c with + | Return id -> pfi i "return %d;\n" id + | Table map -> + let name = + if !ntables = 0 then base else + base ^ string_of_int !ntables + in + incr ntables; + if swap then + pfi i "return %stbl[(l + l*l)/2 + r];\n" name + else pfi i "return %stbl[l][r];\n" name + | IfThen ({test = And (And (t1, t2), t3)} as r) -> + code i @@ IfThen + {r with test = And (t1, And (t2, t3))} + | IfThen {test = And (Pred p, t); cif; cthen} -> + pfi i "if (%a)\n" pred p; + code i (IfThen {test = t; cif; cthen}) + | IfThen {test = Pred p; cif; cthen} -> + pfi i "if (%a) {\n" pred p; + code (i+1) cif; + pfi i "}\n"; + Option.iter (code i) cthen + in + pfi 1 "case %s:\n" (show_op op); + if not no_swap && c.swap then + emit_swap oc 2; + code 2 c.code + +let emit_list + ?(limit=60) ?(cut_before_sep=false) + ~col ~indent:i ~sep ~f oc l = + let sl = String.length sep in + let rstripped_sep, rssl = + if sep.[sl - 1] = ' ' then + String.sub sep 0 (sl - 1), sl - 1 + else sep, sl + in + let lstripped_sep, lssl = + if sep.[0] = ' ' then + String.sub sep 1 (sl - 1), sl - 1 + else sep, sl + in + let rec line col acc = function + | [] -> (List.rev acc, []) + | s :: l -> + let col = col + sl + String.length s in + let no_space = + if cut_before_sep || l = [] then + col > limit + else + col + rssl > limit + in + if no_space then + (List.rev acc, s :: l) + else + line col (s :: acc) l + in + let rec go col l = + if l = [] then () else + let ll, l = line col [] l in + Printf.fprintf oc "%s" (String.concat sep ll); + if l <> [] && cut_before_sep then begin + Printf.fprintf oc "\n"; + indent oc i; + Printf.fprintf oc "%s" lstripped_sep; + go (8*i + lssl) l + end else if l <> [] then begin + Printf.fprintf oc "%s\n" rstripped_sep; + indent oc i; + go (8*i) l + end else () + in + go col (List.map f l) + +let emit_numberer opts n = + let pf m = Printf.fprintf opts.oc m in + let tmp = (atom_state n Tmp).id in + let con = (atom_state n AnyCon).id in + let nst = Array.length n.states in + let cases = + StateMap.by_ops n.statemap |> + List.map (fun (op, map) -> + (op, cgen_case tmp nst map)) + in + let all_swap = + List.for_all (fun (_, c) -> c.swap) cases in + (* opn() *) + if opts.static then pf "static "; + pf "int\n"; + pf "%sopn(int op, int l, int r)\n" opts.pfx; + pf "{\n"; + cases |> List.iter + (gen_tables opts.oc tmp opts.pfx nst); + if List.exists (fun (_, c) -> c.swap) cases then + pf "\tint t;\n\n"; + if all_swap then emit_swap opts.oc 1; + pf "\tswitch (op) {\n"; + cases |> List.iter + (emit_case opts.oc opts.pfx all_swap); + pf "\tdefault:\n"; + pf "\t\treturn %d;\n" tmp; + pf "\t}\n"; + pf "}\n\n"; + (* refn() *) + if opts.static then pf "static "; + pf "int\n"; + pf "%srefn(Ref r, Num *tn, Con *con)\n" opts.pfx; + pf "{\n"; + let cons = + List.filter_map (function + | (Con c, s) -> Some (c, s.id) + | _ -> None) + n.atoms + in + if cons <> [] then + pf "\tint64_t n;\n\n"; + pf "\tswitch (rtype(r)) {\n"; + pf "\tcase RTmp:\n"; + if tmp <> 0 then begin + assert + (List.exists (fun (_, s) -> + s.id = 0 + ) n.atoms && + (* no temp should ever get state 0 *) + List.for_all (fun (a, s) -> + s.id <> 0 || + match a with + | AnyCon | Con _ -> true + | _ -> false + ) n.atoms); + pf "\t\tif (!tn[r.val].n)\n"; + pf "\t\t\ttn[r.val].n = %d;\n" tmp; + end; + pf "\t\treturn tn[r.val].n;\n"; + pf "\tcase RCon:\n"; + if cons <> [] then begin + pf "\t\tif (con[r.val].type != CBits)\n"; + pf "\t\t\treturn %d;\n" con; + pf "\t\tn = con[r.val].bits.i;\n"; + cons |> inverse |> group_by_fst + |> List.iter (fun (id, cs) -> + pf "\t\tif ("; + emit_list ~cut_before_sep:true + ~col:20 ~indent:2 ~sep:" || " + ~f:(fun c -> "n == " ^ Int64.to_string c) + opts.oc cs; + pf ")\n"; + pf "\t\t\treturn %d;\n" id + ); + end; + pf "\t\treturn %d;\n" con; + pf "\tdefault:\n"; + pf "\t\treturn INT_MIN;\n"; + pf "\t}\n"; + pf "}\n\n"; + (* match[]: patterns per state *) + if opts.static then pf "static "; + pf "bits %smatch[%d] = {\n" opts.pfx nst; + n.states |> Array.iteri (fun sn s -> + let tops = + List.filter_map (function + | Top ("$" | "%") -> None + | Top r -> Some ("BIT(P" ^ r ^ ")") + | _ -> None) s.point |> setify + in + if tops <> [] then + pf "\t[%d] = %s,\n" + sn (String.concat " | " tops); + ); + pf "};\n\n" + +let var_id vars f = + List.mapi (fun i x -> (x, i)) vars |> + List.assoc f + +let compile_action vars act = + let pcs = Hashtbl.create 100 in + let rec gen pc (act: Action.t) = + try + [10 + Hashtbl.find pcs act.id] + with Not_found -> + let code = + match act.node with + | Action.Stop -> + [0] + | Action.Push (sym, k) -> + let c = if sym then 1 else 2 in + [c] @ gen (pc + 1) k + | Action.Set (v, {node = Action.Pop k; _}) + | Action.Set (v, ({node = Action.Stop; _} as k)) -> + let v = var_id vars v in + [3; v] @ gen (pc + 2) k + | Action.Set _ -> + (* for now, only atomic patterns can be + * tied to a variable, so Set must be + * followed by either Pop or Stop *) + assert false + | Action.Pop k -> + [4] @ gen (pc + 1) k + | Action.Switch cases -> + let cases = + inverse cases |> group_by_fst |> + List.sort (fun (_, cs1) (_, cs2) -> + let n1 = List.length cs1 + and n2 = List.length cs2 in + compare n2 n1) + in + (* the last case is the one with + * the max number of entries *) + let cases = List.rev (List.tl cases) + and last = fst (List.hd cases) in + let ncases = + List.fold_left (fun n (_, cs) -> + List.length cs + n) + 0 cases + in + let body_off = 2 + 2 * ncases + 1 in + let pc, tbl, body = + List.fold_left + (fun (pc, tbl, body) (a, cs) -> + let ofs = body_off + List.length body in + let case = gen pc a in + let pc = pc + List.length case in + let body = body @ case in + let tbl = + List.fold_left (fun tbl c -> + tbl @ [c; ofs] + ) tbl cs + in + (pc, tbl, body)) + (pc + body_off, [], []) + cases + in + let ofs = body_off + List.length body in + let tbl = tbl @ [ofs] in + assert (2 + List.length tbl = body_off); + [5; ncases] @ tbl @ body @ gen pc last + in + if act.node <> Action.Stop then + Hashtbl.replace pcs act.id pc; + code + in + gen 0 act + +let emit_matchers opts ms = + let pf m = Printf.fprintf opts.oc m in + if opts.static then pf "static "; + pf "uchar *%smatcher[] = {\n" opts.pfx; + List.iter (fun (vars, pname, m) -> + pf "\t[P%s] = (uchar[]){\n" pname; + pf "\t\t"; + let bytes = compile_action vars m in + emit_list + ~col:16 ~indent:2 ~sep:"," + ~f:string_of_int opts.oc bytes; + pf "\n"; + pf "\t},\n") + ms; + pf "};\n\n" + +let emit_c opts n = + emit_numberer opts n diff --git a/vendor/qbe/tools/mgen/fuzz.ml b/vendor/qbe/tools/mgen/fuzz.ml new file mode 100644 index 0000000..0821286 --- /dev/null +++ b/vendor/qbe/tools/mgen/fuzz.ml @@ -0,0 +1,413 @@ +(* fuzz the tables and matchers generated *) +open Match + +module Buffer: sig + type 'a t + val create: ?capacity:int -> unit -> 'a t + val reset: 'a t -> unit + val size: 'a t -> int + val get: 'a t -> int -> 'a + val set: 'a t -> int -> 'a -> unit + val push: 'a t -> 'a -> unit +end = struct + type 'a t = + { mutable size: int + ; mutable data: 'a array } + let mk_array n = Array.make n (Obj.magic 0) + let create ?(capacity = 10) () = + if capacity < 0 then invalid_arg "Buffer.make"; + {size = 0; data = mk_array capacity} + let reset b = b.size <- 0 + let size b = b.size + let get b n = + if n >= size b then invalid_arg "Buffer.get"; + b.data.(n) + let set b n x = + if n >= size b then invalid_arg "Buffer.set"; + b.data.(n) <- x + let push b x = + let cap = Array.length b.data in + if size b = cap then begin + let data = mk_array (2 * cap + 1) in + Array.blit b.data 0 data 0 cap; + b.data <- data + end; + let sz = size b in + b.size <- sz + 1; + set b sz x +end + +let binop_state n op s1 s2 = + let key = K (op, s1, s2) in + try StateMap.find key n.statemap + with Not_found -> atom_state n Tmp + +type id = int +type term_data = + | Binop of op * id * id + | Leaf of atomic_pattern +type term = + { id: id + ; data: term_data + ; state: p state } + +let pp_term fmt (ta, id) = + let fpf x = Format.fprintf fmt x in + let rec pp _fmt id = + match ta.(id).data with + | Leaf (Con c) -> fpf "%Ld" c + | Leaf AnyCon -> fpf "$%d" id + | Leaf Tmp -> fpf "%%%d" id + | Binop (op, id1, id2) -> + fpf "@[(%s@%d:%d @[%a@ %a@])@]" + (show_op op) id ta.(id).state.id + pp id1 pp id2 + in pp fmt id + +(* A term pool is a deduplicated set of term + * that maintains nodes numbering using the + * statemap passed at creation time *) +module TermPool = struct + type t = + { terms: term Buffer.t + ; hcons: (term_data, id) Hashtbl.t + ; numbr: numberer } + + let create numbr = + { terms = Buffer.create () + ; hcons = Hashtbl.create 100 + ; numbr } + let reset tp = + Buffer.reset tp.terms; + Hashtbl.clear tp.hcons + + let size tp = Buffer.size tp.terms + let term tp id = Buffer.get tp.terms id + + let mk_leaf tp atm = + let data = Leaf atm in + match Hashtbl.find tp.hcons data with + | id -> term tp id + | exception Not_found -> + let id = Buffer.size tp.terms in + let state = atom_state tp.numbr atm in + Buffer.push tp.terms {id; data; state}; + Hashtbl.add tp.hcons data id; + term tp id + let mk_binop tp op t1 t2 = + let data = Binop (op, t1.id, t2.id) in + match Hashtbl.find tp.hcons data with + | id -> term tp id + | exception Not_found -> + let id = Buffer.size tp.terms in + let state = + binop_state tp.numbr op t1.state t2.state + in + Buffer.push tp.terms {id; data; state}; + Hashtbl.add tp.hcons data id; + term tp id + + let rec add_pattern tp = function + | Bnr (op, p1, p2) -> + let t1 = add_pattern tp p1 in + let t2 = add_pattern tp p2 in + mk_binop tp op t1 t2 + | Atm atm -> mk_leaf tp atm + | Var (_, atm) -> add_pattern tp (Atm atm) + + let explode_term tp id = + let rec aux tms n id = + let t = term tp id in + match t.data with + | Leaf _ -> (n, {t with id = n} :: tms) + | Binop (op, id1, id2) -> + let n1, tms = aux tms n id1 in + let n = n1 + 1 in + let n2, tms = aux tms n id2 in + let n = n2 + 1 in + (n, { t with data = Binop (op, n1, n2) + ; id = n } :: tms) + in + let n, tms = aux [] 0 id in + Array.of_list (List.rev tms), n +end + +module R = Random + +(* uniform pick in a list *) +let list_pick l = + let rec aux n l x = + match l with + | [] -> x + | y :: l -> + if R.int (n + 1) = 0 then + aux (n + 1) l y + else + aux (n + 1) l x + in + match l with + | [] -> invalid_arg "list_pick" + | x :: l -> aux 1 l x + +let term_pick ~numbr = + let ops = + if numbr.ops = [] then + numbr.ops <- + (StateMap.fold (fun k _ ops -> + match k with + | K (op, _, _) -> op :: ops) + numbr.statemap [] |> setify); + numbr.ops + in + let rec gen depth = + (* exponential probability for leaves to + * avoid skewing towards shallow terms *) + let atm_prob = 0.75 ** float_of_int depth in + if R.float 1.0 <= atm_prob || ops = [] then + let atom, st = list_pick numbr.atoms in + (st, Atm atom) + else + let op = list_pick ops in + let s1, t1 = gen (depth - 1) in + let s2, t2 = gen (depth - 1) in + ( binop_state numbr op s1 s2 + , Bnr (op, t1, t2) ) + in fun ~depth -> gen depth + +exception FuzzError + +let rec pattern_depth = function + | Bnr (_, p1, p2) -> + 1 + max (pattern_depth p1) (pattern_depth p2) + | Atm _ -> 0 + | Var (_, atm) -> pattern_depth (Atm atm) + +let ( %% ) a b = + 1e2 *. float_of_int a /. float_of_int b + +let progress ?(width = 50) msg pct = + Format.eprintf "\x1b[2K\r%!"; + let progress_bar fmt = + let n = + let fwidth = float_of_int width in + 1 + int_of_float (pct *. fwidth /. 1e2) + in + Format.fprintf fmt " %s%s %.0f%%@?" + (String.concat "" (List.init n (fun _ -> "▒"))) + (String.make (max 0 (width - n)) '-') + pct + in + Format.kfprintf progress_bar + Format.err_formatter msg + +let fuzz_numberer rules numbr = + (* pick twice the max pattern depth so we + * have a chance to find non-trivial numbers + * for the atomic patterns in the rules *) + let depth = + List.fold_left (fun depth r -> + max depth (pattern_depth r.pattern)) + 0 rules * 2 + in + (* fuzz until the term pool we are constructing + * is no longer growing fast enough; or we just + * went through sufficiently many iterations *) + let max_iter = 1_000_000 in + let low_insert_rate = 1e-2 in + let tp = TermPool.create numbr in + let rec loop new_stats i = + let (_, _, insert_rate) = new_stats in + if insert_rate <= low_insert_rate then () else + if i >= max_iter then () else + (* periodically update stats *) + let new_stats = + let (num, cnt, rate) = new_stats in + if num land 1023 = 0 then + let rate = + 0.5 *. (rate +. float_of_int cnt /. 1023.) + in + progress " insert_rate=%.1f%%" + (i %% max_iter) (rate *. 1e2); + (num + 1, 0, rate) + else new_stats + in + (* create a term and check that its number is + * accurate wrt the rules *) + let st, term = term_pick ~numbr ~depth in + let state_matched = + List.filter_map (fun cu -> + match cu with + | Top ("$" | "%") -> None + | Top name -> Some name + | _ -> None) + st.point |> setify + in + let rule_matched = + List.filter_map (fun r -> + if pattern_match r.pattern term then + Some r.name + else None) + rules |> setify + in + if state_matched <> rule_matched then begin + let open Format in + let pp_str_list = + let pp_sep fmt () = fprintf fmt ",@ " in + pp_print_list ~pp_sep pp_print_string + in + eprintf "@.@[fuzz error for %s" + (show_pattern term); + eprintf "@ @[state matched: %a@]" + pp_str_list state_matched; + eprintf "@ @[rule matched: %a@]" + pp_str_list rule_matched; + eprintf "@]@."; + raise FuzzError; + end; + if state_matched = [] then + loop new_stats (i + 1) + else + (* add to the term pool *) + let old_size = TermPool.size tp in + let _ = TermPool.add_pattern tp term in + let new_stats = + let (num, cnt, rate) = new_stats in + if TermPool.size tp <> old_size then + (num + 1, cnt + 1, rate) + else + (num + 1, cnt, rate) + in + loop new_stats (i + 1) + in + loop (1, 0, 1.0) 0; + Format.eprintf + "@.@[ generated %.3fMiB of test terms@]@." + (float_of_int (Obj.reachable_words (Obj.repr tp)) + /. 128. /. 1024.); + tp + +let rec run_matcher stk m (ta, id as t) = + let state id = ta.(id).state.id in + match m.Action.node with + | Action.Switch cases -> + let m = + try List.assoc (state id) cases + with Not_found -> failwith "no switch case" + in + run_matcher stk m t + | Action.Push (sym, m) -> + let l, r = + match ta.(id).data with + | Leaf _ -> failwith "push on leaf" + | Binop (_, l, r) -> (l, r) + in + if sym && state l > state r + then run_matcher (l :: stk) m (ta, r) + else run_matcher (r :: stk) m (ta, l) + | Action.Pop m -> begin + match stk with + | id :: stk -> run_matcher stk m (ta, id) + | [] -> failwith "pop on empty stack" + end + | Action.Set (v, m) -> + (v, id) :: run_matcher stk m t + | Action.Stop -> [] + +let rec term_match p (ta, id) = + let (|>>) x f = + match x with None -> None | Some x -> f x + in + let atom_match a = + match ta.(id).data with + | Leaf a' -> pattern_match (Atm a) (Atm a') + | Binop _ -> pattern_match (Atm a) (Atm Tmp) + in + match p with + | Var (v, a) when atom_match a -> + Some [(v, id)] + | Atm a when atom_match a -> Some [] + | (Atm _ | Var _) -> None + | Bnr (op, pl, pr) -> begin + match ta.(id).data with + | Binop (op', idl, idr) when op' = op -> + term_match pl (ta, idl) |>> fun l1 -> + term_match pr (ta, idr) |>> fun l2 -> + Some (l1 @ l2) + | _ -> None + end + +let test_matchers tp numbr rules = + let {statemap = sm; states = sa; _} = numbr in + let total = ref 0 in + let matchers = + let htbl = Hashtbl.create (Array.length sa) in + List.map (fun r -> (r.name, r.pattern)) rules |> + group_by_fst |> + List.iter (fun (r, ps) -> + total := !total + List.length ps; + let pm = (ps, lr_matcher sm sa rules r) in + sa |> Array.iter (fun s -> + if List.mem (Top r) s.point then + Hashtbl.add htbl s.id pm)); + htbl + in + let seen = Hashtbl.create !total in + for id = 0 to TermPool.size tp - 1 do + if id land 1023 = 0 || + id = TermPool.size tp - 1 then begin + progress + " coverage=%.1f%%" + (id %% TermPool.size tp) + (Hashtbl.length seen %% !total) + end; + let t = TermPool.explode_term tp id in + Hashtbl.find_all matchers + (TermPool.term tp id).state.id |> + List.iter (fun (ps, m) -> + let norm = List.fast_sort compare in + let ok = + match norm (run_matcher [] m t) with + | asn -> `Match (List.exists (fun p -> + match term_match p t with + | None -> false + | Some asn' -> + if asn = norm asn' then begin + Hashtbl.replace seen p (); + true + end else false) ps) + | exception e -> `RunFailure e + in + if ok <> `Match true then begin + let open Format in + let pp_asn fmt asn = + fprintf fmt "@["; + pp_print_list + ~pp_sep:(fun fmt () -> fprintf fmt ";@ ") + (fun fmt (v, d) -> + fprintf fmt "@[%s←%d@]" v d) + fmt asn; + fprintf fmt "@]" + in + eprintf "@.@[matcher error for"; + eprintf "@ @[%a@]" pp_term t; + begin match ok with + | `RunFailure e -> + eprintf "@ @[exception: %s@]" + (Printexc.to_string e) + | `Match (* false *) _ -> + let asn = run_matcher [] m t in + eprintf "@ @[assignment: %a@]" + pp_asn asn; + eprintf "@ @[could not match"; + List.iter (fun p -> + eprintf "@ + @[%s@]" + (show_pattern p)) ps; + eprintf "@]" + end; + eprintf "@]@."; + raise FuzzError + end) + done; + Format.eprintf "@." + + diff --git a/vendor/qbe/tools/mgen/main.ml b/vendor/qbe/tools/mgen/main.ml new file mode 100644 index 0000000..fb49259 --- /dev/null +++ b/vendor/qbe/tools/mgen/main.ml @@ -0,0 +1,214 @@ +open Cgen +open Match + +let mgen ~verbose ~fuzz path lofs input oc = + let info ?(level = 1) fmt = + if level <= verbose then + Printf.eprintf fmt + else + Printf.ifprintf stdout fmt + in + + let rules = + match Sexp.(run_parser ppats) input with + | `Error (ps, err, loc) -> + Printf.eprintf "%s:%d:%d %s\n" + path (lofs + ps.Sexp.line) ps.Sexp.coln err; + Printf.eprintf "%s" loc; + exit 1 + | `Ok rules -> rules + in + + info "adding ac variants...%!"; + let nparsed = + List.fold_left + (fun npats (_, _, ps) -> + npats + List.length ps) + 0 rules + in + let varsmap = Hashtbl.create 10 in + let rules = + List.concat_map (fun (name, vars, patterns) -> + (try assert (Hashtbl.find varsmap name = vars) + with Not_found -> ()); + Hashtbl.replace varsmap name vars; + List.map + (fun pattern -> {name; vars; pattern}) + (List.concat_map ac_equiv patterns) + ) rules + in + info " %d -> %d patterns\n" + nparsed (List.length rules); + + let rnames = + setify (List.map (fun r -> r.name) rules) in + + info "generating match tables...%!"; + let sa, am, sm = generate_table rules in + let numbr = make_numberer sa am sm in + info " %d states, %d rules\n" + (Array.length sa) (StateMap.cardinal sm); + if verbose >= 2 then begin + info "-------------\nstates:\n"; + Array.iteri (fun i s -> + info " state %d: %s\n" + i (show_pattern s.seen)) sa; + info "-------------\nstatemap:\n"; + Test.print_sm stderr sm; + info "-------------\n"; + end; + + info "generating matchers...\n"; + let matchers = + List.map (fun rname -> + info "+ %s...%!" rname; + let m = lr_matcher sm sa rules rname in + let vars = Hashtbl.find varsmap rname in + info " %d nodes\n" (Action.size m); + info ~level:2 " -------------\n"; + info ~level:2 " automaton:\n"; + info ~level:2 "%s\n" + (Format.asprintf " @[%a@]" Action.pp m); + info ~level:2 " ----------\n"; + (vars, rname, m) + ) rnames + in + + if fuzz then begin + info ~level:0 "fuzzing statemap...\n"; + let tp = Fuzz.fuzz_numberer rules numbr in + info ~level:0 "testing %d patterns...\n" + (List.length rules); + Fuzz.test_matchers tp numbr rules + end; + + info "emitting C...\n"; + flush stderr; + + let cgopts = + { pfx = ""; static = true; oc = oc } in + emit_c cgopts numbr; + emit_matchers cgopts matchers; + + () + +let read_all ic = + let bufsz = 4096 in + let buf = Bytes.create bufsz in + let data = Buffer.create bufsz in + let read = ref 0 in + while + read := input ic buf 0 bufsz; + !read <> 0 + do + Buffer.add_subbytes data buf 0 !read + done; + Buffer.contents data + +let split_c src = + let begin_re, eoc_re, end_re = + let re = Str.regexp in + ( re "mgen generated code" + , re "\\*/" + , re "end of generated code" ) + in + let str_match regexp str = + try + let _: int = + Str.search_forward regexp str 0 + in true + with Not_found -> false + in + + let rec go st lofs pfx rules lines = + let line, lines = + match lines with + | [] -> + failwith ( + match st with + | `Prefix -> "could not find mgen section" + | `Rules -> "mgen rules not terminated" + | `Skip -> "mgen section not terminated" + ) + | l :: ls -> (l, ls) + in + match st with + | `Prefix -> + let pfx = line :: pfx in + if str_match begin_re line + then + let lofs = List.length pfx in + go `Rules lofs pfx rules lines + else go `Prefix 0 pfx rules lines + | `Rules -> + let pfx = line :: pfx in + if str_match eoc_re line + then go `Skip lofs pfx rules lines + else go `Rules lofs pfx (line :: rules) lines + | `Skip -> + if str_match end_re line then + let join = String.concat "\n" in + let pfx = join (List.rev pfx) ^ "\n\n" + and rules = join (List.rev rules) + and sfx = join (line :: lines) + in (lofs, pfx, rules, sfx) + else go `Skip lofs pfx rules lines + in + + let lines = String.split_on_char '\n' src in + go `Prefix 0 [] [] lines + +let () = + let usage_msg = + "mgen [--fuzz] [--verbose ] " in + + let fuzz_arg = ref false in + let verbose_arg = ref 0 in + let input_paths = ref [] in + + let anon_fun filename = + input_paths := filename :: !input_paths in + + let speclist = + [ ( "--fuzz", Arg.Set fuzz_arg + , " Fuzz tables and matchers" ) + ; ( "--verbose", Arg.Set_int verbose_arg + , " Set verbosity level" ) + ; ( "--", Arg.Rest_all (List.iter anon_fun) + , " Stop argument parsing" ) ] + in + Arg.parse speclist anon_fun usage_msg; + + let input_paths = !input_paths in + let verbose = !verbose_arg in + let fuzz = !fuzz_arg in + let input_path, input = + match input_paths with + | ["-"] -> ("-", read_all stdin) + | [path] -> (path, read_all (open_in path)) + | _ -> + Printf.eprintf + "%s: single input file expected\n" + Sys.argv.(0); + Arg.usage speclist usage_msg; exit 1 + in + let mgen = mgen ~verbose ~fuzz in + + if Str.last_chars input_path 2 <> ".c" + then mgen input_path 0 input stdout + else + let tmp_path = input_path ^ ".tmp" in + Fun.protect + ~finally:(fun () -> + try Sys.remove tmp_path with _ -> ()) + (fun () -> + let lofs, pfx, rules, sfx = split_c input in + let oc = open_out tmp_path in + output_string oc pfx; + mgen input_path lofs rules oc; + output_string oc sfx; + close_out oc; + Sys.rename tmp_path input_path; + ()); + + () diff --git a/vendor/qbe/tools/mgen/match.ml b/vendor/qbe/tools/mgen/match.ml new file mode 100644 index 0000000..9c02ca4 --- /dev/null +++ b/vendor/qbe/tools/mgen/match.ml @@ -0,0 +1,651 @@ +type cls = Kw | Kl | Ks | Kd +type op_base = + | Oadd + | Osub + | Omul + | Oor + | Oshl + | Oshr +type op = cls * op_base + +let op_bases = + [Oadd; Osub; Omul; Oor; Oshl; Oshr] + +let commutative = function + | (_, (Oadd | Omul | Oor)) -> true + | (_, _) -> false + +let associative = function + | (_, (Oadd | Omul | Oor)) -> true + | (_, _) -> false + +type atomic_pattern = + | Tmp + | AnyCon + | Con of int64 +(* Tmp < AnyCon < Con k *) + +type pattern = + | Bnr of op * pattern * pattern + | Atm of atomic_pattern + | Var of string * atomic_pattern + +let is_atomic = function + | (Atm _ | Var _) -> true + | _ -> false + +let show_op_base o = + match o with + | Oadd -> "add" + | Osub -> "sub" + | Omul -> "mul" + | Oor -> "or" + | Oshl -> "shl" + | Oshr -> "shr" + +let show_op (k, o) = + show_op_base o ^ + (match k with + | Kw -> "w" + | Kl -> "l" + | Ks -> "s" + | Kd -> "d") + +let rec show_pattern p = + match p with + | Atm Tmp -> "%" + | Atm AnyCon -> "$" + | Atm (Con n) -> Int64.to_string n + | Var (v, p) -> + show_pattern (Atm p) ^ "'" ^ v + | Bnr (o, pl, pr) -> + "(" ^ show_op o ^ + " " ^ show_pattern pl ^ + " " ^ show_pattern pr ^ ")" + +let get_atomic p = + match p with + | (Atm a | Var (_, a)) -> Some a + | _ -> None + +let rec pattern_match p w = + match p with + | Var (_, p) -> + pattern_match (Atm p) w + | Atm Tmp -> + begin match get_atomic w with + | Some (Con _ | AnyCon) -> false + | _ -> true + end + | Atm (Con _) -> w = p + | Atm (AnyCon) -> + not (pattern_match (Atm Tmp) w) + | Bnr (o, pl, pr) -> + begin match w with + | Bnr (o', wl, wr) -> + o' = o && + pattern_match pl wl && + pattern_match pr wr + | _ -> false + end + +type +'a cursor = (* a position inside a pattern *) + | Bnrl of op * 'a cursor * pattern + | Bnrr of op * pattern * 'a cursor + | Top of 'a + +let rec fold_cursor c p = + match c with + | Bnrl (o, c', p') -> fold_cursor c' (Bnr (o, p, p')) + | Bnrr (o, p', c') -> fold_cursor c' (Bnr (o, p', p)) + | Top _ -> p + +let peel p x = + let once out (p, c) = + match p with + | Var (_, p) -> (Atm p, c) :: out + | Atm _ -> (p, c) :: out + | Bnr (o, pl, pr) -> + (pl, Bnrl (o, c, pr)) :: + (pr, Bnrr (o, pl, c)) :: out + in + let rec go l = + let l' = List.fold_left once [] l in + if List.length l' = List.length l + then l' + else go l' + in go [(p, Top x)] + +let fold_pairs l1 l2 ini f = + let rec go acc = function + | [] -> acc + | a :: l1' -> + go (List.fold_left + (fun acc b -> f (a, b) acc) + acc l2) l1' + in go ini l1 + +let iter_pairs l f = + fold_pairs l l () (fun x () -> f x) + +let inverse l = + List.map (fun (a, b) -> (b, a)) l + +type 'a state = + { id: int + ; seen: pattern + ; point: ('a cursor) list } + +let rec binops side {point; _} = + List.filter_map (fun c -> + match c, side with + | Bnrl (o, c, r), `L -> Some ((o, c), r) + | Bnrr (o, l, c), `R -> Some ((o, c), l) + | _ -> None) + point + +let group_by_fst l = + List.fast_sort (fun (a, _) (b, _) -> + compare a b) l |> + List.fold_left (fun (oo, l, res) (o', c) -> + match oo with + | None -> (Some o', [c], []) + | Some o when o = o' -> (oo, c :: l, res) + | Some o -> (Some o', [c], (o, l) :: res)) + (None, [], []) |> + (function + | (None, _, _) -> [] + | (Some o, l, res) -> (o, l) :: res) + +let sort_uniq cmp l = + List.fast_sort cmp l |> + List.fold_left (fun (eo, l) e' -> + match eo with + | None -> (Some e', l) + | Some e when cmp e e' = 0 -> (eo, l) + | Some e -> (Some e', e :: l)) + (None, []) |> + (function + | (None, _) -> [] + | (Some e, l) -> List.rev (e :: l)) + +let setify l = + sort_uniq compare l + +let normalize (point: ('a cursor) list) = + setify point + +let next_binary tmp s1 s2 = + let pm w (_, p) = pattern_match p w in + let o1 = binops `L s1 |> + List.filter (pm s2.seen) |> + List.map fst in + let o2 = binops `R s2 |> + List.filter (pm s1.seen) |> + List.map fst in + List.map (fun (o, l) -> + o, + { id = -1 + ; seen = Bnr (o, s1.seen, s2.seen) + ; point = normalize (l @ tmp) }) + (group_by_fst (o1 @ o2)) + +type p = string + +module StateSet : sig + type t + val create: unit -> t + val add: t -> p state -> + [> `Added | `Found ] * p state + val iter: t -> (p state -> unit) -> unit + val elems: t -> (p state) list +end = struct + open Hashtbl.Make(struct + type t = p state + let equal s1 s2 = s1.point = s2.point + let hash s = Hashtbl.hash s.point + end) + type nonrec t = + { h: int t + ; mutable next_id: int } + let create () = + { h = create 500; next_id = 0 } + let add set s = + assert (s.point = normalize s.point); + try + let id = find set.h s in + `Found, {s with id} + with Not_found -> begin + let id = set.next_id in + set.next_id <- id + 1; + add set.h s id; + `Added, {s with id} + end + let iter set f = + let f s id = f {s with id} in + iter f set.h + let elems set = + let res = ref [] in + iter set (fun s -> res := s :: !res); + !res +end + +type table_key = + | K of op * p state * p state + +module StateMap = struct + include Map.Make(struct + type t = table_key + let compare ka kb = + match ka, kb with + | K (o, sl, sr), K (o', sl', sr') -> + compare (o, sl.id, sr.id) + (o', sl'.id, sr'.id) + end) + let invert n sm = + let rmap = Array.make n [] in + iter (fun k {id; _} -> + match k with + | K (o, sl, sr) -> + rmap.(id) <- + (o, (sl.id, sr.id)) :: rmap.(id) + ) sm; + Array.map group_by_fst rmap + let by_ops sm = + fold (fun tk s ops -> + match tk with + | K (op, l, r) -> + (op, ((l.id, r.id), s.id)) :: ops) + sm [] |> group_by_fst +end + +type rule = + { name: string + ; vars: string list + ; pattern: pattern } + +let generate_table rl = + let states = StateSet.create () in + let rl = + (* these atomic patterns must occur in + * rules so that we are able to number + * all possible refs *) + [ { name = "$"; vars = [] + ; pattern = Atm AnyCon } + ; { name = "%"; vars = [] + ; pattern = Atm Tmp } ] @ rl + in + (* initialize states *) + let ground = + List.concat_map + (fun r -> peel r.pattern r.name) rl |> + group_by_fst + in + let tmp = List.assoc (Atm Tmp) ground in + let con = List.assoc (Atm AnyCon) ground in + let atoms = ref [] in + let () = + List.iter (fun (seen, l) -> + let point = + if pattern_match (Atm Tmp) seen + then normalize (tmp @ l) + else normalize (con @ l) + in + let s = {id = -1; seen; point} in + let _, s = StateSet.add states s in + match get_atomic seen with + | Some atm -> atoms := (atm, s) :: !atoms + | None -> () + ) ground + in + (* setup loop state *) + let map = ref StateMap.empty in + let map_add k s' = + map := StateMap.add k s' !map + in + let flag = ref `Added in + let flagmerge = function + | `Added -> flag := `Added + | _ -> () + in + (* iterate until fixpoint *) + while !flag = `Added do + flag := `Stop; + let statel = StateSet.elems states in + iter_pairs statel (fun (sl, sr) -> + next_binary tmp sl sr |> + List.iter (fun (o, s') -> + let flag', s' = + StateSet.add states s' in + flagmerge flag'; + map_add (K (o, sl, sr)) s'; + )); + done; + let states = + StateSet.elems states |> + List.sort (fun s s' -> compare s.id s'.id) |> + Array.of_list + in + (states, !atoms, !map) + +let intersperse x l = + let rec go left right out = + let out = + (List.rev left @ [x] @ right) :: + out in + match right with + | x :: right' -> + go (x :: left) right' out + | [] -> out + in go [] l [] + +let rec permute = function + | [] -> [[]] + | x :: l -> + List.concat (List.map + (intersperse x) (permute l)) + +(* build all binary trees with ordered + * leaves l *) +let rec bins build l = + let rec go l r out = + match r with + | [] -> out + | x :: r' -> + go (l @ [x]) r' + (fold_pairs + (bins build l) + (bins build r) + out (fun (l, r) out -> + build l r :: out)) + in + match l with + | [] -> [] + | [x] -> [x] + | x :: l -> go [x] l [] + +let products l ini f = + let rec go acc la = function + | [] -> f (List.rev la) acc + | xs :: l -> + List.fold_left (fun acc x -> + go acc (x :: la) l) + acc xs + in go ini [] l + +(* combinatorial nuke... *) +let rec ac_equiv = + let rec alevel o = function + | Bnr (o', l, r) when o' = o -> + alevel o l @ alevel o r + | x -> [x] + in function + | Bnr (o, _, _) as p + when associative o -> + products + (List.map ac_equiv (alevel o p)) [] + (fun choice out -> + List.concat_map + (bins (fun l r -> Bnr (o, l, r))) + (if commutative o + then permute choice + else [choice]) @ out) + | Bnr (o, l, r) + when commutative o -> + fold_pairs + (ac_equiv l) (ac_equiv r) [] + (fun (l, r) out -> + Bnr (o, l, r) :: + Bnr (o, r, l) :: out) + | Bnr (o, l, r) -> + fold_pairs + (ac_equiv l) (ac_equiv r) [] + (fun (l, r) out -> + Bnr (o, l, r) :: out) + | x -> [x] + +module Action: sig + type node = + | Switch of (int * t) list + | Push of bool * t + | Pop of t + | Set of string * t + | Stop + and t = private + { id: int; node: node } + val equal: t -> t -> bool + val size: t -> int + val stop: t + val mk_push: sym:bool -> t -> t + val mk_pop: t -> t + val mk_set: string -> t -> t + val mk_switch: int list -> (int -> t) -> t + val pp: Format.formatter -> t -> unit +end = struct + type node = + | Switch of (int * t) list + | Push of bool * t + | Pop of t + | Set of string * t + | Stop + and t = + { id: int; node: node } + + let equal a a' = a.id = a'.id + let size a = + let seen = Hashtbl.create 10 in + let rec node_size = function + | Switch l -> + List.fold_left + (fun n (_, a) -> n + size a) 0 l + | (Push (_, a) | Pop a | Set (_, a)) -> + size a + | Stop -> 0 + and size {id; node} = + if Hashtbl.mem seen id + then 0 + else begin + Hashtbl.add seen id (); + 1 + node_size node + end + in + size a + + let mk = + let hcons = Hashtbl.create 100 in + let fresh = ref 0 in + fun node -> + let id = + try Hashtbl.find hcons node + with Not_found -> + let id = !fresh in + Hashtbl.add hcons node id; + fresh := id + 1; + id + in + {id; node} + let stop = mk Stop + let mk_push ~sym a = mk (Push (sym, a)) + let mk_pop a = + match a.node with + | Stop -> a + | _ -> mk (Pop a) + let mk_set v a = mk (Set (v, a)) + let mk_switch ids f = + match List.map f ids with + | [] -> failwith "empty switch"; + | c :: cs as cases -> + if List.for_all (equal c) cs then c + else + let cases = List.combine ids cases in + mk (Switch cases) + + open Format + let rec pp_node fmt = function + | Switch l -> + fprintf fmt "@[@[switch{"; + let pp_case (c, a) = + let pp_sep fmt () = fprintf fmt "," in + fprintf fmt "@,@[<2>→%a:@ @[%a@]@]" + (pp_print_list ~pp_sep pp_print_int) + c pp a + in + inverse l |> group_by_fst |> inverse |> + List.iter pp_case; + fprintf fmt "@]@,}@]" + | Push (true, a) -> fprintf fmt "pushsym@ %a" pp a + | Push (false, a) -> fprintf fmt "push@ %a" pp a + | Pop a -> fprintf fmt "pop@ %a" pp a + | Set (v, a) -> fprintf fmt "set(%s)@ %a" v pp a + | Stop -> fprintf fmt "•" + and pp fmt a = pp_node fmt a.node +end + +(* a state is commutative if (a op b) enters + * it iff (b op a) enters it as well *) +let symmetric rmap id = + List.for_all (fun (_, l) -> + let l1, l2 = + List.filter (fun (a, b) -> a <> b) l |> + List.partition (fun (a, b) -> a < b) + in + setify l1 = setify (inverse l2)) + rmap.(id) + +(* left-to-right matching of a set of patterns; + * may raise if there is no lr matcher for the + * input rule *) +let lr_matcher statemap states rules name = + let rmap = + let nstates = Array.length states in + StateMap.invert nstates statemap + in + let exception Stuck in + (* the list of ids represents a class of terms + * whose root ends up being labelled with one + * such id; the gen function generates a matcher + * that will, given any such term, assign values + * for the Var nodes of one pattern in pats *) + let rec gen + : 'a. int list -> (pattern * 'a) list + -> (int -> (pattern * 'a) list -> Action.t) + -> Action.t + = fun ids pats k -> + Action.mk_switch (setify ids) @@ fun id_top -> + let sym = symmetric rmap id_top in + let id_ops = + if sym then + let ordered (a, b) = a <= b in + List.map (fun (o, l) -> + (o, List.filter ordered l)) + rmap.(id_top) + else rmap.(id_top) + in + (* consider only the patterns that are + * compatible with the current id *) + let atm_pats, bin_pats = + List.filter (function + | Bnr (o, _, _), _ -> + List.exists + (fun (o', _) -> o' = o) + id_ops + | _ -> true) pats |> + List.partition + (fun (pat, _) -> is_atomic pat) + in + try + if bin_pats = [] then raise Stuck; + let pats_l = + List.map (function + | (Bnr (o, l, r), x) -> + (l, (o, x, r)) + | _ -> assert false) + bin_pats + and pats_r = + List.map (fun (l, (o, x, r)) -> + (r, (o, l, x))) + and patstop = + List.map (fun (r, (o, l, x)) -> + (Bnr (o, l, r), x)) + in + let id_pairs = List.concat_map snd id_ops in + let ids_l = List.map fst id_pairs + and ids_r id_left = + List.filter_map (fun (l, r) -> + if l = id_left then Some r else None) + id_pairs + in + (* match the left arm *) + Action.mk_push ~sym + (gen ids_l pats_l + @@ fun lid pats -> + (* then the right arm, considering + * only the remaining possible + * patterns and knowing that the + * left arm was numbered 'lid' *) + Action.mk_pop + (gen (ids_r lid) (pats_r pats) + @@ fun _rid pats -> + (* continue with the parent *) + k id_top (patstop pats))) + with Stuck -> + let atm_pats = + let seen = states.(id_top).seen in + List.filter (fun (pat, _) -> + pattern_match pat seen) atm_pats + in + if atm_pats = [] then raise Stuck else + let vars = + List.filter_map (function + | (Var (v, _), _) -> Some v + | _ -> None) atm_pats |> setify + in + match vars with + | [] -> k id_top atm_pats + | [v] -> Action.mk_set v (k id_top atm_pats) + | _ -> failwith "ambiguous var match" + in + (* generate a matcher for the rule *) + let ids_top = + Array.to_list states |> + List.filter_map (fun {id; point = p; _} -> + if List.exists ((=) (Top name)) p then + Some id + else None) + in + let rec filter_dups pats = + match pats with + | p :: pats -> + if List.exists (pattern_match p) pats + then filter_dups pats + else p :: filter_dups pats + | [] -> [] + in + let pats_top = + List.filter_map (fun r -> + if r.name = name then + Some r.pattern + else None) rules |> + filter_dups |> + List.map (fun p -> (p, ())) + in + gen ids_top pats_top (fun _ pats -> + assert (pats <> []); + Action.stop) + +type numberer = + { atoms: (atomic_pattern * p state) list + ; statemap: p state StateMap.t + ; states: p state array + ; mutable ops: op list + (* memoizes the list of possible operations + * according to the statemap *) } + +let make_numberer sa am sm = + { atoms = am + ; states = sa + ; statemap = sm + ; ops = [] } + +let atom_state n atm = + List.assoc atm n.atoms diff --git a/vendor/qbe/tools/mgen/sexp.ml b/vendor/qbe/tools/mgen/sexp.ml new file mode 100644 index 0000000..624619e --- /dev/null +++ b/vendor/qbe/tools/mgen/sexp.ml @@ -0,0 +1,292 @@ +type pstate = + { data: string + ; line: int + ; coln: int + ; indx: int } + +type perror = + { error: string + ; ps: pstate } + +exception ParseError of perror + +type 'a parser = + { fn: 'r. pstate -> ('a -> pstate -> 'r) -> 'r } + +let update_pos ps beg fin = + let l, c = (ref ps.line, ref ps.coln) in + for i = beg to fin - 1 do + if ps.data.[i] = '\n' then + (incr l; c := 0) + else + incr c + done; + { ps with line = !l; coln = !c } + +let pret (type a) (x: a): a parser = + let fn ps k = k x ps in { fn } + +let pfail error: 'a parser = + let fn ps _ = raise (ParseError {error; ps}) + in { fn } + +let por: 'a parser -> 'a parser -> 'a parser = + fun p1 p2 -> + let fn ps k = + try p1.fn ps k with ParseError e1 -> + try p2.fn ps k with ParseError e2 -> + if e1.ps.indx > e2.ps.indx then + raise (ParseError e1) + else + raise (ParseError e2) + in { fn } + +let pbind: 'a parser -> ('a -> 'b parser) -> 'b parser = + fun p1 p2 -> + let fn ps k = + p1.fn ps (fun x ps -> (p2 x).fn ps k) + in { fn } + +(* handy for recursive rules *) +let papp p x = pbind (pret x) p + +let psnd: 'a parser -> 'b parser -> 'b parser = + fun p1 p2 -> pbind p1 (fun _x -> p2) + +let pfst: 'a parser -> 'b parser -> 'a parser = + fun p1 p2 -> pbind p1 (fun x -> psnd p2 (pret x)) + +module Infix = struct + let ( let* ) = pbind + let ( ||| ) = por + let ( |<< ) = pfst + let ( |>> ) = psnd +end + +open Infix + +let pre: ?what:string -> string -> string parser = + fun ?what re -> + let what = + match what with + | None -> Printf.sprintf "%S" re + | Some what -> what + and re = Str.regexp re in + let fn ps k = + if not (Str.string_match re ps.data ps.indx) then + (let error = + Printf.sprintf "expected to match %s" what in + raise (ParseError {error; ps})); + let ps = + let indx = Str.match_end () in + { (update_pos ps ps.indx indx) with indx } + in + k (Str.matched_string ps.data) ps + in { fn } + +let peoi: unit parser = + let fn ps k = + if ps.indx <> String.length ps.data then + raise (ParseError + { error = "expected end of input"; ps }); + k () ps + in { fn } + +let pws = pre "[ \r\n\t*]*" +let pws1 = pre "[ \r\n\t*]+" + +let pthen p1 p2 = + let* x1 = p1 in + let* x2 = p2 in + pret (x1, x2) + +let rec plist_tail: 'a parser -> ('a list) parser = + fun pitem -> + (pws |>> pre ")" |>> pret []) ||| + (let* itm = pitem in + let* itms = plist_tail pitem in + pret (itm :: itms)) + +let plist pitem = + pws |>> pre ~what:"a list" "(" + |>> plist_tail pitem + +let plist1p p1 pitem = + pws |>> pre ~what:"a list" "(" + |>> pthen p1 (plist_tail pitem) + +let ppair p1 p2 = + pws |>> pre ~what:"a pair" "(" + |>> pthen p1 p2 |<< pws |<< pre ")" + +let run_parser p s = + let ps = + {data = s; line = 1; coln = 0; indx = 0} in + try `Ok (p.fn ps (fun res _ps -> res)) + with ParseError e -> + let rec bol i = + if i = 0 then i else + if i < String.length s && s.[i] = '\n' + then i+1 (* XXX BUG *) + else bol (i-1) + in + let rec eol i = + if i = String.length s then i else + if s.[i] = '\n' then i else + eol (i+1) + in + let bol = bol e.ps.indx in + let eol = eol e.ps.indx in + (* + Printf.eprintf "bol:%d eol:%d indx:%d len:%d\n" + bol eol e.ps.indx (String.length s); (* XXX debug *) + *) + let lines = + String.split_on_char '\n' + (String.sub s bol (eol - bol)) + in + let nl = List.length lines in + let caret = ref (e.ps.indx - bol) in + let msg = ref [] in + let pfx = " > " in + lines |> List.iteri (fun ln l -> + if ln <> nl - 1 || l <> "" then begin + let ll = String.length l + 1 in + msg := (pfx ^ l ^ "\n") :: !msg; + if !caret <= ll then begin + let pad = String.make !caret ' ' in + msg := (pfx ^ pad ^ "^\n") :: !msg; + end; + caret := !caret - ll; + end; + ); + `Error + ( e.ps, e.error + , String.concat "" (List.rev !msg) ) + +(* ---------------------------------------- *) +(* pattern parsing *) +(* ---------------------------------------- *) +(* Example syntax: + + (with-vars (a b c d) + (patterns + (ob (add (tmp a) (con d))) + (bsm (add (tmp b) (mul (tmp m) (con 2 4 8)))) )) + *) +open Match + +let pint64 = + let* s = pre "[-]?[0-9_]+" in + pret (Int64.of_string s) + +let pid = + pre ~what:"an identifer" + "[a-zA-Z][a-zA-Z0-9_]*" + +let pop_base = + let sob, obs = show_op_base, op_bases in + let* s = pre ~what:"an operator" + (String.concat "\\|" (List.map sob obs)) + in pret (List.find (fun o -> s = sob o) obs) + +let pop = let* ob = pop_base in pret (Kl, ob) + +let rec ppat vs = + let pcons_tail = + let* cs = plist_tail (pws1 |>> pint64) in + match cs with + | [] -> pret [AnyCon] + | _ -> pret (List.map (fun c -> Con c) cs) + in + let pvar = + let* id = pid in + if not (List.mem id vs) then + pfail ("unbound variable: " ^ id) + else + pret id + in + pws |>> ( + ( let* c = pint64 in pret [Atm (Con c)] ) + ||| + ( pre "(con)" |>> pret [Atm AnyCon] ) ||| + ( let* cs = pre "(con" |>> pcons_tail in + pret (List.map (fun c -> Atm c) cs) ) ||| + ( let* v = pre "(con" |>> pws1 |>> pvar in + let* cs = pcons_tail in + pret (List.map (fun c -> Var (v, c)) cs) ) + ||| + ( pre "(tmp)" |>> pret [Atm Tmp] ) ||| + ( let* v = pre "(tmp" |>> pws1 |>> pvar in + pws |>> pre ")" |>> pret [Var (v, Tmp)] ) + ||| + ( let* (op, rands) = + plist1p (pws |>> pop) (papp ppat vs) in + let nrands = List.length rands in + if nrands < 2 then + pfail ( "binary op requires at least" + ^ " two arguments" ) + else + let mk x y = Bnr (op, x, y) in + pret + (products rands [] + (fun rands pats -> + (* construct a left-heavy tree *) + let r0 = List.hd rands in + let rs = List.tl rands in + List.fold_left mk r0 rs :: pats)) ) + ) + +let pwith_vars ?(vs = []) p = + ( let* vs = + pws |>> pre "(with-vars" |>> pws |>> + plist (pws |>> pid) + in pws |>> p vs |<< pws |<< pre ")" ) + ||| p vs + +let ppats = + pwith_vars @@ fun vs -> + pre "(patterns" |>> plist_tail + (pwith_vars ~vs @@ fun vs -> + let* n, ps = ppair pid (ppat vs) in + pret (n, vs, ps)) + +(* ---------------------------------------- *) +(* tests *) +(* ---------------------------------------- *) + +let () = + if false then + let show_patterns ps = + "[" ^ String.concat "; " + (List.map show_pattern ps) ^ "]" + in + let pat s = + Printf.printf "parse %s = " s; + let vars = + [ "foobar"; "a"; "b"; "d" + ; "m"; "s"; "x" ] + in + match run_parser (ppat vars) s with + | `Ok p -> + Printf.printf "%s\n" (show_patterns p) + | `Error (_, e, _) -> + Printf.printf "ERROR: %s\n" e + in + pat "42"; + pat "(tmp)"; + pat "(tmp foobar)"; + pat "(con)"; + pat "(con 1 2 3)"; + pat "(con x 1 2 3)"; + pat "(add 1 2)"; + pat "(add 1 2 3 4)"; + pat "(sub 1 2)"; + pat "(sub 1 2 3)"; + pat "(tmp unbound_var)"; + pat "(add 0)"; + pat "(add 1 (add 2 3))"; + pat "(add (tmp a) (con d))"; + pat "(add (tmp b) (mul (tmp m) (con s 2 4 8)))"; + pat "(add (con 1 2) (con 3 4))"; + () diff --git a/vendor/qbe/tools/mgen/test.ml b/vendor/qbe/tools/mgen/test.ml new file mode 100644 index 0000000..9ef40b9 --- /dev/null +++ b/vendor/qbe/tools/mgen/test.ml @@ -0,0 +1,134 @@ +open Match +open Fuzz +open Cgen + +(* unit tests *) + +let test_pattern_match = + let pm = pattern_match + and nm = fun x y -> not (pattern_match x y) in + begin + assert (nm (Atm Tmp) (Atm (Con 42L))); + assert (pm (Atm AnyCon) (Atm (Con 42L))); + assert (nm (Atm (Con 42L)) (Atm AnyCon)); + assert (nm (Atm (Con 42L)) (Atm Tmp)); + end + +let test_peel = + let o = Kw, Oadd in + let p = Bnr (o, Bnr (o, Atm Tmp, Atm Tmp), + Atm (Con 42L)) in + let l = peel p () in + let () = assert (List.length l = 3) in + let atomic_p (p, _) = + match p with Atm _ -> true | _ -> false in + let () = assert (List.for_all atomic_p l) in + let l = List.map (fun (p, c) -> fold_cursor c p) l in + let () = assert (List.for_all ((=) p) l) in + () + +let test_fold_pairs = + let l = [1; 2; 3; 4; 5] in + let p = fold_pairs l l [] (fun a b -> a :: b) in + let () = assert (List.length p = 25) in + let p = sort_uniq compare p in + let () = assert (List.length p = 25) in + () + +(* test pattern & state *) + +let print_sm oc = + StateMap.iter (fun k s' -> + match k with + | K (o, sl, sr) -> + let top = + List.fold_left (fun top c -> + match c with + | Top r -> top ^ " " ^ r + | _ -> top) "" s'.point + in + Printf.fprintf oc + " (%s %d %d) -> %d%s\n" + (show_op o) + sl.id sr.id s'.id top) + +let rules = + let oa = Kl, Oadd in + let om = Kl, Omul in + let va = Var ("a", Tmp) + and vb = Var ("b", Tmp) + and vc = Var ("c", Tmp) + and vs = Var ("s", Tmp) in + let vars = ["a"; "b"; "c"; "s"] in + let rule name pattern = + List.map + (fun pattern -> {name; vars; pattern}) + (ac_equiv pattern) + in + match `X64Addr with + (* ------------------------------- *) + | `X64Addr -> + (* o + b *) + rule "ob" (Bnr (oa, Atm Tmp, Atm AnyCon)) + @ (* b + s * m *) + rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 2L), vs))) + @ + rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 4L), vs))) + @ + rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 8L), vs))) + @ (* b + s *) + rule "bs1" (Bnr (oa, vb, vs)) + @ (* o + s * m *) + (* rule "osm" (Bnr (oa, Atm AnyCon, Bnr (om, Atm (Con 4L), Atm Tmp))) *) [] + @ (* o + b + s *) + rule "obs1" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb), vs)) + @ (* o + b + s * m *) + rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb), + Bnr (om, Var ("m", Con 2L), vs))) + @ + rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb), + Bnr (om, Var ("m", Con 4L), vs))) + @ + rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb), + Bnr (om, Var ("m", Con 8L), vs))) + (* ------------------------------- *) + | `Add3 -> + [ { name = "add" + ; vars = [] + ; pattern = Bnr (oa, va, Bnr (oa, vb, vc)) } ] @ + [ { name = "add" + ; vars = [] + ; pattern = Bnr (oa, Bnr (oa, va, vb), vc) } ] + +(* + +let sa, am, sm = generate_table rules +let () = + Array.iteri (fun i s -> + Format.printf "@[state %d: %s@]@." + i (show_pattern s.seen)) + sa +let () = print_sm stdout sm; flush stdout + +let matcher = lr_matcher sm sa rules "obsm" (* XXX *) +let () = Format.printf "@[%a@]@." Action.pp matcher +let () = Format.printf "@[matcher size: %d@]@." (Action.size matcher) + +let numbr = make_numberer sa am sm + +let () = + let opts = { pfx = "" + ; static = true + ; oc = stdout } in + emit_c opts numbr; + emit_matchers opts + [ ( ["b"; "o"; "s"; "m"] + , "obsm" + , matcher ) ] + +(* +let tp = fuzz_numberer rules numbr +let () = test_matchers tp numbr rules +*) + +*) diff --git a/vendor/qbe/tools/test.sh b/vendor/qbe/tools/test.sh index 23c6663..0df297f 100755 --- a/vendor/qbe/tools/test.sh +++ b/vendor/qbe/tools/test.sh @@ -1,8 +1,12 @@ #!/bin/sh dir=`dirname "$0"` -bin=$dir/../qbe -binref=$dir/../qbe.ref +if [ -z "${bin:-}" ]; then + bin=$dir/../qbe +fi +if [ -z "${binref:-}" ]; then + binref=${bin}.ref +fi tmp=/tmp/qbe.zzzz @@ -12,55 +16,98 @@ asmref=$tmp.ref.s exe=$tmp.exe out=$tmp.out +qemu_not_needed() { + "$@" +} + +cc= +find_cc_and_qemu() { + if [ -n "$cc" ]; then + return + fi + target="$1" + candidate_cc="$2" + if $candidate_cc -v >/dev/null 2>&1; then + cc=$candidate_cc + echo "cc: $cc" + + 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 + 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" + fi + fi + echo + + fi +} + init() { case "$TARGET" in arm64) for p in aarch64-linux-musl aarch64-linux-gnu do - cc="$p-gcc -no-pie -static" - qemu="qemu-aarch64" - if - $cc -v >/dev/null 2>&1 && - $qemu -version >/dev/null 2>&1 - then - if sysroot=$($cc -print-sysroot) && test -n "$sysroot" - then - qemu="$qemu -L $sysroot" - fi - break - fi - cc= + find_cc_and_qemu aarch64 "$p-gcc -no-pie -static" "qemu-aarch64" done - if test -z "$cc" + if test -z "$cc" -o -z "$qemu" then echo "Cannot find arm64 compiler or qemu." - exit 1 + exit 77 fi bin="$bin -t arm64" ;; rv64) for p in riscv64-linux-musl riscv64-linux-gnu do - cc="$p-gcc -no-pie -static" - qemu="qemu-riscv64" - if - $cc -v >/dev/null 2>&1 && - $qemu -version >/dev/null 2>&1 - then - if sysroot=$($cc -print-sysroot) && test -n "$sysroot" - then - qemu="$qemu -L $sysroot" - fi - break - fi - cc= + find_cc_and_qemu riscv64 "$p-gcc -no-pie -static" "qemu-riscv64" + done + if test -z "$cc" -o -z "$qemu" + then + echo "Cannot find riscv64 compiler or qemu." + exit 77 + fi + bin="$bin -t rv64" + ;; + x86_64) + for p in x86_64-linux-musl x86_64-linux-gnu + do + find_cc_and_qemu x86_64 "$p-gcc -no-pie -static" "qemu-x86_64" + done + if test -z "$cc" -o -z "$qemu" + then + echo "Cannot find x86_64 compiler or qemu." + exit 77 + 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 riscv64 compiler or qemu." + echo "Cannot find windows compiler or wine." exit 1 fi - bin="$bin -t rv64" + export WINEDEBUG=-all + bin="$bin -t amd64_win" ;; "") case `uname` in @@ -82,7 +129,7 @@ init() { ;; *) echo "Unknown target '$TARGET'." - exit 1 + exit 77 ;; esac } @@ -157,7 +204,7 @@ once() { if test -s $out then - $qemu $exe a b c | diff -u - $out + $qemu $exe a b c | tr -d '\r' | diff -u - $out ret=$? reason="output" else diff --git a/vendor/qbe/util.c b/vendor/qbe/util.c index 362fa98..00bd92b 100644 --- a/vendor/qbe/util.c +++ b/vendor/qbe/util.c @@ -155,13 +155,38 @@ vgrow(void *vp, ulong len) } void -strf(char str[NString], char *s, ...) +addins(Ins **pvins, uint *pnins, Ins *i) +{ + if (i->op == Onop) + return; + vgrow(pvins, ++(*pnins)); + (*pvins)[(*pnins)-1] = *i; +} + +void +addbins(Ins **pvins, uint *pnins, Blk *b) +{ + Ins *i; + + for (i=b->ins; i<&b->ins[b->nins]; i++) + addins(pvins, pnins, i); +} + +char * +strf(Pool pool, char *s, ...) { va_list ap; + int n; + char *p; va_start(ap, s); - vsnprintf(str, NString, s, ap); + n = vsnprintf(NULL, 0, s, ap); va_end(ap); + p = (pool == PFn ? alloc : emalloc)(n + 1); + va_start(ap, s); + vsnprintf(p, n + 1, s, ap); + va_end(ap); + return p; } uint32_t @@ -229,6 +254,62 @@ iscmp(int op, int *pk, int *pc) return 1; } +void +igroup(Blk *b, Ins *i, Ins **i0, Ins **i1) +{ + Ins *ib, *ie; + + ib = b->ins; + ie = ib + b->nins; + switch (i->op) { + case Oblit0: + *i0 = i; + *i1 = i + 2; + return; + case Oblit1: + *i0 = i - 1; + *i1 = i + 1; + return; + case_Opar: + for (; i>ib && ispar((i-1)->op); i--) + ; + *i0 = i; + for (; iop); i++) + ; + *i1 = i; + return; + case Ocall: + case_Oarg: + for (; i>ib && isarg((i-1)->op); i--) + ; + *i0 = i; + for (; iop != Ocall; i++) + ; + 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 (; iop == Osel1; i++) + ; + *i1 = i; + return; + default: + if (ispar(i->op)) + goto case_Opar; + if (isarg(i->op)) + goto case_Oarg; + *i0 = i; + *i1 = i + 1; + return; + } +} + int argcls(Ins *i, int n) { @@ -253,50 +334,43 @@ emiti(Ins i) } void -idup(Ins **pd, Ins *s, ulong n) +idup(Blk *b, Ins *s, ulong n) { - *pd = alloc(n * sizeof(Ins)); - if (n) - memcpy(*pd, s, n * sizeof(Ins)); + vgrow(&b->ins, n); + icpy(b->ins, s, n); + b->nins = n; } Ins * icpy(Ins *d, Ins *s, ulong n) { if (n) - memcpy(d, s, n * sizeof(Ins)); + memmove(d, s, n * sizeof(Ins)); return d + 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] = {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}, + /* 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}, }; -int -cmpneg(int c) -{ - assert(0 <= c && c < NCmp); - return cmptab[c][0]; -} - int cmpop(int c) { @@ -304,6 +378,16 @@ cmpop(int c) return cmptab[c][1]; } +int +cmpwlneg(int op) +{ + if (INRANGE(op, Ocmpw, Ocmpw1)) + return cmptab[op - Ocmpw][0] + Ocmpw; + if (INRANGE(op, Ocmpl, Ocmpl1)) + return cmptab[op - Ocmpl][0] + Ocmpl; + die("not a wl comparison"); +} + int clsmerge(short *pk, short k) { @@ -334,6 +418,28 @@ phicls(int t, Tmp *tmp) return t1; } +uint +phiargn(Phi *p, Blk *b) +{ + uint n; + + if (p) + for (n=0; nnarg; n++) + if (p->blk[n] == b) + return n; + return -1; +} + +Ref +phiarg(Phi *p, Blk *b) +{ + uint n; + + n = phiargn(p, b); + assert(n != -1u && "block not found"); + return p->arg[n]; +} + Ref newtmp(char *prfx, int k, Fn *fn) { @@ -344,7 +450,7 @@ newtmp(char *prfx, int k, Fn *fn) vgrow(&fn->tmp, fn->ntmp); memset(&fn->tmp[t], 0, sizeof(Tmp)); if (prfx) - strf(fn->tmp[t].name, "%s.%d", prfx, ++n); + fn->tmp[t].name = strf(PFn, "%s.%d", prfx, ++n); fn->tmp[t].cls = k; fn->tmp[t].slot = -1; fn->tmp[t].nuse = +1; @@ -398,22 +504,40 @@ getcon(int64_t val, Fn *fn) } int -addcon(Con *c0, Con *c1) +addcon(Con *c0, Con *c1, int m) { - if (c0->type == CUndef) + if (m != 1 && c1->type == CAddr) + return 0; + if (c0->type == CUndef) { *c0 = *c1; - else { + c0->bits.i *= m; + } else { if (c1->type == CAddr) { if (c0->type == CAddr) return 0; c0->type = CAddr; c0->sym = c1->sym; } - c0->bits.i += c1->bits.i; + c0->bits.i += c1->bits.i * m; } return 1; } +int +isconbits(Fn *fn, Ref r, int64_t *v) +{ + Con *c; + + if (rtype(r) == RCon) { + c = &fn->con[r.val]; + if (c->type == CBits) { + *v = c->bits.i; + return 1; + } + } + return 0; +} + void salloc(Ref rt, Ref rs, Fn *fn) { @@ -594,3 +718,57 @@ dumpts(BSet *bs, Tmp *tmp, FILE *f) fprintf(f, " %s", tmp[t].name); fprintf(f, " ]\n"); } + +void +runmatch(uchar *code, Num *tn, Ref ref, Ref *var) +{ + Ref stkbuf[20], *stk; + uchar *s, *pc; + int bc, i; + int n, nl, nr; + + assert(rtype(ref) == RTmp); + stk = stkbuf; + pc = code; + while ((bc = *pc)) + switch (bc) { + case 1: /* pushsym */ + case 2: /* push */ + assert(stk < &stkbuf[20]); + assert(rtype(ref) == RTmp); + nl = tn[ref.val].nl; + nr = tn[ref.val].nr; + if (bc == 1 && nl > nr) { + *stk++ = tn[ref.val].l; + ref = tn[ref.val].r; + } else { + *stk++ = tn[ref.val].r; + ref = tn[ref.val].l; + } + pc++; + break; + case 3: /* set */ + var[*++pc] = ref; + if (*(pc + 1) == 0) + return; + /* fall through */ + case 4: /* pop */ + assert(stk > &stkbuf[0]); + ref = *--stk; + pc++; + break; + case 5: /* switch */ + assert(rtype(ref) == RTmp); + n = tn[ref.val].n; + s = pc + 1; + for (i=*s++; i>0; i--, s++) + if (n == *s++) + break; + pc += *s; + break; + default: /* jump */ + assert(bc >= 10); + pc = code + (bc - 10); + break; + } +}