diff options
| author | 2026-03-17 13:22:00 +0100 | |
|---|---|---|
| committer | 2026-03-17 13:22:00 +0100 | |
| commit | a8d6f8bf30c07edb775e56889f568ca20240bedf (patch) | |
| tree | b5a452b2675b2400f15013617291fe6061180bbf /src | |
| parent | 24f14b7ad1af08d872971d72ce089a529911f657 (diff) | |
REFACTOR: move sources to src/
Diffstat (limited to 'src')
| -rw-r--r-- | src/a_common.h | 465 | ||||
| -rw-r--r-- | src/a_main.c | 710 | ||||
| -rw-r--r-- | src/a_targ.c | 121 | ||||
| -rw-r--r-- | src/c.c | 4772 | ||||
| -rw-r--r-- | src/c.h | 139 | ||||
| -rw-r--r-- | src/c_builtin.c | 177 | ||||
| -rw-r--r-- | src/c_embedfilesdir.c | 106 | ||||
| -rw-r--r-- | src/c_eval.c | 437 | ||||
| -rw-r--r-- | src/c_keywords.def | 76 | ||||
| -rw-r--r-- | src/c_lex.c | 2496 | ||||
| -rw-r--r-- | src/c_lex.h | 126 | ||||
| -rw-r--r-- | src/c_type.c | 311 | ||||
| -rw-r--r-- | src/c_type.h | 177 | ||||
| -rw-r--r-- | src/io.c | 1255 | ||||
| -rw-r--r-- | src/ir.c | 689 | ||||
| -rw-r--r-- | src/ir.h | 353 | ||||
| -rw-r--r-- | src/ir_abi0.c | 462 | ||||
| -rw-r--r-- | src/ir_builder.c | 307 | ||||
| -rw-r--r-- | src/ir_cfg.c | 130 | ||||
| -rw-r--r-- | src/ir_cse.c | 92 | ||||
| -rw-r--r-- | src/ir_dump.c | 319 | ||||
| -rw-r--r-- | src/ir_fold.c | 133 | ||||
| -rw-r--r-- | src/ir_inliner.c | 309 | ||||
| -rw-r--r-- | src/ir_intrin.c | 77 | ||||
| -rw-r--r-- | src/ir_intrin.def | 2 | ||||
| -rw-r--r-- | src/ir_mem2reg.c | 317 | ||||
| -rw-r--r-- | src/ir_op.def | 79 | ||||
| -rw-r--r-- | src/ir_regalloc.c | 1417 | ||||
| -rw-r--r-- | src/ir_simpl.c | 308 | ||||
| -rw-r--r-- | src/ir_ssa.c | 46 | ||||
| -rw-r--r-- | src/ir_stack.c | 33 | ||||
| -rw-r--r-- | src/o_elf.c | 572 | ||||
| -rw-r--r-- | src/o_elf.h | 206 | ||||
| -rw-r--r-- | src/obj.c | 118 | ||||
| -rw-r--r-- | src/obj.h | 36 | ||||
| -rw-r--r-- | src/t_aarch64.h | 16 | ||||
| -rw-r--r-- | src/t_aarch64_aapcs.c | 77 | ||||
| -rw-r--r-- | src/t_aarch64_emit.c | 1023 | ||||
| -rw-r--r-- | src/t_aarch64_isel.c | 515 | ||||
| -rw-r--r-- | src/t_x86-64.h | 18 | ||||
| -rw-r--r-- | src/t_x86-64_emit.c | 1422 | ||||
| -rw-r--r-- | src/t_x86-64_isel.c | 652 | ||||
| -rw-r--r-- | src/t_x86-64_sysv.c | 310 | ||||
| -rw-r--r-- | src/u_endian.h | 189 | ||||
| -rw-r--r-- | src/u_mem.c | 390 | ||||
| -rw-r--r-- | src/version.h | 13 |
46 files changed, 21998 insertions, 0 deletions
diff --git a/src/a_common.h b/src/a_common.h new file mode 100644 index 0000000..a24aa3d --- /dev/null +++ b/src/a_common.h @@ -0,0 +1,465 @@ +#ifndef COMMON_H_ +#define COMMON_H_ + +#include <stdarg.h> +#include <stddef.h> +#include <string.h> + +#ifdef __clang__ /* stop linter from complaining about "unused" inline functions */ +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#define bool _Bool +typedef unsigned char uchar; +typedef signed char schar; +typedef unsigned short ushort; +typedef unsigned long long uvlong; +typedef signed long long vlong; +typedef unsigned uint; + +#if __STDC_VERSION__ >= 202311L +#define NORETURN [[noreturn]] +#elif __STDC_VERSION__ >= 201112L +#define NORETURN _Noreturn +#else +#define NORETURN +#endif + +#ifdef __has_builtin +#define HAS_BUILTIN(b) __has_builtin(__builtin_##b) +#else +#define HAS_BUILTIN(_) 0 +#endif + +#define static_assert(x) _Static_assert(x, #x) +#define in_range(x, Lo, Hi) ((uint) (x) - (Lo) <= (Hi) - (Lo)) /* lo <= x <= hi; lo > 0, hi > 0 */ +#define alignup(x, A) (((x) + ((A) - 1)) & -(A)) +#define countof(a) (sizeof(a) / sizeof 0[a]) + +enum { SPANFILEBITS = 10 }; +struct span { + struct span0 { + uint off; + uint len : 32-SPANFILEBITS, + file : SPANFILEBITS; + } sl, /* original source location */ + ex; /* the location after #include/macro expansion */ +}; + +void _assertfmt(const char *file, int line, const char *func, const char *expr); +#if HAS_BUILTIN(trap) +#define assert(x) (!(x) ? _assertfmt(__FILE__,__LINE__,__func__,#x), __builtin_trap() : (void)0) +#else +#define assert(x) (void)(!(x) ? _assertfmt(__FILE__,__LINE__,__func__,#x), *(volatile int *)0 : 0) +#endif + +static inline size_t +hashs(size_t h, const char *s) +{ + while (*s) h = (uchar)*s++ + h*65599; + return h; +} +static inline size_t +hashb(size_t h, const void *d, size_t n) +{ + const uchar *b = d; + while (n--) h = *b++ + h*65599; + return h; +} +static inline size_t +ptrhash(const void *p) { + return (size_t)p * 2654435761u; +} +static inline uint +popcnt(uvlong x) { +#if HAS_BUILTIN(popcountll) + return x ? __builtin_popcountll(x) : 0; +#else + uint n = 0; + while (x) n += x&1, x >>= 1; + return n; +#endif +} +static inline bool +ispo2(uvlong x) { + return (x != 0) & ((x & (x - 1)) == 0); +} +static inline uint +ilog2(uvlong x) { /* assumes x is a power of 2 */ +#if HAS_BUILTIN(ctzll) + return __builtin_ctzll(x); +#else + uint n = 0; + while (x >>= 1) ++n; + return n; +#endif +} +static inline uint +lowestsetbit(uvlong x) +{ +#if HAS_BUILTIN(ctzll) + return __builtin_ctzll(x); +#else + int i = 0; + for (uvlong mask = 1;; ++i, mask <<= 1) + if (x & mask) + break; + return i; +#endif +} + +#define aisprint(c) in_range(c, ' ', '~') +#define aisdigit(c) in_range(c, '0', '9') +#define aisodigit(c) in_range(c, '0', '7') +#define aisalpha(c) in_range((c)|0x20, 'a', 'z') +static inline bool aisspace(int c) { return c == ' ' || in_range(c, '\t', '\r'); } +static inline bool aisxdigit(int c) { return aisdigit(c) || in_range(c|0x20, 'a', 'f'); } + +/******************/ +/* COMPILER STATE */ +/******************/ + +enum cstd { + STDC89, + STDC99, + STDC11, + STDC23, +}; +struct option { + enum cstd cstd; + bool pedant; + bool trigraph; + bool nocolor; + bool pie, pic; + bool werror; + bool wnone; + enum optz { + OPT0 = -1, + OPT1 = 1, + OPT2 = 2, + } o; + union { + struct { + bool p : 1, /* after parsing */ + a : 1, /* after abi0 */ + m : 1, /* after mem */ + y : 1, /* after inline */ + o : 1, /* after optimizations */ + s : 1, /* after stack */ + i : 1, /* after isel */ + l : 1, /* after liveness fixup */ + r : 1; /* after regalloc */ + }; + uint any; + } dbg; + struct wbuf *dbgout; +}; +extern struct option ccopt; +extern struct cinclpaths { + struct inclpath { + struct inclpath *next; + const char *path; + } *list, **tail; +} cinclpaths[5]; +enum { /* GCC include directory search order: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html#Options-for-Directory-Search */ + CINCL_iquote, + CINCL_I, + CINCL_isystem, + CINCLsys, + CINCL_idirafter, +}; + +/**********/ +/* Target */ +/**********/ + +struct targtriple { + enum mcarch { ISxxx, ISx86_64, ISaarch64 } arch; + enum mcos { OSunknown, OSlinux } os; + enum mcabi { ABInone, ABIgnu, ABImusl } abi; +}; +extern const struct mctarg *mctarg; +extern struct targtriple target; +void targ_init(const char *); + +/*********/ +/** MEM **/ +/*********/ + +/* libc *alloc wrappers */ +void *xmalloc(size_t n, const char *); +void *xcalloc(size_t n, const char *); +void *xrealloc(void *, size_t n, const char *); +void free(void *); +#define xmalloc(n) xmalloc(n, __func__) +#define xcalloc(n) xcalloc(n, __func__) +#define xrealloc(p,n) xrealloc(p, n, __func__) + +/* string interning */ +typedef const struct internstr {char c;} *internstr; +internstr intern_(const char *, uint len); +#define intern(s) intern_(s, 0) + +/* growable buffer that stores its capacity in the allocated memory */ +#define xbnew_(n) (void *)(1 + (size_t *)xcalloc(sizeof(size_t) + (n))) +#define xbcap_(p) ((size_t *)(p))[-1] +static inline void +xbgrow_(void **p, size_t n) +{ + if (!n) return; + if (!*p) { *p = xbnew_(n); xbcap_(*p) = n; assert(n>0); } + else if (xbcap_(*p) < n) { + size_t k = xbcap_(*p); + assert(k > 0); + do k *= 2; while (k < n); + *p = 1 + (size_t *)xrealloc(&xbcap_(*(p)), sizeof(size_t) + k); + xbcap_(*p) = k; + }; +} +#define xbgrow(p, n) xbgrow_((void **)(p), (n) * sizeof**(p)) +#define xbpush(p, n, x) (xbgrow(p, (*(n) + 1)), (*(p))[(*(n))++] = (x)) +#define xbfree(p) ((p) ? free(&xbcap_(p)) : (void)0) +#define xbcap(p) ((p) ? xbcap_(p) / sizeof*(p) : 0) +#define xbgrowz(p, n) do { \ + size_t tmp = *(p) ? xbcap_(*(p)) : 0; \ + xbgrow(p, n); \ + memset((char*)*(p)+tmp, 0, xbcap_(*(p))-tmp); \ +} while (0) + + +/** arenas **/ +struct arena { + uint cap : 31, + dyn : 1; + uint n; + struct arena *prev; + uchar mem[]; +}; + +extern struct arena *globarena; + +struct arena *newarena(uint chunksiz); +void *alloc(struct arena **, uint siz, uint align); +void *allocz(struct arena **, uint siz, uint align); +static inline void * +alloccopy(struct arena **arena, const void *src, uint siz, uint align) +{ + if (!siz) return NULL; + return memcpy(alloc(arena, siz, align), src, siz); +} +void freearena(struct arena **); + +/** vec **/ +struct vecbase { void *p; uint n; uint cap : 31, dyn : 1; }; +#define vec_of(T) union { \ + struct { T *p; uint n; }; \ + struct vecbase _vb; \ +} +void vinit_(struct vecbase *, void *inlbuf, uint cap, uint siz); +void vpush_(struct vecbase *, uint siz); +void *vpushn_(struct vecbase *, uint siz, const void *dat, uint ndat); +void vresize_(struct vecbase *, uint siz, uint N); +#define VINIT(inlbuf, Cap) { ._vb.p = (inlbuf), ._vb.cap = (Cap) } +#define vfree(v) ((v)->_vb.dyn ? free((v)->p) : (void)0, memset((v), 0, sizeof*(v))) +#define vinit(v, inlbuf, Cap) (vfree(v), vinit_(&(v)->_vb, inlbuf, (Cap), sizeof *(v)->p)) +#define vpush(v, x) (vpush_(&(v)->_vb, sizeof *(v)->p), (v)->p[(v)->n++] = (x)) +#define vpushn(v, xs, N) vpushn_(&(v)->_vb, sizeof *(v)->p, xs, N) +#define vresize(v, N) vresize_(&(v)->_vb, sizeof *(v)->p, N) + +/** map of short -> T **/ +#define imap_of(T) struct { T *v; int tmp; struct imapbase mb; } +struct imapbase { short *k; struct bitset *bs; uint n, N; }; + +void imap_init_(struct imapbase *, void **v, uint vsiz, uint N); +int imap_get_(struct imapbase *, short k); +int imap_set_(struct imapbase *, void **v, uint vsiz, short k); +#define imap_free(m) (free((m)->mb.k), memset((m), 0, sizeof *(m))) +#define imap_init(m, N) (imap_free(m), imap_init_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, (N))) +#define imap_clear(m) ((m)->mb.bs ? bszero((m)->mb.bs, BSSIZE((m)->mb.N)) : (void)0, \ + (m)->mb.n = 0) +#define imap_get(m, k) ((m)->tmp = imap_get_(&(m)->mb, k), (m)->tmp < 0 ? NULL : &(m)->v[(m)->tmp]) +#define imap_set(m, k, ...) ((m)->tmp = imap_set_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, k), \ + (m)->v[(m)->tmp] = (__VA_ARGS__), &(m)->v[(m)->tmp]) +#define imap_each(m,kx,pvx) \ + for (int _i = 0; _i < (m)->mb.N && ((kx) = (m)->mb.k[_i], (pvx) = &(m)->v[_i], 1); ++_i) \ + if (bstest((m)->mb.bs, _i)) +#define imap_copy(dst,src) do { \ + size_t N = (src)->mb.N; \ + if (!N) break; \ + (dst)->mb.n = (src)->mb.n; \ + imap_init((dst), N); \ + memcpy((dst)->mb.k, (src)->mb.k, \ + N*(sizeof*(src)->mb.k + sizeof*(src)->v) + BSSIZE(N)*sizeof(struct bitset)); \ +} while (0) + + +/** map of non-null ptr -> T **/ +#define pmap_of(T) struct { T *v; int tmp; struct pmapbase mb; } +struct pmapbase { void **k; uint n, N; }; + +void pmap_init_(struct pmapbase *, void **v, uint vsiz, uint N); +int pmap_get_(struct pmapbase *, const void *k); +int pmap_set_(struct pmapbase *, void **v, uint vsiz, const void *k); +void pmap_del_(struct pmapbase *, const void *k); +extern char pmap_tombstone_[]; +#define pmap_free(m) (free((m)->mb.k), memset((m), 0, sizeof *(m))) +#define pmap_init(m, N) (pmap_free(m), pmap_init_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, (N))) +#define pmap_get(m, k) (((m)->tmp = pmap_get_(&(m)->mb, k)) < 0 ? NULL : &(m)->v[(m)->tmp]) +#define pmap_set(m, k, x) ((m)->tmp = pmap_set_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, k), \ + (m)->v[(m)->tmp] = (x)) +#define pmap_del(m, k) pmap_del_(&(m)->mb, k) +#define pmap_each(m,kx,pvx) \ + for (size_t _i = 0; _i < (m)->mb.N && ((kx) = (m)->mb.k[_i], (pvx) = &(m)->v[_i], 1); ++_i) \ + if (kx && kx != pmap_tombstone_) + +/** bitset **/ +struct bitset { size_t u; }; +enum { BSNBIT = 8 * sizeof(struct bitset) }; +#define BSSIZE(nbit) ((nbit)/BSNBIT + ((nbit)%BSNBIT != 0)) + +static inline bool +bstest(const struct bitset *bs, uint i) +{ + return bs[i/BSNBIT].u >> i%BSNBIT & 1; +} + +static inline void +bsset(struct bitset *bs, uint i) +{ + bs[i/BSNBIT].u |= 1ull << i%BSNBIT; +} + +static inline void +bsclr(struct bitset *bs, uint i) +{ + bs[i/BSNBIT].u &= ~(1ull << i%BSNBIT); +} + +static inline void +bszero(struct bitset bs[/*siz*/], uint siz) +{ + memset(bs, 0, siz * sizeof *bs); +} + +static inline void +bscopy(struct bitset dst[/*siz*/], const struct bitset src[/*siz*/], uint siz) +{ + while (siz--) dst++->u = src++->u; +} + +static inline void +bsunion(struct bitset dst[/*siz*/], const struct bitset src[/*siz*/], uint siz) +{ + while (siz--) dst++->u |= src++->u; +} + +static inline uint +bscount(struct bitset bs[/*siz*/], uint siz) +{ + uint n = 0; + while (siz--) n += popcnt(bs++->u); + return n; +} + +static inline bool +bsiter(uint *i, struct bitset bs[/*siz*/], uint siz) +{ + uint k = *i/BSNBIT, j = *i%BSNBIT; + if (k >= siz) return 0; + size_t t = bs[k].u & ~(((size_t)1 << j) - 1); + while (!t) { + if (++k >= siz) return 0; + t = bs[k].u; + } + *i = k*BSNBIT + lowestsetbit(t); + return 1; +} +#define bs_each(T, var, bs, siz) for (T (var) = 0; bsiter(&(var), (bs), (siz)); ++(var)) + +static inline bool +bsiterzr(uint *i, struct bitset bs[/*siz*/], uint siz) +{ + uint k = *i/BSNBIT, j = *i%BSNBIT; + if (k >= siz) return 0; + size_t t = ~bs[k].u & ~(((size_t)1 << j) - 1); + while (!t) { + if (++k >= siz) return 0; + t = ~bs[k].u; + } + *i = k*BSNBIT + lowestsetbit(t); + return 1; +} + +/********/ +/** IO **/ +/********/ + +struct wbuf { + union { + struct { + char *buf; + uint cap; + uint len; + int fd; + }; + void *fp; + }; + bool err; + bool isfp; +}; + +/* read-only file mapping that is at least 1 page larger than the real file + * so it's legal to read a few bytes beyond it to avoid some bounds checks */ +struct memfile { + const uchar *p; + uint n; + bool statik; +}; + +struct embedfile { + const char *name; + const char *s; + size_t len; +}; + +#define MEMBUF(buf_, cap_) { .buf = (buf_), .cap = (cap_), .fd = -1 } +#define FDBUF(buf_, cap_, fd_) { .buf = (buf_), .cap = (cap_), .fd = (fd_) } +extern struct wbuf bstdout, bstderr; +void ioinit(void); +void iowrite(struct wbuf *, const void *src, int n); +void ioputc(struct wbuf *, uchar); +void ioflush(struct wbuf *); +int vbfmt(struct wbuf *, const char *, va_list ap); +int bfmt(struct wbuf *, const char *, ...); +#define pfmt(...) bfmt(&bstdout, __VA_ARGS__) +#define efmt(...) bfmt(&bstderr, __VA_ARGS__) +struct memfile mapopen(const char **err, const char *path); +void mapclose(struct memfile *); +void *mapzeros(uint); +int munmap(void *, size_t); +int getpredeffile(struct memfile **, const char *name); +int openfile(const char **err, struct memfile **, const char *path); +const char *getfilename(int id, uint atoff); +struct memfile *getfile(int id); +void addfileline(int id, uint off); +void setfileline(int id, uint off, int line, const char *file); +const char *getfilepos(int *line, int *col, int id, uint off); +bool isoncefile(int id, internstr *guard); +void markfileonce(int id, internstr guard); +void markfileseen(int id); +bool isfileseen(int id); +void closefile(int id); + +enum diagkind { DGERROR, DGWARN, DGNOTE, }; +void vdiag(const struct span *, enum diagkind, const char *, va_list); +NORETURN void fatal(const struct span *, const char *, ...); +void error(const struct span *, const char *, ...); +void warn(const struct span *, const char *, ...); +void note(const struct span *, const char *, ...); +ushort *utf8to16(uint *ulen, struct arena **, const uchar *s, size_t len); +uint *utf8to32(uint *ulen, struct arena **, const uchar *s, size_t len); +int utf8enc(char out[4], uint cp); + +#endif /* COMMON_H_ */ + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/a_main.c b/src/a_main.c new file mode 100644 index 0000000..9710814 --- /dev/null +++ b/src/a_main.c @@ -0,0 +1,710 @@ +#include "common.h" +#include "version.h" +#include "hostconfig.h" /* run ./configure */ +#include "obj/obj.h" +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> +#include <time.h> + +struct option ccopt; +struct cinclpaths cinclpaths[5]; + +static void +addinclpath(int ord, const char *path) +{ + struct inclpath *p = alloc(&globarena, sizeof *p, 0); + assert((uint)ord < countof(cinclpaths)); + p->path = path; + p->next = NULL; + if (cinclpaths[ord].list) { + *cinclpaths[ord].tail = p; + } else { + cinclpaths[ord].list = p; + } + cinclpaths[ord].tail = &p->next; +} + +/* parse an argument of the form 'opt=abcd' + * e.g. arg="foo=bar123"; opt="foo"; returns "bar123" */ +static const char * +optval(const char *arg, const char *opt) +{ + uint n1 = strlen(arg), n2 = strlen(opt); + if (n1 < n2+1 || memcmp(arg, opt, n2) != 0 || arg[n2] != '=') + return NULL; + return arg + n2 + 1; +} + +/* "foo.bar" -> "bar"; ".dotfile" -> "" */ +static const char * +fileext(const char *path) +{ + const char *dot = NULL; + assert(path && *path && "empty"); + + for (++path; *path; ++path) { + if (*path == '.') dot = path; + } + return dot ? dot+1 : ""; +} + +enum inft { IFTauto, IFTc, IFTasm, IFTobj, IFTar, IFTdll }; + +static enum inft +ftdetect(const char *s) +{ + const char *ext = fileext(s); + if (!strcmp(ext, "c")) return IFTc; + if (!strcmp(ext, "o")) return IFTobj; + if (!strcmp(ext, "a")) return IFTar; + if (!strcmp(ext, "s")) return IFTasm; + if (!strcmp(ext, "so")) return IFTdll; + warn(NULL, "assuming %'s is C source file", s); + return IFTc; +} + +static union { + struct arena a; + char mem[sizeof(struct arena) + (1<<10)]; +} _arenamem; +struct arena *globarena = &_arenamem.a; + +/* withext("x/y.c", "o") -> "y.o"; withext("f9", "s") -> "f9.s" */ +static const char * +withext(const char *path, const char *ext) +{ + char *res; + size_t len; + const char *oext, *file = path; + + while (*path) + if (*path++ == '/') + file = path; + assert(*file && "no filename"); + oext = fileext(file); + if (!*oext) + len = strlen(file); + else + len = oext - file - 1; + res = alloc(&globarena, len + 1 + (ext ? strlen(ext) + 1 : 0), 1); + memcpy(res, file, len); + if (ext) { + res[len] = '.'; + memcpy(res + len + 1, ext, strlen(ext)); + } else { + res[len] = 0; + } + return res; +} + +struct infile { + enum inft ft; + const char *path, *temp; +}; +static struct infile infilebuf[16]; +static struct task { + enum outft { OFTexe, OFTdll, OFTobj, OFTasm, OFTc } outft; + const char *out; + const char *targ; + vec_of(struct infile) inf; + char **runargs; + vec_of(const char *) linkargs; + bool verbose, run, syntaxonly; +} task = { .inf = VINIT(infilebuf, countof(infilebuf)) }; + +static void prihelp(void); + +static void +optparse(char **args) +{ + const char *arg, *x; + enum inft ft = IFTauto; + + while ((arg = *++args)) { + if (*arg++ != '-' || !*arg) { + vpush(&task.inf, ((struct infile) { + ft ? ft : ftdetect(arg-1), + arg[-1] != '-' ? arg-1 : "/dev/stdin" + })); + ft = IFTauto; + if (task.run) { + task.runargs = args+1; + return; + } + continue; + } + int cinclord; + if (!strcmp(arg, "help") || !strcmp(arg, "h") || !strcmp(arg, "-help")) { + prihelp(); + exit(0); + } else if (!strcmp(arg, "dumpmachine")) { + pfmt("%s\n", HOST_TRIPLE); + exit(0); + } else if (!strcmp(arg, "-version")) { + pfmt("antcc version "ANTCC_VERSION_STR"\n" + "target: "HOST_TRIPLE"\n" + "include paths: "XSTR(HOST_INCLUDE_DIRS)"\n" + "host cc for linking: " HOST_CC "\n"); + exit(0); + } else if (!strcmp(arg, "dumpversion")) { + pfmt("%s\n", ANTCC_VERSION_STR); + exit(0); + } else if ((x = optval(arg, "std"))) { + if (!strcmp(x, "c89") || !strcmp(x, "c90")) ccopt.cstd = STDC89; + else if (!strcmp(x, "c99")) ccopt.cstd = STDC99; + else if (!strcmp(x, "c11")) ccopt.cstd = STDC11; + else if (!strcmp(x, "c2x")) ccopt.cstd = STDC23; + else if (!strcmp(x, "c23")) ccopt.cstd = STDC23; + else goto Bad; + } else if (!strcmp(arg, "pedantic")) { + ccopt.pedant = 1; + } else if (!strcmp(arg, "trigraphs")) { + ccopt.trigraph = 1; + } else if (*arg == 'd' && arg[1]) { + /* see common.h§struct option */ + while (*++arg) switch (*arg | 32) { + case 'p': ccopt.dbg.p = 1; break; + case 'a': ccopt.dbg.a = 1; break; + case 'm': ccopt.dbg.m = 1; break; + case 'o': ccopt.dbg.o = 1; break; + case 's': ccopt.dbg.s = 1; break; + case 'i': ccopt.dbg.i = 1; break; + case 'y': ccopt.dbg.y = 1; break; + case 'l': ccopt.dbg.l = 1; break; + case 'r': ccopt.dbg.r = 1; break; + default: warn(NULL, "-d: invalid debug flag %'c", *arg); + } + } else if (*arg == 'o') { + if (arg[1]) task.out = arg+1; + else if (args[1]) task.out = *++args; + else fatal(NULL, "missing path after `-o`"); + } else if (!strcmp(arg, "fsyntax-only")) { + task.syntaxonly = 1; + } else if (*arg == 'f') { + /* -fabc / -fno-abc flags */ + const char *flag = arg+1; + bool set = 1; + if (!strncmp(flag, "no-", 3)) { + set = 0; + flag += 3; + } + if (!strcmp(flag, "pie") || !strcmp(flag, "PIE")) ccopt.pie = set; + else if (!strcmp(flag, "pic") || !strcmp(flag, "PIC")) ccopt.pic = set; + else goto Bad; + } else if (!strcmp(arg, "target") || !strcmp(arg, "-target")) { + const char *s = *++args; + if (!s) fatal(NULL, "missing target name"); + task.targ = s; + } else if (*arg == 'l' || *arg == 'L' || *arg == 'B' || !strcmp(arg, "shared") || !strcmp(arg, "pthread")) { + vpush(&task.linkargs, arg-1); + } else if (!strcmp(arg, "v") || !strcmp(arg, "-verbose")) { + task.verbose = 1; + } else if (!strcmp(arg, "c")) { + task.outft = OFTobj; + } else if (!strcmp(arg, "E")) { + task.outft = OFTc; + } else if (!strcmp(arg, "xc")) { + ft = IFTc; + } else if (!strcmp(arg, "xo")) { + ft = IFTobj; + } else if (!strcmp(arg, "run")) { + task.run = 1; + if (task.inf.n > 0) { + task.runargs = args+1; + return; + } + } else if (*arg == 'g') { + /* TODO debug info */ + } else if (*arg == 'O') { + char o = arg[1]; + if (!o || o == 'g') ccopt.o = 0; /* default opts */ + else if (o == '1' || o == 's' || o == 'z') ccopt.o = OPT1; + else if ((uint)o - '1' < 9) ccopt.o = OPT2; + else if (o == '0') ccopt.o = OPT0; + else goto Bad; + } else if (*arg == 'D' || *arg == 'U') { + void cpppredef(bool undef, const char *cmd); + const char *def = arg[1] ? arg+1 : *++args; + if (!def) fatal(NULL, "macro name missing after `-%c`", *arg); + cpppredef(*arg == 'U', def); + } else if (*arg == 'O') { + /* TODO optimization level */ + } else if (*arg == 'I' || !strcmp(arg, "-include-directory")) { + const char *p; + cinclord = CINCL_I; + if (*arg == 'I' && arg[1]) p = arg+1; + else CIncl: p = *++args; + if (!p) fatal(NULL, "missing path after `%s`", arg-1); + addinclpath(cinclord, p); + } else if (!strcmp(arg, "iquote")) { + cinclord = CINCL_iquote; + goto CIncl; + } else if (!strcmp(arg, "isystem")) { + cinclord = CINCL_isystem; + goto CIncl; + } else if (!strcmp(arg, "idirafter")) { + cinclord = CINCL_idirafter; + goto CIncl; + } else if (!strcmp(arg, "nostdinc") || !strcmp(arg, "-no-standard-includes")) { + cinclpaths[CINCLsys].list = NULL; + } else if (*arg == 'M') { + ++arg; + if (*arg == 'F' || *arg == 'T' || *arg == 'Q') { + const char *p = arg[1] ? arg+1 : *++args; + if (!p) fatal(NULL, "missing path after `-M%c`", *arg); + } + /* TODO depfiles */ + } else if (*arg == 'W') { + if (!strcmp(arg+1, "error")) { + ccopt.werror = 1; + } + /* TODO warning switches */ + } else if (*arg == 'w') { + ccopt.wnone = 1; + /* TODO warning switches */ + } else Bad: warn(NULL, "unrecognized option: %'s", arg-1); + } + + if (task.inf.n == 0) fatal(NULL, "no input files"); + + if (!task.out && !task.syntaxonly) { + switch (task.outft) { + case OFTdll: + case OFTexe: if (!task.run) task.out = "a.out"; break; + case OFTasm: task.out = withext(task.inf.p[0].path, "s"); break; + case OFTobj: task.out = withext(task.inf.p[0].path, "o"); break; + case OFTc: break; + } + } + if (!in_range(task.outft, OFTexe, OFTdll) && task.outft != OFTc && task.inf.n > 1) + fatal(NULL, "too many input files"); +} + +static const char * +tempfile(const char *path, const char *ext) +{ + int id; + static int id2; + static char sbuf[1024]; + const char *tmpdir; + const char *file = path; + struct wbuf fbuf = MEMBUF(sbuf, sizeof sbuf); + + tmpdir = getenv("TMPDIR"); + tmpdir = tmpdir ? tmpdir : "/tmp"; + id = getpid(); + id = id < 0 ? time(NULL) : id; + + while (*path) + if (*path++ == '/') + file = path; + bfmt(&fbuf, "%s/antcc-%x@%u@", tmpdir, id, id2++); + while (*file++) { + char c = file[-1]; + if (in_range(c, 'a', 'z') || in_range(c, 'A', 'Z') || in_range(c, '0', '9') + || c == '_' || c == '-') + { + ioputc(&fbuf, c); + } else if (c == '.') break; + else ioputc(&fbuf, '_'); + } + bfmt(&fbuf, "%s%s", &"."[!ext], ext ? ext : ""); + ioputc(&fbuf, 0); + assert(!fbuf.err); + return alloccopy(&globarena, fbuf.buf, fbuf.len, 1); +} + +static int cc1(const char *out, const char *in); + +static const char *tempout; +static pid_t rootp; +static void +mktemps(void) { + rootp = getpid(); + for (int i = 0; i < task.inf.n; ++i) { + if (task.inf.p[i].ft == IFTc) + task.inf.p[i].temp = tempfile(task.inf.p[i].path, "o"); + } + if (!task.out) + task.out = tempout = tempfile(task.inf.n > 1 ? "run" : withext(task.inf.p[0].path, NULL), NULL); +} + +static void +cleantemps(void) +{ + if (getpid() != rootp) return; + for (int i = 0; i < task.inf.n; ++i) { + if (task.inf.p[i].temp) { + unlink(task.inf.p[i].temp); + task.inf.p[i].temp = NULL; + } + } + if (tempout) + unlink(tempout), tempout = NULL; +} +static void +sigcleantemps(int _) +{ + cleantemps(); +} + +static void +compileobjs(void) +{ + int wstat; + pid_t p; + + if (!ccopt.dbg.any && !task.syntaxonly) mktemps(); + for (int i = 0; i < task.inf.n; ++i) { + enum inft ft = task.inf.p[i].ft; + if (ft == IFTc) { + if ((p = fork()) < 0) { + error(NULL, "fork(): %s\n", strerror(errno)); + exit(1); + } else if (p == 0) { + exit(cc1(task.inf.p[i].temp, task.inf.p[i].path)); + } + waitpid(p, &wstat, 0); + if (!WIFEXITED(wstat)) exit(127); + if (WEXITSTATUS(wstat) != 0) { + cleantemps(); + exit(WEXITSTATUS(wstat)); + } + } else if (ft == IFTobj || ft == IFTar || ft == IFTdll) { + // passthru + } else assert(!"not obj"); + } + if (!ccopt.dbg.any && !task.syntaxonly) { + atexit(cleantemps); + signal(SIGINT, sigcleantemps); + signal(SIGABRT, sigcleantemps); + signal(SIGILL, sigcleantemps); + } +} + +#include <fcntl.h> + +static bool +hasprog(const char *prog) +{ + pid_t p; + if ((p = fork()) < 0) { + return 0; + } else if (p == 0) { + int nulfd = open("/dev/null", O_WRONLY); + if (nulfd < 0) return 0; + for (int fd = 0; fd <= 2; ++fd) { + dup2(fd, nulfd); + close(fd); + } + const char *cmd[] = {prog, NULL}; + if (execvp(*cmd, (char **)cmd)) { + close(nulfd); + error(NULL, "execvp: %s", strerror(errno)); + exit(125); + } + } + int wstat; + waitpid(p, &wstat, 0); + if (!WIFEXITED(wstat)) return 0; + return WEXITSTATUS(wstat) < 125; +} + +static bool +iscrosscc(void) +{ + return target.os != HOST_OS || target.arch != HOST_ARCH || target.abi != HOST_ABI; +} + +struct cmdargs { vec_of(const char *); }; + +static void +findlinkcmd(struct cmdargs *cmd) +{ + if (task.targ && iscrosscc()) { + /* try to find a cross compiling toolchain, e.g. aarch64-linux-gnu-gcc */ + static const char *ccs[] = {"cc", "gcc", "clang"}; + char cross[1024]; + for (int i = 0; i < countof(ccs); ++i) { + struct wbuf wbuf = MEMBUF(cross, sizeof cross); + int n = bfmt(&wbuf, "%s-%s", task.targ, ccs[i]); + assert(n < sizeof cross-1); + cross[n] = 0; + if (hasprog(cross)) { + vpush(cmd, alloccopy(&globarena, cross, n, 1)); + return; + } + } + /* zig cc fallback, which works great as cross compiler toolchain */ + if (hasprog("zig")) { + vpush(cmd, "zig"); + vpush(cmd, "cc"); + vpush(cmd, "-target"); + vpush(cmd, task.targ); + } else { + fatal(NULL, "cannot link to cross-compilation target: no appropiate toolchain installed"); + } + } else { + vpush(cmd, HOST_CC); + } +} + +static int +dolink(void) +{ + const char *cmdbuf[100]; + pid_t p; + int wstat; + struct cmdargs cmd = VINIT(cmdbuf, countof(cmdbuf)); + + findlinkcmd(&cmd); + if (!strcmp(cmd.p[0], "zig")) { + note(NULL, "using 'zig cc' as a cross-compiler"); + } + if (task.outft == OFTdll) { + vpush(&cmd, "-shared"); + } else if (task.outft == OFTexe) { + vpush(&cmd, ccopt.pie ? "-pie" : "-no-pie"); + } + vpush(&cmd, "-o"); + vpush(&cmd, task.out); + assert(task.inf.n > 0); + for (int i = 0; i < task.inf.n; ++i) { + const char *o; + switch (task.inf.p[i].ft) { + case IFTc: o = task.inf.p[i].temp; break; + case IFTobj: case IFTar: case IFTdll: + o = task.inf.p[i].path; break; + default: assert(!"link obj?"); + } + vpush(&cmd, o); + } + vpushn(&cmd, task.linkargs.p, task.linkargs.n); + if (task.verbose) { + efmt("> "); + for (int i = 0; i < cmd.n; ++i) + efmt("%s ", cmd.p[i]); + efmt("\n"); + } + vpush(&cmd, NULL); + if ((p = fork()) < 0) { + error(NULL, "fork: %s\n", strerror(errno)); + exit(1); + } else if (p == 0) { + if (execvp(cmd.p[0], (char **)cmd.p)) { + error(NULL, "execvp: %s\n", strerror(errno)); + exit(1); + } + } + vfree(&cmd); + waitpid(p, &wstat, 0); + if (!WIFEXITED(wstat)) return 127; + if (WEXITSTATUS(wstat) != 0) { + error(NULL, "link command failed"); + return 1; + } + return 0; +} + +static int +dorun(void) +{ + if (target.arch != HOST_ARCH || target.os != HOST_OS) { + warn(NULL, "'-run' with cross-compiled binary"); + } + if (task.verbose) { + efmt("> exec %s", task.out); + for (char **s = task.runargs; *s; ++s) + efmt(" %s", *s); + efmt("\n"); + } +#if _POSIX_C_SOURCE >= 200809L + /* use fexecve */ + int fexecve(int fd, char *const argv[], char *const envp[]); + int fd = open(task.out, O_RDONLY); + if (fd < 0) { + error(NULL, "open: %s\n", strerror(errno)); + return 1; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + error(NULL, "fcntl: %s\n", strerror(errno)); + return 1; + } + cleantemps(); + extern char **environ; + fexecve(fd, task.runargs - 1, environ); + error(NULL, "fexecv: %s\n", strerror(errno)); + return 1; +#else + pid_t p; + if ((p = fork()) < 0) { + error(NULL, "fork(): %s\n", strerror(errno)); + exit(1); + } else if (p == 0) { + if (!execv(task.out, task.runargs - 1)) { + error(NULL, "execv(): %s\n", strerror(errno)); + exit(1); + } + } + int wstat; + waitpid(p, &wstat, 0); + if (!WIFEXITED(wstat)) return 127; + return WEXITSTATUS(wstat); +#endif +} + +static int +driver(void) +{ + void cpp(struct wbuf *, const char *); + if (task.verbose) + efmt("# Target: %s\n", task.targ ? task.targ : HOST_TRIPLE); + if (task.syntaxonly) + task.out = "/dev/null"; // HACK + if (task.outft == OFTobj) { + assert(task.inf.n == 1); + if (task.inf.p[0].ft != IFTc) + fatal(NULL, "not a C source file: %s", task.inf.p[0].path); + return cc1(task.out, task.inf.p[0].path); + } else if (task.outft == OFTc) { + struct wbuf _buf = {0}, *buf = &bstdout; + if (task.out) { + buf = &_buf; + buf->buf = alloc(&globarena, buf->cap = 1<<12, 1); + buf->fd = open(task.out, O_CREAT | O_TRUNC | O_WRONLY, 0777); + if (buf->fd < 0) { + error(NULL, "open(%'s): %s", task.out, strerror(errno)); + return 1; + } + } + bool ok = 1; + if (!task.out && task.inf.n == 1) + cpp(buf, task.inf.p[0].path); + else for (int i = 0; i < task.inf.n; ++i) { + pid_t p; + int wstat; + + if ((p = fork()) < 0) { + error(NULL, "fork(): %s\n", strerror(errno)); + ok = 0; + } else if (p == 0) { + cpp(buf, task.inf.p[i].path); + exit(0); + } + waitpid(p, &wstat, 0); + if (!WIFEXITED(wstat)) ok = 0; + ok = ok && WEXITSTATUS(wstat) == 0; + } + ioflush(buf); + if (task.out) + close(buf->fd); + return ok ? 0 : 1; + } else if (task.outft == OFTexe || task.outft == OFTdll) { + compileobjs(); + if (ccopt.dbg.any || task.syntaxonly) return 0; + if (!task.run) return dolink(); + int st = dolink(); + if (st == 0) st = dorun(); + return st; + } + assert(0); +} + +static int +cc1(const char *out, const char *in) +{ + void ccomp(const char *); + extern int nerror; + + if (task.verbose) efmt("cc1(/*out*/ %'s, /*in*/ %'s)\n", out, in); + if (!ccopt.dbg.any && !task.syntaxonly) objini(in, out); + ccomp(in); + if (!ccopt.dbg.any && !task.syntaxonly && !nerror) objfini(); + return !!nerror; +} + +static void +detectcolor(void) +{ + const char *s; + if (!isatty(STDERR_FILENO) + || ((s = getenv("NO_COLOR")) && *s) + || ((s = getenv("TERM")) && !strcmp(s, "dumb"))) + ccopt.nocolor = 1; +} + +static void +sysinclpaths(void) +{ + static const char *paths[] = { + HOST_INCLUDE_DIRS + }; + for (int i = 0; i < countof(paths); ++i) + addinclpath(CINCLsys, paths[i]); +} + +static void +prihelp(void) +{ + pfmt("antcc version "ANTCC_VERSION_STR"\n" + "Usage: antcc [options] infile(s)...\n" + " antcc [options] -run infile [arguments...]\n" + " antcc [options] infile(s)... -run [arguments...]\n" + "Options:\n" + " -help \tPrint this help message\n" + " -std=<..> \tSet C standard (c89, c99, c11, c23)\n" + " -pedantic \tWarnings for strict standards compliance\n" + " -d{pamyosilr} \tDebug print IR after {parse, abi, mem, inlining, opts, stack, isel, live, rega}\n" + " -o <file> \tPlace the output into <file>\n" + " -v \tVerbose output\n" + " -c \tEmit object file but do not link\n" + " -run \tRun compiled sources\n" + " -Idir \tAdd include path\n" + " -Dsym[=val] \tDefine macro\n" + " -Usym \tUndefine macro\n" + " -E \tPreprocess only\n" + " -llib \tLink with library\n" + " -fpie \tEmit code for position independent executable\n" + " -fpic \tEmit position independent code\n" + " -O<..> \tSet optimization level (0|g|1|2|s|z) (default: -Og)\n" + " -x<c|o> \tSpecify type of next input file (C, object)\n" + " -W[...] \tTurn on warnings (stub)\n" + " -Werror \tTurn warnings into errors\n" + " -w \tSuppress warnings\n" + " --version \tPrint version\n" + ); +} + +int +main(int argc, char **argv) +{ + globarena->cap = sizeof(_arenamem.mem) - sizeof(struct arena); + + ioinit(); + /* setup defaults */ + detectcolor(); + sysinclpaths(); + ccopt.cstd = STDC11; + ccopt.pie = 1; + ccopt.dbgout = &bstdout; + + /* parse cli ags */ + if (argc == 1) { + prihelp(); + return 1; + } + optparse(argv); + + /* global init */ + targ_init(task.targ); + if (!target.arch) + fatal(NULL, "unsupported target: %s", task.targ ? task.targ : HOST_TRIPLE); + + return driver(); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/a_targ.c b/src/a_targ.c new file mode 100644 index 0000000..fdc11f8 --- /dev/null +++ b/src/a_targ.c @@ -0,0 +1,121 @@ +#include "common.h" +#include "type.h" + +extern const struct mctarg t_x86_64_sysv, t_aarch64_aapcs; +static const struct targ { + struct { enum mcarch arch; uint oss, abis; }; + struct { uchar longsize, vlongsize, ptrsize, valistsize; }; + struct { uchar longalign, vlongalign, doublealign, ptralign; }; + bool charsigned; + uchar sizetype, ptrdifftype, wchartype; + const struct mctarg *mctarg; +} targs[] = { + { {ISx86_64, -1, -1}, {8,8,8,24}, {8,8,8,8}, 1, TYULONG, TYLONG, TYINT, &t_x86_64_sysv }, + { {ISaarch64, -1, -1}, {8,8,8,32}, {8,8,8,8}, 0, TYULONG, TYLONG, TYUINT, &t_aarch64_aapcs }, +}; + +struct targtriple target; +uchar targ_primsizes[TYPTR+1]; +uchar targ_primalign[TYPTR+1]; +uint targ_valistsize; +enum typetag targ_sizetype, targ_ptrdifftype, targ_wchartype; +bool targ_charsigned, targ_bigendian, targ_64bit; +enum mcarch targ_arch; +const struct mctarg *mctarg; + +static bool +matchstr(const char **s, const char *pat) +{ + const char *p; + for (p = *s; *pat; ++p, ++pat) { + if (*pat == '$') { if (*p) return 0; else break; } + else if (*p != *pat) return 0; + } + *s = p; + return 1; +} + +static bool +parsetriple(struct targtriple *trg, const char *str) +{ + if (matchstr(&str, "x86_64-") || matchstr(&str, "amd64-")) + trg->arch = ISx86_64; + else if (matchstr(&str, "aarch64-") || matchstr(&str, "arm64-")) + trg->arch = ISaarch64; + else return 0; + + if (matchstr(&str, "unknown-") || matchstr(&str, "pc-")) {} + + if (matchstr(&str, "linux-")) { + trg->os = OSlinux; + } else if (matchstr(&str, "linux$")) { + trg->os = OSlinux; + trg->abi = ABIgnu; + } else return 0; + + if (matchstr(&str, "gnu")) { + trg->abi = ABIgnu; + } else if (matchstr(&str, "musl")) { + trg->abi = ABImusl; + } else return 0; + + return 1; +} + +#include "hostconfig.h" /* run ./configure */ + +void +targ_init(const char *starg) +{ + const struct targ *t = NULL; + uchar *sizes = targ_primsizes, *align = targ_primalign; + + if (!starg) { + target.arch = HOST_ARCH; + target.os = HOST_OS; + target.abi = HOST_ABI; + } else if (!parsetriple(&target, starg)) { + fatal(NULL, "unrecognized target: %s", starg); + } + + for (size_t i = 0; i < countof(targs); ++i) { + if (targs[i].arch == target.arch) + if (targs[i].oss & (1 << target.os)) + if (targs[i].abis & (1 << target.abi)) { + t = &targs[i]; + break; + } + } + if (!t) fatal(NULL, "unsupported target: %s", starg ? starg : HOST_TRIPLE); + + sizes[TYBOOL] = sizes[TYCHAR] = sizes[TYSCHAR] = sizes[TYUCHAR] = 1; + sizes[TYSHORT] = sizes[TYUSHORT] = 2; + sizes[TYUINT] = sizes[TYINT] = 4; + sizes[TYFLOAT] = 4; + sizes[TYDOUBLE] = 8; + sizes[TYLDOUBLE] = 8; + memcpy(align, sizes, sizeof targ_primalign); + sizes[TYULONG] = sizes[TYLONG] = t->longsize; + sizes[TYUVLONG] = sizes[TYVLONG] = t->vlongsize; + sizes[TYPTR] = t->ptrsize; + align[TYULONG] = align[TYLONG] = t->longalign; + align[TYUVLONG] = align[TYVLONG] = t->vlongalign; + align[TYDOUBLE] = t->doublealign; + align[TYLDOUBLE] = t->doublealign; + align[TYPTR] = t->ptralign; + sizes[TYCOMPLEXF] = sizes[TYFLOAT]*2; + sizes[TYCOMPLEX] = sizes[TYDOUBLE]*2; + sizes[TYCOMPLEXL] = sizes[TYLDOUBLE]*2; + align[TYCOMPLEXF] = align[TYFLOAT]; + align[TYCOMPLEX] = align[TYDOUBLE]; + align[TYCOMPLEXL] = align[TYLDOUBLE]; + targ_valistsize = t->valistsize; + targ_sizetype = t->sizetype; + targ_ptrdifftype = t->ptrdifftype; + targ_wchartype = t->wchartype; + targ_charsigned = t->charsigned; + targ_bigendian = 0; + targ_64bit = t->ptrsize == 8; + mctarg = t->mctarg; + targ_arch = ISx86_64; +} @@ -0,0 +1,4772 @@ +#include "c.h" +#include "lex.h" +#include "../endian.h" +#include "../ir/ir.h" +#include "../obj/obj.h" + +/** Parsing helper functions **/ +#define peek(Cm,Tk) lexpeek((Cm)->lx,Tk) +static int +lexc(struct comp *cm, struct token *tk) +{ + struct token tk2, tk_[1]; + int t = lex(cm->lx, tk ? tk : tk_); + if (t == TKSTRLIT && peek(cm, &tk2) == TKSTRLIT && tk2.wide == tk->wide) { + /* 5.1.1.2 Translation phase 6: concatenate adjacent string literal tokens */ + static char buf[200]; + vec_of(char) rest = VINIT(buf, sizeof buf); + do { + lex(cm->lx, NULL); + if (tk) { + joinspan(&tk->span.ex, tk2.span.ex); + if (!tk->wide) + vpushn(&rest, tk2.s, tk2.len); + else if (tk->wide && targ_primsizes[targ_wchartype] == 2) + vpushn(&rest, tk2.ws16, tk2.len*2); + else + vpushn(&rest, tk2.ws32, tk2.len*4); + } + } while (peek(cm, &tk2) == TKSTRLIT && tk2.wide == tk->wide); + if (tk) { + if (!tk->wide) { + tk->s = memcpy(alloc(&cm->exarena, tk->len + rest.n, 1), tk->s, tk->len); + memcpy((char *)tk->s + tk->len, rest.p, rest.n); + tk->len += rest.n; + } else if (tk->wide == 1) { + tk->ws16 = memcpy(alloc(&cm->exarena, tk->len + rest.n*2, 2), tk->ws16, tk->len*2); + memcpy((short *)tk->s + tk->len, rest.p, rest.n); + tk->len += rest.n * 2; + } else { + tk->ws32 = memcpy(alloc(&cm->exarena, tk->len + rest.n*4, 4), tk->ws32, tk->len*4); + memcpy((int *)tk->s + tk->len, rest.p, rest.n); + tk->len += rest.n * 4; + } + } + vfree(&rest); + } + if (ccopt.pedant && in_range(t, TKWBEGIN_, TKWEND_) && (tk = tk ? tk : tk_)->extwarn) { + static struct bitset already[BSSIZE(TKWEND_-TKWBEGIN_+1)]; + if (!bstest(already, t-TKWBEGIN_)) { + bsset(already, t-TKWBEGIN_); + warn(&tk->span, "%'tk in %M is an extension", tk); + } + } + return t; +} +#define lex(Cm,Tk) lexc(Cm,Tk) +static bool +match(struct comp *cm, struct token *tk, enum toktag t) +{ + if (peek(cm, NULL) == t) { + lex(cm, tk); + return 1; + } + return 0; +} +static bool +expect(struct comp *cm, enum toktag t, const char *s) +{ + struct token tk; + if (!match(cm, &tk, t)) { + peek(cm, &tk); + if (aisprint(t)) tk.span.ex.len = tk.span.sl.len = 1; + error(&tk.span, "expected %'tt%s%s", t, s?" ":"",s ? s : ""); + return 0; + } + return 1; +} + +/******************************************/ +/* Data structures for declaration parser */ +/******************************************/ + +enum declkind { + DTOPLEVEL, + DFUNCPARAM, + DFUNCVAR, + DFIELD, + DCASTEXPR, +}; + +/* Since a declaration can have multiple declarators, and we need to process + * each one individually, the declaration parser is a state machine + * (conceptually a generator coroutine); the state is zero-initialized (except + * for the .kind field), each call to pdecl yields the next individual decl, + * st.more indicates whether there are more decls left to parse (the coroutine + * has yielded), or this declaration list is done (the coroutine has finalized) + */ +struct declstate { + enum declkind kind; + union type base; + uchar scls; + uchar qual; + bool fnnoreturn : 1, + fninline : 1; + uint align; + bool base0, /* caller set initial base type, but there may be declspecs to parse */ + more, /* caller should keep calling pdecl to get next decl */ + varini, /* caller should parse an initializer ('=' <ini>) and + call pdecl() to advance state before checking .more */ + funcdef, /* caller should parse an func definition ('{' <body> '}'). + the declaration list is finished. */ + bitf, /* caller should parse a bitfield size and + call pdecl() to advance state before checking .more */ + tagdecl, /* declarator is a tagged type */ + empty; /* nothing decl (';') */ + internstr *pnames; /* param names for function definition */ + struct span *pspans; /* param spans ditto */ + uchar *pqual; /* param quals ditto */ + int attr; +}; +static struct decl pdecl(struct declstate *st, struct comp *cm); + +static struct decl *finddecl(struct comp *cm, internstr name); + +/* next token starts a decl? */ +static bool +isdecltok(struct comp *cm) +{ + struct token tk; + if (peek(cm, &tk) == TKIDENT) { + struct decl *decl = finddecl(cm, tk.name); + return decl && decl->scls == SCTYPEDEF; + } else { + static const bool kws[] = { +#define kw(x) [TKW##x-TKWBEGIN_] = 1 + kw(auto), kw(extern), kw(static), kw(register), kw(typedef), + kw(_Thread_local), kw(thread_local), kw(_Static_assert), + kw(inline), kw(_Noreturn), + kw(const), kw(volatile), kw(restrict), kw(_Atomic), + kw(void), kw(float), kw(double), kw(_Complex), + kw(signed), kw(unsigned), kw(short), kw(long), + kw(int), kw(char), kw(_Bool), kw(bool), + kw(struct), kw(union), kw(enum), + kw(__typeof__), kw(typeof), kw(typeof_unqual), + kw(__attribute__) +#undef kw + }; + return ((uint)tk.t-TKWBEGIN_) < countof(kws) && kws[tk.t-TKWBEGIN_]; + } +} + +/* next token starts an expr? */ +static bool +isexprtok(struct comp *cm) +{ + struct token tk; + if (peek(cm, &tk) == TKIDENT) { + struct decl *decl = finddecl(cm, tk.name); + return !decl || decl->scls != SCTYPEDEF; + } else { + static const bool tks[] = { +#define tk(x) [x] = 1 + tk('+'), tk('-'), tk('*'), tk('&'), tk('~'), tk('!'), tk(TKINC), tk(TKDEC), + tk(TKWsizeof), tk(TKW_Alignof), tk(TKWalignof), tk(TKWtrue), tk(TKWfalse), + tk('('), tk(TKNUMLIT), tk(TKCHRLIT), tk(TKSTRLIT), tk(TKW_Generic) +#undef tk + }; + return tk.t < countof(tks) && tks[tk.t]; + } +} + +/**********************************/ +/* Environment (scope) management */ +/**********************************/ + +struct envdecls declsbuf; +struct tagged { /* a tagged type declaration */ + union type ty; + struct span span; +}; +static struct tagged envtaggedbuf[1<<7]; +static vec_of(struct tagged) envtagged = VINIT(envtaggedbuf, countof(envtaggedbuf)); +struct env { + struct env *up; + /* list of decls is implicitly envdecls[decl..ndecl] */ + ushort decl, ndecl; + /* ditto for envtagged[] */ + ushort tagged, ntagged; +}; +/* use a hashmap for lookups of top-level declarations, since there's usually many of those */ +static pmap_of(ushort) tldeclmap; + +static void +envdown(struct comp *cm, struct env *e) +{ + assert(cm->env->decl + cm->env->ndecl == declsbuf.n); + assert(cm->env->tagged + cm->env->ntagged == envtagged.n); + e->decl = declsbuf.n; + e->tagged = envtagged.n; + e->ndecl = e->ntagged = 0; + e->up = cm->env; + cm->env = e; +} + +static void +envup(struct comp *cm) +{ + struct env *env = cm->env; + assert(env->decl + env->ndecl == declsbuf.n); + declsbuf.n -= env->ndecl; + envtagged.n -= env->ntagged; + assert(env->up); + cm->env = env->up; +} + +int +envadddecl(struct env *env, const struct decl *d) +{ + assert(env->decl + env->ndecl == declsbuf.n); + vpush(&declsbuf, *d); + assert(declsbuf.n < 1<<16); + ++env->ndecl; + if (!env->up) pmap_set(&tldeclmap, d->name, declsbuf.n-1); + return declsbuf.n - 1; +} + +/* iters in reversed order of insertion (most to least recent) */ +/* use like so: for (d = NULL; enviterdecl(&d, env);) ... */ +static inline bool +enviterdecl(struct decl **d, struct env *env) +{ + if (!env->ndecl) return 0; + if (!*d) *d = &declsbuf.p[env->decl + env->ndecl - 1]; + else if (*d == &declsbuf.p[env->decl]) return 0; + else --*d; + return 1; +} + +static struct tagged * +envaddtagged(struct env *env, union type ty, const struct span *span) +{ + struct tagged tagged = { ty, *span }; + assert(env->tagged + env->ntagged == envtagged.n); + vpush(&envtagged, tagged); + assert(envtagged.n < 1<<16); + ++env->ntagged; + return &envtagged.p[envtagged.n - 1]; +} + +/* like enviterdecl */ +static inline bool +envitertagged(struct tagged **l, struct env *env) +{ + if (!env->ntagged) return 0; + if (!*l) *l = &envtagged.p[env->tagged + env->ntagged - 1]; + else if (*l == &envtagged.p[env->tagged]) return 0; + else --*l; + return 1; +} + +static bool +redeclarationok(const struct decl *old, const struct decl *new) +{ + bool takeoldscls = 0; + if (old->scls != new->scls) { + if (old->scls == SCSTATIC && (new->scls &~ SCEXTERN) == SCNONE && old->ty.t == TYFUNC && new->ty.t == TYFUNC) + takeoldscls = 1; + else + return 0; + } + switch (old->scls) { + case SCSTATIC: + case SCEXTERN: + if (old->ty.bits == new->ty.bits) goto OkFuncs; + if (old->ty.t != new->ty.t) return 0; + if (old->ty.t == TYARRAY /* allow 'int x[]; int x[100];' */ + && typechild(old->ty).bits == typechild(new->ty).bits + && (isincomplete(old->ty) || isincomplete(new->ty))) + { + return 1; + } + if (old->ty.t == TYFUNC /* allow 'int f(); int f(int);' (some K&R) */ + && typedata[old->ty.dat].ret.bits == typedata[new->ty.dat].ret.bits + && (typedata[old->ty.dat].kandr || typedata[new->ty.dat].kandr)) + { OkFuncs: + if (takeoldscls) ((struct decl *)new)->scls = old->scls; + return 1; + } + return 0; + case SCTYPEDEF: + return old->ty.bits == new->ty.bits; + } + return 0; +} + +static int +putdecl(struct comp *cm, const struct decl *decl) +{ + for (struct env *env = cm->env; env; env = env->up) { + struct decl *l; + if (!env->up) { + ushort *pi = pmap_get(&tldeclmap, decl->name); + if (pi) { + l = &declsbuf.p[*pi]; + goto Match; + } + } else for (l = NULL; enviterdecl(&l, env);) { + if (decl->name == l->name) { + Match: + if ((cm->env->up != NULL && decl->scls == SCSTATIC) || (l->isdef && decl->isdef)) { + error(&decl->span, "redefinition of '%s'", decl->name); + note(&l->span, "previously defined here"); + break; + } else if (!redeclarationok(l, decl)) { + error(&decl->span, "incompatible redeclaration of '%s'", decl->name); + note(&l->span, "previously declared here"); + break; + } + if (l->isdef && !decl->isdef) return l - declsbuf.p; + break; + } + } + if (decl->scls != SCEXTERN) break; + } + return envadddecl(cm->env, decl); +} + +static struct decl * +finddecl(struct comp *cm, internstr name) +{ + assert(name); + for (struct env *e = cm->env; e; e = e->up) { + if (!e->up) { + ushort *pi = pmap_get(&tldeclmap, name); + if (pi) return &declsbuf.p[*pi]; + } else for (struct decl *l = NULL; enviterdecl(&l, e);) { + if (name == l->name) + return l; + } + } + return NULL; +} + +static union type +gettagged(struct comp *cm, struct span *span, enum typetag tt, internstr name, bool dodef) +{ + struct typedata td = {0}; + assert(name); + for (struct env *e = cm->env; e; e = e->up) { + for (struct tagged *l = NULL; envitertagged(&l, e);) { + if (name == ttypenames[typedata[l->ty.dat].id]) { + if (dodef && e != cm->env) + goto Break2; + *span = l->span; + return l->ty; + } + } + } + if (tt == TYENUM && ccopt.pedant) { + warn(span, "forward-declared enum is an extension"); + } +Break2: + td.t = tt; + return envaddtagged(cm->env, mktagtype(name, &td), span)->ty; +} + +static union type +deftagged(struct comp *cm, struct span *span, enum typetag tt, internstr name, union type ty) +{ + struct typedata td = {0}; + assert(name); + for (struct tagged *l = NULL; envitertagged(&l, cm->env);) { + if (name == ttypenames[typedata[l->ty.dat].id]) { + *span = l->span; + return l->ty; + } + } + td.t = tt; + return envaddtagged(cm->env, ty.t ? ty : mktagtype(name, &td), span)->ty; +} + +/*********************/ +/* Expr Typechecking */ +/*********************/ + +#define iszero(ex) ((ex).t == ENUMLIT && isint((ex).ty) && (ex).u == 0) + +static bool +islvalue(const struct expr *ex) +{ + if (ex->t == EGETF) return islvalue(ex->sub); + return ex->t == ESYM || ex->t == EDEREF || ex->t == EINIT || ex->t == ESTRLIT; +} + +static union type /* 6.5.2.6 default argument promotions */ +argpromote(union type t) +{ + if (isint(t)) t.t = intpromote(t.t); + else if (t.t == TYFLOAT) t.t = TYDOUBLE; + else if (t.t == TYARRAY) return mkptrtype(typechild(t), t.flag & TFCHLDQUAL); + else if (t.t == TYFUNC) return mkptrtype(t, 0); + return t; +} + +bool +assigncheck(union type t, const struct expr *src) +{ + union type srcty = typedecay(src->ty);; + if (assigncompat(t, srcty)) { + if (t.t == TYPTR && srcty.t == TYPTR + && (t.flag & TFCHLDQUAL & srcty.flag & TFCHLDQUAL) != (srcty.flag & TFCHLDQUAL)) { + warn(&src->span, "usage of '%ty' discards pointer qualifiers", src->ty); + } + return 1; + } else if (t.t == TYPTR && srcty.t == TYPTR) { + warn(&src->span, "converting between incompatible pointer types ('%ty' -> '%ty')", srcty, t); + return 1; + } else if (t.t == TYPTR && iszero(*src)) return 1; + return 0; +} + +static bool +initcheck(union type t, const struct expr *src) +{ + if (assigncheck(t, src)) return 1; + if (t.bits == src->ty.bits && (src->t == EINIT || src->t == ESTRLIT)) return 1; + return 0; +} + +static void +incdeccheck(enum toktag tt, const struct expr *ex, const struct span *span) +{ + if (!isscalar(ex->ty)) + error(&ex->span, "invalid operand to %tt '%ty'", tt, ex->ty); + else if (!islvalue(ex)) + error(&ex->span, "operand to %tt is not an lvalue", tt); + else if (ex->ty.t == TYPTR && isincomplete(typechild(ex->ty))) + error(span, "arithmetic on pointer to incomplete type '%ty'", ex->ty); + else if (ex->ty.t == TYPTR && typechild(ex->ty).t == TYFUNC) + error(span, "arithmetic on function pointer '%ty'", ex->ty); +} + +static bool /* 6.5.4 Cast operators */ +castcheck(union type to, const struct expr *ex) +{ + union type src = ex->ty; + if (to.t == TYVOID) return 1; + if (isagg(to)) return 0; + if (to.bits == src.bits) return 1; + if (isarith(to) && isarith(src)) return 1; + if (isint(to) && isptrcvt(src)) return 1; + if (to.t == TYPTR && isint(src)) return 1; + if (to.t == TYPTR && isptrcvt(src)) return 1; + return 0; +} + +static union type /* 6.5.2.1 Array subscripting */ +subscriptcheck(const struct expr *ex, const struct expr *rhs, const struct span *span) +{ + union type ty; + if (ex->ty.t == TYPTR || ex->ty.t == TYARRAY) { + if (isincomplete(typedecay(ty = typechild(ex->ty)))) { + error(span, "cannot dereference pointer to incomplete type '%ty'", ty); + ty = mktype(TYINT); + } else if (ty.t == TYFUNC) { + error(span, "subscripted value is pointer to function"); + ty = mktype(TYINT); + } + } else { + error(&ex->span, "subscripted value is not pointer-convertible '%ty'", ex->ty); + ty = mktype(TYINT); + } + if (!isint(rhs->ty)) + error(&rhs->span, "array subscript is not integer ('%ty')", rhs->ty); + return ty; +} + +static uint /* 6.5.3.4 The sizeof and _Alignof operators */ +sizeofalignofcheck(const struct span *span, enum toktag tt, union type ty, const struct expr *ex) +{ + uint r = (tt == TKWsizeof ? typesize : typealign)(ty); + if (ty.t == TYVOID) { + if (ccopt.pedant) warn(span, "applying %'tt to void type", tt); + r = 1; + } else if (isincomplete(ty)) { + error(span, "cannot apply %'tt to incomplete type '%ty'", tt, ty); + } else if (ty.t == TYFUNC) { + error(span, "cannot apply %'tt to function type '%ty'", tt, ty); + } else if (tt == TKWsizeof && ex && ex->t == EGETF && ex->fld.bitsiz) { + error(span, "cannot apply %'tt to bitfield", tt); + } + if (tt != TKWsizeof && ex && ccopt.pedant) + warn(span, "%'tt applied to an expression is a GNU extension", tt); + return r; +} + +static bool /* 6.5.8 Relational operators */ +relationalcheck(const struct expr *a, const struct expr *b) +{ + union type t1 = a->ty, t2 = b->ty; + if (isarith(t1) && isarith(t2)) return 1; + if (isptrcvt(t1) && isptrcvt(t2)) { + t1 = typedecay(t1); + t2 = typedecay(t2); + return t1.dat == t2.dat; + } + return 0; +} + +static bool +isnullpo(const struct expr *ex) /* match '0' or '(void *) 0' */ +{ + static const union type voidptr = {{ TYPTR, .flag = TFCHLDPRIM, .child = TYVOID }}; + while (ex->t == ECAST && ex->ty.bits == voidptr.bits) + ex = ex->sub; + if (iszero(*ex)) return 1; + return eval((struct expr *)ex, EVINTCONST) /* GNU extension. should we warn? */ + && iszero(*ex); +} + +static bool /* 6.5.9 Equality operators */ +equalitycheck(const struct expr *a, const struct expr *b) +{ + union type t1 = a->ty, t2 = b->ty; + if (isarith(t1) && isarith(t2)) return 1; + if (isptrcvt(t1) && isptrcvt(t2)) { + t1 = typedecay(t1), t2 = typedecay(t2); + /* comparing .dat works for both TFCHLDPRIM and not, (checks equal child types) + * quals are ignored either way */ + return t1.dat == t2.dat || typechild(t1).t == TYVOID || typechild(t2).t == TYVOID; + } + return (isptrcvt(t1) && isnullpo(b)) || (isptrcvt(t2) && isnullpo(a)); +} + +static union type /* 6.5.15 Conditional operator */ +condtype(const struct expr *a, const struct expr *b) +{ + union type t1 = typedecay(a->ty), t2 = typedecay(b->ty), s1, s2; + if (isarith(t1) && isarith(t2)) return cvtarith(t1, t2); + if (t1.bits == t2.bits) return t1; + if (t1.t == TYPTR && isnullpo(b)) return t1; + if (isnullpo(a) && t2.t == TYPTR) return t2; + if (t1.t == TYPTR && t2.t == TYPTR) { + s1 = typechild(t1), s2 = typechild(t2); + if (s1.bits == s2.bits || s2.t == TYVOID || s1.t == TYVOID) { + return mkptrtype(s1.t == TYVOID ? s1 : s2, (t1.flag | t2.flag) & TFCHLDQUAL); + } + } + return mktype(0); +} + +static void +bintypeerr(const struct span *span, enum toktag tt, union type lhs, union type rhs) +{ + error(span, "bad operands to %tt ('%ty', '%ty')", tt, lhs, rhs); +} + +enum binopclass { /* binary operator type-checking classes */ + BCSET = 1<<7, /* is a (compound) assignment operator? */ + BCSEQ = 1, BCADDITIVE, BCARITH, BCINT, BCSHFT, BCEQL, BCCMP, BCLOG, +}; + +/* table indexed by binary op token; + * containing precedence level, expression kind and type-checking class */ +static const struct { uchar prec, t, k; } bintab[] = { + ['*'] = {13, EMUL, BCARITH}, + ['/'] = {13, EDIV, BCARITH}, + ['%'] = {13, EREM, BCINT}, + ['+'] = {12, EADD, BCADDITIVE}, + ['-'] = {12, ESUB, BCADDITIVE}, + [TKSHL] = {11, ESHL, BCSHFT}, + [TKSHR] = {11, ESHR, BCSHFT}, + ['<'] = {10, ELTH, BCCMP}, + ['>'] = {10, EGTH, BCCMP}, + [TKLTE] = {10, ELTE, BCCMP}, + [TKGTE] = {10, EGTE, BCCMP}, + [TKEQU] = {9, EEQU, BCEQL}, + [TKNEQ] = {9, ENEQ, BCEQL}, + ['&'] = {8, EBAND, BCINT}, + ['^'] = {7, EXOR, BCINT}, + ['|'] = {6, EBIOR, BCINT}, + [TKLOGAND] = {5, ELOGAND, BCLOG}, + [TKLOGIOR] = {4, ELOGIOR, BCLOG}, + ['?'] = {3, ECOND}, /* not actually a binop (special cased) */ + ['='] = {2, ESET, BCSET}, + [TKSETADD] = {2, ESETADD, BCSET|BCADDITIVE}, [TKSETSUB] = {2, ESETSUB, BCSET|BCADDITIVE}, + [TKSETMUL] = {2, ESETMUL, BCSET|BCARITH}, [TKSETDIV] = {2, ESETDIV, BCSET|BCARITH}, + [TKSETREM] = {2, ESETREM, BCSET|BCINT}, [TKSETAND] = {2, ESETAND, BCSET|BCINT}, + [TKSETIOR] = {2, ESETIOR, BCSET|BCINT}, [TKSETXOR] = {2, ESETXOR, BCSET|BCINT}, + [TKSETSHL] = {2, ESETSHL, BCSET|BCSHFT}, [TKSETSHR] = {2, ESETSHR, BCSET|BCSHFT}, + [','] = {1, ESEQ, BCSEQ} +}; + +static union type +bintypecheck(const struct span *span, enum toktag tt, struct expr *lhs, struct expr *rhs) +{ + enum binopclass k = bintab[tt].k; + union type ty = lhs->ty; + + assert(k); + if (k & BCSET) { + if (!islvalue(lhs)) + error(&lhs->span, "left-hand-side of assignment is not an lvalue"); + else if (lhs->qual & QCONST) + error(&lhs->span, "cannot assign to const-qualified lvalue (%tq)", ty, lhs->qual); + else if (isincomplete(ty)) + error(&lhs->span, "cannot assign to incomplete type '%ty'", ty); + else if (ty.t == TYARRAY) + error(&lhs->span, "cannot assign to array type '%ty'", ty); + else if (ty.t == TYFUNC) + error(&lhs->span, "cannot assign to function designator '%ty'", lhs->ty); + } + switch (k &~ BCSET) { + case 0: + if (isagg(ty) && !(lhs->qual & QCONST) && typedata[ty.dat].anyconst) + error(&lhs->span, "cannot assign to aggregate with const-qualified member"); + if (!assigncheck(ty, rhs)) + goto Error; + break; + case BCSEQ: + ty = rhs->ty; + break; + case BCADDITIVE: + if (tt == '+' && isptrcvt(rhs->ty)) { + /* int + ptr -> ptr + int (for convenience) */ + const struct expr swaptmp = *lhs; + *lhs = *rhs; + *rhs = swaptmp; + ty = lhs->ty; + } + if (isarith(ty) && isarith(rhs->ty)) { + /* num +/- num */ + ty = cvtarith(ty, rhs->ty); + assert(ty.t); + } else if ((ty.t == TYPTR || ty.t == TYARRAY) && isint(rhs->ty)) { + /* ptr +/- int */ + union type pointee = typechild(ty); + if (isincomplete(pointee)) + error(span, "arithmetic on pointer to incomplete type '%ty'", ty); + else if (pointee.t == TYFUNC) + error(span, "arithmetic on function pointer '%ty'", ty); + ty = typedecay(ty); + } else if (tt == '-' && isptrcvt(ty) && isptrcvt(rhs->ty)) { + /* ptr - ptr */ + union type pointee1 = typechild(typedecay(ty)), + pointee2 = typechild(typedecay(rhs->ty)); + if (isincomplete(pointee1)) + error(span, "arithmetic on pointer to incomplete type '%ty'", ty); + else if (pointee1.t == TYFUNC) + error(span, "arithmetic on function pointer '%ty'", lhs->ty); + else if (pointee1.bits != pointee2.bits) { + error(span, "arithmetic on incompatible pointer types: '%ty', '%ty'", + ty, rhs->ty); + } + ty = mktype(targ_ptrdifftype); + } else goto Error; + break; + case BCARITH: + ty = cvtarith(ty, rhs->ty); + if (!ty.t) { + ty.t = TYINT; + Error: + bintypeerr(span, tt, lhs->ty, rhs->ty); + } + break; + case BCINT: + if (!isint(ty) || !isint(rhs->ty)) + goto Error; + ty = cvtarith(ty, rhs->ty); + assert(ty.t); + break; + case BCSHFT: /* 6.5.7 Bitwise shift operators */ + if (!isint(ty) || !isint(rhs->ty)) { + ty = mktype(TYINT); + goto Error; + } + ty.t = intpromote(ty.t); + assert(ty.t); + break; + case BCEQL: + ty = mktype(TYINT); + if (!equalitycheck(lhs, rhs)) { + if (isptrcvt(lhs->ty) && isptrcvt(rhs->ty)) + warn(span, "comparison of distinct pointer types ('%ty' and '%ty')", lhs->ty, rhs->ty); + else + goto Error; + } + break; + case BCCMP: + ty = mktype(TYINT); + if (!relationalcheck(lhs, rhs)) { + if (isptrcvt(lhs->ty) && isptrcvt(rhs->ty)) + warn(span, "comparison of distinct pointer types ('%ty' and '%ty')", lhs->ty, rhs->ty); + else + goto Error; + } + break; + case BCLOG: /* 6.5.13-14 Logical AND/OR operator */ + ty = mktype(TYINT); + if (!isscalar(typedecay(ty)) || !isscalar(typedecay(rhs->ty))) + goto Error; + break; + } + return (k & BCSET) || !ty.t ? lhs->ty : ty; +} + +/****************/ +/* Expr Parsing */ +/****************/ + +#define mkexpr(t_,span_,ty_,...) ((struct expr){.t=(t_), .ty=(ty_), .span=(span_), __VA_ARGS__}) + +static struct expr * +exprdup(struct comp *cm, const struct expr *e) +{ + return alloccopy(&cm->exarena, e, sizeof *e, 0); +} +static struct expr * +exprdup2(struct comp *cm, const struct expr *e1, const struct expr *e2) +{ + struct expr *r = alloc(&cm->exarena, 2*sizeof *r, 0); + r[0] = *e1, r[1] = *e2; + return r; +} + +static struct expr expr(struct comp *cm); +static struct expr commaexpr(struct comp *cm); + +enum { IMPLICITSYMTY = 0xFF, }; + +static struct expr /* 6.5.2.2 Function calls */ +callexpr(struct comp *cm, const struct span *span_, const struct expr *callee) +{ + struct token tk; + struct expr ex, arg; + struct span span = callee->span; + union type ty = callee->ty; + const struct typedata *td = NULL; + struct expr argbuf[10]; + vec_of(struct expr) args = VINIT(argbuf, countof(argbuf)); + bool spanok = joinspan(&span.ex, span_->ex); + bool printsig = 0; + const struct builtin *builtin = NULL; + + if (callee->t == ESYM && !callee->ty.t && declsbuf.p[callee->decl].isbuiltin) { + builtin = declsbuf.p[callee->decl].builtin; + assert(!ty.t); + } + + if (callee->t == ESYM && ty.t == IMPLICITSYMTY) { /* implicit function decl.. */ + internstr name = callee->implicitsym; + struct decl decl = { + (ty = mkfntype(mktype(TYINT), 0, NULL, /* kandr */ 1, 0)), + .scls = SCEXTERN, .span = span, .name = name, .sym = name + }; + warn(&span, "call to undeclared function '%s'", name); + ((struct expr *)callee)->ty = decl.ty; + ((struct expr *)callee)->decl = putdecl(cm, &decl); + } + + if (!builtin) { + if (ty.t == TYPTR) /* auto-deref when calling a function pointer */ + ty = typechild(ty); + if (ty.t != TYFUNC) + error(&span, "calling a value of type '%ty'", callee->ty); + else + td = &typedata[ty.dat]; + if (ty.t == TYFUNC && td->ret.t != TYVOID && isincomplete(td->ret)) + error(&span, "cannot call function with incomplete return type '%ty'", td->ret); + } + + if (!match(cm, &tk, ')')) for (;;) { + arg = expr(cm); + spanok = spanok && joinspan(&span.ex, callee->span.ex); + if (td && args.n == td->nmemb && !td->variadic && !td->kandr) { + error(&arg.span, "too many args to function taking %d params", td->nmemb); + printsig = 1; + } + if (arg.ty.t == TYVOID) { + error(&arg.span, "invalid use of void expression"); + } else if (td && args.n < td->nmemb && !td->kandr) { + if (!assigncheck(td->param[args.n], &arg)) { + error(&arg.span, "arg #%d of type '%ty' is incompatible with '%ty'", + args.n+1, arg.ty, td->param[args.n]); + printsig = 1; + } + } + vpush(&args, arg); + peek(cm, &tk); + if (match(cm, &tk, ',')) { + spanok = spanok && joinspan(&span.ex, tk.span.ex); + } else if (expect(cm, ')', "or ',' after arg")) { + break; + } + } + if (!spanok || !joinspan(&span.ex, tk.span.ex)) span = *span_; + + if (td && !td->variadic && !td->kandr && args.n < td->nmemb) { + error(&tk.span, "not enough args to function taking %d param%s", + td->nmemb, td->nmemb != 1 ? "s" : ""); + printsig = 1; + } + if (printsig) note(&callee->span, "function signature is '%ty'", ty); + + ex = mkexpr(ECALL, span, ty.t == TYFUNC ? td->ret : ty, .narg = args.n, + .sub = alloc(&cm->exarena, (args.n+1)*sizeof(struct expr), 0)); + ex.sub[0] = *callee; + memcpy(ex.sub+1, args.p, args.n*sizeof(struct expr)); + vfree(&args); + if (builtin) { + builtin->sema(cm, &ex); + } + return ex; +} + +static void +ppostfixopers(struct comp *cm, struct expr *ex) +{ + struct expr tmp, rhs; + struct token tk, tk2; + struct span span; + union type ty; + + for (;;) switch (peek(cm, &tk)) { + default: return; + case TKINC: + case TKDEC: + lex(cm, &tk); + span = ex->span; + if (!joinspan(&span.ex, tk.span.ex)) span = tk.span; + incdeccheck(tk.t, ex, &span); + *ex = mkexpr(tk.t == TKINC ? EPOSTINC : EPOSTDEC, span, ex->ty, .sub = exprdup(cm, ex)); + continue; + case '[': /* a[subscript] */ + lex(cm, NULL); + rhs = commaexpr(cm); + span = ex->span; + if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, ex->span.ex) + || (peek(cm, &tk), !joinspan(&span.ex, tk.span.ex))) + span = tk.span; + expect(cm, ']', NULL); + + if (isint(ex->ty) && isptrcvt(rhs.ty)) { + /* swap idx[ptr] -> ptr[idx] */ + tmp = *ex; + *ex = rhs; + rhs = tmp; + } + + ty = subscriptcheck(ex, &rhs, &span); + assert(ty.t); + if (!iszero(rhs)) { + tmp.sub = exprdup2(cm, ex, &rhs); + tmp.t = EADD; + tmp.span = span; + tmp.ty = typedecay(ex->ty); + } + tmp.sub = exprdup(cm, iszero(rhs) ? ex : &tmp); + tmp.span = span; + tmp.t = EDEREF; + tmp.qual = ex->ty.flag & TFCHLDQUAL; + tmp.ty = ty; + *ex = tmp; + continue; + case '(': /* call(args) */ + lex(cm, &tk); + span = ex->span; + *ex = callexpr(cm, &span, ex); + continue; + case TKARROW: + if (ex->ty.t != TYPTR && ex->ty.t != TYARRAY) + error(&ex->span, "operand to -> is not a pointer: '%ty'", ex->ty); + else + *ex = mkexpr(EDEREF, ex->span, typechild(ex->ty), .qual = ex->ty.flag & TFCHLDQUAL, + .sub = exprdup(cm, ex)); + /* fallthru */ + case '.': + lex(cm, &tk); + span = ex->span; + peek(cm, &tk2); /* field name */ + if (!expect(cm, TKIDENT, NULL)) tk2.s = ""; + if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, tk2.span.ex)) + span = tk.span; + if (!isagg(ex->ty)) { + error(&span, "member access operand is not an aggregate: '%ty'%s", ex->ty, + ex->ty.t == TYPTR && isagg(typechild(ex->ty)) ? "; did you mean to use '->'?" : ""); + } else { + struct fielddata fld = {.t = mktype(TYINT)}; + if (*tk2.s && !getfield(&fld, ex->ty, tk2.name)) + error(&span, "'%ty' has no such field: '%s'", ex->ty, tk2.name); + if (ex->t == EGETF && ex->qual == fld.qual) { /* accumulate */ + ex->span = span; + ex->ty = fld.t; + ex->fld.off += fld.off; + ex->fld.bitoff = fld.bitoff; + ex->fld.bitsiz = fld.bitsiz; + } else { + *ex = mkexpr(EGETF, span, fld.t, .qual = ex->qual | fld.qual, .sub = exprdup(cm, ex), + .fld = { fld.off, fld.bitsiz, fld.bitoff }); + } + } + continue; + } +} + +static struct expr +vaargexpr(struct comp *cm, struct span *span) +{ + struct token tk; + struct expr ex = mkexpr(EXXX, *span, mktype(TYVOID), ); + if (expect(cm, '(', "after __builtin_va_arg")) { + struct expr arg = expr(cm); + struct decl decl; + union type ty; + expect(cm, ',', NULL); + decl = pdecl(&(struct declstate){DCASTEXPR}, cm); + ty = decl.ty; + peek(cm, &tk); + if (expect(cm, ')', NULL)) + joinspan(&span->ex, tk.span.ex); + if (ty.t == TYARRAY) + warn(&decl.span, "va_arg type argument is array type '%ty', which is undefined behavior", decl.ty); + else if (ty.t == TYFUNC) + error(&decl.span, "va_arg type argument is function type '%ty'", decl.ty); + else { + ty = argpromote(ty); + if (ty.bits != decl.ty.bits) { + warn(&decl.span, + "va_arg type argument is promotable type '%ty', which has undefined behavior" + " (it will be promoted to '%ty')", decl.ty, ty); + } + } + ex = mkexpr(EVAARG, *span, decl.ty, .sub = exprdup(cm, &arg)); + } + return ex; +} + +static struct expr +genericexpr(struct comp *cm, struct span *span) +{ + struct token tk; + if (expect(cm, '(', "after _Generic")) { + struct expr control = expr(cm), dfault = {0}, ex = {0}; + expect(cm, ',', NULL); + for (;;) { + if (match(cm, &tk, TKWdefault)) { + expect(cm, ':', NULL); + if (dfault.t) { + error(&tk.span, "duplicate 'default' specifier in generic selection expression"); + (void)expr(cm); + } else { + dfault = expr(cm); + } + } else { + struct decl decl = pdecl(&(struct declstate){DCASTEXPR}, cm); + union type ty = decl.ty; + expect(cm, ':', NULL); + if (!ex.t && + (typedecay(ty).bits == typedecay(control.ty).bits + || ((ty.t == TYENUM || control.ty.t == TYENUM) && scalartypet(ty) == scalartypet(control.ty)))) + ex = expr(cm); + else + (void)expr(cm); + } + if (match(cm, &tk, ')')) break; + else if (!expect(cm, ',', "or `)'")) { + if (!isdecltok(cm)) { + peek(cm, &tk); /* want the span */ + break; + } + } + } + if (!ex.t) ex = dfault; + if (!ex.t) { + error(&control.span, + "controlling type '%ty' not compatible with any generic association type", + control.ty); + ex.ty.t = TYINT; + } + ex.span = *span; + joinspan(&ex.span.ex, tk.span.ex); + return ex; + } + return mkexpr(ENUMLIT,*span,mktype(TYINT),); +} + +static inline int +tkprec(int tt) +{ + return ((uint)tt < countof(bintab)) ? bintab[tt].prec : 0; +} + +static struct expr initializer(struct comp *cm, union type *ty, enum evalmode ev, + bool globl, enum qualifier qual, internstr name); + +static internstr istr__func__, istr_main, istr_memset; + +static internstr mkhiddensym(const char *fnname, const char *name, int id); + +/* parse an expression with the given operator precedence */ +/* param ident is a kludge to support block labels without backtracking or extra lookahead + * see stmt() */ +enum exprctx { EFROMSTMT = 1, EARRAYCOUNT, EATTRARG }; +static struct expr +exprparse(struct comp *cm, int prec, const struct token *ident, enum exprctx ctx) +{ + struct token tk; + struct span span; + struct expr ex; + union type ty; + struct { + struct span span; + union { + union type ty; /* cast type */ + struct { + uchar t0; /* t == 0 */ + short tt; /* token */ + }; + }; + } unops[4]; + int nunop = 0; + + if (ident) { + assert(ident->t == TKIDENT); + tk = *ident; + ident = NULL; + goto Ident; + } + +Unary: + switch (lex(cm, &tk)) { + /* unary operators (gather) */ + case '*': + if (ctx == EARRAYCOUNT && peek(cm, NULL) == ']') { + /* kludge for C99 `int x[*]` (unk VLA size) */ + return (struct expr) { 0, .span = tk.span }; + } + /* fallthru */ + case '+': case '-': case '~': case '!': + case '&': case TKINC: case TKDEC: + Unops: + unops[nunop].span = tk.span; + unops[nunop].t0 = 0; + unops[nunop].tt = tk.t; + if (++nunop >= countof(unops)) { + ex = exprparse(cm, 999, NULL, 0); + break; + } + goto Unary; + + /* might be unary op (cast) or primary expr */ + case '(': + if (!isdecltok(cm)) { /* (expr) */ + struct span span = tk.span; + ex = commaexpr(cm); + joinspan(&span.ex, ex.span.ex); + peek(cm, &tk); + if (expect(cm, ')', NULL)) joinspan(&span.ex, tk.span.ex); + ex.span = span; + } else { /* (type) expr */ + struct declstate st = { DCASTEXPR }; + struct decl decl = pdecl(&st, cm); + struct span span = tk.span; + assert(decl.ty.t); + peek(cm, &tk); + if (expect(cm, ')', NULL)) + joinspan(&span.ex, tk.span.ex); + if (peek(cm, NULL) == '{') { + if (ccopt.cstd < STDC99) + warn(&tk.span, "compound literals are a c99 feature"); + ex = initializer(cm, &decl.ty, (decl.scls & SCSTATIC) ? EVSTATICINI : EVFOLD, + /* globl */ 0, decl.qual, NULL); + break; + } + unops[nunop].span = span; + unops[nunop].ty = decl.ty; + if (++nunop >= countof(unops)) { + ex = exprparse(cm, 999, NULL, 0); + break; + } + goto Unary; + } + break; + /* base exprs */ + case TKNUMLIT: + case TKCHRLIT: + ex = mkexpr(ENUMLIT, tk.span, mktype(0), ); + if (!(ty.t = parsenumlit(&ex.u, &ex.f, &tk, 0))) + error(&tk.span, "bad %s literal %'tk", tk.t == TKNUMLIT ? "number" : "character", &tk); + ex.ty.t = ty.t ? ty.t : TYINT; + break; + case TKWtrue: case TKWfalse: + ex = mkexpr(ENUMLIT, tk.span, mktype(TYBOOL), .u = tk.t == TKWtrue); + break; + case TKSTRLIT: + ty = mktype(((const char []){TYCHAR, TYSHORT, TYINT})[tk.wide]); + ex = mkexpr(ESTRLIT, tk.span, mkarrtype(ty, 0, tk.len+1), { .s.p = (void *)tk.s, .s.n = tk.len }); + break; + case TKIDENT: Ident: { + struct decl *decl = finddecl(cm, tk.name); + if (!decl) { + if (cm->env->up && tk.name->c == '_' + && (!strcmp(&tk.name->c, "__FUNCTION__") || !strcmp(&tk.name->c, "__PRETTY_FUNCTION__"))) { + /* hack: treat these identifiers as __func__ synonym to support the GNU extension */ + warn(&tk.span, "%'tk is a GNU extension", &tk); + decl = finddecl(cm, istr__func__); + assert(decl && decl->scls == SCSTATIC); + goto Sym; + } else if (ctx == EATTRARG && nunop == 0 && (peek(cm, NULL) == ',' || peek(cm, NULL) == ')')) { + return ex = mkexpr(ESYM, tk.span, mktype(IMPLICITSYMTY), .implicitsym = tk.name); + } else if (peek(cm, NULL) == '(') { /* implicit function decl? */ + ex = mkexpr(ESYM, tk.span, mktype(IMPLICITSYMTY), .implicitsym = tk.name); + } else { + error(&tk.span, "undeclared identifier %'tk", &tk); + ex = mkexpr(ESYM, tk.span, mktype(TYINT), .implicitsym = NULL); + } + } else if (decl->scls == SCTYPEDEF) { + error(&tk.span, "unexpected typename %'tk (expected expression)", &tk); + ex = mkexpr(ESYM, tk.span, decl->ty, .implicitsym = NULL); + } else if (decl->isenum) { + ex = mkexpr(ENUMLIT, tk.span, decl->ty, .i = decl->value); + } else Sym: { + if (decl->name == istr__func__ && decl->isbuiltin) { /* lazy __func__ */ + internstr fnname = decl->sym; + decl->isbuiltin = 0; + decl->sym = mkhiddensym(&fnname->c, "__func__", 1); + uint off = objnewdat(decl->sym, objout.code ? Stext : Srodata, 0, typesize(decl->ty), typealign(decl->ty)); + uchar *p = objout.code ? objout.textbegin + off : objout.rodata.p + off; + memcpy(p, fnname, typearrlen(decl->ty)-1); + } + ex = mkexpr(ESYM, tk.span, decl->ty, .qual = decl->qual, .decl = decl - declsbuf.p); + } + break; } + case TKWsizeof: case TKW_Alignof: case TKWalignof: { + enum toktag tt = tk.t; + uint res; + span = tk.span; + if (!match(cm, NULL, '(')) /* sizeof/alignof expr */ + goto Unops; + else if (isdecltok(cm)) { /* sizeof/alignof (type) */ + struct declstate st = { DCASTEXPR }; + ty = pdecl(&st, cm).ty; + peek(cm, &tk); + if (expect(cm, ')', NULL)) + joinspan(&span.ex, tk.span.ex); + res = sizeofalignofcheck(&span, tt, ty, NULL); + } else { /* sizeof/alignof expr */ + struct expr tmp = commaexpr(cm); + peek(cm, &tk); + if (expect(cm, ')', NULL)) + joinspan(&span.ex, tk.span.ex); + ppostfixopers(cm, &tmp); + ty = tmp.ty; + res = sizeofalignofcheck(&span, tt, ty, &tmp); + } + ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype), .u = res); + break; } + case TKW__builtin_va_arg: + span = tk.span; + ex = vaargexpr(cm, &span); + break; + case TKW_Generic: + span = tk.span; + ex = genericexpr(cm, &span); + break; + default: + fatal(&tk.span, "expected %s (near %'tk)", ctx == EFROMSTMT ? "statement" : "expression", &tk); + } + + ppostfixopers(cm, &ex); + + /* unary operators (process) */ + while (nunop-- > 0) { + enum exprkind ek; + span = unops[nunop].span; + joinspan(&span.ex, ex.span.ex); + if (unops[nunop].t0 == 0) { + switch (unops[nunop].tt) { + case '+': + ek = EPLUS; + goto Alu; + case '-': + ek = ENEG; + goto Alu; + case '~': + ek = ECOMPL; + goto Alu; + case '!': + ek = ELOGNOT; + Alu: + ty = ek == ELOGNOT ? mktype(TYINT) : cvtarith(ex.ty, ex.ty); + if (!ty.t || (ek == ECOMPL && !isint(ty))) { + error(&tk.span, "invalid operand to %'tk '%ty'", &tk, ex.ty); + ty = mktype(TYINT); + } + ex = mkexpr(ek, span, ty, .sub = exprdup(cm, &ex)); + break; + case TKINC: case TKDEC: + ty = ex.ty; + incdeccheck(tk.t, &ex, &span); + ex = mkexpr(unops[nunop].tt == TKINC ? EPREINC : EPREDEC, span, ty, + .sub = exprdup(cm, &ex)); + break; + case '*': + if (ex.ty.t == TYPTR || ex.ty.t == TYARRAY) { + ty = typechild(ex.ty); + if (ty.t != TYVOID && isincomplete(typedecay(ty))) { + error(&span, "cannot dereference pointer to incomplete type '%ty'", ty); + ty = mktype(TYINT); + } + } else { + error(&span, "invalid operand to unary * '%ty'", ex.ty); + ty = mktype(TYINT); + } + ex = mkexpr(EDEREF, span, ty, .qual = ex.ty.flag & TFCHLDQUAL, + .sub = exprdup(cm, &ex)); + break; + case '&': + if (!islvalue(&ex)) + error(&span, "operand to unary & is not an lvalue"); + if (ex.t == EGETF && ex.fld.bitsiz) + error(&span, "cannot take address of bitfield"); + ex = mkexpr(EADDROF, span, mkptrtype(ex.ty, ex.qual), .sub = exprdup(cm, &ex)); + break; + case TKWsizeof: case TKW_Alignof: case TKWalignof: + ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype), + .u = sizeofalignofcheck(&span, unops[nunop].tt, ex.ty, &ex)); + break; + default: assert(0); + } + } else { /* cast */ + ty = unops[nunop].ty; + if (!castcheck(ty, &ex)) + error(&span, "cannot cast value of type '%ty' to '%ty'", ex.ty, ty); + if (ex.t == ENUMLIT && isint(ex.ty) && ty.t == TYPTR) + ex.ty = ty; + else + ex = mkexpr(ECAST, span, ty, .sub = exprdup(cm, &ex)); + } + } + + /* binary operators */ + for (int opprec; (opprec = tkprec(peek(cm, &tk))) >= prec;) { + enum exprkind ek = bintab[tk.t].t; + struct expr rhs, tmp; + lex(cm, NULL); + if (ek != ECOND) { + /* only the assignment operators are right-associative */ + bool leftassoc = (bintab[tk.t].k & BCSET) == 0; + /* ex OP rhs */ + span.sl = tk.span.sl; + span.ex = ex.span.ex; + rhs = exprparse(cm, opprec + leftassoc, NULL, 0); + if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, rhs.span.ex)) + span.ex = tk.span.ex; + ty = bintypecheck(&span, tk.t, &ex, &rhs); + assert(ty.t); + ex = mkexpr(ek, span, ty, .sub = exprdup2(cm, &ex, &rhs)); + } else { + /* logical-OR-expression ? expression : conditional-expression */ + struct expr *sub; + span.sl = tk.span.sl; + span.ex = ex.span.ex; + if (!isscalar(ex.ty) && !isptrcvt(ex.ty)) + error(&ex.span, "?: condition is not a scalar type: '%ty'", ex.ty); + tmp = commaexpr(cm); + joinspan(&tk.span.ex, tmp.span.ex); + expect(cm, ':', NULL); + rhs = exprparse(cm, opprec, NULL, 0); + if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, tmp.span.ex) + || !joinspan(&span.ex, rhs.span.ex)) + span.ex = tk.span.ex; + ty = condtype(&tmp, &rhs); + if (!ty.t) { + error(&span, "incompatible types in conditional expression: '%ty', '%ty'", tmp.ty, rhs.ty); + ty = tmp.ty; + } + sub = alloc(&cm->exarena, 3 * sizeof*sub, 0); + sub[0] = ex, sub[1] = tmp, sub[2] = rhs; + ex = mkexpr(ECOND, span, ty, .sub = sub); + } + } + + return ex; +} + +static struct expr +expr(struct comp *cm) +{ + return exprparse(cm, bintab['='].prec, NULL, 0); /* non-comma expr */ +} + +static struct expr +arraycountexpr(struct comp *cm) +{ + return exprparse(cm, bintab['='].prec, NULL, EARRAYCOUNT); /* non-comma expr, or lone '*' */ +} + +static struct expr +constantexpr(struct comp *cm) +{ + return exprparse(cm, bintab['?'].prec, NULL, 0); /* conditional-expr */ +} + +static struct expr +commaexpr(struct comp *cm) +{ + return exprparse(cm, 1, NULL, 0); +} + +/****************/ +/* Initializers */ +/****************/ + +static uint +nmemb(union type ty) +{ + switch (ty.t) { + case TYARRAY: return typearrlen(ty) ? typearrlen(ty) : -1u; + case TYUNION: case TYSTRUCT: return typedata[ty.dat].nmemb; + default: return 1; + } +} + +static bool +objectp(union type ty) +{ + return isagg(ty) || ty.t == TYARRAY; +} + +static bool +chrarrayof(union type ty, union type chld) +{ + assert(isint(chld)); + return ty.t == TYARRAY && isint(typechild(ty)) && typesize(typechild(ty)) == typesize(chld); +} + +static union type +membertype(uint *off, uint *bitsiz, uint *bitoff, union type ty, uint idx) +{ + *bitsiz = *bitoff = 0; + if (!objectp(ty)) { + *off = 0; + return ty; + } else if (ty.t == TYARRAY) { + *off = typesize(typechild(ty)) * idx; + return typechild(ty); + } else if (idx < typedata[ty.dat].nmemb) { + struct fielddata fld = typedata[ty.dat].fld[idx].f; + *off = fld.off; + *bitsiz = fld.bitsiz, *bitoff = fld.bitoff; + return fld.t; + } + *off = ~0u; + return mktype(0); +} + +struct initparser { + struct initcur { + union type ty; + uint idx; + uint off; + short prev; + } buf[32], *cur, *sub; + struct arena **arena; + uint arrlen; + enum evalmode ev; + bool dyn; /* when set, data is written to a temporary buffer first, because either: + - size is not known until parsing done (implicit array size) + - data section is not known until parsing done (to avoid relocs in .rodata) + otherwise write to the corresponding object data section buffer directly */ + union { + struct init *init; /* for initializer with automatic storage */ + struct { /* for static storage (dyn = 0) */ + enum section sec; + uint off; + }; + struct { /* for static storage (dyn = 1) */ + vec_of(uchar) ddat; + struct dreloc { + struct dreloc *link; + internstr sym; + vlong addend; + uint off; + } *drel; + }; + }; +}; + +static void +excesscheck(struct initparser *ip, const struct span *span) +{ + union type sub = ip->sub->ty; + uint n = nmemb(sub); + if (ip->sub->idx == n) { + if (sub.t == TYARRAY) + warn(span, "excess elements in array initializer for '%ty'", sub); + else if (sub.t == TYSTRUCT) + warn(span, "excess elements in initializer; '%ty' has %u member%s", sub, n, &"s"[n==1]); + else if (sub.t == TYUNION) + warn(span, "excess elements in union initializer"); + else + warn(span, "excess elements in scalar initializer"); + } +} + +#if 1 +#define dumpini(_) +#else +/* debugging */ +static void +dumpini(struct initparser *ip) +{ + efmt(">>>\n"); + for (struct initcur *s = ip->buf; s < ip->sub+1; ++s) { + efmt(" "); + efmt("%d. [%ty, %u]", s- ip->buf, s->ty, s->idx); + if (s == ip->cur) efmt(" <-- cursor"); + ioputc(&bstderr, '\n'); + } + efmt("<<<\n"); +} +#endif + +static vlong /* -> returns addend */ +expr2reloc(internstr *psym, const struct expr *ex) +{ + if (ex->t == ESSYMREF) { + *psym = ex->ssym.sym; + return ex->ssym.off; + } else if (ex->t == ESTRLIT || ex->t == EINIT) { + if (ex->t == ESTRLIT) assert(ex->ty.t == TYARRAY); + *psym = xcon2sym(expraddr(NULL, ex).i); + return 0; + } + fatal(&ex->span, "internal bug: non static reloc?"); +} + +static bool +rodatarelocok(void) +{ + return !(ccopt.pie | ccopt.pic); +} + +static void +iniwrite(struct comp *cm, struct initparser *ip, uint off, uint bitsiz, uint bitoff, union type ty, struct expr *ex) +{ + if (ex->ty.t == TYSTRUCT && ip->ev == EVSTATICINI) { + assert(ty.bits == ex->ty.bits); + for (uint i = 0, n = nmemb(ex->ty); i < n; ++i) { + uint suboff; + union type sub = membertype(&suboff, &bitsiz, &bitoff, ex->ty, i); + iniwrite(cm, ip, off + suboff, bitsiz, bitoff, sub, exprdup(cm, &mkexpr(EGETF, ex->span, sub, .sub = ex))); + } + } else if (ip->ev == EVSTATICINI) { + uchar *p; + uint siz = typesize(ty); + if (nerror) return; + if (ip->dyn) { + if (ip->ddat.n < off + siz) { + uint old = ip->ddat.n; + vresize(&ip->ddat, off + siz); + memset(ip->ddat.p + old, 0, ip->ddat.n - old); + assert(off + siz == ip->ddat.n); + } + p = ip->ddat.p + off; + } else { + p = (ip->sec == Sdata ? objout.data.p : objout.rodata.p) + ip->off + off; + } + + if (ex->t == ENUMLIT) { + struct expr *e = ex, tmp; + if (ex->ty.bits != ty.bits && ty.t != TYPTR) { + tmp = mkexpr(ECAST, ex->span, ty, .sub = ex); + e = &tmp; + assert(eval(e, EVSTATICINI)); + assert(e->t == ENUMLIT); + } + if (!bitsiz) switch (siz) { + default: assert(0); + case 1: *p = e->u; break; + case 2: wr16targ(p, e->u); break; + case 4: isint(ty) ? wr32targ(p, e->u) : wrf32targ(p, e->f); break; + case 8: isint(ty) ? wr64targ(p, e->u) : wrf64targ(p, e->f); break; + } else { + uvlong mask = (bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1) << bitoff; + if (bitoff + bitsiz > siz*8) siz <<= 1; /* straddles an allocation boundary */ + switch (siz) { + default: assert(0); + case 1: *p = (*p &~ mask) | (e->u << bitoff & mask); break; + case 2: wr16targ(p, (rd16targ(p) &~ mask) | (e->u << bitoff & mask)); break; + case 4: wr32targ(p, (rd32targ(p) &~ mask) | (e->u << bitoff & mask)); break; + case 8: wr64targ(p, (rd64targ(p) &~ mask) | (e->u << bitoff & mask)); break; + } + } + } else if (ty.t == TYARRAY && ex->t == ESTRLIT) { + uint n = ex->s.n * typesize(typechild(ty)); + if (siz < n) n = siz; + /* XXX endian for wide strs */ + memcpy(p, ex->s.p, n); + } else { + internstr sym; + vlong addend = expr2reloc(&sym, ex); + if (!ip->dyn) { + assert(ip->sec != Srodata || rodatarelocok()); + objreloc(sym, targ_64bit ? REL_ABS64 : REL_ABS32, + ip->sec, ip->off + off, addend); + } else { + struct dreloc *rel = alloc(ip->arena, sizeof *rel, 0); + rel->link = ip->drel; + rel->sym = sym; + rel->off = off; + rel->addend = addend; + ip->drel = rel; + } + } + } else { + assert(cm != NULL); + struct init *init = ip->init; + struct initval val = { + .off = off, + .bitsiz = bitsiz, + .bitoff = bitoff, + .ex = *ex + }, *new = alloccopy(&cm->exarena, &val, sizeof val, 0); + *init->tail = new; + init->tail = &new->next; + if (!bitsiz) for (uint i = off, end = i + typesize(ex->ty); i < end; ++i) { + if (BSSIZE(end) > countof(init->zero)) break; + bsclr(init->zero, i); + } + } +} + +static bool +iniwriterec(struct comp *cm, struct initparser *ip, uint off, struct expr *ex) +{ + assert(ex->t == EINIT); + for (struct initval *v = ex->init->vals; v; v = v->next) { + if (v->ex.t == EINIT) iniwriterec(cm, ip, off + v->off, &v->ex); + else if (ip->ev && !eval(&v->ex, ip->ev) && ip->ev != EVFOLD) return 0; + else iniwrite(cm, ip, off + v->off, v->bitsiz, v->bitoff, v->ex.ty, &v->ex); + } + return 1; +} + +static struct initcur * +iniadvance(struct initparser *ip, struct initcur *c, const struct span *span) +{ + if (c - ip->buf >= countof(ip->buf) - 1) + fatal(span, "too many nested initializers"); + return c + 1; +} + +/* set the initializer cursor object */ +static void +inifocus(struct initparser *ip, struct comp *cm, const struct span *span, uint idx) +{ + while (idx >= nmemb(ip->sub->ty) && ip->sub != ip->cur) { + --ip->sub; + idx = ip->sub->idx; + } + uint off, bitsiz, bitoff; + union type targ = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, idx); + struct initcur *next = iniadvance(ip, ip->cur, span); + assert(!bitsiz); + + if (isagg(ip->sub->ty) && targ.t == TYARRAY && !typearrlen(targ)) + error(span, "cannot initialize flexible array member"); + excesscheck(ip, span); + + next->ty = targ; + next->idx = 0; + next->off = ip->sub->off + off; + next->prev = ip->cur - ip->buf; + ++ip->cur->idx; + ip->sub = ip->cur = next; +} + +/* initialize a character array with a string literal */ +static void +inistrlit(struct comp *cm, struct expr *ex, union type *ty) +{ + if (isincomplete(*ty)) { + *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, ex->s.n + 1); + } else if (typearrlen(*ty) < ex->s.n) { + warn(&ex->span, "string literal in initializer is truncated from %u to %u bytes", + (ex->s.n+1)*typesize(typechild(*ty)), typesize(*ty)); + } + ex->ty = *ty; +} + +/* read scalar initializer into initializer list and avance */ +static void +ininext(struct initparser *ip, struct comp *cm) +{ + uint off, bitsiz, bitoff; + union type targ; + struct expr ex = expr(cm); + +Retry: + targ = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx); + + if (isagg(ip->sub->ty) && targ.t == TYARRAY && !typearrlen(targ)) { + error(&ex.span, "cannot initialize flexible array member"); + ++ip->sub->idx; + return; + } + if (ex.t == ESTRLIT && chrarrayof(targ, typechild(ex.ty))) { + assert(!isincomplete(targ)); + inistrlit(cm, &ex, &targ); + iniwrite(cm, ip, ip->sub->off + off, 0,0, targ, &ex); + ++ip->sub->idx; + return; + } else if (ex.t == ESTRLIT && ip->sub->idx == 0 && chrarrayof(ip->sub->ty, typechild(ex.ty))) { + /* handle e.g. (char []){"foo"} */ + assert(off == 0); + targ = ip->sub->ty; + inistrlit(cm, &ex, &targ); + iniwrite(cm, ip, ip->sub->off, 0,0, targ, &ex); + if (ip->sub == ip->buf && ip->arrlen < ex.s.n+1) + ip->arrlen = ex.s.n+1; + --ip->sub; + return; + } else if (ip->sub->idx >= nmemb(ip->sub->ty) && ip->sub != ip->cur) { + --ip->sub; + goto Retry; + } else if (objectp(targ) && targ.bits != ex.ty.bits) { + struct initcur *next = iniadvance(ip, ip->sub, &ex.span); + if (ip->sub - ip->buf == countof(ip->buf) - 1) + fatal(&ex.span, "too many nested initializers"); + ++ip->sub->idx; + *next = (struct initcur) { targ, .off = ip->sub->off + off }; + ip->sub = next; + goto Retry; + } + excesscheck(ip, &ex.span); + + if (targ.t) { + if (!initcheck(targ, &ex)) + error(&ex.span, "cannot initialize '%ty' with expression of type '%ty'", targ, ex.ty); + else { + if (targ.bits == ex.ty.bits && ex.t == EINIT) { + if (!iniwriterec(cm, ip, ip->sub->off + off, &ex)) + goto CannotEval; + } else if (ip->ev && !eval(&ex, ip->ev) && ip->ev != EVFOLD) { + CannotEval: + error(&ex.span, "cannot evaluate expression statically"); + } else { + struct expr *pex = &ex; + if (ip->ev != EVSTATICINI) { + if (ex.ty.bits != targ.bits) + ex = mkexpr(ECAST, ex.span, targ, .sub = exprdup(cm, &ex)); + pex = exprdup(cm, &ex); + } + iniwrite(cm, ip, ip->sub->off + off, bitsiz, bitoff, targ, pex); + } + } + } + if (ip->sub == ip->buf && ip->arrlen < ip->sub->idx+1) + ip->arrlen = ip->sub->idx+1; + + if (++ip->sub->idx == 0) { + error(&ex.span, "element makes object too large"); + --ip->sub->idx; + } +} + +static int +aggdesignator(struct initparser *ip, union type ty, internstr name, const struct span *span) +{ + const struct typedata *td = &typedata[ty.dat]; + for (int i = 0; i < td->nmemb; ++i) { + struct namedfield *fld = &td->fld[i]; + if (fld->name == name) { + return i; + } else if (!fld->name) { + int save, sub; + struct initcur *next = iniadvance(ip, ip->sub, span); + save = ip->sub->idx; + ip->sub->idx = i+1; + *next = (struct initcur) { fld->f.t, .off = ip->sub->off + fld->f.off }; + ip->sub = next; + sub = aggdesignator(ip, fld->f.t, name, span); + if (sub == -1) { + --ip->sub; + ip->sub->idx = save; + } else return sub; + } + } + return -1; +} + +static bool +designators(struct initparser *ip, struct comp *cm) +{ + struct token tk; + struct span span; + bool some = 0; + + for (;;) { + uint off, bitsiz, bitoff; + uvlong idx = ~0ull; + if (match(cm, &tk, '[')) { + struct expr ex = commaexpr(cm); + span = tk.span; + joinspan(&span.ex, ex.span.ex); + peek(cm, &tk); + if (some) { + union type ty = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx++); + struct initcur *next = iniadvance(ip, ip->sub, &tk.span); + assert(!bitsiz); + *next = (struct initcur) { ty, .off = ip->sub->off + off }; + ip->sub = next; + dumpini(ip); + } + if (expect(cm, ']', NULL)) joinspan(&span.ex, tk.span.ex); + if (ip->sub->ty.t != TYARRAY) + error(&ex.span, "array designator used with non-array type '%ty'", ip->sub->ty); + if (!eval(&ex, EVINTCONST)) + error(&ex.span, "array designator index is not an integer constant"); + else if (issigned(ex.ty) && ex.i < 0) + error(&ex.span, "negative array designator index"); + else if (ex.i > ~0u - 1) + error(&ex.span, "index too large"); + else { + idx = ex.u; + ip->sub->idx = idx; + if (ip->sub == ip->buf && ip->arrlen < idx+1) + ip->arrlen = idx+1; + dumpini(ip); + } + some = 1; + } else if (match(cm, &tk, '.')) { + span = tk.span; + peek(cm, &tk); + if (some) { + union type ty = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx++); + struct initcur *next = iniadvance(ip, ip->sub, &tk.span); + *next = (struct initcur) { ty, .off = ip->sub->off + off }; + ip->sub = next; + dumpini(ip); + } + if (expect(cm, TKIDENT, NULL)) joinspan(&span.ex, tk.span.ex); + if (!isagg(ip->sub->ty)) + error(&span, "member designator used with non-aggregate type '%ty'", ip->sub->ty); + else if (tk.t == TKIDENT) { + int idx; + for (;;) { + idx = aggdesignator(ip, ip->sub->ty, tk.name, &span); + if (idx >= 0 || ip->sub == ip->cur) break; + --ip->sub; + } + ip->sub->idx = idx; + if (idx < 0) + error(&span, "%ty has no such field: '%s'", ip->cur->ty, tk.name); + dumpini(ip); + } + some = 1; + } else { + if (some) { + expect(cm, '=', NULL); + } + return some; + } + } +} + +static struct expr +initializer(struct comp *cm, union type *ty, enum evalmode ev, bool globl, + enum qualifier qual, internstr sym) +{ + struct token tk; + struct span span; + struct init res = {0}; + struct initparser ip[1] = {0}; + + ip->arena = &cm->exarena; + ip->ev = ev; + if (ev == EVSTATICINI) { + if (ty->t == TYARRAY && isincomplete(*ty)) { + ip->dyn = 1; + } else if (qual & QCONST && !rodatarelocok()) { + ip->dyn = 1; + vresize(&ip->ddat, typesize(*ty)); + memset(ip->ddat.p, 0, typesize(*ty)); + } else { + ip->sec = qual & QCONST ? Srodata : Sdata; + if (!nerror) + ip->off = objnewdat(sym, ip->sec, globl, typesize(*ty), typealign(*ty)); + } + } else { + ip->init = &res; + res.tail = &res.vals; + } + + if (!match(cm, &tk, '{')) { + struct expr ex = expr(cm); + if (ex.t == ESTRLIT && chrarrayof(*ty, typechild(ex.ty))) { + inistrlit(cm, &ex, ty); + iniwrite(cm, ip, 0, 0, 0, *ty, &ex); + } else if (!initcheck(*ty, &ex)) { + error(&ex.span, "cannot initialize '%ty' with expression of type '%ty'", *ty, ex.ty); + } else { + if (ev && !eval(&ex, ev) && ev != EVFOLD) + error(&ex.span, "cannot evaluate expression statically"); + else + iniwrite(cm, ip, 0, 0, 0, *ty, &ex); + } + if (ip->dyn) + goto Dynfix; + return ex; + } + + assert(countof(res.zero) == BSSIZE(64)); + if (ev != EVSTATICINI) { + memset(res.zero, 0xFF, sizeof res.zero); + } + + span = tk.span; + ip->sub = ip->cur = ip->buf; + ip->cur->ty = *ty; + for (;;) { + peek(cm, &tk); + joinspan(&span.ex, tk.span.ex); + if (tk.t == '[' || tk.t == '.') { + designators(ip, cm); + } + if (match(cm, &tk, '}')) { + if (ip->cur == ip->buf) break; + ip->sub = ip->cur = ip->buf + ip->cur->prev; + dumpini(ip); + } else if (match(cm, &tk, '{')) { + struct span span = tk.span; + inifocus(ip, cm, &tk.span, ip->sub->idx); + if (peek(cm, &tk) == '}') { + if (!joinspan(&span.ex, tk.span.ex)) span = tk.span; + if (!objectp(ip->sub->ty)) { + error(&span, "scalar initializer cannot be empty"); + } else if (ccopt.cstd < STDC23 && ccopt.pedant) { + warn(&span, "empty initializer in %M is an extension"); + } + } else if (ip->sub->ty.t && !objectp(ip->sub->ty)) { + warn(&span, "brace initializer for scalar object '%ty'", ip->sub->ty); + } + continue; + } else { + dumpini(ip); + ininext(ip, cm); + } + match(cm, NULL, ','); + if (peek(cm, &tk) != '}' && ip->sub->ty.t == TYUNION) { + if (ip->sub == ip->cur) { + warn(&tk.span, "excess elements in union initializer"); + } else while (ip->sub != ip->cur && ip->sub->ty.t == TYUNION) { + --ip->sub; + } + } + } + if (ip->dyn) { + enum section sec; + uint off, siz, align; + uchar *p; + + if (isincomplete(*ty)) { + uint len = ip->arrlen > ip->cur->idx ? ip->arrlen : ip->cur->idx; + if (len == 0) + error(&span, "array cannot have zero length"); + *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, len); + } + Dynfix: + if (qual & QCONST && (ip->drel == NULL || rodatarelocok())) + sec = Srodata; + else + sec = Sdata; + if (!nerror) { + off = objnewdat(sym, sec, globl, siz = typesize(*ty), align = typealign(*ty)); + p = sec == Srodata ? objout.rodata.p : objout.data.p; + memcpy(p + off, ip->ddat.p, ip->ddat.n); + memset(p + off + ip->ddat.n, 0, typesize(*ty) - ip->ddat.n); + for (struct dreloc *rel = ip->drel; rel; rel = rel->link) { + objreloc(rel->sym, targ_64bit ? REL_ABS64 : REL_ABS32, sec, off + rel->off, rel->addend); + } + } + vfree(&ip->ddat); + } + dumpini(ip); + + if (ev == EVSTATICINI) { + return (struct expr){.span = span}; + } else { + uint siz; + if (isincomplete(*ty)) { + uint len = ip->arrlen > ip->cur->idx ? ip->arrlen : ip->cur->idx; + if (!len) + error(&span, "initializer creates a zero-sized array"); + *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, len); + } + + assert(countof(res.zero) == 1); + siz = typesize(*ty); + if (siz && siz <= 64) + res.zero->u &= ~0ull >> (64 - siz); + + return mkexpr(EINIT, span, *ty, .init = alloccopy(&cm->exarena, &res, sizeof res, 0)); + } +} + +/* debugging */ +void +dumpexpr(const struct expr *ex, bool prity) +{ + static const char *name[] = { + [EXXX] = "xxx", [ENUMLIT] = "numlit", [ESTRLIT] = "strlit", + [ESSYMREF] = "ssymref", [ESYM] = "sym", [EVAARG] = "vaarg", + [EINIT] = "init", [EGETF] = "getf", [ECALL] = "call", + [ECOND] = "cond", [EPLUS] = "plus", [ENEG] = "neg", + [ECOMPL] = "compl", [ELOGNOT] = "lognot", [EDEREF] = "deref", + [EADDROF] = "addrof", [ECAST] = "cast", [EPREINC] = "preinc", + [EPOSTINC] = "postinc", [EPREDEC] = "predec", [EPOSTDEC] = "postdec", + [EADD] = "add", [ESUB] = "sub", [EMUL] = "mul", + [EDIV] = "div", [EREM] = "rem", [EBAND] = "band", + [EBIOR] = "bior", [EXOR] = "xor", [ESHL] = "shl", + [ESHR] = "shr", [ELOGAND] = "logand", [ELOGIOR] = "logior", + [EEQU] = "equ", [ENEQ] = "neq", [ELTH] = "lth", + [EGTH] = "gth", [ELTE] = "lte", [EGTE] = "gte", + [ESET] = "set", [ESETADD] = "setadd", [ESETSUB] = "setsub", + [ESETMUL] = "setmul", [ESETDIV] = "setdiv", [ESETREM] = "setrem", + [ESETAND] = "setand", [ESETIOR] = "setior", [ESETXOR] = "setxor", + [ESETSHL] = "setshl", [ESETSHR] = "setshr", [ESEQ] = "seq", + }; + ioputc(&bstderr, '('); + efmt("%s ", name[ex->t]); + if (ex->ty.t && (prity || ex->t == EVAARG || ex->t == ECAST)) + efmt("<%ty> ", ex->ty); + int nsub = 0; + switch (ex->t) { + case ENUMLIT: if (!isflt(ex->ty)) efmt(isunsigned(ex->ty) ? "%lu" : "%ld", ex->u); + else efmt("%f", ex->f); + break; + case ESTRLIT: efmt("%'S", ex->s.p, ex->s.n); break; /* XXX widestr */ + case ESYM: efmt("%y", declsbuf.p[ex->decl].name); break; + case ESSYMREF: efmt("%y%+d", ex->ssym.sym, ex->ssym.off); + if (ex->ssym.func) efmt(" @func"); + if (ex->ssym.local) efmt(" @local"); + break; + case EVAARG: dumpexpr(ex->sub, prity); break; + case EGETF: dumpexpr(ex->sub, prity); efmt(" #+%u", ex->fld.off); + if (ex->fld.bitsiz) efmt("[%d:%d]", ex->fld.bitoff, ex->fld.bitsiz); + break; + case ECALL: nsub = ex->narg+1; goto Sub; + case ECOND: nsub = ex->narg+1; goto Sub; + default: + if (in_range(ex->t, EPLUS, EPOSTDEC)) nsub = 1; + else nsub = 2; + Sub: + for (int i = 0; i < nsub; ++i) { + if (i) ioputc(&bstderr, ' '); + dumpexpr(&ex->sub[i], prity); + } + break; + case EINIT: assert(!"nyi"); + } + ioputc(&bstderr, ')'); +} + +/*****************/ +/* Decls Parsing */ +/*****************/ + +static union type +buildagg(struct comp *cm, enum typetag tt, internstr name, int id) +{ + struct token tk; + union type t; + struct span flexspan; + struct namedfield fbuf[32]; + vec_of(struct namedfield) fld = VINIT(fbuf, countof(fbuf)); + struct typedata td = {tt}; + bool isunion = tt == TYUNION; + const char *tag = isunion ? "union" : "struct"; + uint bitsiz = 0, bitfbyteoff = 0, + bitoff = 0, bitftypesiz = 0; + + while (!match(cm, &tk, '}')) { + struct declstate st = { DFIELD }; + do { + struct decl decl = pdecl(&st, cm); + uint tysize; + if (st.empty) { + if (ccopt.pedant) + warn(&decl.span, "extra semicolon in aggregate"); + continue; + } + tysize = typesize(decl.ty); + if (fld.n && td.flexi) { + td.flexi = 0; + error(&flexspan, "flexible array member is not at end of struct"); + } + if (!isunion && decl.ty.t == TYARRAY && !typearrlen(decl.ty)) { + td.flexi = 1; + flexspan = decl.span; + } else if (isincomplete(decl.ty)) { + error(&decl.span, "field has incomplete type '%ty'", decl.ty); + } else if (decl.ty.t == TYFUNC) { + error(&decl.span, "field has function type '%ty'", decl.ty); + } + bitsiz = 0; + if (st.bitf) { + struct expr ex = constantexpr(cm); + const char *name = decl.name ? &decl.name->c : "<anonymous>"; + if (!isint(decl.ty)) { + error(&decl.span, "bit-field '%s' has non-integer type '%ty'", name, decl.ty); + } else if (!isint(ex.ty)) { + error(&ex.span, "integer constant expression has non-integer type '%ty'", decl.ty); + } else if (!eval(&ex, EVINTCONST)) { + error(&ex.span, "cannot evaluate integer constant expression"); + } else if (ex.i < 0) { + error(&ex.span, "bit-field '%s' has negative width '%ld'", name, ex.i); + } else if (ex.i > 8*tysize) { + error(&ex.span, "width of bit-field '%s' (%ld) exceeds width of type (%d)", + name, ex.i, 8*tysize); + } else if (ex.i == 0 && decl.name) { + error(&ex.span, "named bit-field '%s' has zero width", name); + } else { + bitsiz = ex.i; + if (bitsiz == 0) { + if (bitftypesiz) { + bitfbyteoff += bitftypesiz; + bitfbyteoff = alignup(bitfbyteoff, typealign(decl.ty)); + } + bitoff = 0; + } else if (bitftypesiz && bitftypesiz < tysize) { + /* end of previous bitfield */ + bitoff = 0; + bitfbyteoff += bitftypesiz; + } else if (!bitftypesiz) { + bitoff = 0; + bitfbyteoff = alignup(td.siz, typealign(decl.ty)); + } else if (bitoff + bitsiz > 8*bitftypesiz) { + /* no straddling boundaries */ + bitoff = 0; + bitfbyteoff += bitftypesiz; + } + if (tysize > bitftypesiz) bitftypesiz = tysize; + } + pdecl(&st, cm); + } else { + bitftypesiz = bitoff = bitsiz = 0; + } + if (decl.ty.t) { + uint align = typealign(decl.ty); + uint siz = tysize; + uint off = bitftypesiz ? bitfbyteoff : isunion ? 0 : alignup(td.siz, align); + struct namedfield f = { decl.name, { decl.ty, off, bitsiz, bitoff, .qual = decl.qual }}; + if (bitftypesiz && siz != bitftypesiz) while (f.f.bitoff + f.f.bitsiz > 8*siz) { + /* adjust bitfields narrower than container type */ + f.f.off += siz; + f.f.bitoff -= 8*siz; + } + if (!decl.name && !bitftypesiz) { + if (!isagg(decl.ty) || ttypenames[typedata[decl.ty.dat].id]) { + warn(&decl.span, "declaration does not declare anything"); + continue; + } else if (ccopt.cstd < STDC11 && ccopt.pedant) { + warn(&decl.span, "anonymous %s in %M is an extension", + decl.ty.t == TYUNION ? "union" : "struct"); + } + } + if (decl.name || !bitftypesiz) + vpush(&fld, f); + td.anyconst |= decl.qual & QCONST; + if (isagg(decl.ty)) { + td.anyconst |= typedata[decl.ty.dat].anyconst; + if (typedata[decl.ty.dat].flexi && !isunion) + error(&decl.span, "nested aggregate has flexible array member"); + } + if (isunion) + td.siz = td.siz < siz ? siz : td.siz; + else + td.siz = off + siz; + td.align = td.align < align ? align : td.align; + bitoff += bitsiz; + } + } while (st.more); + } + if (td.flexi && fld.n == 1) + error(&flexspan, "flexible array member in otherwise empty aggregate"); + if (td.flexi && ccopt.cstd < STDC99 && ccopt.pedant) + warn(&flexspan, "flexible array member in %M is an extension"); + if (fld.n == 0) { + struct namedfield dummy = { intern(""), { mktype(TYCHAR), 0 }}; + error(&tk.span, "%s cannot have zero members", tag); + vpush(&fld, dummy); + td.siz = td.align = 1; + } + td.siz = alignup(td.siz, td.align); + td.fld = fld.p; + td.nmemb = fld.n; + if (id != -1) + t = completetype(name, id, &td); + else + t = mktagtype(name, &td); + vfree(&fld); + return t; +} + +static inline void +inttyminmax(vlong *min, uvlong *max, enum typetag tt) +{ + uint bits = 8*targ_primsizes[tt]; + *min = isunsignedt(tt) ? 0 : -(1ull << (bits - 1)); + *max = isunsignedt(tt) ? ~0ull >> (64 - bits) : bits == 64 ? ~0ull>>1 : (1ll << (bits - 1)) - 1; +} + +/* the backing type of enum (without a C23 fixed backing type) is int or the + * smallest-rank type that all the enumerators fit in, or if it doesn't exist, + * then the biggest signed type. the type of enumeration constants is the type of + * its defining expression when present or the type of the previous enumerator + * or in case of overflow the smallest type that fits (previous value + 1) + * this isn't strictly conforming since pre C23 enums are pretty loosely defined, + * and this is similar to existing compiler's de-facto behaviour (though gcc + * prefers to use unsigned types when possible). should add support for -fshort-enums + */ +static union type +buildenum(struct comp *cm, internstr name, const struct span *span, int id) +{ + struct token tk; + vlong tymin, minv = 0; + uvlong tymax, maxv = 0; + struct typedata td = {TYENUM, .backing = TYINT}; + union type ty = mktype(td.backing); + struct span maxvspan; + vlong iota = 0; + bool somelonglong = 0; + + inttyminmax(&tymin, &tymax, td.backing); + while (!match(cm, &tk, '}')) { + struct decl decl = {0}; + peek(cm, &tk); + expect(cm, TKIDENT, NULL); + if (match(cm, NULL, '=') || (peek(cm, NULL) == TKNUMLIT && !expect(cm, '=', NULL))) { + struct expr ex = expr(cm); + if (eval(&ex, EVINTCONST)) { + iota = ex.i; + if (ex.ty.t != ty.t) + inttyminmax(&tymin, &tymax, ex.ty.t); + ty = ex.ty; + } else { + error(&ex.span, "enum value is not an integer constant"); + } + } else if (tk.t != TKIDENT) { + lex(cm, NULL); + continue; + } + while (issigned(ty) ? (iota > (vlong)tymax || iota < tymin) : iota > tymax) + inttyminmax(&tymin, &tymax, ++ty.t); + somelonglong |= ty.t >= TYVLONG; + if ((isunsigned(ty) || iota > 0) && iota > maxv) + maxv = iota, maxvspan = tk.span; + else if (issigned(ty) && iota < minv) + minv = iota; + + decl.name = tk.name; + decl.ty = ty; + decl.isenum = 1; + decl.value = iota++; + putdecl(cm, &decl); + if (!match(cm, &tk, ',')) { + if (expect(cm, '}', "or `,'")) + break; + else lex(cm, NULL); + } + } + + td.backing = 0; + if (minv >= 0 && maxv <= ~0u) { + td.backing = TYUINT; + } else for (int t = TYINT; t <= TYUVLONG; ++t) { + inttyminmax(&tymin, &tymax, t); + if (minv >= tymin && maxv <= tymax) { + td.backing = t; + break; + } + } + if (!td.backing) { + td.backing = !somelonglong && ccopt.cstd == STDC89 && ccopt.pedant ? TYLONG : TYVLONG; + warn(&maxvspan, "enumerators exceed range of enum's backing type '%ty'", mktype(td.backing)); + } + if (td.backing >= TYVLONG && !somelonglong && ccopt.cstd == STDC89 && ccopt.pedant) + warn(span, "enum backing type is '%ty' in %M", mktype(td.backing)); + + if (id != -1) + ty = completetype(name, id, &td); + else + ty = mktagtype(name, &td); + ty.backing = td.backing; + return ty; +} + +static union type +tagtype(struct comp *cm, enum toktag kind) +{ + struct token tk; + union type t; + struct span span; + enum typetag tt = kind == TKWenum ? TYENUM : kind == TKWstruct ? TYSTRUCT : TYUNION; + internstr tag = NULL; + + peek(cm, &tk); + if (match(cm, &tk, TKIDENT)) + tag = tk.name; + span = tk.span; + if (!match(cm, NULL, '{')) { + if (!tag) { + error(&tk.span, "expected %tt name or '{'", kind); + return mktype(0); + } + t = gettagged(cm, &span, tt, tag, /* def? */ peek(cm, NULL) == ';'); + } else { + if (tag) { + t = deftagged(cm, &span, tt, tag, mktype(0)); + if (t.t != tt || !isincomplete(t)) { + if (t.t != tt) + error(&tk.span, + "defining tagged type %'tk as %tt clashes with previous definition", + &tk, kind); + else + error(&tk.span, "redefinition of '%tt %s'", kind, tag); + note(&span, "previous definition:"); + } + } + if (tt == TYENUM) + t = buildenum(cm, tag, &span, tag ? typedata[t.dat].id : -1); + else + t = buildagg(cm, tt, tag, tag ? typedata[t.dat].id : -1); + } + + if (t.t != tt) { + error(&tk.span, "declaring tagged type %'tk as %tt clashes with previous definition", + &tk, kind); + note(&span, "previous definition:"); + } + return t; +} + +static bool +attrspec(struct comp *cm, int *attr) +{ /* __attribute__ (( attribute-list )) */ + if (!match(cm, NULL, TKW__attribute__)) return 0; + if (!expect(cm, '(', "after __attribute__") || !expect(cm, '(', "after __attribute__")) { + Bad: + fatal(NULL, NULL); + } + while (!match(cm, NULL, ')')) { + struct token tk; + lex(cm, &tk); + if (tk.t != TKIDENT && !in_range(tk.t, TKWBEGIN_, TKWEND_)) { + fatal(&tk.span, "expected attribute name"); + } + internstr name = tk.name; + int ltrim = name[0].c == '_' && name[1].c == '_', + rtrim = tk.len > 2 && name[tk.len-1].c == '_' && name[tk.len-2].c == '_'; + if (ltrim || rtrim) { /* trim surrounding '__' */ + name = intern_(&name->c + ltrim*2, tk.len - ltrim*2 - rtrim*2); + } + if (match(cm, NULL, '(')) { + while (!match(cm, NULL, ')')) { + (void)exprparse(cm, bintab['='].prec, NULL, EATTRARG); + if (!match(cm, NULL, ',')) { + if (expect(cm, ')', NULL)) break; + else goto Bad; + } + } + } + (void)name; + if (!match(cm, NULL, ',')) { + if (expect(cm, ')', NULL)) break; + else goto Bad; + } + } + if (!expect(cm, ')', NULL)) + goto Bad; + return 1; +} + +static union type +ptypeof(struct comp *cm) +{ + union type ty; + expect(cm, '(', NULL); + if (isdecltok(cm)) { /* typeof (type) */ + struct declstate st = { DCASTEXPR }; + ty = pdecl(&st, cm).ty; + } else { /* typeof (expr) */ + ty = commaexpr(cm).ty; + } + expect(cm, ')', NULL); + return ty; +} + +static void +declspec(struct declstate *st, struct comp *cm, struct span *pspan) +{ + struct token tk; + struct decl *decl; + enum arith { + KSIGNED = 1<<0, + KUNSIGNED = 1<<1, + KBOOL = 1<<2, + KCHAR = 1<<3, + KSHORT = 1<<4, + KLONG = 1<<5, + KLONGLONG = 1<<6, + KINT = 1<<7, + KFLOAT = 1<<8, + KDOUBLE = 1<<9, + KCOMPLEX = 1<<10, + } arith = 0; + struct span span = {0}; + union type ty = st->base; + bool properdecl = st->kind == DFUNCVAR || st->kind == DTOPLEVEL; + + for (bool first = 1;; first = 0) { + enum storageclass scls = 0; + peek(cm, &tk); + if (first) span = tk.span; + switch (tk.t) { + /* storage-class-specifier */ + case TKWtypedef: scls = SCTYPEDEF; break; + case TKWextern: scls = SCEXTERN; break; + case TKWstatic: scls = SCSTATIC; break; + case TKWauto: scls = SCAUTO; break; + case TKWregister: scls = SCREGISTER; break; + case TKWthread_local: + case TKW_Thread_local: scls = SCTHREADLOCAL; break; + + /* function-specifier */ + case TKWinline: + if (!properdecl) BadFnSpec: { + error(&tk.span, "function specifier %'tk is not allowed here", &tk); + break; + } + st->fninline = 1; + break; + case TKW_Noreturn: + if (!properdecl) goto BadFnSpec; + st->fnnoreturn = 1; + break; + + /* alignment-specifier */ + case TKW_Alignas: assert(!"nyi alignas"); break; + + /* type-qualifier */ + case TKWconst: st->qual |= QCONST; break; + case TKWvolatile: st->qual |= QVOLATILE; break; + case TKWrestrict: /* unimplemented */ break; + case TKW_Atomic: /* unimplemented */ break; + + /* type-specifier */ + case TKWvoid: + if (st->base.t) { + DupBase: + error(&tk.span, "more than one data type in declaration specifier"); + } else { + st->base = mktype(TYVOID); + } + break; + case TKWsigned: + arith |= KSIGNED; + break; + case TKWunsigned: + arith |= KUNSIGNED; + break; + case TKW_Bool: case TKWbool: + if (arith & KBOOL) goto DupArith; + arith |= KBOOL; + break; + case TKWchar: + if (arith & KCHAR) { + DupArith: + error(&tk.span, "duplicate %tk specifier", &tk); + } + arith |= KCHAR; + break; + case TKWshort: + arith |= KSHORT; + break; + case TKWlong: + if ((arith & (KLONG | KLONGLONG)) == KLONG) + arith = (arith &~ KLONG) | KLONGLONG; + else if ((arith & (KLONG | KLONGLONG)) == 0) + arith |= KLONG; + else + error(&tk.span, "too long"); + break; + case TKWint: + if (arith & KINT) goto DupArith; + arith |= KINT; + break; + case TKWfloat: + if (arith & KFLOAT) goto DupArith; + arith |= KFLOAT; + break; + case TKWdouble: + if (arith & KDOUBLE) goto DupArith; + arith |= KDOUBLE; + break; + case TKW_Complex: + if (arith & KCOMPLEX) goto DupArith; + arith |= KCOMPLEX; + break; + case TKWenum: + case TKWstruct: + case TKWunion: + lex(cm, &tk); + ty = tagtype(cm, tk.t); + st->tagdecl = 1; + joinspan(&span.ex, tk.span.ex); + if (st->base.t) goto DupBase; + st->base = ty; + continue; + case TKW__typeof__: case TKWtypeof: + lex(cm, &tk); + ty = ptypeof(cm); + joinspan(&span.ex, tk.span.ex); + if (st->base.t) goto DupBase; + st->base = ty; + continue; + case TKIDENT: + if (!st->base.t && !arith && (decl = finddecl(cm, tk.name)) + && decl->scls == SCTYPEDEF) { + lex(cm, &tk); + st->base = decl->ty; + continue; + } + /* fallthru */ + default: + goto End; + case TKW_BitInt: + case TKW_Decimal128: case TKW_Decimal32: + case TKW_Decimal64: case TKW_Imaginary: + error(&tk.span, "%'tk is unsupported", &tk); + arith = arith ? arith : KINT; + } + + if ((!properdecl && scls && !(st->kind == DFUNCPARAM && scls == SCREGISTER)) || (scls == SCAUTO && st->kind == DTOPLEVEL)) + error(&tk.span, "storage class specifier %'tk is not allowed here", &tk); + else + st->scls |= scls; + + joinspan(&span.ex, tk.span.ex); + lex(cm, &tk); + } +End: + if (pspan) *pspan = span; + + if (st->scls && properdecl) { + if (popcnt(st->scls) > 1) + error(&span, "invalid combination of storage class specifiers"); + } + if (st->base.t && arith) { + /* combining arith type specifiers and other types */ + Bad: + error(&span, "invalid type specifiers"); + st->base = mktype(TYINT); + } else if (!st->base.t && arith) { + enum typetag t; + if (arith == KFLOAT) + t = TYFLOAT; + else if (arith == KDOUBLE) + t = TYDOUBLE; + else if (arith == (KLONG | KDOUBLE)) + t = TYLDOUBLE; + else if (arith == KBOOL) + t = TYBOOL; + else if (arith == KCHAR) + t = TYCHAR; + else if (arith == (KSIGNED | KCHAR)) + t = TYSCHAR; + else if (arith == (KUNSIGNED | KCHAR)) + t = TYUCHAR; + else if ((arith & ~KINT & ~KSIGNED) == KSHORT) + t = TYSHORT; + else if ((arith & ~KINT) == (KUNSIGNED | KSHORT)) + t = TYUSHORT; + else if ((arith & ~KINT & ~KSIGNED) == 0) + t = TYINT; + else if ((arith & ~KINT) == KUNSIGNED) + t = TYUINT; + else if ((arith & ~KINT & ~KSIGNED) == KLONG) + t = TYLONG; + else if ((arith & ~KINT) == (KUNSIGNED | KLONG)) + t = TYULONG; + else if ((arith & ~KINT & ~KSIGNED) == KLONGLONG) + t = TYVLONG; + else if ((arith & ~KINT) == (KUNSIGNED | KLONGLONG)) + t = TYUVLONG; + else if (arith == (KCOMPLEX | KFLOAT)) + t = TYCOMPLEXF; + else if (arith == (KCOMPLEX | KDOUBLE)) + t = TYCOMPLEX; + else if (arith == (KCOMPLEX | KLONG | KDOUBLE)) + t = TYCOMPLEXL; + else + goto Bad; + st->base = mktype(t ? t : TYINT); + } else if (!st->base.t) { + if (ccopt.cstd < STDC23 && peek(cm, NULL) == TKIDENT) { + if (ccopt.cstd > STDC89) + warn(&tk.span, "type implicitly declared as int"); + st->base = mktype(TYINT); + } else { + error(&tk.span, "type specifier missing"); + } + } +} + +/* circular doubly linked list used to parse declarators */ +enum { EARRAYUNSIZED = 0xFF /* for count.t */ }; +static struct decllist { + struct decllist *prev, *next; + uchar t; /* TYPTR, TYARRAY or TYFUNC */ + union { + uchar qual; /* TYPTR */ + struct expr count; /* TYARRAY */ + struct { /* TYFUNC */ + union type *param; + internstr *pnames; + struct span *pspans; + uchar *pqual; + short npar; + bool kandr : 1, variadic : 1; + }; + }; + struct span span; +} decltmp[64], *declfreelist; +static bool usingdeclparamtmp; +static union type declparamtmp[16]; +static internstr declpnamestmp[16]; +static struct span declpspanstmp[16]; +static uchar declpqualtmp[16]; + +static void +declinsert(struct decllist *list, const struct decllist *node) +{ + struct decllist *pnode = declfreelist; + if (!pnode) fatal(NULL, "too many nested declarators"); + declfreelist = declfreelist->next; + *pnode = *node; + pnode->next = list->next; + pnode->prev = list; + list->next->prev = pnode; + list->next = pnode; +} + +static int +cvqual(struct comp *cm) +{ + struct token tk; + int q = 0; + while (match(cm, &tk, TKWconst) || match(cm, &tk, TKWvolatile) || match(cm, &tk, TKWrestrict)) + q |= tk.t == TKWconst ? QCONST : tk.t == TKWvolatile ? QVOLATILE : 0; + return q; +} + +static void +decltypes(struct comp *cm, struct decllist *list, internstr *name, struct span *span, struct span *namespan) +{ + struct token tk; + struct decllist *ptr, node; + + while (match(cm, &tk, '*')) { + node.t = TYPTR; + node.qual = cvqual(cm); + node.span = tk.span; + declinsert(list, &node); + joinspan(&span->ex, tk.span.ex); + } + ptr = list->next; + switch (peek(cm, &tk)) { + case '(': + lex(cm, &tk); + if (isdecltok(cm)) { + goto Func; + } else if (match(cm, &tk, ')')) { + /* T () is K&R func proto */ + node.span = tk.span; + node.t = TYFUNC; + node.param = NULL; + node.pqual = NULL; + node.pnames = NULL; + node.pspans = NULL; + node.variadic = 0; + node.kandr = 1; + node.npar = 0; + declinsert(ptr->prev, &node); + joinspan(&span->ex, tk.span.ex); + break; + } else { + decltypes(cm, list, name, span, namespan); + expect(cm, ')', NULL); + joinspan(&span->ex, tk.span.ex); + } + break; + case TKIDENT: + if (!name) + error(&tk.span, "unexpected identifier in type name"); + else { + *name = tk.name; + *namespan = tk.span; + } + lex(cm, &tk); + joinspan(&span->ex, tk.span.ex); + break; + default: + if (name) + *name = NULL; + } + for (;;) { + if (match(cm, &tk, '[')) { + node.span = tk.span; + int q = 0; + bool statik = 0; + if (in_range(peek(cm, &tk), TKWBEGIN_, TKWEND_)) { + q = cvqual(cm); + statik = match(cm, NULL, TKWstatic); + q |= cvqual(cm); + } + (void)q, (void)statik; /* stub */ + + if (match(cm, &tk, ']')) { + node.count.t = EARRAYUNSIZED; + } else { + node.count = arraycountexpr(cm); + peek(cm, &tk); + joinspan(&node.span.ex, tk.span.ex); + expect(cm, ']', NULL); + } + node.t = TYARRAY; + declinsert(ptr->prev, &node); + joinspan(&span->ex, node.span.ex); + } else if (match(cm, &tk, '(')) Func: { + vec_of(union type) params = {0}; + vec_of(uchar) qual = {0}; + vec_of(internstr) names = {0}; + vec_of(struct span) spans = {0}; + + if (!usingdeclparamtmp) { + usingdeclparamtmp = 1; + vinit(¶ms, declparamtmp, countof(declparamtmp)); + vinit(&qual, declpqualtmp, countof(declpqualtmp)); + vinit(&names, declpnamestmp, countof(declpnamestmp)); + vinit(&spans, declpspanstmp, countof(declpspanstmp)); + } + + node.span = tk.span; + node.kandr = 0; + node.variadic = 0; + if (ccopt.cstd < STDC23 && !isdecltok(cm)) { + node.kandr = 1; + } + + if (!match(cm, &tk, ')')) for (;;) { + if (match(cm, &tk, TKDOTS)) { + node.variadic = 1; + expect(cm, ')', NULL); + break; + } + if (node.kandr) { + if (match(cm, &tk, TKIDENT)) { + vpush(¶ms, mktype(TYINT)); + vpush(&names, tk.name); + vpush(&spans, tk.span); + } else error(&tk.span, "expected identifier"); + } else if (!isdecltok(cm) && peek(cm, &tk) != TKIDENT) { + error(&tk.span, "expected parameter declarator"); + } else { + struct declstate st = { DFUNCPARAM }; + struct decl decl; + decl = pdecl(&st, cm); + decl.ty = typedecay(decl.ty); + vpush(¶ms, decl.ty); + vpush(&names, decl.name); + vpush(&spans, decl.span); + vpush(&qual, decl.qual); + if (decl.ty.t == TYVOID) { + if (params.n > 1 || decl.qual || decl.name || peek(cm, &tk) != ')') { + error(&decl.span, "function parameter #%d has void type", + params.n, decl.ty, qual.p[params.n-1]); + } + } + } + peek(cm, &tk); + joinspan(&node.span.ex, tk.span.ex); + if (!match(cm, &tk, ',')) { + expect(cm, ')', "or `,'"); + break; + } + } else { + if (ccopt.cstd < STDC23) node.kandr = 1; + } + if (node.kandr && ccopt.cstd != STDC89 && params.n > 0) { + warn(&node.span, "K&R function prototype is deprecated"); + } else if (params.n == 1 && params.p[0].t == TYVOID && !qual.p[0] && !names.p[0]) { /* (void) */ + vfree(¶ms); + vfree(&names); + vfree(&spans); + vfree(&qual); + } + node.t = TYFUNC; + node.param = params.n ? params.p : NULL; + node.pqual = qual.n ? qual.p : NULL; + node.pnames = params.n ? names.p : NULL; + node.pspans = params.n ? spans.p : NULL; + node.npar = params.n; + declinsert(ptr->prev, &node); + joinspan(&span->ex, node.span.ex); + } else break; + } +} + +static struct decl +declarator(struct declstate *st, struct comp *cm, struct span span0) { + struct decl decl = { st->base, st->scls, .qual = st->qual, .align = st->align, .span = span0 }; + struct decllist list = { &list, &list }, *l; + static bool inidecltmp = 0; + struct span namespan ={0}; + if (!inidecltmp) { + inidecltmp = 1; + for (int i = 0; i < countof(decltmp); ++i) { + decltmp[i].next = declfreelist; + declfreelist = &decltmp[i]; + } + } + + decltypes(cm, &list, st->kind == DCASTEXPR ? NULL : &decl.name, &decl.span, &namespan); + if (!decl.name && st->kind != DCASTEXPR && st->kind != DFUNCPARAM) { + if (list.prev == &list) lex(cm, NULL); + error(&decl.span, "expected `(', `*' or identifier"); + } + for (l = list.prev; l != &list; l = l->prev) { + switch (l->t) { + case TYPTR: + decl.ty = mkptrtype(decl.ty, decl.qual); + decl.qual = l->qual; + break; + case TYARRAY: + if (isincomplete(decl.ty)) + error(&l->span, "array has incomplete element type '%ty'", decl.ty); + else if (decl.ty.t == TYFUNC) + error(&l->span, "array has element has function type '%ty'", decl.ty); + if (l->count.t == EARRAYUNSIZED) /* unsized '[]' */ + decl.ty = mkarrtype(decl.ty, decl.qual, 0); + else { + uint n = 0; + struct expr *ex = &l->count; + if (!ex->t) { /* ['*'] */ + if (l->prev != &list) error(&l->span, "[*] array declarator is not allowed here"); + } else if (!eval(ex, EVINTCONST)) { + error(&ex->span, "array length is not an integer constant"); + } else if (issigned(ex->ty) && ex->i < 0) { + error(&ex->span, "array length is negative"); + } else if (ex->u > (1ull << (8*sizeof n)) - 1) { + error(&ex->span, "array too long (%ul)", ex->u); + } else if (ex->u == 0) { + /* when struct field, silently accept zero-length as synonym of '[]' for flexible array member */ + if (l->prev != &list || st->kind != DFIELD) { + warn(&ex->span, "array cannot have zero length"); + } + } else { + n = ex->u; + } + decl.ty = mkarrtype(decl.ty, decl.qual, n); + } + break; + case TYFUNC: + if (decl.ty.t == TYFUNC) + error(&decl.span, "function cannot return function type '%ty'", decl.ty); + else if (decl.ty.t == TYARRAY) + error(&decl.span, "function cannot return array type '%ty'", decl.ty); + if (l->kandr && ccopt.cstd > STDC89 && (ccopt.cstd >= STDC23 || ccopt.pedant)) + warn(&l->span, "function declaration without a prototype is deprecated"); + decl.ty = mkfntype(decl.ty, l->npar, l->param, l->kandr, l->variadic); + if (l->param != declparamtmp) free(l->param); + if (l->prev == &list && l->npar) { /* last */ + st->pnames = alloccopy(&cm->fnarena, l->pnames, l->npar * sizeof(char *), 0); + st->pspans = alloccopy(&cm->fnarena, l->pspans, l->npar * sizeof(struct span), 0); + st->pqual = l->pqual ? alloccopy(&cm->fnarena, l->pqual, l->npar, 1) : NULL; + decl.inlin = st->fninline; + decl.noret = st->fnnoreturn; + } + if (l->pqual != declpqualtmp) free(l->pqual); + if (l->pnames != declpnamestmp) free(l->pnames); + if (l->pspans != declpspanstmp) free(l->pspans); + if (l->param == declparamtmp) usingdeclparamtmp = 0; + decl.qual = 0; + break; + } + + l->next = declfreelist; + declfreelist = l; + } + if (st->kind != DCASTEXPR && decl.name) + decl.span = namespan; + return decl; +} + +static void +pstaticassert(struct comp *cm, struct span *span) +{ + struct expr ex; + struct token tk, msg = {0}; + + /* _Static_assert '(' <expr> [ ',' <strlit> ] ')' ';' */ + expect(cm, '(', NULL); + ex = expr(cm); + peek(cm, &tk); + if (match(cm, &tk, ',')) { + peek(cm, &msg); + expect(cm, TKSTRLIT, NULL); + } + peek(cm, &tk); + expect(cm, ')', NULL); + expect(cm, ';', NULL); + + joinspan(&span->ex, tk.span.ex); + if (!msg.t && ccopt.cstd == STDC11) + warn(span, "static assert without message is a C23 extension"); + if (!eval(&ex, EVINTCONST)) { + error(&ex.span, "static assert expression is not an integer constant"); + } else if (iszero(ex)) { + if (msg.t) + error(&ex.span, "static assertion failed: %'S", msg.s, msg.len); + else + error(&ex.span, "static assertion failed"); + } +} + +static struct decl +pdecl(struct declstate *st, struct comp *cm) { + struct token tk; + struct decl decl; + bool properdecl = st->kind == DTOPLEVEL || st->kind == DFUNCVAR; + bool first = 0; + + assert(!st->funcdef); + + if (st->varini || st->bitf) { + memset(&decl, 0, sizeof decl); + goto AfterIniBitf; + } + decl.sym = NULL; + + if (st->base0) goto DeclSpec; + if (!st->base.t) { + if (properdecl && (match(cm, &tk, TKW_Static_assert) || match(cm, &tk, TKWstatic_assert))) { + pstaticassert(cm, &tk.span); + return (struct decl){0}; + } + first = 1; + if (match(cm, &tk, ';')) { + st->empty = 1; + return (struct decl){.span = tk.span}; + } + + DeclSpec: + st->base0 = 0; + while (attrspec(cm, &st->attr)) ; + declspec(st, cm, &decl.span); + } else { + peek(cm, &tk); + decl.span = tk.span; + } + if (st->scls == SCTYPEDEF) properdecl = 0; + + if (first && st->tagdecl && match(cm, &tk, ';')) { + decl = (struct decl) { st->base, st->scls, st->qual, .align = st->align, .span = decl.span }; + return decl; + } else if (st->kind == DFIELD && match(cm, &tk, ':')) { + decl = (struct decl) { st->base, st->scls, st->qual, .align = st->align, .span = decl.span }; + st->bitf = 1; + return decl; + } + decl = declarator(st, cm, decl.span); + if (decl.ty.t != TYFUNC && st->fninline) + error(&decl.span, "`inline' used on non-function declaration"); + if (decl.ty.t != TYFUNC && st->fnnoreturn) + error(&decl.span, "`_Noreturn' used on non-function declaration"); + /* trailing attributes */ + if (st->kind == DTOPLEVEL || st->kind == DFUNCVAR) { + while (attrspec(cm, &st->attr)) ; + if (match(cm, NULL, TKW__asm__) && expect(cm, '(', NULL)) { + peek(cm, &tk); + if (expect(cm, TKSTRLIT, "asm symbol name")) { + decl.sym = intern_(tk.s, tk.len); + } + expect(cm, ')', NULL); + } + while (attrspec(cm, &st->attr)) ; + } + + if (properdecl && match(cm, &tk, '=')) { + st->varini = 1; + return decl; + } else if (first && decl.ty.t == TYFUNC && match(cm, &tk, '{')) { + st->funcdef = 1; + return decl; + } else if (st->kind == DFIELD && match(cm, &tk, ':')) { + st->bitf = 1; + return decl; + } + +AfterIniBitf: + st->varini = st->bitf = 0; + st->more = 0; + if (st->kind != DCASTEXPR && st->kind != DFUNCPARAM) { + if (match(cm, &tk, ',')) + st->more = 1; + else expect(cm, st->kind == DFUNCPARAM ? ')' : ';', "or `,'"); + } + + return decl; +} + +/*****************/ +/* IR Generation */ +/*****************/ + +static inline union ref +exprvalue(struct function *fn, const struct expr *ex) +{ + return compileexpr(fn, ex, /*discard*/ 0); +} +static inline void +expreffects(struct function *fn, const struct expr *ex) +{ + compileexpr(fn, ex, /*discard*/ 1); +} + +static void +structcopy(struct function *fn, union type ty, union ref dst, union ref src) +{ + union irtype typ = mkirtype(ty); + addinstr(fn, mkarginstr(typ, dst)); + addinstr(fn, mkarginstr(typ, src)); + addinstr(fn, mkintrin(INstructcopy, 0, 2)); +} + +static union ref +structreturn(struct function *fn, const struct expr *src) +{ + return expraddr(fn, src); +} + +static union ref compilecall(struct function *fn, const struct expr *ex); + +static internstr +mkhiddensym(const char *fnname, const char *name, int id) +{ + char buf[200]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + assert(id > 0); + if (fnname) + bfmt(&wbuf, "%s.%s.%d", fnname, name, id-1); + else + bfmt(&wbuf, "%s.%d", name, id-1); + ioputc(&wbuf, 0); + assert(!wbuf.err); + return intern(buf); +} + +static void geninit(struct function *fn, union type t, union ref dst, const struct expr *src); +static union ref condexprvalue(struct function *fn, const struct expr *ex, bool discard); + +union ref +expraddr(struct function *fn, const struct expr *ex) +{ + struct decl *decl; + union ref r; + + switch (ex->t) { + case ESYM: + decl = &declsbuf.p[ex->decl]; + assert(decl != NULL); + switch (decl->scls) { + case SCAUTO: case SCREGISTER: + assert(decl->id >= 0); + return mkref(RTMP, decl->id); + case SCEXTERN: case SCNONE: case SCSTATIC: + return mksymref(decl->sym, (SFUNC & -(decl->ty.t == TYFUNC)) | (SLOCAL & -(decl->scls == SCSTATIC || decl->isdef))); + default: + assert(0); + } + break; + case ESSYMREF: + return irbinop(fn, Oadd, KPTR, + mksymref(ex->ssym.sym, (SFUNC & -ex->ssym.func) | (SLOCAL & -ex->ssym.local)), + mkintcon(KPTR, ex->ssym.off)); + case ESTRLIT: + /* XXX endian for wide strs */ + return mkdatref(NULL, ex->ty, typesize(ex->ty), typealign(ex->ty), ex->s.p, ex->s.n * typesize(typechild(ex->ty)), /*deref*/0, fn != NULL); + case EDEREF: + return exprvalue(fn, ex->sub); + case EGETF: + r = expraddr(fn, ex->sub); + assert(ex->fld.bitsiz == 0); + return irbinop(fn, Oadd, KPTR, r, mkintcon(KI32, ex->fld.off)); + case ESET: + assert(isagg(ex->ty)); + r = expraddr(fn, &ex->sub[1]); + structcopy(fn, ex->ty, expraddr(fn, &ex->sub[0]), r); + return r; + case ECALL: + assert(isagg(ex->ty)); + return compilecall(fn, ex); + case EVAARG: + assert(isagg(ex->ty)); + return builtin_va_arg_comp(fn, ex, 0); + case EINIT: + if (fn) { + /* compound literal, allocate temp */ + r = addinstr(fn, mkalloca(typesize(ex->ty), typealign(ex->ty))); + geninit(fn, ex->ty, r, ex); + return r; + } else { + /* emit static dat */ + static int id; + struct initparser ip[1] = {0}; + union type ty = ex->ty; + internstr sym = mkhiddensym(NULL, ".LC", ++id); + ip->sec = Sdata; /* TODO put in rodata if possible */ + ip->ev = EVSTATICINI; + assert(!isincomplete(ty)); + ip->off = objnewdat(sym, ip->sec, 0, typesize(ty), typealign(ty)); + if (!iniwriterec(NULL, ip, 0, (struct expr *)ex)) + error(&ex->span, "cannot not evaluate expression statically"); + return mksymref(sym, 0); + } + case ESEQ: + expreffects(fn, &ex->sub[0]); + return expraddr(fn, &ex->sub[1]); + case ECOND: + assert(isagg(ex->ty)); + return condexprvalue(fn, ex, 0); + default: + assert(!"lvalue?>"); + } + +} + +static union ref +genload(struct function *fn, union type t, union ref ref, bool volatyl) +{ + struct instr ins = {0}; + + assert(isscalar(t)); + ins.cls = type2cls[scalartypet(t)]; + assert(ins.cls); + switch (typesize(t)) { + case 1: ins.op = issigned(t) ? Oloads8 : Oloadu8; break; + case 2: ins.op = issigned(t) ? Oloads16 : Oloadu16; break; + case 4: ins.op = isflt(t) ? Oloadf32 : issigned(t) ? Oloads32 : Oloadu32; break; + case 8: ins.op = isflt(t) ? Oloadf64 : Oloadi64; break; + default: assert(0); + } + ins.l = ref; + ins.keep = volatyl; + return addinstr(fn, ins); +} + +static union ref +genstore(struct function *fn, union type t, union ref ptr, union ref val) +{ + struct instr ins = {0}; + + assert(isscalar(t)); + switch (typesize(t)) { + case 1: ins.op = Ostorei8; break; + case 2: ins.op = Ostorei16; break; + case 4: ins.op = isflt(t) ? Ostoref32 : Ostorei32; break; + case 8: ins.op = isflt(t) ? Ostoref64 : Ostorei64; break; + default: assert(0); + } + ins.l = ptr; + ins.r = val; + return addinstr(fn, ins); +} + +static void genbitfstore(struct function *fn, const union type ty, union ref addr, + const struct exgetfld *fld, union ref tmp, union ref val); + +static void +geninit(struct function *fn, union type t, union ref dst, const struct expr *src) +{ + union ref adr; + if (src->t == EINIT) { + struct init *ini = src->init; + uint siz = typesize(t); + uint align = typealign(t); + struct bitset azero[1] = {0}; + + if (BSSIZE(siz) <= countof(ini->zero)) { + for (int i = 0; i < siz; i += align) { + for (int j = 0; j < align; ++j) { + if (bstest(ini->zero, i + j)) { + bsset(azero, i); + break; + } + } + } + if (bscount(azero, countof(azero)) < 32) { + /* write individual zeros at non initialized gaps */ + for (uint i = 0; bsiter(&i, azero, countof(azero)) && i < siz; i += align) { + adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, i)); + addinstr(fn, mkinstr(Ostorei8 + ilog2(align), 0, .l = adr, .r = ZEROREF)); + } + } else { + goto Memset0; + } + } else Memset0: { + /* memset(dst,0,siz) */ + /* TODO make it into an intrinsic */ + struct instr call = { Ocall, KPTR }; + addinstr(fn, mkarginstr(cls2type(KPTR), dst)); + addinstr(fn, mkarginstr(cls2type(KI32), ZEROREF)); + addinstr(fn, mkarginstr(cls2type(type2cls[targ_sizetype]), mkintcon(type2cls[targ_sizetype], siz))); + call.l = mksymref(istr_memset, 1); + call.r = mkcallarg(cls2type(KPTR), 3, -1); + addinstr(fn, call); + } + for (struct initval *val = ini->vals; val; val = val->next) { + uint off = val->off; + struct expr *ex = &val->ex; + adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, off)); + if (ex->t == EINIT || ex->t == ESTRLIT) { + geninit(fn, ex->ty, adr, ex); + } else if (isagg(ex->ty)) { + structcopy(fn, ex->ty, adr, expraddr(fn, ex)); + } else if (!val->bitsiz) { + genstore(fn, ex->ty, adr, exprvalue(fn, ex)); + } else { + union ref q = exprvalue(fn, ex); + genbitfstore(fn, ex->ty, adr, &(struct exgetfld){0, val->bitsiz, val->bitoff}, NOREF, q); + } + } + } else if (src->t == ESTRLIT) { + union type ctyp = typechild(src->ty); + uint csiz = typesize(ctyp); + adr = dst; + for (uint i = 0; i < src->s.n; ++i) { + if (csiz == 1) + genstore(fn, ctyp, adr, mkref(RICON, src->s.p[i])); + else if (csiz == 2) + genstore(fn, ctyp, adr, mkref(RICON, src->s.w16[i])); + else + genstore(fn, ctyp, adr, mkintcon(KI32, src->s.w32[i])); + adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, (i+1)*csiz)); + } + genstore(fn, ctyp, adr, ZEROREF); /* null term */ + } else assert(0); +} + +static bool +isboollike(struct function *fn, union ref r) +{ + struct instr *ins; + if (r.t == RICON && (r.i == 0 || r.i == 1)) return 1; + if (r.t != RTMP) return 0; + ins = &instrtab[r.i]; + if (oiscmp(ins->op)) /* these instrs already have output range of [0,1] */ + return 1; + if (ins->op == Ophi) { /* check if all the phi args are boollike */ + struct block *blk; + union ref *phi = NULL; + for (blk = fn->curblk; phi == NULL; blk = blk->lprev) { + /* find blk that defines phi */ + assert(blk != fn->entry); + for (int i = 0; i < blk->phi.n; ++i){ + if (blk->phi.p[i] == r.i) { + phi = phitab.p[ins->l.i]; + break; + } + } + } + for (int i = 0; i < blk->npred; ++i) { + if (!isboollike(fn, phi[i])) { + return 0; + } + } + return 1; + } + if (in_range(ins->op, Oand, Oxor)) + return isboollike(fn, ins->l) && isboollike(fn, ins->r); + if (ins->op == Ocopy || in_range(ins->op, Oexts8, Oextu32)) + return isboollike(fn, ins->l); + if (ins->op == Oparam) + return typedata[fn->fnty.dat].param[ins->l.i].t == TYBOOL; + return 0; +} + +union ref +scalarcvt(struct function *fn, union type to, union type from, union ref ref) +{ + enum irclass kto = type2cls[scalartypet(to)], kfrom = type2cls[scalartypet(from)]; + enum op op; + if (to.bits == from.bits) return ref; + assert(kto && kfrom); + if (kto == kfrom && to.t != TYBOOL) return ref; + + if (kisflt(kto) || kisflt(kfrom)) { + if (ref.t == RICON) { + assert(kisflt(kto) && kisint(kfrom)); + return mkfltcon(kto, kto == KF32 ? (float)ref.i : (double)ref.i); + } + if (kisflt(kto) && kfrom == KI32) op = issigned(from) ? Ocvts32f : Ocvtu32f; + else if (to.t == TYBOOL && kisflt(kfrom)) return irbinop(fn, Oneq, kfrom, ref, mkfltcon(kfrom, 0.0)); + else if (kisflt(kto) && kfrom == KI64) op = issigned(from) ? Ocvts64f : Ocvtu64f; + else if (kto == KF64 && kfrom == KF32) op = Ocvtf32f64; + else if (kto == KF32 && kfrom == KF64) op = Ocvtf64f32; + else if (kfrom == KF32) op = issigned(to) ? Ocvtf32s : Ocvtf32u; + else if (kfrom == KF64) op = issigned(to) ? Ocvtf64s : Ocvtf64u; + else assert(0); + } else { + if (to.t == TYBOOL) { + if (from.t == TYBOOL) return ref; + if (isboollike(fn, ref)) + return kfrom == KI32 ? ref : scalarcvt(fn, mktype(TYINT), from, ref); + return irbinop(fn, Oneq, kfrom, ref, ZEROREF); + } + else if (kfrom == KI32 && issigned(from)) op = Oexts32; + else if (kfrom == KI32) op = Oextu32; + else if (kto == KI32 && isintcon(ref)) + return issigned(to) ? mkintcon(kto, (int)intconval(ref)) : mkintcon(kto, (uint)intconval(ref)); + else op = Ocopy; + } + return irunop(fn, op, kto, ref); +} + +static union ref +narrow(struct function *fn, enum irclass to, union type t, union ref ref, uint bitsiz) +{ + enum typetag tt = scalartypet(t); + assert(isscalar(t)); + if (targ_primsizes[tt] < cls2siz[to]) { + enum op op; + if (isfltt(tt)) { + assert(to == KF32 && tt >= TYDOUBLE); + op = Ocvtf64f32; + } else { + static const enum op ext[5][2] = { + [1] = {Oextu8, Oexts8}, [2] = {Oextu16, Oexts16}, [4] = {Oextu32, Oexts32} + }; + op = ext[targ_primsizes[tt]][issignedt(tt)]; + } + ref = irunop(fn, op, to, ref); + } + if (bitsiz) { + assert(kisint(to) && isintt(tt) && bitsiz < 8*targ_primsizes[tt]); + if (!issignedt(tt)) { + ref = irbinop(fn, Oand, to, ref, mkintcon(to, (1ull<<bitsiz)-1)); + } else { + uint sh = 8*cls2siz[to] - bitsiz; + ref = irbinop(fn, Oshl, to, ref, mkref(RICON, sh)); + ref = irbinop(fn, Osar, to, ref, mkref(RICON, sh)); + } + } + return ref; +} + +union ref +genptroff(struct function *fn, enum op op, uint siz, union ref ptr, + union type t, union ref idx) +{ + uint cls = type2cls[targ_sizetype]; + union ref off; + assert(siz); + + idx = scalarcvt(fn, mktype(targ_sizetype), t, idx); + if (siz == 1) off = idx; + else if (idx.t == RICON) { + if (op == Osub) op = Oadd, idx.i = -idx.i; + off = mkintcon(cls, idx.i * (int)siz); + } else { + off = irbinop(fn, Omul, cls, idx, mkintcon(cls, siz)); + } + assert(in_range(op, Oadd, Osub)); + return irbinop(fn, op, KPTR, ptr, off); +} + +union ref +genptrdiff(struct function *fn, uint siz, union ref a, union ref b) +{ + uint cls = type2cls[targ_ptrdifftype]; + assert(siz > 0); + a = irbinop(fn, Osub, cls, a, b); + if (siz == 1) return a; + else if (ispo2(siz)) + return irbinop(fn, Osar, cls, a, mkintcon(cls, ilog2(siz))); + else + return irbinop(fn, Odiv, cls, a, mkintcon(cls, siz)); +} + +/* used to emit the jumps in an in if (), while (), etc condition */ +static void +condjump(struct function *fn, const struct expr *ex, struct block *tr, struct block *fl) +{ + struct block *next, *next2; +Recur: + for (; ex->t == ESEQ; ex = &ex->sub[1]) + expreffects(fn, &ex->sub[0]); + if (ex->t == ELOGAND) { + next = newblk(fn); + condjump(fn, &ex->sub[0], next, fl); + useblk(fn, next); + ex = &ex->sub[1]; + goto Recur; + } else if (ex->t == ELOGIOR) { + next = newblk(fn); + condjump(fn, &ex->sub[0], tr, next); + useblk(fn, next); + ex = &ex->sub[1]; + goto Recur; + } else if (ex->t == ECOND) { + next = newblk(fn); + next2 = newblk(fn); + condjump(fn, &ex->sub[0], next, next2); + useblk(fn, next); + condjump(fn, &ex->sub[1], tr, fl); + useblk(fn, next2); + condjump(fn, &ex->sub[2], tr, fl); + } else if (ex->t == ELOGNOT) { + Negate: + /* swap tr,fl */ + next = tr; + tr = fl; + fl = next; + ex = &ex->sub[0]; + goto Recur; + } else if (ex->t == EEQU && isnullpo(&ex->sub[1])) { /* == 0 */ + goto Negate; + } else if (ex->t == ENEQ && isnullpo(&ex->sub[1])) { /* != 0 */ + ex = &ex->sub[0]; + goto Recur; + } else { + putcondbranch(fn, exprvalue(fn, ex), tr, fl); + } +} + +struct condphis { + union type typ; + vec_of(union ref) ref; +}; + +static void +condexprrec(struct function *fn, const struct expr *ex, struct condphis *phis, struct block *end) +{ +Recur: + for (; ex->t == ESEQ; ex = &ex->sub[1]) + expreffects(fn, &ex->sub[0]); + int prevpred = end->npred; + if (ex->t == ELOGAND) { + struct block *tr = newblk(fn); + condjump(fn, &ex->sub[0], tr, end); + assert(prevpred <= end->npred); + if (phis) for (int n = end->npred - prevpred; n > 0; --n) { + vpush(&phis->ref, mkref(RICON, 0)); + } + useblk(fn, tr); + ex = &ex->sub[1]; + goto Recur; + } else if (ex->t == ELOGIOR) { + struct block *fl = newblk(fn); + condjump(fn, &ex->sub[0], end, fl); + assert(prevpred <= end->npred); + if (phis) for (int n = end->npred - prevpred; n > 0; --n) { + vpush(&phis->ref, mkref(RICON, 1)); + } + useblk(fn, fl); + ex = &ex->sub[1]; + goto Recur; + } else if (ex->t == ECOND) { + struct block *tr = newblk(fn), *fl = newblk(fn); + condjump(fn, &ex->sub[0], tr, fl); + useblk(fn, tr); + condexprrec(fn, &ex->sub[1], phis, end); + useblk(fn, fl); + ex = &ex->sub[2]; + goto Recur; + } else { + if (!phis) { + expreffects(fn, ex); + } else { + union ref val = exprvalue(fn, ex); + if (isscalar(phis->typ)) + val = scalarcvt(fn, phis->typ, ex->ty, val); + else assert(ex->ty.bits == phis->typ.bits); + vpush(&phis->ref, val); + } + putbranch(fn, end); + } +} + +/* the naive way to generate something like a ? b : c ? d : e, uses multiple phis, + * this code reduces such nested conditional expressions into one phi */ +static union ref +condexprvalue(struct function *fn, const struct expr *ex, bool discard) +{ + union ref refbuf[8]; + struct condphis phis = { ex->t == ECOND ? ex->ty : mktype(TYBOOL), VINIT(refbuf, countof(refbuf)) }; + struct block *dst = newblk(fn); + condexprrec(fn, ex, discard ? NULL : &phis, dst); + useblk(fn, dst); + if (discard) return NOREF; + enum irclass k; + if (isscalar(ex->ty)) { + k = type2cls[scalartypet(ex->ty)]; + assert(k); + } else { + assert(isagg(ex->ty) || isptrcvt(ex->ty)); + k = KPTR; + } + union ref r = addphi(fn, k, phis.ref.p); + vfree(&phis.ref); + return r; +} + +static union ref +compilecall(struct function *fn, const struct expr *ex) +{ + struct instr ins = {0}; + struct expr *sub = ex->sub; + const struct typedata *td = &typedata[sub[0].ty.dat]; + struct instr insnsbuf[10]; + vec_of(struct instr) insns = VINIT(insnsbuf, countof(insnsbuf)); + + if (sub[0].t == ESYM && declsbuf.p[sub[0].decl].isbuiltin) { + return declsbuf.p[sub[0].decl].builtin->comp(fn, (struct expr *)ex, 0); + } + ins.op = Ocall; + if (isagg(ex->ty)) { + ins.cls = KPTR; + } else { + assert(isscalar(ex->ty) || ex->ty.t == TYVOID); + ins.cls = type2cls[scalartypet(ex->ty)]; + assert(ins.cls || ex->ty.t == TYVOID); + } + ins.l = exprvalue(fn, &sub[0]); + for (int i = 0; i < ex->narg; ++i) { + struct expr *arg = &sub[i+1]; + union type ty = i < td->nmemb ? td->param[i] : argpromote(arg->ty); + union ref r = scalarcvt(fn, ty, typedecay(arg->ty), exprvalue(fn, arg)); + vpush(&insns, mkarginstr(mkirtype(ty), r)); + } + for (int i = 0; i < insns.n; ++i) + addinstr(fn, insns.p[i]); + vfree(&insns); + ins.r = mkcallarg(mkirtype(ex->ty), ex->narg, td->variadic ? td->nmemb : td->kandr ? 0 : -1); + union ref r = addinstr(fn, ins); + if (sub[0].t == ESYM && declsbuf.p[sub[0].decl].noret) /* trap if noreturn func returns */ + puttrap(fn); + return r; +} + +static union ref +genbitfload(struct function *fn, union ref *tmpval, const union type ty, union ref *addr, + const struct exgetfld *fld, bool volatyl) +{ + enum irclass k = type2cls[scalartypet(ty)]; + uint off = fld->off, bitsiz = fld->bitsiz, bitoff = fld->bitoff; + union ref tmp; + uvlong mask; + + assert(k); + *addr = irbinop(fn, Oadd, KPTR, *addr, mkintcon(KI32, off)); + tmp = genload(fn, ty, *addr, volatyl); + if (tmpval) *tmpval = tmp; + if (!issigned(ty)) { + /* shift right and mask */ + tmp = irbinop(fn, Oslr, k, tmp, mkref(RICON, bitoff)); + if (bitsiz < 8*typesize(ty)) { + mask = bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1; + tmp = irbinop(fn, Oand, k, tmp, mkintcon(k, mask)); + } + } else { + /* shift left and shift right arithmetic to propagate sign bit */ + int sh = 8*cls2siz[k] - bitsiz - bitoff; + tmp = irbinop(fn, Oshl, k, tmp, mkref(RICON, sh)); + sh += bitoff; + tmp = irbinop(fn, Osar, k, tmp, mkref(RICON, sh)); + } + return tmp; +} + +static void +genbitfstore(struct function *fn, const union type ty, union ref addr, + const struct exgetfld *fld, union ref tmp, union ref val) +{ + enum irclass k = type2cls[scalartypet(ty)]; + uint off = fld->off, bitsiz = fld->bitsiz, bitoff = fld->bitoff; + uint bittypesize = 8*typesize(ty); + uvlong mask; + + assert(k); + if (!tmp.bits) { + addr = irbinop(fn, Oadd, KPTR, addr, mkintcon(KPTR, off)); + tmp = genload(fn, ty, addr, 0); + } + mask = (bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1) << bitoff; + + /* mask out bits in existing container */ + tmp = irbinop(fn, Oand, k, tmp, mkintcon(k, ~mask)); + + /* shift and mask source value */ + if (isintcon(val)) { + val = mkintcon(k, ((uvlong)intconval(val) << bitoff) & mask); + } else { + val = irbinop(fn, Oshl, k, val, mkref(RICON, bitoff)); + if (bitsiz < bittypesize) + val = irbinop(fn, Oand, k, val, mkintcon(k, mask)); + } + /* combine and write */ + if (bitsiz < bittypesize) + val = irbinop(fn, Oior, k, tmp, val); + genstore(fn, ty, addr, val); +} + +static bool +knowntruthy(bool *t, struct expr *ex) +{ + if (!eval(ex, EVFOLD)) return 0; + + switch (ex->t) { + default: assert(0 && "!scalar?"); + case ENUMLIT: + *t = isflt(ex->ty) ? ex->f != 0.0 : ex->u != 0; + break; + case ESTRLIT: case ESSYMREF: + /* string literals & symbol addresses are always truthy */ + *t = 1; + break; + } + return 1; +} + +union ref +compileexpr(struct function *fn, const struct expr *ex, bool discard) +{ + union type ty; + union ref l, r, q, adr; + uint bitsiz; + enum op op; + enum irclass cls = type2cls[scalartypet(ex->ty)]; + int swp = 0; + struct expr *sub; + + //eval((struct expr *)ex, EVFOLD); + sub = ex->sub; + + if (ex->ty.t != TYVOID && !isscalar(ex->ty)) { + /* fn & array designators evaluate to their address; + * so do aggregates for the purpose of code generation */ + if (isagg(ex->ty) && isincomplete(ex->ty)) + error(&ex->span, "use of incomplete type '%ty'", ex->ty); + return expraddr(fn, ex); + } + switch (ex->t) { + case ENUMLIT: + if (discard) return NOREF; + if (isflt(ex->ty)) + return mkfltcon(cls, ex->f); + return mkintcon(cls, ex->i); + case ESYM: + if (discard && !(ex->qual & QVOLATILE)) return NOREF; + return genload(fn, ex->ty, expraddr(fn, ex), ex->qual & QVOLATILE); + case ESSYMREF: + return expraddr(fn, ex); + case EVAARG: + return builtin_va_arg_comp(fn, ex, discard); + case EGETF: + if (discard && !(ex->qual & QVOLATILE)) return NOREF; + if (ex->fld.bitsiz) { + /* bit-field */ + r = expraddr(fn, ex->sub); + return genbitfload(fn, NULL, ex->ty, &r, &ex->fld, ex->qual & QVOLATILE); + } + return genload(fn, ex->ty, expraddr(fn, ex), ex->qual & QVOLATILE); + case ECAST: + if (ex->ty.t == TYVOID) { + expreffects(fn, sub); + return NOREF; + } + /* fallthru */ + case EPLUS: + r = compileexpr(fn, sub, discard); + if (discard) return NOREF; + r = scalarcvt(fn, ex->ty, sub->ty, r); + if (isint(ex->ty) && (typesize(ex->ty) < typesize(sub->ty) || issigned(ex->ty) != issigned(sub->ty))) + return narrow(fn, type2cls[scalartypet(ex->ty)], ex->ty, r, 0); + return r; + case ENEG: + op = Oneg; + goto Unary; + case ECOMPL: + op = Onot; + Unary: + l = compileexpr(fn, sub, discard); + if (discard) return NOREF; + l = scalarcvt(fn, ex->ty, sub->ty, l); + return irunop(fn, op, cls, l); + case ELOGNOT: + for (; sub->t == ELOGNOT; ex = sub, sub = sub->sub) + swp ^= 1; + op = Oequ + swp; + l = compileexpr(fn, sub, discard); + if (discard) return NOREF; + l = scalarcvt(fn, ex->ty, sub->ty, l); + r = mkintcon(cls, 0); + return irbinop(fn, op, cls, l, r); + case EDEREF: + discard &= (ex->qual & QVOLATILE) == 0; + r = compileexpr(fn, sub, discard); + if (discard) return NOREF; + return genload(fn, ex->ty, r, ex->qual & QVOLATILE); + case EADDROF: + return expraddr(fn, sub); + case EMUL: + op = Omul; + goto BinArith; + case EDIV: + op = isunsigned(ex->ty) ? Oudiv : Odiv; + goto BinArith; + case EREM: + op = issigned(ex->ty) ? Orem : Ourem; + goto BinArith; + case EBAND: + op = Oand; + goto BinArith; + case EXOR: + op = Oxor; + goto BinArith; + case EBIOR: + op = Oior; + goto BinArith; + case ESHL: + op = Oshl; + goto BinArith; + case ESHR: + op = issigned(ex->ty) ? Osar : Oslr; + goto BinArith; + case ESUB: + op = Osub; + goto BinArith; + case EADD: + op = Oadd; + BinArith: + l = compileexpr(fn, &sub[0], discard); + r = compileexpr(fn, &sub[1], discard); + if (discard) return NOREF; + if (op == Osub && isptrcvt(sub[0].ty) && isptrcvt(sub[1].ty)) { + /* ptr - ptr */ + return genptrdiff(fn, typesize(typechild(sub[0].ty)), l, r); + } else if ((op != Oadd && op != Osub) || cls != KPTR) { + /* num OP num */ + l = scalarcvt(fn, ex->ty, sub[0].ty, l); + r = scalarcvt(fn, ex->ty, sub[1].ty, r); + } else { + assert(isptrcvt(sub[0].ty)); + /* ptr +/- num */ + return genptroff(fn, op, typesize(typechild(sub[0].ty)), l, sub[1].ty, r); + } + return irbinop(fn, op, cls, l, r); + case EPOSTINC: + case EPOSTDEC: + op = ex->t == EPOSTINC ? Oadd : Osub; + if (ex->ty.t == TYPTR) + r = mkintcon(type2cls[targ_sizetype], typesize(typechild(ex->ty))); + else + r = isflt(ex->ty) ? mkfltcon(type2cls[ex->ty.t], 1.0) : mkref(RICON, 1); + bitsiz = 0; + if (sub[0].t == EGETF && (bitsiz = sub->fld.bitsiz)) { + union ref tmp; + adr = expraddr(fn, &sub[0].sub[0]); + l = genbitfload(fn, &tmp, sub[0].ty, &adr, &sub[0].fld, sub[0].qual & QVOLATILE); + q = irbinop(fn, op, cls, l, r); + genbitfstore(fn, sub[0].ty, adr, &sub[0].fld, tmp, q); + } else { + adr = expraddr(fn, sub); + l = genload(fn, sub->ty, adr, sub->qual & QVOLATILE); + q = irbinop(fn, op, cls, l, r); + genstore(fn, sub->ty, adr, q); + } + return discard ? NOREF : l; + case EPREINC: + case EPREDEC: + op = ex->t == EPREINC ? Oadd : Osub; + if (ex->ty.t == TYPTR) + r = mkintcon(type2cls[targ_sizetype], typesize(typechild(ex->ty))); + else + r = isflt(ex->ty) ? mkfltcon(type2cls[ex->ty.t], 1.0) : mkref(RICON, 1); + if (sub[0].t == EGETF && (bitsiz = sub->fld.bitsiz)) { + ty = ex->ty; + goto CompoundBitf; + } + adr = expraddr(fn, sub); + l = genload(fn, sub->ty, adr, sub->qual & QVOLATILE); + q = irbinop(fn, op, cls, l, r); + genstore(fn, sub->ty, adr, q); + if (discard) return NOREF; + return narrow(fn, cls, ex->ty, q, 0); + case EEQU: + op = Oequ; + goto Cmp; + case ENEQ: + op = Oneq; + goto Cmp; + case ELTH: + op = Olth; + goto Cmp; + case ELTE: + op = Olte; + goto Cmp; + case EGTH: + op = Ogth; + goto Cmp; + case EGTE: + op = Ogte; + Cmp: + ty = cvtarith(sub[0].ty, sub[1].ty); + if (!ty.t) ty.t = TYPTR; + if (isunsigned(ty) && in_range(op, Olth, Ogte)) + op += Oulth - Olth; + l = compileexpr(fn, &sub[0], discard); + r = compileexpr(fn, &sub[1], discard); + if (discard) return NOREF; + l = scalarcvt(fn, ty, sub[0].ty, l); + r = scalarcvt(fn, ty, sub[1].ty, r); + cls = type2cls[ty.t]; + return irbinop(fn, op, cls, l, r); + case ESET: + assert(isscalar(ex->ty)); + q = scalarcvt(fn, sub[0].ty, sub[1].ty, exprvalue(fn, &sub[1])); + if (sub[0].t == EGETF && (bitsiz = sub[0].fld.bitsiz)) { + /* bit-field */ + adr = expraddr(fn, &sub[0].sub[0]); + genbitfstore(fn, ex->ty, adr, &sub[0].fld, NOREF, q); + } else { + bitsiz = 0; + adr = expraddr(fn, &sub[0]); + genstore(fn, ex->ty, adr, q); + } + if (discard) return NOREF; + return bitsiz ? narrow(fn, cls, sub[0].ty, q, bitsiz) : q; + case ESETMUL: + op = Omul; + goto Compound; + case ESETDIV: + op = isunsigned(ex->ty) ? Oudiv : Odiv; + goto Compound; + case ESETREM: + op = issigned(ex->ty) ? Orem : Ourem; + goto Compound; + case ESETAND: + op = Oand; + goto Compound; + case ESETXOR: + op = Oxor; + goto Compound; + case ESETIOR: + op = Oior; + goto Compound; + case ESETSHL: + op = Oshl; + goto Compound; + case ESETSHR: + op = issigned(ex->ty) ? Osar : Oslr; + goto Compound; + case ESETSUB: + op = Osub; + goto Compound; + case ESETADD: + op = Oadd; + Compound: + ty = in_range(ex->t, ESETSHL, ESETSHR) ? mktype(intpromote(ex->ty.t)) + : cvtarith(sub[0].ty, sub[1].ty); + r = exprvalue(fn, &sub[1]); + if (sub[0].t == EGETF && (bitsiz = sub[0].fld.bitsiz)) { + /* bit-field */ + union ref tmp; + CompoundBitf: + adr = expraddr(fn, &sub[0].sub[0]); + l = genbitfload(fn, &tmp, sub[0].ty, &adr, &sub[0].fld, sub[0].qual & QVOLATILE); + q = irbinop(fn, op, cls, l, r); + genbitfstore(fn, sub[0].ty, adr, &sub[0].fld, tmp, q); + } else { + bitsiz = 0; + adr = expraddr(fn, &sub[0]); + l = genload(fn, ex->ty, adr, ex->qual & QVOLATILE); + if ((op != Oadd && op != Osub) || cls != KPTR) { + l = scalarcvt(fn, ty, sub[0].ty, l); + r = scalarcvt(fn, ty, sub[1].ty, r); + q = irbinop(fn, op, type2cls[ty.t], l, r); + q = scalarcvt(fn, ex->ty, ty, q); + } else { + q = genptroff(fn, op, typesize(typechild(ex->ty)), l, sub[1].ty, r); + } + genstore(fn, ex->ty, adr, q); + } + if (discard) return NOREF; + return bitsiz ? narrow(fn, cls, ex->ty, q, bitsiz) : q; + case ECALL: + r = compilecall(fn, ex); + if (isint(ex->ty)) + return narrow(fn, cls, ex->ty, r, 0); + return r; + case ECOND: + for (bool c; knowntruthy(&c, &ex->sub[0]);) { + r = compileexpr(fn, &ex->sub[2-c], discard); + if (discard) return NOREF; + return scalarcvt(fn, ex->ty, ex->sub[2-c].ty, r); + } + + if (ex->ty.t == TYVOID || discard) { + struct block *tr, *fl, *end; + condjump(fn, &sub[0], tr = newblk(fn), fl = newblk(fn)); + useblk(fn, tr); + expreffects(fn, &sub[1]); + end = newblk(fn); + if (fn->curblk) + putbranch(fn, end); + useblk(fn, fl); + expreffects(fn, &sub[2]); + if (fn->curblk) + putbranch(fn, end); + useblk(fn, end); + return NOREF; + } + return condexprvalue(fn, ex, discard); + case ELOGAND: + case ELOGIOR: + for (bool c; knowntruthy(&c, &ex->sub[0]);) { + c ^= ex->t == ELOGIOR; + r = compileexpr(fn, &ex->sub[c], discard); + if (discard) return NOREF; + return scalarcvt(fn, mktype(TYBOOL), ex->sub[c].ty, r); + } + return condexprvalue(fn, ex, discard); + case ESEQ: + expreffects(fn, &sub[0]); + return compileexpr(fn, &sub[1], discard); + default: assert(!"nyi expr"); + } +} + +/************************************/ +/* Statements parsing & compilation */ +/************************************/ + +static void +stmtterm(struct comp *cm) +{ + expect(cm, ';', "to terminate previous statement"); +} + +static void block(struct comp *cm, struct function *fn); +static bool stmt(struct comp *cm, struct function *fn); +static void localdecl(struct comp *cm, struct function *fn, bool forinit); + +struct label { + struct label *link; + internstr name; + struct block *blk; + struct span usespan; + /* if usespan.ex.len == 0, this label is resolved and blk is the block that + * the label starts, otherwise the label is unresolved and blk is the head + * of a linked list of relocations, the next list entry is in blk->s1, etc, + * terminated by NULL */ +}; + +static struct label * +findlabel(struct comp *cm, internstr name) +{ + for (struct label *l = cm->labels; l; l = l->link) + if (l->name == name) return l; + return NULL; +} + +static void +deflabel(struct comp *cm, struct function *fn, const struct span *span, internstr name) +{ + struct label *label = findlabel(cm, name); + if (label && label->usespan.ex.len == 0) { + error(span, "redefinition of label '%s'", name); + } else if (label) { + struct block *new = NULL; + if (!nerror) { + new = newblk(fn); + if (fn->curblk) putbranch(fn, new); + } + /* fix up relocations */ + for (struct block *list = label->blk, *next; list; list = next) { + next = list->s1; + if (!nerror) { + useblk(fn, list); + putbranch(fn, new); + } + } + label->usespan = (struct span){0}; + label->blk = new; + if (!nerror) useblk(fn, new); + } else { + struct label l = { cm->labels, name }; + if (!nerror) { + struct block *new = newblk(fn); + if (fn->curblk) putbranch(fn, new); + useblk(fn, new); + } + l.blk = fn->curblk; + cm->labels = alloccopy(fn->arena, &l, sizeof l, 0); + } +} + +static bool +loopbody(struct comp *cm, struct function *fn, struct block *brk, struct block *cont) +{ + struct block *save[2]; + bool terminates = 0; + + save[0] = cm->breakto, save[1] = cm->loopcont; + cm->breakto = brk, cm->loopcont = cont; + ++cm->loopdepth; + + terminates = stmt(cm, fn); + + --cm->loopdepth; + cm->breakto = save[0], cm->loopcont = save[1]; + + return terminates; +} + +#define EMITS if (doemit && !nerror) + +struct swcase { + vlong val; + struct block *blk; + struct span span; +}; +struct switchstmt { + struct block *bdefault; + union type condtype; + vec_of(struct swcase) cases; +}; + +static int +cmpswcase(const void *aa, const void *bb) +{ + const struct swcase *a = aa, *b = bb; + vlong v1 = a->val, v2 = b->val; + if (v1 != v2) return v1 < v2 ? -1 : 1; + return (a > b) - (a < b); /* preserve original order */ +} + +static void +swsortcases(struct swcase *cs, uint n) +{ + void qsort(void *, size_t n, size_t size, int (*)(const void *, const void *)); + qsort(cs, n, sizeof *cs, cmpswcase); +} + +static bool +genswitch(struct comp *cm, struct function *fn, const struct expr *ex) +{ + union ref sel; + bool doemit = fn->curblk; + struct block *begin = NULL, *end = NULL, *breaksave = cm->breakto; + struct switchstmt *stsave = cm->switchstmt, st = {.condtype = ex->ty}; + enum irclass k = type2cls[scalartypet(ex->ty)]; + struct swcase casebuf[8]; + vinit(&st.cases, casebuf, countof(casebuf)); + + assert(k); + end = newblk(fn); + EMITS { + sel = exprvalue(fn, ex); + assert(isint(ex->ty)); + } + cm->switchstmt = &st; + cm->breakto = end; + begin = fn->curblk; + fn->curblk = NULL; + ++cm->switchdepth; + stmt(cm, fn); + --cm->switchdepth; + doemit = fn->curblk; + cm->switchstmt = stsave; + cm->breakto = breaksave; + + EMITS putbranch(fn, end); + useblk(fn, begin); + swsortcases(st.cases.p, st.cases.n); + doemit = 1; + if (!st.bdefault) st.bdefault = end; + /* TODO: optimize instead of generating the equivalent of if == .. else if .. chain + * XX 1. sort by case values (also for easy duplicates checking) + * 2. contiguous ranges (case a..b: -> x >= && x <= b) + * 3. binary search + * 4. jump tables? (harder, backend refactoring) + */ + vlong prev; + for (int i = 0; i < st.cases.n; ++i) { + const struct swcase *c = &st.cases.p[i]; + if (i > 0) { + assert(c->val >= prev); + if (c->val == prev) { + error(&c->span, "duplicate case value"); + note(&c[-1].span, "previously defined here"); + } + } + EMITS { + struct block *next = i < st.cases.n - 1 ? newblk(fn) : st.bdefault; + putcondbranch(fn, irbinop(fn, Oequ, k, sel, mkintcon(k, c->val)), c->blk, next); + if (next != st.bdefault) useblk(fn, next); + } + prev = c->val; + } + vfree(&st.cases); + if (fn->curblk != end) { + if (fn->curblk) EMITS putbranch(fn, end); + if (end->npred > 0) { + useblk(fn, end); + } else { + fn->curblk = NULL; + freeblk(fn, end); + } + } + + return fn->curblk == NULL; +} + +static bool /* return 1 if stmt is terminating (ends with a jump) */ +stmt(struct comp *cm, struct function *fn) +{ + struct block *tr, *fl, *end, *begin; + union { + struct arena a; + char mem[sizeof(struct arena) + sizeof(struct expr)*4]; + } atmp = { .a.cap = sizeof(struct expr)*4 }; + struct arena *atmpp; + struct expr ex; + struct env e; + union ref r; + struct token tk; + bool terminates = 0; + bool doemit = fn->curblk; + + while (match(cm, &tk, TKIDENT) || match(cm, &tk, TKWcase) || match(cm, &tk, TKWdefault)) { + if (tk.t == TKWcase) { + /* case <expr> ':' */ + if (!cm->switchstmt) error(&tk.span, "'case' outside of switch statement"); + ex = constantexpr(cm); + if (!eval(&ex, EVINTCONST)) + error(&ex.span, "not an integer constant expression"); + else if (cm->switchstmt && ex.ty.bits != cm->switchstmt->condtype.bits) { + struct expr tmp = ex; + ex = mkexpr(ECAST, ex.span, cm->switchstmt->condtype, .sub = &tmp); + bool ok = eval(&ex, EVINTCONST); + assert(ok && "cast const int?"); + if (ex.i != tmp.i) + warn(&ex.span, "overflow converting case value to switch condition type"); + } + expect(cm, ':', NULL); + if (!fn->curblk || (fn->curblk->phi.n > 0 || fn->curblk->ins.n > 0)) { + begin = newblk(fn); + EMITS putbranch(fn, begin); + useblk(fn, begin); + } + if (cm->switchstmt) + vpush(&cm->switchstmt->cases, ((struct swcase) {ex.i, fn->curblk, ex.span})); + } else if (tk.t == TKWdefault) { + /* default ':' */ + if (!cm->switchstmt) error(&tk.span, "'default' outside of switch statement"); + expect(cm, ':', NULL); + if (!fn->curblk || (fn->curblk->phi.n > 0 || fn->curblk->ins.n > 0)) { + begin = newblk(fn); + EMITS putbranch(fn, begin); + useblk(fn, begin); + } + if (cm->switchstmt) { + if (cm->switchstmt->bdefault) error(&tk.span, "multiple 'default' labels in one switch"); + cm->switchstmt->bdefault = fn->curblk; + } + } else if (tk.t == TKIDENT && match(cm, NULL, ':')) { + /* <label> ':' */ + deflabel(cm, fn, &tk.span, tk.name); + } else { + assert(tk.t == TKIDENT); + /* kludge for no backtracking and no lookahead */ + ex = exprparse(cm, 1, &tk, EFROMSTMT); + stmtterm(cm); + EMITS expreffects(fn, &ex); + return fn->curblk == NULL; + } + doemit = 1; + } + + switch (peek(cm, NULL)) { + case '{': + lex(cm, NULL); + envdown(cm, &e); + block(cm, fn); + envup(cm); + break; + case ';': + lex(cm, NULL); + break; + case TKWif: + lex(cm, NULL); + expect(cm, '(', NULL); + ex = commaexpr(cm); + expect(cm, ')', NULL); + if (!isscalar(ex.ty)) + error(&ex.span, "'if' condition is not a scalar '%ty'", ex.ty); + tr = fl = end = NULL; + EMITS { + tr = newblk(fn); + fl = newblk(fn); + condjump(fn, &ex, tr, fl); + useblk(fn, tr); + } + terminates = stmt(cm, fn); + if (!match(cm, NULL, TKWelse)) { + end = fl; + EMITS if (!terminates) putbranch(fn, end); + terminates = 0; + } else { + EMITS { + end = newblk(fn); + if (!terminates) putbranch(fn, end); + useblk(fn, fl); + } + terminates &= stmt(cm, fn); + EMITS { + if (fn->curblk) putbranch(fn, end); + } + } + EMITS if (!terminates) useblk(fn, end); + break; + case TKWelse: + lex(cm, &tk); + error(&tk.span, "'else' without matching 'if'"); + break; + case TKWwhile: /* while ( <cond> ) <body> */ + lex(cm, NULL); + expect(cm, '(', NULL); + ex = commaexpr(cm); + expect(cm, ')', NULL); + if (!isscalar(ex.ty)) + error(&ex.span, "'while' condition is not a scalar '%ty'", ex.ty); + tr = begin = end = NULL; + /* @begin: + * <cond> + * b <cond>, @tr, @end + * @tr: + * <body> + * b @begin + * @end: + * <- + */ + doemit = 1; + EMITS { + begin = newblk(fn); + if (fn->curblk) + putbranch(fn, begin); + useblk(fn, begin); + condjump(fn, &ex, tr = newblk(fn), end = newblk(fn)); + useblk(fn, tr); + } + terminates = loopbody(cm, fn, end, begin); + EMITS { + if (fn->curblk) putbranch(fn, begin); + useblk(fn, end); + } + break; + case TKWdo: /* do <body> while ( <cond> ) ; */ + lex(cm, NULL); + begin = tr = end = NULL; + /* @begin: + * <body> + * b @tr + * @tr: <- necessary for continue stmt + * <cond> + * b <cond>, @begin, @end + * @end: + * <- + */ + doemit = 1; + EMITS { + begin = newblk(fn); + if (fn->curblk) + putbranch(fn, begin); + useblk(fn, begin); + tr = newblk(fn); + end = newblk(fn); + } + terminates = loopbody(cm, fn, end, tr); + expect(cm, TKWwhile, NULL); + expect(cm, '(', NULL); + ex = commaexpr(cm); + expect(cm, ')', NULL); + if (!isscalar(ex.ty)) + error(&ex.span, "'while' condition is not a scalar '%ty'", ex.ty); + stmtterm(cm); + EMITS { + if (!terminates) putbranch(fn, tr); + useblk(fn, tr); + condjump(fn, &ex, begin, end); + useblk(fn, end); + } + break; + case TKWfor: /* for ( <init>? ; <cond>? ; <iter>? ) <body> */ + lex(cm, NULL); + begin = tr = end = fl = NULL; + expect(cm, '(', NULL); + /* -> + * <init> + * b @begin + * @begin: + * <cond> + * b <cond>, @tr, @fl + * @tr: + * <body> + * b @end + * @end: <- necessary for continue stmt + * <iter> + * b @begin + * @fl: + * <- + * + * if cond omitted, tr = begin + * if iter omitted, end = begin + */ + envdown(cm, &e); + if (!match(cm, NULL, ';')) { /* init */ + if (isdecltok(cm)) { + localdecl(cm, fn, 1); + } else { + ex = commaexpr(cm); + EMITS expreffects(fn, &ex); + expect(cm, ';', NULL); + } + } + doemit = 1; + EMITS { + end = tr = begin = newblk(fn); + if (fn->curblk) + putbranch(fn, begin); + useblk(fn, begin); + fl = newblk(fn); + } + if (!match(cm, NULL, ';')) { /* cond */ + ex = commaexpr(cm); + expect(cm, ';', NULL); + if (!isscalar(ex.ty)) + error(&ex.span, "'for' condition is not a scalar type ('%ty')", ex.ty); + EMITS { + tr = newblk(fn); + condjump(fn, &ex, tr, fl); + useblk(fn, tr); + } + } + atmpp = NULL; + if (!match(cm, NULL, ')')) { /* iter */ + /* since exarena is free'd at the end of each stmt, create a new temporary + * arena to parse this expression because loop body statements would free it + * otherwise */ + struct arena *tmp = cm->exarena; + cm->exarena = &atmp.a; + ex = commaexpr(cm); + atmpp = cm->exarena; + cm->exarena = tmp; + + end = newblk(fn); + expect(cm, ')', NULL); + } + + terminates = loopbody(cm, fn, fl, end); + + EMITS { + if (end != begin) { /* have iter */ + if (!terminates) putbranch(fn, end); + useblk(fn, end); + expreffects(fn, &ex); + putbranch(fn, begin); + } else if (!terminates) putbranch(fn, begin); + if (fl->npred > 0) { + useblk(fn, fl); + } else { + freeblk(fn, fl); + terminates = 1; + } + } + if (atmpp && atmpp != cm->exarena) freearena(&atmpp); + envup(cm); + break; + case TKWswitch: + lex(cm, NULL); + expect(cm, '(', NULL); + ex = commaexpr(cm); + expect(cm, ')', NULL); + if (!isint(ex.ty)) + error(&ex.span, "'switch' value is not an integer: '%ty'", ex.ty); + terminates = genswitch(cm, fn, &ex); + break; + case TKWbreak: + lex(cm, &tk); + if (!cm->loopdepth && !cm->switchdepth) + error(&tk.span, "'break' outside of loop or switch statement"); + EMITS putbranch(fn, cm->breakto); + stmtterm(cm); + break; + case TKWcontinue: + lex(cm, &tk); + if (!cm->loopdepth) + error(&tk.span, "'continue' outside of loop"); + EMITS putbranch(fn, cm->loopcont); + stmtterm(cm); + break; + case TKWgoto: + lex(cm, &tk); + peek(cm, &tk); + if (expect(cm, TKIDENT, NULL)) { + struct label *label = findlabel(cm, tk.name); + if (!label) { + /* create reloc list */ + struct label l = { cm->labels, tk.name, fn->curblk, tk.span }; + assert(l.usespan.ex.len); + cm->labels = alloccopy(fn->arena, &l, sizeof l, 0); + fn->curblk = NULL; + } else if (label && label->usespan.ex.len != 0) { + /* append to relocs list */ + struct block *next = label->blk; + label->blk = fn->curblk; + EMITS { + fn->curblk->s1 = next; + fn->curblk = NULL; + } + } else { + EMITS { + assert(label->blk); + putbranch(fn, label->blk); + } + } + } + stmtterm(cm); + break; + case TKWreturn: + lex(cm, &tk); + if (isexprtok(cm)) { + ex = commaexpr(cm); + if (fn->retty.t == TYVOID) { + if (ex.ty.t != TYVOID) error(&ex.span, "void function should not return a value"); + else if (ccopt.pedant) warn(&ex.span, "returning void expression is an extension"); + } else if (!assigncheck(fn->retty, &ex)) { + error(&ex.span, + "cannot return '%ty' value from function with return type '%ty'", + ex.ty, fn->retty); + } + EMITS { + if (isscalar(fn->retty)) + r = scalarcvt(fn, fn->retty, ex.ty, exprvalue(fn, &ex)); + else if (fn->retty.t == TYVOID) + r = (expreffects(fn, &ex), NOREF); + else + r = structreturn(fn, &ex); + putreturn(fn, r, NOREF); + } + } else { + if (fn->retty.t != TYVOID) + error(&tk.span, "non-void function should return a value"); + EMITS putreturn(fn, NOREF, NOREF); + } + stmtterm(cm); + break; + default: + ex = exprparse(cm, 1, NULL, EFROMSTMT); + stmtterm(cm); + EMITS expreffects(fn, &ex); + break; + } + freearena(&cm->exarena); + lexerfreetemps(cm->lx); + return fn->curblk == NULL; +} + +/* parse and compile a function-local declaration */ +static void +localdecl(struct comp *cm, struct function *fn, bool forini) +{ + struct expr ini; + struct token tk; + const bool doemit = fn->curblk; + struct declstate st = { DFUNCVAR }; + + if (!forini && match(cm, &tk, TKIDENT)) { + if (match(cm, NULL, ':')) { + /* <label> ':' */ + deflabel(cm, fn, &tk.span, tk.name); + stmt(cm, fn); + return; + } + /* finddecl() -> non null because localdecl() is called when isdecltok() */ + st.base = finddecl(cm, tk.name)->ty; + st.base0 = 1; + } + do { + struct decl decl = pdecl(&st, cm); + if (decl.name) { + static int staticid; + bool put = 0; + bool dynarr = 0; + + switch (decl.scls) { + case SCSTATIC: + if (forini) + error(&decl.span, "static declaration in 'for' loop initializer"); + if (!decl.sym) + decl.sym = mkhiddensym(&fn->name->c, &decl.name->c, ++staticid); + goto Initz; + case SCNONE: + if (decl.ty.t == TYFUNC) { + decl.scls = SCEXTERN; + if (!decl.sym) decl.sym = decl.name; + break; + } + decl.scls = SCAUTO; + /* fallthru */ + case SCAUTO: + case SCREGISTER: + if (decl.ty.t == TYFUNC) { + error(&decl.span, "declaring variable '%s' with function type '%ty'", decl.name, decl.ty); + goto Err; + } else if (isincomplete(decl.ty) && !(dynarr = (decl.ty.t == TYARRAY && st.varini))) { + error(&decl.span, "declaring variable '%s' with incomplete type '%ty'", decl.name, decl.ty); + goto Err; + } + decl.id = -1; + if (!nerror) { + struct instr alloc = mkalloca(typesize(decl.ty), typealign(decl.ty)); + if (fn->curblk) decl.id = addinstr(fn, alloc).i; + else decl.id = insertinstr(fn->entry, fn->entry->ins.n, alloc).i; + } + Initz: + if (st.varini) { + int d = putdecl(cm, &decl); + union type ty = decl.ty; + bool statik = decl.scls & (SCSTATIC | SCEXTERN); + ini = initializer(cm, &ty, statik ? EVSTATICINI : EVFOLD, + /* globl? */ decl.scls == SCEXTERN, decl.qual, statik ? decl.sym : NULL); + declsbuf.p[d].ty = ty; + put = 1; + pdecl(&st, cm); + if (!statik) { + /* fix alloca for actual size, for implicitly sized arrays */ + assert(!isincomplete(ty)); + EMITS instrtab[decl.id] = mkalloca(typesize(ty), typealign(ty)); + + if (!initcheck(ty, &ini)) { + struct span span = decl.span; + joinspan(&span.ex, ini.span.ex); + error(&span, "cannot initialize '%ty' variable with '%ty'", + ty, ini.ty); + } + EMITS { + if (ini.t == EINIT || (ty.t == TYARRAY && ini.t == ESTRLIT)) + geninit(fn, ty, mkref(RTMP, decl.id), &ini); + else if (isagg(ty)) + structcopy(fn, ty, mkref(RTMP, decl.id), exprvalue(fn, &ini)); + else { + genstore(fn, ty, mkref(RTMP, decl.id), + scalarcvt(fn, ty, ini.ty, exprvalue(fn, &ini))); + } + } + } else if (decl.scls == SCEXTERN) { + struct span span = decl.span; + joinspan(&span.ex, ini.span.ex); + error(&span, "block local 'extern' variable cannot have an initializer"); + } + } else if (decl.scls == SCSTATIC) { + /* zero-initialized static */ + if (decl.ty.t == TYARRAY && isincomplete(decl.ty)) + error(&decl.span, "definition of variable with array type needs size or initializer"); + else if (isincomplete(decl.ty)) + error(&decl.span, "definition of static variable with incomplete type"); + else + objnewdat(decl.sym, Sbss, 0, typesize(decl.ty), typealign(decl.ty)); + } + break; + case SCTYPEDEF: + if (forini) + error(&decl.span, "typedef in 'for' loop initializer"); + break; + case SCEXTERN: + if (!decl.sym) decl.sym = decl.name; + if (forini) + error(&decl.span, "extern declaration in 'for' loop initializer"); + if (st.varini) goto Initz; + break; + default: assert(0); + } + if (st.funcdef) { + struct span span = decl.span; + joinspan(&span.ex, (peek(cm, &tk), tk.span.ex)); + error(&span, "function definition not allowed here"); + int bal = 1; + do switch (lex(cm, NULL)) { + case TKEOF: break; + case '{': ++bal; break; + case '}': --bal; break; + } while (bal); + } + Err: + if (!put) putdecl(cm, &decl); + } else if (forini) { + error(&decl.span, "non-variable declaration in 'for' loop initializer"); + } + } while (st.more); +} + +static void +block(struct comp *cm, struct function *fn) +{ + struct token tk; + + while (!match(cm, &tk, '}')) { + if (isdecltok(cm)) + localdecl(cm, fn, 0); + else + stmt(cm, fn); + } + cm->fnblkspan = tk.span; +} + +static void +function(struct comp *cm, struct function *fn, internstr *pnames, const struct span *pspans, uchar *pquals) +{ + const struct typedata *td = &typedata[fn->fnty.dat]; + const bool doemit = fn->curblk; + struct env e; + struct token tk; + envdown(cm, &e); + + /* emit Oparam instructions */ + EMITS { + for (int i = 0; i < td->nmemb; ++i) { + union irtype pty = mkirtype(td->param[i]); + union ref r = addinstr(fn, mkinstr(Oparam, pty.isagg ? KPTR : pty.cls, + mkref(RICON, i), mktyperef(pty))); + assert(r.t == RTMP && r.i == i); + } + } + /* add parameters to symbol table and create prologue (arguments) block */ + for (int i = 0; i < td->nmemb; ++i) { + if (pnames[i]) { + struct decl arg = { .ty = td->param[i], .qual = pquals ? pquals[i] : 0, + .name = pnames[i], .scls = SCAUTO, .span = pspans[i] }; + EMITS { + if (isscalar(arg.ty)) { + arg.id = addinstr(fn, mkalloca(typesize(arg.ty), typealign(arg.ty))).i; + genstore(fn, arg.ty, mkref(RTMP, arg.id), mkref(RTMP, i)); + } else { + arg.id = addinstr(fn, mkinstr(Ocopy, KPTR, mkref(RTMP, i))).i; + } + } + putdecl(cm, &arg); + } else if (ccopt.cstd < STDC23) { + warn(&pspans[i], "missing name of parameter #%d", i+1); + } + } + + /* put __func__, though its data is generated lazily the first time it is encountered */ + putdecl(cm, &(struct decl) { + .ty = mkarrtype(mktype(TYCHAR), QCONST, strlen(&fn->name->c) + 1), .qual = QCONST, + .name = istr__func__, .scls = SCSTATIC, .span = (peek(cm, &tk), tk.span), + .isbuiltin = 1, .sym = fn->name, + }); + + /* end prologue */ + EMITS { + struct block *blk; + putbranch(fn, blk = newblk(fn)); + useblk(fn, blk); + } + cm->labels = NULL; + block(cm, fn); + envup(cm); + for (struct label *l = cm->labels; l; l = l->link) { + if (l->usespan.ex.len) { + error(&l->usespan, "label '%s' used but never defined", l->name); + } + } + if (fn->curblk) { + if (fn->retty.t == TYINT && fn->name == istr_main) { + /* implicit return 0 for main function (ISO C standard behavior) */ + putreturn(fn, ZEROREF, NOREF); + } else { + if (fn->retty.t != TYVOID && !nerror) { + /* it may not actually be reachable after constant-folding + * peephole optimizations (from code like assert(0 && "x")) */ + if (blkreachable(fn, fn->curblk)) { + warn(&cm->fnblkspan, "non-void function '%s' may not return a value", fn->name); + } + } + putreturn(fn, NOREF, NOREF); + } + } +} + +/* top-level declaration */ +static void +tldecl(struct comp *cm) +{ + struct declstate st = { DTOPLEVEL }; + do { + bool noscls = 0; + int nerr = nerror; + struct decl decl = pdecl(&st, cm); + + if (nerror != nerr && st.varini) { + (void)expr(cm); + pdecl(&st, cm); + continue; + } + if (st.empty) break; + if (!decl.scls) { + noscls = 1; + decl.scls = SCEXTERN; + } + if (!decl.sym) decl.sym = decl.name; + decl.isdef = st.varini; + if (st.funcdef) { + const struct typedata *td = &typedata[decl.ty.dat]; + if (td->ret.t != TYVOID && isincomplete(td->ret)) + error(&decl.span, "function definition with incomplete return type '%ty'", td->ret); + for (int i = 0; i < td->nmemb; ++i) { + if (td->param[i].t != TYVOID && isincomplete(td->param[i])) + error(&st.pspans[i], "parameter has incomplete type '%ty'", td->param[i]); + } + decl.isdef = 1; + int idecl = putdecl(cm, &decl); + struct decl *d = &declsbuf.p[idecl]; + if (d->inlin && decl.scls != SCSTATIC) fatal(&d->span, "non-static inline is unimplemented"); + struct function fn = { &cm->fnarena, .name = d->sym, .globl = d->scls != SCSTATIC, .fnty = decl.ty, .retty = td->ret, .inlin = d->inlin }; + irinit(&fn); + function(cm, &fn, st.pnames, st.pspans, st.pqual); + if (!nerror && ccopt.dbg.p) + irdump(&fn); + irfini(&fn); + } else if (decl.name) { + int idecl = putdecl(cm, &decl); + struct decl *d = &declsbuf.p[idecl]; + if (st.varini) { + if (isagg(decl.ty) && isincomplete(decl.ty)) + error(&decl.span, "initialization of variable with incomplete type '%ty'", decl.ty); + struct expr ini = initializer(cm, &decl.ty, EVSTATICINI, d->scls != SCSTATIC, d->qual, d->sym); + d = &declsbuf.p[idecl]; + d->ty = decl.ty; + if (d->scls == SCEXTERN && !noscls) { + struct span span = decl.span; + joinspan(&span.ex, ini.span.ex); + warn(&span, "'extern' variable has initializer"); + } + pdecl(&st, cm); + } else if (d->ty.t != TYFUNC && d->scls != SCTYPEDEF && (d->scls != SCEXTERN || noscls)) { + /* tentative definitions */ + if (!objhassym(d->sym, NULL)) { + uint size = typesize(d->ty); + if (isincomplete(d->ty)) { + if (d->ty.t == TYARRAY) { + warn(&d->span, "tentative array definition assumed to have one element"); + size = typesize(typechild(d->ty)); + assert(size != 0); + } else if (isagg(d->ty)) { + warn(&d->span, "tentative definition with incomplete type '%ty'", d->ty); + assert(size == 0); + } else assert(0); + } + if (size) objnewdat(d->sym, Sbss, d->scls == SCEXTERN, size, typealign(d->ty)); + } + } + if (ccopt.dbg.p) bfmt(ccopt.dbgout, "var %s : %tq\n", d->name, d->ty, d->qual); + } else { + if (ccopt.dbg.p && decl.ty.t) bfmt(ccopt.dbgout, "type %ty\n", decl.ty); + } + freearena(&cm->fnarena); + freearena(&cm->exarena); + lexerfreetemps(cm->lx); + } while (st.more); +} + +union type cvalistty; +void +docomp(struct comp *cm) +{ + static struct env toplevel; + struct token tk[1]; + + istr__func__ = intern("__func__"); + istr_main = intern("main"); + istr_memset = intern("memset"); + if (!cm->env) { + vinit(&declsbuf, NULL, 1<<10); + pmap_init(&tldeclmap, 1<<8); + cm->env = &toplevel; + } + if (!cvalistty.t) { + struct typedata td = { + .t = TYSTRUCT, .siz = targ_valistsize, .align = targ_primalign[TYPTR], .nmemb = 1, + .fld = &(struct namedfield){intern("-"), {mkarrtype(mktype(TYPTR), 0, 3)}} + }; + cvalistty = mkarrtype(mktagtype(intern("__builtin_va_list"), &td), 0, 1); + } + peek(cm, tk); + envadddecl(cm->env, &(struct decl) { cvalistty, SCTYPEDEF, .span = tk->span, .name = intern("__builtin_va_list") }); + putbuiltins(cm->env); + + while (peek(cm, tk) != TKEOF) { + if (tk->t == ';') { + lex(cm, tk); + } else if (!isdecltok(cm) && tk->t != TKIDENT) { + error(&tk->span, "expected declaration"); + do lex(cm, tk); while (tk->t != TKEOF && !isdecltok(cm)); + } else { + tldecl(cm); + } + } +} + +static void +initcm(struct comp *cm, const char *file) +{ + enum { N = 1<<12 }; + static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem[2]; + const char *err; + switch (initlexer(cm->lx, &err, file)) { + default: assert(0); + case LXERR: + fatal(NULL, "Cannot open %'s: %s", file, err); + case LXOK: + cm->fnarena = (void *)amem[0].m; + cm->fnarena->cap = N; + cm->exarena = (void *)amem[1].m; + cm->exarena->cap = N; + } +} + +void +ccomp(const char *file) +{ + struct comp cm = {&(struct lexer){0}}; + initcm(&cm, file); + docomp(&cm); +} + +void +cpp(struct wbuf *out, const char *file) +{ + struct comp cm = {&(struct lexer){0}}; + initcm(&cm, file); + lexerdump(cm.lx, out); +} + +/* vim:set ts=3 sw=3 expandtab: */ @@ -0,0 +1,139 @@ +#include "../common.h" +#include "../type.h" + +/*************/ +/* EXPR TREE */ +/*************/ + +enum exprkind { + EXXX, ENUMLIT, ESTRLIT, ESYM, ESSYMREF, EVAARG, EINIT, EGETF, ECALL, ECOND, + /* unary */ + EPLUS, ENEG, ECOMPL, ELOGNOT, EDEREF, EADDROF, ECAST, + EPREINC, EPOSTINC, EPREDEC, EPOSTDEC, + /* binary */ + EADD, ESUB, EMUL, EDIV, EREM, EBAND, EBIOR, EXOR, ESHL, ESHR, + ELOGAND, ELOGIOR, + EEQU, ENEQ, ELTH, EGTH, ELTE, EGTE, + ESET, ESETADD, ESETSUB, ESETMUL, ESETDIV, ESETREM, ESETAND, ESETIOR, ESETXOR, ESETSHL, ESETSHR, + ESEQ, +}; +#define isunop(t) in_range(t, EPLUS, EPOSTDEC) +#define isbinop(t) in_range(t, EADD, ESEQ) +#define isassign(t) in_range(t, ESET, ESETSHR) +#define assigntobinop(t) ((t) - ESETADD + EADD) + +struct expr { + uchar t; + uchar qual; + ushort narg; /* ECALL */ + union type ty; + struct span span; + union { + struct { + struct expr *sub; /* child(ren) */ + struct exgetfld { + ushort off; + uchar bitsiz, bitoff; + } fld; /* EGETF */ + }; + uvlong u; vlong i; double f; /* ENUMLIT */ + struct { + union { + uchar *p; + ushort *w16; + uint *w32; + }; + uint n; + } s; /* ESTRLIT */ + int decl; /* ESYM, index into declsbuf */ + internstr implicitsym; /* ESYM (undeclared) */ + struct { + internstr sym; + int off; + bool func : 1, local : 1; + } ssym; /* ESSYMREF (static symbol addr + off) */ + struct init *init; /* EINIT */ + }; +}; + +struct init { + struct bitset zero[BSSIZE(64)]; /* bytes to zero out up to 64 */ + struct initval { + struct initval *next; + uint off; + uchar bitoff, bitsiz; + struct expr ex; + } *vals, **tail; +}; + +/** C compiler state **/ +struct comp { + struct lexer *lx; + struct env *env; + struct arena *fnarena, *exarena; + struct span fnblkspan; + uint loopdepth, switchdepth; + struct block *breakto, *loopcont; + struct switchstmt *switchstmt; + struct label *labels; +}; + +enum storageclass { + SCNONE, + SCTYPEDEF = 1<<0, + SCEXTERN = 1<<1, + SCSTATIC = 1<<2, + SCTHREADLOCAL = 1<<3, + SCAUTO = 1<<4, + SCREGISTER = 1<<5, +}; + +struct decl { + union type ty; + uchar scls; + uchar qual : 2, + noret : 1, + inlin : 1, + isenum : 1, + isdef : 1, + isbuiltin : 1; + struct span span; + internstr name; + union { + internstr sym; + struct { ushort align; int id; }; + vlong value; + const struct builtin *builtin; + }; +}; + +extern struct envdecls {vec_of(struct decl);} declsbuf; +extern union type cvalistty; +struct function; +int envadddecl(struct env *env, const struct decl *d); +bool assigncheck(union type t, const struct expr *src); +union ref expraddr(struct function *, const struct expr *); +union ref scalarcvt(struct function *, union type to, union type from, union ref); +union ref compileexpr(struct function *, const struct expr *, bool discard); +void dumpexpr(const struct expr *, bool printtypes); + +/** builtin.c **/ +struct builtin { + bool (*sema)(struct comp *, struct expr *); + union ref (*comp)(struct function *, struct expr *, bool discard); +}; +void putbuiltins(struct env *); +union ref builtin_va_arg_comp(struct function *, const struct expr *, bool discard); + +/** eval.c **/ +enum evalmode { + EVNONE, + EVINTCONST, + EVARITH, + EVSTATICINI, + EVFOLD, +}; + +bool eval(struct expr *, enum evalmode); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_builtin.c b/src/c_builtin.c new file mode 100644 index 0000000..5c59857 --- /dev/null +++ b/src/c_builtin.c @@ -0,0 +1,177 @@ +#include "c.h" +#include "../ir/ir.h" + +static bool +callcheck(const struct span *span, int nparam, const union type *param, int narg, struct expr *args) +{ + bool ok = 1; + for (int i = 0, n = narg < nparam ? narg : nparam; i < n; ++i) { + if (!assigncheck(typedecay(param[i]), &args[i])) { + ok = 0; + error(&args[i].span, "arg #%d of type '%ty' is incompatible with '%ty'", + i, args[i].ty, param[i]); + } + } + + if (narg > nparam) { + error(&args[nparam].span, "too many args to builtin function taking %d params", nparam); + ok = 0; + } else if (narg < nparam) { + error(span, "not enough args to builtin function taking %d param%s", nparam, + nparam != 1 ? "s" : ""); + ok = 0; + } + return ok; +} + +#define DEF_FNLIKE_SEMA(name, retty, ...) \ + static bool \ + name##_sema(struct comp *cm, struct expr *ex) { \ + union type par[] = { {{0}}, __VA_ARGS__ }; \ + ex->ty = retty; \ + return callcheck(&ex->span, countof(par)-1, par+1, ex->narg, ex->sub+1); \ + } + +/* __builtin_va_start */ +static bool +va_start_sema(struct comp *cm, struct expr *ex) +{ + ex->ty = mktype(TYVOID); + return callcheck(&ex->span, 1, &cvalistty, ex->narg, ex->sub+1); +} +static union ref +va_start_comp(struct function *fn, struct expr *ex, bool discard) +{ + assert(ex->t == ECALL && ex->narg == 1); + assert(typedecay(ex->sub[1].ty).bits == typedecay(cvalistty).bits); + if (!typedata[fn->fnty.dat].variadic) + error(&ex->span, "va_start used in non-variadic function"); + addinstr(fn, mkinstr(Ovastart, 0, compileexpr(fn, &ex->sub[1], 0))); + return NOREF; +} + +/* __builtin_va_end */ +static bool +va_end_sema(struct comp *cm, struct expr *ex) +{ + ex->ty = mktype(TYVOID); + return callcheck(&ex->span, 1, &cvalistty, ex->narg, ex->sub+1); +} + +static union ref +va_end_comp(struct function *fn, struct expr *ex, bool discard) +{ + return NOREF; +} + +/* __builtin_va_copy */ +DEF_FNLIKE_SEMA(va_copy, mktype(TYVOID), cvalistty, cvalistty) +static union ref +va_copy_comp(struct function *fn, struct expr *ex, bool discard) +{ + union irtype typ = mkirtype(cvalistty.t == TYARRAY ? typechild(cvalistty) : cvalistty); + for (int i = 1; i <= 2; ++i) + assert(typedecay(ex->sub[i].ty).bits == typedecay(cvalistty).bits); + union ref dst = compileexpr(fn, &ex->sub[1], 0), src = compileexpr(fn, &ex->sub[2], 0); + addinstr(fn, mkarginstr(typ, dst)); + addinstr(fn, mkarginstr(typ, src)); + addinstr(fn, mkintrin(INstructcopy, 0, 2)); + return NOREF; +} + +/* __builtin_trap */ +DEF_FNLIKE_SEMA(trap, mktype(TYVOID), ) +static union ref +trap_comp(struct function *fn, struct expr *ex, bool discard) +{ + puttrap(fn); + useblk(fn, newblk(fn)); /* unreachable block, but simplifies expr codegen */ + return NOREF; +} + +static inline union ref +cvtintref(struct function *fn, enum irclass dst, union ref src) +{ + if (src.t == RTMP) { + if (insrescls(instrtab[src.i]) != dst) + return addinstr(fn, mkinstr(Ocopy, dst, src)); + return src; + } else if (isintcon(src)) { + vlong x = intconval(src); + return mkintcon(dst, cls2siz[dst] == 4 ? (int)x : x); + } + assert(!"int ref?"); +} + +/* __builtin_bswap16 */ +DEF_FNLIKE_SEMA(bswap16, mktype(TYUSHORT), mktype(TYUSHORT)) +static union ref +bswap16_comp(struct function *fn, struct expr *ex, bool discard) +{ + assert(isint(ex->ty)); + return irunop(fn, Obswap16, KI32, scalarcvt(fn, ex->ty, ex->sub[1].ty, + compileexpr(fn, &ex->sub[1], 0))); +} +/* __builtin_bswap32 */ +DEF_FNLIKE_SEMA(bswap32, mktype(TYUINT), mktype(TYUINT)) +static union ref +bswap32_comp(struct function *fn, struct expr *ex, bool discard) +{ + assert(isint(ex->ty)); + return irunop(fn, Obswap32, KI32, scalarcvt(fn, ex->ty, ex->sub[1].ty, + compileexpr(fn, &ex->sub[1], 0))); +} +/* __builtin_bswap64 */ +DEF_FNLIKE_SEMA(bswap64, mktype(TYUVLONG), mktype(TYUVLONG)) +static union ref +bswap64_comp(struct function *fn, struct expr *ex, bool discard) +{ + assert(isint(ex->ty)); + return irunop(fn, Obswap64, KI64, scalarcvt(fn, ex->ty, ex->sub[1].ty, + compileexpr(fn, &ex->sub[1], 0))); +} + +#define LIST_BUILTINS(_) \ + _(va_start) _(va_copy) _(va_end) \ + _(trap) _(bswap16) _(bswap32) _(bswap64) + +static const struct { + const char *name; + struct builtin b; +} tab[] = { +#define FNS(x) { "__builtin_" #x, { x##_sema, x##_comp } }, + LIST_BUILTINS(FNS) +#undef FNS +}; + +void +putbuiltins(struct env *env) +{ + for (int i = 0; i < countof(tab); ++i) { + envadddecl(env, &(struct decl) { + .name = intern(tab[i].name), + .isbuiltin = 1, + .builtin = &tab[i].b, + }); + } +} + +/* this is separate because it's a keyword */ +union ref +builtin_va_arg_comp(struct function *fn, const struct expr *ex, bool discard) +{ + assert(ex->t == EVAARG && ex->ty.t); + enum irclass k = isagg(ex->ty) ? KPTR : type2cls[scalartypet(ex->ty)]; + return addinstr(fn, mkinstr(Ovaarg, k, compileexpr(fn, ex->sub, 0), mktyperef(mkirtype(ex->ty)))); +} + +bool +hasbuiltin(const char *name, uint len) +{ + for (int i = 0; i < countof(tab); ++i) + if (!strncmp(name, tab[i].name, len)) + return 1; + return 0; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_embedfilesdir.c b/src/c_embedfilesdir.c new file mode 100644 index 0000000..d1c8dd4 --- /dev/null +++ b/src/c_embedfilesdir.c @@ -0,0 +1,106 @@ +#include <stddef.h> + +struct embedfile { + const char *name; + const char *s; + size_t len; +}; + +#define S(s) s"\0\0\0\0\0", (sizeof s) - 1 + +struct embedfile embedfilesdir[] = { +{"stddef.h", S("\ +#pragma once\n\ +typedef __typeof__((char*)0 - (char*)0) ptrdiff_t;\n\ +typedef __typeof__(sizeof 0) size_t;\n\ +typedef __typeof__(L'a') wchar_t;\n\ +#undef NULL\n\ +#define NULL ((void *)0)\n\ +#undef offsetof\n\ +#define offsetof(type, memb) ((size_t)((char *)&((type *)0)->memb - (char *)0))\n\ +")}, + +{"stdarg.h", S("\ +#pragma once\n\ +typedef __builtin_va_list va_list;\n\ +#ifndef __GNUC_VA_LIST\n\ +#define __GNUC_VA_LIST\n\ +typedef __builtin_va_list __gnuc_va_list;\n\ +#endif\n\ +#define va_start(ap,n) __builtin_va_start(ap)\n\ +#define va_arg(ap,type) __builtin_va_arg(ap, type)\n\ +#define va_copy(dst,src) __builtin_va_copy(dst, src)\n\ +#define va_end(ap) __builtin_va_end(ap)\n\ +")}, + +{"stdbool.h", S("\ +#pragma once\n\ +#if __STDC_VERSION__ < 202311L /* in C23 they are keywords */\n\ +#define bool _Bool \n\ +#define true 1\n\ +#define false 0\n\ +#endif\n\ +#define __bool_true_false_are_defined 1\n\ +")}, + +{"float.h", S("\ +#pragma once\n\ +#define FLT_ROUNDS (-1)\n\ +#define FLT_EVAL_METHOD (-1)\n\ +#define FLT_HAS_SUBNORM (-1)\n\ +#define DBL_HAS_SUBNORM (-1)\n\ +#define LDBL_HAS_SUBNORM (-1)\n\ +#define FLT_RADIX 2\n\ +#define FLT_MANT_DIG 24\n\ +#define DBL_MANT_DIG 53\n\ +#define LDBL_MANT_DIG 53\n\ +#define FLT_DECIMAL_DIG 9\n\ +#define DBL_DECIMAL_DIG 17\n\ +#define LDBL_DECIMAL_DIG 17\n\ +#define DECIMAL_DIG 17\n\ +#define FLT_DIG 6\n\ +#define DBL_DIG 15\n\ +#define LDBL_DIG 15\n\ +#define FLT_MIN_EXP (-125)\n\ +#define DBL_MIN_EXP (-1021)\n\ +#define LDBL_MIN_EXP (-1021)\n\ +#define FLT_MIN_10_EXP (-37)\n\ +#define DBL_MIN_10_EXP (-307)\n\ +#define LDBL_MIN_10_EXP (-307)\n\ +#define FLT_MAX_EXP 128\n\ +#define DBL_MAX_EXP 1024\n\ +#define LDBL_MAX_EXP 1024\n\ +#define FLT_MAX_10_EXP 38\n\ +#define DBL_MAX_10_EXP 308\n\ +#define LDBL_MAX_10_EXP 308\n\ +#define FLT_MAX 3.40282e+38\n\ +#define DBL_MAX 1.79769e+308\n\ +#define LDBL_MAX 1.79769e+308\n\ +#define FLT_EPSILON 1.19209e-07\n\ +#define DBL_EPSILON 2.22045e-16\n\ +#define LDBL_EPSILON 2.22045e-16\n\ +#define FLT_MIN 1.17549e-38\n\ +#define DBL_MIN 2.22507e-308\n\ +#define LDBL_MIN 2.22507e-308\n\ +#define FLT_TRUE_MIN 1.4013e-45\n\ +#define DBL_TRUE_MIN 4.94066e-324\n\ +#define LDBL_TRUE_MIN 4.94066e-324\n\ +")}, + +{"stdnoreturn.h", S("\ +#define noreturn _Noreturn\n\ +")}, + +{"stdalign.h", S("\ +#if __STDC_VERSION__ < 202311L\n\ +#define alignas _Alignas\n\ +#define alignof _Alignof\n\ +#define __alignas_is_defined 1\n\ +#define __alignof_is_defined 1\n\ +#endif\n\ +")}, + + {NULL} +}; + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_eval.c b/src/c_eval.c new file mode 100644 index 0000000..3dfbbfb --- /dev/null +++ b/src/c_eval.c @@ -0,0 +1,437 @@ +#include "c.h" +#include "../ir/ir.h" +#include <limits.h> + +static int +targ2hosttype(enum typetag t) +{ + if (t == TYPTR) t = targ_64bit ? TYUVLONG : TYUINT; + if (isintt(t)) { + int siz = targ_primsizes[t]; + int sgn = issignedt(t); +#define U(Ty,Tag) if (!sgn & (siz == sizeof(unsigned Ty))) return Tag; +#define S(Ty,Tag) if ( sgn & (siz == sizeof(signed Ty))) return Tag; + U(char, TYUCHAR) + S(char, TYSCHAR) + U(short, TYUSHORT) + S(short, TYSHORT) + U(int, TYUINT) + S(int, TYINT) + U(long long, TYUVLONG) + S(long long, TYVLONG) +#undef U +#undef S + } else if (t == TYLDOUBLE) return TYDOUBLE; + else if (isfltt(t) || iscomplext(t)) return t; + return 0; +} + +static bool +numcast(union type ty, struct expr *dst, const struct expr *src) +{ + enum typetag td = targ2hosttype(scalartypet(ty)), + ts = targ2hosttype(scalartypet(src->ty)); + vlong isrc; + struct expr tmp; + if (src == dst) tmp = *src, src = &tmp; + + assert(src->t == ENUMLIT); +#define TT(d,s) (td == d && ts == s) +#define TF(d) (td == d && isfltt(ts)) + if (!ts || !td) return 0; + else if (TT(TYFLOAT, TYFLOAT)) dst->f = (float) src->f; + else if (TT(TYFLOAT, TYDOUBLE)) dst->f = (float) src->f; + else if (TT(TYDOUBLE, TYFLOAT)) dst->f = src->f; + else if (TT(TYDOUBLE, TYDOUBLE)) dst->f = src->f; + else if (TT(TYFLOAT, TYUVLONG)) dst->f = (float) src->u; + else if (TT(TYDOUBLE, TYUVLONG)) dst->f = (double) src->u; + else if (td == TYFLOAT) dst->f = (float) src->i; + else if (td == TYDOUBLE) dst->f = (double) src->i; + else if (TF(TYUVLONG)) dst->u = src->f; + else if (TF(TYBOOL)) dst->i = (bool) src->f; + else if (isfltt(ts)) { isrc = src->f; goto Narrow; } + else { + isrc = src->i; + Narrow: + switch (td) { +#define I(Ty, Tag) case Tag: dst->i = (Ty) isrc; break; + I(bool, TYBOOL) + I(signed char, TYSCHAR) + I(unsigned char, TYUCHAR) + I(signed short, TYSHORT) + I(unsigned short, TYUSHORT) + I(signed int, TYINT) + I(unsigned int, TYUINT) + I(signed long long, TYVLONG) + I(unsigned long long, TYUVLONG) +#undef I + case TYFLOAT: dst->f = (float) src->f; break; + case TYDOUBLE: dst->f = src->f; break; + default: assert(0 && "bad cast?"); + } + } +#undef TT +#undef TF + + dst->t = ENUMLIT; + dst->ty = ty; + return 1; +} + +static struct expr * +lit2ssym(struct expr *ex) +{ + ex->ssym.sym = xcon2sym(expraddr(NULL, ex).i); + ex->ssym.local = 1; + ex->ssym.func = ex->ssym.off = 0; + ex->t = ESSYMREF; + return ex; +} + +static struct expr +staticaddrof(struct expr *ex, enum evalmode mode) +{ + struct expr ret = { .ty = mkptrtype(ex->ty, ex->qual), .span = ex->span }; + if (ex->t == ESYM && ex->ty.t < NTYPETAG) { + const struct decl *decl = &declsbuf.p[ex->decl]; + if (decl->sym && (decl->scls & (SCAUTO|SCREGISTER)) == 0) { + ret.t = ESSYMREF; + ret.ssym.sym = decl->sym; + ret.ssym.off = 0; + ret.ssym.func = decl->ty.t == TYFUNC; + ret.ssym.local = (decl->scls == SCSTATIC || decl->isdef); + } + } else if (ex->t == EDEREF && eval(ex->sub, EVSTATICINI)) { + ret = *ex->sub; + } else if (ex->t == EGETF && (ret = staticaddrof(ex->sub, mode)).t) { + if (ret.t == ESSYMREF) { + ex->t = ESSYMREF; + vlong off = (vlong) ret.ssym.off + ex->fld.off; + if ((int) off != off) return ret.t = 0, ret; + ret.ssym.off = off; + } else if (ret.t == ENUMLIT) { + ret.u += ex->fld.off; + } else assert(0); + } else if (ex->t == ESTRLIT || (mode == EVSTATICINI && ex->t == EINIT)) { + ret = *lit2ssym(ex); + } else if (ex->t == ENUMLIT || ex->t == ESSYMREF) ret = *ex; + return ret; +} + +static bool +isstaticlval(const struct expr *ex, enum evalmode mode) +{ + return ex->t == ESTRLIT || ex->t == ESSYMREF || (mode == EVSTATICINI && ex->t == EINIT); +} + +static bool +truthy(const struct expr *ex) +{ + switch (ex->t) { + default: assert(0 && "!scalar?"); + case ENUMLIT: + return isflt(ex->ty) ? ex->f != 0.0 : ex->u != 0; + case ESTRLIT: case ESSYMREF: + return 1; + } +} + +static bool +unop(struct expr *ex, enum evalmode mode) +{ + struct expr *sub = ex->sub; + + if (mode >= EVSTATICINI && ex->t == EDEREF) { + uvlong off; + uchar *p; + uint len; + uint csiz; + /* HACK */ + if (sub->t == ESTRLIT) { + /* *"s" */ + off = 0; + p = sub->s.p, len = sub->s.n; + csiz = typesize(typechild(sub->ty)); + StrRead: + if (off > len) return 0; + ex->t = ENUMLIT; + ex->ty = mktype(TYINT); + if (off == len) ex->u = 0; + else if (csiz == 1) ex->u = p[off]; + else if (csiz == 2) ex->u = ((short *)p)[off]; + else if (csiz == 4) ex->u = ((int *)p)[off]; + return 1; + } else if (sub->t == EADD && sub->sub[0].t == ESTRLIT && eval(&sub->sub[1], EVINTCONST)) { + /* "s"[0] */ + assert(sub->sub[1].t == ENUMLIT && isint(sub->sub[1].ty)); + off = sub->sub[1].u; + p = sub->sub[0].s.p, len = sub->sub[0].s.n; + csiz = typesize(typechild(sub->sub[0].ty)); + goto StrRead; + } else if (sub->t == EADD && sub->sub[1].t == ESTRLIT && eval(&sub->sub[0], EVINTCONST)) { + /* 0["s"] */ + assert(sub->sub[0].t == ENUMLIT && isint(sub->sub[0].ty)); + off = sub->sub[0].u; + p = sub->sub[1].s.p, len = sub->sub[1].s.n; + csiz = typesize(typechild(sub->sub[1].ty)); + goto StrRead; + } else return 0; + } else if (ex->t == EADDROF) { + assert(ex->ty.t == TYPTR); + struct expr ex2 = staticaddrof(ex->sub, mode); + if (!ex2.t) return 0; + ex2.span = ex->span; + ex2.ty = ex->ty; + *ex = ex2; + return 1; + } else if (ex->t == EGETF && !ex->fld.bitsiz) { + /* <lvalue>.memb -> is an address lvalue if 'memb' is of array type */ + if (ex->ty.t == TYARRAY) { + struct expr ex2; + if ((ex2 = staticaddrof(ex->sub, mode)).t) { + if (ex2.t == ENUMLIT) { + ex->t = ENUMLIT; + ex->u = ex2.u + ex->fld.off; + return 1; + } else { + assert(ex2.t == ESSYMREF); + ex->t = ESSYMREF; + vlong off = (vlong) sub->ssym.off + ex->fld.off; + if ((int) off != off) return 0; + ex->ssym = ex2.ssym; + ex->ssym.off = off; + return mode >= EVSTATICINI; + } + } + } + return 0; + } + if (!eval(sub, mode)) return 0; + switch (ex->t) { + case ECAST: + if (ex->ty.t == TYPTR && sub->t == ENUMLIT) { + ex->t = ENUMLIT; + ex->u = sub->u; + return 1; + } else if (isstaticlval(sub, mode) + && (ex->ty.t == TYPTR || (isint(ex->ty) && typesize(ex->ty) == targ_primsizes[TYPTR]))) { + /* ptr -> int */ + if (sub->t == ESTRLIT || sub->t == EINIT) + lit2ssym(sub); + sub->span = ex->span, sub->ty = ex->ty; + *ex = *sub; + return 1; + } + break; + case EPLUS: + break; + case ENEG: + if (sub->t != ENUMLIT) return 0; + if (isint(sub->ty)) sub->u = -sub->u; + else assert(isflt(sub->ty)), sub->f = -sub->f; + break; + case ECOMPL: + if (sub->t != ENUMLIT) return 0; + assert(isint(sub->ty)); + sub->u = ~sub->u; + break; + case ELOGNOT: + sub->u = !truthy(sub); + sub->t = ENUMLIT; + break; + default: + return 0; + } + if (sub->t != ENUMLIT || !numcast(ex->ty, ex, sub)) return 0; + return 1; +} + +static bool +binop(struct expr *ex, enum evalmode mode) +{ + struct expr *a = &ex->sub[0], *b = &ex->sub[1]; + if (!eval(a, mode)) return 0; + union type opty; + if (in_range(ex->t, EADD, ESHR)) + opty = ex->ty; + else /* compare, logical, set (result type != operation type) */ + opty = cvtarith(a->ty, b->ty); + if (isstaticlval(a, mode)) { + if ((ex->t == EADD || ex->t == ESUB) && eval(b, mode) && b->t == ENUMLIT) { + assert(isint(b->ty)); + assert(in_range(a->ty.t, TYPTR, TYARRAY)); + if (a->t == ESTRLIT) { + lit2ssym(a); + } else assert(a->t == ESSYMREF); + vlong addend = b->i * typesize(typechild(a->ty)), + off = a->ssym.off + (uvlong) (ex->t == EADD ? addend : -addend); + ex->t = ESSYMREF; + if ((int) off != off) return 0; + ex->ssym = a->ssym; + ex->ssym.off = off; + return 1; + } + return 0; + } + + enum { U = 0, S = 1<<8 , F = 1<<9 }; + int op = issigned(opty)<<8 | isflt(opty)<<9 | ex->t; + bool c; + if (ex->t != ELOGAND && ex->t != ELOGIOR) { + if (!numcast(opty, a, a)) return 0; + if (!eval(b, mode) || !numcast(opty, b, b)) return 0; + } + switch (op) { + case EADD|U: + case EADD|S: a->u += opty.t == TYPTR ? b->u * typesize(typechild(opty)) : b->u; break; + case EADD|F: a->f += b->f; break; + + case ESUB|U: + case ESUB|S: if (opty.t == TYPTR) { + assert(a->t == ENUMLIT && b->t == ENUMLIT); + assert(!isincomplete(typechild(ex->ty))); + a->u = (a->u - b->u) / typesize(typechild(ex->ty)); + } else a->u -= b->u; + break; + case ESUB|F: a->f -= b->f; break; + + case EMUL|U: + case EMUL|S: a->u *= b->u; break; + case EMUL|F: a->f *= b->f; break; + + case EDIV|U: if (!b->u) return 0; + a->u /= b->u; + break; + case EDIV|S: if (!b->i) return 0; + if (a->i == LLONG_MIN && b->i == -1) break; + a->i /= b->i; + break; + case EDIV|F: a->f /= b->f; break; + + case EREM|U: if (!b->u) return 0; + a->u %= b->u; + break; + case EREM|S: if (!b->i) return 0; + if (a->i == LLONG_MIN && b->i == -1) a->i = 0; + a->i %= b->i; + break; + + case EBAND|U: + case EBAND|S: a->u &= b->u; break; + + case EBIOR|U: + case EBIOR|S: a->u |= b->u; break; + + case EXOR|U: + case EXOR|S: a->u ^= b->u; break; + + case ESHL|S: if (a->i < 0) return 0; + case ESHL|U: if (b->u >= 8*targ_primsizes[opty.t]) return 0; + a->u <<= b->u; + break; + + case ESHR|U: if (b->u >= 8*targ_primsizes[opty.t]) return 0; + a->u >>= b->i; + break; + case ESHR|S: if (b->u >= 8*targ_primsizes[opty.t]) return 0; + a->i >>= b->i; + break; + + case EEQU|U: + case EEQU|S: a->u = a->u == b->u; break; + case EEQU|F: a->u = a->f == b->f; break; + + case ENEQ|U: + case ENEQ|S: a->u = a->u != b->u; break; + case ENEQ|F: a->u = a->f != b->f; break; + + case ELTH|U: a->u = a->u < b->u; break; + case ELTH|S: a->u = a->i < b->i; break; + case ELTH|F: a->u = a->f < b->f; break; + + case EGTH|U: a->u = a->u > b->u; break; + case EGTH|S: a->u = a->i > b->i; break; + case EGTH|F: a->u = a->f > b->f; break; + + case ELTE|U: a->u = a->u <= b->u; break; + case ELTE|S: a->u = a->i <= b->i; break; + case ELTE|F: a->u = a->f <= b->f; break; + + case EGTE|U: a->u = a->u >= b->u; break; + case EGTE|S: a->u = a->i >= b->i; break; + case EGTE|F: a->u = a->f >= b->f; break; + + case ELOGAND|U: + case ELOGAND|S: + case ELOGAND|F: + c = (op & F) ? a->f : a->u; + if (c) { + if (!eval(b, mode) || !numcast(opty, b, b)) return 0; + c = (op & F) ? b->f : b->u; + } + a->u = c; + break; + + case ELOGIOR|U: + case ELOGIOR|S: + case ELOGIOR|F: + c = op & F ? a->f : a->u; + if (!c) { + if (!eval(b, mode) || !numcast(opty, b, b)) return 0; + c = (op & F) ? b->f : b->u; + } + a->u = c; + break; + default: return 0; + } + + if (!in_range(ex->t, EADD, ESHR)) { + a->t = ENUMLIT; + a->ty = mktype(TYINT); + } + return numcast(ex->ty, ex, a); +} + +bool +eval(struct expr *ex, enum evalmode mode) +{ + switch (ex->t) { + case EGETF: goto Unop; + case ESEQ: + if (!eval(&ex->sub[0], mode)) return 0; + *ex = ex->sub[1]; + return eval(ex, mode); + case ECOND: + if (!eval(&ex->sub[0], mode)) return 0; + *ex = ex->sub[2-truthy(&ex->sub[0])]; + return eval(ex, mode); + case EINIT: + for (struct initval *v = ex->init->vals; v; v = v->next) { + if (!eval(&v->ex, mode)) return 0; + } + return 1; + case ENUMLIT: + if (mode <= EVINTCONST) return !isflt(ex->ty); + return 1; + case ESYM: + if (in_range(ex->ty.t, TYARRAY, TYFUNC) + && mode >= EVSTATICINI) { + struct expr ex2 = staticaddrof(ex, mode); + if (ex2.t) { + union type ty = ex->ty; + *ex = ex2; + ex->ty = ty; + return 1; + } + } + return 0; + case ESTRLIT: case ESSYMREF: + return mode >= EVSTATICINI; + default: + if (isunop(ex->t)) Unop: return unop(ex, mode); + if (isbinop(ex->t)) return binop(ex, mode); + } + return 0; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_keywords.def b/src/c_keywords.def new file mode 100644 index 0000000..e71f25f --- /dev/null +++ b/src/c_keywords.def @@ -0,0 +1,76 @@ +/* !SORTED */ +/* token cstd (alias) */ +_(_Alignas, STDC11, ) +_(_Alignof, STDC11, ) +_(_Atomic, STDC11, ) +_(_BitInt, STDC23, ) +_(_Bool, STDC99, ) +_(_Complex, STDC99, "__complex", "__complex__") +_(_Decimal128, STDC23, ) +_(_Decimal32, STDC23, ) +_(_Decimal64, STDC23, ) +_(_Generic, STDC11, ) +_(_Imaginary, STDC99, ) +_(_Noreturn, STDC11, ) +_(_Static_assert, STDC11, ) +_(_Thread_local, STDC11, ) +_(__asm__, 0, ) +_(__attribute__, 0, ) +_(__builtin_va_arg, 0, ) +_(__extension__, 0, ) +_(__typeof__, 0, "__typeof") +_(alignas, STDC23, ) +_(alignof, STDC23, ) +_(auto, 0, ) +_(bool, STDC23, ) +_(break, 0, ) +_(case, 0, ) +_(char, 0, ) +_(const, 0, "__const", "__const__") +_(constexpr, STDC23, ) +_(continue, 0, ) +_(default, 0, ) +_(do, 0, ) +_(double, 0, ) +_(else, 0, ) +_(enum, 0, ) +_(extern, 0, ) +_(false, STDC23, ) +_(float, 0, ) +_(for, 0, ) +_(goto, 0, ) +_(if, 0, ) +_(inline, STDC99, "__inline", "__inline__") +_(int, 0, ) +_(long, 0, ) +_(nullptr, STDC23, ) +_(register, 0, ) +_(restrict, STDC99, "__restrict", "__restrict__") +_(return, 0, ) +_(short, 0, ) +_(signed, 0, "__signed", "__signed__") +_(sizeof, 0, ) +_(static, 0, ) +_(static_assert, STDC23, ) +_(struct, 0, ) +_(switch, 0, ) +_(thread_local, STDC23, ) +_(true, STDC23, ) +_(typedef, 0, ) +_(typeof, STDC23, ) +_(typeof_unqual, STDC23, ) +_(union, 0, ) +_(unsigned, 0, ) +_(void, 0, ) +_(volatile, 0, "__volatile", "__volatile__") +_(while, 0, ) + +#ifndef TKWBEGIN_ +# define TKWBEGIN_ TKW_Alignas +#endif +#ifndef TKWEND_ +# define TKWEND_ TKWwhile +#endif +#ifndef TKWMAXLEN_ +# define TKWMAXLEN_ (sizeof "__builtin_va_arg" - 1) +#endif diff --git a/src/c_lex.c b/src/c_lex.c new file mode 100644 index 0000000..c196a21 --- /dev/null +++ b/src/c_lex.c @@ -0,0 +1,2496 @@ +#include "lex.h" +#include "../version.h" +#include <string.h> +#include <stdlib.h> + +/* fill internal circular character buffer with input after translation phase 1 & 2 + * (trigraph substitution and backslash-newline deletion */ +static void +fillchrbuf(struct lexer *lx) +{ + const uchar *p = lx->dat + lx->idx; + int i = lx->chrbuf0, idx = lx->idx; + int rem = countof(lx->chrbuf) - i; + assert(rem >= 0); + if (rem > 0) { + memmove(lx->chrbuf, lx->chrbuf+i, rem * sizeof *lx->chrbuf); + memmove(lx->chridxbuf, lx->chridxbuf+i, rem * sizeof *lx->chridxbuf); + } + lx->chrbuf0 = 0; + i = rem; + + for (; i < countof(lx->chrbuf); ++i) { + uchar c; + /* skip backslash-newline* */ + for (;;) { + if (p[0] == '\\') { + if (p[1] == '\n') { + idx += 2; + p += 2; + } else if (p[1] == '\r' && p[2] == '\n') { + idx += 3; + p += 3; + } else break; + } else if (ccopt.trigraph && !memcmp(p, "\?\?/\n", 4)) { + idx += 4; + p += 4; + } else if (ccopt.trigraph && !memcmp(p, "\?\?/\r\n", 5)) { + idx += 5; + p += 5; + } else break; + addfileline(lx->fileid, idx); + } + + if (idx >= lx->ndat) { + c = 0; + } else if (ccopt.trigraph && ((p[0] == '?') & (p[1] == '?'))) { + switch (p[2]) { + case '=': c = '#'; break; + case '(': c = '['; break; + case ')': c = ']'; break; + case '!': c = '|'; break; + case '<': c = '{'; break; + case '>': c = '}'; break; + case '-': c = '~'; break; + case '/': c = '\\'; break; + case '\'': c = '^'; break; + default: goto NoTrigraph; + } + p += 3; + idx += 3; + } else { + NoTrigraph: + ++idx; + if ((c = *p++) == '\n') + addfileline(lx->fileid, idx); + } + lx->chrbuf[i] = c; + lx->chridxbuf[i] = idx; + } + lx->idx = idx; +} + +static uchar +next(struct lexer *lx) +{ + if (lx->chrbuf0 >= countof(lx->chrbuf)) + fillchrbuf(lx); + lx->chridx = lx->chridxbuf[lx->chrbuf0]; + uchar c = lx->chrbuf[lx->chrbuf0]; + lx->eof = lx->chridx >= lx->ndat; + ++lx->chrbuf0; + return c; +} + +static uchar +peek(struct lexer *lx, int off) +{ + assert(off < countof(lx->chrbuf)); + if (lx->chrbuf0 + off >= countof(lx->chrbuf)) + fillchrbuf(lx); + return lx->chrbuf[lx->chrbuf0 + off]; +} + +static bool +match(struct lexer *lx, uchar c) +{ + if (!lx->eof && peek(lx, 0) == c) { + next(lx); + return 1; + } + return 0; +} + +static bool +aissep(int c) { + static const bool tab[] = { + ['('] = 1, [')'] = 1, ['['] = 1, [']'] = 1, + ['{'] = 1, ['}'] = 1, ['.'] = 1, [','] = 1, + [';'] = 1, ['?'] = 1, ['+'] = 1, ['-'] = 1, + ['*'] = 1, ['/'] = 1, ['&'] = 1, ['|'] = 1, + ['^'] = 1, ['~'] = 1, ['='] = 1, ['\''] = 1, + ['"'] = 1, ['<'] = 1, ['>'] = 1, [':'] = 1, + ['@'] = 1, ['#'] = 1, ['%'] = 1, ['\\'] = 1, + ['`'] = 1, ['!'] = 1, + }; + if (!aisprint(c) || aisspace(c)) + return 1; + return (uint)c < sizeof(tab) && tab[c]; +} + +enum typetag +parsenumlit(uvlong *outi, double *outf, const struct token *tk, bool ispp) +{ + if (tk->t == TKCHRLIT) { + uvlong n = 0; + if (!tk->wide) { + for (int i = 0; i < tk->len; ++i) + n = n << 8 | (uchar)tk->s[i]; + } else if (tk->wide == 1) { + n = tk->ws16[0]; + } else { + assert(tk->wide == 2); + n = tk->ws32[0]; + } + if (outi) *outi = n; + return TYINT; + } else if (memchr(tk->s, '.', tk->len)) { + extern double strtod(const char *, char **); + double f; + char buf[80], *suffix; + Float: /* float literal */ + assert(tk->len < sizeof buf - 1 && "numlit too big"); + memcpy(buf, tk->s, tk->len); + buf[tk->len] = 0; + f = strtod(buf, &suffix); + if (suffix == buf) + return 0; + if (!*suffix) { + if (outf) *outf = f; + return TYDOUBLE; + } else if ((suffix[0]|0x20) == 'f' && !suffix[1]) { + if (outf) *outf = f; + return TYFLOAT; + } else if ((suffix[0]|0x20) == 'l' && !suffix[1]) { + if (outf) *outf = f; + return TYLDOUBLE; + } + return 0; + } else { /* int literal */ + static uvlong max4typ[TYUVLONG-TYINT+1]; + uvlong n = 0; + int base = 10, nsx; + bool dec, u = 0, longlongok = ccopt.cstd >= STDC99 || !ccopt.pedant; + enum typetag ty = 0; + const char *sx; /*suffix*/ + char c; + + if (!max4typ[0]) + for (ty = TYINT; ty <= TYUVLONG; ++ty) + max4typ[ty-TYINT] = ((1ull << (8*targ_primsizes[ty]-1))-1) << isunsignedt(ty) | 1; + + sx = tk->s; + if (tk->len > 2 && sx[0] == '0') { + if ((sx[1]|32) == 'x') sx += 2, base = 16; /* 0x.. */ + else if ((sx[1]|32) == 'b') sx += 2, base = 2; /* 0b.. */ + else base = 8; /* 0.. */ + } + for (; sx < tk->s + tk->len; ++sx) { + if (base < 16) { + if (!in_range(c = *sx, '0', '0'+base-1)) break; + n = n*base + c - '0'; + } else { + if (in_range(c = *sx, '0', '9')) n = n*base + c - '0'; + else if (in_range(c|32, 'a', 'f')) n = n*base + 0xa + (c|32) - 'a'; + else break; + } + } + dec = base == 10; + nsx = tk->len - (sx - tk->s); + + if (nsx == 0) /* '' */ {} + else if ((sx[0]|32) == 'u') { + u = 1; + if (nsx == 1) /* 'u' */ {} + else if ((sx[1]|32) == 'l') { + if (nsx == 2) /* 'ul' */ goto L; + if (sx[1] == sx[2] && nsx == 3) /* 'ull' */ goto LL; + return 0; + } else return 0; + } else if ((sx[0]|32) == 'l') { + if (nsx == 1) /* 'l' */ goto L; + if ((sx[1]|32) == 'u' && nsx == 2) /* 'lu' */ { u=1; goto L; } + if (sx[1] == sx[0]) { + if (nsx == 2) /* 'll' */ goto LL; + if ((sx[2]|32) == 'u' && nsx == 3) /* 'llu' */ { u=1; goto LL; } + } + return 0; + } else if ((sx[0]|32) == 'e' || (sx[0]|32) == 'p') + goto Float; + else return 0; + +#define I(T) if (n <= max4typ[T - TYINT]) { ty = T; goto Ok; } + I(TYINT) + if (u || !dec) I(TYUINT) + L: + I(TYLONG) + if (u || !dec || !longlongok) I(TYULONG) + if (longlongok) { + LL: + I(TYVLONG) + if (u || !dec) I(TYUVLONG) + } + if (ispp) { ty = TYUVLONG; goto Ok; } +#undef I + /* too big */ + if (outi) *outi = n; + return 0; + Ok: + if (u && issignedt(ty)) ++ty; /* make unsigned */ + if (outi) *outi = n; + if (ispp) { + if (u) return TYUVLONG; + else if (n <= max4typ[TYVLONG-TYINT]) return TYVLONG; + } + if (ty >= TYVLONG && !longlongok) + warn(&tk->span, "'long long' in %M is an extension"); + return ty; + } +} + +static void +readstrchrlit(struct lexer *lx, struct token *tk, char delim, int wide) +{ + int c, i; + uchar tmp[200]; + vec_of(uchar) b = VINIT(tmp, sizeof tmp); + struct span span = {0}; + uint n, beginoff, idx; + beginoff = idx = lx->chridx; + + while ((c = next(lx)) != delim) { + static uint wmax[] = {0xFF, 0xFFFF, 0xFFFFFFFFu}; + if (c == '\n' || c == TKEOF) { + Noterm: + span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid }; + error(&span, "missing terminating %c character", delim); + break; + } else if (c == '\\') { + span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid }; + switch (c = next(lx)) { + case '\n': case TKEOF: + goto Noterm; + case '\'': c = '\''; break; + case '\\': c = '\\'; break; + case '"': c = '"'; break; + case '?': c = '?'; break; + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'x': case 'X': /* hex */ + n = 0; + if (!aisxdigit(peek(lx, 0))) goto Badescseq; + do { + c = next(lx); + if (c-'0' < 10) n = n<<4 | (c-'0'); + else n = n<<4 | (10 + (c|0x20)-'a'); + } while (aisxdigit(peek(lx, 0))); + if (n > wmax[wide]) { + span.sl.len = lx->chridx - span.sl.off; + error(&span, "hex escape sequence out of range"); + } + c = n; + break; + default: + if (aisodigit(c)) { /* octal */ + n = c-'0'; + for (i = 2; i--;) { + if (!aisodigit(peek(lx, 0))) break; + n = n<<3 | ((c = next(lx))-'0'); + } + if (n > wmax[wide]) { + span.sl.len = lx->chridx - span.sl.off; + error(&span, "octal escape sequence out of range"); + } + c = n; + break; + } + Badescseq: + span.sl.len = lx->chridx - span.sl.off; + error(&span, "invalid escape sequence"); + } + } + if (!wide || c <= 0xFF) { + vpush(&b, c); + } else { + /* XXX this doesn't work for non-utf sequences, UTF-16 surrogates, etc + * the source utf8 -> utf16/32 conversion should be done on the fly, then + * these can also be appended directly, rather than doing the conversion at the end */ + char p[4]; + int n = utf8enc(p, c); + vpushn(&b, p, n); + } + idx = lx->chridx;; + } + if (delim == '"') { + tk->t = TKSTRLIT; + tk->len = b.n; + if ((tk->wide = wide)) { + tk->litlit = 0; + if (wide == 1) + tk->ws16 = utf8to16(&tk->len, lx->tmparena, b.p, b.n); + else + tk->ws32 = utf8to32(&tk->len, lx->tmparena, b.p, b.n); + } else if (lx->chridx - beginoff == tk->len + 1) { + tk->litlit = 1; + tk->s = (char *)&lx->dat[beginoff]; + } else { + tk->litlit = 0; + vpush(&b, 0); + tk->s = alloccopy(lx->tmparena, b.p, b.n, 1); + } + } else { + if (b.n == 0) { + span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid }; + error(&span, "empty character literal"); + } else if (b.n > targ_primsizes[TYINT]) { + span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid }; + error(&span, "multicharacter literal too long"); + } + tk->t = TKCHRLIT; + tk->len = b.n; + if ((tk->wide = wide)) { + tk->litlit = 0; + if (wide == 1) + tk->ws16 = utf8to16(&tk->len, lx->tmparena, b.p, b.n); + else + tk->ws32 = utf8to32(&tk->len, lx->tmparena, b.p, b.n); + } else if (lx->chridx - beginoff == tk->len + 1) { + tk->litlit = 1; + tk->s = (char *)&lx->dat[beginoff]; + } else { + tk->litlit = 0; + tk->s = alloccopy(lx->tmparena, b.p, tk->len, 1); + } + } + vfree(&b); +} + +/* for #include directive, read "header" or <header> */ +static void +readheadername(struct lexer *lx, struct token *tk, char delim) +{ + int c; + uchar tmp[200]; + vec_of(uchar) b = VINIT(tmp, sizeof tmp); + struct span span = {0}; + uint beginoff, idx; + beginoff = idx = lx->chridx; + + while ((c = next(lx)) != delim) { + if (c == '\n' || lx->eof) { + span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid }; + error(&span, "missing terminating %c character", delim); + break; + } + vpush(&b, c); + idx = lx->chridx;; + } + tk->t = delim == '"' ? TKPPHDRQ : TKPPHDRH; + tk->len = b.n; + if (lx->chridx - beginoff == tk->len + 1) { + tk->litlit = 1; + tk->s = (char *)&lx->dat[beginoff]; + } else { + tk->litlit = 0; + vpush(&b, 0); + tk->s = alloccopy(lx->tmparena, b.p, b.n, 1); + } + vfree(&b); +} + +/* matches "<digit> | <identifier-nondigit> | '.' | ([eEpP][+-])" */ +static bool +isppnum(char prev, char c) +{ + if (!aissep(c) || c == '.') + return 1; + if (c == '+' || c == '-') + return (prev|0x20) == 'e' || (prev|0x20) == 'p'; + return 0; +} + +enum { MAXLITLEN = 256 }; /* maximum length of num literals and identifiers */ +static int +lex0(struct lexer *lx, struct token *tk, bool includeheader) +{ + int idx,q; + bool space = 0; +Begin: + idx = lx->chridx; + if (lx->chrbuf0+4 >= countof(lx->chrbuf)) + fillchrbuf(lx); + lx->chridx = lx->chridxbuf[lx->chrbuf0]; + uchar *p = &lx->chrbuf[lx->chrbuf0++], + c = p[0]; + switch (c) { + +#define RET(t_) do { tk->t = (t_); goto End; } while (0) +#define TK2(c2,t) if (p[1] == c2) { \ + lx->chridx = lx->chridxbuf[lx->chrbuf0]; \ + ++lx->chrbuf0; \ + RET(t); \ + } +#define TK3(c2,c3,t) if (p[1] == c2 && p[2] == c3) { \ + lx->chridx = lx->chridxbuf[++lx->chrbuf0]; \ + ++lx->chrbuf0; \ + RET(t); \ + } + + case ' ': case '\t': case '\f': case '\v': case '\r': + space = 1; + goto Begin; + break; + case '(': case ')': case ',': case ':': + case ';': case '?': case '[': case ']': + case '{': case '}': case '~': case '$': + case '@': case '`': case '\\': case '\n': + RET(c); + case '!': + TK2('=', TKNEQ); + RET(c); + case '#': + TK2('#', TKPPCAT); + RET(c); + case '+': + TK2('+', TKINC); + TK2('=', TKSETADD); + RET(c); + case '-': + TK2('-', TKDEC); + TK2('=', TKSETSUB); + TK2('>', TKARROW); + RET(c); + case '*': + TK2('=', TKSETMUL); + RET(c); + case '/': + TK2('=', TKSETDIV); + if (match(lx, '/')) { + /* // single line comment */ + for (;;) { + do { + if (lx->chrbuf[lx->chrbuf0] == '\n') { + lx->chridx = lx->chridxbuf[lx->chrbuf0++]; + lx->eof = lx->chridx >= lx->ndat; + RET('\n'); + } else if (lx->eof) RET(TKEOF); + } while (++lx->chrbuf0 < countof(lx->chrbuf)); + fillchrbuf(lx); + lx->chridx = lx->chridxbuf[lx->chrbuf0]; + lx->eof = lx->chridx >= lx->ndat; + } + } else if (match(lx, '*')) { + // /* multi line comment */ + if (lx->chrbuf0+1 >= countof(lx->chrbuf)) fillchrbuf(lx); + for (;;) { + do { + if (lx->chrbuf[lx->chrbuf0] == '*' && lx->chrbuf[lx->chrbuf0+1] == '/') { + lx->chridx = lx->chridxbuf[lx->chrbuf0+1]; + lx->chrbuf0 += 2; + lx->eof = lx->chridx >= lx->ndat; + space = 1; + goto Begin; + } + } while (++lx->chrbuf0+1 < countof(lx->chrbuf)); + fillchrbuf(lx); + lx->chridx = lx->chridxbuf[lx->chrbuf0]; + if ((lx->eof = (lx->chridx >= lx->ndat))) { + struct span span = {{ idx, lx->chridx - idx, lx->fileid }}; + fatal(&span, "unterminated comment"); + } + } + } + RET(c); + case '%': + TK2('=', TKSETREM); + RET(c); + case '^': + TK2('=', TKSETXOR); + RET(c); + case '=': + TK2('=', TKEQU); + RET(c); + case '<': + if (includeheader) { + readheadername(lx, tk, '>'); + goto End; + } + TK2('=', TKLTE); + TK3('<','=', TKSETSHL) + TK2('<', TKSHL); + RET(c); + case '>': + TK2('=', TKGTE); + TK3('>','=', TKSETSHR) + TK2('>', TKSHR); + RET(c); + case '&': + TK2('&', TKLOGAND); + TK2('=', TKSETAND); + RET(c); + case '|': + TK2('|', TKLOGIOR); + TK2('=', TKSETIOR); + RET(c); + case '"': + if (includeheader) { + readheadername(lx, tk, '"'); + } else { + case '\'': + tk->wideuni = 0; + readstrchrlit(lx, tk, c, 0); + } + goto End; + case '.': + TK3('.','.',TKDOTS) + if (aisdigit(p[1])) goto Numlit; + RET(c); + case 'L': + if (match(lx, (q = '\'')) || match(lx, (q = '"'))) { + tk->wideuni = 0; + readstrchrlit(lx, tk, q, /* wide */ targ_primsizes[targ_wchartype] == 2 ? 1 : 2); + goto End; + } + /* fallthru */ + default: + if (aisdigit(c)) Numlit: { + --lx->chrbuf0; + if (lx->chrbuf0 + MAXLITLEN >= countof(lx->chrbuf)) + fillchrbuf(lx); + int n = 1; + uchar *p = &lx->chrbuf[lx->chrbuf0]; + for (; isppnum(p[n-1], p[n]); ++n) { + if (n >= MAXLITLEN) { + lx->chridx = lx->chridxbuf[lx->chrbuf0+n-1]; + TooLong: + fatal(&(struct span) {{ idx, lx->chridx - idx, lx->fileid }}, + "token is too long"); + } + } + tk->len = n; + lx->chridx = lx->chridxbuf[(lx->chrbuf0 += n) - 1]; + if (n == lx->chridx - idx) { + tk->litlit = 1; + tk->s = (char *)&lx->dat[idx]; + } else { + tk->litlit = 0; + tk->s = alloccopy(lx->tmparena, p, n, 1); + } + RET(TKNUMLIT); + } else if (c == '_' || aisalpha(c)) { + --lx->chrbuf0; + if (lx->chrbuf0 + MAXLITLEN >= countof(lx->chrbuf)) + fillchrbuf(lx); + uchar *p = &lx->chrbuf[lx->chrbuf0]; + int n = 1; + for (; !aissep(p[n]); ++n) { + if (n >= MAXLITLEN) { + lx->chridx = lx->chridxbuf[lx->chrbuf0+n-1]; + goto TooLong; + } + } + tk->blue = 0; + tk->len = n; + tk->name = intern_((char *)p, n); + lx->chridx = lx->chridxbuf[(lx->chrbuf0 += n) - 1]; + RET(TKIDENT); + } + /* fallthru */ + case 0: if (lx->idx >= lx->ndat) RET(TKEOF); +#undef TK2 + } + fatal(&(struct span) {{ idx, lx->chridx - idx, lx->fileid }}, + "unexpected character %'c at %d (%d)", c, idx, lx->idx); +End: + tk->space = space; + tk->span.sl.file = lx->fileid; + tk->span.sl.off = idx; + tk->span.sl.len = lx->chridx - idx; + tk->span.ex = tk->span.sl; + return tk->t; +#undef RET +} + +/****************/ +/* PREPROCESSOR */ +/****************/ + +static bool +tokequ(const struct token *a, const struct token *b) +{ + if (a->t != b->t) return 0; + if (a->t == TKNUMLIT || a->t == TKSTRLIT || a->t == TKCHRLIT) { + if (a->len != b->len) return 0; + return !memcmp(a->s, b->s, a->len); + } else if (a->t == TKIDENT) { + return a->name == b->name; + } else if (a->t == TKPPMACARG || a->t == TKPPMACSTR) { + return a->argidx == b->argidx; + } + return 1; +} + +static vec_of(struct token) mtoksbuf, /* buffers for macro replacement list tokens */ + mdyntoksbuf; /* for function-like macros after parameter substitution */ + +struct macro { + internstr *param; + struct span0 span; + uchar nparam; + bool predef : 1, + special : 1, + fnlike : 1, + variadic : 1; + short id; + union { + void (*handler)(struct lexer *, struct token *); + struct rlist { + uint off; /* mtoksbuf[] */ + int n; + } rl; + const struct token *single; /* predef */ + void (*handlerfn)(struct lexer *, struct token *ret, const struct token *arg, int narg); + }; +}; + +static bool +macroequ(const struct macro *a, const struct macro *b) +{ + if (a->special != b->special) return 0; + if (a->fnlike != b->fnlike || a->variadic != b->variadic) return 0; + if (a->fnlike) { + if (a->nparam != b->nparam) return 0; + for (int i = 0; i < a->nparam; ++i) + if (a->param[i] != b->param[i]) + return 0; + } + if (a->special) return a->handler == b->handler; + if (a->rl.n != b->rl.n) return 0; + const struct token *tka = &mtoksbuf.p[a->rl.off], *tkb = &mtoksbuf.p[b->rl.off]; + for (int i = 0; i < a->rl.n; ++i) { + if (!tokequ(&tka[i], &tkb[i])) + return 0; + if (i > 0 && tka[i].space != tkb[i].space) + return 0; + } + return 1; +} + +static void +freemac(struct macro *mac) +{ + if (mac->special) return; + free(mac->param); +} + +static pmap_of(struct macro) macroht; + +static void +putmac(internstr name, struct macro *mac) +{ + static short id; + if (!macroht.v) pmap_init(¯oht, 1<<10); + struct macro *slot = pmap_get(¯oht, name); + mac->id = id++; + if (slot) { + if (!macroequ(slot, mac)) { + if (slot->predef) + warn(&(struct span){mac->span}, "redefining builtin macro"); + else { + warn(&(struct span){mac->span}, "redefining macro"); + note(&(struct span){slot->span}, "previous definition:"); + } + freemac(slot); + *slot = *mac; + } else { + freemac(mac); + } + } else { + pmap_set(¯oht, name, *mac); + } +} + +static void +delmac(internstr name) +{ + struct macro *slot = pmap_get(¯oht, name); + if (!slot) return; + freemac(slot); + pmap_del(¯oht, name); +} + +static inline internstr +macname(struct macro *mac) +{ + return macroht.mb.k[mac - macroht.v]; +} + +static inline struct macro * +findmac(internstr name) +{ + return pmap_get(¯oht, name); +} + +static void popmac(struct lexer *, bool all); + +static struct macrostack { + struct { + union { + uint off; /* mtoksbuf[]/mdyntoksbuf[] */ + const struct token *p; + }; + int n; + } rl; + struct span0 exspan; + int idx; + short macid; /* -1 for argument undergoing expansion */ + bool space : 1, stop : 1, dyn; +} mstk[1200]; + +static void NORETURN +lxfatal(struct lexer *lx, const struct span *span, const char *fmt, ...) +{ + if (fmt) { + va_list ap; + va_start(ap, fmt); + vdiag(span, DGERROR, fmt, ap); + va_end(ap); + } + int n = lx->macstk ? lx->macstk - mstk : 0, i = 0; + for (struct macrostack *l = lx->macstk; l && l > mstk; --l, ++i) { + if (i < 4 || i > n - 5) { + note(&(struct span){l->exspan}, "expanded from here"); + } else if (i == 5) { + efmt(" (...) \n"); + } + } + for (struct lexer *sv = lx->save; sv; sv = sv->save) { + int line; + const char *f = getfilepos(&line, NULL, sv->fileid, sv->chridx-2); + note(NULL, "in file included from %s:%d", f, line); + } + if (!fmt || span) efmt("Aborting due to previous error.\n"); + exit(1); +} + +static void +ppskipline(struct lexer *lx) +{ + while (lx->macstk) popmac(lx, 1); + for (int c; (c = peek(lx, 0)) != '\n' && !lx->eof; next(lx)) { + if (c == '/' && peek(lx, 1) == '*') { /* comment */ + next(lx), next(lx); + bool done = 0; + while (!((c = peek(lx, 0)) == '*' && peek(lx, 1) == '/')) { + if (lx->eof) { + struct span span = {{ lx->idx, lx->chridx - lx->idx, lx->fileid }}; + lxfatal(lx, &span, "unterminated comment"); + } + done = c == '\n'; + next(lx); + } + next(lx); + if (done) return; + } + } +} + +#define isppident(tk) in_range((tk).t, TKIDENT, TKWEND_) + +static bool +tokpaste(struct lexer *lx, struct token *dst, const struct token *l, const struct token *r) +{ + int t; + if (isppident(*l) && (isppident(*r) || r->t == TKNUMLIT)) { + /* foo ## bar ; foo ## 123 */ + t = TKIDENT; + } else if (l->t == TKNUMLIT && (isppident(*r) || r->t == TKNUMLIT)) { + /* 0x ## abc ; 213 ## 456 */ + t = TKNUMLIT; + } else if (l->t && !r->t) { + if (dst) *dst = *l; + return 1; + } else if (!l->t && r->t) { + if (dst) *dst = *r; + return 1; + } else { + static const struct { char s[2]; char t; } tab[] = { + {"==", TKEQU}, {"!=", TKNEQ}, {"<=", TKLTE}, {">=", TKGTE}, + {">>", TKSHR}, {"<<", TKSHL}, {"++", TKINC}, {"--", TKDEC}, + {"->", TKARROW}, {"##", TKPPCAT}, {"&&", TKLOGAND}, {"||", TKLOGIOR}, + {"+=", TKSETADD}, {"-=", TKSETSUB}, {"*=", TKSETMUL}, {"/=", TKSETDIV}, + {"%=", TKSETREM}, {"|=", TKSETIOR}, {"^=", TKSETXOR}, {"&=", TKSETAND}, + {{TKSHL,'='}, TKSETSHL}, {{TKSHR,'='}, TKSETSHR} + }; + for (int i = 0; i < countof(tab); ++i) { + if (tab[i].s[0] == l->t && tab[i].s[1] == r->t) { + if (dst) dst->t = tab[i].t; + return 1; + } + } + + if (dst) { + error(&l->span, "pasting %'tk and %'tk does not form a valid preprocessing token", l, r); + note(&r->span, "right-hand side"); + } + return 0; + } + + if (!dst) return 1; + char buf[200]; + memset(dst, 0, sizeof *dst); + dst->span = l->span; + if (dst->span.ex.file == r->span.ex.file && dst->span.ex.off < r->span.ex.off) + joinspan(&dst->span.ex, r->span.ex); + dst->t = t; + dst->len = l->len + r->len; + char *s = (isppident(*dst) && dst->len + 1 < sizeof buf) ? buf : alloc(lx->tmparena, dst->len + 1, 1); + memcpy(s, l->s, l->len); + memcpy(s + l->len, r->s, r->len); + s[dst->len] = 0; + dst->space = l->space; + if (isppident(*dst)) { + dst->blue = 0; + dst->name = intern(s); + } else { + dst->s = s; + } + return 1; +} + +enum { MAXMACROARGS = 128 }; + +static void +ppdefine(struct lexer *lx) +{ + struct token tk0, tk; + internstr mname; + struct macro mac = {0}; + struct bitset usedparams[BSSIZE(MAXMACROARGS)] = {0}; + + lex0(lx, &tk0, 0); + if (tk0.t != TKIDENT) { + error(&tk0.span, "macro name missing"); + ppskipline(lx); + return; + } + mname = tk0.name; + mac.span = tk0.span.sl; + + if (match(lx, '(')) { + /* gather params for function-like macro */ + vec_of(internstr) params = {0}; + vinit(¶ms, NULL, 4); + mac.fnlike = 1; + while (lex0(lx, &tk, 0) != ')') { + if (mac.variadic) { + error(&tk.span, "expected `)' after `...'"); + if (tk.t == TKEOF || tk.t == '\n') return; + break; + } + if (params.n > 0) { + if (tk.t == TKDOTS) { /* GNU extension 'args...' */ + mac.variadic = 1; + continue; + } if (tk.t != ',') { + error(&tk.span, "expected `,' or `)'"); + if (tk.t == TKEOF || tk.t == '\n') return; + break; + } + lex0(lx, &tk, 0); + } + if (tk.t == TKIDENT) + vpush(¶ms, tk.name); + else if (tk.t == TKDOTS) { + mac.variadic = 1; + vpush(¶ms, intern("__VA_ARGS__")); + } else { + error(&tk.span, "expected parameter name or `)'"); + if (tk.t == TKEOF || tk.t == '\n') return; + break; + } + } + if (!params.n) vfree(¶ms); + mac.param = params.p; + mac.nparam = params.n; + } + + /* gather replacement list */ + mac.rl.off = mtoksbuf.n; + for (int n = 0; lex0(lx, &tk, 0) != '\n' && tk.t != TKEOF;) { + if (n == 0 && !tk.space) + warn(&tk.span, "no whitespace after macro name"); + struct token *prev = n ? &mtoksbuf.p[mtoksbuf.n-1] : NULL; + if (mac.fnlike && tk.t == TKIDENT) { + for (int i = 0; i < mac.nparam; ++i) { + if (tk.name == mac.param[i]) { + bsset(usedparams, i); + tk.argidx = i; + if (prev && prev->t == '#') { + tk.t = TKPPMACSTR; + *prev = tk; + goto Next; + } else { + tk.t = TKPPMACARG; + break; + } + } + } + } + if (n > 1 && prev->t == TKPPCAT) { + struct token new; + if (prev[-1].t != TKPPMACARG && tk.t != TKPPMACARG + && tokpaste(lx, &new, &prev[-1], &tk)) + { + /* trivial concatenations */ + prev[-1] = new; + --mtoksbuf.n; + --n; + continue; + } + } + if (in_range(tk.t, TKNUMLIT, TKSTRLIT) && !tk.litlit) + tk.s = alloccopy(&globarena, tk.s, tk.len << tk.wide, 1); + vpush(&mtoksbuf, tk); + ++n; + Next:; + } + mac.rl.n = mtoksbuf.n - mac.rl.off; + /* mark unused params as such by nulling out param name, + * this way they aren't expanded when unused in the macro body */ + for (uint i = 0; bsiterzr(&i, usedparams, countof(usedparams)) && i < mac.nparam; ++i) { + mac.param[i] = NULL; + } + putmac(mname, &mac); +} + +static void +expecteol(struct lexer *lx, const char *ppname) +{ + struct token tk; + assert(!lx->macstk); + if (lex0(lx, &tk, 0) != '\n' && tk.t != TKEOF) { + (ccopt.pedant ? error : warn)(&tk.span, "extra tokens after #%s", ppname); + ppskipline(lx); + } +} +static void +ppundef(struct lexer *lx) +{ + struct token tk; + + lex0(lx, &tk, 0); + if (tk.t != TKIDENT) { + error(&tk.span, "macro name missing"); + ppskipline(lx); + return; + } + expecteol(lx, "undef"); + delmac(tk.name); +} + +static void +pushmacstk(struct lexer *lx, const struct span *span, const struct macrostack *m) +{ + struct macrostack *l = lx->macstk; + if (!l) l = mstk; + else if ((++l == mstk+countof(mstk))) lxfatal(lx, span, "macro expansion depth limit reached"); + *l = *m; + l->idx = 0; + l->exspan = span->ex; + lx->macstk = l; +} + +static void +popmac(struct lexer *lx, bool all) +{ + struct macrostack *stk; + + assert(stk = lx->macstk); + do { + if (stk->dyn) + mdyntoksbuf.n -= stk->rl.n; + if (lx->macstk == mstk) lx->macstk = NULL; + else --lx->macstk; + if (!all) break; + } while ((stk = lx->macstk) && stk->idx >= stk->rl.n && !stk->stop); +} + + +static inline const struct token * +stkgetrl(struct macrostack *s) +{ + if (s->macid < 0) return s->rl.p; + return (s->dyn ? mdyntoksbuf.p : mtoksbuf.p) + s->rl.off; +} + +static void expandfnmacro(struct lexer *lx, struct span *span, internstr mname, struct macro *mac); + +static enum expandres { EXPNONE, EXPINL, EXPSTACK } +tryexpand(struct lexer *lx, struct token *tk) +{ + struct span span = tk->span; + struct macro *mac = NULL; + internstr mname = tk->name; + + if (tk->t != TKIDENT || tk->blue || !(mac = findmac(mname))) + return EXPNONE; + + /* prevent infinite recursion */ + for (struct macrostack *l = lx->macstk; l && l+1 > mstk; --l) { + if (l->macid == mac->id) { + tk->blue = 1; + return EXPNONE; + } + } + + struct macrostack *stkprev = lx->macstk; + if (mac->special && !mac->fnlike) { + mac->handler(lx, tk); + return EXPINL; + } else if (mac->fnlike) { + /* look if there is a '(' token ahead, expand if so */ + struct macrostack *s = lx->macstk; + if (s && s->idx >= s->rl.n && !s->stop) { + popmac(lx, 1); + s = lx->macstk; + } + if (!s) { /* top-level context: looking ahead in file data */ + struct token tk; + int t; + for (;;) { /* skip whitespace and comments */ + if (aisspace(t = peek(lx, 0))) next(lx); + else if (t == '/') { + int idx = lx->chridx; + switch (peek(lx, 1)) { + case '/': + while (!lx->eof && next(lx) != '\n') ; + continue; + case '*': + next(lx), next(lx); + while (peek(lx, 0) != '*' || peek(lx, 1) != '/') { + if (lx->eof) { + struct span span = {{ idx, lx->chridx - idx, lx->fileid }}; + lxfatal(lx, &span, "unterminated comment"); + } + next(lx); + } + next(lx), next(lx); + continue; + } + break; + } else break; + } + if (t != '(') return 0; + lex0(lx, &tk, 0); + } else { /* expansion context: look ahead in macro stack */ + if (s->idx >= s->rl.n || stkgetrl(s)[s->idx].t != '(') return 0; + ++s->idx; + } + expandfnmacro(lx, &span, mname, mac); + } else if (mac->predef && mac->single) { + struct span span = tk->span; + *tk = *mac->single; + tk->span = span; + return EXPINL; + } else if (mac->rl.n) { + pushmacstk(lx, &span, &(struct macrostack){ + .rl = { .off = mac->rl.off, .n = mac->rl.n }, + .macid = mac->id, + .space = tk->space, + }); + } + if (lx->macstk != stkprev) { + lx->macstk->space = tk->space; + } + return EXPSTACK; +} + +static bool +advancemacstk(struct lexer *lx, struct token *tk) +{ + struct macrostack *s = lx->macstk; + assert(s != NULL); + if (s->idx >= s->rl.n) { + if (s->stop) { + tk->t = TKEOF; + return 1; + } + popmac(lx, 1); + return 0; + } + *tk = stkgetrl(s)[s->idx]; + if (s->idx == 0) { + /* the first token of the replaced expansion gets its space from the + * context in which it is expanded */ + tk->space = s->space; + } + ++s->idx; + assert(tk->t && tk->t != TKEOF); + tk->span.ex = s->exspan; + return tryexpand(lx, tk) != EXPSTACK; +} + +static void +expandfnmacro(struct lexer *lx, struct span *span, internstr mname, struct macro *mac) +{ + struct token _argsbuf[30]; + vec_of(struct token) argsbuf = VINIT(_argsbuf, countof(_argsbuf)); /* buffer for argument tokens */ + struct span excessspan; + int cur, len, i, bal, narg; + struct token tk; + bool toomany = 0; + struct argtks { + int idx, n; /* slices of argsbuf */ + int idx2, n2; + ushort nfirstx, /* for concatenation to work properly with expanded arguments, */ + nlastx; /* length of expanded first and last tokens of the unexpanded argument */ + } _args0[4], + *args = mac->nparam < countof(_args0) ? _args0 : alloc(lx->tmparena, sizeof *args * mac->nparam, 0); + + cur = i = bal = len = narg = 0; + for (struct macrostack *s = lx->macstk;;) { + if (!s) { + bool nl = 0; + for (;; nl = 1) { + lex0(lx, &tk, 0); + if (tk.t != '\n') break; + } + tk.space |= nl; + } + else { + tk = s->idx < s->rl.n ? stkgetrl(s)[s->idx++] : (struct token){TKEOF}; + } + if (((tk.t == ')' && bal == 0) || tk.t == TKEOF)) break; + if (tk.t == ',' && bal == 0) { + ++narg; + if (i == mac->nparam-1 && !mac->variadic) { + excessspan = tk.span; + toomany = 1; + } else if (i < mac->nparam - mac->variadic) { + assert(i < MAXMACROARGS); + args[i].idx = cur; + args[i].n = len; + cur = argsbuf.n; + len = 0; + ++i; + } else if (mac->variadic) { + vpush(&argsbuf, tk); + ++len; + } + } else if (!toomany) { + if (tk.t == '(') ++bal; + else if (tk.t == ')') --bal; + vpush(&argsbuf, tk); + ++len; + } + } + + if (tk.t == TKEOF) { + joinspan(&span->ex, tk.span.ex); + lxfatal(lx, span, "unterminated function-like macro invocation"); + } else if (i < mac->nparam) { + ++narg; + args[i].idx = cur; + args[i].n = len; + cur = argsbuf.n; + len = 0; + ++i; + } + joinspan(&span->ex, tk.span.ex); + int expargs0 = argsbuf.n; + for (int i = 0; i < mac->nparam; ++i) { + struct argtks *arg = &args[i]; + if (i >= narg) { + memset(arg, 0, sizeof *arg); + } else if (!mac->param || (mac->param[i] && arg->n > 0)) { + /* expand args used in the macro body */ + pushmacstk(lx, &tk.span, &(struct macrostack) { + .rl = { .p = argsbuf.p + arg->idx, .n = arg->n }, + .macid = -1, + .stop = 1, + }); + struct macrostack *l = lx->macstk; + arg->idx2 = argsbuf.n; + arg->nfirstx = arg->nlastx = 1; + int ilastx = -1; + for (bool pad = 0;;) { + struct macrostack *sprev = lx->macstk; + if (!advancemacstk(lx, &tk)) { + pad |= tk.space && lx->macstk == sprev; /* preserve whitespace empty macro */ + if (lx->macstk == l && l->idx == 1) + arg->nfirstx = argsbuf.n - arg->idx2; + if (lx->macstk == l+1 && lx->macstk->idx == 0 && l->idx == l->rl.n) + ilastx = argsbuf.n - arg->idx2; + continue; + } + if (tk.t == TKEOF) break; + size_t off = l->rl.p - argsbuf.p; + tk.space |= pad; + vpush(&argsbuf, tk); + l->rl.p = argsbuf.p + off; + pad = 0; + } + arg->n2 = argsbuf.n - arg->idx2; + arg->nlastx = ilastx < 0 ? 1 : args->n2 - ilastx; + assert(lx->macstk == l); + popmac(lx, 0); + } else { + memset(arg, 0, sizeof *arg); + } + } + if (narg < mac->nparam - mac->variadic) { + warn(span, "macro `%s' passed %d arguments, but takes %d", mname, narg, mac->nparam); + } else if (toomany) { + joinspan(&excessspan.ex, tk.span.ex); + warn(&excessspan, "macro `%s' passed %d arguments, but takes just %d", mname, narg, mac->nparam); + } + if (mac->special) { + mac->handlerfn(lx, &tk, argsbuf.p+expargs0, argsbuf.n-expargs0); + vpush(&mdyntoksbuf, tk); + pushmacstk(lx, span, &(struct macrostack){ + .rl = { .off = mdyntoksbuf.n-1, .n = 1 }, + .dyn = 1, + .macid = mac->id, + }); + } else if (mac->nparam > 0) { /* make new rlist with args replaced */ + bool vaoptskip = 0, spacepad = 0; + int vaoptbal = 0; + uint off = mdyntoksbuf.n; + for (int i = 0; i < mac->rl.n; ++i) { + struct argtks *arg; + const struct token *tki = &mtoksbuf.p[mac->rl.off+i]; + if (vaoptskip) { + assert(vaoptbal > 0); + if (tki->t == '(') ++vaoptbal; + else if (tki->t == ')') { + if (--vaoptbal == 0) vaoptskip = 0; + } + continue; + } + if (tki->t == TKPPCAT && i > 0 && i < mac->rl.n-1) { /* concatenation */ + const struct token *lhs = tki-1, + *rhs = tki+1; + bool space = lhs->space | spacepad; + if (lhs->t == ',' && mac->variadic + && rhs->t == TKPPMACARG && rhs->argidx == mac->nparam-1) { + /* handle GNU extension: ', ## __VA_ARGS__' */ + arg = &args[rhs->argidx]; + if (narg < mac->nparam) { /* no vaargs -> skip comma */ + assert(arg->n == 0); + --mdyntoksbuf.n; + } else { /* otherwise put comma and substitute vaargs */ + vpushn(&mdyntoksbuf, argsbuf.p+arg->idx2, arg->n2); + mdyntoksbuf.p[mdyntoksbuf.n - arg->n2].space |= rhs->space | tk.space; + } + ++i; /* we already handled rhs (__VA_ARGS__) */ + continue; + } + if (i > 2 && tki[-2].t == TKPPCAT) { + /* handles chained concatenations: xyz ## arg ## c + * lhs ^ rhs */ + lhs = (off < mdyntoksbuf.n) ? &mdyntoksbuf.p[--mdyntoksbuf.n] : NULL; + } else if (lhs->t == TKPPMACARG) { + arg = &args[lhs->argidx]; + lhs = arg->n ? &argsbuf.p[arg->idx + arg->n-1] : NULL; + if (lhs && arg->n > 1) space |= lhs->space; + } else { + --mdyntoksbuf.n; + } + if (rhs->t == TKPPMACARG) { + arg = &args[rhs->argidx]; + rhs = arg->n ? &argsbuf.p[arg->idx] : NULL; + } else { + ++i; + } + if (!lhs && !rhs) continue; + spacepad = 0; + if (!lhs) vpush(&mdyntoksbuf, *rhs); + else if (!rhs) vpush(&mdyntoksbuf, *lhs); + else { + struct token new; + if (tokpaste(lx, &new, lhs, rhs)) { + new.span.sl = tki->span.sl; + } + vpush(&mdyntoksbuf, new); + } + mdyntoksbuf.p[mdyntoksbuf.n-1].space = space; + } else if (tki->t != TKPPMACARG && tki->t != TKPPMACSTR) { /* regular token */ + if (tki->t == TKIDENT && mac->variadic) { + /* handle GNUC __VA_OPT__(...) */ + static internstr istr_vaopt; + if (!istr_vaopt) istr_vaopt = intern("__VA_OPT__"); + if (tki->name == istr_vaopt && i+2 < mac->rl.n && tki[1].t == '(') { + vaoptbal = 1; + vaoptskip = args[mac->nparam-1].n == 0; + ++i; /* skip open paren */ + continue; + } + } + if (vaoptbal) { + if (tki->t == '(') ++vaoptbal; + else if (tki->t == ')') { + /* skip closing paren of __VA_OPT__ invocation */ + if (--vaoptbal == 0) continue; + } + } + vpush(&mdyntoksbuf, *tki); + mdyntoksbuf.p[mdyntoksbuf.n-1].space |= spacepad; + spacepad = 0; + } else if (tki->t == TKPPMACARG) { + arg = &args[tki->argidx]; + if (arg->n == 0) { + spacepad = 1; + continue; + } + struct token *rl = argsbuf.p + arg->idx2; + int n = arg->n2; + bool skipfirst = 0; + if (i > 0 && tki[-1].t == TKPPCAT) { + /* skip first unexpanded token, was pasted */ + rl += arg->nfirstx; + n -= arg->nfirstx; + skipfirst = 1; + } + if (i < mac->rl.n-2 && tki[1].t == TKPPCAT) { + /* skip last unexpanded token, will be pasted */ + n -= arg->nlastx; + } + if (n > 0) { + vpushn(&mdyntoksbuf, rl, n); + if (!skipfirst) + /* the first token of the expanded body gets its space from the replacement list */ + mdyntoksbuf.p[mdyntoksbuf.n - n].space = tki->space | spacepad; + } + spacepad = 0; + } else { /* PPMACSTR */ + char tmp[200]; + struct wbuf buf = MEMBUF(tmp, sizeof tmp); + int n = 0; + + arg = &args[tki->argidx]; + // XXX this is wrong bc the string literal produced should be re-parsed later + // i.e. stringifying the token sequence '\n' should ultimately produce a + // string with an actual newline, not {'\\','n'} + Redo: + for (int i = 0; i < arg->n; ++i) { + struct token *tk = &argsbuf.p[arg->idx + i]; + if (i > 0 && tk->space) + n += bfmt(&buf, " "); + n += bfmt(&buf, "%tk", tk); + } + ioputc(&buf, 0); + if (buf.err) { + struct wbuf new = MEMBUF(alloc(lx->tmparena, n+1, 1), n+1); + assert(buf.buf == tmp); + memcpy(&buf, &new, sizeof buf); + goto Redo; + } + vpush(&mdyntoksbuf, ((struct token) { + .t = TKSTRLIT, + .wide = 0, + .space = tki->space | spacepad, + .s = buf.buf != tmp ? buf.buf : alloccopy(lx->tmparena, buf.buf, buf.len, 1), + .len = buf.len-1, + })); + spacepad = 0; + } + } + uint n = mdyntoksbuf.n - off; + + if (n) { + pushmacstk(lx, span, &(struct macrostack){ + .rl = { .off = off, .n = n }, + .macid = mac->id, + .dyn = 1, + }); + } + } else if (mac->rl.n) { + pushmacstk(lx, span, &(struct macrostack){ + .rl = { .off = mac->rl.off, .n = mac->rl.n }, + .macid = mac->id, + }); + } + vfree(&argsbuf); +} + +static struct token epeektk; +static int +elex(struct lexer *lx, struct token *tk) +{ + assert(tk); + if (epeektk.t) { + int tt = epeektk.t; + if (tk) *tk = epeektk; + epeektk.t = 0; + return tt; + } + if (lx->macstk) { + if (!advancemacstk(lx, tk)) + return elex(lx, tk); + return tk->t; + } + + lex0(lx, tk, 0); + return tk->t; +} + +static int +epeek(struct lexer *lx, struct token *tk) +{ + if (!epeektk.t) elex(lx, &epeektk); + if (tk) *tk = epeektk; + return epeektk.t; +} + +static int +tkprec(int tt) +{ + static const char tab[] = { + ['*'] = 12, ['/'] = 12, ['%'] = 12, + ['+'] = 11, ['-'] = 11, + [TKSHL] = 10, [TKSHR] = 10, + ['<'] = 9, ['>'] = 9, [TKLTE] = 9, [TKGTE] = 9, + [TKEQU] = 8, [TKNEQ] = 8, + ['&'] = 7, + ['^'] = 6, + ['|'] = 5, + [TKLOGAND] = 4, + [TKLOGIOR] = 3, + ['?'] = 2, + }; + if ((uint)tt < countof(tab)) + return tab[tt] - 1; + return -1; +} + +static vlong +expr(struct lexer *lx, bool *pu, int prec, bool ignore) +{ + struct token tk; + enum typetag ty; + char unops[16]; + int nunop = 0; + vlong x, y; + bool xu = 0, yu; /* x unsigned?; y unsigned? */ + +Unary: + elex(lx, &tk); +Switch: + switch (tk.t) { + case '-': case '~': case '!': + unops[nunop++] = tk.t; + if (nunop >= countof(unops)) { + x = expr(lx, &xu, 999, ignore); + break; + } + /* fallthru */ + case '+': goto Unary; + case '(': + x = expr(lx, &xu, 1, ignore); + if (elex(lx, &tk) != ')') { + error(&tk.span, "expected ')'"); + goto Err; + } + break; + case TKNUMLIT: + case TKCHRLIT: + ty = parsenumlit((uvlong *)&x, NULL, &tk, 1); + if (!ty) { + error(&tk.span, "bad number literal"); + goto Err; + } else if (isfltt(ty)) { + error(&tk.span, "float literal in preprocessor expresion"); + goto Err; + } + xu = isunsignedt(ty); + break; + default: + if (tk.t == TKIDENT) { + xu = 0; + if (!strcmp(tk.s, "defined")) { + /* 'defined' ppident */ + bool paren = 0; + lex0(lx, &tk, 0); + if ((paren = tk.t == '(')) lex0(lx, &tk, 0); + if (!isppident(tk)) { + error(&tk.span, "expected macro name"); + goto Err; + } + if (paren && lex0(lx, &tk, 0) != ')') { + error(&tk.span, "expected `)'"); + goto Err; + } + x = findmac(tk.name) != NULL; + } else { + switch (tryexpand(lx, &tk)) { + case EXPSTACK: goto Unary; + case EXPINL: goto Switch; + case EXPNONE: x = 0; break; /* non defined pp name -> 0 */ + } + } + break; + } + error(&tk.span, "expected preprocessor integer expression (near %'tk)", &tk); + goto Err; + } + + while (nunop > 0) switch (unops[--nunop]) { + case '-': x = -(uvlong)x; break; + case '~': x = ~x; break; + case '!': x = !x; break; + default: assert(0); + } + + for (int opprec; (opprec = tkprec(epeek(lx, &tk))) >= prec;) { + elex(lx, &tk); + if (tk.t == TKLOGAND) { + x = !!x & !!expr(lx, &yu, opprec+1, ignore || !x); + xu = 0; + } else if (tk.t == TKLOGIOR) { + x = !!x | !!expr(lx, &yu, opprec+1, ignore || x); + xu = 0; + } else if (tk.t == '?') { + struct span span = tk.span; + vlong m = expr(lx, &xu, 1, ignore || !x); + if (elex(lx, &tk) != ':') { + error(&tk.span, "expected ':'"); + note(&span, "to match conditional expression here"); + goto Err; + } + y = expr(lx, &yu, 1, ignore || x); + x = x ? m : y; + xu |= yu; + } else { + y = expr(lx, &yu, opprec + 1, ignore); + bool u = xu | yu; + switch ((int) tk.t) { + case '+': x += (uvlong) y; break; + case '-': x -= (uvlong) y; break; + case '*': x = u ? (uvlong) x * y : x * y; break; + case '&': x &= y; break; + case '^': x ^= y; break; + case '|': x |= y; break; + case '/': if (y) x = u ? (uvlong) x / y : x / y; + else if (ignore) x = 0; + else goto Div0; + break; + case '%': if (y) x = u ? (uvlong) x % y : x % y; + else if (ignore) x = 0; + else Div0: error(&tk.span, "division by zero"); + break; + case TKSHL: if ((uvlong)y < 64) x <<= y; + else if (ignore) x = 0; + else goto BadShift; + break; + u = xu; + case TKSHR: if ((uvlong)y < 64) x = u ? (uvlong) x >> y : x >> y; + else if (ignore) x = 0; + else BadShift: error(&tk.span, "bad shift by %ld", y); + u = xu; + break; + case '<': x = u ? (uvlong) x < y : x < y; u = 0; break; + case '>': x = u ? (uvlong) x > y : x > y; u = 0; break; + case TKLTE: x = u ? (uvlong) x <= y : x <= y; u = 0; break; + case TKGTE: x = u ? (uvlong) x >= y : x >= y; u = 0; break; + case TKEQU: x = x == y; u = 0; break; + case TKNEQ: x = x != y; u = 0; break; + default: assert(0); + } + xu = u; + } + } + if (!prec) { /* not a sub expr */ + if (elex(lx, &tk) != '\n' && tk.t != TKEOF) { + error(&tk.span, "extra tokens after preprocessor expression"); + ppskipline(lx); + } + } + if (pu) *pu = xu; + return x; + +Err: + ppskipline(lx); + if (pu) *pu = xu; + return 0; +} + +enum { + PPCNDFALSE, /* the condition was zero, skip until #else/#elif */ + PPCNDTRUE, /* the condition was non-zero, emit until #else/#elif */ + PPCNDTAKEN /* some branch was already taken, skip until #else */ +}; +static struct ppcnd { + struct span0 ifspan; + int filedepth; + uchar cnd; + bool elsep; +} ppcndstk[32]; +static int nppcnd; + +static int includedepth; + +static void +ppif(struct lexer *lx, const struct span *span) +{ + vlong v = expr(lx, NULL, 0, 0); + assert(nppcnd < countof(ppcndstk) && "too many nested #if"); + ppcndstk[nppcnd].ifspan = span->sl; + ppcndstk[nppcnd].filedepth = includedepth; + ppcndstk[nppcnd].cnd = v ? PPCNDTRUE : PPCNDFALSE; + ppcndstk[nppcnd++].elsep = 0; +} + +static void +ppifxdef(struct lexer *lx, bool defp, const struct span *span) +{ + struct token tk; + + lex0(lx, &tk, 0); + if (tk.t != TKIDENT) { + error(&tk.span, "macro name missing"); + ppskipline(lx); + return; + } + expecteol(lx, defp ? "ifdef" : "ifndef"); + if (!defp && lx->firstdirective) lx->inclguard = tk.name; + assert(nppcnd < countof(ppcndstk) && "too many nested #if"); + ppcndstk[nppcnd].ifspan = span->sl; + ppcndstk[nppcnd].filedepth = includedepth; + ppcndstk[nppcnd].cnd = (findmac(tk.name) == NULL) ^ defp ? PPCNDTRUE : PPCNDFALSE; + ppcndstk[nppcnd++].elsep = 0; +} + +static void +ppelif(struct lexer *lx, const struct span *span) +{ + vlong v; + struct ppcnd *cnd; + + if (!nppcnd) { + error(span, "#elif without matching #if"); + ppif(lx, span); + return; + } + v = expr(lx, NULL, 0, 0); + cnd = &ppcndstk[nppcnd-1]; + if (cnd->elsep) { + error(span, "#elif after #else"); + return; + } + switch (cnd->cnd) { + case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break; + case PPCNDFALSE: cnd->cnd = v ? PPCNDTRUE : PPCNDFALSE; break; + } +} +static void +ppelifxdef(struct lexer *lx, bool defp, const struct span *span) +{ + struct token tk; + struct ppcnd *cnd; + + if (!nppcnd) { + error(span, "#elif%sdef without matching #if", &"n"[defp]); + ppif(lx, span); + return; + } + cnd = &ppcndstk[nppcnd-1]; + if (cnd->elsep) { + error(span, "#elif%sdef after #else", &"n"[defp]); + return; + } + lex0(lx, &tk, 0); + if (tk.t != TKIDENT) { + error(&tk.span, "macro name missing"); + ppskipline(lx); + return; + } + expecteol(lx, defp ? "elifdef" : "elifndef"); + switch (cnd->cnd) { + case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break; + case PPCNDFALSE: cnd->cnd = (findmac(tk.name) == NULL) ^ defp ? PPCNDTRUE : PPCNDFALSE; break; + case PPCNDTAKEN: assert(0); + } +} + +static void +ppendif(struct lexer *lx, const struct span *span) +{ + expecteol(lx, "endif"); + if (!nppcnd) { + error(span, "#endif without matching #if"); + return; + } + --nppcnd; +} + +static void +ppelse(struct lexer *lx, const struct span *span) +{ + struct ppcnd *cnd; + expecteol(lx, "else"); + if (!nppcnd) { + error(span, "#else without matching #if"); + return; + } + cnd = &ppcndstk[nppcnd-1]; + if (cnd->elsep) + error(span, "#else after #else"); + switch (cnd->cnd) { + case PPCNDFALSE: cnd->cnd = PPCNDTRUE; break; + case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break; + } + cnd->elsep = 1; +} + +enum { MAXINCLUDE = 200 }; +static bool +tryincludepath(struct lexer *lx, const struct span *span, char *path) +{ + struct lexer new; + const char *err; + switch (initlexer(&new, &err, path)) { + default: assert(0); + case LXERR: return 0; + case LXFILESEEN: + xbfree(path); + /* fallthru */ + case LXOK: + new.save = xmalloc(sizeof *new.save); + lx->inclnerror = nerror; + lx->inclnwarn = nwarn; + memcpy(new.save, lx, sizeof *lx); + *lx = new; + + if (++includedepth == MAXINCLUDE) + lxfatal(lx, span, "Maximum nested include depth of %d reached", includedepth); + break; + case LXFILESKIP: + xbfree(path); + break; + } + return 1; +} + +static bool +doinclude(struct lexer *lx, const struct span *span, bool quote, const char *str, size_t slen) +{ + char *path = NULL; + const char *base, *end; + if (quote) { + if (str[0] == '/') { + /* try absolute path */ + xbgrow(&path, slen + 1); + memcpy(path, str, slen); + path[slen] = 0; + if (tryincludepath(lx, span, path)) return 1; + goto NotFound; + } + + /* try relative to current file's directory */ + base = getfilename(lx->fileid, 0); + for (end = base; *end != 0; ++end) {} + for (--end; *end != '/' && end != base; --end) {} + if (*end == '/') ++end; + xbgrow(&path, end - base + slen + 1); + memcpy(path, base, end - base); + memcpy(path + (end - base), str, slen); + path[end - base + slen] = 0; + if (tryincludepath(lx, span, path)) return 1; + } + /* try system paths. order: + * 1. -iquote + * 2. -I + * 3. -isystem + * 4. embedded include files + * 5. standard system includes + * 6. -idirafter + */ + for (int i = quote ? CINCL_iquote : CINCL_I; i < countof(cinclpaths); ++i) { + for (struct inclpath *p = cinclpaths[i].list; p; p = p->next) { + if (i == CINCLsys) { + /* try embedded files pseudo-path */ + xbgrow(&path, slen + 3); + path[0] = '@', path[1] = ':'; + memcpy(path+2, str, slen); + path[slen+2] = 0; + if (tryincludepath(lx, span, path)) return 1; + } + int ndir = strlen(p->path); + xbgrow(&path, ndir + slen + 2); + memcpy(path, p->path, ndir); + path[ndir++] = '/'; + memcpy(path + ndir, str, slen); + path[ndir + slen] = 0; + if (tryincludepath(lx, span, path)) return 1; + } + } +NotFound: + error(span, "file not found: %'S", str, slen); + xbfree(path); + return 0; +} + +static bool +ppinclude(struct lexer *lx, const struct span *span0) +{ + struct token tk; + struct span span = *span0; + + if (in_range(lex0(lx, &tk, 1), TKPPHDRH, TKPPHDRQ)) { + expecteol(lx, "include"); + joinspan(&span.ex, tk.span.ex); + return doinclude(lx, &span, tk.t == TKPPHDRQ, tk.s, tk.len); + } else if (tk.t == '\n' || tk.t == TKEOF) { + goto BadSyntax; + } else { + /* '#include pp-tokens' + * gather and expand pp-tokens */ + struct token tksbuf[8]; + vec_of(struct token) tks = VINIT(tksbuf, countof(tksbuf)); + for (;;) { + if (!lx->macstk) { + if (tryexpand(lx, &tk) == EXPSTACK) continue; + vpush(&tks, tk); + } else if (advancemacstk(lx, &tk)) { + vpush(&tks, tk); + continue; + } + if (lex0(lx, &tk, 0) == '\n' || tk.t == TKEOF) break; + } + if (tks.n >= 1 && tks.p[0].t == TKSTRLIT) { /* "header.h" */ + if (tks.n > 1) + (ccopt.pedant ? error : warn)(&tks.p[1].span, "extra tokens after #include"); + joinspan(&span.ex, tks.p[0].span.ex); + return doinclude(lx, &span, 1, tks.p[0].s, tks.p[0].len); + } else if (tks.n > 2 && tks.p[0].t == '<' && tks.p[tks.n-1].t == '>') { /* <header.h> */ + /* this is multiple tokens, concatenate them together */ + char buf[4096]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + for (int i = 1; i < tks.n-1; ++i) { + struct token *tk = &tks.p[i]; + bfmt(&wbuf, &" %tk"[!tk->space], tk); + } + joinspan(&span.ex, tks.p[tks.n-1].span.ex); + if (wbuf.err) error(&span, "path too long"); + else { + return doinclude(lx, &span, 0, buf, wbuf.len); + } + } else { + BadSyntax: + error(&tk.span, "expected \"header\" or <header>"); + ppskipline(lx); + } + vfree(&tks); + } + return 1; +} + +static void +ppline(struct lexer *lx, struct token *tk0) +{ + struct token tk, tks[2]; + int ntk = 0; + struct span span = tk0->span; + bool ext = 0; + if (tk0->t == TKNUMLIT) { /* handles GNU-style post preprocessing directive '# n ...' */ + tks[ntk++] = *tk0; + ext = 1; + } + while (ntk < 2) { + if (lx->macstk && advancemacstk(lx, &tk)) { + tks[ntk++] = tk; + if (lx->macstk->idx >= lx->macstk->rl.n) popmac(lx, 1); + } else if (!lx->macstk && (lex0(lx, &tk, 0) == '\n' || tk.t == TKEOF)) { + break; + } else if (tk.t == TKIDENT && tryexpand(lx, &tk) == EXPSTACK) { + continue; + } else { + tks[ntk++] = tk; + } + } + uvlong lineno = 0; + char *file = NULL; + if (ntk > 0 && tks[0].t == TKNUMLIT) { + if (!parsenumlit(&lineno, NULL, &tks[0], 1) || (lineno == 0 && !ext)) + goto BadNum; + if (lineno >= 1<<(32-SPANFILEBITS)) { + warn(&tks[0].span, "ignoring #line number that is too big"); + lineno = 0; + goto Err; + } + } else { + BadNum: + error(ntk ? &tks[0].span : &span, "#line requires a positive integer argument"); + Err: + if (lx->macstk || (tk.t != '\n' && tk.t != TKEOF)) ppskipline(lx); + return; + } + if (ntk > 1) { + if (tks[1].t == TKSTRLIT && !tks[1].wide) { + file = alloc(&globarena, tks[1].len+1, 0); + memcpy(file, tks[1].s, tks[1].len); + file[tks[1].len] = 0; + } else { + error(&tks[1].span, "invalid filename for #line directive"); + } + } + if (lineno) setfileline(lx->fileid, lx->chridx, lineno, file); + if (lx->macstk) { + span.sl.off = span.ex.off = lx->chridx; + span.sl.len = span.ex.len = 1; + ppskipline(lx); + if (!ext) + (ccopt.pedant ? error : warn)(&span, "extra tokens after #line"); + } else if (tk.t != '\n' && tk.t != TKEOF) { + if (ext) ppskipline(lx); + else expecteol(lx, "line"); + } +} + +static void +pppragma(struct lexer *lx, const struct span *span0) +{ + struct token tk; + struct span span = *span0; + if (lex0(lx, &tk, 0) == TKIDENT && !strcmp(tk.s, "once")) { + markfileonce(lx->fileid, NULL); + } else { + joinspan(&span.ex, tk.span.ex); + warn(&span, "unknown pragma ignored"); + ppskipline(lx); + return; + } + expecteol(lx, "pragma"); +} + +static void +ppdiag(struct lexer *lx, const struct span *span0, bool err) +{ + const uchar *p = getfile(lx->fileid)->p; + uint off = lx->chridx, end; + ppskipline(lx); + end = lx->chridx; + while (off < end && aisspace(p[off])) ++off; + (err ? error : warn)(span0, "%S", p + off, end - off); +} + +enum directive { + PPXXX, + /* !sorted */ + PPDEFINE, + PPELIF, + PPELIFDEF, + PPELIFNDEF, + PPELSE, + PPENDIF, + PPERROR, + PPIF, + PPIFDEF, + PPIFNDEF, + PPINCLUDE, + PPLINE, + PPPRAGMA, + PPUNDEF, + PPWARNING, +}; + +static enum directive +findppcmd(const struct token *tk) +{ + static const char *tab[] = { + /* !sorted */ + "define", + "elif", + "elifdef", + "elifndef", + "else", + "endif", + "error", + "if", + "ifdef", + "ifndef", + "include", + "line", + "pragma", + "undef", + "warning", + }; + int l = 0, h = countof(tab) - 1, i, cmp; + const char *s = tk->s; + + if (tk->t == TKWif) return PPIF; + if (tk->t == TKWelse) return PPELSE; + /* binary search over sorted array */ + while (l <= h) { + i = (l + h) / 2; + cmp = strcmp(tab[i], s); + if (cmp < 0) l = i + 1; + else if (cmp > 0) h = i - 1; + else return i + 1; + } + return PPXXX; +} + +static void +identkeyword(struct token *tk) +{ +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-braces" +#endif + static const struct { + const char *s; + struct kw { uchar t, cstd : 4, ext : 1; } kw; + const char *alias[2]; + } kwtab[] = { +#define _(kw, cstd, ...) { #kw, {TKW##kw, cstd}, __VA_ARGS__ }, +#include "keywords.def" +#undef _ + }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + static pmap_of(struct kw) kwmap; + if (!kwmap.v) { + pmap_init(&kwmap, 128); + for (int i = 0; i < countof(kwtab); ++i) { + struct kw kw = kwtab[i].kw; + /* allow future keywords but only if they begin with _ */ + if (kw.cstd <= ccopt.cstd || kwtab[i].s[0] == '_') { + kw.ext = kw.cstd > ccopt.cstd; + pmap_set(&kwmap, intern(kwtab[i].s), kw); + } + for (const char *const *palias = kwtab[i].alias, *const *end = palias+2; + palias != end && *palias; ++palias) + { + pmap_set(&kwmap, intern(*palias), kw); + } + } + } + struct kw *kw = pmap_get(&kwmap, tk->name); + if (kw) { + tk->t = kw->t; + tk->extwarn = kw->ext; + } +} + +int +lex(struct lexer *lx, struct token *tk_) +{ + struct token tkx[1], *tk; + int t; + +Begin: + assert(tk_ != &lx->peektok); + tk = tk_ ? tk_ : tkx; + if (lx->peektok.t) { + *tk = lx->peektok; + memset(&lx->peektok, 0, sizeof lx->peektok); + return tk->t; + } + + if (lx->macstk) { + if (!advancemacstk(lx, tk)) + goto Begin; + if (tk->t == TKIDENT) identkeyword(tk); + return tk->t; + } + bool linebegin = 1, + skip = nppcnd ? ppcndstk[nppcnd-1].cnd != PPCNDTRUE : 0, + inclerror = 0; /* set when #include header file not found: process other directives then abort */ + enum directive lastcmd = 0; + for (;;) { + while ((t = lex0(lx, tk, 0)) == '\n') linebegin = 1; + if (t == '#' && linebegin) { + if (lex0(lx, tk, 0) == '\n') { } + else if (tk->t == TKNUMLIT || tk->t == TKIDENT) { + lastcmd = tk->t == TKNUMLIT ? PPLINE : findppcmd(tk); + if (nppcnd == lx->nppcnd0) lx->inclguard = NULL; + if (!skip) { + switch (lastcmd) { + case PPXXX: goto BadPP; + case PPDEFINE: ppdefine(lx); break; + case PPUNDEF: ppundef(lx); break; + case PPIF: ppif(lx, &tk->span); break; + case PPIFDEF: ppifxdef(lx, 1, &tk->span); break; + case PPIFNDEF: ppifxdef(lx, 0, &tk->span); break; + case PPELIF: ppelif(lx, &tk->span); break; + case PPELIFDEF: ppelifxdef(lx, 1, &tk->span); break; + case PPELIFNDEF: ppelifxdef(lx, 0, &tk->span); break; + case PPELSE: ppelse(lx, &tk->span); break; + case PPENDIF: ppendif(lx, &tk->span); break; + case PPLINE: ppline(lx, tk); break; + case PPPRAGMA: pppragma(lx, &tk->span); break; + case PPWARNING: ppdiag(lx, &tk->span, 0); break; + case PPERROR: ppdiag(lx, &tk->span, 1); break; + case PPINCLUDE: inclerror |= !ppinclude(lx, &tk->span); break; + default: assert(0&&"nyi"); + } + } else { + switch (lastcmd) { + case PPIF: /* increment nesting level */ + case PPIFDEF: + case PPIFNDEF: + assert(nppcnd < countof(ppcndstk) && "too many nested #if"); + ppcndstk[nppcnd].ifspan = tk->span.sl; + ppcndstk[nppcnd].cnd = PPCNDTAKEN; + ppcndstk[nppcnd++].elsep = 0; + break; + case PPELIF: ppelif(lx, &tk->span); break; + case PPELIFDEF: ppelifxdef(lx, 1, &tk->span); break; + case PPELIFNDEF: ppelifxdef(lx, 0, &tk->span); break; + case PPELSE: ppelse(lx, &tk->span); break; + case PPENDIF: ppendif(lx, &tk->span); break; + default: ppskipline(lx); break; + } + } + if (lastcmd != PPINCLUDE) + lx->firstdirective = 0; + skip = nppcnd ? ppcndstk[nppcnd-1].cnd != PPCNDTRUE : 0; + } else { + if (!skip) { + BadPP: + error(&tk->span, "invalid preprocessor directive"); + } + ppskipline(lx); + } + linebegin = 1; + } else { + lx->firstdirective = 0; + linebegin = 0; + if (skip && t != TKEOF) + continue; + if (tryexpand(lx, tk) == EXPSTACK) + goto Begin; + if (t == TKEOF && nppcnd && ppcndstk[nppcnd-1].filedepth == includedepth) { + struct span span = { ppcndstk[nppcnd-1].ifspan }; + error(&span, "#if is not matched by #endif"); + } + if (t == TKEOF && lx->save) { + /* end of #include'd file, restore previous state */ + if (lastcmd == PPENDIF && lx->inclguard) { + markfileonce(lx->fileid, lx->inclguard); + } + struct lexer *sv = lx->save; + if (sv->inclnerror != nerror || sv->inclnwarn != nwarn) { + int line; + const char *f = getfilepos(&line, NULL, sv->fileid, sv->chridx-2); + note(NULL, "in file included from %s:%d", f, line); + } + memcpy(lx, sv, sizeof *lx); + free(sv); + --includedepth; + linebegin = 1; + lx->firstdirective = 0; + } else if (t == TKEOF && inclerror) { + break; + } else { + if (nppcnd == lx->nppcnd0) lx->inclguard = NULL; + if (t == TKIDENT) identkeyword(tk); + if (!inclerror) return tk->t; + } + } + } + assert(inclerror); + efmt("Aborting due to previous error(s).\n"); + exit(1); + assert(0); +} + +int +lexpeek(struct lexer *lx, struct token *tk_) +{ + struct token tkx[1], *tk; + uint t; + + tk = tk_ ? tk_ : tkx; + if ((t = lx->peektok.t)) { + *tk = lx->peektok; + return t; + } + t = lex(lx, tk); + lx->peektok = *tk; + return t; +} + +/* Predefined/builtin macros */ + +static vec_of(uchar) ppcmdline; + +void +cpppredef(bool undef, const char *cmd) +{ + const char *sep = strchr(cmd, '='), *body = sep ? sep+1 : "1"; + uint namelen = sep ? sep - cmd : strlen(cmd); + char line[1024]; + struct wbuf wbuf = MEMBUF(line, sizeof line); + if (!ppcmdline.p) vinit(&ppcmdline, NULL, 1<<10); + int n; + if (undef) + n = bfmt(&wbuf, "#undef %S\n", cmd, namelen); + else + n = bfmt(&wbuf, "#define %S %s\n", cmd, namelen, body); + assert(n <= sizeof line); + vpushn(&ppcmdline, line, n); +} + +static void +mac__file__(struct lexer *lx, struct token *tk) +{ + tk->t = TKSTRLIT; + tk->s = getfilename(lx->fileid, lx->chridx); + tk->wide = 0; + tk->len = strlen(tk->s); +} + +static void +mac__line__(struct lexer *lx, struct token *tk) +{ + char buf[20]; + int line; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + getfilepos(&line, NULL, lx->fileid, lx->chridx); + bfmt(&wbuf, "%d", line), buf[wbuf.len++] = 0; + tk->t = TKNUMLIT; + tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1); + tk->len = wbuf.len-1; +} + +#include <time.h> + +static void +mac__date__(struct lexer *lx, struct token *tk) +{ + char buf[20]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + time_t tm = time(NULL); + struct tm *ts = localtime(&tm); + tk->t = TKSTRLIT; + tk->wide = 0; + tk->len = 11; + if (ts) { + bfmt(&wbuf, "%S %2d %4d%c", + &"JanFebMarAprMayJunJulAugSepOctNovDec"[ts->tm_mon*3], 3, + ts->tm_mday, 1900+ts->tm_year, 0); + assert(wbuf.len == 11+1); + tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1); + } else { + tk->s = "\?\?\? \?\? \?\?\?\?"; + } +} + +static void +mac__time__(struct lexer *lx, struct token *tk) +{ + char buf[20]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + time_t tm = time(NULL); + struct tm *ts = localtime(&tm); + tk->t = TKSTRLIT; + tk->wide = 0; + tk->len = 8; + if (ts) { + bfmt(&wbuf, "%.2d:%.2d:%.2d%c", ts->tm_hour, ts->tm_min, ts->tm_sec, 0); + tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1); + assert(wbuf.len == 8+1); + } else { + tk->s = "\?\?:\?\?:\?\?"; + } +} + +static void +mac__counter__(struct lexer *lx, struct token *tk) +{ + char buf[20]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + static int counter; + bfmt(&wbuf, "%d", counter++), buf[wbuf.len++] = 0; + tk->t = TKNUMLIT; + tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1); + tk->len = wbuf.len-1; +} + +static void +mac__has_builtin(struct lexer *lx, struct token *tk, const struct token *args, int narg) +{ + extern bool hasbuiltin(const char *, uint n); + bool has = 0; + tk->t = TKNUMLIT, tk->len = 1; + if (narg >= 1) { + if (args[0].t == TKIDENT) + has = hasbuiltin(args[0].s, args[0].len); + else if (in_range(args[0].t, TKWBEGIN_, TKWEND_)) + has = args[0].len >= sizeof "__builtin_" && !memcmp(args[0].s, "__builtin_", 10); + else goto Bad; + if (narg != 1) + error(&args[1].span, "expected `)' after '%tk'", &args[0]); + } else Bad: { + error(narg ? &args[0].span : &tk->span, "'__has_builtin' requires an identifier"); + } + tk->s = &"01"[has]; +} + + +static void +putdef1(const char *name) +{ + static const struct token tok_1 = { TKNUMLIT, .s = "1", .len = 1, .litlit = 1 }; + putmac(intern(name), &(struct macro) { + .predef = 1, + .single = &tok_1 + }); +} + +static void +putdefs1(const char *s) +{ + for (; *s; s += strlen(s) + 1) putdef1(s); +} + +static void +addpredefmacros(struct arena **tmparena) +{ + static struct token tok_stdc = {TKNUMLIT}, + tok_major = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_MAJOR), + .len = sizeof XSTR(ANTCC_VERSION_MAJOR) - 1}, + tok_minor = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_MINOR), + .len = sizeof XSTR(ANTCC_VERSION_MINOR) - 1}, + tok_patch = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_PATCH), + .len = sizeof XSTR(ANTCC_VERSION_PATCH) - 1}; + static struct { const char *name; struct macro m; } macs[] = { + { "__FILE__", { .predef = 1, .special = 1, .handler = mac__file__ }}, + { "__LINE__", { .predef = 1, .special = 1, .handler = mac__line__ }}, + { "__DATE__", { .predef = 1, .special = 1, .handler = mac__date__ }}, + { "__TIME__", { .predef = 1, .special = 1, .handler = mac__time__ }}, + { "__COUNTER__", { .predef = 1, .special = 1, .handler = mac__counter__ }}, + { "__has_builtin", { .predef = 1, .nparam = 1, .fnlike = 1, .special = 1, .handlerfn = mac__has_builtin }}, + { "__STDC_VERSION__", { .predef = 1, .single = &tok_stdc }}, + { "__antcc_major__", { .predef = 1, .single = &tok_major }}, + { "__antcc_minor__", { .predef = 1, .single = &tok_minor }}, + { "__antcc_patch__", { .predef = 1, .single = &tok_patch }}, + { "__extension__", { .predef = 1, .single = NULL }}, + }; + static const char + cpredefs[] = + "__antcc__\0__STDC__\0__STDC_NO_ATOMICS__\0__STDC_NO_COMPLEX__\0__STDC_NO_THREADS__\0__STDC_NO_VLA__\0", + *ospredefs[] = { + [OSlinux] = "__linux\0__linux__\0linux\0unix\0__unix\0__unix__\0" + }, *archpredefs[] = { + [ISx86_64] = "__x86_64__\0__x86_64\0", + [ISaarch64] = "__aarch64__\0__aarch64\0", + }, cstdver[][8] = { + [STDC89] = "199409L", + [STDC99] = "199901L", + [STDC11] = "201112L", + [STDC23] = "202311L", + }; + + tok_stdc.s = cstdver[ccopt.cstd]; + tok_stdc.len = 7; + + for (int i = 0; i < countof(macs); ++i) + putmac(intern(macs[i].name), &macs[i].m); + putdefs1(cpredefs); + if (target.os != OSunknown) putdef1("__STDC_HOSTED__"); + putdefs1(ospredefs[target.os]); + putdefs1(archpredefs[target.arch]); + + if (ppcmdline.n) { + struct memfile *f; + struct lexer lx[1] = {0}; + lx->fileid = getpredeffile(&f, "<command line>"); + assert(!f->p); + lx->ndat = f->n = ppcmdline.n; + vpushn(&ppcmdline, "\0\0\0\0\0\0", 6); + lx->dat = f->p = ppcmdline.p; + lx->tmparena = tmparena; + lx->chrbuf0 = countof(lx->chrbuf); + lx->firstdirective = 1; + while (lex(lx, NULL) != TKEOF) ; + } +} + +enum initlexer +initlexer(struct lexer *lx, const char **err, const char *file) +{ + enum { NARENA = 1<<12 }; + static union { char m[sizeof(struct arena) + NARENA]; struct arena *_align; } amem; + static struct arena *tmparena = (void *)amem.m; + + if (!tmparena->cap) tmparena->cap = NARENA; + if (!mtoksbuf.p) vinit(&mtoksbuf, NULL, 1024); + if (!mdyntoksbuf.p) vinit(&mdyntoksbuf, NULL, 256); + if (!macroht.v) addpredefmacros(&tmparena); + + struct memfile *f; + int fileid = openfile(err, &f, file); + if (fileid < 0) + return LXERR; + internstr guard; + if (isfileseen(fileid) && isoncefile(fileid, &guard) && (!guard || findmac(guard))) { + //efmt("skipping %s .. guard %s\n", file, guard ? guard : "<none>"); + return LXFILESKIP; + } + memset(lx, 0, sizeof *lx); + lx->fileid = fileid; + markfileseen(fileid); + + lx->dat = f->p; + lx->ndat = f->n; + lx->tmparena = &tmparena; + lx->chrbuf0 = countof(lx->chrbuf); + lx->firstdirective = 1; + lx->nppcnd0 = nppcnd; + return getfilename(fileid, 0) != file ? LXFILESEEN : LXOK; +} + +/* callback to let lexer release temp memory for arena allocated token data */ +void +lexerfreetemps(struct lexer *lx) +{ + if (!lx->macstk) { + /* some of the tokens could be somewhere in the macro stack */ + freearena(lx->tmparena); + } +} + +void +lexerdump(struct lexer *lx, struct wbuf *out) +{ + struct token prev = {0}, tok; + int file = lx->fileid, line = 1, col = 1; + const char *lastfile = getfilename(file, 0); + bfmt(out, "# %d %'s\n", 1, lastfile); + while (lex(lx, &tok) != TKEOF) { + int tkline, tkcol; + const char *fname = getfilepos(&tkline, &tkcol, tok.span.ex.file, tok.span.ex.off); + if (tok.span.ex.file != file || fname != lastfile) { + file = tok.span.ex.file; + bfmt(out, "\n# %d %'s\n", tkline, fname); + col = 1; + lexerfreetemps(lx); + lastfile = fname; + } else if (line < tkline && tkline - line < 5) { + do + ioputc(out, '\n'); + while (++line != tkline); + col = 1; + } else if (line != tkline) { + bfmt(out, "\n# %d\n", tkline); + line = tkline; + col = 1; + lexerfreetemps(lx); + } else if (prev.t && (tok.space || tokpaste(lx, NULL, &prev, &tok))) { + /* preserve whitespace & paste avoidance */ + ioputc(out, ' '); + ++col; + } + if (col == 1) + for (; col < tkcol; ++col) + ioputc(out, ' '); + line = tkline; + bfmt(out, "%tk", &tok); + col += tok.span.ex.len; + prev = tok; + } + bfmt(out, "\n"); + ioflush(out); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_lex.h b/src/c_lex.h new file mode 100644 index 0000000..e70bc78 --- /dev/null +++ b/src/c_lex.h @@ -0,0 +1,126 @@ +#include "../common.h" +#include "../type.h" + +static inline bool +joinspan(struct span0 *dst, struct span0 snd) +{ + if (dst->file != snd.file) return 0; + if (dst->off > snd.off) return 0; + dst->len = snd.off + snd.len - dst->off; + return 1; +} + +enum toktag { /* single-character tokens' tag value is the character itself */ + TKEOF = 0xFF, + TKXXX = 0, + TKNUMLIT, + TKCHRLIT, + TKSTRLIT, + TKPPHDRH, /* <hdr> (for #include) */ + TKPPHDRQ, /* "hdr" (for #include) */ + TKPPMACARG, /* macro param, in repl list */ + TKPPMACSTR, /* stringify macro param, in repl list */ + TKEQU = '@', /* == */ + TKNEQ, /* != */ + TKLTE, /* <= */ + TKGTE, /* >= */ + TKSHR, /* >> */ + TKSHL, /* << */ + TKINC, /* ++ */ + TKDEC, /* -- */ + TKDOTS, /* ... */ + TKARROW, /* -> */ + TKPPCAT, /* ## */ + TKLOGAND, /* && */ + TKLOGIOR, /* || */ + TKSETADD, /* += */ + TKSETSUB, /* -= */ + TKSETMUL, /* *= */ + TKSETDIV, /* /= */ + TKSETREM, /* %= */ + TKSETIOR, /* |= */ + TKSETXOR, /* ^= */ + TKSETAND, /* &= */ + TKSETSHL, /* <<= */ + TKSETSHR, /* >>= */ + TKIDENT = 0x80, +#define _(kw, stdc, ...) TKW##kw, +#include "keywords.def" +#undef _ + NTOKTAG, +}; +static_assert(NTOKTAG < 256); + +struct token { + uchar t; /* toktag */ + bool litlit : 1, + blue : 1, /* preprocessor token painted blue */ + extwarn : 1; /* warn this keyword token is an extension */ + uchar wide : 2, /* for CHRLIT & STRLIT; 1 -> 16bit, 2 -> 32bit */ + wideuni : 1, /* ditto, 0 -> 'L', 1 -> 'u'/'U' (C11) */ + space : 1; /* preceded by whitespace? */ + union { + uint len; + ushort argidx; + }; + struct span span; + union { + internstr name; + const char *s; + const ushort *ws16; + const uint *ws32; + }; + /* for (multi-)character tokens s & len are unused + * for keywords, s is constant cstring, len = strlen(s) + * for idents, s is interned cstring, len = strlen(s) + * for strlit and chrlit: + * when litlit : s points to start of string within file buffer (after the ") + * len == span.sl.len - 2 (string data appears literally in source code) + * otherwise s is heap allocated buffer of len bytes + * when wide, litlit = 0 and use ws16/ws32 + * for numlit: + * when litlit : s points to start of token within file buffer (normal case) + * len == span.sl.len (number literal appears literally in source code) + * otherwise s is heap allocated buffer of len bytes + * for macro arg/stringify: + * s is like keyword/ident + * argidx is index in macro param list, + * macidx is macro id of which it is a parameter + */ +}; + +extern int nerror, nwarn; +struct lexer { + struct lexer *save; + short fileid; + const uchar *dat; + uint ndat; + uint idx, chridx; + ushort chrbuf0; + struct macrostack *macstk; + struct token peektok; + bool eof, err; + struct arena **tmparena; + bool firstdirective; + short nppcnd0; + short inclnerror, inclnwarn; + internstr inclguard; + uchar chrbuf[1<<10]; + uint chridxbuf[1<<10]; +}; + +enum initlexer { + LXOK, + LXFILESEEN, + LXFILESKIP, + LXERR, +}; + +int lex(struct lexer *, struct token *); +int lexpeek(struct lexer *, struct token *); +enum typetag parsenumlit(uvlong *, double *, const struct token *, bool ispp); +enum initlexer initlexer(struct lexer *, const char **err, const char *file); +void lexerdump(struct lexer *, struct wbuf *out); +void lexerfreetemps(struct lexer *); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_type.c b/src/c_type.c new file mode 100644 index 0000000..e8a5b1e --- /dev/null +++ b/src/c_type.c @@ -0,0 +1,311 @@ +#include "type.h" + +struct typedata typedata[1<<13]; +internstr ttypenames[1<<10]; + +static ushort +hashtd(const struct typedata *td) +{ + uint h = td->t*33; + bool t; + switch (td->t) { + case TYARRAY: + h = hashb(h, &td->arrlen, sizeof td->arrlen); + /* fallthru */ + case TYPTR: + h = hashb(h, &td->child, sizeof td->child); + break; + case TYSTRUCT: + case TYUNION: + case TYENUM: + h = hashb(h, &td->id, sizeof td->id); + break; + case TYFUNC: + h = hashb(h, &td->ret, sizeof td->ret); + h = hashb(h, &td->nmemb, sizeof td->nmemb); + h = hashb(h, (t = td->kandr, &t), sizeof t); + h = hashb(h, (t = td->variadic, &t), sizeof t); + for (int i = 0; i < td->nmemb; ++i) { + h = hashb(h, &td->param[i], sizeof *td->param); + } + break; + default: + assert(0 && "bad typedata tag"); + } + return h ^ h>>16; +} + +static bool +tdequ(const struct typedata *a, const struct typedata *b) +{ + if (a->t != b->t) return 0; + switch (a->t) { + case TYARRAY: + return a->arrlen == b->arrlen && a->child.bits == b->child.bits; + case TYPTR: + return a->child.bits == b->child.bits; + case TYSTRUCT: + case TYUNION: + case TYENUM: + return a->id == b->id; + case TYFUNC: + if (a->ret.bits != b->ret.bits) return 0; + if (a->nmemb != b->nmemb) return 0; + if (a->variadic != b->variadic) return 0; + if (a->kandr != b->kandr) return 0; + for (int i = 0; i < a->nmemb; ++i) { + if (a->param[i].bits != b->param[i].bits) + return 0; + } + return 1; + default: + assert(0 && "bad typedata tag"); + } +} + +static ushort +interntd(const struct typedata *td) +{ + uint h, i, n = countof(typedata); + for (i = h = hashtd(td); n--; ++i) { + struct typedata *slot = &typedata[i &= countof(typedata) - 1]; + if (!slot->t) { + uint nmemb; + static struct arena *datarena; + if (!datarena) { + enum { N = 1<<12 }; + static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem; + datarena = (void *)amem.m, datarena->cap = N; + } + + Copy: + nmemb = td->nmemb; + *slot = *td; + switch (slot->t) { + case TYENUM: + if (slot->var) + slot->var = alloccopy(&datarena, td->var, nmemb * sizeof *slot->var, 0); + break; + case TYSTRUCT: + case TYUNION: + if (slot->fld) + slot->fld = alloccopy(&datarena, td->fld, nmemb * sizeof *slot->fld, 0); + break; + case TYFUNC: + if (slot->param) + slot->param = alloccopy(&datarena, td->param, nmemb * sizeof *slot->param, 0); + } + return i; + } else if (tdequ(slot, td)) { + if (td->t == TYSTRUCT || td->t == TYUNION || td->t == TYENUM) + goto Copy; + return i; + } + } + assert(0 && "typedata[] full"); +} + +bool +isincomplete(union type t) +{ + switch (t.t) { + case TYVOID: return 1; + case TYARRAY: return !typearrlen(t); + case TYSTRUCT: + case TYUNION: + return typedata[t.dat].nmemb == 0; + case TYENUM: + return !typedata[t.dat].backing; + } + return 0; +} + +uint +typesize(union type t) +{ + if (isprim(t) || t.t == TYPTR) return targ_primsizes[t.t]; + switch (t.t) { + case TYENUM: + return targ_primsizes[typedata[t.dat].backing]; + case TYARRAY: + if (t.flag & TFCHLDPRIM) + return targ_primsizes[t.child] * t.arrlen; + /* fallthru */ + case TYSTRUCT: + case TYUNION: + return typedata[t.dat].siz; + } + return 0; +} + +uint +typealign(union type t) +{ + if (isprim(t) || t.t == TYPTR) return targ_primalign[t.t]; + switch (t.t) { + case TYENUM: + return targ_primalign[typedata[t.dat].backing]; + case TYARRAY: + return typealign(typechild(t)); + case TYSTRUCT: + case TYUNION: + return typedata[t.dat].align; + } + return 0; +} + +union type +mkptrtype(union type t, int qual) +{ + if (isprim(t)) + return mktype(TYPTR, .flag = TFCHLDPRIM | (qual & TFCHLDQUAL), .child = t.t); + else if (t.t == TYENUM || t.t == TYFUNC || isagg(t)) + return mktype(TYPTR, .flag = TFCHLDISDAT | (qual & TFCHLDQUAL), .dat = t.dat); + return mktype(TYPTR, .flag = qual & TFCHLDQUAL, + .dat = interntd(&(struct typedata) { TYPTR, .child = t })); +} + +union type +mkarrtype(union type t, int qual, uint n) +{ + if (isprim(t) && n < 256) + return mktype(TYARRAY, .flag = TFCHLDPRIM | (qual & TFCHLDQUAL), .child = t.t, .arrlen = n); + return mktype(TYARRAY, .flag = qual & TFCHLDQUAL, + .dat = interntd(&(struct typedata) { TYARRAY, .child = t, .arrlen = n, .siz = n * typesize(t) })); +} + +union type +mkfntype(union type ret, uint n, const union type *par, bool kandr, bool variadic) +{ + struct typedata td = { TYFUNC, .ret = ret, .nmemb = n, .param = par }; + td.kandr = kandr, td.variadic = variadic; + return mktype(TYFUNC, .dat = interntd(&td)); +} + +union type +completetype(internstr name, int id, struct typedata *td) +{ + assert(td->t == TYENUM || td->t == TYSTRUCT || td->t == TYUNION); + td->id = id; + assert(id < countof(ttypenames) && "too many tag types"); + if (ttypenames[id]) + assert(ttypenames[id] == name && "bad redefn"); + else + ttypenames[id] = name; + return mktype(td->t, .dat = interntd(td), .backing = td->t == TYENUM ? td->backing : 0); +} + +union type +mktagtype(internstr name, struct typedata *td) +{ + static int id; + return completetype(name, id++, td); +} + +static bool +getfieldrec(struct fielddata *res, uint off, const struct typedata *td, internstr name) +{ +Begin: + for (int i = 0; i < td->nmemb; ++i) { + struct namedfield *fld = &td->fld[i]; + if (fld->name == name) { /* match */ + *res = fld->f; + res->off += off; + return 1; + } else if (!fld->name) { /* anonymous struct/union */ + const struct typedata *ftd = &typedata[fld->f.t.dat]; + assert(isagg(fld->f.t)); + if (i == td->nmemb - 1) { /* last field, tail recurse */ + off += fld->f.off; + td = ftd; + goto Begin; + } else if (getfieldrec(res, off + fld->f.off, ftd, name)) + return 1; + } + } + return 0; +} + +bool +getfield(struct fielddata *res, union type ty, internstr name) +{ + assert(isagg(ty)); + return getfieldrec(res, 0, &typedata[ty.dat], name); +} + +union type +typedecay(union type t) +{ + if (t.t == TYARRAY) + return mkptrtype(typechild(t), t.flag & TFCHLDQUAL); + if (t.t == TYFUNC) + return mkptrtype(t, 0); + return t; +} + +bool /* 6.5.16.1 Simple assignment Constraints */ +assigncompat(union type dst, union type src) +{ + if (dst.bits == src.bits) return 1; + if (isarith(dst) && isarith(src)) return 1; + if (dst.t == TYPTR && src.t == TYPTR) { + union type ds = typechild(dst), ss = typechild(src); + if (ds.bits == ss.bits) return 1; /* T* with different qualifiers */ + if (ss.t == TYVOID || ds.t == TYVOID) return 1; /* T* <-> void* */ + enum typetag dt = scalartypet(ds), /* handle enums */ + st = scalartypet(ss); + if (!dt || !st) return 0; /* unequal incomplete enums */ + /* plain char exception */ + if (st == TYCHAR && in_range(dt, TYUCHAR, TYSCHAR)) return 1; + if (dt == TYCHAR && in_range(st, TYUCHAR, TYSCHAR)) return 1; + } else if (dst.t == TYBOOL && src.t == TYPTR) + return 1; + return 0; +} + +enum typetag +intpromote(enum typetag t) +{ + static int intisshort = -1; + if (intisshort < 0) intisshort = targ_primsizes[TYINT] == targ_primsizes[TYSHORT]; + if (intisshort && t == TYUSHORT) return TYUINT; + return t < TYINT ? TYINT : t; +} + +union type /* 6.3.1.8 Usual arithmetic conversions */ +cvtarith(union type a, union type b) +{ + const union type none = {0}; + + if (!isarith(a) || !isarith(b)) return none; + if (a.t == TYENUM) a = typechild(a); + if (b.t == TYENUM) b = typechild(b); + if (isflt(a) || isflt(b)) { + /* when one type is float, choose type with greatest rank */ + /* enumeration order of type tags reflects arithmetic type rank */ + return a.t > b.t ? a : b; + } + a.t = intpromote(a.t); + b.t = intpromote(b.t); + if (a.bits == b.bits) return a; + + if (issigned(a) == issigned(b)) { + /* when both are integers with same signage, choose type with greatest rank */ + return a.t > b.t ? a : b; + } + /* if the signed type can represent all values of the unsigned type, + * choose it, otherwise choose its corresponding unsigned type */ + /* so long long + unsigned = long long; + * but long long + unsigned long = unsigned long long */ + if (issigned(a)) { + if (targ_primsizes[a.t] <= targ_primsizes[b.t]) + a.t += 1; /* make unsigned */ + return a.t > b.t ? a : b; + } else { + if (targ_primsizes[b.t] <= targ_primsizes[a.t]) + b.t += 1; /* make unsigned */ + return b.t > a.t ? b : a; + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/c_type.h b/src/c_type.h new file mode 100644 index 0000000..ad8e1b1 --- /dev/null +++ b/src/c_type.h @@ -0,0 +1,177 @@ +#ifndef TYPE_H_ +#define TYPE_H_ +#include "common.h" + +enum qualifier { + QCONST = 1<<0, + QVOLATILE = 1<<1, +}; + +enum typetag { /* ordering is important here! */ + TYXXX, + TYENUM, + TYBOOL, TYCHAR, TYSCHAR, TYUCHAR, TYSHORT, TYUSHORT, TYINT, TYUINT, TYLONG, TYULONG, TYVLONG, TYUVLONG, + TYFLOAT, TYDOUBLE, TYLDOUBLE, TYCOMPLEXF, TYCOMPLEX, TYCOMPLEXL, + TYVOID, + TYPTR, TYARRAY, TYFUNC, + TYSTRUCT, TYUNION, + NTYPETAG, + TYSIGNEDSET_ = 1<<TYSCHAR | 1<<TYSHORT | 1<<TYINT | 1<<TYLONG | 1<<TYVLONG, + TYUNSIGNEDSET_ = 1<<TYUCHAR | 1<<TYUSHORT | 1<<TYUINT | 1<<TYULONG | 1<<TYUVLONG, + TYSCALARSET_ = ((1u << (TYCOMPLEXL - TYENUM + 1)) - 1) << TYENUM | 1<<TYPTR +}; + +enum typeflagmask { + TFCHLDQUAL = 3, + TFCHLDPRIM = 1<<2, + TFCHLDISDAT = 1<<3, +}; + +union type { + struct { + uchar t; /* type tag */ + union { + uchar flag; + uchar backing; /* type tag for enum backing int */ + }; + union { + struct { + uchar child; /* prim type tag */ + uchar arrlen; /* small array */ + }; + ushort dat; /* index into typedata */ + }; + }; + uint bits; +}; +static_assert(sizeof(union type) == 4); + +#define isprimt(t) in_range((t), TYBOOL, TYVOID) +#define isintt(t) in_range((t), TYENUM, TYUVLONG) +#define issignedt(t) ((TYSIGNEDSET_ | targ_charsigned << TYCHAR) >> (t) & 1) +#define isunsignedt(t) ((TYUNSIGNEDSET_ | !targ_charsigned << TYCHAR) >> (t) & 1) +#define isfltt(t) in_range((t), TYFLOAT, TYLDOUBLE) +#define isaritht(t) in_range((t), TYENUM, TYCOMPLEXL) +#define isscalart(t) (TYSCALARSET_ >> (t) & 1) +#define isptrcvtt(t) in_range((t), TYPTR, TYFUNC) +#define isaggt(t) in_range((t), TYSTRUCT, TYUNION) +#define isprim(ty) isprimt((ty).t) +#define isint(ty) isintt((ty).t) +#define issigned(ty) issignedt(scalartypet(ty)) +#define isunsigned(ty) isunsignedt(scalartypet(ty)) +#define isflt(ty) isfltt((ty).t) +#define isarith(ty) isaritht((ty).t) +#define isscalar(ty) isscalart((ty).t) +#define isptrcvt(ty) isptrcvtt((ty).t) +#define isagg(ty) isaggt((ty).t) +#define iscomplext(t) in_range((t), TYCOMPLEXF, TYCOMPLEXL) +#define iscomplex(ty) iscomplext((ty).t) +#define mktype(...) ((union type) {{ __VA_ARGS__ }}) + +struct enumvar { + internstr name; + union { vlong i; uvlong u; }; +}; + +struct fielddata { + union type t; + ushort off; + uchar bitsiz, + bitoff : 6, + qual : 2; +}; +struct namedfield { + internstr name; + struct fielddata f; +}; + +struct typedata { + uchar t; + ushort id; + union { + union type child; + const union type *param; /* functions */ + struct { /* aggregates */ + struct namedfield *fld; + }; + struct { /* enum */ + uchar backing; + struct enumvar *var; + }; + }; + union { + uint arrlen; /* array */ + struct { + short nmemb; /* functions, aggregates, enums */ + uchar align; + union { + struct { /* function */ + bool kandr : 1, variadic : 1; + }; + struct { /* aggregate */ + bool anyconst : 1, flexi : 1; + }; + }; + }; + }; + union { + uint siz; /* aggregate & array */ + union type ret; /* function */ + }; +}; + +extern struct typedata typedata[]; +extern internstr ttypenames[/*id*/]; + +bool isincomplete(union type); +uint typesize(union type); +uint typealign(union type); +union type mkptrtype(union type, int qual); +union type mkarrtype(union type t, int qual, uint n); +union type mkfntype(union type ret, uint n, const union type *, bool kandr, bool variadic); +union type mktagtype(internstr name, struct typedata *td); +bool getfield(struct fielddata *res, union type, internstr); +union type completetype(internstr name, int id, struct typedata *td); +union type typedecay(union type); +bool assigncompat(union type dst, union type src); +enum typetag intpromote(enum typetag); +union type cvtarith(union type a, union type b); +static inline union type +typechild(union type t) +{ + if (t.t == TYENUM) return mktype(t.backing ? t.backing : typedata[t.dat].backing); + if (t.flag & TFCHLDPRIM) return mktype(t.child); + if (t.flag & TFCHLDISDAT) { + union type chld = mktype(typedata[t.dat].t, .dat = t.dat); + if (chld.t == TYENUM) chld.backing = typedata[t.dat].backing; + return chld; + } + return typedata[t.dat].child; +} +static inline enum typetag +scalartypet(union type t) +{ + if (t.t == TYENUM) return t.backing ? t.backing : typedata[t.dat].backing; + if (isptrcvt(t)) return TYPTR; + assert(!iscomplex(t)); + return t.t; +} +static inline uint +typearrlen(union type t) +{ + return (t.flag & TFCHLDPRIM) ? t.arrlen : typedata[t.dat].arrlen; +} + +/**********/ +/* Target */ +/**********/ + +extern uint targ_valistsize; +extern uchar targ_primsizes[]; +extern uchar targ_primalign[]; +extern enum typetag targ_sizetype, targ_ptrdifftype, targ_wchartype; +extern bool targ_charsigned, targ_bigendian, targ_64bit; + +#endif + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..d33afb1 --- /dev/null +++ b/src/io.c @@ -0,0 +1,1255 @@ +#include "c/lex.h" +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +struct wbuf bstdout, bstderr; + +void +ioinit(void) +{ + bstdout.fp = stdout; + bstdout.isfp = 1; + bstderr.fp = stderr; + bstderr.isfp = 1; +} + +void +iowrite(struct wbuf *buf, const void *Src, int n) +{ + const uchar *src = Src; + + if (buf->isfp) { + fwrite(Src, 1, n, buf->fp); + buf->err = ferror(buf->fp) != 0; + return; + } + while (n > 0) { + int avail = buf->cap - buf->len; + int amt = avail < n ? avail : n; + + memcpy(buf->buf + buf->len, src, amt); + n -= amt; + src += amt; + buf->len += amt; + if (n > 0 && buf->len == buf->cap) { + if (buf->fd < 0) { + buf->err = 1; + return; + } + ioflush(buf); + } + } +} + +void +ioflush(struct wbuf *buf) +{ + int i, ret; + + if (buf->isfp) { + fflush(buf->fp); + buf->err = ferror(buf->fp) != 0; + return; + } + buf->err = 0; + if (buf->fd < 0) { + buf->len = 0; + return; + } + for (i = 0; buf->len > 0;) { + ret = write(buf->fd, buf->buf + i, buf->len); + if (ret > 0) { + assert(ret <= buf->len); + buf->len -= ret; + i += ret; + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; + } else { + buf->err = 1; + break; + } + } +} + +void +ioputc(struct wbuf *buf, uchar c) +{ + if (buf->isfp) { + buf->err = fputc(c, buf->fp) != EOF; + return; + } + if (buf->len == buf->cap) { + if (buf->fd < 0) { + buf->err = 1; + return; + } + ioflush(buf); + } + buf->buf[buf->len++] = c; +} + +static int +putquoted(struct wbuf *buf, uchar c, uchar qchar, int next) +{ + if (c == qchar || c == '\\' || !aisprint(c)) { + int n = (ioputc(buf, '\\'), 1); + uchar cseq; + + switch (c) { + case '\\': + case '\'': + case '"': + cseq = c; + Charseq: + n += (ioputc(buf, cseq), 1); + break; + case '\a': cseq = 'a'; goto Charseq; + case '\b': cseq = 'b'; goto Charseq; + case '\f': cseq = 'f'; goto Charseq; + case '\r': cseq = 'r'; goto Charseq; + case '\t': cseq = 't'; goto Charseq; + case '\v': cseq = 'v'; goto Charseq; + case '\n': cseq = 'n'; goto Charseq; + default: + if (!next || in_range(next, '0', '7')) + n += bfmt(buf, "%.3o", c); + else + n += bfmt(buf, "%o", c); + } + return n; + } + if (c == '?' && (!next || next == '?')) { + return ioputc(buf, c), ioputc(buf, '\\'), 2; + } + return ioputc(buf, c), 1; +} + +static int +putuint(struct wbuf *buf, uvlong x, int base, bool lower) +{ + uchar tmp[64]; + uchar *end = tmp + sizeof(tmp); + uchar *s = end; + switch (base) { + case 2: + do *--s = '0' + x%2; while (x >>= 1); + break; + case 8: + do *--s = '0' + x%8; while (x >>= 3); + break; + case 10: + do *--s = '0' + x%10; while (x /= 10); + break; + case 16: + do *--s = "0123456789ABCDEF"[x&15] | (-lower & 0x20); while (x >>= 4); + break; + default: + assert(0&&"base"); + } + iowrite(buf, s, end - s); + return end - s; +} + +static void +fmterr(const char *fmt, ...) +{ + va_list ap; + + efmt("fmt Error: "); + va_start(ap, fmt); + vbfmt(&bstderr, fmt, ap); + va_end(ap); + ioputc(&bstderr, '\n'); + ioflush(&bstderr); + ioflush(&bstdout); + abort(); +} + +#define bwriteS(buf, S) (iowrite(buf, S, sizeof S - 1), sizeof S - 1) +#define bputc(B, C) (ioputc(B, C), 1) + +static int +priquals(struct wbuf *buf, int q) +{ + const char s[] = " const volatile", *p = s; + int m = sizeof s - 1; + if (!q) return 0; + else if (q == QCONST) m -= 9; + else if (q == QVOLATILE) p += 6, m -= 6; + else assert(q == (QCONST | QVOLATILE)); + iowrite(buf, p, m); + return m; +} +static int +pritypebefore(struct wbuf *buf, union type ty, int qual) +{ + const char *s, *s2; + union type chld; + int n; + switch (ty.t) { + case TYVOID: s = "void"; Prim: n = bfmt(buf, "%s", s); return n + priquals(buf, qual); + case TYBOOL: s = "bool"; goto Prim; + case TYCHAR: s = "char"; goto Prim; + case TYSCHAR: s = "signed char"; goto Prim; + case TYUCHAR: s = "unsigned char"; goto Prim; + case TYSHORT: s = "short"; goto Prim; + case TYUSHORT: s = "unsigned short"; goto Prim; + case TYINT: s = "int"; goto Prim; + case TYUINT: s = "unsigned int"; goto Prim; + case TYLONG: s = "long"; goto Prim; + case TYULONG: s = "unsigned long"; goto Prim; + case TYVLONG: s = "long long"; goto Prim; + case TYUVLONG: s = "unsigned long long"; goto Prim; + case TYFLOAT: s = "float"; goto Prim; + case TYDOUBLE: s = "double"; goto Prim; + case TYLDOUBLE:s = "long double"; goto Prim; + case TYCOMPLEXF:s = "float complex"; goto Prim; + case TYCOMPLEX: s = "double complex"; goto Prim; + case TYCOMPLEXL:s = "long double complex"; goto Prim; + case TYPTR: + chld = typechild(ty); + n = pritypebefore(buf, chld, ty.flag & TFCHLDQUAL); + if (!isptrcvtt(chld.t)) + n += bputc(buf, ' '); + if (chld.t == TYARRAY || chld.t == TYFUNC) + n += bputc(buf, '('); + n += bputc(buf, '*'); + n += priquals(buf, qual); + return n; + case TYARRAY: + return pritypebefore(buf, typechild(ty), ty.flag & TFCHLDQUAL); + case TYFUNC: + return pritypebefore(buf, typedata[ty.dat].ret, 0); + case TYSTRUCT: + s = "struct"; + Tagged: + n = bfmt(buf, "%s %s", s, (s2 = (char *)ttypenames[typedata[ty.dat].id]) ? s2 : "(anonymous)"); + return n + priquals(buf, qual); + case TYUNION: + s = "union"; + goto Tagged; + case TYENUM: + s = "enum"; + goto Tagged; + default: + return bfmt(buf, "?\?%d?",ty.t); + } +} + +static int +pritypeafter(struct wbuf *buf, union type ty, int qual) +{ + const struct typedata *td; + int n = 0; + switch (ty.t) { + case TYPTR: + if (typechild(ty).t == TYARRAY || typechild(ty).t == TYFUNC) + n += bputc(buf, ')'); + n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL); + break; + case TYARRAY: + n += bputc(buf, '['); + if (typearrlen(ty)) + n += bfmt(buf, "%u", typearrlen(ty)); + n += bputc(buf, ']'); + n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL); + break; + case TYFUNC: + td = &typedata[ty.dat]; + n += bputc(buf, '('); + for (int i = 0; i < td->nmemb; ++i) { + n += bfmt(buf, "%ty", td->param[i]); + if (i < td->nmemb - 1 || td->variadic) + n += bwriteS(buf, ", "); + } + if (td->variadic) n += bwriteS(buf, "..."); + else if (td->nmemb == 0 && !td->kandr) n += bwriteS(buf, "void"); + n += bwriteS(buf, ")"); + n += pritypeafter(buf, td->ret, 0); + break; + } + return n; +} + +static int +fmttype(struct wbuf *buf, union type ty, int qual) +{ + int n = pritypebefore(buf, ty, qual); + n += pritypeafter(buf, ty, qual); + return n; +} + +static int +putdouble(struct wbuf *buf, double x) +{ + char tmp[200]; + int n = snprintf(tmp, sizeof tmp, "%f", x); + if (n >= sizeof tmp-1) n = snprintf(tmp, sizeof tmp, "%g", x); + assert(n < sizeof tmp-1); + iowrite(buf, tmp, n); + return n; +} + +int +vbfmt(struct wbuf *out, const char *fmt, va_list ap) +{ + bool quote, umod, lmod, zmod, lower, possign; + int base; + vlong i; + int pad, prec, q; + const char *s; + void *p; + struct token *tok; + union type ty; + double f; + char tmpbuf1[70], tmpbuf2[70]; + struct wbuf tmp1 = MEMBUF(tmpbuf1, sizeof tmpbuf1); + struct wbuf tmp2 = MEMBUF(tmpbuf2, sizeof tmpbuf2); + struct wbuf *buf = out; + int n = 0, prevn; + + while (*fmt) { + buf = out; + if (*fmt++ != '%') { + n += bputc(buf, fmt[-1]); + continue; + } + if (*fmt == '%') { + n += bputc(buf, *fmt++); + continue; + } + fmt += quote = *fmt == '\''; + fmt += possign = *fmt == '+'; + pad = 0; + if (aisdigit(*fmt)) { /* left pad */ + for (; aisdigit(*fmt); ++fmt) + pad = pad*10 + *fmt-'0'; + if (pad) { + tmp1.len = 0; + buf = &tmp1; + } + } else if (*fmt == '-') { /* right pad */ + if (!aisdigit(*++fmt)) + fmterr("padding amount expected"); + for (; aisdigit(*fmt); ++fmt) + pad = pad*10 - (*fmt-'0'); + } + prec = -1; + if (*fmt == '.') { + if (!aisdigit(*++fmt)) + fmterr("precision expected"); + prec = 0; + for (; aisdigit(*fmt); ++fmt) + prec = prec*10 + *fmt-'0'; + } + fmt += umod = *fmt == 'u'; + if ((zmod = *fmt == 'z')) + ++fmt, lmod = 0; + else + fmt += lmod = *fmt == 'l'; + lower = 0; + prevn = n; + switch (*fmt++) { + case 'c': /* character */ + if (quote) { + n += bputc(buf, '\''); + n += putquoted(buf, va_arg(ap, int), '\'', -1); + n += bputc(buf, '\''); + } else { + n += bputc(buf, va_arg(ap, int)); + } + break; + case 's': /* nullterminated string */ + s = va_arg(ap, const char *); + if (quote) { + if (!s) { + n += bwriteS(buf, "(null)"); + break; + } + QuotedStr: + n += bputc(buf, '"'); + if (lmod) /* lower */ + for (; *s; ++s) n += putquoted(buf, aisalpha(*s) ? *s|32 : *s, '"', s[1]); + else + for (; *s; ++s) n += putquoted(buf, *s, '"', s[1]); + n += bputc(buf, '"'); + } else { + assert(s && "%s null!"); + if (lmod) /* lower */ + for (; *s; ++s) n += bputc(buf, aisalpha(*s) ? *s|32 : *s); + else + while (*s) n += bputc(buf, *s++); + } + break; + case 'S': /* string ptr + len */ + s = va_arg(ap, const char *); + i = va_arg(ap, uint); + PriS: + assert(s && "%S null"); + if (quote) { + n += bputc(buf, '"'); + for (; i--; ++s) n += putquoted(buf, *s, '"', i ? s[1] : -1); + n += bputc(buf, '"'); + } else { + iowrite(buf, s, i); + n += i; + } + break; + case 'y': /* symbol: print string literally if valid identifier, or quote it */ + s = va_arg(ap, const char *); + assert(s && "%y null"); + for (i = 0; s[i]; ++i) { + if (aisalpha(s[i]) || s[i] == '_' || (i > 0 && aisdigit(s[i]))) + continue; + goto QuotedStr; + } + /* valid identifier */ + while (*s) n += bputc(buf, *s++); + break; + case 'd': /* decimal */ + base = 10; + Int: + if (base != 10) umod = 1; + i = lmod ? va_arg(ap, vlong) + : umod ? va_arg(ap, uint) + : zmod && sizeof(&i-&i) > sizeof(int) ? va_arg(ap, vlong) + : (vlong)va_arg(ap, int); + tmp2.len = 0; + if (!umod && i < 0) { + n += bputc(buf, '-'); + i = -(uvlong)i; + } else if (possign) { + n += bputc(buf, '+'); + } + if (quote) { + switch (base) { + case 2: n += bwriteS(buf, "0b"); break; + case 8: n += bwriteS(buf, "0"); break; + case 16: n += bwriteS(buf, "0x"); break; + } + } + n += putuint(prec > 0 ? &tmp2 : buf, i, base, lower); + if (prec > 0) { + int fil = prec - tmp2.len; + while (fil-- > 0) n += bputc(buf, '0'); + iowrite(buf, tmp2.buf, tmp2.len); + } + break; + case 'o': /* octal */ + base = 8; + goto Int; + case 'b': /* binary */ + base = 2; + goto Int; + case 'x': case 'X': /* hexadecimal */ + base = 16; + lower = fmt[-1] == 'x'; + goto Int; + case 'p': /* pointer */ + p = va_arg(ap, void *); + if (!p && quote) { + n += bwriteS(buf, "NULL"); + } else { + n += bwriteS(buf, "0x"); + tmp2.len = 0; + n += putuint(prec > 0 ? &tmp2 : buf, (uvlong)p, 16, 1); + if (prec > 0) { + int fil = prec - tmp2.len; + while (fil-- > 0) n += bputc(buf, '0'); + iowrite(buf, tmp2.buf, tmp2.len); + } + } + break; + case 'f': /* float */ + f = va_arg(ap, double); + n += putdouble(buf, f); + break; + case 't': /* token/tokentag/type */ + switch (*fmt++) { + case 'k': /* tk token */ + tok = va_arg(ap, struct token *); + Tok: + switch (tok->t) { + case TKXXX: + n += bwriteS(buf, "\?\?\?"); + break; + case TKNUMLIT: + if (quote) { + n += bputc(buf, '`'); + iowrite(buf, tok->s, tok->len); + n += tok->len; + n += bputc(buf, '\''); + } else { + s = tok->s; + i = tok->len; + goto PriS; + } + break; + case TKCHRLIT: + if (tok->wide) n += bputc(buf, tok->wideuni ? tok->wide == 1 ? 'u' : 'U' : 'L'); + n += bputc(buf, '\''); + if (tok->wide == 0) + for (int i = 0; i < tok->len; ++i) + n += putquoted(buf, tok->s[i], '\'', i < tok->len - 1 ? tok->s[i+1] : -1); + else { + char p[4]; + uint c = tok->wide == 1 ? tok->ws16[0] : tok->ws32[0]; + int l = utf8enc(p, c); + if (l == 1) + n += putquoted(buf, *p, '\'', -1); + else + n += (iowrite(buf, p, l), l); + } + n += bputc(buf, '\''); + break; + case TKSTRLIT: + if (tok->wide == 0) { + s = tok->s; + i = tok->len; + quote = 1; + goto PriS; + } else { + n += bputc(buf, tok->wideuni ? tok->wide == 1 ? 'u' : 'U' : 'L'); + n += bputc(buf, '\"'); + for (int i = 0; i < tok->len; ++i) { + char p[4]; + uint c = tok->wide == 1 ? tok->ws16[i] : tok->ws32[i]; + int l = utf8enc(p, c); + if (l == 1) + n += putquoted(buf, *p, '\"', 0); + else + n += (iowrite(buf, p, l), l); + } + n += bputc(buf, '\"'); + } + break; + case TKPPMACSTR: + if (quote) n += bputc(buf, '`'); + n += bfmt(buf, "#%s", tok->s); + if (quote) n += bputc(buf, '\''); + break; + case TKPPMACARG: + case TKIDENT: + if (quote) n += bputc(buf, '`'); + n += bfmt(buf, "%s", tok->name); + if (quote) n += bputc(buf, '\''); + break; + case TKEOF: + n += bwriteS(buf, "<end-of-file>"); + break; + case TKEQU: s = "=="; C2: iowrite(buf, s, 2); n += 2; break; + case TKNEQ: s = "!="; goto C2; + case TKLTE: s = "<="; goto C2; + case TKGTE: s = ">="; goto C2; + case TKSHR: s = ">>"; goto C2; + case TKSHL: s = "<<"; goto C2; + case TKINC: s = "++"; goto C2; + case TKDEC: s = "--"; goto C2; + case TKDOTS: n += bwriteS(buf, "..."); break; + case TKARROW: s = "->"; goto C2; + case TKPPCAT: s = "##"; goto C2; + case TKLOGAND: s = "&&"; goto C2; + case TKLOGIOR: s = "||"; goto C2; + case TKSETADD: s = "+="; goto C2; + case TKSETSUB: s = "-="; goto C2; + case TKSETMUL: s = "*="; goto C2; + case TKSETDIV: s = "/="; goto C2; + case TKSETREM: s = "%="; goto C2; + case TKSETIOR: s = "|="; goto C2; + case TKSETXOR: s = "^="; goto C2; + case TKSETAND: s = "&="; goto C2; + case TKSETSHR: n += bwriteS(buf, ">>="); break; + case TKSETSHL: n += bwriteS(buf, "<<="); break; + default: + if (quote) n += bputc(buf, '`'); + if (in_range(tok->t, TKWBEGIN_, TKWEND_)) { + iowrite(buf, tok->name, tok->len); + n += tok->len; + } else if (aisprint(tok->t)) { + n += bputc(buf, tok->t); + } else { + n += bwriteS(buf, "??"); + } + if (quote) n += bputc(buf, '\''); + break; + } + break; + case 't': /* tt token tag */ + tok = &(struct token) { va_arg(ap, int) }; + switch (tok->t) { + case TKNUMLIT: + n += bwriteS(buf, "numeric literal"); + break; + case TKSTRLIT: + n += bwriteS(buf, "string literal"); + break; + case TKIDENT: + n += bwriteS(buf, "identifier"); + break; + case TKEOF: + n += bwriteS(buf, "<end-of-file>"); + break; + default: + if (tok->t >= TKWBEGIN_ && tok->t <= TKWEND_) { + static const char *tab[] = { + #define _(kw, c, ...) #kw, + #include "c/keywords.def" + #undef _ + }; + tok->s = tab[tok->t - TKWBEGIN_]; + tok->len = strlen(tok->s); + } + goto Tok; + } + break; + case 'y': /* ty type */ + ty = va_arg(ap, union type); + n += fmttype(buf, ty, 0); + break; + case 'q': /* tq qualified type */ + ty = va_arg(ap, union type); + q = va_arg(ap, int); + n += fmttype(buf, ty, q); + break; + default: + if (fmt[-1] == ' ' || !aisprint(fmt[-1])) + fmterr("expected format specifier"); + else + fmterr("unknown format specifier 't%c'", fmt[-1]); + } + break; + case 'g': /* graphics rendition (color) */ + if (!ccopt.nocolor) n += bwriteS(buf, "\033["); + while (*fmt++ != '.') { + if (ccopt.nocolor) continue; + n += bputc(buf, fmt[-1]); + } + if (!ccopt.nocolor) n += bputc(buf, 'm'); + break; + case 'M': /* cc mode */ + iowrite(buf, &"C89\0C99\0C11\0C23"[ccopt.cstd*4], 3); + n += 3; + n += bwriteS(buf, " mode"); + break; + default: + if (umod || lmod) { + --fmt; + base = 10; + goto Int; + } + if (fmt[-1] == ' ' || !aisprint(fmt[-1])) + fmterr("expected format specifier"); + else + fmterr("unknown format specifier '%c'", fmt[-1]); + } + if (pad > 0) { /* left pad */ + while (pad-- > buf->len) + n += bputc(out, ' '); + assert(buf != out); + iowrite(out, buf->buf, buf->len); + out->err |= buf->err; + } else if (pad < 0) { /* right pad */ + int len = n - prevn; + while (pad++ < -len) + n += bputc(out, ' '); + } + } + return n; +} + +int +bfmt(struct wbuf *buf, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vbfmt(buf, fmt, ap); + va_end(ap); + return ret; +} + +void +gpritype(union type ty) +{ + efmt("%ty\n", ty); + ioflush(&bstderr); +} + +static uint pagesiz; + +extern struct embedfile embedfilesdir[]; + +struct memfile +mapopen(const char **err, const char *path) +{ + struct stat stat; + int fd = -1; + void *p = NULL; + struct memfile f = {0}; + uint mapsiz; + + assert("nullp" && err && path); + + if (!pagesiz) pagesiz = sysconf(_SC_PAGESIZE); + *err = NULL; + + if (*path == '@' && path[1] == ':') { + for (struct embedfile *e = embedfilesdir; e->name; ++e) { + if (!strcmp(e->name, path+2)) { + return (struct memfile) { (const uchar *)e->s, e->len, .statik = 1 }; + } + } + } + + if ((fd = open(path, O_RDONLY)) < 0) + goto Err; + if (fstat(fd, &stat) != 0) + goto Err; + + if (S_ISREG(stat.st_mode)) { + if (stat.st_size > UINT_MAX) { + Big: + errno = EFBIG; + goto Err; + } + mapsiz = alignup(stat.st_size, pagesiz); + if ((p = mmap(NULL, mapsiz + pagesiz, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) + goto Err; + if (mapsiz > 0 && mmap(p, mapsiz, PROT_READ, MAP_FIXED|MAP_PRIVATE, fd, 0) == MAP_FAILED) + goto Err; + + close(fd); + f.p = p; + f.n = stat.st_size; + return f; + } else if (S_ISFIFO(stat.st_mode) || S_ISCHR(stat.st_mode)) { + uint cap = 0; + int ret; + + do { + enum { CHUNKSIZ = 1<<10 }; + if (f.n + CHUNKSIZ >= cap && (cap += CHUNKSIZ) < CHUNKSIZ) { + /* overflow */ + free(p); + goto Big; + } + if (!(f.p = p ? realloc(p, cap) : malloc(cap))) { + free(p); + goto Err; + } + p = (void *)f.p; + ret = read(fd, (char *)p + f.n, CHUNKSIZ); + if (ret >= 0) + f.n += ret; + else if (errno != EAGAIN && errno != EWOULDBLOCK) + goto Err; + } while (ret != 0); + + close(fd); + fd = -1; + mapsiz = alignup(f.n, pagesiz); + if ((f.p = mmap(NULL, mapsiz + pagesiz, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { + free(p); + goto Err; + } + memcpy((void *)f.p, p, f.n); + free(p); + mprotect((void *)f.p, mapsiz + pagesiz, PROT_READ); + return f; + } else { + *err = "Not a file"; + } + +Err: + if (fd >= 0) close(fd); + if (!*err) *err = strerror(errno); + return f; +} + +void +mapclose(struct memfile *f) +{ + assert(f->p); + if (!f->statik) + munmap((void *)f->p, alignup(f->n, pagesiz) + pagesiz); + memset(f, 0, sizeof *f); +} + +void * +mapzeros(uint N) +{ + void *p = mmap(NULL, N, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + return p == MAP_FAILED ? NULL : p; +} + +void +_assertfmt(const char *file, int line, const char *func, const char *expr) +{ + ioflush(&bstdout); + efmt("%s:%d: %s: Assertion `%s' failed.\n", file, line, func, expr); + ioflush(&bstderr); +} + +struct fileuid { + long dev; + union { + long ino; + const char *str; + }; +}; + +/* one entry per #line */ +struct linemap { + int phys; + int toline; + const char *tofile; +}; + +static struct file { + struct fileuid uid; + const char *path; + struct memfile f; + vec_of(uint) lineoffs; + vec_of(struct linemap) linemap; + bool once; + bool seen; + internstr guardmac; +} *fileht[1<<SPANFILEBITS]; +static int nfiles; + +int +getpredeffile(struct memfile **pf, const char *name) +{ + struct file *f; + struct fileuid uid; + uint h, id, n = countof(fileht); + + uid.dev = -11; + uid.ino = hashs(0, name); + for (id = h = uid.dev ^ uid.ino;; ++id) { + id &= countof(fileht) - 1; + f = fileht[id]; + if (f && f->uid.dev == uid.dev && f->uid.ino == uid.ino) { + break; + } else if (!f) { + f = allocz(&globarena, sizeof *f, 0); + f->uid = uid; + f->path = name; + f->f = (struct memfile) { .statik = 1 }; + fileht[id] = f; + vinit(&f->lineoffs, NULL, 10); + vpush(&f->lineoffs, 0); + ++nfiles; + break; + } + assert(--n > 0 && "fileht full"); + } + *pf = &f->f; + return id; +} + +int +openfile(const char **err, struct memfile **pf, const char *path) +{ + struct stat st; + struct file *f; + struct fileuid uid; + size_t h, id, n = countof(fileht); + + if (*path == '@' && path[1] == ':') { + /* fast path to rule out filenames we know for sure aren't builtin */ + /* !KEEP SYNC with embedfilesdir */ + if (path[2] != 's' /* std*.h */ + && path[2] != 'f' /* float.h */) return -1; + uid.dev = -1; + uid.str = path; + h = hashs(0, path+2); + } else { + if (stat(path, &st) != 0) { + *err = strerror(errno); + return -1; + } + uid.dev = st.st_dev, uid.ino = st.st_ino; + h = uid.dev ^ uid.ino; + } + for (id = h;; ++id) { + id &= countof(fileht) - 1; + f = fileht[id]; + if (f && f->uid.dev == uid.dev && (uid.dev >= 0 ? f->uid.ino == uid.ino : !strcmp(f->uid.str, uid.str))) { + break; + } else if (!f) { + struct memfile m; + m = mapopen(err, path); + if (*err) return -1; + f = allocz(&globarena, sizeof *f, 0); + f->uid = uid; + f->path = path; + f->f = m; + fileht[id] = f; + vinit(&f->lineoffs, NULL, 50); + vpush(&f->lineoffs, 0); + ++nfiles; + break; + } + assert(--n > 0 && "fileht full"); + } + *pf = &f->f; + return id; +} + +const char * +getfilename(int id, uint atoff) +{ + assert((uint)id < countof(fileht) && fileht[id]); + if (!fileht[id]->linemap.n || !atoff) + return fileht[id]->path; + return getfilepos(NULL, NULL, id, atoff); +} + +struct memfile * +getfile(int id) +{ + assert((uint)id < countof(fileht) && fileht[id]); + return &fileht[id]->f; +} + +void +addfileline(int id, uint off) +{ + assert((uint)id < countof(fileht) && fileht[id]); + vec_of(uint) *lineoffs = (void *)&fileht[id]->lineoffs; + if (lineoffs->n && off > lineoffs->p[lineoffs->n-1]) + vpush(lineoffs, off); +} + +void +setfileline(int id, uint off, int line, const char *file) +{ + assert((uint)id < countof(fileht) && fileht[id]); + vec_of(struct linemap) *linemap = (void *)&fileht[id]->linemap; + vec_of(uint) *lineoffs = (void *)&fileht[id]->lineoffs; + int phys = 2; + for (int i = lineoffs->n-1; i >= 0; --i) { + if (lineoffs->p[i] < off) { + phys = i+2; + break; + } + } + if (linemap->n > 0) { + assert(linemap->p[linemap->n-1].phys < phys); + if (!file) file = linemap->p[linemap->n-1].tofile; + } + vpush(linemap, ((struct linemap){ phys, line, file })); +} + +const char * +getfilepos(int *pline, int *pcol, int id, uint off) +{ + assert((uint)id < countof(fileht) && fileht[id]); + uint *offs = fileht[id]->lineoffs.p; + uint n = fileht[id]->lineoffs.n; + /* binary search over offsets array */ + int l = 0, h = n - 1, i = 0; + while (l <= h) { + i = (l + h) / 2; + if (offs[i] < off) l = i + 1; + else if (offs[i] > off) h = i - 1; + else break; + } + i -= offs[i] > off; + int line = i + 1, col = off - offs[i] + 1; + const char *file = fileht[id]->path; + vec_of(struct linemap) *linemap = (void *)&fileht[id]->linemap; + if (linemap->n) { + /* binary search over linemap array */ + l = 0, h = linemap->n - 1, i = 0; + while (l <= h) { + i = (l + h) / 2; + if (linemap->p[i].phys < line) l = i + 1; + else if (linemap->p[i].phys > line) h = i - 1; + else break; + } + i -= linemap->p[i].phys > line; + if (i >= 0) { + line = linemap->p[i].toline + (line - linemap->p[i].phys); + if (linemap->p[i].tofile) file = linemap->p[i].tofile; + } + } + if (pline) *pline = line; + if (pcol) *pcol = col; + return file; +} + +bool +isoncefile(int id, internstr *guard) +{ + assert(id < countof(fileht) && fileht[id]); + *guard = fileht[id]->guardmac; + return fileht[id]->once; +} + +void +markfileonce(int id, internstr guard) +{ + assert(id < countof(fileht) && fileht[id]); + fileht[id]->once = 1; + fileht[id]->guardmac = guard; +} + +void +markfileseen(int id) +{ + assert(id < countof(fileht) && fileht[id]); + fileht[id]->seen = 1; +} + +bool +isfileseen(int id) +{ + assert(id < countof(fileht) && fileht[id]); + return fileht[id]->seen; +} + +void +closefile(int id) +{ + assert(id < countof(fileht) && fileht[id]); + mapclose(&fileht[id]->f); +} + +void +vdiag(const struct span *span, enum diagkind kind, const char *fmt, va_list ap) +{ + /* to avoid concurrent invocations of the compiler mixing up the diagnostics + * in the unbuffered stderr output, use a separate buffer here and write() + * it all out bypassing stdio */ + static char ebuf[4096]; + static struct wbuf out = FDBUF(ebuf, sizeof ebuf, STDERR_FILENO); + static int depth = 0; /* needed for nested note() calls */ + + static const char *label[] = { "error", "warning", "note" }; + static const char *color[] = { "%g1;31.", "%g1;35.", "%g1;36." }; + int line, col; + struct memfile *f; + const struct span0 *loc; + + ++depth; + if (span) { + loc = span->ex.len ? &span->ex : &span->sl; + f = getfile(loc->file); + const char *file = getfilepos(&line, &col, loc->file, loc->off); + bfmt(&out, "%s:%d:%d: ", file, line, col); + } + bfmt(&out, color[kind]); + bfmt(&out, "%s: %g.", label[kind]); + vbfmt(&out, fmt, ap); + bfmt(&out, "\n"); + if (span) { + uint i; + int nmark; + char mark = '^'; + + /* find start of line */ + for (i = loc->off - 1; i + 1 > 0 && f->p[i] != '\n'; --i) ; + if (i || f->p[i] == '\n') ++i; + + nmark = loc->len; + while (i < loc->off + loc->len) { + int j, end; + int curoff = bfmt(&out, "%5d | ", line); + const uchar *linep = &f->p[i]; + bool begintabs = 1; + for (end = 0; f->p[i] != '\n' && i < f->n; ++i, ++end) { + uchar c = f->p[i]; + if (c == '\t') { + if (!begintabs) c = ' '; + } else { + begintabs = 0; + } + ioputc(&out, c); + } + ioputc(&out, '\n'); + ++i; + + for (j = -curoff; j < 0; ++j) + ioputc(&out, j == -2 ? '|' : ' '); + for (begintabs = 1; j < col-1; ++j) { + uchar c = *linep++; + if (c == '\t') { + if (!begintabs) c = ' '; + } else { + c = ' '; + begintabs = 0; + } + ioputc(&out, c); + } + bfmt(&out, color[kind]); + do { + ioputc(&out, mark); + mark = '~'; + } while (--nmark > 0 && ++j < end); + col = 1; + ++line; + bfmt(&out, "%g.\n"); + --nmark; + } + ioputc(&out, '\n'); + } + + if (span && loc == &span->ex && span->sl.len) + if (span->ex.file != span->sl.file || !((uint) span->sl.off - span->ex.off < span->ex.len)) + note(&(struct span){ span->sl }, "expanded from here"); + + if (--depth == 0) ioflush(&out); +} + +void _Noreturn +fatal(const struct span *span, const char *fmt, ...) +{ + if (fmt) { + va_list ap; + va_start(ap, fmt); + vdiag(span, DGERROR, fmt, ap); + va_end(ap); + } + if (!fmt || span) efmt("Aborting due to previous error.\n"); + exit(1); +} + +int nerror, nwarn; +enum { MAXERROR = 20 }; + +void +error(const struct span *span, const char *fmt, ...) +{ + va_list ap; + + ++nerror; + va_start(ap, fmt); + vdiag(span, DGERROR, fmt, ap); + va_end(ap); + if (nerror > MAXERROR) { + efmt("Too many errors emitted, stopping now.\n"); + exit(1); + } +} + +void +warn(const struct span *span, const char *fmt, ...) +{ + va_list ap; + + if (ccopt.wnone) return; + if (ccopt.werror) ++nerror; + else ++nwarn; + va_start(ap, fmt); + vdiag(span, ccopt.werror ? DGERROR : DGWARN, fmt, ap); + va_end(ap); + if (nerror > MAXERROR) { + efmt("Too many errors emitted, stopping now.\n"); + exit(1); + } +} + +void +note(const struct span *span, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vdiag(span, DGNOTE, fmt, ap); + va_end(ap); +} + +/*** UTF util ***/ + +ushort * +utf8to16(uint *ulen, struct arena **arena, const uchar *s, size_t len) +{ + assert(0 && "nyi"); +} + +uint * +utf8to32(uint *ulen, struct arena **arena, const uchar *s, size_t len) +{ + uint *ret, *w; + const uchar *p, *end; + size_t n = 0; + bool istrunc; + + if (!len) return NULL; + + for (p = end = s; p < s + len; ++n) { + end = p; + if ((*p & 0xF8) == 0xF0) /* 11110xxx */ + p += 4; + else if ((*p & 0xF0) == 0xE0) /* 1110xxxx */ + p += 3; + else if ((*p & 0xE0) == 0xC0) /* 110xxxxx */ + p += 2; + else p += 1; + } + istrunc = p > s+len; + if (!istrunc) end += 1; + + ret = allocz(arena, n * sizeof *ret, sizeof *ret); + for (w = ret, p = s; p < end; ++w) { + if ((*p & 0xF8) == 0xF0) { /* 11110xxx */ + *w = (uint)(p[0] & 0x07) << 18 + | (uint)(p[1] & 0x3F) << 12 + | (uint)(p[2] & 0x3F) << 6 + | (uint)(p[3] & 0x3F); + p += 4; + } else if ((*p & 0xF0) == 0xE0) { /* 1110xxxx */ + *w = (uint)(p[0] & 0x07) << 12 + | (uint)(p[1] & 0x3F) << 6 + | (uint)(p[2] & 0x3F); + p += 3; + } else if ((*p & 0xE0) == 0xC0) { /* 110xxxxx */ + *w = (uint)(p[0] & 0x07) << 6 + | (uint)(p[1] & 0x3F); + p += 2; + } else { + *w = *p; + p += 1; + } + } + if (istrunc) *w++ = 0xFFFD; + *ulen = n; + + return ret; +} + +int +utf8enc(char p[4], uint cp) +{ + if ((cp & 0xffffff80) == 0) { + p[0] = cp; + return 1; + } else if ((cp & 0xfffff800) == 0) { + p[0] = 0xC0 | (cp >> 6 & 0x1F); + p[1] = 0x80 | (cp & 0x3F); + return 2; + } else if ((cp & 0xffff0000) == 0) { + p[0] = 0xE0 | (cp >> 12 & 0x0F); + p[1] = 0x80 | (cp >> 6 & 0x3F); + p[2] = 0x80 | (cp & 0x3F); + return 3; + } else { + p[0] = 0xF0 | (cp >> 18 & 0x07); + p[1] = 0x80 | (cp >> 12 & 0x3F); + p[2] = 0x80 | (cp >> 6 & 0x3F); + p[3] = 0x80 | (cp & 0x3F); + return 4; + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir.c b/src/ir.c new file mode 100644 index 0000000..b612143 --- /dev/null +++ b/src/ir.c @@ -0,0 +1,689 @@ +#include "ir.h" +#include "../obj/obj.h" + +uchar type2cls[NTYPETAG]; +uchar cls2siz[] = { [KI32] = 4, [KI64] = 8, [KF32] = 4, [KF64] = 8 }; +uchar cls2load[] = { + [KI32] = Oloadu32, [KI64] = Oloadi64, + [KF32] = Oloadf32, [KF64] = Oloadf64, [KPTR] = -1 +}, cls2store[] = { + [KI32] = Ostorei32, [KI64] = Ostorei64, + [KF32] = Ostoref32, [KF64] = Ostoref64, [KPTR] = -1 +}; +const uchar siz2intcls[] = { [1] = KI32, [2] = KI32, [4] = KI32, [8] = KI64 }; + +const char *opnames[] = { + "?\??", +#define _(o,...) #o, +#include "op.def" +#undef _ +}; + +const uchar opnarg[] = { + 0, +#define _(o,n) n, +#include "op.def" +#undef _ +}; + +struct instr instrtab[MAXINSTR]; +struct use *instruse[MAXINSTR]; +int ninstr; +static int instrfreelist; +static struct use *usefreelist; +static struct arena **usearena; +struct calltab calltab; +struct phitab phitab; +struct dattab dattab; +struct contab contab; +struct addrtab addrtab; +static ushort *addrht; +static int naddrht; +int visitmark; + +void +irinit(struct function *fn) +{ + static struct call callsbuf[64]; + static union ref *phisbuf[64]; + static struct irdat datsbuf[64]; + static struct xcon consbuf[64]; + static struct addr addrsbuf[64]; + + assert(fn->arena && !fn->passarena); + + ninstr = 0; + instrfreelist = -1; + usefreelist = NULL; + usearena = fn->arena; + vinit(&calltab, callsbuf, countof(callsbuf)); + for (int i = 0; i < phitab.n; ++i) xbfree(phitab.p[i]); + vinit(&phitab, phisbuf, countof(phisbuf)); + vinit(&contab, consbuf, countof(consbuf)); + if (!dattab.p) vinit(&dattab, datsbuf, countof(datsbuf)); + naddrht = 128; + if (!addrht) xbgrowz(&addrht, naddrht); + else if (addrtab.n) memset(addrht, 0, xbcap(addrht) * sizeof *addrht); + vinit(&addrtab, addrsbuf, countof(addrsbuf)); + + if (!type2cls[TYINT]) { + for (int i = TYBOOL; i <= TYUVLONG; ++i) { + int siz = targ_primsizes[i]; + type2cls[i] = siz < 8 ? KI32 : KI64; + } + type2cls[TYFLOAT] = KF32; + type2cls[TYDOUBLE] = KF64; + type2cls[TYLDOUBLE] = KF64; + type2cls[TYPTR] = KPTR; + type2cls[TYARRAY] = KPTR; + cls2siz[KPTR] = targ_primsizes[TYPTR]; + cls2load[KPTR] = targ_64bit ? Oloadi64 : Oloads32; + cls2store[KPTR] = targ_64bit ? Ostorei64 : Ostorei32; + } + fn->entry = fn->curblk = allocz(fn->arena, sizeof(struct block), 0); + fn->nblk = 1; + fn->entry->lprev = fn->entry->lnext = fn->entry; + fn->prop = FNUSE; /* builder keeps this */ +} + +static int +newaddr(const struct addr *addr) +{ + if (addrtab.n >= naddrht/4*3 /*75% load factor */) { + xbgrowz(&addrht, naddrht*2); + memset(addrht, 0, naddrht * sizeof *addrht); + naddrht *= 2; + for (int i = 0; i < addrtab.n; ++i) { /* rehash */ + const struct addr *addr = &addrtab.p[i]; + for (uint h = hashb(0, addr, sizeof *addr), j = h;; ++j) { + if (!addrht[j &= naddrht - 1]) { + addrht[j] = i+1; + break; + } + } + } + } + for (uint h = hashb(0, addr, sizeof *addr), i = h;; ++i) { + i &= naddrht - 1; + if (!addrht[i]) { + vpush(&addrtab, *addr); + return (addrht[i] = addrtab.n) - 1; + } else if (!memcmp(&addrtab.p[addrht[i]-1], addr, sizeof *addr)) { + return addrht[i] - 1; + } + } +} + +union ref +newxcon(const struct xcon *con) +{ + assert((con->issym ^ con->isdat) || con->cls); + vpush(&contab, *con); + return mkref(RXCON, contab.n-1); +} + +union irtype +mkirtype(union type t) +{ + if (t.t == TYVOID || isscalar(t)) + return (union irtype) { .cls = type2cls[scalartypet(t)] }; + assert(isagg(t)); + return (union irtype) { .isagg = 1, .dat = t.dat }; +} + +union ref +mkintcon(enum irclass k, vlong i) +{ + if (i < 1l << 28 && i >= -(1l << 28)) { + return mkref(RICON, i); + } else { + struct xcon con = { .cls = k, .i = i }; + if (cls2siz[k] == 4) /* check upper half is zero or -1 */ + assert(in_range((i >> 32) + 1, 0, 1)); + return newxcon(&con); + } +} + +union ref +mkfltcon(enum irclass k, double f) +{ + struct xcon con = { .cls = k, .f = k == KF32 ? (float) f : f }; + return newxcon(&con); +} + +union ref +mksymref(internstr s, enum symflags symflags) +{ + struct xcon con = { .issym = 1, .sym = s, .flag = symflags }; + return newxcon(&con); +} + +union ref +mkdatref(internstr name, union type ctype, uint siz, uint align, + const void *bytes, uint n, bool deref, bool funclocal) +{ + struct irdat dat = { .ctype = ctype, .align = align, .siz = siz, .name = name, .section = Srodata }; + if (funclocal && objout.code && align >= 4 && align <= targ_primsizes[TYPTR] && siz <= 16) + dat.section = Stext; + + assert(n <= siz && siz && align); + if (!name) { + char buf[32]; + struct wbuf wbuf = MEMBUF(buf, sizeof buf); + + bfmt(&wbuf, ".L%c.%d", dat.section == Stext ? 'L' : 'D', dattab.n); + ioputc(&wbuf, 0); + assert(!wbuf.err); + dat.name = name = intern(buf); + } + dat.off = objnewdat(name, dat.section, 0, siz, align); + uchar *p = (dat.section == Stext ? objout.textbegin : objout.rodata.p) + dat.off; + if (n) memcpy(p, bytes, n); + if (dat.section != Stext) memset(p+n, 0, siz - n); + vpush(&dattab, dat); + return newxcon(&(struct xcon){.isdat = 1, .deref = deref, .dat = dattab.n - 1, .flag = SLOCAL}); +} + +internstr +xcon2sym(int ref) +{ + struct xcon con = contab.p[ref]; + assert(con.issym ^ con.isdat); + return con.issym ? con.sym : dattab.p[con.dat].name; +} + +struct instr +mkalloca(uint siz, uint align) +{ + struct instr ins = { .cls = KPTR }; + assert(ispo2(align) && align <= 16); + ins.op = Oalloca1 + ilog2(align); + ins.l = mkref(RICON, siz/align + (siz%align != 0)); + return ins; +} + +union ref +mkcallarg(union irtype ret, uint narg, int vararg) +{ + struct call call = { .ret=ret, .narg=narg, .vararg=vararg }; + assert(vararg == -1 || (uint)vararg <= narg); + vpush(&calltab, call); + return mkref(RXXX, calltab.n-1); +} + +union ref +mkaddr(struct addr addr) +{ + return mkref(RADDR, newaddr(&addr)); +} + +void +addpred(struct block *blk, struct block *p) +{ + if (blk->npred == 0) { + blk->_pred0 = p; + ++blk->npred; + return; + } + if (blk->npred == 1) { + struct block *p0 = blk->_pred0; + blk->_pred = NULL; + xbgrow(&blk->_pred, 4); + *blk->_pred = p0; + } + xbpush(&blk->_pred, &blk->npred, p); +} + +void +delpred(struct block *blk, struct block *p) +{ + for (int i = 0; i < blk->npred; ++i) { + if (blkpred(blk, i) == p) { + for (int j = 0; j < blk->phi.n; ++j) { + union ref *phiargs = phitab.p[instrtab[blk->phi.p[j]].l.i]; + for (int k = i; k < blk->npred - 1; ++k) { + phiargs[k] = phiargs[k + 1]; + } + } + for (int k = i; k < blk->npred - 1; ++k) { + blkpred(blk, k) = blkpred(blk, k + 1); + } + if (--blk->npred == 1) { + struct block *p0 = blk->_pred[0]; + xbfree(blk->_pred); + blk->_pred0 = p0; + } + return; + } + } + //assert(0&&"blk not in p"); +} + +struct block * +newblk(struct function *fn) +{ + struct block *blk = allocz(fn->arena, sizeof(struct block), 0); + blk->id = -1; + return blk; +} + +void +freeblk(struct function *fn, struct block *blk) +{ + if (blk->npred > 1) + xbfree(blk->_pred); + blk->npred = 0; + blk->_pred = NULL; + + for (int i = 0; i < blk->phi.n; ++i) { + int ui = blk->phi.p[i]; + union ref *r = phitab.p[instrtab[ui].l.i]; + for (int j = 0; j < blk->npred; ++j) { + deluse(blk, ui, *r); + } + } + for (int i = 0; i < blk->ins.n; ++i) { + int ui = blk->ins.p[i]; + struct instr *ins = &instrtab[ui]; + if (ins->l.t == RTMP) deluse(blk, ui, ins->l); + if (ins->r.t == RTMP) deluse(blk, ui, ins->r); + } + for (int i = 0; i < 2; ++i) { + if (blk->jmp.arg[i].t == RTMP) deluse(blk, USERJUMP, blk->jmp.arg[i]); + } + if (blk->s1) delpred(blk->s1, blk); + if (blk->s2) delpred(blk->s2, blk); + vfree(&blk->phi); + vfree(&blk->ins); + if (blk->id != -1) + --fn->nblk; + if (blk->lprev) blk->lprev->lnext = blk->lnext; + if (blk->lnext) blk->lnext->lprev = blk->lprev; + blk->id = 1u<<31; +} + +struct block * +insertblk(struct function *fn, struct block *pred, struct block *subst) +{ + struct block *new = newblk(fn); + struct block **s = pred->s1 == subst ? &pred->s1 : &pred->s2; + assert(*s == subst); + new->lnext = pred->lnext; + new->lprev = pred; + pred->lnext->lprev = new; + pred->lnext = new; + *s = new; + new->jmp.t = Jb; + new->s1 = subst; + addpred(new, pred); + for (int i = 0; i < subst->npred; ++i) { + if (blkpred(subst, i) == pred) { + blkpred(subst, i) = new; + ++fn->nblk; + return new; + } + } + assert(0); +} + +struct block * +blksplitafter(struct function *fn, struct block *blk, int idx) +{ + struct block *new = newblk(fn); + ++fn->nblk; + new->lprev = blk; + new->lnext = blk->lnext; + blk->lnext = new; + new->lnext->lprev = new; + if (idx == -1) { + new->ins = blk->ins; + memset(&blk->ins, 0, sizeof blk->ins); + } else { + if (idx < blk->ins.n-1) + vpushn(&new->ins, &blk->ins.p[idx+1], blk->ins.n-idx-1); + blk->ins.n -= new->ins.n; + } + new->jmp = blk->jmp; + blk->jmp.t = Jb; + memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + for (int i = 0; i < 2; ++i) { + struct block *s = (&blk->s1)[i]; + if (s) for (int i = 0; i < s->npred; ++i) { + if (blkpred(s, i) == blk) + blkpred(s, i) = new; + } + } + new->s1 = blk->s1, new->s2 = blk->s2; + blk->s1 = new, blk->s2 = NULL; + addpred(new, blk); + fn->prop &= ~FNUSE; + return new; +} + +int +allocinstr(void) +{ + int t; + if (instrfreelist != -1) { + t = instrfreelist; + memcpy(&instrfreelist, &instrtab[t], sizeof(int)); + if (instruse[t]) deluses(t); + } else { + assert(ninstr < countof(instrtab)); + t = ninstr++; + instruse[t] = NULL; + } + return t; +} + +void +adduse(struct block *ublk, int ui, union ref r) { + if (r.t != RTMP) return; + struct use *use; + if (usefreelist) { + use = usefreelist; + usefreelist = usefreelist->next; + } else { + use = alloc(usearena, sizeof *use, 0); + } + assert(use != instruse[r.i]); + use->next = instruse[r.i]; + use->blk = ublk, + use->u = ui; + instruse[r.i] = use; +} + +bool +deluse(struct block *ublk, int ui, union ref r) { + if (r.t != RTMP) return 0; + + for (struct use **puse = &instruse[r.i]; *puse; puse = &(*puse)->next) { + struct use *use = *puse; + if (use->blk == ublk && use->u == ui) { + *puse = use->next; + use->blk = 0; + use->u = 0; + use->next = usefreelist; + usefreelist = use; + return 1; + } + } + return 0; +} + +void +filluses(struct function *fn) +{ + struct block *blk = fn->entry; + + for (int i = 0; i < ninstr; ++i) + deluses(i); + + do { + for (int i = 0; i < blk->phi.n; ++i) { + int ins = blk->phi.p[i]; + union ref *phi = phitab.p[instrtab[ins].l.i]; + for (int i = 0; i < blk->npred; ++i) + adduse(blk, ins, phi[i]); + } + for (int i = 0; i < blk->ins.n; ++i) { + int ins = blk->ins.p[i]; + adduse(blk, ins, instrtab[ins].l); + adduse(blk, ins, instrtab[ins].r); + } + adduse(blk, USERJUMP, blk->jmp.arg[0]); + adduse(blk, USERJUMP, blk->jmp.arg[1]); + } while ((blk = blk->lnext) != fn->entry); + + fn->prop |= FNUSE; +} + +int +newinstr(struct block *at, struct instr ins) +{ + int new = allocinstr(); + instrtab[new] = ins; + if (at) { + adduse(at, new, ins.l); + adduse(at, new, ins.r); + } + return new; +} + +union ref +insertinstr(struct block *blk, int idx, struct instr ins) +{ + int new = newinstr(blk, ins); + if (idx == blk->ins.n) vpush(&blk->ins, new); + else { + assert(idx >= 0 && idx < blk->ins.n); + vpush_(&blk->ins._vb, sizeof *blk->ins.p); + vresize(&blk->ins, blk->ins.n); + for (int i = blk->ins.n++; i > idx; --i) + blk->ins.p[i] = blk->ins.p[i - 1]; + blk->ins.p[idx] = new; + } + return mkref(RTMP, new); +} + +union ref +insertphi(struct block *blk, enum irclass cls) +{ + int new = allocinstr(); + union ref *refs = NULL; + assert(blk->npred > 0); + xbgrowz(&refs, blk->npred); + vpush(&phitab, refs); + instrtab[new] = mkinstr(Ophi, cls, mkref(RXXX, phitab.n - 1)); + vpush(&blk->phi, new); + return mkref(RTMP, new); +} + +uint +numberinstrs(struct function *fn) +{ + struct block *blk = fn->entry; + int start = 0; + do { + blk->inumstart = start; + start += blk->ins.n+2; + } while ((blk = blk->lnext) != fn->entry); + return start-1; +} + +static bool +reachablerec(struct function *fn, struct block *blk) +{ + if (blk == fn->entry) return 1; + markvisited(blk); + if (blk->npred == 1 && !wasvisited(blkpred(blk, 0))) + return reachablerec(fn, blkpred(blk, 0)); + else for (int i = 0; i < blk->npred; ++i) { + struct block *p = blkpred(blk, i); + if (!wasvisited(p) && reachablerec(fn, p)) return 1; + } + return 0; +} + +bool +blkreachable(struct function *fn, struct block *blk) +{ + startbbvisit(); + return reachablerec(fn, blk); +} + +/* require use */ +void +replcuses(union ref from, union ref to) +{ + assert(from.t == RTMP); + for (struct use *use = instruse[from.i], *next; use; use = next) { + union ref *u; + int n, j; + next = use->next; + if (use->u == from.i) continue; + if (use->u == USERJUMP) { + u = &use->blk->jmp.arg[0]; + n = 2; + } else if (instrtab[use->u].op == Ophi) { + u = phitab.p[instrtab[use->u].l.i]; + n = use->blk->npred; + if (use->blk->phi.n == 0) continue; /* shouldn't happen */ + } else { + u = &instrtab[use->u].l; + n = 2; + } + + for (j = 0; j < n; ++j) { + if (u[j].bits == from.bits) { + u[j].bits = to.bits; + adduse(use->blk, use->u, to); + next = use; + break; + } + } + } +} + +void +deluses(int ins) +{ + for (struct use *use = instruse[ins], *next; use; use = next) { + next = use->next; + use->blk = 0; + use->u = 0; + use->next = usefreelist; + usefreelist = use; + } + instruse[ins] = NULL; +} + +void +delinstr(struct block *blk, int idx) +{ + int t = blk->ins.p[idx]; + assert(idx >= 0 && idx < blk->ins.n); + for (int i = 0; i < 2; ++i) { + deluse(blk, t, (&instrtab[t].l)[i]); + } + memcpy(&instrtab[t], &instrfreelist, sizeof(int)); + instrfreelist = t; + deluses(t); + for (int i = idx; i < blk->ins.n - 1; ++i) + blk->ins.p[i] = blk->ins.p[i + 1]; + --blk->ins.n; +} + +void +delphi(struct block *blk, int idx) +{ + int t = blk->phi.p[idx]; + assert(idx >= 0 && idx < blk->phi.n); + memcpy(&instrtab[t], &instrfreelist, sizeof(int)); + instrfreelist = t; + deluses(t); + for (int i = idx; i < blk->phi.n - 1; ++i) + blk->phi.p[i] = blk->phi.p[i + 1]; + --blk->phi.n; +} + +void +delnops(struct block *blk) +{ + int i, n, t; + /* delete trailing nops */ + while (blk->ins.n > 0 && instrtab[t = blk->ins.p[blk->ins.n - 1]].op == Onop) { + --blk->ins.n; + deluses(t); + memcpy(&instrtab[t], &instrfreelist, sizeof(int)); + instrfreelist = t; + } + /* delete rest of nops */ + for (i = blk->ins.n - 2, n = 0; i >= 0; --i) { + if (instrtab[t = blk->ins.p[i]].op == Onop) { + deluses(t); + memcpy(&instrtab[t], &instrfreelist, sizeof(int)); + instrfreelist = t; + ++n; + } else if (n) { + memmove(blk->ins.p+i+1, blk->ins.p+i+1+n, (blk->ins.n - n - i - 1)*sizeof *blk->ins.p); + blk->ins.n -= n; + n = 0; + } + } + if (n) + memmove(blk->ins.p, blk->ins.p + n, (blk->ins.n -= n)*sizeof *blk->ins.p); +} + +void +fillblkids(struct function *fn) +{ + int i = 0; + struct block *blk = fn->entry; + do blk->id = i++; while ((blk = blk->lnext) != fn->entry); + + fn->prop |= FNBLKID; +} + +/** Misc **/ + +static void +freefn(struct function *fn) +{ + struct block *blk = fn->entry; + do { + if (blk->npred > 1) xbfree(blk->_pred); + vfree(&blk->phi); + vfree(&blk->ins); + } while ((blk = blk->lnext) != fn->entry); +} + +void +irfini(struct function *fn) +{ + extern int nerror; + static union { char m[sizeof(struct arena) + (4<<10)]; struct arena *_align; } amem; + struct arena *passarena = (void *)&amem.m; + fn->passarena = &passarena; + if (nerror) { + freefn(fn); + return; + } + + abi0(fn); + lowerintrin(fn); + if (ccopt.o > OPT0) { + mem2reg(fn); + freearena(fn->passarena); + copyopt(fn); + } + if (ccopt.o >= OPT1) { + doinline(fn); + filldom(fn); + if (!(fn->prop & FNUSE)) filluses(fn); + cselim(fn); + freearena(fn->passarena); + simpl(fn); + freearena(fn->passarena); + } + if (maybeinlinee(fn)) { + // goto Fin; XXX do this by having inline function rematerialization when symbol is actually referenced + } + lowerstack(fn); + freearena(fn->passarena); + if (ccopt.dbg.o) { + bfmt(ccopt.dbgout, "<< Before isel >>\n"); + irdump(fn); + } + mctarg->isel(fn); + regalloc(fn); + freearena(fn->passarena); + if (objout.code) + mctarg->emit(fn); + +//Fin: + freearena(fn->passarena); + freefn(fn); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir.h b/src/ir.h new file mode 100644 index 0000000..ab3e474 --- /dev/null +++ b/src/ir.h @@ -0,0 +1,353 @@ +#include "../common.h" +#include "../type.h" + +enum irclass { + KXXX, + KI32, KI64, KPTR, + KF32, KF64, +}; + +#define kisint(k) in_range((k), KI32, KPTR) +#define kisflt(k) in_range((k), KF32, KF64) + +union irtype { + struct { ushort _ : 1, cls : 15; }; + struct { ushort isagg : 1, dat : 15; }; + ushort bits; +}; + +struct irdat { + uchar align : 6, globl : 1; + uchar section; + union type ctype; + uint siz; + uint off; + internstr name; +}; + +enum symflags { + SLOCAL = 1, + SFUNC = 2, +}; +struct xcon { + bool issym, isdat, deref; + uchar cls; + uchar flag; + union { + internstr sym; + int dat; + vlong i; + double f; + }; +}; + +struct abiarg { + union irtype ty; + union { + struct { ushort _ : 1, reg : 15; }; + struct { ushort isstk : 1, stk : 15; }; + }; +}; + +struct call { + union irtype ret; + ushort narg; + short vararg; /* first variadic arg or -1 */ + ushort argstksiz; + struct abiarg *abiarg; + struct abiarg abiret[2]; + uchar r2off; +}; + +enum refkind { + RXXX, /* used for empty ref (zeros), undef, and the special args of call,phi,etc */ + RTMP, /* reference to another instruction's result */ + RREG, /* machine register */ + RICON, /* small integer constants */ + RXCON, /* other constants (incl. external symbols) */ + RADDR, /* target-specific addressing mode */ + RTYPE, /* irtype */ + RSTACK, /* stack base offset */ +}; + +union ref { + struct { unsigned t : 3; signed i : 29; }; + uint bits; +}; +static_assert(sizeof(union ref) == 4); + +struct addr { + union ref base, index; + int shift, disp; +}; + +#define insrescls(ins) (oiscmp((ins).op) ? KI32 : (ins).cls) +#define NOREF ((union ref) {0}) +#define UNDREF ((union ref) {{ 0, -1 }}) +#define ZEROREF ((union ref) {{ RICON, 0 }}) +#define mkref(t, x) ((union ref) {{ (t), (x) }}) +#define mktyperef(t) ((union ref) {{ RTYPE, (t).bits }}) +#define ref2type(r) ((union irtype) {.bits = (r).i}) +#define rswap(a,b) do { union ref _t = (a); (a) = (b); (b) = _t; } while (0) + +enum op { + Oxxx, +#define _(o,...) O##o, +#include "op.def" +#undef _ + NOPER, +}; + +#define oiscmp(o) in_range(o, Oequ, Ougte) +#define oisarith(o) in_range(o, Oneg, Ougte) +#define oisalloca(o) in_range(o, Oalloca1, Oalloca16) +#define oisstore(o) in_range(o, Ostorei8, Ostoref64) +#define oisload(o) in_range(o, Oloads8, Oloadf64) +extern const char *opnames[]; +extern const uchar opnarg[]; + +enum intrin { + INxxx, +#define _(b,...) IN##b, +#include "intrin.def" +#undef _ +}; + +struct instr { + uchar op, + cls; /* operation data class; also result class except for cmp ops (always i32) */ + uchar skip : 1, /* ignore during codegen: forms part of one machine instruction */ + keep : 1; /* for codegen, keep instr even if result seems unused */ + uchar inplace : 1; /* set (by isel) for instructions which modify its first arg in place */ + uchar reg; /* 0 -> no reg; else reg + 1 */ + union ref l, r; /* args */ +}; +static_assert(sizeof(struct instr) == 4*3); + +enum jumpkind { JXXX, Jb, Jret, Jtrap, }; + +struct block { + int id; + int npred; + int visit; + ushort loop; + int inumstart; + union { + struct block *_pred0; + struct block **_pred; + }; + struct block *lprev, *lnext; + struct block *s1, *s2; + struct block *idom; + vec_of(ushort) phi; + vec_of(ushort) ins; + struct { uchar t; union ref arg[2]; } jmp; +}; + +#define blkpred(blk, i) 0[(blk)->npred < 2 ? &(blk)->_pred0 : &(blk)->_pred[i]] + +enum { USERJUMP = 0xFFFF }; +struct use { struct use *next; struct block *blk; ushort u; }; + +enum { MAXREGS = 64 }; +/** register set **/ +typedef uvlong regset; +#define BIT(x) (1ull<<(x)) +#define rsset(pS, r) (*(pS) |= 1ull << (r)) +#define rsclr(pS, r) (*(pS) &=~ (1ull << (r))) +#define rstest(S, r) ((S) >> (r) & 1) +static inline bool +rsiter(int *i, uvlong rs) +{ + if (*i > 63) return 0; + uvlong mask = -(1ull << *i); + if ((rs & mask) == 0) return 0; + *i = lowestsetbit(rs & mask); + return 1; +} + +enum { + FNBLKID = 1<<0, + FNUSE = 1<<1, + FNRPO = 1<<2, + FNDOM = 1<<3, +}; +struct function { + struct arena **arena, **passarena; + internstr name; + struct block *entry, *curblk; + struct use *use; + short *nuse; + union type fnty, retty; + struct abiarg *abiarg, abiret[2]; + uint prop; + uint nblk; + int stksiz; + ushort nabiarg, nabiret; + bool globl; + bool isleaf; + bool inlin; + regset regusage; +}; + +#define FREQUIRE(_prop) assert((fn->prop & (_prop)) == (_prop) && "preconditions not met") + +enum objkind { OBJELF }; + +struct mctarg { + short gpr0, /* first gpr */ + ngpr, /* gpr count */ + bpr, /* frame/base pointer reg */ + gprscratch, fprscratch, /* scratch registers for regalloc */ + fpr0, /* first fpr */ + nfpr; /* fpr count */ + regset rcallee, /* callee-saved */ + rglob; /* globally live (never used for regalloc) */ + const char (*rnames)[6]; + enum objkind objkind; + /* abiret: lower return type: + * scalar/small struct -> returns number of regs (1..2), + * r & cls filled with reg and irclass of each scalar return, + * for struct, r2off filled with byte offset within struct for 2nd reg + * big struct -> returns 0, is passed via hidden pointer argument, + * r[0] contains register for returning said pointer or -1, + * r[1] contains register for hidden pointer argument, + * *ni is set to 1 if said register is the first ABI integer argument + */ + int (*abiret)(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype); + /* abiarg: lower argument type: + * scalar/small struct -> returns number of regs (1..2), + * r & cls filled with reg and irclass of each scalar arg + * for struct, r2off filled with byte offset within struct for 2nd reg + * if reg == -1 -> stack + * big struct -> returns 0, + * if passed in stack cls[0] == 0, r[0] == negative SP offset + * if passed by pointer cls[0] == KPTR, r[0] contains integer register + * or negative SP offset if stack + */ + int (*abiarg)(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype); + void (*vastart)(struct function *, struct block *, int *curi); + void (*vaarg)(struct function *, struct block *, int *curi); + void (*isel)(struct function *); + void (*emit)(struct function *); +}; + +enum { MAXINSTR = 1<<15 }; + +/** ir.c **/ +extern uchar type2cls[]; +extern uchar cls2siz[]; +extern uchar cls2load[]; +extern uchar cls2store[]; +extern const uchar siz2intcls[]; +extern struct instr instrtab[]; +extern struct use *instruse[]; +extern struct calltab {vec_of(struct call);} calltab; +extern struct phitab {vec_of(union ref *);} phitab; +extern struct dattab {vec_of(struct irdat);} dattab; +extern struct contab {vec_of(struct xcon);} contab; +extern struct addrtab {vec_of(struct addr);} addrtab; +extern int visitmark; +#define mkinstr(O, C, ...) ((struct instr) { .op = (O), .cls = (C), .reg=0, __VA_ARGS__ }) +#define mkarginstr(ty, x) mkinstr(Oarg, 0, mktyperef(ty), (x)) +void irinit(struct function *); +void irfini(struct function *); +#define cls2type(k) ((union irtype){.cls=(k)}) +union irtype mkirtype(union type); +union ref newxcon(const struct xcon *); +union ref mkintcon(enum irclass, vlong); +union ref mkfltcon(enum irclass, double); +#define iscon(r) in_range((r).t, RICON, RXCON) +#define concls(r) ((r).t == RICON ? KI32 : contab.p[(r).i].cls) +#define isintcon(r) (iscon(r) && kisint(concls(r))) +#define isfltcon(r) ((r).t == RXCON && kisflt(contab.p[(r).i].cls)) +#define isnumcon(r) ((r).t == RICON || ((r).t == RXCON && contab.p[(r).i].cls)) +#define isaddrcon(r,derefok) ((r).t == RXCON && !contab.p[(r).i].cls && (derefok || !contab.p[(r).i].deref)) +#define intconval(r) ((r).t == RICON ? (r).i : contab.p[(r).i].i) +#define fltconval(r) ((r).t == RICON ? (r).i : contab.p[(r).i].f) +union ref mksymref(internstr, enum symflags); +union ref mkdatref(internstr sym, union type ctype, uint siz, uint align, + const void *, uint n, bool deref, bool funclocal); +internstr xcon2sym(int ref); +struct instr mkalloca(uint siz, uint align); +union ref mkcallarg(union irtype ret, uint narg, int vararg); +#define mkintrin(B, C, N) mkinstr(Ointrin, C, {{.t=RICON,B}}, mkcallarg((union irtype){{0}},N,-1)) +union ref mkaddr(struct addr); +void addpred(struct block *blk, struct block *p); + +struct block *newblk(struct function *); +void freeblk(struct function *, struct block *); +struct block *insertblk(struct function *, struct block *pred, struct block *subst); +struct block *blksplitafter(struct function *, struct block *, int idx); +void adduse(struct block *ublk, int ui, union ref r); +int newinstr(struct block *at, struct instr ins); +union ref insertinstr(struct block *, int idx, struct instr); +union ref insertphi(struct block *, enum irclass cls); +void replcuses(union ref from, union ref to); +bool deluse(struct block *ublk, int ui, union ref r); +void deluses(int ins); +void filluses(struct function *); +void delinstr(struct block *, int idx); +void delphi(struct block *, int idx); +void delnops(struct block *blk); +void delpred(struct block *blk, struct block *p); +void fillblkids(struct function *); +#define startbbvisit() (void)(++visitmark) +#define wasvisited(blk) ((blk)->visit == visitmark) +#define markvisited(blk) ((blk)->visit = visitmark) +uint numberinstrs(struct function *); +bool blkreachable(struct function *fn, struct block *blk); + +/** builder.c **/ +union ref irbinop(struct function *, enum op, enum irclass, union ref lhs, union ref rhs); +union ref irunop(struct function *, enum op, enum irclass, union ref); +union ref addinstr(struct function *, struct instr); +union ref addphi(struct function *, enum irclass, union ref []); +void useblk(struct function *, struct block *); +void putbranch(struct function *, struct block *); +void putcondbranch(struct function *, union ref arg, struct block *t, struct block *f); +void putreturn(struct function *, union ref r0, union ref r1); +void puttrap(struct function *); + +/** fold.c **/ +bool foldbinop(union ref *to, enum op, enum irclass, union ref l, union ref r); +bool foldunop(union ref *to, enum op, enum irclass, union ref); + +/** irdump.c **/ +void irdump(struct function *); + +/** mem2reg.c **/ +void mem2reg(struct function *); + +/** ssa.c **/ +void copyopt(struct function *); + +/** cfg.c **/ +void sortrpo(struct function *); +void filldom(struct function *); +void fillloop(struct function *); + +/** abi0.c **/ +void abi0(struct function *); +void abi0_call(struct function *, struct instr *, struct block *blk, int *curi); + +/** simpl.c **/ +void simpl(struct function *); + +/** cselim.c **/ +void cselim(struct function *); + +/** inliner.c **/ +bool maybeinlinee(struct function *); +void doinline(struct function *); + +/** intrin.c **/ +void lowerintrin(struct function *); + +/** stack.c **/ +void lowerstack(struct function *); + +/** regalloc.c **/ +void regalloc(struct function *); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_abi0.c b/src/ir_abi0.c new file mode 100644 index 0000000..a3687d9 --- /dev/null +++ b/src/ir_abi0.c @@ -0,0 +1,462 @@ +#include "ir.h" + +/** This pass adds in ABI arguments/returns register mappings + ** and lowers aggregate params/args/returns into scalars + ** + ** invariant: all `call` instructions when doing this pass shall be preceded by + ** exactly narg `arg` instructions with no other instructions in between + **/ + +struct abiargsvec { vec_of(struct abiarg); }; + +static int +abiret(struct abiarg abiret[2], struct abiargsvec *abiargs, uchar *r2off, int *ni, union irtype retty) +{ + short r[2]; + uchar cls[2]; + int retreg = 0; + retreg = mctarg->abiret(r, cls, r2off, ni, retty); + if (retty.isagg) { + if (!retreg) { + vpush(abiargs, ((struct abiarg) { cls2type(KPTR), .reg = r[1] })); + if (r[0] == -1) { + memset(abiret, 0, 2*sizeof *abiret); + } else { + abiret[0].ty = cls2type(KPTR); + abiret[0].reg = r[0]; + } + } + } else if (retty.cls) { + assert(retreg == 1); + } + for (int i = 0; i < retreg; ++i) { + abiret[i].ty = cls2type(cls[i]); + abiret[i].isstk = 0; + abiret[i].reg = r[i]; + } + return retreg; +} + +static int +abiarg(struct abiargsvec *abiargs, uchar *r2off, int *ni, int *nf, int *ns, union irtype ty) +{ + short r[2]; + uchar cls[2]; + int ret = mctarg->abiarg(r, cls, r2off, ni, nf, ns, ty); + if (!ret) { /* in stack */ + vpush(abiargs, ((struct abiarg) { ty, .isstk = 1, .stk = r[0] })); + } else if (ret == 1 && ty.isagg && cls[0] == KPTR) { /* aggregate by pointer */ + vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] })); + } else { /* by regs */ + vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] })); + if (ret == 2) + vpush(abiargs, ((struct abiarg) { cls2type(cls[1]), .reg = r[1] })); + } + return ret; +} + +static struct instr +copyparam(struct function *fn, int *curi, int param, struct abiarg abi) +{ + struct instr par = mkinstr(Oparam, abi.ty.cls, mkref(RICON, param), mktyperef(abi.ty)); + if (!abi.isstk) { /* reg */ + assert(!abi.ty.isagg); + return par; + } + par.r = mktyperef((union irtype){.cls = KPTR}); + if (!abi.ty.isagg) { /* scalar in stack */ + enum op ld; + par.cls = KPTR; + if (abi.ty.cls == KPTR) abi.ty.cls = siz2intcls[cls2siz[abi.ty.cls]]; + switch (abi.ty.cls) { + default: assert(0); + case KI32: ld = Oloads32; break; + case KI64: ld = Oloadi64; break; + case KF32: ld = Oloadf32; break; + case KF64: ld = Oloadf64; break; + } + return mkinstr(ld, abi.ty.cls, insertinstr(fn->entry, (*curi)++, par)); + } else { /* aggregate in stack */ + par.cls = KPTR; + return par; + } +} + +static void +patchparam(struct function *fn, int *curi, int *param, int tydat, int nabi, struct abiarg abi[2], uchar r2off) +{ + struct block *blk = fn->entry; + assert(in_range(nabi,1,2)); + + for (; *curi < blk->ins.n; ++*curi) { + struct instr *ins = &instrtab[blk->ins.p[*curi]]; + if (ins->op != Oparam) continue; + assert(ins->r.t == RTYPE + && ins->r.i == (tydat < 0 ? abi[0].ty : (union irtype){.isagg=1, .dat=tydat}).bits); + if (abi[0].ty.isagg || tydat < 0) { + /* aggregate in stack or scalar, just copy */ + assert(nabi == 1); + *ins = copyparam(fn, curi, *param, abi[0]); + } else { /* aggregate in registers, materialize */ + union ref alloc, r[2]; + struct instr st; + const struct typedata *td; + uint nalloc; + uint align; + + assert(tydat >= 0); + td = &typedata[tydat]; + assert(td->siz <= 16 && td->align <= 16); + align = td->siz <= 4 ? 4 : alignup(td->align, 8); + nalloc = td->siz/align + (td->siz%align != 0); + *ins = mkinstr(Oalloca1 + ilog2(align), KPTR, mkref(RICON, nalloc)); + alloc = mkref(RTMP, ins - instrtab); + r[0] = insertinstr(blk, ++*curi, copyparam(fn, NULL, *param, abi[0])); + if (nabi > 1) + r[1] = insertinstr(blk, ++*curi, copyparam(fn, NULL, ++*param, abi[1])); + /* transform + * %x = copy %p + * into + * %x = alloca... + * store* %x, %a + * store* %x + N, %b + */ + st = mkinstr(cls2store[abi[0].ty.cls], 0, alloc, r[0]); + insertinstr(blk, ++*curi, st); + if (nabi > 1) { + struct instr tmp = mkinstr(Oadd, KPTR, alloc, mkref(RICON, r2off)); + st = mkinstr(cls2store[abi[1].ty.cls], 0, insertinstr(blk, ++*curi, tmp), r[1]); + insertinstr(blk, ++*curi, st); + } + } + ++*param; + ++*curi; + break; + } +} + +static void +load2regs(union ref out[2], union irtype typ, union ref src, int nabi, struct abiarg abi[2], uchar r2off, struct block *blk, int *curi) +{ + uint align = typedata[typ.dat].align; + uint siz = typedata[typ.dat].siz; + if (src.t == RTMP && oisalloca(instrtab[src.i].op)) { + /* use actual alignment as opposed to min required type alignment */ + uint aalign = 1 << (instrtab[src.i].op - Oalloca1); + assert(aalign >= align); + align = aalign; + } + /* deconstruct into + * %a = load* %x + * (%b = load* %x + N) + */ + /* XXX this generates pretty bad code for small-alignment structs even on platforms where unaligned loads are available.. */ + if (align >= 4) { + for (int i = 0; i < nabi; ++i) { + struct instr ins = {0}; + union ref temp; + switch (ins.cls = abi[i].ty.cls) { + default: assert(0); + case KI32: ins.op = Oloadu32; break; + case KI64: ins.op = Oloadi64; break; + case KF32: ins.op = Oloadf32; break; + case KF64: ins.op = Oloadf64; break; + } + if (i == 0) + ins.l = src; + else { + struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, r2off)); + ins.l = insertinstr(blk, (*curi)++, adr); + } + temp = insertinstr(blk, (*curi)++, ins); + //insertinstr(blk, (*curi)++, mkarginstr(abi[i].ty, temp)); + out[i] = temp; + } + } else { + for (int i = 0; i < nabi; ++i) { + struct instr ld = {0}; + union ref reg, temp; + uint n = cls2siz[abi[i].ty.cls] / align; + assert(n > 0); + ld.op = Oloadu8 + ilog2(align)*2; + ld.cls = abi[i].ty.cls; + for (int o = 0; o < n && (i*cls2siz[ld.cls])+o*align < siz; ++o) { + if (i+o == 0) + ld.l = src; + else { + struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, (i == 0 ? 0 : r2off) + o*align)); + ld.l = insertinstr(blk, (*curi)++, adr); + } + temp = insertinstr(blk, (*curi)++, ld); + if (o > 0) { + union ref t = insertinstr(blk, (*curi)++, mkinstr(Oshl, ld.cls, temp, mkref(RICON, o*align*8))); + reg = insertinstr(blk, (*curi)++, mkinstr(Oior, ld.cls, reg, t)); + } else { + reg = temp; + } + } + //insertinstr(blk, arginst++, mkarginstr(abi[i].ty, reg)); + out[i] = reg; + } + } +} + +static int +patcharg(struct block *blk, int *icall, struct call *call, + int argidx, int nabi, struct abiarg abi[2], uchar r2off) +{ + int arginst = *icall - (call->narg - argidx); + struct instr *arg = &instrtab[blk->ins.p[arginst]]; + assert(arg->op == Oarg && arg->l.t == RTYPE); + if (ref2type(arg->l).isagg) { /* aggregate argument */ + if (abi[0].ty.isagg) { /* aggregate in stack */ + /* XXX do this better.. */ + /* ptr %dst = arg <stk dst> */ + /* (blit %dst, %src) */ + union ref dst = mkref(RTMP, arg - instrtab); + uint align = typedata[abi->ty.dat].align, siz = typedata[abi->ty.dat].siz; + union ref src = arg->r; + if (src.t == RTMP && oisalloca(instrtab[src.i].op)) { + align = 1 << (instrtab[src.i].op - Oalloca1); + } + assert(align <= 8); + arg->cls = KPTR; + arg->r = mkref(RICON, abi->stk); + for (uint off = 0; off < siz; off += align) { + union ref sadr = off == 0 ? src : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, src, mkref(RICON, off))); + union ref tmp = insertinstr(blk, ++arginst, mkinstr(Oloads8+2*ilog2(align), align < 8 ? KI32 : KI64, sadr)); + union ref dadr = off == 0 ? dst : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, dst, mkref(RICON, off))); + insertinstr(blk, ++arginst, mkinstr(Ostorei8+ilog2(align), 0, dadr, tmp)); + } + *icall = arginst + (call->narg - argidx); + return 1; + } else if (abi[0].ty.cls == KPTR) { /* aggregate by pointer */ + arg->cls = KPTR; + return 1; + } else { /* aggregate in registers */ + union ref r[2]; + delinstr(blk, arginst); + load2regs(r, ref2type(arg->l), arg->r, nabi, abi, r2off, blk, &arginst); + for (int i = 0; i < nabi; ++i) + insertinstr(blk, arginst++, mkinstr(Oarg, 0, mktyperef(abi[i].ty), r[i])); + *icall = arginst + (call->narg - argidx - 1); + return nabi; + } + } else { /* normal scalar argument */ + return 1; + } +} +void +abi0_call(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + union ref retmem; + struct abiarg abiargsbuf[32]; + struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))}; + bool sretarghidden = 0; + int ni, nf, ns, vararg, nret = 0; + struct call *call = &calltab.p[ins->r.i]; + + vararg = call->vararg; + ni = nf = ns = 0; + assert(!ins->cls == !call->ret.bits); + nret = abiret(call->abiret, &abiargs, &call->r2off, &ni, call->ret); + if (call->ret.isagg) { /* adjust struct return */ + union irtype retty = call->ret; + struct typedata *td = &typedata[retty.dat]; + uint align = td->align, ralign; + struct instr alloca; + int ialloca; + for (int i = 0; i < nret; ++i) + align = align < (ralign = cls2siz[call->abiret[i].ty.cls]) ? ralign : align; + alloca = mkalloca(td->siz, align); + + sretarghidden = ni == 0; + /* swap alloca and call temps so users of original call point to alloca */ + retmem = insertinstr(blk, ialloca = (*curi)++ - call->narg, *ins); + *ins = alloca; + blk->ins.p[ialloca] = ins - instrtab; + blk->ins.p[*curi] = retmem.i; + ins = &instrtab[retmem.i]; + retmem.i = blk->ins.p[ialloca]; + + if (!nret) /* hidden pointer argument */ + insertinstr(blk, (*curi)++ - call->narg, + mkinstr(Oarg, 0, mktyperef((union irtype){.cls=KPTR}), retmem)); + } + + /* adjust args */ + for (int i = 0, i2 = ni + sretarghidden; i < call->narg; ++i) { + int arginst = *curi - (call->narg - i); + struct instr *arg = &instrtab[blk->ins.p[arginst]]; + union irtype pty = ref2type(arg->l); + uchar r2off; + int first = abiargs.n; + int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty); + ret = patcharg(blk, curi, call, i, ret, &abiargs.p[first], r2off); + if (call->vararg == i) vararg = i2; + i2 += ret; + } + call->argstksiz = ns; + /* adjust return */ + if (call->ret.isagg) { + ins->cls = 0; + if (!nret) { /* hidden pointer argument */ + ins->cls = 0; + if (!call->abiret[0].isstk) { + /* the result location pointer is also returned by the callee, e.g. in x86 */ + ins->cls = KPTR; + ++nret; + /* even if this is not used, the register copy + * must be emitted for the register allocator to know */ + } + } else { /* aggregate returned in regs */ + union ref r[2]; + struct instr ret2; + assert(in_range(nret, 1, 2)); + ins->cls = call->abiret[0].ty.cls; + r[0] = mkref(RTMP, ins - instrtab); + if (nret == 2) { + ret2 = mkinstr(Ocall2r, call->abiret[1].ty.cls, r[0]); + r[1] = insertinstr(blk, ++*curi, ret2); + } + for (int i = 0; i < nret; ++i) { + struct instr store = { cls2store[call->abiret[i].ty.cls] }; + if (i == 0) { + store.l = retmem; + } else { + struct instr addr = mkinstr(Oadd, KPTR, retmem, mkref(RICON, call->r2off)); + store.l = insertinstr(blk, ++*curi, addr); + } + store.r = r[i]; + insertinstr(blk, ++*curi, store); + } + } + } + + if (call->ret.isagg) call->ret = (union irtype){0}; + call->vararg = vararg; + call->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof(struct abiarg), 0); + call->narg = abiargs.n; + vfree(&abiargs); +} + +void +abi0(struct function *fn) +{ + struct abiarg abiargsbuf[32]; + uint nparam = typedata[fn->fnty.dat].nmemb; + const union type *paramty = typedata[fn->fnty.dat].param; + struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))}; + int rvovar = -1; + int ni = 0, nf = 0, ns = 0, istart = 0; + uchar r2off; + struct block *blk; + union ref sret = {0}; + + FREQUIRE(FNUSE); + + if (fn->retty.t == TYVOID) { + fn->nabiret = 0; + } else { + fn->nabiret = abiret(fn->abiret, &abiargs, &r2off, &ni, mkirtype(fn->retty)); + if (!fn->nabiret && isagg(fn->retty)) { /* ret agg by hidden pointer */ + struct instr param = copyparam(fn, NULL, 0, abiargs.p[0]); + sret = insertinstr(fn->entry, 0, param); + ++istart; + /* increment real param ordinals */ + for (int i = 1; i < fn->entry->ins.n; ++i) { + struct instr *ins = &instrtab[fn->entry->ins.p[i]]; + if (ins->op == Oparam) ++ins->l.i; + } + } + } + + /* adjust params */ + for (int i = 0, param = abiargs.n; i < nparam; ++i) { + union irtype pty = mkirtype(paramty[i]); + int first = abiargs.n; + uchar r2off; + int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty); + patchparam(fn, &istart, ¶m, pty.isagg ? pty.dat : -1, ret+!ret, &abiargs.p[first], r2off); + } + fn->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof *abiargs.p, 0); + fn->nabiarg = abiargs.n; + vfree(&abiargs); + + if (!fn->nabiret && isagg(fn->retty)) { + /* for structures returned by hidden pointer argument, + * if all return instrs return local var X, make X point to the result location, + * (return value optimization (RVO)) */ + blk = fn->entry; + do { + union ref arg = blk->jmp.arg[0]; + if (blk->jmp.t != Jret) continue; + if (!arg.bits) continue; + if (arg.t != RTMP || !oisalloca(instrtab[arg.i].op)) { + rvovar = -1; + break; + } + if (rvovar == -1) { + rvovar = arg.i; + } else if (arg.i != rvovar) { + rvovar = -1; + break; + } + } while ((blk = blk->lnext) != fn->entry); + if (rvovar != -1) + instrtab[rvovar] = mkinstr(Ocopy, KPTR, sret); + } + + blk = fn->entry->lnext; + int id = 1; + do { + /* adjust vaargs and calls */ + for (int iinstr = 0; iinstr < blk->ins.n; ++iinstr) { + struct instr *ins = &instrtab[blk->ins.p[iinstr]]; + if (ins->op == Ovastart) mctarg->vastart(fn, blk, &iinstr); + else if (ins->op == Ovaarg) mctarg->vaarg(fn, blk, &iinstr); + else if (ins->op == Ocall) abi0_call(fn, ins, blk, &iinstr); + } + + /* adjust returns */ + if (isagg(fn->retty) && blk->jmp.t == Jret && blk->jmp.arg[0].bits) { + assert(!blk->jmp.arg[1].bits); + if (fn->nabiret) { /* aggregate return in register(s) */ + union ref r[2]; + int curi = blk->ins.n; + load2regs(r, mkirtype(fn->retty), blk->jmp.arg[0], fn->nabiret, fn->abiret, r2off, blk, &curi); + for (int i = 0; i < fn->nabiret; ++i) { + blk->jmp.arg[i] = r[i]; + adduse(blk, USERJUMP, r[i]); + } + } else { + /* aggregate return (arg[0] is pointer to return value) */ + if (rvovar == -1) { + /* blit %sret, %arg */ + union irtype typ = mkirtype(fn->retty); + insertinstr(blk, blk->ins.n, mkarginstr(typ, sret)); + insertinstr(blk, blk->ins.n, mkarginstr(typ, blk->jmp.arg[0])); + insertinstr(blk, blk->ins.n, mkintrin(INstructcopy, 0, 2)); + } else assert(blk->jmp.arg[0].bits == mkref(RTMP, rvovar).bits); + if (fn->abiret[0].ty.cls) { + blk->jmp.arg[0] = rvovar == -1 ? sret : mkref(RTMP, rvovar); + adduse(blk, USERJUMP, blk->jmp.arg[0]); + } + else memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + } + } + blk->id = id++; + } while ((blk = blk->lnext) != fn->entry); + + + /* vaargs might break these */ + if (!(fn->prop & FNUSE)) filluses(fn); + fn->prop &= ~(FNBLKID | FNRPO); + + if (ccopt.dbg.a) { + bfmt(ccopt.dbgout, "<< After abi0 >>\n"); + irdump(fn); + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_builder.c b/src/ir_builder.c new file mode 100644 index 0000000..206e66d --- /dev/null +++ b/src/ir_builder.c @@ -0,0 +1,307 @@ +#include "ir.h" + +/* binary arithmetic builder with peephole optimizations */ +union ref +irbinop(struct function *fn, enum op op, enum irclass k, union ref l, union ref r) +{ + static const union ref ONE = {.t=RICON, .i=1}; + vlong iv; + union ref c; + + if (foldbinop(&c, op, k, l, r)) + return c; + + switch (op) { + case Oadd: + if (l.bits == ZEROREF.bits) return r; /* 0 + x ==> x */ + if (r.bits == ZEROREF.bits) return l; /* x + 0 ==> x */ + break; + case Osub: + if (r.bits == ZEROREF.bits) return l; /* x - 0 ==> x */ + if (kisint(k) && l.bits == r.bits) return ZEROREF; /* x - x ==> 0 */ + break; + case Omul: + if (isnumcon(l)) rswap(l, r); /* put const in rhs */ + if (kisflt(k)) break; + if (r.bits == ZEROREF.bits) /* x * 0 ==> 0 */ + return ZEROREF; + if (r.bits == ONE.bits) /* x * 1 ==> x */ + return l; + if (isintcon(r) && ispo2(iv = intconval(r))) { + /* x * 2^y ==> x << y */ + op = Oshl; + r = mkref(RICON, ilog2(iv)); + } + break; + case Odiv: + break; + case Oudiv: + if (kisflt(k)) break; + if (isintcon(r) && ispo2(iv = intconval(r))) { + /* x / 2^y ==> x >> y */ + op = Oslr; + r = mkref(RICON, ilog2(iv)); + } + break; + case Orem: + break; + case Ourem: + if (kisflt(k)) break; + if (isintcon(r) && ispo2(iv = intconval(r))) { + /* x % 2^y ==> x & 2^y-1 */ + op = Oand; + r = mkintcon(k, iv - 1); + } + break; + case Oand: + if (isnumcon(l)) rswap(l, r); /* put const in rhs */ + if (r.bits == ZEROREF.bits) /* x & 0 ==> 0 */ + return ZEROREF; + break; + case Oior: + if (isnumcon(l)) rswap(l, r); /* put const in rhs */ + if (r.bits == ZEROREF.bits) /* x | 0 ==> x */ + return l; + case Oxor: + if (isnumcon(l)) rswap(l, r); /* put const in rhs */ + if (r.bits == ZEROREF.bits) /* x ^ 0 ==> x */ + return l; + case Oshl: case Osar: case Oslr: + if (r.bits == ZEROREF.bits) /* x shift 0 ==> x */ + return l; + break; + case Oequ: + if (r.bits == l.bits && kisint(k)) + return ONE; + break; + case Oneq: + if (r.bits == l.bits && kisint(k)) + return ZEROREF; + break; + case Olth: case Ogth: + case Olte: case Ogte: + break; + case Oulth: + if (r.bits == ZEROREF.bits) /* x u< 0 ==> f */ + return ZEROREF; + break; + case Ougth: + if (l.bits == ZEROREF.bits) /* 0 u> x ==> f */ + return ZEROREF; + break; + case Oulte: + if (l.bits == ZEROREF.bits) /* 0 u<= x ==> t */ + return ONE; + break; + case Ougte: + if (r.bits == ZEROREF.bits) /* x u>= 0 ==> t */ + return ONE; + break; + default: + assert(!"binop?"); + } + return fn ? addinstr(fn, mkinstr(op, k, l, r)) : NOREF; +} + +/* implements f32/f64 -> u64 conversion */ +static union ref +cvtfu64(struct function *fn, enum irclass from, union ref x) +{ + struct block *t, *f, *merge; + union ref tmp, phiarg[2]; + /* if (x < 2p63) cvtfXs(x) else (cvtfXs(x - 2p63) | (1<<63)) */ + union ref max = mkfltcon(from, 0x1.0p63); + enum op cvt = from == KF32 ? Ocvtf32s : Ocvtf64s; + putcondbranch(fn, irbinop(fn, Olth, from, x, max), t = newblk(fn), f = newblk(fn)); + + useblk(fn, t); + phiarg[0] = irunop(fn, cvt, KI64, x); + putbranch(fn, merge = newblk(fn)); + + useblk(fn, f); + tmp = irbinop(fn, Osub, from, x, max); + tmp = irunop(fn, cvt, KI64, tmp); + phiarg[1] = irbinop(fn, Oior, KI64, tmp, mkintcon(KI64, 1ull<<63)); + putbranch(fn, merge); + + useblk(fn, merge); + return addphi(fn, KI64, phiarg); +} + +/* implements u64 -> f32/f64 conversion */ +static union ref +cvtu64f(struct function *fn, enum irclass to, union ref x) +{ + struct block *t, *f, *merge; + union ref t1, t2, phiarg[2]; + + /* if ((s64)x >= 0) cvts64f(x) else cvts64f((x>>1)|(x&1))*2 */ + + putcondbranch(fn, irbinop(fn, Ogte, KI64, x, ZEROREF), t = newblk(fn), f = newblk(fn)); + + useblk(fn, t); + phiarg[0] = irunop(fn, Ocvts64f, to, x); + putbranch(fn, merge = newblk(fn)); + + useblk(fn, f); + t1 = irbinop(fn, Oslr, KI64, x, mkref(RICON, 1)); + t2 = irbinop(fn, Oand, KI64, x, mkref(RICON, 1)); + t1 = irbinop(fn, Oior, KI64, t1, t2); + t1 = irunop(fn, Ocvts64f, to, t1); + phiarg[1] = irbinop(fn, Oadd, to, t1, t1); + putbranch(fn, merge); + + useblk(fn, merge); + return addphi(fn, to, phiarg); +} + +union ref +irunop(struct function *fn, enum op op, enum irclass k, union ref a) +{ + union ref c; + struct instr *ins = NULL; + if (foldunop(&c, op, k, a)) + return c; + if (a.t == RTMP) ins = &instrtab[a.i]; + switch (op) { + case Oneg: + if (ins && ins->op == Oneg) /* -(-x) ==> x */ + return ins->l; + break; + case Onot: + if (ins && ins->op == Onot) /* ~(~x) ==> x */ + return ins->l; + break; + case Ocvtf32s: case Ocvtf32f64: case Ocvtf64s: + case Ocvtf64f32: case Ocvts32f: case Ocvtu32f: + case Ocvts64f: + break; + case Ocvtf32u: case Ocvtf64u: + if (target.arch == ISx86_64 && k == KI64 && fn) { + /* this should probably be handled in a separate "arithmetic-lowering" pass, earlier than isel + */ + return cvtfu64(fn, op == Ocvtf32u ? KF32 : KF64, a); + } + break; + case Ocvtu64f: + /* XXX see above */ + if (target.arch == ISx86_64 && fn) + return cvtu64f(fn, k, a); + case Oexts8: case Oextu8: case Oexts16: case Oextu16: + case Oexts32: case Oextu32: + case Ocopy: + break; + case Obswap16: case Obswap32: case Obswap64: + break; + default: assert(!"unop?"); + } + return fn ? addinstr(fn, mkinstr(op, k, a)) : NOREF; +} + +int allocinstr(void); + +union ref +addinstr(struct function *fn, struct instr ins) +{ + int new = allocinstr(); + assert(fn->curblk != NULL); + instrtab[new] = ins; + adduse(fn->curblk, new, ins.l); + adduse(fn->curblk, new, ins.r); + vpush(&fn->curblk->ins, new); + return mkref(RTMP, new); +} + +void +useblk(struct function *fn, struct block *blk) +{ + extern int nerror; + if (fn->curblk && nerror == 0) assert(fn->curblk->jmp.t && "never finished block"); + if (blk) assert(!blk->jmp.t && "reusing built block"); + if (blk && !blk->lprev) { /* initialize */ + blk->lnext = fn->entry; + blk->lprev = fn->entry->lprev; + blk->lprev->lnext = blk; + blk->id = fn->nblk++; + fn->entry->lprev = blk; + } + fn->curblk = blk; +} + +union ref +addphi(struct function *fn, enum irclass cls, union ref *r) +{ + assert(fn->curblk); + if (fn->curblk->npred == 0) return UNDREF; + if (fn->curblk->npred == 1) /* 1-argument phi is identity */ + return *r; + + union ref *refs = NULL; + xbgrow(&refs, fn->curblk->npred); + memcpy(refs, r, fn->curblk->npred * sizeof *r); + vpush(&phitab, refs); + /*assert(fn->curblk->ins.n == 0);*/ + int new = allocinstr(); + instrtab[new] = mkinstr(Ophi, cls, .l.i = phitab.n-1); + for (int i = 0; i < fn->curblk->npred; ++i) { + adduse(fn->curblk, new, r[i]); + } + vpush(&fn->curblk->phi, new); + return mkref(RTMP, new); +} + +#define putjump(fn, j, arg0, arg1, T, F) \ + fn->curblk->jmp.t = j; \ + fn->curblk->jmp.arg[0] = arg0; \ + fn->curblk->jmp.arg[1] = arg1; \ + fn->curblk->s1 = T; \ + fn->curblk->s2 = F; \ + fn->curblk = NULL; + +void +putbranch(struct function *fn, struct block *blk) +{ + assert(fn->curblk && blk); + addpred(blk, fn->curblk); + putjump(fn, Jb, NOREF, NOREF, blk, NULL); +} + +void +putcondbranch(struct function *fn, union ref arg, struct block *t, struct block *f) +{ + assert(fn->curblk && t && f); + if (iscon(arg)) { + bool truthy; + if (isintcon(arg)) truthy = intconval(arg) != 0; + else if (isfltcon(arg)) truthy = fltconval(arg) != 0.0; + else if (isaddrcon(arg,0)) truthy = 1; /* XXX ok to assume symbols have non null addresses? */ + else goto Cond; + putbranch(fn, truthy ? t : f); + } else { + Cond: + adduse(fn->curblk, USERJUMP, arg); + addpred(t, fn->curblk); + addpred(f, fn->curblk); + putjump(fn, Jb, arg, NOREF, t, f); + } +} + +void +putreturn(struct function *fn, union ref r0, union ref r1) +{ + assert(fn->curblk); + adduse(fn->curblk, USERJUMP, r0); + adduse(fn->curblk, USERJUMP, r1); + putjump(fn, Jret, r0, r1, NULL, NULL); +} + +void +puttrap(struct function *fn) +{ + assert(fn->curblk); + putjump(fn, Jtrap, NOREF, NOREF, NULL, NULL); +} + +#undef putjump + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_cfg.c b/src/ir_cfg.c new file mode 100644 index 0000000..86af3f3 --- /dev/null +++ b/src/ir_cfg.c @@ -0,0 +1,130 @@ +#include "ir.h" + +static void +porec(int *nblk, struct block ***rpo, struct block *b) +{ + if (wasvisited(b)) return; + assert(*nblk > 0 && "nblk bad"); + --*nblk; + markvisited(b); + if (b->s2) porec(nblk, rpo, b->s2); + if (b->s1) porec(nblk, rpo, b->s1); + *--*rpo = b; +} + +/* also blkid */ +void +sortrpo(struct function *fn) +{ + static struct block **rpobuf; + struct block **rpoend, **rpo, *blk, *next; + int i, ndead; + + xbgrow(&rpobuf, fn->nblk); + rpo = rpoend = rpobuf + fn->nblk, + + startbbvisit(); + fn->entry->id = 0; + int nblk = fn->nblk; + porec(&nblk, &rpo, fn->entry); + ndead = rpo - rpobuf; + if (ndead > 0) for (blk = fn->entry->lprev; blk != fn->entry; blk = next) { + next = blk->lprev; + if (!wasvisited(blk)) { + for (int i = 0; i < blk->ins.n; ++i) { + /* if unreachable block has alloca pseudo-instrs, move them to the entry + * to be able to delete it */ + if (oisalloca(instrtab[blk->ins.p[i]].op)) { + vpush(&fn->entry->ins, blk->ins.p[i]); + } + } + for (int i = 0; i < blk->npred; ++i) + assert(!wasvisited(blkpred(blk, i))); + freeblk(fn, blk); + --ndead; + } + } + for (i = 1, ++rpo; rpo < rpoend; ++rpo, ++i) { + rpo[-1]->lnext = rpo[0]; + rpo[0]->lprev = rpo[-1]; + rpo[0]->id = i; + } + fn->entry->lprev = rpo[-1]; + rpo[-1]->lnext = fn->entry; + + fn->prop |= FNBLKID | FNRPO; +} + +/* also blkid */ +void +filldom(struct function *fn) +{ + struct block *blk = fn->entry; + int i = 0; + + FREQUIRE(FNRPO); + + /* Implements 'A Simple, Fast Dominance Algorithm' by K. Cooper, T. Harvey, and K. Kennedy */ + do blk->id = i++, blk->idom = NULL; while ((blk = blk->lnext) != fn->entry); + fn->entry->idom = fn->entry; + for (bool changed = 1; changed;) { + changed = 0; + do { + int j; + struct block *new = NULL; + if (blk->npred == 0) continue; + for (j = 0; j < blk->npred; ++j) + if ((new = blkpred(blk, j))->id < blk->id) break; + assert(new); + for (int i = 0; i < blk->npred; ++i) { + if (i == j) continue; + struct block *p = blkpred(blk, i); + if (p->idom) { /* new = intersect(p, new) */ + while (p != new) { + while (p->id > new->id) p = p->idom; + while (p->id < new->id) new = new->idom; + } + } + } + if (blk->idom != new) { + blk->idom = new; + changed = 1; + } + } while ((blk = blk->lnext) != fn->entry); + } + fn->prop |= FNBLKID | FNDOM; +} + +static void +loopmark(struct block *head, struct block *blk) +{ + if (blk->id < head->id || blk->visit == head->id) return; + blk->visit = head->id; + ++blk->loop; + for (int i = 0; i < blk->npred; ++i) + loopmark(head, blkpred(blk, i)); +} + +void +fillloop(struct function *fn) +{ + struct block *b = fn->entry; + int id = 0; + FREQUIRE(FNRPO); + do { + b->id = id++; + b->visit = -1u; + b->loop = 0; + } while ((b = b->lnext) != fn->entry); + do { + for (int i = 0; i < b->npred; ++i) { + struct block *p = blkpred(b, i); + if (p->id > b->id) { /* b is loop header */ + loopmark(b, p); + } + } + } while ((b = b->lnext) != fn->entry); + fn->prop |= FNBLKID; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_cse.c b/src/ir_cse.c new file mode 100644 index 0000000..23c5cf9 --- /dev/null +++ b/src/ir_cse.c @@ -0,0 +1,92 @@ +#include "ir.h" + +static inline bool +pure(const struct instr *ins) +{ + return oisarith(ins->op) || (oisload(ins->op) && !ins->keep); +} + +static inline bool +insequ(const struct instr *a, const struct instr *b) +{ + if (a->op != b->op) return 0; + enum op op = a->op; + switch (opnarg[op]) { + default: assert(0); + case 2: if (a->r.bits != b->r.bits) return 0; + case 1: if (a->l.bits != b->l.bits) return 0; + } + return 1; +} + +static struct ht { + uint t; + ushort memno, cutoff; + struct block *b; +} *insht; +static uint ninsht; + +static inline size_t +hashins(const struct instr *ins) +{ + return hashb(0, ins, sizeof *ins); +} + +static bool +doms(struct block *blk, struct block *b) +{ + for (;; b = b->idom) { + if (blk == b) return 1; + if (blk == b->idom) return 1; + if (blk->id > b->id) return 0; + } +} + +static int +uniq(int t, struct block *blk, int cutoff, int memno) +{ + assert((uint)t < MAXINSTR); + struct instr *ins = &instrtab[t]; + if (!pure(ins)) return t; + for (size_t h = hashins(&instrtab[t]), i = h;; ++i) { + struct ht *p = &insht[i &= (ninsht-1)]; + if (!p->b) Put: { + p->b = blk; + p->memno = memno; + p->cutoff = cutoff; + return p->t = t; + } else if (insequ(&instrtab[p->t], ins)) { + if (p->cutoff == cutoff && (!oisload(ins->op) || p->memno == memno) && doms(p->b, blk)) + return p->t; + goto Put; + } + } +} + +void +cselim(struct function *fn) +{ + FREQUIRE(FNUSE | FNRPO | FNDOM | FNBLKID); + extern int ninstr; + for (ninsht = 32; ninsht <= ninstr; ninsht *= 2) ; + insht = allocz(fn->passarena, ninsht * sizeof *insht, 0); + int memno = 0, cutoff = 0; + struct block *blk = fn->entry; + do { + ++memno; + for (int i = 0; i < blk->ins.n; ++i) { + int t = blk->ins.p[i], q; + if ((q = uniq(t, blk, cutoff, memno)) != t) { + replcuses(mkref(RTMP, t), mkref(RTMP, q)); + delinstr(blk, i--); + } else if (oisstore(instrtab[t].op)) { + /* assume everything alias everything */ + ++memno; + } else if (instrtab[t].op == Ocall) { + ++cutoff; + } + } + } while ((blk = blk->lnext) != fn->entry); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_dump.c b/src/ir_dump.c new file mode 100644 index 0000000..b0ce603 --- /dev/null +++ b/src/ir_dump.c @@ -0,0 +1,319 @@ +#include "ir.h" +#include "../obj/obj.h" +#include "../endian.h" + +static int nextdat; + +static struct wbuf *out = &bstdout; + +static bool +prilitdat(const struct irdat *dat, const char *prefix) +{ + uchar *p; + switch (dat->section) { + default: assert(0); + case Sdata: p = objout.data.p + dat->off; break; + case Srodata: p = objout.rodata.p + dat->off; break; + case Stext: p = objout.textbegin + dat->off; break; + } + if (dat->ctype.t == TYARRAY && typechild(dat->ctype).t == TYCHAR && dat->siz-1 < 60 && p[dat->siz-1] == 0) { + bfmt(out, "%s%'S", prefix, p, dat->siz-1); + } else if (dat->ctype.t == TYFLOAT) { + bfmt(out, "%s%f", prefix, rdf32targ(p)); + } else if (dat->ctype.t == TYDOUBLE) { + bfmt(out, "%s%f", prefix, rdf64targ(p)); + } else if (dat->ctype.t == TYVLONG) { + bfmt(out, "%s0x%lx", prefix, rd64targ(p)); + } else return 0; + return 1; +} + +static void +pridat(const struct irdat *dat) +{ + static const char *snames[] = { [Sdata] = ".data", [Srodata] = ".rodata", [Stext] = ".text" }; + uchar *p; + switch (dat->section) { + default: assert(0); + case Sdata: p = objout.data.p + dat->off; break; + case Srodata: p = objout.rodata.p + dat->off; break; + case Stext: p = objout.textbegin + dat->off; break; + } + enum { + MINZERO = 4, + MAXLINE = 60, + }; + int npri = 0; + int strbegin = 0, nstr = 0; + bfmt(out, "%s %ty %y(align %d, size %d):\n\t", snames[dat->section], dat->ctype, dat->name, dat->align, dat->siz); + if (!prilitdat(dat, "lit: ")) { + for (int i = 0; i < dat->siz; ++i) { + int c = p[i]; + if (npri > MAXLINE) { + npri = 0; + bfmt(out, "\n\t"); + } + if (aisprint(c)) { + if (!nstr++) strbegin = i; + } else { + if (nstr) { + npri += bfmt(out, "asc %'S,", p+strbegin, nstr); + nstr = 0; + bfmt(out, "b "); + } + npri += bfmt(out, "%d,", c); + } + } + if (nstr) npri += bfmt(out, "asc %'S,", p+strbegin, nstr); + } + bfmt(out, "\n"); +} + +static const char *clsname[] = { + "?", "i32", "i64", "ptr", "f32", "f64" +}; + +static void +prityp(union irtype typ) +{ + if (!typ.isagg) + bfmt(out, clsname[typ.cls]); + else { + const struct typedata *td = &typedata[typ.dat]; + const char *tag = td->t == TYSTRUCT ? "struct" : "union"; + if (ttypenames[td->id]) + bfmt(out, "%s.%s.%d", tag, ttypenames[td->id], td->id); + else + bfmt(out, "%s.%d", tag, td->id); + } +} + +static const char *intrinname[] = { + "?\??", +#define _(b,...) #b, +#include "intrin.def" +#undef _ +}; + +static void +dumpref(enum op o, union ref ref) +{ + struct xcon *con; + switch (ref.t) { + case RXXX: + if (ref.bits == UNDREF.bits) + bfmt(out, "undef"); + else + bfmt(out, "??"); + break; + case RTMP: + bfmt(out, "%%%d", ref.i); + if (instrtab[ref.i].reg) + bfmt(out, "(%s)", mctarg->rnames[instrtab[ref.i].reg-1]); + break; + case RREG: + bfmt(out, "%s", mctarg->rnames[ref.i]); + break; + case RICON: + if (o == Ointrin) bfmt(out, "\"%s\"", intrinname[ref.i]); + else bfmt(out, "%d", ref.i); + break; + case RXCON: + con = &contab.p[ref.i]; + if (con->deref) bfmt(out, "*["); + if (con->issym || con->isdat) { + bfmt(out, "$%y", xcon2sym(ref.i)); + if (con->isdat) { + struct irdat *dat = &dattab.p[con->dat]; + if (prilitdat(dat, " (= ")) { + if (isscalar(dat->ctype)) { + struct wbuf tmp = MEMBUF((char [1]){0}, 1); + bfmt(&tmp, "%ty", dat->ctype); + ioputc(out, *tmp.buf); + } + ioputc(out, ')'); + } + } + } else switch (con->cls) { + case KI32: bfmt(out, "%d", (int)con->i); break; + case KI64: bfmt(out, "%ld", con->i); break; + case KPTR: bfmt(out, "%'lx", con->i); break; + case KF32: bfmt(out, "%fs", con->f); break; + case KF64: bfmt(out, "%fd", con->f); break; + default: assert(0); + } + if (con->deref) bfmt(out, "]"); + break; + case RTYPE: + prityp(ref2type(ref)); + break; + case RADDR: + { + const struct addr *addr = &addrtab.p[ref.i]; + bool k = 0; + bfmt(out, "addr ["); + if ((k = addr->base.bits)) dumpref(0, addr->base); + if (addr->index.bits) { + if (k) bfmt(out, " + "); + dumpref(0, addr->index); + if (addr->shift) + bfmt(out, " * %d", 1<<addr->shift); + k = 1; + } + if (k && addr->disp) { + bfmt(out, " %c %d", "-+"[addr->disp > 0], addr->disp < 0 ? -addr->disp : addr->disp); + } + assert(k); + bfmt(out, "]"); + } + break; + case RSTACK: + bfmt(out, "[stack %d]", ref.i); + break; + default: assert(!"ref"); + } +} + +static void +dumpcall(struct call *call) +{ + if (call->ret.isagg) { + bfmt(out, "sret "); + prityp(call->ret); + bfmt(out, ", "); + } + if (call->vararg < 0) { + bfmt(out, "#%d", call->narg); + } else { + assert(call->vararg <= call->narg); + bfmt(out, "#%d, ... #%d", call->vararg, call->narg - call->vararg); + } +} + +static void +dumpinst(const struct instr *ins) +{ + int i; + if (ins->op == Omove) { + bfmt(out, "move %s ", clsname[ins->cls]); + } else { + enum irclass cls = insrescls(*ins); + if (ins->reg) { + if (cls) + bfmt(out, "%s ", clsname[cls]); + bfmt(out, "(%%%d)%s = ", ins - instrtab, mctarg->rnames[ins->reg - 1]); + } else if (cls) { + bfmt(out, "%s %%%d", clsname[cls], ins - instrtab); + bfmt(out, " = "); + } + bfmt(out, "%s ", opnames[ins->op]); + if (oiscmp(ins->op)) + bfmt(out, "%s ", clsname[ins->cls]); + } + for (i = 0; i < opnarg[ins->op]; ++i) { + if (i) bfmt(out, ", "); + if (i == 1 && (ins->op == Ocall || ins->op == Ointrin)) { + dumpcall(&calltab.p[ins->r.i]); + } else { + dumpref(ins->op, (&ins->l)[i]); + } + } + if (oisalloca(ins->op) && ins->l.t == RICON) { + bfmt(out, " \t; %d bytes", ins->l.i << (ins->op - Oalloca1)); + } + bfmt(out, "\n"); +} + +void +dumpblk(struct function *fn, struct block *blk) +{ + static const char *jnames[] = { 0, "b", "ret", "trap" }; + int i; + bfmt(out, " @%d:", blk->id); + if (blk->npred) { + bfmt(out, " \t; preds:"); + for (i = 0; i < blk->npred; ++i) { + if (i) ioputc(out, ','); + bfmt(out, " @%d", blkpred(blk, i)->id); + } + } + if (fn->prop & FNDOM && blk->idom) + bfmt(out, "\t; idom: @%d", blk->idom->id); + if (blk->loop) + bfmt(out, "\t; loop depth: %d", blk->loop); + ioputc(out, '\n'); + for (i = 0; i < blk->phi.n; ++i) { + struct instr *phi = &instrtab[blk->phi.p[i]]; + union ref *refs = phitab.p[phi->l.i]; + if (i == 0) bfmt(out, "%-4d", blk->inumstart); + else bfmt(out, " |> "); + bfmt(out, " %s ", clsname[phi->cls]); + if (!phi->reg) bfmt(out, "%%%d = %s ", blk->phi.p[i], opnames[phi->op]); + else bfmt(out, "(%%%d)%s = %s ", phi - instrtab, mctarg->rnames[phi->reg-1], opnames[phi->op]); + for (int i = 0; i < blk->npred; ++i) { + if (i) bfmt(out, ", "); + bfmt(out, "@%d ", blkpred(blk, i)->id); + dumpref(0, refs[i]); + } + ioputc(out, '\n'); + } + for (i = 0; i < blk->ins.n; ++i) { + bfmt(out, "%-4d ", blk->inumstart + 1 + i); + dumpinst(&instrtab[blk->ins.p[i]]); + } + bfmt(out, "%-4d %s ", blk->inumstart + 1 + i, jnames[blk->jmp.t]); + if (blk->jmp.t == Jret && blk->jmp.arg[0].bits && !fn->nabiret && isagg(fn->retty)) { + /* un-lowered struct return */ + dumpref(0, mktyperef(mkirtype(fn->retty))); + bfmt(out, " "); + } + for (i = 0; i < 2; ++i) { + if (!blk->jmp.arg[i].bits) break; + if (i > 0) bfmt(out, ", "); + dumpref(0, blk->jmp.arg[i]); + } + if (i && blk->s1) bfmt(out, ", "); + if (blk->s1 && blk->s2) bfmt(out, "@%d, @%d", blk->s1->id, blk->s2->id); + else if (blk->s1) bfmt(out, "@%d", blk->s1->id); + bfmt(out, "\n"); +} + +void +irdump(struct function *fn) +{ + struct block *blk; + + /* print datas that have never been printed before */ + while (nextdat < dattab.n) pridat(&dattab.p[nextdat++]); + + bfmt(out, "function %s : %ty\n", fn->name, fn->fnty); + if (fn->abiarg || fn->nabiret) { + bfmt(out, "abi: ("); + for (int i = 0; i < fn->nabiarg; ++i) { + if (i > 0) bfmt(out, ", "); + if (!fn->abiarg[i].isstk) { + bfmt(out, "%s", mctarg->rnames[fn->abiarg[i].reg]); + } else { + prityp(fn->abiarg[i].ty); + bfmt(out, " <stk>"); + } + } + bfmt(out, ")"); + if (fn->retty.t != TYVOID) { + bfmt(out, " -> %s", mctarg->rnames[fn->abiret[0].reg]); + if (fn->nabiret > 1) + bfmt(out, ", %s", mctarg->rnames[fn->abiret[1].reg]); + } + bfmt(out, "\n"); + } + numberinstrs(fn); + blk = fn->entry; + do { + assert(blk->lprev->lnext == blk); + dumpblk(fn, blk); + assert(blk->lnext != NULL); + } while ((blk = blk->lnext) != fn->entry); + bfmt(out, "\n"); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_fold.c b/src/ir_fold.c new file mode 100644 index 0000000..4c9861e --- /dev/null +++ b/src/ir_fold.c @@ -0,0 +1,133 @@ +#include "ir.h" +#include "../endian.h" +#include <limits.h> + +#ifdef __clang__ +__attribute__((no_sanitize("float-cast-overflow"))) /* silence UBsan for float->int overflow */ +#endif +static union ref +foldint(enum op op, enum irclass k, union ref lr, union ref rr) +{ + vlong x; + union { + vlong s; + uvlong u; + } l = {.s = intconval(lr)}, r = {.s = intconval(rr)}; + bool w = cls2siz[k] == 8; + if (in_range(op, Odiv, Ourem)) assert(r.u != 0); + switch (op) { + case Ocopy: x = l.s; break; + case Oneg: x = -l.s; break; + case Onot: x = ~l.s; break; +#define CVTF2I(TF, TI) do { \ + TF f = fltconval(lr); \ + if (f != f) x = 0; \ + else x = (TI)f; \ +} while (0) + case Ocvtf32s: if (w) CVTF2I(float, vlong); else CVTF2I(float, int); break; + case Ocvtf32u: if (w) CVTF2I(float, uvlong); else CVTF2I(float, uint); break; + case Ocvtf64s: if (w) CVTF2I(double, vlong); else CVTF2I(double, int); break; + case Ocvtf64u: if (w) CVTF2I(double, uvlong); else CVTF2I(double, uint); break; +#undef CVTF2I + case Oexts8: x = (schar)l.s; break; + case Oextu8: x = (uchar)l.s; break; + case Oexts16: x = (short)l.s; break; + case Oextu16: x = (ushort)l.s; break; + case Oexts32: x = (int)l.s; break; + case Oextu32: x = (uint)l.s; break; + case Obswap16: x = bswap16(l.u); break; + case Obswap32: x = bswap32(l.u); break; + case Obswap64: x = bswap64(l.u); break; + case Oadd: x = l.u + r.u; break; + case Osub: x = l.u - r.u; break; + case Omul: x = l.u * r.u; break; + case Odiv: if (r.s == -1 && (w ? l.s == LLONG_MIN : (int)l.s == INT_MIN)) x = l.s; + else x = w ? l.s / r.s : (int)l.s / (int)r.s; + break; + case Oudiv: x = w ? l.u / r.u : (uint)l.u / (uint)r.u; break; + case Orem: if (r.s == -1 && (w ? l.s == LLONG_MIN : (int)l.s == INT_MIN)) x = 0; + else x = w ? l.s % r.s : (int)l.s % (int)r.s; + break; + case Ourem: x = w ? l.u % r.u : (uint)l.u % (uint)r.u; break; + case Oand: x = l.u & r.u; break; + case Oior: x = l.u | r.u; break; + case Oxor: x = l.u ^ r.u; break; + case Oshl: x = l.u << (r.u & (w ? 63 : 31)); break; + case Oslr: x = w ? l.u >> (r.u&63) : (int)l.s >> (r.u&31); break; + case Osar: x = w ? l.s >> (r.u&63) : (int)l.s >> (r.u&31); break; + case Oequ: x = l.s == r.s; break; + case Oneq: x = l.s != r.s; break; + case Olth: x = l.s < r.s; break; + case Ogth: x = l.s > r.s; break; + case Olte: x = l.s <= r.s; break; + case Ogte: x = l.s >= r.s; break; + case Oulth: x = l.u < r.u; break; + case Ougth: x = l.u > r.u; break; + case Oulte: x = l.u <= r.u; break; + case Ougte: x = l.u >= r.u; break; + default: assert(0); + } + if (cls2siz[k] < 8) x = (int)x; + return mkintcon(k, x); +} + +static union ref +foldflt(enum op op, enum irclass k, union ref lr, union ref rr) +{ + int xi; + double x, l = fltconval(lr), r = fltconval(rr); + bool w = k == KF64; + switch (op) { + case Ocopy: x = l; break; + case Oneg: x = -l; break; + case Ocvtf32f64: x = (float)l; break; + case Ocvtf64f32: x = (float)l; break; + case Ocvts32f: x = (int)intconval(lr); break; + case Ocvtu32f: x = (int)intconval(lr); break; + case Ocvts64f: x = (vlong)intconval(lr); break; + case Ocvtu64f: x = (uvlong)intconval(lr); break; + case Oadd: x = l + r; break; + case Osub: x = l - r; break; + case Omul: x = l * r; break; + case Odiv: x = l / r; break; + case Oequ: xi = l == r; goto Cmp; + case Oneq: xi = l != r; goto Cmp; + case Olth: xi = l < r; goto Cmp; + case Ogth: xi = l > r; goto Cmp; + case Olte: xi = l <= r; goto Cmp; + case Ogte: xi = l >= r; Cmp: return mkref(RICON, xi); + default: assert(0); + } + if (!w) x = (float)x; + return mkfltcon(k, x); +} + +bool +foldbinop(union ref *to, enum op op, enum irclass k, union ref l, union ref r) +{ + if (!oisarith(op)) + return 0; + if (!isnumcon(l) || !isnumcon(r)) return 0; + if (in_range(op, Odiv, Ourem) && kisint(k) && intconval(r) == 0) + return 0; + if (kisint(k)) + *to = foldint(op, k, l, r); + else + *to = foldflt(op, k, l, r); + return 1; +} + +bool +foldunop(union ref *to, enum op op, enum irclass k, union ref a) +{ + if (!isnumcon(a)) return 0; + if (op != Ocopy && !oisarith(op)) + return 0; + if (kisint(k)) + *to = foldint(op, k, a, ZEROREF); + else + *to = foldflt(op, k, a, ZEROREF); + return 1; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_inliner.c b/src/ir_inliner.c new file mode 100644 index 0000000..b99d3dc --- /dev/null +++ b/src/ir_inliner.c @@ -0,0 +1,309 @@ +#include "ir.h" + +struct savedfunc { + uint ninstr; + struct instr *instrtab; + struct xcon *contab; + struct call *calltab; + union ref **phitab; + struct block *entry; + union type fnty, retty; + struct abiarg *abiarg, abiret[2]; + ushort nabiarg, nabiret; + ushort nretpoints; +}; + +enum { MAX_INLINED_FN_NINS = 50, + MAX_INLINED_FN_NBLK = 16, }; + +static pmap_of(struct savedfunc *) savedfns; + +bool +maybeinlinee(struct function *fn) +{ + static struct arena *savearena; + extern int ninstr; + + // TODO better heuristics + if (ccopt.o < OPT1) return 0; + if (!fn->inlin && ccopt.o < OPT2) return 0; + if (ninstr > MAX_INLINED_FN_NINS) return 0; + if (fn->nblk > MAX_INLINED_FN_NBLK) return 0; + for (int i = 0; i < fn->nabiarg; ++i) { + /* TODO inlining functions with stack args */ + if (fn->abiarg[i].isstk) return 0; + } + if (fn->nabiret > 1) return 0; /* TODO 2reg scalar return */ + + if (!savearena) { + enum { N = 1<<12 }; + static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem; + savearena = (void *)amem.m; + savearena->cap = N; + } + + if (ccopt.dbg.y) { + bfmt(ccopt.dbgout, "> stashing '%s' for inlining\n", fn->name); + } + struct savedfunc *sv = allocz(&savearena, sizeof *sv, 0); + sv->fnty = fn->fnty, sv->retty = fn->retty; + if (fn->abiarg) + sv->abiarg = alloccopy(&savearena, fn->abiarg, sizeof *sv->abiarg * fn->nabiarg, 0); + sv->nabiarg = fn->nabiarg; + if ((sv->nabiret = fn->nabiret) > 0) + memcpy(sv->abiret, fn->abiret, sizeof sv->abiret); + struct block *bmap[MAX_INLINED_FN_NBLK]; + struct block *b = fn->entry; + int id = 0; + do { + b->id = id++; + struct block *q = alloccopy(&savearena, b, sizeof *b, 0); + if (q->phi.n) + q->phi.p = alloccopy(&savearena, q->phi.p, sizeof *q->phi.p * q->phi.n, 0); + if (q->ins.n) + q->ins.p = alloccopy(&savearena, q->ins.p, sizeof *q->ins.p * q->ins.n, 0); + if (q->npred > 1) + q->_pred = alloccopy(&savearena, q->_pred, sizeof *q->_pred * q->npred, 0); + q->lprev = NULL; + q->idom = NULL; + bmap[b->id] = q; + sv->nretpoints += b->jmp.t == Jret; + } while ((b = b->lnext) != fn->entry); + b = sv->entry = bmap[0]; + do { + if (b->s1) b->s1 = bmap[b->s1->id]; + if (b->s2) b->s2 = bmap[b->s2->id]; + if (b->npred == 1) + b->_pred0 = bmap[b->_pred0->id]; + else for (int i = 0; i < b->npred; ++i) + b->_pred[i] = bmap[b->_pred[i]->id]; + b->lnext = b->lnext == fn->entry ? NULL : bmap[b->lnext->id]; + } while ((b = b->lnext)); + + sv->instrtab = alloccopy(&savearena, instrtab, sizeof *instrtab * (sv->ninstr = ninstr), 0); + sv->contab = alloccopy(&savearena, contab.p, sizeof *contab.p * contab.n, 0); + if (calltab.n) { + sv->calltab = alloccopy(&savearena, calltab.p, sizeof *calltab.p * calltab.n, 0); + for (int i = 0; i < calltab.n; ++i) { + if (sv->calltab[i].abiarg) + sv->calltab[i].abiarg = alloccopy(&savearena, sv->calltab[i].abiarg, + sv->calltab[i].narg * sizeof *sv->calltab[i].abiarg, 0); + } + } + if (phitab.n) { + sv->phitab = alloc(&savearena, sizeof *phitab.p * phitab.n, 0); + for (int i = 0; i < phitab.n; ++i) { + sv->phitab[i] = alloccopy(&savearena, phitab.p[i], sizeof *phitab.p[i] * xbcap(phitab.p[i]), 0); + } + } + pmap_set(&savedfns, fn->name, sv); + return 1; +} + +static union ref +mapref(short *instrmap, struct savedfunc *sv, union ref r) +{ + assert(r.bits); + if (r.t == RTMP) return r.i = instrmap[r.i], r; + if (r.t == RXCON) return newxcon(&sv->contab[r.i]); + assert(r.t != RADDR); + assert(r.t != RSTACK); + return r; +} + +static struct block * +inlcall(struct function *fn, struct block *blk, int curi, struct savedfunc *sv) +{ + int res = blk->ins.p[curi], res2; + struct instr *ins = &instrtab[res]; + struct call *call = &calltab.p[ins->r.i]; + union ref retvals[64]; + union ref args[64]; + assert(sv->nabiret < 2 && sv->nretpoints < countof(retvals)); + for (int n = call->narg, i = curi-1; n > 0; --i) { + assert(i >= 0); + struct instr *ins = &instrtab[blk->ins.p[i]]; + if (ins->op == Oarg) { + args[--n] = ins->r; + *ins = mkinstr(Onop,0,); + } + } + if (call->abiret[1].ty.bits) { + assert(curi+1 < blk->ins.n); + res2 = blk->ins.p[curi+1]; + assert(instrtab[res2].op == Ocall2r); + } + struct block *exit = blksplitafter(fn, blk, curi-1); + if (!ins->cls) { + *ins = mkinstr(Onop,0,); + } else { + if (sv->nretpoints == 1) { + *ins = mkinstr(Ocopy, ins->cls, ); + } else { + /* turn into a phi */ + *ins = mkinstr(Ophi, ins->cls, ); + exit->ins.p[0] = newinstr(blk, mkinstr(Onop,0,)); + vpush(&exit->phi, res); + } + } + + struct block *bmap[MAX_INLINED_FN_NBLK]; + short instrmap[MAX_INLINED_FN_NINS]; + for (struct block *b = sv->entry; b; b = b->lnext) { + bmap[b->id] = newblk(fn); + } + for (int i = 0; i < sv->ninstr; ++i) { + /* TODO don't wastefully allocate for tombstone instructions + * - mark instructions some way when they are freed (put in instrfreelist) + * */ + int allocinstr(void); + instrmap[i] = allocinstr(); + } + + exit->npred = 0; + exit->_pred0 = NULL; + blk->s1 = bmap[0]; + int iret = 0; + for (struct block *b = sv->entry, *prev = blk, *new; b; prev = new, b = b->lnext) { + new = bmap[b->id]; + new->id = fn->nblk++; + new->lprev = prev; + prev->lnext = new; + new->lnext = exit; + exit->lprev = new; + if (b->npred == 1) new->_pred0 = bmap[b->_pred0->id]; + else if (b->npred > 0) { + xbgrow(&new->_pred, b->npred); + for (int i = 0; i < b->npred; ++i) + new->_pred[i] = bmap[b->_pred[i]->id]; + } + new->npred = b->npred; + vresize(&new->phi, b->phi.n); + for (int i = 0; i < b->phi.n; ++i) { + int t = b->phi.p[i]; + union ref *refs = NULL, + *src = sv->phitab[sv->instrtab[t].l.i]; + xbgrow(&refs, b->npred); + for (int i = 0; i < b->npred; ++i) + refs[i] = mapref(instrmap, sv, src[i]); + vpush(&phitab, refs); + instrtab[instrmap[t]] = mkinstr(Ophi, sv->instrtab[t].cls, .l.i = phitab.n-1); + new->phi.p[i] = instrmap[t]; + } + vresize(&new->ins, b->ins.n); + for (int i = 0; i < b->ins.n; ++i) { + int t = b->ins.p[i]; + struct instr *ins = &instrtab[instrmap[t]]; + *ins = sv->instrtab[t]; + if (ins->op == Oparam) { + ins->op = Ocopy; + assert(ins->l.t == RICON && (uint) ins->l.i < call->narg); + ins->l = args[ins->l.i]; + } else if (ins->op == Ocall || ins->op == Ointrin) { + ins->l = mapref(instrmap, sv, ins->l); + for (struct instr *ins2; + ins->l.t == RTMP && (ins2 = &instrtab[ins->l.i])->op == Ocopy + && (isaddrcon(ins2->l,0) || (ins2->l.t == RTMP && instrtab[ins->l.i].cls == KPTR));) { + /* for an indirect function call, eagerly copy-propagate the callee. this allows + * subsequent inlining of function pointers statically known after the inlining */ + ins->l = ins2->l; + } + vpush(&calltab, sv->calltab[ins->r.i]); + ins->r.i = calltab.n-1; + } else { + if (ins->l.t) ins->l = mapref(instrmap, sv, ins->l); + if (ins->r.t) ins->r = mapref(instrmap, sv, ins->r); + } + new->ins.p[i] = instrmap[t]; + } + if (b->jmp.t == Jret) { + new->jmp.t = Jb; + new->s1 = exit; + retvals[iret++] = b->jmp.arg[0].bits ? mapref(instrmap, sv, b->jmp.arg[0]) : UNDREF; + addpred(exit, new); + } else { + new->jmp.t = b->jmp.t; + for (int i = 0; i < 2; ++i) { + if (!b->jmp.arg[i].bits) break; + new->jmp.arg[i] = mapref(instrmap, sv, b->jmp.arg[i]); + } + } + if (b->s1) { + new->s1 = bmap[b->s1->id]; + if (b->s2) new->s2 = bmap[b->s2->id]; + } + } + exit->id = fn->nblk; + fn->prop &= ~FNUSE; + if (ins->cls && sv->nretpoints > 0) { + assert(sv->nretpoints == exit->npred); + if (sv->nretpoints == 1) { + /* fill copy */ + ins->l = retvals[0]; + } else { + /* fill phi */ + union ref *refs = NULL; + xbgrow(&refs, sv->nretpoints); + memcpy(refs, retvals, sizeof *refs * sv->nretpoints); + vpush(&phitab, refs); + ins->l = mkref(RXXX, phitab.n-1); + } + } + bmap[0]->_pred0 = blk; + bmap[0]->npred = 1; + return exit; +} + +enum { MAX_REC_INLINE = 16 }; +void +doinline(struct function *fn) +{ + if (calltab.n == 0) return; + struct block *b = fn->entry; + struct stack { /* stack of callees being inline expanded */ + struct block *b; /* block after the end of expansion */ + struct savedfunc *sv; + } stkbuf[MAX_REC_INLINE], *stk = stkbuf + MAX_REC_INLINE, *stkend = stk; + bool dumpbefore = 0; + do { + if (stk != stkend && b == stk->b) + ++stk; /* pop */ + else if (stk == stkbuf) /* stack full? */ + continue; + for (int i = 0; i < b->ins.n; ++i) { + struct instr *ins = &instrtab[b->ins.p[i]]; + if (ins->op != Ocall) continue; + if (!isaddrcon(ins->l,0)) continue; + struct call *call = &calltab.p[ins->r.i]; + internstr fname = xcon2sym(ins->l.i); + struct savedfunc **pcallee, *sv; + if ((pcallee = pmap_get(&savedfns, fname)) + && (sv = *pcallee)->nabiarg == call->narg && call->vararg == -1 + && call->narg == sv->nabiarg + && (!call->narg || !memcmp(sv->abiarg, call->abiarg, sizeof *sv->abiarg * sv->nabiarg)) + && !memcmp(sv->abiret, call->abiret, sizeof sv->abiret)) { + for (struct stack *s = stk; s != stkend; ++s) { + if (s->sv == sv) goto Skip; /* recursion encountered */ + } + if (ccopt.dbg.y) { + if (!dumpbefore) { + bfmt(ccopt.dbgout, "<< Before inlining >>\n"); + irdump(fn); + dumpbefore = 1; + } + } + + (--stk)->b = inlcall(fn, b, i, *pcallee); + stk->sv = sv; + if (ccopt.dbg.y) { + bfmt(ccopt.dbgout, "<< After inlining '%s' (@%d-@%d) >>\n", fname, b->lnext->id, stk->b->id); + irdump(fn); + } + break; + } + Skip:; + } + } while ((b = b->lnext) != fn->entry); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_intrin.c b/src/ir_intrin.c new file mode 100644 index 0000000..ca49341 --- /dev/null +++ b/src/ir_intrin.c @@ -0,0 +1,77 @@ +#include "ir.h" + +struct arg { union ref *arg, *ty; }; + +static int +intrin(struct block *blk, int *curi, enum intrin in, struct arg *args, int narg, union irtype ret) +{ + struct instr *this = &instrtab[blk->ins.p[*curi]]; + const struct typedata *td; + union irtype ty; + uint ncopy, step; + + switch (in) { + case 0: assert(0); + case INstructcopy: + assert(narg == 2 && args[0].ty->bits == args[1].ty->bits); + ty = ref2type(*args[0].ty); + assert(ty.isagg); + td = &typedata[ty.cls]; + step = td->align <= 8 ? td->align : 8; + ncopy = td->siz / step; + if (ncopy > 4) { + enum irclass cls = siz2intcls[cls2siz[KPTR]]; + /* memcpy */ + *args[1].ty = *args[0].ty = mktyperef(cls2type(KPTR)); + insertinstr(blk, (*curi)++, mkarginstr(cls2type(cls), mkintcon(cls, td->siz))); + *this = mkinstr(Ocall, 0, mksymref(intern("memcpy"), SFUNC), this->r); + calltab.p[this->r.i].narg = 3; + calltab.p[this->r.i].ret = cls2type(0); + return 0; + } else { + delinstr(blk, (*curi)--); + for (int off = 0; off < td->siz; off += step) { + union ref psrc = *args[1].arg, pdst = *args[0].arg, src; + if (off) { + pdst = insertinstr(blk, ++*curi, mkinstr(Oadd, KPTR, *args[0].arg, mkref(RICON, off))); + psrc = insertinstr(blk, ++*curi, mkinstr(Oadd, KPTR, *args[1].arg, mkref(RICON, off))); + } + src = insertinstr(blk, ++*curi, mkinstr(Oloads8 + 2*ilog2(step), step < 8 ? KI32 : KI64, psrc)); + insertinstr(blk, ++*curi, mkinstr(Ostorei8 + ilog2(step), 0, pdst, src)); + } + return 1; + } + } + assert(0); +} + +void +lowerintrin(struct function *fn) +{ + struct block *blk = fn->entry; + struct arg argsbuf[32]; + vec_of(struct arg) args = VINIT(argsbuf, countof(argsbuf)); + + do { + for (int i = 0; i < blk->ins.n; ++i) { + struct instr *ins = &instrtab[blk->ins.p[i]]; + if (ins->op == Oarg) + vpush(&args, ((struct arg){ &ins->r, &ins->l })); + else if (ins->op == Ocall) + vinit(&args, argsbuf, countof(argsbuf)); + else if (ins->op == Ointrin) { + int arg0 = i - args.n; + assert(calltab.p[ins->r.i].narg == args.n); + if (intrin(blk, &i, ins->l.i, args.p, args.n, calltab.p[ins->r.i].ret)) + for (int j = args.n; j > 0; --j, --i) + delinstr(blk, arg0); + else + abi0_call(fn, ins, blk, &i); + vinit(&args, argsbuf, countof(argsbuf)); + } + } + assert(args.n == 0); + } while ((blk = blk->lnext) != fn->entry); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_intrin.def b/src/ir_intrin.def new file mode 100644 index 0000000..2ccc3b5 --- /dev/null +++ b/src/ir_intrin.def @@ -0,0 +1,2 @@ +/* NAME NARG */ +_(structcopy, 2) diff --git a/src/ir_mem2reg.c b/src/ir_mem2reg.c new file mode 100644 index 0000000..7a5874c --- /dev/null +++ b/src/ir_mem2reg.c @@ -0,0 +1,317 @@ +#include "ir.h" +#include <stdlib.h> /* qsort */ + +static const uchar loadszcls[] = { + [Oloads8 - Oloads8] = 1|KI32<<4, [Oloadu8 - Oloads8] = 1|KI32<<4, + [Oloads16 - Oloads8] = 2|KI32<<4, [Oloadu16 - Oloads8] = 2|KI32<<4, + [Oloads32 - Oloads8] = 4|KI32<<4, [Oloadu32 - Oloads8] = 4|KI32<<4, + [Oloadi64 - Oloads8] = 8|KI64<<4, + [Oloadf32 - Oloads8] = 4|KF32<<4, + [Oloadf64 - Oloads8] = 8|KF64<<4, +}; +static const uchar load2ext[] = { + [Oloads8 - Oloads8] = Oexts8, [Oloadu8 - Oloads8] = Oextu8, + [Oloads16 - Oloads8] = Oexts16, [Oloadu16 - Oloads8] = Oextu16, + [Oloads32 - Oloads8] = Oexts32, [Oloadu32 - Oloads8] = Oextu32, + [Oloadi64 - Oloads8] = Ocopy, +}; +static const uchar storesz[] = { + [Ostorei8 - Ostorei8] = 1, + [Ostorei16 - Ostorei8] = 2, + [Ostorei32 - Ostorei8] = 4, + [Ostorei64 - Ostorei8] = 8, + [Ostoref32 - Ostorei8] = 4, + [Ostoref64 - Ostorei8] = 8, +}; +#define loadsz(o) (loadszcls[(o) - Oloads8] & 0xF) +#define loadcls(o) (loadszcls[(o) - Oloads8] >> 4) +#define load2ext(o) (load2ext[(o) - Oloads8]) +#define storesz(o) (storesz[(o) - Ostorei8]) + +/* Implements algorithm in 'Simple and Efficient Construction of Static Single Assignment' (Braun et al) */ + +struct ssabuilder { + struct arena **arena; + imap_of(union ref *) curdefs; /* map of var to (map of block to def of var) */ + struct bitset *sealed, /* set of sealed blocks */ + *marked; /* blocks marked, for 'Marker Algorithm' in the paper */ + int lastvisit; + int nblk; +}; + +static union ref readvar(struct ssabuilder *, int var, enum irclass, struct block *); + +static union ref +deltrivialphis(struct ssabuilder *sb, int var, struct block *blk, union ref phiref) +{ + assert(instrtab[phiref.i].op == Ophi); + union ref *args = phitab.p[instrtab[phiref.i].l.i]; + union ref same = {0}; + for (int i = 0; i < blk->npred; ++i) { + if (args[i].bits == same.bits || args[i].bits == phiref.bits) { + continue; /* unique value or self-reference */ + } if (same.bits != 0) { + return phiref; /* non-trivial */ + } + same = args[i]; + } + + if (same.bits == 0) + same = UNDREF; /* the phi is unreachable or in the start block */ + + /* replace uses */ + replcuses(phiref, same); + union ref **pcurdefs = imap_get(&sb->curdefs, var); + assert (pcurdefs); + for (int i = blk->id; i < sb->nblk; ++i) { + if ((*pcurdefs)[i].bits == phiref.bits) + (*pcurdefs)[i] = same; + } + + /* stops infinite recursion and marks phi for removal */ + instrtab[phiref.i].op = Onop; + + /* recursively try to remove all phi users as they might have become trivial */ +Redo: + for (struct use *use = instruse[phiref.i]; use; use = use->next) { + if (use->u != USERJUMP && instrtab[use->u].op == Ophi && use->u != phiref.i) { + union ref it = mkref(RTMP, use->u); + union ref vphi2 = deltrivialphis(sb, var, use->blk, it); + if (vphi2.bits != it.bits) { + same = vphi2; + /* deletion happened so phiref use may have changed */ + goto Redo; + } + } + } + deluses(phiref.i); + + return same; +} + +static union ref +addphiargs(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk, union ref phiref) +{ + union ref *args = phitab.p[instrtab[phiref.i].l.i]; + for (int i = 0; i < blk->npred; ++i) { + struct block *pred = blkpred(blk, i); + args[i] = readvar(sb, var, cls, pred); + adduse(blk, phiref.i, args[i]); + } + instrtab[phiref.i].r.bits = 0; + return deltrivialphis(sb, var, blk, phiref); +} + +static void +writevar(struct ssabuilder *sb, int var, struct block *blk, union ref val) +{ + union ref **pcurdefs; + if (!(pcurdefs = imap_get(&sb->curdefs, var))) { + pcurdefs = imap_set(&sb->curdefs, var, allocz(sb->arena, sb->nblk * sizeof(union ref), 0)); + } + if (val.t == RTMP) assert(instrtab[val.i].op != Onop); + (*pcurdefs)[blk->id] = val; +} + +enum { RPENDINGPHI = 7 }; +static union ref +readvarrec(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk) +{ + union ref val; + assert(blk->npred > 0); + if (!bstest(sb->sealed, blk->id)) { /* unsealed block */ + /* add pending phi */ + val = insertphi(blk, cls); + instrtab[val.i].r = mkref(RPENDINGPHI, var); + } else if (blk->npred == 1) { /* trivial case when block has a single predecessor */ + val = readvar(sb, var, cls, blkpred(blk, 0)); + } else if (!bstest(sb->marked, blk->id)) { + /* Marker Algorithm to optimize the common case where a control flow join doesn't + * need a phi function for the variable: mark the block in case we recursively reach + * it again, collect defs from predecessors, only create a phi if they differ. + * This avoids creating temporary trivial phi functions in acyclic data flow + */ + bsset(sb->marked, blk->id); + for (int i = 0; i < blk->npred; ++i) { + struct block *pred = blkpred(blk, i); + union ref it = readvar(sb, var, cls, pred); + if (!bstest(sb->marked, blk->id)) { + /* recursion reached this blk again, use its phi */ + union ref **pcurdefs = imap_get(&sb->curdefs, var); + /* must have called writevar */ + assert(*pcurdefs && (*pcurdefs)[blk->id].bits); + return (*pcurdefs)[blk->id]; + } + if (i == 0) val = it; + else if (it.bits != val.bits) /* found a different definition, must add phi */ + goto AddPhi; + } + bsclr(sb->marked, blk->id); + } else { /* reached block while recursing */ + AddPhi: + val = insertphi(blk, cls); + writevar(sb, var, blk, val); + val = addphiargs(sb, var, cls, blk, val); + bsclr(sb->marked, blk->id); + return val; + } + writevar(sb, var, blk, val); + return val; +} + +static union ref +readvar(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk) +{ + union ref **pcurdefs; + if ((pcurdefs = imap_get(&sb->curdefs, var)) && (*pcurdefs)[blk->id].bits) + return (*pcurdefs)[blk->id]; + if (blk->npred == 0) /* entry block, var is read before being written to */ + return UNDREF; + return readvarrec(sb, var, cls, blk); +} + +static bool +trysealrec(struct ssabuilder *sb, struct block *blk) +{ +Recur: + if (bstest(sb->sealed, blk->id)) return 1; + if (blk->id > sb->lastvisit) return 0; + markvisited(blk); + for (int i = 0; i < blk->npred; ++i) { + struct block *p = blkpred(blk, i); + if (wasvisited(p)) continue; + if (p->id > sb->lastvisit) return 0; + } + + bsset(sb->sealed, blk->id); + for (int i = 0; i < blk->phi.n; ++i) { + struct instr *ins = &instrtab[blk->phi.p[i]]; + if (ins->r.t == RPENDINGPHI) + addphiargs(sb, ins->r.i, ins->cls, blk, mkref(RTMP, blk->phi.p[i])); + } + if (blk->s1 && !blk->s2) { + blk = blk->s1; + goto Recur; + } else if (blk->s1 && blk->s2) { + trysealrec(sb, blk->s1); + blk = blk->s2; + goto Recur; + } + return 1; +} + +static void +tryseal(struct ssabuilder *sb, struct block *blk) +{ + startbbvisit(); + trysealrec(sb, blk); +} + +static int +blkfindins(struct block *blk, int ins) +{ + for (int i = 0; i < blk->phi.n; ++i) + if (blk->phi.p[i] == ins) return -1; + for (int i = 0; i < blk->ins.n; ++i) + if (blk->ins.p[i] == ins) return i; + assert(0 && "ins not in blk"); +} + +static int +rcmpuse(const void *a, const void *b) +{ + /* postorder sort */ + const struct use *ua = *(struct use **)a, *ub = *(struct use **)b; + struct block *blk = ua->blk; + if (ua->blk != ub->blk) return ub->blk->id - ua->blk->id; + assert(ua->u != USERJUMP && ub->u != USERJUMP); + return blkfindins(blk, ub->u) - blkfindins(blk, ua->u); +} + +void +mem2reg(struct function *fn) +{ + struct ssabuilder sb = { fn->passarena, .nblk = fn->nblk }; + + FREQUIRE(FNUSE); + + sb.sealed = allocz(sb.arena, BSSIZE(fn->nblk) * sizeof *sb.sealed, 0); + sb.marked = allocz(sb.arena, BSSIZE(fn->nblk) * sizeof *sb.sealed, 0); + sortrpo(fn); + + struct block *blk = fn->entry; + do { + for (int i = 0; i < blk->ins.n; ++i) { + enum irclass k = 0; + int sz = 0; + enum op ext = Ocopy; + int var = blk->ins.p[i]; + struct instr *ins = &instrtab[var]; + struct use *use; + + /* find allocas only used in loads/stores of uniform size */ + if (!oisalloca(ins->op) || !(use = instruse[var])) continue; + struct use *usesbuf[64]; + vec_of(struct use *) uses = VINIT(usesbuf, countof(usesbuf)); + do { + if (use->u == USERJUMP) goto Skip; + struct instr *m = &instrtab[use->u]; + if (oisload(m->op) && (!sz || sz == loadsz(m->op))) { + sz = loadsz(m->op); + k = loadcls(m->op); + if (sz < 4) ext = load2ext(m->op); + } else if (oisstore(m->op) && m->l.bits == mkref(RTMP, var).bits + && (!sz || sz == storesz(m->op))) { + sz = storesz(m->op); + } else goto Skip; + vpush(&uses, use); + } while ((use = use->next)); + + /* visit uses in rpo */ + qsort(uses.p, uses.n, sizeof *uses.p, rcmpuse); + for (int i = uses.n-1; i >= 0; --i) { + use = uses.p[i]; + ins = &instrtab[use->u]; + + if (oisstore(ins->op)) { + writevar(&sb, var, use->blk, ins->r); + *ins = mkinstr(Onop,0,); + } else if (oisload(ins->op)) { + union ref val = readvar(&sb, var, k = ins->cls, use->blk); + adduse(use->blk, use->u, val); + if (ext != Ocopy && isintcon(val)) { /* fold constant int extension */ + val = irunop(NULL, ext, k, val); + assert(val.bits); + ext = Ocopy; + } + *ins = mkinstr(ext, k, val); + } + } + /* remove alloca */ + delinstr(blk, i--); + + Skip: + vfree(&uses); + } + + assert(sb.lastvisit == blk->id); + ++sb.lastvisit; + tryseal(&sb, blk); + } while ((blk = blk->lnext) != fn->entry); + + do { + /* remove phis marked for deletion */ + for (int i = 0; i < blk->phi.n; ++i) + if (instrtab[blk->phi.p[i]].op == Onop) + delphi(blk, i--); + } while ((blk = blk->lnext) != fn->entry); + + imap_free(&sb.curdefs); + if (ccopt.dbg.m) { + bfmt(ccopt.dbgout, "<< After mem2reg >>\n"); + irdump(fn); + } +} + + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_op.def b/src/ir_op.def new file mode 100644 index 0000000..4a18b4b --- /dev/null +++ b/src/ir_op.def @@ -0,0 +1,79 @@ +/* OP NARG */ +_(nop, 0) +_(copy, 1) +_(move, 2) +_(neg, 1) +_(not, 1) +_(cvtf32s, 1) +_(cvtf32u, 1) +_(cvtf32f64, 1) +_(cvtf64s, 1) +_(cvtf64u, 1) +_(cvtf64f32, 1) +_(cvts32f, 1) +_(cvtu32f, 1) +_(cvts64f, 1) +_(cvtu64f, 1) +_(exts8, 1) +_(extu8, 1) +_(exts16, 1) +_(extu16, 1) +_(exts32, 1) +_(extu32, 1) +_(bswap16, 1) +_(bswap32, 1) +_(bswap64, 1) +_(add, 2) +_(sub, 2) +_(mul, 2) +_(div, 2) +_(udiv, 2) +_(rem, 2) +_(urem, 2) +_(and, 2) +_(ior, 2) +_(xor, 2) +_(shl, 2) +_(sar, 2) +_(slr, 2) +_(equ, 2) +_(neq, 2) +_(lth, 2) +_(gth, 2) +_(lte, 2) +_(gte, 2) +_(ulth, 2) +_(ugth, 2) +_(ulte, 2) +_(ugte, 2) +_(alloca1, 1) +_(alloca2, 1) +_(alloca4, 1) +_(alloca8, 1) +_(alloca16, 1) +_(loads8, 1) +_(loadu8, 1) +_(loads16, 1) +_(loadu16, 1) +_(loads32, 1) +_(loadu32, 1) +_(loadi64, 1) +_(loadf32, 1) +_(loadf64, 1) +_(storei8, 2) +_(storei16, 2) +_(storei32, 2) +_(storei64, 2) +_(storef32, 2) +_(storef64, 2) +_(param, 2) +_(arg, 2) +_(call, 2) +_(call2r, 1) +_(intrin, 2) +_(phi, 1) +_(swap, 2) +_(vastart, 1) +_(vaarg, 2) +/* machine-specific instructions */ +_(xvaprologue, 1) diff --git a/src/ir_regalloc.c b/src/ir_regalloc.c new file mode 100644 index 0000000..1200c77 --- /dev/null +++ b/src/ir_regalloc.c @@ -0,0 +1,1417 @@ +#include "ir.h" + +/** Implements linear scan register allocation **/ +/* Some references: + * Linear Scan Register Allocation on SSA Form (Wimmer 2010) + - https://c9x.me/compile/bib/Wimmer10a.pdf + * Linear Scan Register Allocation in the Context of SSA Form + and Register Constraints (Mössenböck 2002) + - https://bernsteinbear.com/assets/img/linear-scan-ra-context-ssa.pdf + */ + +#if 1 +#define DBG(...) if(ccopt.dbg.r) bfmt(ccopt.dbgout, __VA_ARGS__) +#else +#define DBG(...) ((void)0) +#endif + +/* The algorithm used here to introduce phis for temporaries whose definitions + * appear later than some of its uses is very similar to that in mem2reg() */ + +static int livelastblk; +struct pendingphi { ushort var, phi; }; +static vec_of(struct pendingphi) *pendingphis; +static int npendingphi; +static ushort **curdefs; +static union ref readvar(struct bitset *defined, enum irclass cls, int var, struct block *blk); + +static void +fillphi(struct bitset *defined, union ref phi, enum irclass cls, int var, struct block *blk) +{ + union ref *args = phitab.p[instrtab[phi.i].l.i]; + assert(blk->npred > 0); + for (int i = 0; i < blk->npred; ++i) + args[i] = readvar(defined, cls, var, blk); +} + +static union ref +readvar(struct bitset *defined, enum irclass cls, int var, struct block *blk) +{ + union ref val; + + if (bstest(defined, var)) return mkref(RTMP, var); + assert(cls && "?"); + + /* memoed definition */ + if (xbcap(curdefs) > blk->id && xbcap(curdefs[blk->id]) > var && curdefs[blk->id][var]) + return mkref(RTMP, curdefs[blk->id][var]); + + xbgrowz(&curdefs, blk->id + 1); + if (blk->id > livelastblk) { + ++npendingphi; + val = insertphi(blk, cls); + xbgrowz(&pendingphis, blk->id + 1); + vpush(&pendingphis[blk->id], ((struct pendingphi) { var, val.i })); + } else if (blk->npred == 1) { + val = readvar(defined, cls, var, blkpred(blk, 0)); + } else { + val = insertphi(blk, cls); + fillphi(defined, val, cls, var, blk); + } + xbgrowz(&curdefs[blk->id], var + 1); + assert(val.i > 0); + curdefs[blk->id][var] = val.i; + return val; +} + +static void +liveuse(struct bitset *defined, struct instr *ins, union ref *r, struct block *blk) +{ + int var; + if (r->t == RADDR) { + liveuse(defined, ins, &addrtab.p[r->i].base, blk); + liveuse(defined, ins, &addrtab.p[r->i].index, blk); + return; + } else if (r->t != RTMP) return; + var = r->i; + if (bstest(defined, var)) return; + + *r = readvar(defined, insrescls(instrtab[r->i]), var, blk); +} + +/* regalloc() assumes every use of a temporary is visited before its definition + * so this pass fixes cases where that would not apply by introducing phi functions */ +static void +fixlive(struct function *fn) +{ + extern int ninstr; + struct block *blk = fn->entry; + struct bitset definedbuf[4] = {0}; + struct bitset *defined = definedbuf; + + if (BSSIZE(ninstr) >= countof(definedbuf)) + defined = xcalloc(sizeof *defined * BSSIZE(ninstr)); + npendingphi = 0; + + do { + for (int i = 0; i < blk->phi.n; ++i) { + int var = blk->phi.p[i]; + bsset(defined, var); + } + + for (int i = 0; i < blk->ins.n; ++i) { + int var = blk->ins.p[i]; + struct instr *ins = &instrtab[var]; + if (ins->l.t) liveuse(defined, ins, &ins->l, blk); + if (ins->r.t) liveuse(defined, ins, &ins->r, blk); + bsset(defined, var); + } + } while ((blk = blk->lnext) != fn->entry); + + do { + vec_of(struct pendingphi) *pphi; + + if (!npendingphi) break; + if (xbcap(pendingphis) <= blk->id) break; + + pphi = (void *)&pendingphis[blk->id]; + npendingphi -= pphi->n; + for (int i = 0; i < pphi->n; ++i) { + fillphi(defined, mkref(RTMP, pphi->p[i].phi), instrtab[pphi->p[i].phi].cls, pphi->p[i].var, blk); + } + vfree(pphi); + } while ((blk = blk->lnext) != fn->entry); + + if (ccopt.dbg.l) { + bfmt(ccopt.dbgout, "<< After liveness fixup >>\n"); + irdump(fn); + } + if (defined != definedbuf) free(defined); +} + +static regset gpregset, fpregset; + +#define isfpr(reg) in_range((reg), mctarg->fpr0, mctarg->fpr0 + mctarg->nfpr - 1) +#define isgpr(reg) in_range((reg), mctarg->gpr0, mctarg->gpr0 + mctarg->nfpr - 1) + +/* an allocated physical register or stack slot */ +enum { ADEAD, AREG, ASTACK }; +union alloc { struct { ushort t : 2, a : 14; }; ushort bits; }; +#define afree() ((union alloc) { .t=ADEAD }) +#define areg(r) ((union alloc) { .t=AREG, .a=(r) }) +#define astack(s) ((union alloc) { .t=ASTACK, .a=(s) }) + +enum { MAXSPILL = 512 }; + +/* half-closed instr range [from, to) */ +struct range { ushort from, to; }; +#define mkrange(f,t) ((struct range){(f), (t)}) + +/* a temporary's lifetime interval */ +struct interval { + struct interval *next; /* for linked list of active,inactive,handled sets in linear scan */ + union alloc alloc; + schar rhint : 7; /* register hint */ + bool fpr : 1; /* needs float register? */ + uint cost; /* spilling cost estimate */ + + /* sorted ranges array */ + ushort nrange; + union { + struct range _rinl[2]; + struct range *_rdyn; + }; +}; + +struct rega { + struct function *fn; + struct arena **arena; + + int intercount; /* number of actual intervals */ + int ninter; /* size of inter */ + struct interval *inter; /* map of tmp -> interval */ + struct fixinterval { + struct fixinterval *next; + regset rs; + struct range range; + } *fixed; /* linked list of fixed intervals, always sorted */ + + struct bitset freestk[BSSIZE(MAXSPILL)]; /* free stack slots */ + int maxstk, /* highest stack slot used */ + stktop; +}; + +#define stkslotref(fn, off) \ + (mkaddr((struct addr){.base = mkref(RREG, mctarg->bpr), .disp = -(fn)->stksiz - 8 - (off)})) + +/* Parallel moves algorithm from QBE: https://c9x.me/git/qbe.git/tree/rega.c?id=e493a7f23352f51acc0a1e12284ab19d7894488a#n201 */ + +enum { PMTOMOVE, PMMOVING, PMDONE }; +struct pmstate { + struct function *fn; + int npmove; + struct pmove { + uchar k; /* enum irclass */ + uchar stat; /* PMTOMOVE|MOVING|DONE */ + union alloc dst, src; + } pmove[MAXREGS]; +}; + +static void +pmadd(struct pmstate *pms, enum irclass k, union alloc dst, union alloc src) +{ + if (!memcmp(&dst, &src, sizeof dst)) return; + assert(pms->npmove < MAXREGS); + pms->pmove[pms->npmove++] = (struct pmove) { k, PMTOMOVE, dst, src }; +} + +#define mkmove(k, rd, rs) mkinstr(Omove, k, mkref(RREG, rd), mkref(RREG, rs)) +static void +emitmove(struct function *fn, enum irclass k, union alloc dst, union alloc src, struct block *blk, int curi) +{ + struct instr mv = {.keep = 1}; + int reg; + if (dst.t == AREG && src.t == AREG) { + insertinstr(blk, curi, mkmove(k, dst.a, src.a)); + return; + } + if (src.t == ASTACK) { + switch (mv.cls = k) { + default: assert(0); + case KI32: mv.op = Oloads32; break; + case KI64: mv.op = Oloadi64; break; + case KPTR: mv.op = targ_64bit ? Oloadi64 : Oloads32; break; + case KF32: mv.op = Oloadf32; break; + case KF64: mv.op = Oloadf64; break; + } + if (dst.t == AREG) + reg = dst.a; + else + reg = kisint(k) ? mctarg->gprscratch : mctarg->fprscratch; + mv.reg = reg+1; + mv.l = stkslotref(fn, src.a*8); + insertinstr(blk, curi++, mv); + } else reg = src.a; + if (dst.t == ASTACK) { + mv = mkinstr(cls2store[k], 0, stkslotref(fn, dst.a*8), mkref(RREG, reg)); + insertinstr(blk, curi, mv); + } +} + +static int +pmrec(struct pmstate *pms, int i, struct block *blk, int curi, enum irclass *k) +{ + struct pmove *pm = &pms->pmove[i]; + if (pm->dst.bits == pm->src.bits) { + pm->stat = PMDONE; + return -1; + } + + /* widen when necessary */ + assert(kisint(pm->k) == kisint(*k)); + if (cls2siz[pm->k] > cls2siz[*k]) + *k = pm->k; + + int j, c; + for (j = 0; j < pms->npmove; ++j) { + if (pms->pmove[j].dst.bits == pm->src.bits) + break; + } + if (j == pms->npmove) goto Done; + switch (pms->pmove[j].stat) { + default: assert(0); + case PMMOVING: + c = j; + Swap: + if (pm->src.t == AREG && pm->dst.t == AREG) { + insertinstr(blk, curi, + mkinstr(Oswap, *k, mkref(RREG, pm->dst.a), mkref(RREG, pm->src.a), .keep = 1)); + } else if (pm->src.t != pm->dst.t) { + union alloc reg, stk, regtmp; + if (pm->src.t == AREG) + reg = pm->src, stk = pm->dst; + else + stk = pm->src, reg = pm->dst; + assert(reg.t == AREG && stk.t == ASTACK); + regtmp = areg(kisint(*k) ? mctarg->gprscratch : mctarg->fprscratch); + emitmove(pms->fn, *k, regtmp, stk, blk, curi++); + insertinstr(blk, curi++, + mkinstr(Oswap, *k, mkref(RREG, reg.a), mkref(RREG, regtmp.a), .keep = 1)); + emitmove(pms->fn, *k, stk, regtmp, blk, curi++); + } else { + /* FIXME using scratch gpr and fpr for this is hackish */ + assert(pm->src.t == ASTACK && pm->dst.t == ASTACK); + int r1 = mctarg->gprscratch, r2 = mctarg->fprscratch; + enum irclass k1 = siz2intcls[cls2siz[*k]], k2 = KF32 + (cls2siz[*k] == 8); + emitmove(pms->fn, k1, areg(r1), pm->src, blk, curi++); + emitmove(pms->fn, k2, areg(r2), pm->dst, blk, curi++); + emitmove(pms->fn, k1, pm->dst, areg(r1), blk, curi++); + emitmove(pms->fn, k2, pm->src, areg(r2), blk, curi++); + } + break; + case PMTOMOVE: + pm->stat = PMMOVING; + c = pmrec(pms, j, blk, curi, k); + if (c == i) { + c = -1; + break; + } else if (c != -1) { + goto Swap; + } + /* fallthru */ + case PMDONE: + Done: + c = -1; + emitmove(pms->fn, *k, pm->dst, pm->src, blk, curi); + break; + } + + pm->stat = PMDONE; + return c; +} + +static void +emitpm(struct pmstate *pms, struct block *blk) +{ + int curi = blk->ins.n; + for (int i = 0; i < pms->npmove; ++i) { + if (pms->pmove[i].stat == PMTOMOVE) { + pmrec(pms, i, blk, curi, &(enum irclass) { pms->pmove[i].k }); + } + } +} + +/* remove phis by inserting parallel moves */ +static void +lowerphis(struct rega *ra, struct block *blk, struct block *suc) +{ + int predno; + struct block *n = NULL; + + if (!blk->npred && blk != ra->fn->entry) { + assert(!blk->phi.n); + blk->ins.n = 0; + return; + } + if (!blk->s2) n = blk; + + for (predno = 0; predno < suc->npred; ++predno) + if (blkpred(suc, predno) == blk) + break; + assert(predno < suc->npred); + + struct pmstate pms; + pms.fn = ra->fn; + pms.npmove = 0; + /* ensure phi args go to the same slot as phi with parallel copies */ + for (int i = 0; i < suc->phi.n; ++i) { + struct instr *phi = &instrtab[suc->phi.p[i]]; + union ref *arg = &phitab.p[phi->l.i][predno]; + union alloc from, to; + + if (arg->t == RREG) continue; + assert(arg->t == RTMP); + DBG("resolve phi @%d, @%d, %%%d <- %%%d\n", blk->id, suc->id, phi - instrtab, arg->i); + if (instrtab[arg->i].reg) { + from = areg(instrtab[arg->i].reg - 1); + DBG(" it had R%d\n", from.a); + } else { + from = ra->inter[arg->i].alloc; + assert(from.t != ADEAD); + DBG(" found %c%d\n", " RS"[from.t], from.a); + if (from.t == AREG) + instrtab[arg->i].reg = from.a+1; + } + if (phi->reg) { + to = areg(phi->reg - 1); + DBG(" phi had R%d\n", to.a); + } else { + to = ra->inter[phi - instrtab].alloc; + if (to.t == ADEAD) { + DBG(" skip dead phi\n"); + continue; + } + DBG(" found phi %c%d\n", " RS"[to.t], to.a); + if (to.t == AREG) + phi->reg = to.a+1; + } + DBG(" > phi move %c%d -> %c%d\n", " RS"[from.t], from.a, " RS"[to.t], to.a); + if (!n) n = insertblk(ra->fn, blk, suc); + pmadd(&pms, phi->cls, to, from); + } + if (n) emitpm(&pms, n); +} + +/* generate copies for phi operands to transform into conventional-SSA */ +static void +fixcssa(struct function *fn) +{ + struct block *blk = fn->entry; + do { + if (!blk->phi.n) continue; + for (int p = 0; p < blk->npred; ++p) { + struct block *n, *pred = blkpred(blk, p); + if (!pred->s2) { + /* pred only has 1 successor (blk), so insert move directly in it */ + n = pred; + } else { + n = insertblk(fn, pred, blk); + assert(n->jmp.t == Jb && n->s1 == blk); + } + for (int i = 0; i < blk->phi.n; ++i) { + int phi = blk->phi.p[i]; + union ref *args = phitab.p[instrtab[phi].l.i]; + args[p] = insertinstr(n, n->ins.n, mkinstr(Ocopy, instrtab[phi].cls, args[p])); + } + } + } while ((blk = blk->lnext) != fn->entry); + + fn->prop &= ~FNBLKID; +} + +static inline bool +rangeoverlap(struct range a, struct range b) +{ + return a.from < b.to && b.from < a.to; +} + +static void +pushrange(struct interval *it, struct range r) +{ + if (it->nrange < 2) it->_rinl[it->nrange++] = r; + else if (it->nrange > 2) xbpush(&it->_rdyn, &it->nrange, r); + else { + struct range *d = NULL; + xbgrow(&d, 4); + memcpy(d, it->_rinl, 2*sizeof *d); + d[it->nrange++] = r; + it->_rdyn = d; + } +} +#define itrange(it, i) ((it)->nrange <= 2 ? (it)->_rinl : (it)->_rdyn)[i] + +static inline int +intervalbeg(struct interval *it) +{ + assert(it->nrange); + return itrange(it, 0).from; +} + +static inline int +intervalend(struct interval *it) +{ + assert(it->nrange); + return itrange(it, it->nrange-1).to; +} + +static bool +intersoverlap(struct interval *a, struct interval *b) +{ + for (int i = 0, j = 0; i < a->nrange && j < b->nrange; ) { + struct range r1 = itrange(a, i), r2 = itrange(b, j); + if (rangeoverlap(r1, r2)) return 1; + if (r1.to <= r2.from) ++i; + else ++j; + } + return 0; +} + +static inline void +incrcost(struct interval *it, struct block *blk) +{ + /* treat each loop as executing instr 8 times */ + it->cost += 1 << (blk->loop * 3); +} + +static bool +intervaldef(struct rega *ra, int t, struct block *blk, int pos, int reghint) +{ + struct interval *it = &ra->inter[t]; + if (it->nrange) { + ushort *beg = &itrange(it, 0).from; + assert(*beg <= pos); + incrcost(it, blk); + *beg = pos; + return 1; + } + return 0; +} + +static void +addrange(struct rega *ra, int t, struct range new, int reghint) +{ + struct interval *it = &ra->inter[t]; + struct range *fst; + int n; + + if (!it->nrange) { + ++ra->intercount; + it->rhint = reghint; + it->fpr = kisflt(insrescls(instrtab[t])); + pushrange(it, new); + return; + } + + fst = &itrange(it, 0); + /* fully covered by first range? */ + if (fst->from <= new.from && fst->to >= new.to) return; + /* overlaps with first range ? */ + if (fst->from <= new.to && new.to < fst->to) { + fst->from = new.from; + } else { + /* put new range at the start */ + pushrange(it, new); + memmove(&itrange(it, 1), &itrange(it, 0), sizeof(struct range) * (it->nrange - 1)); + itrange(it, 0) = new; + } + + /* new range might cover existing ranges (loop header lives), + * check and succesively merge */ + fst = &itrange(it, 0); + n = 0; + for (int i = 1; i < it->nrange; ++i) { + struct range other = itrange(it, i); + if (fst->to >= other.from) { + fst->to = fst->to > other.to ? fst->to : other.to; + ++n; + } else break; + } + + if (n > 0) { + for (int i = 1; i + n < it->nrange; ++i) + itrange(it, i) = itrange(it, i+n); + if (it->nrange > 2 && it->nrange - n <= 2) { + struct range *dyn = it->_rdyn; + memcpy(it->_rinl, dyn, (it->nrange - n) * sizeof *dyn); + xbfree(dyn); + } + it->nrange -= n; + } +} + +static void +usereg(struct rega *ra, int reg, struct block *blk, int pos) +{ + struct fixinterval *fxit; + if (rstest(mctarg->rglob, reg)) return; /* regalloc never allocates globally live regs, so don't need intervals for those */ + for (struct fixinterval *prev = NULL, *fxit = ra->fixed; fxit; prev = fxit, fxit = fxit->next) { + if (fxit->range.from > pos) break; + if (fxit->rs == BIT(reg) && fxit->range.from <= pos && pos < fxit->range.to) { + /* contained by existing interval */ + fxit->range.from = blk->inumstart; + /* insert at head */ + //DBG(">>>extend REG %s range %d-%d\n", mctarg->rnames[reg], fxit->range.from, fxit->range.to); + if (prev) { + prev->next = fxit->next; + fxit->next = ra->fixed; + ra->fixed = fxit; + } + return; + } + } + fxit = alloc(ra->arena, sizeof *fxit, 0); + fxit->next = ra->fixed; + fxit->range = (struct range) {blk->inumstart, pos}; + fxit->rs = BIT(reg); + ra->fixed = fxit; +} + +static bool +defreg(struct rega *ra, int reg, int pos) { + if (rstest(mctarg->rglob, reg)) return 1; + for (struct fixinterval *prev = NULL, *fxit = ra->fixed; fxit; prev = fxit, fxit = fxit->next) { + if (fxit->rs == BIT(reg)) { + if (fxit->range.from <= pos) { + fxit->range.from = pos; + struct fixinterval **at = &ra->fixed; + if ((*at)->range.from > pos) { + /* keep sorted */ + //DBG("moved %s\n", mctarg->rnames[reg]); + if (prev) prev->next = fxit->next; + while ((*at)->range.from < pos) at = &(*at)->next; + fxit->next = *at; + *at = fxit; + } + //DBG(">>>def REG %s range %d-%d\n", mctarg->rnames[reg], fxit->range.from, fxit->range.to); + return 1; + } + break; + } + } + return 0; +} + +/* lifetime interval construction */ +static void +buildintervals(struct rega *ra) +{ + extern int ninstr; + struct block *blk, *last; + struct bitset **livein = alloc(ra->arena, ra->fn->nblk * sizeof *livein, 0); + size_t bssize = BSSIZE(ninstr); + struct loops { /* list of loops */ + struct loops *next; + struct block *hdr, *end; + } *loops = NULL; + for (int i = 0; i < ra->fn->nblk; ++i) + livein[i] = allocz(ra->arena, bssize * sizeof *livein[i], 0); + ra->inter = allocz(ra->arena, ninstr * sizeof *ra->inter, 0); + ra->ninter = ninstr; + + uint n = numberinstrs(ra->fn); + assert((ushort)n == n && "too many instrs for struct range"); + /* visit blocks in reverse, to build lifetime intervals */ + blk = last = ra->fn->entry->lprev; + do { + struct bitset *live = livein[blk->id]; + /* live = union of successor.liveIn for each successor of b */ + if (blk->s1) bsunion(live, livein[blk->s1->id], bssize); + if (blk->s2) bsunion(live, livein[blk->s2->id], bssize); + + /* for each phi function phi of successors of b do + * live.add(phi.inputOf(b)) + */ + for (struct block *suc = blk->s1; suc; suc = blk->s2) { + int predno; + for (predno = 0; blkpred(suc, predno) != blk; ++predno) ; + for (int i = 0; i < suc->phi.n; ++i) { + struct instr *phi = &instrtab[suc->phi.p[i]]; + union ref *arg = &phitab.p[phi->l.i][predno]; + assert(arg->t == RTMP); + bsset(live, arg->i); + incrcost(&ra->inter[arg->i], blk); + } + if (suc == blk->s2) break; + } + + /* for each opd in live do + * intervals[opd].addRange(b.from, b.to) + */ + for (uint i = 0; bsiter(&i, live, bssize); ++i) { + addrange(ra, i, mkrange(blk->inumstart, blk->inumstart + blk->ins.n + 2), -1); + } + + /* for each operation op of b in reverse order do */ + struct instr *ins = NULL; + union ref queue[8] = { blk->jmp.arg[0], blk->jmp.arg[1] }; + goto Branchopd; + for (int curi, pos ; curi >= 0; --curi) { + int out = blk->ins.p[curi], reghint; + ins = &instrtab[out]; + pos = blk->inumstart + 1 + curi; + /* for each output operand opd of op do + * intervals[opd].setFrom(op.id) + * live.remove(opd) + */ + reghint = ins && ins->op == Ocopy && ins->l.t == RREG ? ins->l.i : -1; + if (!intervaldef(ra, out, blk, pos, reghint)) { + if (insrescls(*ins) && ins->op != Omove && !ins->keep && !(ins->op == Ocopy && ins->l.t == RREG)) { + /* dead */ + *ins = mkinstr(Onop,0,); + } + } + bsclr(live, out); + + /* gather fixed intervals */ + if (ins->op == Omove) { + assert(ins->l.t == RREG); + if (ins->l.bits == ins->r.bits) {/* special case `move Rx,Rx`: clobber reg, not a real use */ + usereg(ra, ins->l.i, blk, pos); + assert(defreg(ra, ins->l.i, pos)); + } else if (!defreg(ra, ins->l.i, pos)) { + if (ins->keep) { /* clobber here */ + usereg(ra, ins->l.i, blk, pos); + assert(defreg(ra, ins->l.i, pos)); + } else { + /* dead register use. for example if + * move RCX, %1 + * %2 = shl 1, RCX + * and %2 is dead, the move to RCX can be killed */ + *ins = mkinstr(Onop,0,); + } + } else { + rsset(&ra->fn->regusage, ins->l.i); + } + if (ins->l.bits == ins->r.bits) + continue; + } else if (ins->op == Ocall) { + struct call *call = &calltab.p[ins->r.i]; + regset rclob = (gpregset | fpregset) &~ (mctarg->rglob | mctarg->rcallee); + ra->fn->isleaf = 0; + + for (int i = 0; i < 2; ++i) { + if (call->abiret[i].ty.bits) { + int reg = call->abiret[i].reg; + rsclr(&rclob, reg); + defreg(ra, reg, pos); + } + } + if (rclob) { + struct fixinterval *fxit = alloc(ra->arena, sizeof *fxit, 0); + fxit->next = ra->fixed; + fxit->range = mkrange(pos, pos); + fxit->rs = rclob; + ra->fixed = fxit; + } + for (int j = call->narg - 1; j >= 0; --j) { + struct abiarg abi = call->abiarg[j]; + if (!abi.isstk) { + usereg(ra, abi.reg, blk, pos); + } + } + } + + /* for each input operand opd of op do + * intervals[opd].addRange(b.from, op.id) + * live.add(opd) + */ + reghint = (ins && ins->op == Omove && ins->l.t == RREG) ? ins->l.i : -1; + queue[0] = ins->r, queue[1] = ins->l; + if (0) { + Branchopd: + reghint = -1; + curi = blk->ins.n; + pos = blk->inumstart + blk->ins.n + 1; + } + for (int nqueue = ins && ins->op == Omove ? 1 : 2; nqueue > 0;) { + union ref r = queue[--nqueue]; + + /* do not allocate a reg for a cmp op used as branch argument, since it's a pseudo op */ + if (curi == blk->ins.n && blk->jmp.t == Jb && r.t == RTMP && instrtab[r.i].keep) + continue; + + if (r.t == RTMP) { + assert(instrtab[r.i].op != Onop); + incrcost(&ra->inter[r.i], blk); + addrange(ra, r.i, mkrange(blk->inumstart, pos), reghint); + bsset(live, r.i); + } else if (r.t == RREG) { + usereg(ra, r.i, blk, pos); + } else if (r.t == RADDR) { + reghint = -1; + queue[nqueue++] = addrtab.p[r.i].base; + queue[nqueue++] = addrtab.p[r.i].index; + } + } + } + + /* for each phi function phi of b do + * live.remove(phi.output) + */ + for (int i = 0; i < blk->phi.n; ++i) { + int phi = blk->phi.p[i]; + bsclr(live, phi); + for (int i = 0; i < blk->npred; ++i) + incrcost(&ra->inter[phi], blkpred(blk, i)); + } + + /* if b is loop header then + * loopEnd = last block of the loop starting at b + * for each opd in live do + * intervals[opd].addRange(b.from, loopEnd.to) + */ + struct block *loopend = NULL; + for (int i = 0; i < blk->npred; ++i) { + struct block *pred = blkpred(blk, i); + if (pred->id > blk->id) + loopend = loopend && loopend->id > pred->id ? loopend : pred; + } + if (loopend) { + if (loops) DBG("@lp @%d\n", blk->id); + for (struct loops *l = loops; l; l = l->next) { + /* a nested loop might end later than loopend, which lengthens this outer loop. */ + /* XXX is this correct? more loop analysis might be required? */ + if (l->hdr->id > loopend->id) break; + DBG(" check <@%d-@%d>\n", l->hdr->id, l->end->id); + if (l->hdr->id > blk->id && l->hdr->id < loopend->id && l->end->id > loopend->id) + loopend = l->end; + } + DBG("loop header @%d (to @%d)\n", blk->id, loopend->id); + /* append to loop list */ + loops = alloccopy(ra->arena, &(struct loops){loops, blk, loopend}, sizeof *loops, 0); + for (uint opd = 0; bsiter(&opd, live, bssize); ++opd) { + // DBG(" i have live %%%d\n", opd); + addrange(ra, opd, mkrange(blk->inumstart, loopend->inumstart + loopend->ins.n+1), -1); + } + } + } while ((blk = blk->lprev) != last); + + if (ccopt.dbg.r) { + for (int var = 0; var < ninstr; ++var) { + struct interval *it = &ra->inter[var]; + if (!it->nrange) continue; + DBG("lifetime of %%%d: ", var); + for (int i = 0; i < it->nrange; ++i) { + struct range r = itrange(it, i); + DBG("[%d,%d)%s", r.from, r.to, i < it->nrange-1 ? ", " : ""); + } + DBG(" spill cost: %d\n", it->cost); + } + for (struct fixinterval *fx = ra->fixed; fx; fx = fx->next) { + DBG("fixed {"); + for (int r = 0, f=1; rsiter(&r, fx->rs); ++r, f=0) + DBG(&" %s"[f], mctarg->rnames[r]); + DBG("}: [%d,%d)\n", fx->range.from, fx->range.to); + } + } +} + +static bool +itcontainspos(struct interval *it, int pos) +{ + for (int i = 0; i < it->nrange; ++i) { + struct range r = itrange(it, i); + if (r.from > pos) return 0; + if (pos < r.to) return 1; + } + return 0; +} + +/* quicksort */ +static void +sortintervals(struct interval **xs, int lo, int hi) +{ + assert(lo >= 0 && hi >= 0); + while (lo < hi) { + /* partition */ + int i = lo - 1, p = hi + 1, + pivot = intervalbeg(xs[lo]); + for (;;) { + struct interval *tmp; + do ++i; while (intervalbeg(xs[i]) < pivot); + do --p; while (intervalbeg(xs[p]) > pivot); + if (i >= p) break; + /* swap */ + tmp = xs[i]; + xs[i] = xs[p]; + xs[p] = tmp; + } + /* recur */ + if (p + 1 >= hi) { + hi = p; + } else { + if (lo < p) + sortintervals(xs, lo, p); + lo = p + 1; + } + } +} + +static union alloc +allocstk(struct rega *ra) +{ + uint s = 0; + if (bsiter(&s, ra->freestk, BSSIZE(MAXSPILL))) { + bsclr(ra->freestk, s); + if (ra->stktop < s) ra->stktop = s+1; + } else { + s = ra->stktop++; + } + if (ra->maxstk < s+1) ra->maxstk = s+1; + return astack(s); +} + +static void +freestk(struct rega *ra, int slot) +{ + DBG("FREE stk %d\n",slot); + if (slot < MAXSPILL) + bsset(ra->freestk, slot); + else if (slot == ra->stktop - 1) + --ra->stktop; +} + +#define interval2temp(it) (int)(it - ra->inter) + +static void +linearscan(struct rega *ra) +{ + if (!ra->intercount) return; + + /* sort intervals */ + struct interval **unhandled = alloc(ra->arena, sizeof *unhandled * ra->intercount, 0); + int nunhandled = 0; + for (int i = 0; i < ra->ninter; ++i) { + if (!ra->inter[i].nrange) continue; + unhandled[nunhandled++] = &ra->inter[i]; + } + assert(nunhandled == ra->intercount); + sortintervals(unhandled, 0, nunhandled-1); + + regset freeregs = (gpregset | fpregset) &~ (mctarg->rglob | (1ull<<mctarg->gprscratch) | (1ull<<mctarg->fprscratch)); + memset(ra->freestk, 0xFF, sizeof ra->freestk); + + /* LINEAR SCAN */ + struct interval *actives[2] = {0}, /* gpr set and fpr set */ + *inactives[2] = {0}, + *spilled = NULL, **spilled_tail = &spilled; + for (struct interval **pcurrent = unhandled; nunhandled > 0; ++pcurrent, --nunhandled) { + struct interval *current = *pcurrent; + int pos = intervalbeg(current); + + struct interval **active = &actives[current->fpr], **inactive = &inactives[current->fpr], + **lnk, *it, *next; + /* Expire old intervals */ + /* check for intervals in active that are handled or inactive */ + for (lnk = active, it = *lnk; (next = it?it->next:0), it; it = next) { + assert(it->alloc.t == AREG); + /* ends before position? */ + if (intervalend(it) <= pos) { + /* move from active to handled */ + *lnk = next; + //DBG(" unblock %s %X\n", mctarg->rnames[it->alloc.a], ra->free); + rsset(&freeregs, it->alloc.a); + } else if (!itcontainspos(it, pos)) { /* it does not cover position? */ + /* move from active to inactive */ + *lnk = next; + it->next = *inactive; + *inactive = it; + rsset(&freeregs, it->alloc.a); + DBG(" >> %%%zd unblock %s\n", interval2temp(it), mctarg->rnames[it->alloc.a]); + } else lnk = &it->next; + } + /* check for intervals in inactive that are handled or active */ + for (lnk = inactive, it = *lnk; (next = it?it->next:0), it; it = next) { + assert(it->alloc.t == AREG); + /* ends before position? */ + if (intervalend(it) <= pos) { + /* move from inactive to handled */ + *lnk = next; + } else if (itcontainspos(it, pos)) { /* it covers position? */ + /* move from inactive to active */ + *lnk = next; + it->next = *active; + *active = it; + assert(it->alloc.t == AREG); + assert(rstest(freeregs, it->alloc.a)); + rsclr(&freeregs, it->alloc.a); + DBG(" << %%%zd reblock %s\n", interval2temp(it), mctarg->rnames[it->alloc.a]); + } else lnk = &it->next; + } + + /** find a register for current **/ + + int this = interval2temp(current); + regset avail = freeregs & (current->fpr ? fpregset : gpregset), + fixexcl = 0, excl = 0; + struct instr *ins = &instrtab[this]; + int reg = 0; + + /* exclude regs from overlapping fixed intervals */ + int end = intervalend(current); + for (struct fixinterval *last = NULL, *fxit = ra->fixed; fxit; + last = fxit, fxit = fxit->next) { + if (last) assert(last->range.from <= fxit->range.from && "unsorted fixintervals"); + if (fxit->range.to <= pos) { + ra->fixed = fxit->next; + continue; + } else if (fxit->range.from >= end) { + break; + } + + for (int i = 0; i < current->nrange; ++i) { + if (rangeoverlap(fxit->range, itrange(current, i))) { + fixexcl |= fxit->rs; + } + } + } + /* exclude regs from overlapping inactive intervals */ + for (struct interval *it = *inactive; it; it = it->next) { + if (it->alloc.t == AREG && intersoverlap(it, current)) { + rsset(&excl, it->alloc.a); + } + } + /* for 2-address instrs, exclude reg from 2nd arg (unless arg#1 == arg#2) */ + if (ins->inplace && opnarg[ins->op] == 2) { + int xreg; + if (ins->r.t == RREG) rsset(&excl, ins->r.i); + else if (ins->r.t == RTMP && (xreg = instrtab[ins->r.i].reg)) { + if (ins->r.bits != ins->l.bits) + rsset(&fixexcl, xreg-1); + } + } + excl |= fixexcl; + avail &= ~excl; + + if (!avail) { /* no regs left, must spill */ + struct interval **ptospill = NULL, *tospill = current; + /* heuristic: look for longest-lived active interval with lower spill cost */ + int curend = intervalend(current); + for (lnk = active, it = *lnk; (next = it ? it->next : 0), it; it = next) { + int end = intervalend(it); + if (it->cost < tospill->cost && end > curend && !rstest(fixexcl, it->alloc.a)) { + ptospill = lnk; + tospill = it; + reg = tospill->alloc.a; + } + lnk = &it->next; + } + + /* insert in spilled, keep sorted */ + if (ptospill) { + *ptospill = tospill->next; /* remove from active */ + int from = intervalbeg(tospill); + lnk = &spilled; + /* XXX potentially slow linear search */ + while (*lnk && intervalbeg(*lnk) < from) + lnk = &(*lnk)->next; + tospill->next = *lnk; + *lnk = tospill; + } else { /* tospill == current, so we can just append and keep it sorted */ + *spilled_tail = tospill; + tospill->next = NULL; + } + if (!tospill->next) /* update spilled list tail */ + spilled_tail = &tospill->next; + + assert(spilled != NULL); + if (tospill == current) { + DBG("spilled %%%d\n", this); + continue; + } else { + instrtab[interval2temp(tospill)].reg = 0; + DBG("%%%d takes %s from %%%d (spilled)\n", this, mctarg->rnames[reg], + interval2temp(tospill)); + goto GotReg; + } + } + + /* have free regs, try to use hint */ + if (current->rhint >= 0) + DBG("have hint %s for %%%zd\n", + mctarg->rnames[current->rhint], interval2temp(current)); + if (current->rhint >= 0 && rstest(avail, current->rhint)) { + DBG(" (used hint)\n"); + reg = current->rhint; + goto GotReg; + } else { + /* for two-address instructions, try to use the reg of left arg */ + if (ins->op != Ophi && (opnarg[ins->op] == 1 || (opnarg[ins->op] == 2 && ins->inplace))) { + DBG(" %%%d try %d,%d\n", this, ins->l.t,ins->l.i); + if (ins->l.t == RREG && rstest(avail, reg = ins->l.i)) + goto GotReg; + if (ins->l.t == RTMP) + if ((reg = instrtab[ins->l.i].reg-1) >= 0) + if (rstest(avail, reg)) + goto GotReg; + /* for phi, try to use reg of any arg */ + } else if (ins->op == Ophi) { + union ref *arg = phitab.p[ins->l.i]; + for (int i = 0; i < xbcap(arg); ++i) { + if (arg->t == RREG && rstest(avail, reg = arg->i)) goto GotReg; + if (arg->t == RTMP) + if ((reg = instrtab[arg->i].reg-1) >= 0) + if (rstest(avail, reg)) + goto GotReg; + } + } + + /* no hints to use */ + if (avail &~ mctarg->rcallee) /* prefer caller-saved regs */ + avail &=~ mctarg->rcallee; + /* and pick first available reg */ + reg = lowestsetbit(avail); + } + GotReg: + current->alloc = areg(reg); + ins->reg = reg + 1; + DBG("%%%d got %s\n", this, mctarg->rnames[reg]); + rsclr(&freeregs, reg); + rsset(&ra->fn->regusage, reg); + + /* add current to active */ + current->next = *active; + *active = current; + } + + if (ccopt.dbg.r) { + DBG("regusage: "); + for (int r = 0; r < MAXREGS; ++r) { + if (rstest(ra->fn->regusage, r)) DBG(" %s", mctarg->rnames[r]); + } + DBG("\n"); + } + /* allocate stack slots for spilled intervals + * this is like another (simplified) linear scan pass */ + struct interval *active = NULL; + int prevpos = -1; + if (spilled) DBG("spilled:\n"); + for (struct interval *current = spilled, *next; current; current = next) { + int pos = intervalbeg(current); + DBG(" %%%zd: [%d,%d)\n", interval2temp(current), pos, intervalend(current)); + assert(pos >= prevpos && "unsorted spilled?"); + prevpos = pos; + /* Expire old intervals */ + struct interval **lnk, *it, *lnext; + for (lnk = &active, it = *lnk; (lnext = it ? it->next : 0), it; it = lnext) { + /* ends before position? */ + if (intervalend(it) <= pos) { + /* move from active to handled */ + *lnk = lnext; + freestk(ra, it->alloc.a); + } else lnk = &it->next; + } + /* allocate a stack slot for current and move to active */ + current->alloc = allocstk(ra); + DBG(" got stk%d\n", current->alloc.a); + next = current->next; + current->next = active; + active = current; + } +} + +static bool +isstoreimm(union ref r) +{ + if (r.t == RTMP) return 1; /* register OK */ + if (isintcon(r)) switch (target.arch) { + case ISxxx: assert(0); + /* TODO don't hard code this architecture dependent dispatch */ + case ISx86_64: return concls(r) == KI32; /* x86: MOV [addr], imm32 */ + case ISaarch64: return r.i == 0; /* arm doesn't have STR <imm>, but has zero register */ + } + return 0; +} + +/* replace temps with physical regs, add loads & stores for spilled temps */ +static bool +devirt(struct rega *ra, struct block *blk) +{ + bool allnops = 1; + struct function *fn = ra->fn; + union alloc spillsave[4] = {0}; + memset(ra->freestk, 0, BSSIZE(MAXSPILL) * sizeof *ra->freestk); + + for (int curi = 0; curi < blk->ins.n; ++curi) { + int temp = blk->ins.p[curi]; + struct instr *ins = &instrtab[temp]; + struct interval *it; + union alloc *alloc; + struct addr newaddr; + union ref *argref[4]; + int curi0; + int naddr = 0; + int nargref = 0; + int nspill = 0; + + /** devirtualize operands **/ + for (int i = 0; i < 2; ++i) { + union ref *r = &i[&ins->l]; + if (r->t == RADDR) { + struct addr *a = &addrtab.p[r->i]; + ++naddr; + newaddr = *a; + argref[nargref++] = &newaddr.base; + argref[nargref++] = &newaddr.index; + } else { + argref[nargref++] = r; + } + } + for (int i = 0; i < nargref; ++i) { + union ref *r = argref[i]; + int tr; + if (r->t == RTMP && (it = &ra->inter[r->i])->nrange > 0) { + alloc = &it->alloc; + if (alloc->t == ASTACK && ins->op == Omove && kisint(ins->cls) == kisint(instrtab[r->i].cls)) { + /* move [reg], [stk] -> [reg] = load [stk] */ + assert(r == &ins->r && ins->l.t == RREG); + ins->reg = ins->l.i+1; + ins->op = cls2load[instrtab[r->i].cls]; + ins->l = stkslotref(fn, alloc->a*8); + ins->r = NOREF; + } else if (alloc->t == ASTACK && ins->op == Ocopy && r == &ins->l && ins->reg && kisint(ins->cls) == kisint(instrtab[r->i].cls)) { + /* [reg] = copy [stk] -> [reg] = load [stk] */ + ins->op = cls2load[instrtab[r->i].cls]; + ins->l = stkslotref(fn, alloc->a*8); + } else if (alloc->t == ASTACK) { + /* ref was spilled, gen load to scratch register and use it */ + struct instr ld = {.cls = insrescls(instrtab[r->i])}; + int reg = kisint(ld.cls) ? mctarg->gprscratch : mctarg->fprscratch; + bool dosave = 0; + /* pick scratch register, or any register that doesn't conflict with this instr's srcs/dst */ + if (nspill > 0) { + regset avail = (kisflt(ld.cls) ? fpregset : gpregset) &~ mctarg->rglob; + if (ins->reg) rsclr(&avail, ins->reg-1); + for (int j = 0; j < nargref; ++j) { + struct interval *it; + if (argref[j]->t == RREG) rsclr(&avail, argref[j]->i); + else if (argref[j]->t == RTMP) { + it = &ra->inter[argref[j]->i]; + if (it->alloc.t == AREG) rsclr(&avail, it->alloc.a); + } + } + assert(avail != 0); + if (avail &~ (fn->regusage | mctarg->rcallee)) avail &= ~(fn->regusage | mctarg->rcallee); + reg = lowestsetbit(avail); + /* if not the designated scratch register, we need to save+restore */ + if (rstest(fn->regusage, reg) || rstest(mctarg->rcallee, reg)) { + dosave = 1; + if (!spillsave[nspill-1].t) spillsave[nspill-1] = allocstk(ra); + emitmove(fn, isgpr(reg) ? KPTR : KF64, spillsave[nspill-1], areg(reg), blk, curi++); + } + } + ld.reg = reg+1; + ld.op = cls2load[ld.cls]; + ld.l = stkslotref(fn, alloc->a*8); + insertinstr(blk, curi++, ld); + *r = mkref(RREG, reg); + if (nspill > 0 && dosave) { + emitmove(fn, isgpr(reg) ? KPTR : KF64, areg(reg), spillsave[nspill-1], blk, curi+1); + } + ++nspill; + } else if ((tr = instrtab[r->i].reg)) { + assert(alloc && alloc->t == AREG && alloc->a == tr-1); + *r = mkref(RREG, tr-1); + } + } + } + if (nspill > 1) assert(ins->op != Ocall); + if (naddr) { + union ref *r = ins->l.t == RADDR ? &ins->l : &ins->r; + *r = mkaddr(newaddr); + } + + /* devirtualize destination */ + curi0 = curi; + alloc = temp < ra->ninter && (it = &ra->inter[temp]) && it->nrange ? &it->alloc : NULL; + if (alloc && alloc->t == ASTACK) { + enum irclass cls = insrescls(*ins); + int store = cls2store[cls]; + /* t was spilled, gen store */ + if (ins->op == Ocopy && (ins->l.t == RREG || isstoreimm(ins->l))) { + ins->op = store; + ins->r = ins->l; + ins->l = stkslotref(fn, alloc->a*8); + } else { + bool dosave = 0; + int reg = kisint(insrescls(*ins)) ? mctarg->gprscratch : mctarg->fprscratch; + if (nspill > 0) { + regset avail = (kisflt(cls) ? fpregset : gpregset) &~ mctarg->rglob; + for (int j = 0; j < nargref; ++j) { + if (argref[j]->t == RREG) rsclr(&avail, argref[j]->i); + } + assert(avail != 0); + if (avail &~ (fn->regusage | mctarg->rcallee)) avail &= ~(fn->regusage | mctarg->rcallee); + /* if not the designated scratch register, we need to save+restore */ + reg = lowestsetbit(avail); + if (rstest(fn->regusage, reg) || rstest(mctarg->rcallee, reg)) { + dosave = 1; + if (!spillsave[nspill-1].t) spillsave[nspill-1] = allocstk(ra); + emitmove(fn, isgpr(reg) ? KPTR : KF64, spillsave[nspill-1], areg(reg), blk, curi++); + curi0 = curi; + } + } + ins->reg = reg+1; + insertinstr(blk, ++curi, + mkinstr(store, 0, stkslotref(fn, alloc->a*8), mkref(RREG, reg))); + if (nspill > 0 && dosave) { + emitmove(fn, isgpr(reg) ? KPTR : KF64, areg(reg), + spillsave[nspill-1], blk, ++curi); + } + } + } + if (!ins->reg && insrescls(*ins) && ins->op != Omove && !ins->keep && !oisstore(ins->op)) { + /* dead */ + Nop: + ins->op = Onop; + } else if (ins->op == Omove && ins->r.t == RREG && ins->l.i == ins->r.i) { + /* move r1,r2 / r1=r2 */ + goto Nop; + } else if (ins->op == Ocopy && ins->l.t == RREG && ins->reg-1 == ins->l.i) { + /* r1 = copy r2 / r1=r2 */ + goto Nop; + } else if (ins->op != Onop) { + allnops = 0; + } + if (ins->inplace && ins->l.t == RREG && ins->reg && ins->reg-1 != ins->l.i) { + /* fixup in-place (two-address) instructions */ + allnops = 0; + insertinstr(blk, curi0, mkmove(ins->cls, ins->reg-1, ins->l.i)); + ++curi; + ins->l.i = ins->reg-1; + } + if (!ins->reg && in_range(ins->op, Oloads8, Oloadf64)) { + assert(ins->keep); + ins->reg = kisint(ins->cls) ? mctarg->gprscratch+1 : mctarg->fprscratch+1; + } + } + + if (allnops) vfree(&blk->ins); + return allnops; +} + +static void +fini(struct rega *ra) +{ + int id = 0; + struct function *fn = ra->fn; + struct block *blk = fn->entry; + + do { + blk->id = id++; + bool allnops = devirt(ra, blk); + if (allnops && !blk->s2 && blk->npred > 0) { /* remove no-op blocks */ + bool delet = 1; + for (int i = 0; i < blk->npred; ++i) { + struct block *p = blkpred(blk, i); + if (p == blk || (p->s2 && !blk->s1)) + delet = 0; + } + for (int i = 0; i < blk->npred; ++i) { + struct block *p = blkpred(blk, i); + if (!p->s2 && !blk->s1) { + /* simplify: + * + * @p: + * ... + * b @blk + * @blk: + * NOP + * ret/trap + */ + assert(p->s1 == blk); + p->jmp.t = blk->jmp.t; + p->s1 = NULL; + } else if (blk->s1 && blk->s1 != blk) { + /* simplify: + * + * @p: + * ... + * b %x, @blk, @other + * @blk: + * NOP + * b @next + */ + struct block *next = blk->s1; + if (p->s1 == blk) p->s1 = next; + else if (p->s2 == blk) p->s2 = next; + else continue; + for (int i = 0; i < next->npred; ++i) { + if (blkpred(next, i) == blk) { + blkpred(next, i) = p; + goto NextPred; + } + } + addpred(next, p); + } + NextPred:; + } + if (delet) { + freeblk(fn, blk); + --id; + } + } else if (allnops) { + vfree(&blk->ins); + } + } while ((blk = blk->lnext) != fn->entry); +} + +void +regalloc(struct function *fn) +{ + struct rega ra = {fn, .arena = fn->passarena}; + struct block *blk, *last; + + /* setup */ + if (!fpregset || !gpregset) { + for (int r = 0; r < MAXREGS; ++r) { + if (isfpr(r)) + rsset(&fpregset, r); + else if (isgpr(r)) + rsset(&gpregset, r); + } + } + fn->regusage = 0; + fn->stksiz = alignup(fn->stksiz, 8); + fn->isleaf = 1; + + /* put into reverse post order */ + sortrpo(fn); + + /* fix liveness ranges */ + fixlive(fn); + + /* transform into CSSA */ + fixcssa(fn); + + fillblkids(fn); + fillloop(fn); + + if (ccopt.dbg.r) { + bfmt(ccopt.dbgout, "<< Before linear scan >>\n"); + irdump(fn); + } + + /* linear scan: build lifetime intervals */ + buildintervals(&ra); + + /* linear scan: assign physical registers and stack slots */ + linearscan(&ra); + + /* get out of SSA */ + blk = last = fn->entry->lprev; + do { + if (blk->id < 0) continue; + for (int i = 0; i < blk->npred; ++i) { + lowerphis(&ra, blkpred(blk, i), blk); + } + vfree(&blk->phi); + } while ((blk = blk->lprev) != last); + + /* devirtualize & final cleanup */ + fini(&ra); + fn->stksiz += ra.maxstk*8; + if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name); + + for (struct interval *it = ra.inter; ra.intercount > 0; ++it) { + if (it->nrange > 2) xbfree(it->_rdyn); + if (it->nrange > 0) --ra.intercount; + } + + if (ccopt.dbg.r) { + bfmt(ccopt.dbgout, "<< After regalloc >>\n"); + irdump(fn); + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_simpl.c b/src/ir_simpl.c new file mode 100644 index 0000000..a01879a --- /dev/null +++ b/src/ir_simpl.c @@ -0,0 +1,308 @@ +#include "ir.h" + +static int +mulk(struct instr *ins, struct block *blk, int *curi) +{ + vlong iv = intconval(ins->r); + enum irclass cls = ins->cls; + assert((uvlong)iv > 1 && "trivial mul not handled by irbinop() ?"); + bool neg = iv < 0; + if (neg) iv = -iv; + /* This can be generalized to any sequence of shifts and + * adds/subtracts, but whether that's worth it depends on the number of them + * and the microarchitecture.. clang seems to stop after two shifts. Should + * we compute approximate cost of instrs to determine? For now just handle + * the po2 (+/- 1) case */ + if (ispo2(iv)) { + /* x * 2^y ==> x << y */ + ins->op = Oshl; + ins->r = mkref(RICON, ilog2(iv)); + } else if (ispo2(iv-1)) { + /* x * 5 ==> (x << 2) + x */ + ins->op = Oadd; + ins->r = ins->l; + ins->l = insertinstr(blk, (*curi)++, mkinstr(Oshl, cls, ins->l, mkref(RICON, ilog2(iv-1)))); + } else if (ispo2(iv+1)) { + /* x * 7 ==> (x << 3) - x */ + ins->op = Osub; + ins->r = ins->l; + ins->l = insertinstr(blk, (*curi)++, mkinstr(Oshl, cls, ins->l, mkref(RICON, ilog2(iv+1)))); + } else return 0; + if (neg) { + ins->l = insertinstr(blk, (*curi)++, *ins); + ins->op = Oneg; + ins->r = NOREF; + } + return 1; +} + +static int +divmodk(struct instr *ins, struct block *blk, int *curi) +{ + enum op op = ins->op; + enum irclass cls = ins->cls; + vlong iv = intconval(ins->r); + uint nbit = 8 * cls2siz[cls]; + bool neg = (op == Odiv || op == Orem) && iv < 0; + if (ispo2(iv) || (neg && ispo2(-iv))) { /* simple po2 cases */ + union ref temp; + uint s = ilog2(neg ? -iv : iv); + switch (op) { + default: assert(0); + case Oudiv: /* x / 2^s ==> x >> s */ + ins->op = Oslr, ins->r = mkref(RICON, s); + return 1; + case Ourem: /* x % 2^s ==> x & 2^s-1 */ + ins->op = Oand, ins->r = mkintcon(cls, iv - 1); + return 1; + case Odiv: case Orem: + /* have to adjust to round negatives toward zero */ + /* x' = (((x < 0 ? -1 : 0) >>> (Nbit - s)) + x) */ + temp = insertinstr(blk, (*curi)++, mkinstr(Osar, cls, ins->l, mkref(RICON, nbit - 1))); + temp = insertinstr(blk, (*curi)++, mkinstr(Oslr, cls, temp, mkref(RICON, nbit - s))); + temp = insertinstr(blk, (*curi)++, mkinstr(Oadd, cls, ins->l, temp)); + if (op == Odiv) { + /* (-) (x' >> s) */ + struct instr sar = mkinstr(Osar, cls, temp, mkref(RICON, s)); + if (!neg) *ins = sar; + else { + temp = insertinstr(blk, (*curi)++, sar); + ins->op = Oneg, ins->l = temp, ins->r = NOREF; + } + } else { + /* x - (x' & -(2^s)) */ + temp = insertinstr(blk, (*curi)++, mkinstr(Oand, cls, temp, mkintcon(cls, neg ? iv : -iv))); + ins->op = Osub, ins->r = temp; + } + break; + return 0; + } + } + return 0; +} + +static int +doins(struct instr *ins, struct block *blk, int *curi) +{ + int narg = opnarg[ins->op]; + if (oisarith(ins->op)) { + union ref r = narg == 1 ? irunop(NULL, ins->op, ins->cls, ins->l) + : irbinop(NULL, ins->op, ins->cls, ins->l, ins->r); + if (r.bits) { + *ins = mkinstr(Onop,0,); + replcuses(mkref(RTMP, ins - instrtab), r); + return 1; + } + } + enum irclass k = ins->cls; + switch (ins->op) { + case Ocopy: + if ((ins->l.t == RTMP && k == instrtab[ins->l.i].cls) + || (isnumcon(ins->l) && k == concls(ins->l)) + || (kisint(k) && ins->l.t == RICON)) { + union ref it = ins->l; + *ins = mkinstr(Onop,0,); + replcuses(mkref(RTMP, ins - instrtab), it); + return 1; + } + break; + case Omul: + if (kisflt(k)) break; + if (isnumcon(ins->l)) rswap(ins->l, ins->r); /* put const in rhs */ + if (isintcon(ins->r)) return mulk(ins, blk, curi); + break; + case Odiv: case Oudiv: case Orem: case Ourem: + if (kisflt(k)) break; + if (isintcon(ins->r)) return divmodk(ins, blk, curi); + break; + case Oequ: case Oneq: + if (ins->l.t == RTMP && isintcon(ins->r)) { + /* optimize `x <op> C <cmp> Q` */ + /* could apply with add/sub to lth/lte/gth/gte iff no signed wraparound, which isn't assumed */ + struct instr *lhs = &instrtab[ins->l.i]; + enum op o = lhs->op; + if (ins->cls == lhs->cls && (o == Oadd || o == Osub || o == Oxor) && isintcon(lhs->r)) { + uvlong c = intconval(ins->r), q = intconval(lhs->r); + switch (o) { default: assert(0); + case Oadd: c -= q; break; /* x + 3 == C ==> x == C - 3 */ + case Osub: c += q; break; /* x - 3 == C ==> x == C + 3 */ + case Oxor: c ^= q; break; /* x ^ 3 == C ==> x == C ^ 3 */ + } + ins->l = lhs->l, ins->r = mkintcon(ins->cls, c); + deluse(blk, ins - instrtab, mkref(RTMP, lhs - instrtab)); + if (!instruse[lhs - instrtab]) *lhs = mkinstr(Onop,0,); + return 1; + } + } + } + return 0; +} + +static void +jmpfind(struct block **final, struct block **pblk) +{ + struct block **p2 = &final[(*pblk)->id]; + if (*p2 && !(*p2)->phi.n) { + jmpfind(final, p2); + *pblk = *p2; + } +} + +static void +fillpredsrec(struct block *blk) +{ + while (blk && !wasvisited(blk)) { + markvisited(blk); + if (!blk->s1) return; + if (!blk->s1->phi.n) addpred(blk->s1, blk); + if (!blk->s2) { + blk = blk->s1; + continue; + } + if (!blk->s2->phi.n) addpred(blk->s2, blk); + fillpredsrec(blk->s1); + blk = blk->s2; + } +} + +static void +fillpreds(struct function *fn) +{ + struct block *blk = fn->entry, *next; + do { + if (blk->phi.n) continue; + if (blk->npred > 1) + xbfree(blk->_pred); + blk->npred = 0; + blk->_pred = NULL; + } while ((blk = blk->lnext) != fn->entry); + startbbvisit(); + fillpredsrec(fn->entry); + blk = fn->entry->lnext; + do { /* remove dead blocks */ + next = blk->lnext; + if (!blk->npred) freeblk(fn, blk); + } while ((blk = next) != fn->entry); +} + +static void +mergeblks(struct function *fn, struct block *p, struct block *s) +{ + assert(s->npred == 1 && !s->phi.n); + vpushn(&p->ins, s->ins.p, s->ins.n); + p->jmp = p->s1->jmp; + for (int i = 0; i < 2; ++i) { + struct block *ss = (&s->s1)[i]; + if (ss) for (int j = 0; j < ss->npred; ++j) { + if (blkpred(ss, j) == s) { + blkpred(ss, j) = p; + break; + } + } + } + /* breaks use for temps used in s */ + if (s->ins.n || s->jmp.arg[0].t == RTMP || s->jmp.arg[1].t == RTMP) + fn->prop &= ~FNUSE; + p->s1 = s->s1; + p->s2 = s->s2; + freeblk(fn, s); +} + +void +simpl(struct function *fn) +{ + FREQUIRE(FNUSE); + int inschange = 0, + blkchange = 0; + struct block **jmpfinal = allocz(fn->passarena, fn->nblk * sizeof *jmpfinal, 0); + struct block *blk = fn->entry; + + do { + /* merge blocks: + * @blk: + * ... (1) + * b @s + * @s: + * ... (2) + * b {...} + *=> + * @blk: + * ... (1) + * ... (2) + * b {...} + * */ + if (blk != fn->entry) while (blk->s1 && !blk->s2 && blk->s1->npred == 1 && !blk->phi.n) { + mergeblks(fn, blk, blk->s1); + } + } while ((blk = blk->lnext) != fn->entry); + + if (!(fn->prop & FNUSE)) filluses(fn); + + do { + for (int i = 0; i < blk->phi.n; ++i) { + int phi = blk->phi.p[i]; + /* delete trivial phis */ + union ref *args = phitab.p[instrtab[phi].l.i], + same = *args; + if (same.t == RTMP && instrtab[same.i].op == Ophi) goto Next; + if (blk->npred > 1) for (int j = 1; j < blk->npred; ++j) { + if (args[j].bits != same.bits) goto Next; + } + if (!(fn->prop & FNUSE)) filluses(fn); + replcuses(mkref(RTMP, phi), same); + delphi(blk, i); + Next:; + } + + int curi = 0; + DoIns: + for (; curi < blk->ins.n; ++curi) { + struct instr *ins = &instrtab[blk->ins.p[curi]]; + if (ins->op != Onop) { + if (!(fn->prop & FNUSE)) filluses(fn); + inschange += doins(ins, blk, &curi); + } + } + + if (blk->s2 && isintcon(blk->jmp.arg[0])) { + /* simplify known conditional branch */ + struct block *s = intconval(blk->jmp.arg[0]) ? blk->s1 : blk->s2; + delpred(s == blk->s1 ? blk->s2 : blk->s1, blk); + blk->s1 = s, blk->s2 = NULL; + blk->jmp.arg[0] = NOREF; + if (blk->s1 && !blk->s2 && blk->s1->npred == 1 && blk->s1->phi.n == 0) { + mergeblks(fn, blk, blk->s1); + goto DoIns; + } + } + + if (blk != fn->entry && blk->npred == 0) { + freeblk(fn, blk); + } else if (!blk->phi.n && !blk->ins.n) { /* thread jumps.. */ + if (blk->jmp.t == Jb && !blk->s2) { + jmpfind(jmpfinal, &blk->s1); + if (blk->s1 != blk) { + ++blkchange; + jmpfinal[blk->id] = blk->s1; + } + } + } + } while ((blk = blk->lnext) != fn->entry); + + if (blkchange) { + do { + if (blk->s1) jmpfind(jmpfinal, &blk->s1); + if (blk->s2) jmpfind(jmpfinal, &blk->s2); + if (blk->s1 && blk->s1 == blk->s2) { + memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + blk->s2 = NULL; + } + } while ((blk = blk->lnext) != fn->entry); + fillpreds(fn); + sortrpo(fn); + } + if (!(fn->prop & FNUSE)) filluses(fn); + fn->prop &= ~FNDOM; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_ssa.c b/src/ir_ssa.c new file mode 100644 index 0000000..6598fba --- /dev/null +++ b/src/ir_ssa.c @@ -0,0 +1,46 @@ +#include "ir.h" + +void +copyopt(struct function *fn) +{ + struct block *blk = fn->entry; + + FREQUIRE(FNUSE); + do { + for (int i = 0; i < blk->phi.n; ++i) { + /* simplify same-arg phi */ + int phi = blk->phi.p[i]; + union ref *arg = phitab.p[instrtab[phi].l.i]; + for (int j = 1; j < blk->npred; ++j) { + if (arg[j].bits != arg->bits) goto Next; + } + /* being conservative here because phis could have circular dependencies? */ + if (arg->t != RTMP || instrtab[arg->i].op != Ophi) { + replcuses(mkref(RTMP, phi), *arg); + delphi(blk, i--); + } + Next:; + } + for (int i = 0; i < blk->ins.n; ++i) { + union ref var = mkref(RTMP, blk->ins.p[i]); + struct instr *ins = &instrtab[var.i]; + enum irclass k; + + if (ins->op == Ocopy) { + union ref arg = ins->l; + if (arg.t == RTMP) k = insrescls(instrtab[arg.i]); + else if (arg.t == RICON) k = cls2siz[ins->cls] == 4 ? KI32 : KI64; + else if (arg.t == RXCON) k = isnumcon(arg) ? concls(arg) : KPTR; + else continue; + if (ins->cls != k) continue; + + replcuses(var, arg); + *ins = mkinstr(Onop,0,); + deluses(var.i); + } + } + delnops(blk); + } while ((blk = blk->lnext) != fn->entry); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/ir_stack.c b/src/ir_stack.c new file mode 100644 index 0000000..40a7b1d --- /dev/null +++ b/src/ir_stack.c @@ -0,0 +1,33 @@ +#include "ir.h" + +void +lowerstack(struct function *fn) +{ + fn->stksiz = 0; + FREQUIRE(FNUSE); + + struct block *blk = fn->entry; + do { + for (int i = 0; i < blk->ins.n; ++i) { + int t = blk->ins.p[i]; + struct instr *ins = &instrtab[t]; + if (oisalloca(ins->op)) { + uint alignlog2 = ins->op - Oalloca1; + assert(ins->l.i > 0); + uint siz = ins->l.i << alignlog2; + fn->stksiz += siz; + fn->stksiz = alignup(fn->stksiz, 1 << alignlog2); + if (fn->stksiz > (1<<20)-1) error(NULL, "'%s' stack frame too big", fn->name); + *ins = mkinstr(Onop,0,); + replcuses(mkref(RTMP, t), mkref(RSTACK, fn->stksiz)); + } + } + } while ((blk = blk->lnext) != fn->entry); + + if (ccopt.dbg.s) { + bfmt(ccopt.dbgout, "<< After stack >>\n"); + irdump(fn); + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/o_elf.c b/src/o_elf.c new file mode 100644 index 0000000..5044e56 --- /dev/null +++ b/src/o_elf.c @@ -0,0 +1,572 @@ +#include "elf.h" +#include "obj.h" +#include "../ir/ir.h" /* mctarg */ +#include "../endian.h" +#include <unistd.h> +#include <stdlib.h> /* qsort */ + +static union { + ELF_HDRIDENT; + struct elf32hdr h32; + struct elf64hdr h64; +} hdr; +static vec_of(uchar) strs; +struct sym { + uint name; + uchar bind : 4, + type : 4; + ushort shndx; + uvlong value, + size; +}; +static vec_of(struct sym) symtab; +static pmap_of(ushort) symht; +static uint ntextrel, nrodatarel, ndatarel; +struct reloc { + uchar section; + ushort kind; + uint off; + vlong addend; + union { + uint symidx; + internstr symname; + }; +}; +static vec_of(struct reloc) relocs; + +#define O objout + +void +elfinit(void) +{ + memcpy(hdr.i_mag, ELFMAG, 4); + hdr.i_class = ELFCLASS32 + targ_64bit; + hdr.i_data = ELFDATA2LSB + targ_bigendian; + hdr.i_version = ELFVERSION; + hdr.i_osabi = ELFOSABI_SYSV; + hdr.i_abiversion = 0; + hdr.h32.type = ET_REL; + switch (target.arch) { + default: assert(!"arch?"); + case ISx86_64: hdr.h32.machine = EM_X86_64; break; + case ISaarch64: hdr.h32.machine = EM_ARM64; break; + } + hdr.h32.version = ELFVERSION; + if (targ_64bit) { + hdr.h64.ehsize = sizeof(struct elf64hdr); + hdr.h64.shentsize = sizeof(struct elf64shdr); + } else { + hdr.h32.ehsize = sizeof(struct elf32hdr); + hdr.h32.shentsize = sizeof(struct elf32shdr); + } + vinit(&strs, NULL, 4<<10); + vpush(&symtab, ((struct sym){0})); + vpush(&symtab, ((struct sym){ .type = STT_FILE, .shndx = SHN_ABS})); +} + +uint +str2idx(const char *s) +{ + static pmap_of(uint) ht; + uint *p, i; + + if (!ht.v) pmap_init(&ht, 1<<8); + if ((p = pmap_get(&ht, s))) return *p; + if (!strs.n) vpush(&strs, 0); + i = strs.n; + vpushn(&strs, s, strlen(s)+1); + pmap_set(&ht, s, i); + return i; +} + +static struct sym * +findsym(internstr s) +{ + ushort *idx = pmap_get(&symht, s); + return idx ? &symtab.p[*idx] : NULL; +} + +enum { + TEXT_SHNDX = 1, + RODATA_SHNDX = 2, + DATA_SHNDX = 3, + BSS_SHNDX = 4, +}; + +static const char sect2ndx[] = { + [Snone] = SHN_UND, + [Stext] = TEXT_SHNDX, [Srodata] = RODATA_SHNDX, + [Sdata] = DATA_SHNDX, [Sbss] = BSS_SHNDX, +}, shndx2sect[] = { + [SHN_UND] = Snone, + [TEXT_SHNDX] = Stext, [RODATA_SHNDX] = Srodata, + [DATA_SHNDX] = Sdata, [BSS_SHNDX] = Sbss, +}; + +enum section +elfhassym(internstr nam, uint *value) +{ + struct sym *sym = findsym(nam); + if (sym) { + if (value) *value = sym->value; + return shndx2sect[sym->shndx]; + } + return Snone; +} + +void +elfaddsym(internstr nam, int info, enum section sect, uvlong value, uvlong size) +{ + struct sym *sym = findsym(nam), sym0; + if (!sym) { + sym = &sym0; + sym->name = str2idx(&nam->c); + } + sym->bind = info >> 4; + sym->type = info & 0xF; + sym->shndx = sect2ndx[sect]; + sym->value = value; + sym->size = size; + if (sym == &sym0) { + assert(symtab.n < 1<<16); + pmap_set(&symht, nam, symtab.n); + vpush(&symtab, sym0); + } +} + +static const ushort relktab[][NRELOCKIND] = { + [ISx86_64] = { + [REL_ABS64] = 1, /* R_X86_64_64 */ + [REL_ABS32] = 10, /* R_X86_64_32 */ + [REL_ABS32S] = 11, /* R_X86_64_32S */ + [REL_PCREL32] = 2, /* R_X86_64_PC32 */ + [REL_PLT32] = 4, /* R_X86_64_PLT32 */ + [REL_GOTPCRELX] = 41, /* R_X86_64_GOTPCRELX */ + [REL_GOTPCRELX_REX] = 42, /* R_X86_64_REX_GOTPCRELX */ + }, + [ISaarch64] = { + [REL_ABS64] = 257, /* R_AARCH64_ABS64 */ + [REL_ABS32] = 258, /* R_AARCH64_ABS32 */ + [REL_ABS32S] = 258, /* R_AARCH64_ABS32S */ + [REL_PCREL32] = 261, /* R_AARCH64_PREL2 */ + [REL_PLT32] = 314, /* R_AARCH64_PLT32 */ + [REL_CALL26] = 283, /* R_AARCH64_CALL26 */ + [REL_LD_PREL_LO19] = 273, /* R_AARCH64_LD_PREL_LO19 */ + [REL_ADR_PREL_LO21] = 274, /* R_AARCH64_ADR_PREL_LO21 */ + [REL_ADR_PREL_PG_HI21] = 276, /* R_AARCH64_ADR_PREL_PG_HI21_NC */ + [REL_ADD_ABS_LO12_NC] = 277, /* R_AARCH64_ADD_ABS_LO12_NC */ + + } +}; + +void +elfreloc(internstr sym, enum relockind kind, enum section section, uint off, vlong addend) +{ + switch (section) { + default: assert(0); + case Stext: ++ntextrel; break; + case Srodata: ++nrodatarel; break; + case Sdata: ++ndatarel; break; + } + assert(kind < NRELOCKIND); + vpush(&relocs, ((struct reloc) { section, relktab[target.arch][kind], off, addend, .symname = sym })); +} + +static void +elf64puthdr(struct wbuf *out, struct elf64hdr *hdr) +{ + if (!hostntarg_sameendian()) { + hdr->type = bswap16(hdr->type); + hdr->machine = bswap16(hdr->machine); + hdr->version = bswap32(hdr->version); + hdr->entry = bswap64(hdr->entry); + hdr->phoff = bswap64(hdr->phoff); + hdr->shoff = bswap64(hdr->shoff); + hdr->flags = bswap32(hdr->flags); + hdr->ehsize = bswap16(hdr->ehsize); + hdr->phentsize = bswap16(hdr->phentsize); + hdr->phnum = bswap16(hdr->phnum); + hdr->shentsize = bswap16(hdr->shentsize); + hdr->shnum = bswap16(hdr->shnum); + hdr->shstrndx = bswap16(hdr->shstrndx); + } + iowrite(out, hdr, sizeof *hdr); +} + +static void +elf32puthdr(struct wbuf *out, struct elf32hdr *hdr) +{ + if (!hostntarg_sameendian()) { + hdr->type = bswap16(hdr->type); + hdr->machine = bswap16(hdr->machine); + hdr->version = bswap32(hdr->version); + hdr->entry = bswap32(hdr->entry); + hdr->phoff = bswap32(hdr->phoff); + hdr->shoff = bswap32(hdr->shoff); + hdr->flags = bswap32(hdr->flags); + hdr->ehsize = bswap16(hdr->ehsize); + hdr->phentsize = bswap16(hdr->phentsize); + hdr->phnum = bswap16(hdr->phnum); + hdr->shentsize = bswap16(hdr->shentsize); + hdr->shnum = bswap16(hdr->shnum); + hdr->shstrndx = bswap16(hdr->shstrndx); + } + iowrite(out, hdr, sizeof *hdr); +} + +static void +elf64putshdr(struct wbuf *out, struct elf64shdr *shdr) +{ + if (!hostntarg_sameendian()) { + shdr->name = bswap32(shdr->name); + shdr->type = bswap32(shdr->type); + shdr->flags = bswap64(shdr->flags); + shdr->addr = bswap64(shdr->addr); + shdr->offset = bswap64(shdr->offset); + shdr->size = bswap64(shdr->size); + shdr->link = bswap32(shdr->link); + shdr->info = bswap32(shdr->info); + shdr->addralign = bswap64(shdr->addralign); + shdr->entsize = bswap64(shdr->entsize); + } + iowrite(out, shdr, sizeof *shdr); +} + +static void +elf32putshdr(struct wbuf *out, struct elf32shdr *shdr) +{ + if (!hostntarg_sameendian()) { + shdr->name = bswap32(shdr->name); + shdr->type = bswap32(shdr->type); + shdr->flags = bswap32(shdr->flags); + shdr->addr = bswap32(shdr->addr); + shdr->offset = bswap32(shdr->offset); + shdr->size = bswap32(shdr->size); + shdr->link = bswap32(shdr->link); + shdr->info = bswap32(shdr->info); + shdr->addralign = bswap32(shdr->addralign); + shdr->entsize = bswap32(shdr->entsize); + } + iowrite(out, shdr, sizeof *shdr); +} + +static void +elf64putsym(struct wbuf *out, struct elf64sym *sym) +{ + if (!hostntarg_sameendian()) { + sym->name = bswap32(sym->name); + sym->shndx = bswap16(sym->shndx); + sym->value = bswap64(sym->value); + sym->size = bswap64(sym->size); + } + iowrite(out, sym, sizeof *sym); +} + +static void +elf32putsym(struct wbuf *out, struct elf32sym *sym) +{ + if (!hostntarg_sameendian()) { + sym->name = bswap32(sym->name); + sym->value = bswap32(sym->value); + sym->size = bswap32(sym->size); + sym->shndx = bswap16(sym->shndx); + } + iowrite(out, sym, sizeof *sym); +} + +static void +putsym(struct wbuf *out, const struct sym *sym) +{ + if (targ_64bit) { + elf64putsym(out, &(struct elf64sym) { + sym->name, .info = ELF_S_INFO(sym->bind, sym->type), + .shndx = sym->shndx, .value = sym->value, .size = sym->size }); + } else { + elf32putsym(out, &(struct elf32sym) { + sym->name, .info = ELF_S_INFO(sym->bind, sym->type), + .shndx = sym->shndx, .value = sym->value, .size = sym->size }); + } +} + +static void +elf64putrel(struct wbuf *out, struct elf64rel *rel) +{ + if (!hostntarg_sameendian()) { + rel->offset = bswap64(rel->offset); + rel->info = bswap64(rel->info); + } + iowrite(out, rel, sizeof *rel); +} + +static void +elf32putrel(struct wbuf *out, struct elf32rel *rel) +{ + if (!hostntarg_sameendian()) { + rel->offset = bswap32(rel->offset); + rel->info = bswap32(rel->info); + } + iowrite(out, rel, sizeof *rel); +} + +static void +elf64putrela(struct wbuf *out, struct elf64rela *rel) +{ + if (!hostntarg_sameendian()) { + rel->offset = bswap64(rel->offset); + rel->info = bswap64(rel->info); + rel->addend = bswap64(rel->addend); + } + iowrite(out, rel, sizeof *rel); +} + +static void +elf32putrela(struct wbuf *out, struct elf32rela *rel) +{ + if (!hostntarg_sameendian()) { + rel->offset = bswap32(rel->offset); + rel->info = bswap32(rel->info); + rel->addend = bswap32(rel->addend); + } + iowrite(out, rel, sizeof *rel); +} + +static void +putreloc(struct wbuf *out, const struct reloc *rel, bool userela) +{ + if (userela) { + if (targ_64bit) { + elf64putrela(out, &(struct elf64rela) { + rel->off, ELF64_R_INFO(rel->symidx, rel->kind), rel->addend }); + } else { + elf32putrela(out, &(struct elf32rela) { + rel->off, ELF32_R_INFO(rel->symidx, rel->kind), rel->addend }); + } + } else { + if (targ_64bit) { + elf64putrel(out, &(struct elf64rel) { + rel->off, ELF64_R_INFO(rel->symidx, rel->kind) }); + } else { + elf32putrel(out, &(struct elf32rel) { + rel->off, ELF32_R_INFO(rel->symidx, rel->kind) }); + } + } +} + +/* ensure .symtab entries are ordered like this: + * (0. zero entry: NOTYPE LOCAL UND) + * (1. file: FILE LOCAL ABS "...") + * 2. locals + * 3. defined globals + * 4. undefined globals + */ +static int +symcmp(const void *aa, const void *bb) +{ + const ushort *a = aa, *b = bb; + const struct sym *l = &symtab.p[*a], *r = &symtab.p[*b]; + int tmp; + if ((tmp = l->bind - r->bind)) return tmp; /* locals prio */ + if ((tmp = r->shndx - l->shndx)) return tmp; /* section prio (real sections > SHN_UND) */ + return l->name - r->name; +} + +static void +wordalign(struct wbuf *out, int align) +{ + size_t off = out->len + lseek(out->fd, 0, SEEK_CUR); + while (off++ & (align - 1)) ioputc(out, 0); +} + +static const bool userelatab[] = { [ISx86_64] = 1, [ISaarch64] = 1 }; + +void +elffini(struct wbuf *out) +{ + enum { + shnam_text = 1, shnam_rodata = 7, shnam_data = 15, shnam_bss = 21, shnam_shstrtab = 26, + shnam_strtab = 36, shnam_symtab = 44, shnam_reltext = 52, shnam_relrodata = 63, shnam_reldata = 76 + }; + int align = targ_64bit ? 8 : 4; + bool userela = userelatab[target.arch]; + char shstrs[] = "\0.text\0.rodata\0.data\0.bss\0.shstrtab\0.strtab\0.symtab\0" + ".rela.text\0.rela.rodata\0.rela.data"; + if (!userela) { + /* .rela -> .rel */ + memcpy(shstrs + shnam_reltext + 4, ".text\0", 6); + memcpy(shstrs + shnam_relrodata + 4, ".rodata\0", 8); + memcpy(shstrs + shnam_reldata + 4, ".data\0", 6); + } + + symtab.p[1].name = str2idx(objout.infile); + /* create a mapping of original symbol index -> sorted symtab index */ + uint ndefsym = symtab.n; + ushort ssbuf[2<<10], + *sortedsyms = ndefsym*2 < countof(ssbuf) ? ssbuf : xmalloc(sizeof *sortedsyms * ndefsym*2), + *defsym2idx = sortedsyms + ndefsym; + for (int i = 0; i < ndefsym; ++i) sortedsyms[i] = i; + qsort(sortedsyms+2, ndefsym-2, sizeof *sortedsyms, symcmp); + for (int i = 0; i < ndefsym; ++i) defsym2idx[sortedsyms[i]] = i; + + /* fixup relocs */ + for (int i = 0; i < relocs.n; ++i) { + struct reloc *rel = &relocs.p[i]; + struct sym *sym = findsym(rel->symname); + if (sym) { + uint idx = sym - symtab.p; + rel->symidx = idx < ndefsym ? defsym2idx[idx] : idx; + } else { + assert(symtab.n < 1<<16); + vpush(&symtab, ((struct sym) { str2idx(&rel->symname->c), .bind = STB_GLOBAL, .type = STT_NOTYPE, .shndx = SHN_UND })); + pmap_set(&symht, rel->symname, symtab.n-1); + rel->symidx = symtab.n-1; + } + if (!userela) { + uchar *p; + switch (rel->section) { + default: assert(0); + case Sdata: p = O.data.p + rel->off; break; + case Srodata: p = O.rodata.p + rel->off; break; + case Stext: p = O.textbegin + rel->off; break; + } + if (targ_64bit) + wr64targ(p, rel->addend); + else + wr32targ(p, rel->addend); + } + } + size_t codesize = alignup(O.code - O.textbegin, align), + rodataoff = (targ_64bit ? sizeof hdr.h64 : sizeof hdr.h32) + codesize, + rodatasize = O.rodata.n, + dataoff = rodataoff + rodatasize, + datasize = O.data.n, + bsssize = O.nbss, + shstrsoff = dataoff + datasize, + shstrssize = sizeof(shstrs), + strsoff = shstrsoff + shstrssize, + strssize = strs.n, + symtaboff = alignup(strsoff + strssize, align), + symtabsize = symtab.n * (targ_64bit ? 24 : 16), + reltextoff = symtaboff + symtabsize, + relxsiz = userela ? (targ_64bit ? 24 : 12) : (targ_64bit ? 16 : 8), + reltextsize = ntextrel * relxsiz, + relrodataoff = reltextoff + reltextsize, + relrodatasize = nrodatarel * relxsiz, + reldataoff = relrodataoff + relrodatasize, + reldatasize = ndatarel * relxsiz; + int nlocal = 0; + +#define CHECKOFF(s, x) \ + efmt(s " expect OFF: %u ; ACTUAL %u\n", (uint)(x),(uint)(lseek(out->fd, 0, SEEK_CUR) + out->len)) + + if (targ_64bit) { + hdr.h64.shoff = alignup(reldataoff + reldatasize, align); + hdr.h64.shnum = 11; + hdr.h64.shstrndx = 5; + } else { + hdr.h32.shoff = alignup(reldataoff + reldatasize, align); + hdr.h32.shnum = 11; + hdr.h32.shstrndx = 5; + } + + /* elf header */ + if (targ_64bit) + elf64puthdr(out, &hdr.h64); + else + elf32puthdr(out, &hdr.h32); + + /* .text progbits */ + iowrite(out, O.textbegin, codesize); + + /* .rodata progbits */ + iowrite(out, O.rodata.p, O.rodata.n); + + /* .data progbits */ + iowrite(out, O.data.p, O.data.n); + + /* section names */ + iowrite(out, shstrs, sizeof shstrs); + + /* strings */ + iowrite(out, strs.p, strs.n); + + /* symtab */ + wordalign(out, align); + for (int i = 0; i < symtab.n; ++i) { + struct sym *sym = &symtab.p[i < ndefsym ? sortedsyms[i] : i]; + if (sym->bind == STB_LOCAL) ++nlocal; + putsym(out, sym); + } + if (sortedsyms != ssbuf) free(sortedsyms); + + /* rel.* */ + assert(relocs.n == ntextrel + nrodatarel + ndatarel); + for (enum section s = Stext; s <= Sbss; ++s) { + for (int i = 0; i < relocs.n; ++i) { + struct reloc *rel = &relocs.p[i]; + if (rel->section != s) continue; + putreloc(out, rel, userela); + } + } + + /** Section Headers **/ + wordalign(out, align); +#define putshdr(...) if (targ_64bit) elf64putshdr(out, &(struct elf64shdr) { __VA_ARGS__ }); \ + else elf32putshdr(out, &(struct elf32shdr) { __VA_ARGS__ }); + /* §0 null section */ + putshdr(0); + /* §1 .text */ + putshdr(.name = shnam_text, .type = SHT_PROGBITS, + .flags = SHF_ALLOC | SHF_EXECINSTR, + .offset = targ_64bit ? hdr.h64.ehsize : hdr.h32.ehsize, .size = codesize, + .addralign = align); + /* §2 .rodata */ + putshdr(.name = shnam_rodata, .type = SHT_PROGBITS, + .flags = SHF_ALLOC, + .offset = rodataoff, .size = rodatasize, + .addralign = O.rodataalign,); + /* §3 .data */ + putshdr(.name = shnam_data, .type = SHT_PROGBITS, + .flags = SHF_ALLOC | SHF_WRITE, + .offset = dataoff, .size = datasize, + .addralign = O.dataalign,); + /* §4 .bss */ + putshdr(.name = shnam_bss, .type = SHT_NOBITS, + .size = bsssize, + .flags = SHF_ALLOC | SHF_WRITE, + .addralign = O.bssalign,); + /* §5 .shstrtab */ + putshdr(.name = shnam_shstrtab, .type = SHT_STRTAB, + .offset = shstrsoff, .size = shstrssize, + .flags = SHF_STRINGS ); + /* §6 .strtab */ + putshdr(.name = shnam_strtab, .type = SHT_STRTAB, + .offset = strsoff, .size = strssize, + .flags = SHF_STRINGS ); + /* §7 .symtab */ + putshdr(.name = shnam_symtab, .type = SHT_SYMTAB, + .offset = symtaboff, .size = symtabsize, + .flags = SHF_STRINGS, + .link = 6, /* .strtab */ + .info = nlocal, + .entsize = targ_64bit ? 24 : 16 ); + /* §8 .rel.text */ + putshdr(.name = shnam_reltext, .type = SHT_RELA, + .offset = reltextoff, .size = reltextsize, + .link = 7, /* .symtab */ + .entsize = relxsiz, + .info = TEXT_SHNDX ); + /* §9 .rel.rodata */ + putshdr(.name = shnam_relrodata, .type = SHT_RELA, + .offset = relrodataoff, .size = relrodatasize, + .link = 7, /* .symtab */ + .entsize = relxsiz, + .info = RODATA_SHNDX ); + /* §10 .rel.data */ + putshdr(.name = shnam_reldata, .type = SHT_RELA, + .offset = reldataoff, .size = reldatasize, + .link = 7, /* .symtab */ + .entsize = relxsiz, + .info = DATA_SHNDX ); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/o_elf.h b/src/o_elf.h new file mode 100644 index 0000000..c96ae8b --- /dev/null +++ b/src/o_elf.h @@ -0,0 +1,206 @@ +#include "../common.h" + +#define ELFMAG "\177ELF" +enum { + ELFCLASS32 = 1, + ELFCLASS64 = 2, + + ELFDATA2LSB = 1, + ELFDATA2MSB = 2, + + ELFVERSION = 1, + + ELFOSABI_SYSV = 0, + ELFOSABI_ARM = 97, + ELFOSABI_STANDALONE = 255, + + ET_NONE = 0, + ET_REL, ET_EXEC, ET_DYN, ET_CORE, + + EM_NONE = 0, + EM_386 = 3, + EM_486 = 6, + EM_MIPS = 8, + EM_MIPS_RS4_BE = 0xA, + EM_ARM = 0x28, + EM_X86_64 = 0x3E, + EM_ARM64 = 0xB7, +}; + +#define ELF_HDRIDENT \ + union { \ + uchar ident[16]; \ + struct { \ + uchar i_mag[4], \ + i_class, \ + i_data, \ + i_version, \ + i_osabi, \ + i_abiversion, \ + i_pad[7]; \ + }; \ + } + +struct elf64hdr { + ELF_HDRIDENT; + ushort type, + machine; + uint version; + uvlong entry, + phoff, + shoff; + uint flags; + ushort ehsize, + phentsize, + phnum, + shentsize, + shnum, + shstrndx; +}; +static_assert(sizeof(struct elf64hdr) == 64); + +struct elf32hdr { + ELF_HDRIDENT; + ushort type, + machine; + uint version; + uint entry, + phoff, + shoff; + uint flags; + ushort ehsize, + phentsize, + phnum, + shentsize, + shnum, + shstrndx; +}; +static_assert(sizeof(struct elf32hdr) == 52); + +enum { + SHT_NULL = 0x0, + SHT_PROGBITS = 0x1, + SHT_SYMTAB = 0x2, + SHT_STRTAB = 0x3, + SHT_RELA = 0x4, + SHT_HASH = 0x5, + SHT_DYNAMIC = 0x6, + SHT_NOTE = 0x7, + SHT_NOBITS = 0x8, + SHT_REL = 0x9, + SHT_SHLIB = 0xA, + SHT_DYNSYM = 0xB, + SHT_INIT_ARRAY = 0xE, + SHT_FINI_ARRAY = 0xF, + SHT_PREINIT_ARRAY = 0x10, + SHT_GROUP = 0x12, + SHT_SYMTAB_SHNDX = 0x13, +}; + +enum { + SHF_WRITE = 0x1, + SHF_ALLOC = 0x2, + SHF_EXECINSTR = 0x4, + SHF_MERGE = 0x10, + SHF_STRINGS = 0x20, + SHF_INFO_LINK = 0x40, + SHF_LINK_ORDER = 0x80, + SHF_OS_NONCONFORMING = 0x100, + SHF_GROUP = 0x200, + SHF_TLS = 0x400, +}; + +struct elf64shdr { + uint name, + type; + uvlong flags, + addr, + offset, + size; + uint link, + info; + uvlong addralign, + entsize; +}; +static_assert(sizeof(struct elf64shdr) == 64); + +struct elf32shdr { + uint name, + type, + flags, + addr, + offset, + size, + link, + info, + addralign, + entsize; +}; +static_assert(sizeof(struct elf32shdr) == 40); + +enum { + STB_LOCAL, + STB_GLOBAL, + STB_WEAK +}; + +enum { + STT_NOTYPE, + STT_OBJECT, + STT_FUNC, + STT_SECTION, + STT_FILE, +}; + +enum { + SHN_UND = 0, + SHN_ABS = 0xFFF1, +}; + +#define ELF_S_INFO(b,t) ((b) << 4 | (t)) + +struct elf64sym { + uint name; + uchar info, + other; + ushort shndx; + uvlong value, + size; +}; +static_assert(sizeof(struct elf64sym) == 24); + +struct elf32sym { + uint name, + value, + size; + uchar info, + other; + ushort shndx; +}; +static_assert(sizeof(struct elf32sym) == 16); + +#define ELF64_R_INFO(s,t) ((uvlong) (s) << 32 | (uint)(t)) +struct elf64rel { + uvlong offset, info; +}; +static_assert(sizeof(struct elf64rel) == 16); + +#define ELF32_R_INFO(s,t) ((s) << 8 | (uchar)(t)) +struct elf32rel { + uint offset, info; +}; +static_assert(sizeof(struct elf32rel) == 8); + +struct elf64rela { + uvlong offset, info; + vlong addend; +}; +static_assert(sizeof(struct elf64rela) == 24); + +struct elf32rela { + uint offset, info; + int addend; +}; +static_assert(sizeof(struct elf32rela) == 12); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/obj.c b/src/obj.c new file mode 100644 index 0000000..12f0db7 --- /dev/null +++ b/src/obj.c @@ -0,0 +1,118 @@ +#include "obj.h" +#include "../ir/ir.h" +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + + +void elfinit(void); +enum section elfhassym(internstr , uint *value); +void elfaddsym(internstr , int info, enum section, uvlong value, uvlong size); +void elfreloc(internstr sym, enum relockind, enum section, uint off, vlong addend); +void elffini(struct wbuf *); + +struct objfile objout; + +enum { NTEXT = 4<<20 /* 4MiB */ }; + +void +objini(const char *infile, const char *outfile) +{ + assert(!objout.outfile); + objout.infile = infile; + objout.outfile = outfile; + objout.code = objout.textbegin = mapzeros(NTEXT); + objout.textend = objout.textbegin + NTEXT; + + switch (mctarg->objkind) { + case OBJELF: elfinit(); break; + } +} + +void +objdeffunc(internstr nam, bool globl, uint off, uint siz) +{ + switch (mctarg->objkind) { + case OBJELF: + elfaddsym(nam, /*STT_LOCAL/GLOBAL*/globl << 4 | /*STT_FUNC*/2, Stext, off, siz); + break; + } +} + +enum section +objhassym(internstr name, uint *off) +{ + return elfhassym(name, off); +} + +uint +objnewdat(internstr name, enum section sec, bool globl, uint siz, uint align) +{ + struct objfile *o = &objout; + uint off; + assert(siz && align && ispo2(align)); + switch (sec) { + default: assert(0); + case Stext: + assert(align <= targ_primsizes[TYPTR]); + assert(o->textend - siz > o->code); + while ((o->code - o->textbegin) & (align - 1)) ++o->code; + off = o->code - o->textbegin; + o->code += siz; + break; + case Srodata: + if (align > o->rodataalign) o->rodataalign = align; + while (o->rodata.n & (align - 1)) vpush(&o->rodata, 0); + off = o->rodata.n; + vresize(&o->rodata, o->rodata.n + siz); + memset(o->rodata.p+off, 0, siz); + break; + case Sdata: + if (align > o->dataalign) o->dataalign = align; + while (o->data.n & (align - 1)) vpush(&o->data, 0); + off = o->data.n; + vresize(&o->data, o->data.n + siz); + memset(o->data.p+off, 0, siz); + break; + case Sbss: + if (align > o->bssalign) o->bssalign = align; + off = alignup(o->nbss, align); + o->nbss = off + siz; + break; + } + + switch (mctarg->objkind) { + case OBJELF: + elfaddsym(name, /*STT_LOCAL/GLOBAL*/globl<<4 | /*STT_OBJECT*/1, sec, off, siz); + break; + } + return off; +} + +void +objreloc(internstr sym, enum relockind reloc, enum section section, uint off, vlong addend) +{ + switch (mctarg->objkind) { + case OBJELF: + elfreloc(sym, reloc, section, off, addend); + break; + } +} + +void +objfini(void) +{ + static char buf[1<<12]; + struct wbuf out = FDBUF(buf, sizeof buf, open(objout.outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666)); + if (out.fd < 0) fatal(NULL, "could not open %'s for writing: %s", objout.outfile, strerror(errno)); + + switch (mctarg->objkind) { + case OBJELF: elffini(&out); break; + } + + munmap(objout.textbegin, NTEXT); + ioflush(&out); + close(out.fd); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/obj.h b/src/obj.h new file mode 100644 index 0000000..1982033 --- /dev/null +++ b/src/obj.h @@ -0,0 +1,36 @@ +#include "../common.h" + +extern struct objfile { + const char *infile, *outfile; + uchar *textbegin, *textend; + uchar *code; + uchar dataalign, rodataalign, bssalign; + uint nbss; + vec_of(uchar) data, rodata; +} objout; + +enum relockind { + REL_ABS64, + REL_ABS32, + REL_ABS32S, + REL_PCREL32, + REL_PLT32, + REL_GOTPCRELX, + REL_GOTPCRELX_REX, + REL_CALL26, + REL_ADR_PREL_LO21, + REL_ADR_PREL_PG_HI21, + REL_ADD_ABS_LO12_NC, + REL_LD_PREL_LO19, + NRELOCKIND, +}; +enum section { Snone, Stext, Srodata, Sdata, Sbss }; + +void objini(const char *infile, const char *outfile); +void objdeffunc(internstr nam, bool globl, uint off, uint siz); +enum section objhassym(internstr name, uint *off); +uint objnewdat(internstr name, enum section, bool globl, uint siz, uint align); +void objreloc(internstr sym, enum relockind, enum section, uint off, vlong addend); +void objfini(void); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_aarch64.h b/src/t_aarch64.h new file mode 100644 index 0000000..828909e --- /dev/null +++ b/src/t_aarch64.h @@ -0,0 +1,16 @@ +#include "../ir/ir.h" + +enum reg { + R0 = 0, +#define R(n) (R0+n) + FP = R(29), LR = R(30), SP = R(31), + V0, +#define V(n) (V0+n) +}; + +bool aarch64_logimm(uint *enc, enum irclass, uvlong x); +void aarch64_isel(struct function *); +void aarch64_emit(struct function *); + +/* vim:set ts=3 sw=3 expandtab: */ + diff --git a/src/t_aarch64_aapcs.c b/src/t_aarch64_aapcs.c new file mode 100644 index 0000000..fc08da1 --- /dev/null +++ b/src/t_aarch64_aapcs.c @@ -0,0 +1,77 @@ +#include "all.h" + +static int +abiarg(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype typ) +{ + enum { NINT = 8, NFLT = 8 }; + if (!typ.isagg) { + if (kisflt(cls[0] = typ.cls) && *nf < 8) { + r[0] = V(0) + (*nf)++; + } else if (kisint(cls[0]) && *ni < NINT) { + r[0] = R0 + (*ni)++; + } else { + r[0] = *ns; + *ns += 8; + return 0; /* MEMORY */ + } + return 1; + } else assert(!"nyi"); +} + +static int +abiret(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype typ) +{ + if (!typ.isagg) { + r[0] = kisflt(cls[0] = typ.cls) ? V(0) : R0; + return 1; + } + int nf = 0, ns = 0; + int ret = abiarg(r, cls, r2off, ni, &nf, &ns, typ); + if (ret) return ret; + /* caller-allocated result address in x8 */ + assert(*ni == 0); + r[0] = -1; + r[1] = R(8); + return 0; +} + +static void +vastart(struct function *fn, struct block *blk, int *curi) +{ + assert(!"nyi"); +} + +static void +vaarg(struct function *fn, struct block *blk, int *curi) +{ + assert(!"nyi"); +} + +static const char aarch64_rnames[][6] = { + "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9","R10","R11","R12","R13","R14","R15", + "R16","R17","R18","R19","R20","R21","R22","R23","R24","R25","R26","R27","R28", "FP", "LR", "SP", + "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9","V10","V11","V12","V13","V14","V15", + "V16","V17","V18","V19","V20","V21","V22","V23","V24","V25","V26","V27","V28","V29","V30","V31", +}; + +const struct mctarg t_aarch64_aapcs = { + .gpr0 = R0, .ngpr = 31, + .bpr = FP, + .gprscratch = R(16), .fprscratch = V(31), + .fpr0 = V0, .nfpr = 32, + .rcallee = BIT(R(19)) | BIT(R(20)) | BIT(R(21)) | BIT(R(22)) | BIT(R(23)) + | BIT(R(24)) | BIT(R(25)) | BIT(R(26)) | BIT(R(27)) | BIT(R(28)) + | BIT( V(8)) | BIT( V(9)) | BIT(V(10)) | BIT(V(11)) | BIT(V(12)) + | BIT(V(13)) | BIT(V(14)) | BIT(V(15)), + .rglob = BIT(FP) | BIT(LR) | BIT(SP), + .rnames = aarch64_rnames, + .objkind = OBJELF, + .abiret = abiret, + .abiarg = abiarg, + .vastart = vastart, + .vaarg = vaarg, + .isel = aarch64_isel, + .emit = aarch64_emit, +}; + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_aarch64_emit.c b/src/t_aarch64_emit.c new file mode 100644 index 0000000..9fdcd83 --- /dev/null +++ b/src/t_aarch64_emit.c @@ -0,0 +1,1023 @@ +#include "all.h" +#include "../obj/obj.h" +#include "../endian.h" + +/* References: + * ARM ARM https://developer.arm.com/documentation/ddi0628/aa/?lang=en + * AAELF ABI https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst + */ + +enum operkind { ONONE, OREGZR, OREG, OIMM, OMEM, OSYM }; +enum shiftkind { SLSL, SLSR, SASR, SROR }; +enum addrmode { AIMMIDX, AREGIDX, APREIDX, APOSTIDX }; +enum addrregext { XUXTW = 2, XLSL = 3, XSXTW = 6, XSXTX = 7 }; +struct oper { + uchar t; + union { + struct { /* OREG (opt. shifted) */ + uchar reg; + uchar shft : 2, /* enum shiftkind */ + shamt : 6; + }; + struct { /* OMEM */ + uchar mode : 3; /* enum addrmode */ + uchar base : 5; /* reg */ + union { + struct { + uchar index : 5; /* reg */ + uchar ext : 3; /* enum addrregext */ + uchar shamt; + }; + short disp; + }; + } m; + vlong imm; uvlong uimm; /* OIMM */ + struct { /* OSYM */ + ushort con; + int cdisp; + }; + }; +}; + +#define REGZR ((struct oper){OREGZR, .reg=31}) +#define mkoper(t, ...) ((struct oper){(t), __VA_ARGS__}) +#define reg2oper(r) (assert((uint)(r) <= V(31)), mkoper(OREG, .reg = (r))) + +static struct oper +mkmemoper(uint msiz, union ref r) +{ + if (r.t == RTMP) { + assert(in_range(instrtab[r.i].reg-1, R0, SP)); + return mkoper(OMEM, .m = {AIMMIDX, .base = instrtab[r.i].reg-1}); + } else if (r.t == RREG) { + return mkoper(OMEM, .m = {AIMMIDX, .base = r.i}); + } else if (isaddrcon(r,1)) { + return mkoper(OSYM, .con = r.i,); + } else if (r.t == RADDR) { + const struct addr *addr = &addrtab.p[r.i]; + assert(addr->shift <= 3 && (!addr->disp || !addr->index.bits)); + if (isaddrcon(addr->base,0)) { + assert(!addr->index.bits); + return mkoper(OSYM, .con = addr->base.i, .cdisp = addr->disp); + } + assert(addr->base.t == RREG); + if (!addr->index.bits) { + return mkoper(OMEM, .m = {.mode = AIMMIDX, .base = addr->base.i, .disp = addr->disp}); + } else { + assert(addr->index.t == RREG); + assert(addr->shift == 0 || 1<<addr->shift == msiz); + return mkoper(OMEM, .m = { + .mode = AREGIDX, + .base = addr->base.i, + .index = addr->index.i, + .ext = XLSL, + .shamt = !!addr->shift, + }); + } + } + assert(!"nyi"); +} + +static struct oper +ref2oper(union ref r) +{ + switch (r.t) { + case RTMP: return instrtab[r.i].reg ? mkoper(ONONE,) : reg2oper(instrtab[r.i].reg-1); + case RREG: return reg2oper(r.i); + case RICON: return mkoper(OIMM, .imm = r.i); + case RXCON: + if (kisint(contab.p[r.i].cls)) + return mkoper(OIMM, .imm = contab.p[r.i].i); + else if (kisflt(contab.p[r.i].cls)) { + assert(contab.p[r.i].f == 0.0); + return mkoper(OIMM, .imm = 0); + } else if (!contab.p[r.i].cls) { + return mkoper(OSYM, .con = r.i); + } + assert(0); + case RADDR: return mkmemoper(0, r); + default: assert(0); + } +} + +enum operpat { + PNONE, + PGPRZ, /* R0-R30,ZR */ + PGPRSP, /* R0-R30,SP */ + PSP, /* SP */ + PGPRZSHFT, /* R0-30,ZR SFHT #n */ + PFPR, /* V0 - V31 */ + PZERO, /* zero immediate */ + PU6, /* 6-bit uimm */ + PU12SL12, /* 12 bit uimm, optionally left shifted by 12 */ + PU16SL16, /* 16 bit uimm, left shift by 0/16/32/48 */ + PLOGIMM, /* immediate for logical instrs */ + PMEMAIMM, /* addr 12bit immediate byte offset */ + PMEMAIMMH, /* addr 12bit immediate halfword offset (multiple of 2) */ + PMEMAIMMW, /* addr 12bit immediate word offset (multiple of 4) */ + PMEMAIMMX, /* addr 12bit immediate doubleword offset (multiple of 8) */ + PMEMPREPOST, /* addr signed 9bit immediate byte offset */ + PMEMAREG, /* addr reg offset, optionally left shifted */ + PSYM, /* symbol */ +}; +enum operenc { + EN_ADDSUBEXT3R, /* add/sub-ext-reg */ + EN_ADDSUBSHFT3R, /* add/sub-shift-reg */ + EN_LOGSHFT3R, /* logical/shifted-reg */ + EN_ARITH2R, /* data-processing/1src */ + EN_ARITH3R, /* data-processing/2src */ + EN_ADDSUBIMM, /* add/subtract-imm */ + EN_LOGIMM, /* logical-imm */ + EN_MOVEIMM, /* move/wide-imm */ + EN_MEMAIMM, /* load/store/unsigned-imm */ + EN_MEMAIMMH, /* load/store/unsigned-imm (halfword) */ + EN_MEMAIMMW, /* load/store/unsigned-imm (word) */ + EN_MEMAIMMX, /* load/store/unsigned-imm (doubleword) */ + EN_MEMAPREPOST, /* load/store/pre/postidx-imm */ + EN_MEMAREG, /* load/store/reg-offset */ + EN_MEMPPREPOST, /* load/store-pair/pre/postidx-imm */ + EN_ADRSYMLO21, /* for ADR <sym> */ + EN_ADRSYMPGHI21, /* for ADRP <sym:pghi21> */ + EN_ADDSYMLO12, /* for ADD x,x, <sym:lo12> */ + EN_LDSYMLO19, /* for LDR (literal) */ + EN_FP2R, /* float 1src */ + EN_FP1GPR1, /* fpr + gpr */ + EN_FP3R, /* float 2src */ + EN_FPIMM, /* float-imm */ + EN_FPCMPZ, /* float cmp with zero */ + EN_FPCMP, /* float cmp-imm */ +}; +struct desc { + uchar psiz; /* subset of {4,8} */ + uchar pt[3]; /* bitsets of enum operpat, up to 3 operands */ + uint opc; + uchar operenc; /* enum operenc */ +}; + +/* match operand against pattern */ +static inline bool +opermatch(enum operpat pat, enum irclass k, struct oper o) +{ + switch (pat) { + case PNONE: return !o.t; + case PGPRZ: + return o.t == OREGZR || (o.t == OREG && in_range(o.reg, R0, R(30)) && !o.shamt); + case PGPRSP: + return o.t == OREG && in_range(o.reg, R0, R(31)) && !o.shamt; + case PGPRZSHFT: + return o.t == OREGZR || (o.t == OREG && in_range(o.reg, R0, R(30))); + case PSP: return o.t == OREG && o.reg == SP; + case PFPR: return o.t == OREG && in_range(o.reg, V0, V(31)); + case PZERO: return o.t == OIMM && o.imm == 0; + case PU6: return o.t == OIMM && (uint)o.imm < 63; + case PSYM: return o.t == OSYM; + case PU12SL12: + return o.t == OIMM && ((o.imm &~ 0xFFF) == 0 || (o.imm &~ 0xFFF000) == 0); + case PU16SL16: + return o.t == OIMM + && ((o.imm &~ 0xFFFF) == 0 || (o.imm &~ 0xFFFF0000) == 0 + || (o.imm &~ (0xFFFFull<<32)) == 0 || (o.imm &~ (0xFFFFull<<48)) == 0); + case PLOGIMM: return o.t == OIMM && aarch64_logimm(NULL, k, o.imm); + case PMEMAIMM: + return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<12); + case PMEMAIMMH: + return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<13) && !(o.m.disp % 2); + case PMEMAIMMW: + return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<14) && !(o.m.disp % 4); + case PMEMAIMMX: + return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<15) && !(o.m.disp % 8); + case PMEMAREG: + return o.t == OMEM && o.m.mode == AREGIDX; + case PMEMPREPOST: + return o.t == OMEM && (o.m.mode == APREIDX || o.m.mode == APOSTIDX + || (o.m.mode == AIMMIDX && o.m.disp >= -256 && o.m.disp < 256)); + } + assert(0); +} + +/* code output helpers */ +#define W32(w) (wr32targ(*pcode, (w)), *pcode += 4) + +static uchar *fnstart; +static internstr curfnsym; +static bool usefp; +static int rbpoff; + +/* Given an instruction description table, find the first entry that matches + * the operands and encode it. */ +static void +encode(uchar **pcode, const struct desc *tab, int ntab, enum irclass k, struct oper o[3]) +{ + const struct desc *en = NULL; + for (int i = 0; i < ntab; ++i) { + if (!(tab[i].psiz & cls2siz[k])) continue; + for (int j = 0; j < 3; ++j) + if (!opermatch(tab[i].pt[j], k, o[j])) + goto Skip; + en = &tab[i]; + break; + Skip:; + } + assert(en && "no match for instr"); + + uint sf = cls2siz[k] >> 3; + uint ins = en->opc, sh, nimmrs; + switch (en->operenc) { + default: assert(!"nyi enc"); + case EN_ADDSUBSHFT3R: case EN_LOGSHFT3R: + ins |= sf<<31 | o[2].shft<<22 | o[2].reg<<16 | o[2].shamt<<10 | o[1].reg<<5 | o[0].reg; + break; + case EN_ARITH3R: + ins |= sf<<31 | o[2].reg<<16 | o[1].reg<<5 | o[0].reg; + break; + case EN_ADDSUBIMM: + sh = o[2].imm > 0xFFF; + ins |= sf<<31 | sh<<22 | (o[2].uimm >> 12*sh)<<10 | o[1].reg<<5 | o[0].reg; + break; + case EN_LOGIMM: + assert(aarch64_logimm(&nimmrs, k, o[2].uimm)); + ins |= sf<<31 | nimmrs<<10 | o[1].reg<<5 | o[0].reg; + break; + case EN_MOVEIMM: + sh = o[1].imm ? lowestsetbit(o[1].imm) / 16 : 0; + ins |= sf<<31 | sh<<21 | (o[1].uimm >> 16*sh)<<5 | o[0].reg; + break; + case EN_MEMAIMM: AImm: + ins |= o[1].m.disp<<10 | o[1].m.base<<5 | (o[0].reg&31); + break; + case EN_MEMAIMMH: o[1].m.disp >>= 1; goto AImm; + case EN_MEMAIMMW: o[1].m.disp >>= 2; goto AImm; + case EN_MEMAIMMX: o[1].m.disp >>= 3; goto AImm; + case EN_MEMAPREPOST: + ins |= (o[1].m.disp&0x1FF)<<12 | o[1].m.base<<5 | (o[0].reg&31); + if (o[1].m.mode == APREIDX) ins |= 3<<10; + else if (o[1].m.mode == APOSTIDX) ins |= 1<<10; + break; + case EN_MEMAREG: + assert(o[1].m.shamt <= 1); + ins |= o[1].m.index<<16 | o[1].m.ext<<13 | o[1].m.shamt<<12 | o[1].m.base<<5 | (o[0].reg&31); + break; + case EN_MEMPPREPOST: + assert(o[2].m.disp % 8 == 0); + ins |= (o[2].m.disp/8&0x7F)<<15 | (o[1].reg&31)<<10 | o[2].m.base<<5 | (o[0].reg&31); + if (o[2].m.mode == APREIDX) ins |= 3<<23; + else if (o[2].m.mode == APOSTIDX) ins |= 1<<23; + else ins |= 2<<23; + break; + case EN_ADRSYMLO21: + ins |= o[0].reg; + objreloc(xcon2sym(o[1].con), REL_ADR_PREL_LO21, Stext, *pcode - objout.textbegin, o[1].cdisp); + break; + case EN_ADRSYMPGHI21: + ins |= o[0].reg; + objreloc(xcon2sym(o[1].con), REL_ADR_PREL_PG_HI21, Stext, *pcode - objout.textbegin, o[1].cdisp); + break; + case EN_ADDSYMLO12: + ins |= sf<<31 | o[1].reg<<5 | o[0].reg; + objreloc(xcon2sym(o[2].con), REL_ADD_ABS_LO12_NC, Stext, *pcode - objout.textbegin, o[1].cdisp); + break; + case EN_LDSYMLO19: + ins |= o[0].reg; + objreloc(xcon2sym(o[1].con), REL_LD_PREL_LO19, Stext, *pcode - objout.textbegin, o[1].cdisp); + break; + case EN_FP2R: + ins |= sf<<22 | (o[1].reg&31)<<5 | (o[0].reg&31); + break; + case EN_FP1GPR1: + ins |= (o[1].reg&31)<<5 | (o[0].reg&31); + break; + case EN_FP3R: + ins |= sf<<22 | (o[2].reg&31)<<16 | (o[1].reg&31)<<5 | (o[0].reg&31); + break; + case EN_FPCMPZ: + ins |= sf<<22 | (o[0].reg&31)<<5; + break; + case EN_FPCMP: + ins |= sf<<22 | (o[1].reg&31)<<16 | (o[0].reg&31)<<5; + break; + } + W32(ins); +} +#define DEFINSTR1(X, ...) \ + static void \ + X(uchar **pcode, enum irclass k, struct oper a) \ + { \ + static const struct desc tab[] = { __VA_ARGS__ }; \ + encode(pcode, tab, countof(tab), k, ((struct oper [3]){a})); \ + } + +#define DEFINSTR2(X, ...) \ + static void \ + X(uchar **pcode, enum irclass k, struct oper op1, struct oper op2) \ + { \ + static const struct desc tab[] = { __VA_ARGS__ }; \ + encode(pcode, tab, countof(tab), k, ((struct oper [3]){op1,op2})); \ + } +#define DEFINSTR3(X, ...) \ + static void \ + X(uchar **pcode, enum irclass k, struct oper op1, struct oper op2, struct oper op3) \ + { \ + static const struct desc tab[] = { __VA_ARGS__ }; \ + encode(pcode, tab, countof(tab), k, ((struct oper [3]){op1,op2,op3})); \ + } + +DEFINSTR2(Xadrp, + {8, {PGPRZ, PSYM}, 0x90000000, EN_ADRSYMPGHI21} /* ADR (sym pg hi21) */ +) +DEFINSTR2(Xadr, + {8, {PGPRZ, PSYM}, 0x10000000, EN_ADRSYMLO21} /* ADR (sym pg hi21) */ +) + +DEFINSTR3(Xadd, + {4|8, {PGPRSP, PGPRSP, PU12SL12}, 0x11000000, EN_ADDSUBIMM}, /* ADD (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x0B000000, EN_ADDSUBSHFT3R}, /* ADD (shifted register) */ + { 8, {PGPRZ, PGPRZ, PSYM}, 0x11000000, EN_ADDSYMLO12}, /* ADD (sym lo12) */ +) +DEFINSTR3(Xsub, + {4|8, {PGPRSP, PGPRSP, PU12SL12}, 0x51000000, EN_ADDSUBIMM}, /* SUB (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x4B000000, EN_ADDSUBSHFT3R}, /* SUB (shifted register) */ +) +DEFINSTR3(Xsubs, + {4|8, {PGPRZ, PGPRSP, PU12SL12}, 0x71000000, EN_ADDSUBIMM}, /* SUBS (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x6B000000, EN_ADDSUBSHFT3R}, /* SUBS (shifted register) */ +) + +static void +Xmadd(uchar **pcode, enum irclass k, struct oper d, struct oper n, struct oper m, struct oper a) +{ + assert(opermatch(PGPRZ, k, d) && opermatch(PGPRZ, k, n) + && opermatch(PGPRZ, k, a) && opermatch(PGPRZ, k, m)); + uint sf = k > KI32; + W32(0x1B000000 | sf<<31 | m.reg<<16 | a.reg<<10 | n.reg<<5 | d.reg); +} + +DEFINSTR3(Xsdiv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC00C00, EN_ARITH3R}) +DEFINSTR3(Xudiv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC00800, EN_ARITH3R}) + +DEFINSTR3(Xand, + {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x12000000, EN_LOGIMM}, /* AND (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x0A000000, EN_LOGSHFT3R}, /* AND (shifted register) */ +) +DEFINSTR3(Xorr, + {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x32000000, EN_LOGIMM}, /* ORR (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x2A000000, EN_LOGSHFT3R}, /* ORR (shifted register) */ +) +DEFINSTR3(Xorn, {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x2A200000, EN_LOGSHFT3R}) +DEFINSTR3(Xeor, + {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x52000000, EN_LOGIMM}, /* EOR (immediate) */ + {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x4A000000, EN_LOGSHFT3R}, /* EOR (shifted register) */ +) +DEFINSTR3(Xlslv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02000, EN_ARITH3R}) +DEFINSTR3(Xlsrv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02400, EN_ARITH3R}) +DEFINSTR3(Xasrv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02800, EN_ARITH3R}) +static void +Xubfm(uchar **pcode, enum irclass k, struct oper rd, struct oper rn, uint immr, uint imms) +{ + uint x = k != KI32; + uint nbit = x ? 64 : 32; + assert(opermatch(PGPRZ, k, rd) && opermatch(PGPRZ, k, rn) && immr < nbit && imms < nbit); + W32(x<<31 | 0x53000000 | x<<22 | immr<<16 | imms<<10 | rn.reg<<5 | rd.reg); +} +static void +Xsbfm(uchar **pcode, enum irclass k, struct oper rd, struct oper rn, uint immr, uint imms) +{ + uint x = k != KI32; + uint nbit = x ? 64 : 32; + assert(opermatch(PGPRZ, k, rd) && opermatch(PGPRZ, k, rn) && immr < nbit && imms < nbit); + W32(x<<31 | 0x13000000 | x<<22 | immr<<16 | imms<<10 | rn.reg<<5 | rd.reg); +} + +DEFINSTR2(Xmovz, {4|8, {PGPRZ, PU16SL16}, 0x52800000, EN_MOVEIMM}, /* MOVZ */) +DEFINSTR2(Xmovn, {4|8, {PGPRZ, PU16SL16}, 0x12800000, EN_MOVEIMM}, /* MOVN */) +DEFINSTR2(Xmovk, {4|8, {PGPRZ, PU16SL16}, 0x72800000, EN_MOVEIMM}, /* MOVK */) +DEFINSTR2(Xldr, + {4, {PGPRZ, PMEMAIMMW}, 0xB9400000, EN_MEMAIMMW}, /* LDR (immediate) */ + {8, {PGPRZ, PMEMAIMMX}, 0xF9400000, EN_MEMAIMMX}, + {4, {PGPRZ, PMEMAREG}, 0xB8600800, EN_MEMAREG}, /* LDR (register) */ + {8, {PGPRZ, PMEMAREG}, 0xF8600800, EN_MEMAREG}, + {4, {PGPRZ, PSYM}, 0x18000000, EN_LDSYMLO19}, /* LDR (literal) */ + {8, {PGPRZ, PSYM}, 0x58000000, EN_LDSYMLO19}, + {4, {PGPRZ, PMEMPREPOST}, 0xB8400000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */ + {8, {PGPRZ, PMEMPREPOST}, 0xF8400000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xfldr, + {4, {PFPR, PMEMAIMMW}, 0xBD400000, EN_MEMAIMMW}, /* LDR (immediate) */ + {8, {PFPR, PMEMAIMMX}, 0xFD400000, EN_MEMAIMMX}, + {4, {PFPR, PMEMAREG}, 0xBC600800, EN_MEMAREG}, /* LDR (register) */ + {8, {PFPR, PMEMAREG}, 0xFC600800, EN_MEMAREG}, + {4, {PFPR, PMEMPREPOST}, 0xBC400000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */ + {8, {PFPR, PMEMPREPOST}, 0xFC400000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xldrsw, + {8, {PGPRZ, PMEMAIMMW}, 0xB9800000, EN_MEMAIMMW}, /* LDRSW (immediate) */ +// {8, {PGPRZ, PMEMAREG}, 0xB8A00800, EN_MEMAREG}, /* LDRSW (register) */ + {8, {PGPRZ, PMEMPREPOST}, 0xB8800000, EN_MEMAPREPOST}, /* LDRSW (immediate, (pre/postinc)) */ +) +DEFINSTR2(Xldrh, + {4|8, {PGPRZ, PMEMAIMMH}, 0x79400000, EN_MEMAIMMH}, /* LDRH (immediate) */ + {4|8, {PGPRZ, PMEMAREG}, 0x78600800, EN_MEMAREG}, /* LDRH (register) */ + {4|8, {PGPRZ, PMEMPREPOST}, 0x78400000, EN_MEMAPREPOST}, /* LDRH (immediate, (pre/postinc)) */ +) +DEFINSTR2(Xldrsh, + {4, {PGPRZ, PMEMAIMMH}, 0x79C00000, EN_MEMAIMMH}, /* LDRSH (immediate) */ + {8, {PGPRZ, PMEMAIMMH}, 0x79800000, EN_MEMAIMMH}, + {4, {PGPRZ, PMEMAREG}, 0x78E00800, EN_MEMAREG}, /* LDRSH (register) */ + {8, {PGPRZ, PMEMAREG}, 0x78A00800, EN_MEMAREG}, + {4, {PGPRZ, PMEMPREPOST}, 0x78C00000, EN_MEMAPREPOST}, /* LDRSH (immediate, (pre/postinc)) */ + {8, {PGPRZ, PMEMPREPOST}, 0x78800000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xldrb, + {4|8, {PGPRZ, PMEMAIMM}, 0x39400000, EN_MEMAIMM}, /* LDRB (immediate) */ + {4|8, {PGPRZ, PMEMAREG}, 0x38600800, EN_MEMAREG}, /* LDRB (register) */ + {4|8, {PGPRZ, PMEMPREPOST}, 0x38400000, EN_MEMAPREPOST}, /* LDRB (immediate, (pre/postinc)) */ +) +DEFINSTR2(Xldrsb, + {4, {PGPRZ, PMEMAIMM}, 0x39C00000, EN_MEMAIMM}, /* LDRSB (immediate) */ + {8, {PGPRZ, PMEMAIMM}, 0x39800000, EN_MEMAIMM}, + {4, {PGPRZ, PMEMAREG}, 0x38E00800, EN_MEMAREG}, /* LDRSB (register) */ + {8, {PGPRZ, PMEMAREG}, 0x38A00800, EN_MEMAREG}, + {4, {PGPRZ, PMEMPREPOST}, 0x38C00000, EN_MEMAPREPOST}, /* LDRSB (immediate, (pre/postinc)) */ + {8, {PGPRZ, PMEMPREPOST}, 0x38800000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xstr, + {4, {PGPRZ, PMEMAIMMW}, 0xB9000000, EN_MEMAIMMW}, /* STR (immediate) */ + {8, {PGPRZ, PMEMAIMMX}, 0xF9000000, EN_MEMAIMMX}, + {4, {PGPRZ, PMEMAREG}, 0xB8200800, EN_MEMAREG}, /* STR (register) */ + {8, {PGPRZ, PMEMAREG}, 0xF8200800, EN_MEMAREG}, + {4, {PGPRZ, PMEMPREPOST}, 0xB8000000, EN_MEMAPREPOST}, /* STR (immediate, (pre/postinc)) */ + {8, {PGPRZ, PMEMPREPOST}, 0xF8000000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xfstr, + {4, {PFPR, PMEMAIMMW}, 0xBD000000, EN_MEMAIMMW}, /* LDR (immediate) */ + {8, {PFPR, PMEMAIMMX}, 0xFD000000, EN_MEMAIMMX}, + {4, {PFPR, PMEMAREG}, 0xBC200800, EN_MEMAREG}, /* LDR (register) */ + {8, {PFPR, PMEMAREG}, 0xFC200800, EN_MEMAREG}, + {4, {PFPR, PMEMPREPOST}, 0xBC000000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */ + {8, {PFPR, PMEMPREPOST}, 0xFC000000, EN_MEMAPREPOST}, +) +DEFINSTR2(Xstrh, + {4|8, {PGPRZ, PMEMAIMMH}, 0x79000000, EN_MEMAIMMH}, /* STRH (immediate) */ + {4|8, {PGPRZ, PMEMAREG}, 0x78200800, EN_MEMAREG}, /* STRH (register) */ + {4|8, {PGPRZ, PMEMPREPOST}, 0x78000000, EN_MEMAPREPOST}, /* STRH (immediate, (pre/postinc)) */ +) +DEFINSTR2(Xstrb, + {4|8, {PGPRZ, PMEMAIMM}, 0x39000000, EN_MEMAIMM}, /* STRB (immediate) */ + {4|8, {PGPRZ, PMEMAREG}, 0x38200800, EN_MEMAREG}, /* STRB (register) */ + {4|8, {PGPRZ, PMEMPREPOST}, 0x38000000, EN_MEMAPREPOST}, /* STRB (immediate, (pre/postinc)) */ +) +DEFINSTR3(Xldp, + {8, {PGPRZ, PGPRZ, PMEMPREPOST}, 0xA8400000, EN_MEMPPREPOST} /* LDP (immediate, (pre/postinc)) */ +) +DEFINSTR3(Xstp, + {8, {PGPRZ, PGPRZ, PMEMPREPOST}, 0xA8000000, EN_MEMPPREPOST} /* STP (immediate, (pre/postinc)) */ +) +DEFINSTR3(Xfldp, + {8, {PFPR, PFPR, PMEMPREPOST}, 0x6CC00000, EN_MEMPPREPOST} /* LDP (immediate, (pre/postinc)) */ +) +DEFINSTR3(Xfstp, + {8, {PFPR, PFPR, PMEMPREPOST}, 0x6C800000, EN_MEMPPREPOST} /* STP (immediate, (pre/postinc)) */ +) +static void +Xcall(uchar **pcode, struct oper dst) +{ + if (dst.t == OSYM) { + objreloc(xcon2sym(dst.con), REL_CALL26, Stext, *pcode - objout.textbegin, 0); + W32(0x94000000); /* BL <rel26> */ + } else { + assert(opermatch(PGPRZ, KPTR, dst)); + W32(0xD63F0000 | dst.reg<<5); /* BLR Xn */ + } +} +DEFINSTR2(Xfmov, + {4|8, {PFPR, PFPR}, 0x1E204000, EN_FP2R}, + {4, {PFPR, PGPRZ}, 0x1E270000, EN_FP1GPR1}, + { 8, {PFPR, PGPRZ}, 0x9E670000, EN_FP1GPR1}, + {4, {PGPRZ, PFPR}, 0x1E260000, EN_FP1GPR1}, + { 8, {PGPRZ, PFPR}, 0x9E660000, EN_FP1GPR1}, +) +DEFINSTR2(Xfneg, {4|8, {PFPR, PFPR}, 0x1E214000, EN_FP2R}) +DEFINSTR2(Xscvtfw, {4|8, {PFPR, PGPRZ}, 0x1E220000, EN_FP2R}) +DEFINSTR2(Xscvtfx, {4|8, {PFPR, PGPRZ}, 0x9E220000, EN_FP2R}) +DEFINSTR2(Xfcvtzsw, {4|8, {PGPRZ, PFPR}, 0x1E380000, EN_FP2R}) +DEFINSTR2(Xfcvtzsx, {4|8, {PGPRZ, PFPR}, 0x9E380000, EN_FP2R}) +DEFINSTR2(Xucvtfw, {4|8, {PFPR, PGPRZ}, 0x1E230000, EN_FP2R}) +DEFINSTR2(Xucvtfx, {4|8, {PFPR, PGPRZ}, 0x9E230000, EN_FP2R}) +DEFINSTR2(Xfcvtzuw, {4|8, {PGPRZ, PFPR}, 0x1E390000, EN_FP2R}) +DEFINSTR2(Xfcvtzux, {4|8, {PGPRZ, PFPR}, 0x9E390000, EN_FP2R}) +DEFINSTR2(Xfcvtds, {4, {PFPR, PFPR}, 0x1E624000, EN_FP2R}) +DEFINSTR2(Xfcvtsd, {4, {PFPR, PFPR}, 0x1E22C000, EN_FP2R}) +DEFINSTR3(Xfadd, {4|8, {PFPR, PFPR, PFPR}, 0x1E202800, EN_FP3R}) +DEFINSTR3(Xfsub, {4|8, {PFPR, PFPR, PFPR}, 0x1E203800, EN_FP3R}) +DEFINSTR3(Xfmul, {4|8, {PFPR, PFPR, PFPR}, 0x1E200800, EN_FP3R}) +DEFINSTR3(Xfdiv, {4|8, {PFPR, PFPR, PFPR}, 0x1E201800, EN_FP3R}) +DEFINSTR2(Xfcmp, + {4|8, {PFPR, PZERO}, 0x1E602008, EN_FPCMPZ}, + {4|8, {PFPR, PFPR}, 0x1E602000, EN_FPCMP}, +) + +static void +gencopy(uchar **pcode, enum irclass cls, struct block *blk, int curi, struct oper dst, union ref val) +{ + assert(dst.t == OREG); + struct oper src; + if (val.bits == UNDREF.bits) return; + if (isintcon(val)) { + assert(dst.reg <= R(31)); + /* MOV r, #imm */ + uvlong u = intconval(val); + if (~u <= 0xFFFF) { + /* immediate can be encoded with 1 MOVN instruction */ + Xmovn(pcode, cls, dst, mkoper(OIMM, .imm = ~u)); + } else { + /* generate MOV (+ MOVKs) */ + if (cls == KI32) u = (uint)u; + int s = 0; + while (s < 48 && (u >> s & 0xFFFF) == 0) s += 16; + if ((u &~ (0xFFFFull << s)) != 0 && aarch64_logimm(NULL, cls, u)) { + /* can be encoded as a logical immediate in 1 instr */ + Xorr(pcode, cls, dst, REGZR, mkoper(OIMM, .uimm = u)); + } else { + Xmovz(pcode, cls, dst, mkoper(OIMM, .imm = u & (0xFFFFull << s))); + for (s += 16; s <= 48; s += 16) { + if ((u >> s) & 0xFFFF) + Xmovk(pcode, cls, dst, mkoper(OIMM, .imm = u & (0xFFFFull << s))); + } + } + } + } else if (opermatch(PGPRZ, cls, (src = ref2oper(val))) && kisint(cls)) { + Xorr(pcode, cls, dst, REGZR, src); /* MOV Rd, Rn ==> ORR Rd, zr, Rn */ + } else if (kisflt(cls) || opermatch(PFPR, 0, src)) { + if (src.t == OREG) + Xfmov(pcode, cls, dst, src); + else if (src.t == OIMM && src.imm == 0) + Xfmov(pcode, cls, dst, REGZR); + else assert(0); + } else if (isaddrcon(val,0) || (val.t == RADDR && isaddrcon(addrtab.p[val.i].base,0))) { + if ((ccopt.pic || (contab.p[val.i].flag & SFUNC)) && !(contab.p[val.i].flag & SLOCAL)) { + Xadrp(pcode, KPTR, dst, src); + Xadd(pcode, KPTR, dst, dst, src); + } else { + Xadr(pcode, KPTR, dst, src); + } + } else assert(0); +} + +/* maps blk -> address when resolved; or to linked list of jump displacement + * relocations */ +static struct blkaddr { + bool resolved; + union { + uint addr; + uint relreloc; + }; +} *blkaddr; + +enum cc { + CCEQ, CCNE, CCCS, CCCC, CCMI, CCPL, CCVS, CCVC, + CCHI, CCLS, CCGE, CCLT, CCGT, CCLE, CCAL, CCNV, + CCHS = CCCS, CCLO = CCCC, +}; + +static void +Xbcc(uchar **pcode, enum cc cc, struct block *dst) +{ + int disp, insaddr = *pcode - objout.textbegin; + + if (blkaddr[dst->id].resolved) { + disp = (int)(blkaddr[dst->id].addr - insaddr)/4; + assert(disp >= -(1<<18) && disp < (1<<18)); + } else { + disp = blkaddr[dst->id].relreloc; + blkaddr[dst->id].relreloc = insaddr; + } + assert(in_range(cc, 0, 0xF)); + W32(0x54000000 | (disp & 0x7FFFF)<<5 | cc); +} + +static void +Xcbcc(uchar **pcode, enum irclass k, uint rt, enum cc cc, struct block *dst) +{ + int disp, insaddr = *pcode - objout.textbegin; + if (blkaddr[dst->id].resolved) { + disp = (int)(blkaddr[dst->id].addr - insaddr)/4; + assert(disp >= -(1<<18) && disp < (1<<18)); + } else { + disp = blkaddr[dst->id].relreloc; + blkaddr[dst->id].relreloc = insaddr; + } + assert(in_range(cc, CCEQ, CCNE)); + assert(in_range(rt, 0, 31)); + W32(0x34000000 | (uint)(k > KI32)<<31 | cc<<24 | (disp & 0x7FFFF)<<5 | rt); +} + +/* condition code for CMP */ +static const schar icmpop2cc[] = { + [Oequ] = CCEQ, [Oneq] = CCNE, + [Olth] = CCLT, [Ogth] = CCGT, [Olte] = CCLE, [Ogte] = CCGE, + [Oulth] = CCLO, [Ougth] = CCHI, [Oulte] = CCLS, [Ougte] = CCHS, +}, fcmpop2cc[] = { + [Oequ] = CCEQ, [Oneq] = CCNE, + [Olth] = CCLO, [Ogth] = CCGT, [Olte] = CCLS, [Ogte] = CCGE, +}; + +static void +emitbranch(uchar **pcode, struct block *blk) +{ + enum irclass cbk = 0; + struct oper cbopr; + enum cc cc = CCAL; + assert(blk->s1); + if (blk->s2) { + /* conditional branch.. */ + union ref arg = blk->jmp.arg[0]; + assert(arg.t == RTMP); + struct instr *ins = &instrtab[arg.i]; + if (in_range(ins->op, Oequ, Oneq) && ins->r.bits == ZEROREF.bits) { + cc = ins->op == Oequ ? CCEQ : CCNE; + cbk = ins->cls; + cbopr = ref2oper(ins->l); + assert(opermatch(PGPRZ, ins->cls, cbopr)); + } else if (oiscmp(ins->op)) { + /* for CMP instr */ + cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op]; + } else { + /* implicit by ZF */ + cc = CCNE; + } + if (blk->s1 == blk->lnext) { + /* if s1 is next adjacent block, swap s1,s2 and flip condition to emit a + * single jump */ + struct block *tmp = blk->s1; + blk->s1 = blk->s2; + blk->s2 = tmp; + cc ^= 1; + } + } + /* make sure to fallthru if jumping to next adjacent block */ + if (blk->s2 || blk->s1 != blk->lnext) { + if (cbk) Xcbcc(pcode, cbk, cbopr.reg, cc, blk->s1); + else Xbcc(pcode, cc, blk->s1); + } + if (blk->s2 && blk->s2 != blk->lnext) + Xbcc(pcode, CCAL, blk->s2); +} + +static struct instr *lastcmp; + +static void +emitinstr(uchar **pcode, struct function *fn, struct block *blk, int curi, struct instr *ins) +{ + struct oper dst, o1, o2; + enum irclass cls = ins->cls; + void (*X3)(uchar **, enum irclass, struct oper, struct oper, struct oper) = NULL; + void (*X2)(uchar **, enum irclass, struct oper, struct oper) = NULL; + + switch (ins->op) { + default: fatal(NULL, "aarch64 unimplemented instr: %s", opnames[ins->op]); + case Onop: break; + case Omove: + dst = ref2oper(ins->l); + gencopy(pcode, cls, blk, curi, dst, ins->r); + break; + case Oextu32: cls = KI32; + /* fallthru */ + case Ocopy: + dst = reg2oper(ins->reg-1); + gencopy(pcode, cls, blk, curi, dst, ins->l); + break; + case Oswap: + o1 = ref2oper(ins->l), o2 = ref2oper(ins->r); + if (kisflt(ins->cls) && ins->l.i != mctarg->fprscratch && ins->r.i != mctarg->fprscratch) { + dst = reg2oper(mctarg->fprscratch); + Xfmov(pcode, cls, dst, o1); + Xfmov(pcode, cls, o1, o2); + Xfmov(pcode, cls, o2, dst); + } else if (ins->l.i != mctarg->gprscratch && ins->r.i != mctarg->gprscratch) { + dst = reg2oper(mctarg->gprscratch); + Xorr(pcode, cls, dst, REGZR, o1); + Xorr(pcode, cls, o1, REGZR, o2); + Xorr(pcode, cls, o2, REGZR, dst); + } else { + Xeor(pcode, cls, o1, o1, o2); + Xeor(pcode, cls, o2, o1, o2); + Xeor(pcode, cls, o1, o1, o2); + } + break; + case Onot: /* MVN Rd, Rn ==> ORN Rd, zr, Rn */ + Xorn(pcode, cls, reg2oper(ins->reg-1), REGZR, ref2oper(ins->l)); + break; + case Oneg: + if (kisint(ins->cls)) /* NEG Rd, Rn ==> SUB Rd, zr, Rn */ + Xsub(pcode, cls, reg2oper(ins->reg-1), REGZR, ref2oper(ins->l)); + else + Xfneg(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l)); + break; + case Oexts8: case Oexts16: case Oexts32: /* SXTB/H/W Rd, Rn ==> SBFM Rd, Rn, #0, #7/15/31 */ + Xsbfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), 0, (8<<(ins->op-Oexts8)/2)-1); + break; + case Oextu8: case Oextu16: /* UXTB/H Rd, Rn ==> UBFM Rd, Rn, #0, #7/15 */ + Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), 0, (8<<(ins->op-Oexts8)/2)-1); + break; + case Ocvts32f: X2 = Xscvtfw; goto Cvt; + case Ocvts64f: X2 = Xscvtfx; goto Cvt; + case Ocvtf32s: + X2 = cls == KI32 ? Xfcvtzsw : Xfcvtzsx; + cls = KF32; + goto Cvt; + case Ocvtf64s: + X2 = cls == KI32 ? Xfcvtzsw : Xfcvtzsx; + cls = KF64; + goto Cvt; + case Ocvtu32f: X2 = Xucvtfw; goto Cvt; + case Ocvtu64f: X2 = Xucvtfx; goto Cvt; + case Ocvtf32u: + X2 = cls == KI32 ? Xfcvtzuw : Xfcvtzux; + cls = KF32; + goto Cvt; + case Ocvtf64u: + X2 = cls == KI32 ? Xfcvtzuw : Xfcvtzux; + cls = KF64; + goto Cvt; + case Ocvtf32f64: cls = KF32; X2 = Xfcvtsd; goto Cvt; + case Ocvtf64f32: cls = KF32; X2 = Xfcvtds; goto Cvt; + Cvt: + X2(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l)); + break; + case Oadd: X3 = kisint(cls) ? Xadd : Xfadd; goto ALU3; + case Osub: X3 = kisint(cls) ? Xsub : Xfsub; goto ALU3; + case Omul: if (kisflt(cls)) { X3 = Xfmul; goto ALU3; } + /* MUL Rd,Rn,Rm ==> MADD Rd,Rn,Rm,zr */ + Xmadd(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r), REGZR); + break; + case Odiv: X3 = kisint(cls) ? Xsdiv : Xfdiv; goto ALU3; + case Oudiv: X3 = Xudiv; goto ALU3; + case Oand: X3 = Xand; goto ALU3; + case Oior: X3 = Xorr; goto ALU3; + case Oxor: X3 = Xeor; goto ALU3; + ALU3: + X3(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r)); + break; + case Oshl: + if (ins->r.t == RICON) { + uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1); + assert(s > 0); + Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), nbit-s, nbit-s-1); + } else { + X3 = Xlslv; + goto ALU3; + } + break; + case Oslr: + if (ins->r.t == RICON) { + uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1); + assert(s > 0); + Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), s, nbit-1); + } else { + X3 = Xlsrv; + goto ALU3; + } + break; + case Osar: + if (ins->r.t == RICON) { + uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1); + assert(s > 0); + Xsbfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), s, nbit-1); + } else { + X3 = Xasrv; + goto ALU3; + } + break; + case Oequ: case Oneq: + if (!ins->reg && kisint(cls) && ins->r.bits == ZEROREF.bits) /* handled by emitbranch for CBZ/CBNZ */ + break; + case Olth: case Ogth: case Olte: case Ogte: + case Oulth: case Ougth: case Oulte: case Ougte: + if (lastcmp && lastcmp->cls == cls + && lastcmp->l.bits == ins->l.bits && lastcmp->r.bits == ins->r.bits) + /* reuse flags from previous identical cmp */ ; + else if (kisflt(cls)) + Xfcmp(pcode, cls, ref2oper(ins->l), ref2oper(ins->r)); + else /* CMP ... ==> SUBS zr, ... */ + Xsubs(pcode, cls, REGZR, ref2oper(ins->l), ref2oper(ins->r)); + lastcmp = ins; + if (ins->reg) { + enum cc cc = (kisflt(cls) ? fcmpop2cc : icmpop2cc)[ins->op]; + dst = reg2oper(ins->reg-1); + assert(dst.reg < R(31)); + W32(0x1A9F07E0 | (cc^1)<<12 | dst.reg); /* CSET Wd, <invcond> */ + } + break; + case Oloadu8: X2 = Xldrb; goto Load; + case Oloads8: X2 = Xldrsb; goto Load; + case Oloadu16: X2 = Xldrh; goto Load; + case Oloads16: X2 = Xldrsh; goto Load; + case Oloads32: + if (cls != KI32) { + X2 = Xldrsw; + goto Load; + } + case Oloadu32: + cls = KI32; + /* fallthru */ + case Oloadi64: X2 = Xldr; + Load: + X2(pcode, cls, reg2oper(ins->reg-1), mkmemoper(1<<(ins->op - Oloads8)/2, ins->l)); + break; + case Oloadf32: case Oloadf64: + Xfldr(pcode, cls, reg2oper(ins->reg-1), mkmemoper(ins->op == Oloadf32 ? 4 : 8, ins->l)); + break; + case Ostorei8: cls = KI32; X2 = Xstrb; goto Store; + case Ostorei16: cls = KI32; X2 = Xstrh; goto Store; + case Ostorei32: cls = KI32; X2 = Xstr; goto Store; + case Ostorei64: cls = KI64; X2 = Xstr; + Store: + X2(pcode, cls, ins->r.bits == ZEROREF.bits ? REGZR : ref2oper(ins->r), + mkmemoper(1<<(ins->op-Ostorei8), ins->l)); + break; + case Ostoref32: case Ostoref64: + Xfstr(pcode, KF32 + ins->op-Ostoref32, ref2oper(ins->r), mkmemoper(ins->op == Oloadf32 ? 4 : 8, ins->l)); + break; + case Ocall: + Xcall(pcode, ref2oper(ins->l)); + break; + } +} + +struct frame { + regset save; + struct rpair { uchar a,b; } pairs[10]; + uchar single[2]; + uint nfpairs, ngpairs; +}; + +static void +prologue(uchar **pcode, struct frame *frame, struct function *fn) +{ + *frame = (struct frame){0}; + regset save = frame->save = (fn->regusage & mctarg->rcallee) | (usefp * BIT(FP)) | (!fn->isleaf * BIT(LR)); + if (save) { + int prev = 0; + struct rpair *p = frame->pairs; + for (uint reg = V(8); reg <= V(15); ++reg) { + if (!rstest(save, reg)) continue; + if (prev) { + *p++ = (struct rpair) {prev, reg}; + ++frame->nfpairs; + prev = 0; + } else prev = reg; + } + uint ngpr = popcnt(save & (BIT(32)-1)); + if (prev) { + if (ngpr & 1) { + frame->single[0] = prev; + frame->single[1] = prev = lowestsetbit(save); + rsclr(&save, prev); + } else { + *p++ = (struct rpair) {prev, V(0)}; + ++frame->nfpairs; + } + prev = 0; + } else if (ngpr & 1) { + prev = 0x100; + } + for (uint reg = R(19); reg <= LR; ++reg) { + if (!rstest(save, reg)) continue; + if (prev) { + *p++ = (struct rpair) {prev, reg}; + ++frame->ngpairs; + prev = 0; + } else prev = reg; + } + assert(!prev); + + p = frame->pairs; + struct oper adr = mkoper(OMEM, .m = {.mode = APREIDX, .base = SP, .disp = -16}); + for (int i = 0; i < frame->nfpairs; ++i, ++p) + Xfstp(pcode, KF64, reg2oper(p->a), reg2oper(p->b), adr); + adr.m.disp = -8; + if (frame->single[0]) Xfstr(pcode, KF64, reg2oper(frame->single[0]), adr); + if (frame->single[1]) Xstr(pcode, KPTR, reg2oper(frame->single[1]), adr); + adr.m.disp = -16; + for (int i = 0; i < frame->ngpairs; ++i, ++p) + Xstp(pcode, KPTR, reg2oper(p->a), reg2oper(p->b), adr); + } + + if (usefp) /* MOV x29, sp */ + Xadd(pcode, KPTR, reg2oper(FP), reg2oper(SP), mkoper(OIMM,)); + + /* ensure stack is 16-byte aligned for function calls */ + if (!fn->isleaf && ((fn->stksiz) & 0xF) != 0) { + assert(usefp); + rbpoff -= 8; + fn->stksiz += 8; + } + if (fn->stksiz) Xsub(pcode, KPTR, reg2oper(SP), reg2oper(SP), mkoper(OIMM, .imm = fn->stksiz)); +} + +static void +epilogue(uchar **pcode, struct function *fn, struct frame *frame) +{ + if (fn->stksiz) Xadd(pcode, KPTR, reg2oper(SP), reg2oper(SP), mkoper(OIMM, .imm = fn->stksiz)); + if (frame->save) { + struct rpair *p = frame->pairs + frame->nfpairs + frame->ngpairs - 1; + struct oper adr = mkoper(OMEM, .m = {.mode = APOSTIDX, .base = SP, .disp = 16}); + for (int i = 0; i < frame->ngpairs; ++i, --p) + Xldp(pcode, KPTR, reg2oper(p->a), reg2oper(p->b), adr); + adr.m.disp = 8; + if (frame->single[1]) Xldr(pcode, KPTR, reg2oper(frame->single[1]), adr); + if (frame->single[0]) Xfldr(pcode, KF64, reg2oper(frame->single[0]), adr); + adr.m.disp = 16; + for (int i = 0; i < frame->nfpairs; ++i, --p) + Xfldp(pcode, KF64, reg2oper(p->a), reg2oper(p->b), adr); + } +} + +static void +emitbin(struct function *fn) +{ + struct block *blk; + uchar **pcode = &objout.code; + + while ((*pcode - objout.textbegin) % 4) ++*pcode; + fnstart = *pcode; + curfnsym = fn->name; + + /** prologue **/ + + /* only use frame pointer in non-leaf functions and functions that use the stack */ + usefp = !fn->isleaf || fn->stksiz; + struct frame frame; + prologue(pcode, &frame, fn); + + if (*pcode - fnstart > 8) { + /* largue prologue -> largue epilogue -> transform to use single exit point */ + struct block *exit = NULL; + blk = fn->entry->lprev; + do { + if (blk->jmp.t == Jret) { + if (!exit) { + if (blk->ins.n == 0) { + exit = blk; + continue; + } else { + exit = newblk(fn); + exit->lnext = blk->lnext; + exit->lprev = blk; + blk->lnext = exit; + exit->lnext->lprev = exit; + exit->id = fn->nblk++; + exit->jmp.t = Jret; + } + } + blk->jmp.t = Jb; + memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + blk->s1 = exit; + } else if (exit) { + /* thread jumps to the exit block */ + if (blk->s1 && !blk->s1->ins.n && blk->s1->s1 == exit && !blk->s1->s2) blk->s1 = exit; + if (blk->s2 && !blk->s2->ins.n && blk->s2->s1 == exit && !blk->s2->s2) blk->s2 = exit; + } + } while ((blk = blk->lprev) != fn->entry); + } + + blkaddr = allocz(fn->passarena, fn->nblk * sizeof *blkaddr, 0); + + blk = fn->entry; + do { + struct blkaddr *bb = &blkaddr[blk->id]; + uint bbaddr = *pcode - objout.textbegin; + assert(!bb->resolved); + while (bb->relreloc) { + int disp = (bbaddr - bb->relreloc)/4; + assert(disp >= -(1<<18) && disp < (1<<18)); + uint tmp = rd32targ(objout.textbegin + bb->relreloc); + wr32le(objout.textbegin + bb->relreloc, (tmp &~ (0x7FFFFu<<5)) | (disp & 0x7FFFF)<<5); + bb->relreloc = tmp>>5 & 0x7FFFF; + } + bb->resolved = 1; + bb->addr = bbaddr; + + lastcmp = NULL; + for (int i = 0; i < blk->ins.n; ++i) + emitinstr(pcode, fn, blk, i, &instrtab[blk->ins.p[i]]); + if (blk->jmp.t == Jret) { + if (blk->lnext != fn->entry && blk->lnext->jmp.t == Jret && blk->lnext->ins.n == 0) + continue; /* fallthru to next blk's RET */ + epilogue(pcode, fn, &frame); + W32(0xD65F03C0); /* RET */ + } else if (blk->jmp.t == Jtrap) { + W32(0xD4200020); /* BRK #0x1 */ + } else emitbranch(pcode, blk); + } while ((blk = blk->lnext) != fn->entry); + objdeffunc(fn->name, fn->globl, fnstart - objout.textbegin, *pcode - fnstart); +} + +void +aarch64_emit(struct function *fn) +{ + fn->stksiz = alignup(fn->stksiz, 8); + if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name); + emitbin(fn); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_aarch64_isel.c b/src/t_aarch64_isel.c new file mode 100644 index 0000000..398ea28 --- /dev/null +++ b/src/t_aarch64_isel.c @@ -0,0 +1,515 @@ +#include "all.h" + +#define isimm32(r) (iscon(r) && concls(r) == KI32) + +static inline uint +clz(uvlong x) +{ +#if HAS_BUILTIN(clzll) + return __builtin_clzll(x); +#else + int i = 0; + for (uvlong mask = BIT(63);; ++i, mask >>= 1) + if (x & mask) + break; + return i; +#endif +} + +/* Encode logical immediate */ +bool +aarch64_logimm(uint *enc, enum irclass k, uvlong x) +{ + /* https://github.com/v8/v8/blob/927ccc6076e25a614787c7011315468e40fe39a4/src/codegen/arm64/assembler-arm64.cc#L4409 */ + if (k == KI32) x = (uint)x | x << 32; + bool neg; + if ((neg = x & 1)) x = ~x; + if (x == 0) return 0; + uvlong a = x & (~x + 1), + xa = x + a, + b = xa & (~xa + 1), + xa_b = xa - b, + c = xa_b & (~xa_b + 1), + mask; + uint clza = clz(a), + d, outn; + if (c != 0) { + d = clza - clz(c); + mask = BIT(d) - 1; + outn = 0; + } else { + assert(a != 0); + d = 64; + mask = ~0ull; + outn = 1; + } + if (!ispo2(d)) return 0; + if (((b - a) & ~mask) != 0) return 0; + static const uvlong M[] = { + 0x0000000000000001, 0x0000000100000001, 0x0001000100010001, + 0x0101010101010101, 0x1111111111111111, 0x5555555555555555, + }; + int i = clz(d) - 57; + assert((uint)i < countof(M)); + uvlong m = M[i]; + uvlong y = (b - a) * m; + if (y != x) return 0; + if (enc) { + int clzb = b == 0 ? -1 : clz(b), + s = clza - clzb, r; + if (neg) { + s = d - s; + r = (clzb + 1) & (d - 1); + } else { + r = (clza + 1) & (d - 1); + } + *enc = outn<<12 | r<<6 | (((-d * 2) | (s - 1)) & 0x3F); + } + return 1; +} + + +static void fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi); +static void +regarg(union ref *r, enum irclass k, struct block *blk, int *curi) +{ + if (r->t != RTMP) { + *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, *r)); + if (kisflt(k) || instrtab[r->i].l.t == RSTACK) { + int iprev = *curi-1; + fixarg(&instrtab[r->i].l, &instrtab[r->i], blk, &iprev); + *curi = iprev+1; + } + } +} + +static void +fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi) +{ + enum op op = ins ? ins->op : 0; + if (isintcon(*r)) { + vlong x = intconval(*r); + switch (op) { + case Ocopy: return; + default: + if (oiscmp(op)) { + case Oadd: case Osub: + /* imm12 (lsl 12) */ + if ((x &~ 0xFFF) == 0 || (x &~ 0xFFF000) == 0) return; + break; + case Oshl: case Osar: case Oslr: + if ((uvlong)x < (ins->cls == KI32 ? 32 : 64)) return; + break; + case Oand: case Oior: case Oxor: + if (aarch64_logimm(NULL, ins->cls, x)) return; + break; + } + } + goto Reg; + } else if (isfltcon(*r)) { + enum irclass k = concls(*r), ki = KI32 + k-KF32; + if (contab.p[r->i].f != 0.0) { + union { + vlong i64; + int i32; + float f32; + double f64; + } pun; + vlong i; + if (k == KF32) { + pun.f32 = contab.p[r->i].f; + i = pun.i32; + } else { + pun.f64 = contab.p[r->i].f; + i = pun.i64; + } + union ref gpr = insertinstr(blk, (*curi)++, mkinstr(Ocopy, ki, mkintcon(ki, i))); + *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, gpr)); + } else if (oiscmp(op)) { + return; + } else { + *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, *r)); + } + } else if (r->t == RSTACK) { + struct instr adr = mkinstr(Osub, KPTR, mkref(RREG, FP), mkintcon(KI32, r->i)); + if (op == Ocopy) + *ins = adr; + else + *r = insertinstr(blk, (*curi)++, adr); + } else if (r->t != RTMP) Reg: { + regarg(r, r->t == RTMP ? instrtab[r->i].cls : ins->cls ? ins->cls : KI32, blk, curi); + } +} + +static bool +arithfold(struct instr *ins) +{ + if (isnumcon(ins->l) && (!ins->r.t || isnumcon(ins->r))) { + union ref r; + bool ok = ins->r.t ? foldbinop(&r, ins->op, ins->cls, ins->l, ins->r) : foldunop(&r, ins->op, ins->cls, ins->l); + assert(ok && "fold?"); + *ins = mkinstr(Ocopy, insrescls(*ins), r); + return 1; + } + return 0; +} + +static void +selcall(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + const struct call *call = &calltab.p[ins->r.i]; + int iarg = *curi - 1; + enum irclass cls; + uint argstksiz = alignup(call->argstksiz, 16); + + for (int i = call->narg - 1; i >= 0; --i) { + struct abiarg abi = call->abiarg[i]; + struct instr *arg; + for (;; --iarg) { + assert(iarg >= 0 && i >= 0 && "arg?"); + if ((arg = &instrtab[blk->ins.p[iarg]])->op == Oarg) + break; + } + + if (!abi.isstk) { + assert(!abi.ty.isagg); + *arg = mkinstr(Omove, call->abiarg[i].ty.cls, mkref(RREG, abi.reg), arg->r); + } else { + union ref adr = mkaddr((struct addr){mkref(RREG, SP), .disp = abi.stk}); + int iargsave = iarg; + if (!abi.ty.isagg) { /* scalar arg in stack */ + *arg = mkinstr(cls2store[abi.ty.cls], 0, adr, arg->r); + if (isaddrcon(arg->r,1) || arg->r.t == RADDR) + arg->r = insertinstr(blk, iarg++, mkinstr(Ocopy, abi.ty.cls, arg->r)); + else + fixarg(&ins->r, ins, blk, &iarg); + } else { /* aggregate arg in stack, callee stack frame destination address */ + *arg = mkinstr(Ocopy, KPTR, adr); + } + *curi += iarg - iargsave; + } + } + if (call->argstksiz) { + union ref disp = mkref(RICON, argstksiz); + insertinstr(blk, iarg--, (struct instr){Osub, KPTR, .keep=1, .reg = SP+1, .l=mkref(RREG,SP), disp}); + ++*curi; + insertinstr(blk, *curi+1, (struct instr){Oadd, KPTR, .keep=1, .reg = SP+1, .l=mkref(RREG,SP), disp}); + } + if (isimm32(ins->l)) + ins->l = mkaddr((struct addr){.base = ins->l}); + else if (isintcon(ins->l)) + ins->l = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, ins->l)); + + cls = ins->cls; + ins->cls = 0; + if (cls) { + /* duplicate to reuse same TMP ref */ + insertinstr(blk, (*curi)++, *ins); + *ins = mkinstr(Ocopy, cls, mkref(RREG, call->abiret[0].reg)); + for (int i = 1; i <= 2; ++i) { + if (*curi + i >= blk->ins.n) break; + if (instrtab[blk->ins.p[*curi + i]].op == Ocall2r) { + ins = &instrtab[blk->ins.p[*curi += i]]; + *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, call->abiret[1].reg)); + break; + } + } + } +} + +static bool +aimm(struct addr *addr, int disp) +{ + if (addr->index.bits) return 0; + vlong a = addr->disp; + a += disp; + if ((int)a == a) { + addr->disp = a; + return 1; + } + return 0; +} + +static bool +ascale(struct addr *addr, union ref a, union ref b, uint siz/*1,2,4,8*/) +{ + if (b.t != RICON) return 0; + if (addr->index.bits || (addr->disp && !isaddrcon(addr->base,1))) return 0; + if ((unsigned)b.i > 3 || 1<<b.i != siz) return 0; + if (a.t == RREG || a.t == RTMP) { + addr->index = a; + addr->shift = b.i; + return 1; + } + return 0; +} + +static bool +aadd(struct addr *addr, struct block *blk, int *curi, union ref r, uint siz/*1,2,4,8*/) +{ + if (r.t == RSTACK) { + if (addr->base.bits || addr->index.bits || !aimm(addr, -r.i)) goto Ref; + addr->base = mkref(RREG, FP); + } else if (r.t == RTMP) { + struct instr *ins = &instrtab[r.i]; + if (ins->op == Oadd) { + if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref; + if (!aadd(addr, blk, curi, ins->r, siz)) goto Ref; + ins->skip = 1; + } else if (ins->op == Osub) { + if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref; + if (!isintcon(ins->r)) goto Ref; + if (!aimm(addr, -intconval(ins->r))) goto Ref; + ins->skip = 1; + } else if (ins->op == Oshl) { + if (!ascale(addr, ins->l, ins->r, siz)) goto Ref; + ins->skip = 1; + } else if (ins->op == Ocopy) { + if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref; + ins->skip = 1; + } else goto Ref; + } else if (isnumcon(r)) { + assert(isintcon(r)); + return aimm(addr, intconval(r)); + } else if (isaddrcon(r,1)) { + if (!addr->base.bits && !isaddrcon(addr->index,1)) addr->base = r; + else return 0; + } else if (r.t == RREG) { + /* temporaries are single assignment, but register aren't, so they can't be * + * safely hoisted into an address value, unless they have global lifetime */ + if (!rstest(mctarg->rglob, r.i)) return 0; + Ref: + if (r.t == RSTACK && (addr->base.bits || addr->index.bits)) { + r = insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, mkref(RREG, FP), mkref(RICON, -r.i))); + } + if (!addr->base.bits) addr->base = r; + else if (!addr->index.bits) addr->index = r; + else return 0; + } else return 0; + return 1; +} + +static bool +fuseaddr(union ref *r, struct block *blk, int *curi, uint siz/*1,2,4,8*/) +{ + struct addr addr = {0}; + + if (isaddrcon(*r,1)) return 1; + + if (r->t != RSTACK && r->t != RTMP) return 0; + if (!aadd(&addr, blk, curi, *r, siz)) return 0; + if (!(addr.disp >= -256 && addr.disp < 256) /* for 9-bit signed unscaled offset */ + && !(!(addr.disp & (siz-1)) && (uvlong)addr.disp < (1<<12)*siz)) /* 12-bit unsigned scaled offset */ + return 0; + if (isaddrcon(addr.base,0) && (!(contab.p[addr.base.i].flag & SLOCAL) || addr.index.bits)) { + /* first load symbol address into a temp register */ + if (addr.disp && (ccopt.pic || (contab.p[addr.base.i].flag & SFUNC)) && !addr.index.bits) { + addr.base = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, .l = addr.base)); + } else { + addr.base = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, + mkaddr((struct addr){addr.base, .disp = addr.disp}))); + addr.disp = 0; + } + } + *r = mkaddr(addr); + return 1; +} + +static const uchar loadsz[] = { + [Oloads8 - Oloads8] = 1, [Oloadu8 - Oloads8] = 1, + [Oloads16 - Oloads8] = 2, [Oloadu16 - Oloads8] = 2, + [Oloads32 - Oloads8] = 4, [Oloadu32 - Oloads8] = 4, + [Oloadi64 - Oloads8] = 8, + [Oloadf32 - Oloads8] = 4, + [Oloadf64 - Oloads8] = 8, +}; +static const uchar storesz[] = { + [Ostorei8 - Ostorei8] = 1, + [Ostorei16 - Ostorei8] = 2, + [Ostorei32 - Ostorei8] = 4, + [Ostorei64 - Ostorei8] = 8, + [Ostoref32 - Ostorei8] = 4, + [Ostoref64 - Ostorei8] = 8, +}; +static void +loadstoreaddr(struct block *blk, union ref *r, int *curi, enum op op) +{ + uint siz = oisload(op) ? loadsz[op-Oloads8] : storesz[op-Ostorei8]; + if (isimm32(*r)) { + *r = mkaddr((struct addr){.base = *r}); + } else if (isaddrcon(*r, 0)) { + bool pcrelok = in_range(op, Oloads32, Oloadi64); /* LDR-LDRSW have PC-relative literal form */ + if (!pcrelok || !(contab.p[r->i].flag & SLOCAL)) + regarg(r, KPTR, blk, curi); + } else if (r->t == RTMP || r->t == RSTACK) { + fuseaddr(r, blk, curi, siz); + } else if (r->t != RREG) { + *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, *r)); + } +} + +static void +sel(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + enum op op = ins->op; + enum irclass cls; + + if (oisarith(ins->op) && arithfold(ins)) { + fixarg(&ins->l, ins, blk, curi); + return; + } + + switch (op) { + //default: assert(0); + case Onop: break; + case Oalloca1: case Oalloca2: case Oalloca4: case Oalloca8: case Oalloca16: + assert(!"unlowered alloca"); + break; + case Ocopy: + fixarg(&ins->l, ins, blk, curi); + break; + case Oparam: + assert(ins->l.t == RICON && ins->l.i < fn->nabiarg); + if (!fn->abiarg[ins->l.i].isstk) + *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, fn->abiarg[ins->l.i].reg)); + else /* stack */ + *ins = mkinstr(Oadd, KPTR, mkref(RREG, FP), mkref(RICON, 16+fn->abiarg[ins->l.i].stk)); + break; + case Oneg: case Onot: + case Ocvtf32s: case Ocvtf32u: + case Ocvtf32f64: case Ocvtf64s: + case Ocvtf64u: case Ocvtf64f32: + case Ocvts32f: case Ocvtu32f: + case Ocvts64f: case Ocvtu64f: + case Oexts8: case Oextu8: + case Oexts16: case Oextu16: + case Oexts32: + regarg(&ins->l, ins->cls, blk, curi); + break; + case Oextu32: + regarg(&ins->l, ins->cls, blk, curi); + ins->op = Ocopy; + break; + case Oadd: + if (isnumcon(ins->l)) { + /* swap to have const in rhs */ + union ref tmp = ins->l; + ins->l = ins->r; + ins->r = tmp; + } + case Osub: + if (ins->r.t == RICON && ins->r.i < 0) { + op = ins->op ^= 1; + ins->r.i = -ins->r.i; + } + if (!(isaddrcon(ins->l,0) && (contab.p[ins->l.i].flag & SLOCAL))) + regarg(&ins->l, ins->cls, blk, curi); + fixarg(&ins->r, ins, blk, curi); + break; + case Oand: case Oior: case Oxor: + if (isnumcon(ins->l)) { + /* swap to have const in rhs */ + union ref tmp = ins->l; + ins->l = ins->r; + ins->r = tmp; + } + case Oshl: case Osar: case Oslr: + case Oequ: case Oneq: + case Olth: case Ogth: case Olte: case Ogte: + case Oulth: case Ougth: case Oulte: case Ougte: + case Omove: + regarg(&ins->l, ins->cls, blk, curi); + fixarg(&ins->r, ins, blk, curi); + break; + case Omul: case Odiv: case Oudiv: case Ourem: + regarg(&ins->l, ins->cls, blk, curi); + regarg(&ins->r, ins->cls, blk, curi); + break; + case Oarg: + fixarg(&ins->r, ins, blk, curi); + break; + case Ocall: + selcall(fn, ins, blk, curi); + break; + case Oloads8: case Oloadu8: case Oloads16: case Oloadu16: + case Oloads32: case Oloadu32: case Oloadi64: case Oloadf32: case Oloadf64: + loadstoreaddr(blk, &ins->l, curi, op); + break; + case Ostorei8: case Ostorei16: case Ostorei32: cls = KI32; goto Store; + case Ostorei64: cls = KI64; goto Store; + case Ostoref32: cls = KF32; goto Store; + case Ostoref64: cls = KF64; Store: + loadstoreaddr(blk, &ins->l, curi, op); + regarg(&ins->r, cls, blk, curi); + break; + } +} + +static void +seljmp(struct function *fn, struct block *blk) +{ + if (blk->jmp.t == Jb && blk->jmp.arg[0].bits) { + int curi = blk->ins.n; + fixarg(&blk->jmp.arg[0], NULL, blk, &curi); + union ref c = blk->jmp.arg[0]; + if (c.t != RTMP) { + enum irclass cls = c.t == RICON ? KI32 : c.t == RXCON && contab.p[c.i].cls ? contab.p[c.i].cls : KPTR; + int curi = blk->ins.n; + + c = insertinstr(blk, blk->ins.n, mkinstr(Ocopy, cls, c)); + sel(fn, &instrtab[c.i], blk, &curi); + } + if (!oiscmp(instrtab[c.i].op)) { + enum irclass k = insrescls(instrtab[c.i]); + blk->jmp.arg[0] = insertinstr(blk, blk->ins.n, mkinstr(Oneq, k, c, kisint(k) ? ZEROREF : mkfltcon(k, 0))); + struct instr *ins = &instrtab[blk->jmp.arg[0].i]; + ins->keep = 1; + } else { + instrtab[c.i].keep = 1; + } + } else if (blk->jmp.t == Jret) { + if (blk->jmp.arg[0].bits) { + union ref r = mkref(RREG, fn->abiret[0].reg); + struct instr *ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[0].ty.cls, r, blk->jmp.arg[0])).i]; + int curi = blk->ins.n-1; + fixarg(&ins->r, ins, blk, &curi); + blk->jmp.arg[0] = r; + if (blk->jmp.arg[1].bits) { + r = mkref(RREG, fn->abiret[1].reg); + ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[1].ty.cls, r, blk->jmp.arg[1])).i]; + } + } + } +} + +void +aarch64_isel(struct function *fn) +{ + struct block *blk = fn->entry; + + do { + int i; + for (i = 0; i < blk->phi.n; ++i) { + struct instr *ins = &instrtab[blk->phi.p[i]]; + union ref *phi = phitab.p[ins->l.i]; + for (int i = 0; i < blk->npred; ++i) { + int curi = blkpred(blk, i)->ins.n; + fixarg(&phi[i], ins, blkpred(blk, i), &curi); + } + } + for (i = 0; i < blk->ins.n; ++i) { + struct instr *ins = &instrtab[blk->ins.p[i]]; + sel(fn, ins, blk, &i); + } + seljmp(fn, blk); + } while ((blk = blk->lnext) != fn->entry); + + if (ccopt.dbg.i) { + bfmt(ccopt.dbgout, "<< After isel >>\n"); + irdump(fn); + } + + fn->prop = 0; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_x86-64.h b/src/t_x86-64.h new file mode 100644 index 0000000..c0c38ff --- /dev/null +++ b/src/t_x86-64.h @@ -0,0 +1,18 @@ +#include "../ir/ir.h" + +#define LIST_REGS(_) \ + _(RAX) _(RCX) _(RDX) _(RBX) _(RSP) _(RBP) _(RSI) _(RDI) \ + _(R8) _(R9) _(R10) _(R11) _(R12) _(R13) _(R14) _(R15) \ + _(XMM0) _(XMM1) _(XMM2) _(XMM3) _(XMM4) _(XMM5) _(XMM6) _(XMM7) \ + _(XMM8) _(XMM9) _(XMM10) _(XMM11) _(XMM12) _(XMM13) _(XMM14) _(XMM15) + +enum reg { +#define R(r) r, + LIST_REGS(R) +#undef R +}; + +void x86_64_isel(struct function *); +void x86_64_emit(struct function *); + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_x86-64_emit.c b/src/t_x86-64_emit.c new file mode 100644 index 0000000..d3a466b --- /dev/null +++ b/src/t_x86-64_emit.c @@ -0,0 +1,1422 @@ +#include "all.h" +#include "../obj/obj.h" +#include "../endian.h" + +/** Instruction operands ** + * + * Can be a register, a 32-bit immediate, + * a memory reference [base + index * scale + disp], + * or a relocatable reference to some symbol plus a displacement and maybe index*scale + */ +enum operkind { ONONE, OREG, OIMM, OMEM, OSYM, OSYMGOT }; +enum { NOBASE = 63, NOINDEX = 63 }; +struct oper { + uchar t; + union { + struct { uchar base; }; /* OMEM */ + struct { uchar cindex : 6, cshift : 2; }; /* OSYM */ + }; + union { + struct { uchar index, shift; }; /* OMEM */ + ushort con; /* OSYM */ + }; + union { + uchar reg; /* OREG */ + int disp; /* OMEM, OSYM */ + int imm; /* OIMM */ + }; +}; +#define mkoper(t, ...) ((struct oper){(t), __VA_ARGS__}) +#define reg2oper(R) (assert((uint)(R) <= XMM15), mkoper(OREG, .reg = (R))) + +static struct oper mkmemoper(union ref); + +static struct oper +ioper(int i) +{ + int reg = instrtab[i].reg - 1; + return reg < 0 ? mkoper(ONONE,) : reg2oper(reg); +} + +static struct oper +ref2oper(union ref r) +{ + switch (r.t) { + case RTMP: return ioper(r.i); + case RREG: return reg2oper(r.i); + case RICON: return mkoper(OIMM, .imm = r.i); + case RXCON: + if (contab.p[r.i].cls == KI32) + return mkoper(OIMM, .imm = contab.p[r.i].i); + else if (contab.p[r.i].cls == KI64) { + vlong i = contab.p[r.i].i; + assert(i == (int)i); + return mkoper(OIMM, .imm = i); + } else if (!contab.p[r.i].cls) { + return mkoper(OSYM, .con = r.i, .cindex = NOINDEX); + } + assert(0); + case RADDR: return mkmemoper(r); + default: assert(0); + } +} + +static void +addmemoper(struct oper *mem, struct oper add) +{ + assert(mem->t == OMEM); + if (add.t == OIMM) { + mem->disp += add.imm; + } else if (add.t == OREG) { + if (mem->base == NOBASE) + mem->base = add.reg; + else if (mem->index == NOINDEX) + mem->index = add.reg; + else + assert(0); + } +} + +/* helpers to convert a reference to an operand of a specific kind, + * with assertions to make sure nothing went wrong */ + +static inline struct oper +mkregoper(union ref r) +{ + assert(r.t == RREG || (r.t == RTMP && ioper(r.i).t == OREG)); + return r.t == RREG ? reg2oper(r.i) : ioper(r.i); +} + +static inline struct oper +mkimmoper(union ref r) +{ + assert(iscon(r) && concls(r) == KI32); + return mkoper(OIMM, .imm = intconval(r)); +} + +#define ismemref(ref) ((ref).t == RTMP && ioper((ref).i).t == OMEM) +#define isregref(ref) ((ref).t == RREG || ((ref).t == RTMP && ioper((ref).i).t == OREG)) + +static inline struct oper +mkimmregoper(union ref r) +{ + assert(isregref(r) || (iscon(r) && concls(r) == KI32)); + return ref2oper(r); +} + +static inline struct oper +mkdatregoper(union ref r) +{ + assert(isregref(r) || (r.t == RXCON && contab.p[r.i].deref)); + return ref2oper(r); +} + +static inline struct oper +mkimmdatregoper(union ref r) +{ + assert(isregref(r) || r.t == RICON || (r.t == RXCON && (contab.p[r.i].cls == KI32 || contab.p[r.i].deref))); + return ref2oper(r); +} + +static struct oper +mkmemoper(union ref r) +{ + if (r.t == RTMP) { + struct oper wop = ioper(r.i); + if (wop.t == OMEM) return wop; + assert(wop.t == OREG); + return mkoper(OMEM, .base = wop.reg, .index = NOINDEX); + } else if (r.t == RADDR) { + const struct addr *addr = &addrtab.p[r.i]; + assert(addr->shift <= 3); + if (isaddrcon(addr->base,0)) { + return mkoper(OSYM, .con = addr->base.i, + .cindex = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX, + .cshift = addr->shift, + .disp = addr->disp); + } else if (isintcon(addr->base)) { + assert(!addr->disp); + return mkoper(OMEM, .base = NOBASE, + .index = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX, + .disp = intconval(addr->base), + .shift = addr->shift); + } else if (isaddrcon(addr->index,0)) { + assert(!addr->shift); + return mkoper(OSYM, .con = addr->index.i, + .cindex = addr->base.bits ? mkregoper(addr->base).reg : NOINDEX, + .disp = addr->disp); + } + return mkoper(OMEM, .base = addr->base.bits ? mkregoper(addr->base).reg : NOBASE, + .index = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX, + .disp = addr->disp, + .shift = addr->shift); + } else if (r.t == RXCON) { + assert(!contab.p[r.i].cls); + return mkoper(OSYM, .con = r.i, .cindex = NOINDEX); + } else { + return mkoper(OMEM, .base = isregref(r) ? ref2oper(r).reg : NOBASE, + .index = NOINDEX, + .disp = isregref(r) ? 0 : mkimmoper(r).imm); + } +} + +/** Instruction description tables ** + * + * Each instruction is a list of descs, and the first one that matches + * is emitted. Each entry has a size pattern field, which is a bitset + * of the sizes (in bytes) that the entry matches, and 2 operand patterns, + * which describe the operands that can match (for example, PRAX matches + * a RAX register operand, PGPR matches any integer register, I8 matches + * an immediate operand between [-128,127]) The rest of the fields describe + * the instruction's encoding. + * (reference: https://www.felixcloutier.com/x86/ & https://wiki.osdev.org/X86-64_Instruction_Encoding ) + */ + +enum operpat { + PNONE, + PRAX, + PRCX, + PGPR, + PFPR, + P1, /* imm = 1 */ + PN1, /* imm = -1 */ + PI8, + PU8, + PI16, + PU16, + PI32, + PU32, + PMEM, + PSYM, +}; +enum operenc { + EN_R = 1, /* reg with /r */ + EN_RR, /* reg, reg with /r */ + EN_RRX, /* reg, reg with /r (inverted) */ + EN_MR, /* mem, reg with /r */ + EN_RM, /* reg, mem with /r */ + EN_M, /* mem */ + EN_RI8, /* reg, imm8 with /0 */ + EN_RI32, /* reg, imm32 with /0 */ + EN_MI8, /* mem, imm8 with /x */ + EN_MI16, /* mem, imm16 with /x */ + EN_MI32, /* mem, imm32 with /x */ + EN_O, /* reg with op + reg */ + EN_OI, /* reg, imm32 with op + reg */ + EN_I8, /* imm8 */ + EN_I32, /* imm32 */ + EN_R32, /* rel32 */ + NOPERENC, +}; +struct desc { + uchar psiz; /* subset of {1,2,4,8} */ + uchar ptd, pts; /* bitsets of enum operpat */ + uchar nopc; /* countof opc */ + const char opc[8]; /* opcode bytes */ + uchar operenc; /* enum operenc */ + uchar ext; /* ModR/M.reg opc extension */ + bool r8; /* uses 8bit register */ + bool norexw; /* do not use REX.W even if size is 64 bits */ +}; + +/* match operand against pattern */ +static inline bool +opermatch(enum operpat pat, struct oper oper) +{ + switch (pat) { + case PNONE: return !oper.t; + case PRAX: return oper.t == OREG && oper.reg == RAX; + case PRCX: return oper.t == OREG && oper.reg == RCX; + case PGPR: return oper.t == OREG && oper.reg <= R15; + case PFPR: return oper.t == OREG && oper.reg >= XMM0; + case P1: return oper.t == OIMM && oper.imm == 1; + case PN1: return oper.t == OIMM && oper.imm == -1; + case PI8: return oper.t == OIMM && (schar)oper.imm == oper.imm; + case PU8: return oper.t == OIMM && (uchar)oper.imm == oper.imm; + case PI16: return oper.t == OIMM && (short)oper.imm == oper.imm; + case PU16: return oper.t == OIMM && (ushort)oper.imm == oper.imm; + case PI32: return oper.t == OIMM; + case PU32: return oper.t == OIMM && oper.imm >= 0; + case PMEM: return in_range(oper.t, OMEM, OSYMGOT); + case PSYM: return oper.t == OSYM || oper.t == OSYMGOT; + } + assert(0); +} + +/* code output helpers */ +#define B(b) (*(*pcode)++ = (b)) +#define D(xs, N) (memcpy(*pcode, (xs), (N)), (*pcode) += (N)) +#define I16(w) (wr16le(*pcode, (w)), *pcode += 2) +#define I32(w) (wr32le(*pcode, (w)), *pcode += 4) +#define DS(S) D(S, sizeof S - 1) + +static bool usebp; /* use RBP? */ +static int rbpoff; +static internstr curfnsym; +static uchar *fnstart; + +/* Given an instruction description table, find the first entry that matches + * the operands (where dst, src are the operands in intel syntax order) and encode it */ +static void +encode(uchar **pcode, const struct desc *tab, int ntab, enum irclass k, struct oper dst, struct oper src) +{ + const uchar *opc; + int nopc; + struct oper mem; + enum reg reg; + const struct desc *en = NULL; + for (int i = 0; i < ntab; ++i) { + if ((tab[i].psiz & cls2siz[k]) && opermatch(tab[i].ptd, dst) && opermatch(tab[i].pts, src)) { + en = &tab[i]; + break; + } + } + assert(en && "no match for instr"); + + if (en->ptd == PFPR) dst.reg &= 15; + if (en->pts == PFPR) src.reg &= 15; + opc = (uchar *)en->opc; + nopc = en->nopc; + /* mandatory prefixes go before REX */ + if (*opc == 0x66 || *opc == 0xF2 || *opc == 0xF3) + B(*opc++), --nopc; + int rex = in_range(k, KI64, KPTR) << 3; /* REX.W */ + if (en->norexw) rex = 0; + switch (en->operenc) { + case EN_RR: /* mod = 11; reg = dst; rm = src */ + rex |= (dst.reg >> 3) << 2; /* REX.R */ + rex |= (src.reg >> 3) << 0; /* REX.B */ + if (rex) B(0x40 | rex); + else if (en->r8 && in_range(src.reg, RSP, RDI)) { + /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */ + B(0x40); + } + D(opc, nopc); + B(0300 | (dst.reg & 7) << 3 | (src.reg & 7)); + break; + case EN_RRX: /* mod = 11; reg = src; rm = dst */ + rex |= (src.reg >> 3) << 2; /* REX.R */ + rex |= (dst.reg >> 3) << 0; /* REX.B */ + if (rex) B(0x40 | rex); + else if (en->r8 && in_range(dst.reg, RSP, RDI)) { + /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */ + B(0x40); + } + D(opc, nopc); + B(0300 | (src.reg & 7) << 3 | (dst.reg & 7)); + break; + case EN_MR: + mem = dst; + reg = src.reg; + goto Mem; + case EN_RM: + mem = src; + reg = dst.reg; + goto Mem; + case EN_M: case EN_MI8: case EN_MI16: case EN_MI32: + mem = dst; + reg = en->ext; + Mem: + if (mem.t == OMEM) { + if (mem.base != NOBASE) rex |= mem.base >> 3; /* REX.B */ + if (mem.index != NOINDEX) rex |= mem.index >> 3 << 1; /* REX.X */ + } else { + if (mem.cindex != NOINDEX) rex |= mem.cindex >> 3 << 1; /* REX.X */ + } + if (en->operenc != EN_M) + rex |= (reg >> 3) << 2; /* REX.R */ + if (rex) B(0x40 | rex); + else if (en->r8 && in_range(reg, RSP, RDI)) B(0x40); + + if (mem.t == OSYM || mem.t == OSYMGOT) { + D(opc, nopc); + if (mem.cindex == NOINDEX) { + /* %rip(var) */ + static uchar offs[NOPERENC] = { [EN_MI8] = 1, [EN_MI16] = 2, [EN_MI32] = 4 }; + uint addr; + int disp = mem.disp - 4 - offs[en->operenc]; + internstr sym = xcon2sym(mem.con); + B(/*mod 0*/ (reg & 7) << 3 | RBP); + if (objhassym(sym, &addr) == Stext && mem.t != OSYMGOT) { + I32(addr - (*pcode - objout.textbegin) + disp); + } else { + enum relockind r = REL_PCREL32; + if (mem.t == OSYMGOT) r = rex ? REL_GOTPCRELX_REX : REL_GOTPCRELX; + objreloc(xcon2sym(mem.con), r, Stext, *pcode - objout.textbegin, disp); + I32(0); + } + } else { + /* var(,%reg,shift) */ + assert(!ccopt.pic && !ccopt.pie && "cannot encode [RIP-rel + REG] for position independent"); + B(/*mod 0*/ (reg & 7) << 3 | RSP); + B(mem.cshift << 6 | ((mem.cindex & 7) << 3) | RBP); /* SIB [index*s + disp32] */ + objreloc(xcon2sym(mem.con), REL_ABS32S, Stext, *pcode - objout.textbegin, mem.disp); + I32(0); + } + } else { + int mod; + bool sib = 0; + if (mem.base == RBP) { + if (!usebp) { + /* if RBP isn't being set up (leaf functions with no stack allocations), + * access thru RSP (function arguments in the stack) */ + mem.base = RSP; + mem.disp -= 8; + } else if (mem.disp <= 0) { + mem.disp += rbpoff; + } + } + if (mem.base != NOBASE) { + if (mem.index == NOINDEX && mem.shift == 0) sib = 0; + else sib = 1; + mod = !mem.disp ? 0 /* disp = 0 -> mod = 00 */ + : (schar)mem.disp == mem.disp ? 1 /* disp8 -> mod = 01 */ + : 2; /* disp32 -> mod = 10 */ + if (mod == 0 && (mem.base == RBP || mem.base == R13)) mod = 1; + if (mem.base == RSP || mem.base == R12) sib = 1; + } else { + /* [disp + (index*s)] */ + sib = 1; + mem.base = RBP; + mod = 0; + assert(mem.index != RSP); + } + D(opc, nopc); + B(mod << 6 | (reg & 7) << 3 | (sib ? 4 : (mem.base & 7))); + if (sib) { + if (mem.index == NOINDEX) mem.index = RSP; + B(mem.shift << 6 | (mem.index & 7) << 3 | (mem.base & 7)); + } + if (mod == 1) B(mem.disp); + else if (mod == 2 || (mod == 0 && mem.base == RBP/*RIP-rel*/) || (mod == 0 && sib && mem.base == RBP/*absolute*/)) { + I32(mem.disp); + } + } + if (en->operenc == EN_MI8) B(src.imm); + if (en->operenc == EN_MI16) I16(src.imm); + if (en->operenc == EN_MI32) I32(src.imm); + break; + case EN_R: case EN_RI32: case EN_RI8: + rex |= (dst.reg >> 3) << 0; /* REX.B */ + if (rex) B(0x40 | rex); + else if (en->r8 && in_range(dst.reg, RSP, RDI)) { + /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */ + B(0x40); + } + D(opc, nopc); + B(0300 | en->ext << 3 | (dst.reg & 7)); + if (en->operenc == EN_RI32) + I32(src.imm); + else if (en->operenc == EN_RI8) + B(src.imm); + break; + case EN_O: case EN_OI: + rex |= (dst.reg >> 3) << 0; /* REX.B */ + if (rex) B(0x40 | rex); + D(opc, nopc - 1); + B(opc[nopc-1] + (dst.reg & 7)); + if (en->operenc == EN_OI) I32(src.imm); + break; + case EN_I8: + if (rex) B(0x40 | rex); + D(opc, nopc); + B(src.imm); + break; + case EN_I32: + if (rex) B(0x40 | rex); + D(opc, nopc); + I32(src.imm); + break; + case EN_R32: + if (rex) B(0x40 | rex); + D(opc, nopc); + assert(dst.t == OSYM); + internstr sym = xcon2sym(dst.con); + uint addr; + if (sym == curfnsym) { + I32(fnstart - *pcode - 4); + } else if (objhassym(sym, &addr) == Stext) { + I32(addr - (*pcode - objout.textbegin) - 4); + } else { + enum relockind r = (ccopt.pie|ccopt.pic) ? REL_PLT32 : REL_PCREL32; + objreloc(sym, r, Stext, *pcode - objout.textbegin, -4); + I32(0); + } + break; + } +} + +#define DEFINSTR1(X, ...) \ + static void \ + X(uchar **pcode, enum irclass k, struct oper oper) \ + { \ + static const struct desc tab[] = { __VA_ARGS__ }; \ + encode(pcode, tab, countof(tab), k, oper, mkoper(0,)); \ + } + +#define DEFINSTR2(X, ...) \ + static void \ + X(uchar **pcode, enum irclass k, struct oper dst, struct oper src) \ + { \ + static const struct desc tab[] = { __VA_ARGS__ }; \ + encode(pcode, tab, countof(tab), k, dst, src); \ + } + +#define O(s) (sizeof s)-1,s +DEFINSTR2(Xmovb, + {-1, PMEM, PGPR, O("\x88"), EN_MR, .r8=1}, /* MOV m8, r8 */ + {-1, PMEM, PI32, O("\xC6"), EN_MI8, .r8=1}, /* MOV m8, imm8 */ +) +DEFINSTR2(Xmovw, + {-1, PMEM, PGPR, O("\x66\x89"), EN_MR}, /* MOV m16, r16 */ + {-1, PMEM, PI32, O("\x66\xC7"), EN_MI16}, /* MOV m16, imm16 */ +) +static void Xmov(uchar **pcode, enum irclass k, struct oper dst, struct oper src) +{ + static const struct desc all[] = { + {4 , PGPR, PI32, O("\xB8"), EN_OI}, /* MOV r32, imm */ + {4|8, PGPR, PGPR, O("\x8B"), EN_RR}, /* MOV r32/64, r32/64 */ + {4|8, PMEM, PGPR, O("\x89"), EN_MR}, /* MOV m32/64, r32/64 */ + {4|8, PGPR, PMEM, O("\x8B"), EN_RM}, /* MOV r32/64, m32/64 */ + {4|8, PMEM, PI32, O("\xC7"), EN_MI32}, /* MOV m32/64, imm */ + { 8, PGPR, PU32, O("\xB8"), EN_OI, .norexw=1}, /* MOV r64, uimm */ + { 8, PGPR, PI32, O("\xC7"), EN_RI32}, /* MOV r64, imm */ + {4 , PFPR, PFPR, O("\x0F\x28"), EN_RR}, /* MOVPS xmm, xmm */ + {4 , PFPR, PMEM, O("\xF3\x0F\x10"), EN_RM}, /* MOVSS xmm, m32 */ + {4 , PMEM, PFPR, O("\xF3\x0F\x11"), EN_MR}, /* MOVSS m32, xmm */ + {8 , PFPR, PFPR, O("\x0F\x28"), EN_RR}, /* MOVPS xmm, xmm */ + {8 , PFPR, PMEM, O("\xF2\x0F\x10"), EN_RM}, /* MOVSD xmm, m64 */ + {8 , PMEM, PFPR, O("\xF2\x0F\x11"), EN_MR}, /* MOVSS m64, xmm */ + {4|8, PFPR, PGPR, O("\x66\x0F\x6E"), EN_RR}, /* MOVD/Q xmm, r64/32 */ + {4|8, PGPR, PFPR, O("\x66\x0F\x7E"), EN_RRX}, /* MOVD/Q r64/32, xmm */ + }; + static const uchar k2off[] = { + [KI32] = 0, + [KI64] = 1, [KPTR] = 1, + [KF32] = 7, + [KF64] = 10, + }; + if (kisflt(k) && src.t == OIMM && src.imm == 0) { + /* special case for storing zero float : use integer instruction with zero immediate */ + k = KI32 + (k - KF32); + } + encode(pcode, all + k2off[k], countof(all) - k2off[k], k, dst, src); +} +DEFINSTR2(Xmovsxl, + {8, PGPR, PMEM, O("\x63"), EN_RM}, /* MOVSXD r64, m32 */ + {8, PGPR, PGPR, O("\x63"), EN_RR}, /* MOVSXD r64, r32 */ + {4, PGPR, PMEM, O("\x8B"), EN_RM}, /* MOV r32, m32 */ + {4, PGPR, PGPR, O("\x8B"), EN_RR}, /* MOV r32, r32 */ +) +DEFINSTR2(Xmovsxw, + {4|8, PGPR, PMEM, O("\x0F\xBF"), EN_RM}, /* MOVSX r64, m16 */ + {4|8, PGPR, PGPR, O("\x0F\xBF"), EN_RR}, /* MOVSX r64, r16 */ +) +DEFINSTR2(Xmovsxb, + {4|8, PGPR, PMEM, O("\x0F\xBE"), EN_RM}, /* MOVSX r64, m8 */ + {4|8, PGPR, PGPR, O("\x0F\xBE"), EN_RR, .r8=1}, /* MOVSX r64, r8 */ +) +DEFINSTR2(Xmovzxw, + {4|8, PGPR, PMEM, O("\x0F\xB7"), EN_RM}, /* MOVZX r64, m16 */ + {4|8, PGPR, PGPR, O("\x0F\xB7"), EN_RR}, /* MOVZX r64, r16 */ +) +DEFINSTR2(Xmovzxb, + {4|8, PGPR, PMEM, O("\x0F\xB6"), EN_RM}, /* MOVZX r64, m8 */ + {4|8, PGPR, PGPR, O("\x0F\xB6"), EN_RR, .r8=1}, /* MOVZX r64, r8 */ +) +DEFINSTR2(Xmovaps, + {-1, PMEM, PFPR, O("\x0F\x29"), EN_MR}, /* MOVAPS mem, xmm */ +) +DEFINSTR2(Xxchg, + {4|8, PGPR, PGPR, O("\x87"), EN_RR}, /* XCHG r32/64, r32/64 */ + {4|8, PGPR, PMEM, O("\x87"), EN_RM}, /* XCHG r32/64, m32/64 */ + {4|8, PMEM, PGPR, O("\x87"), EN_MR}, /* XCHG r32/64, m32/64 */ +) +DEFINSTR2(Xlea, + {4|8, PGPR, PMEM, O("\x8D"), EN_RM}, /* LEA r32/64,m32/64 */ + { 8, PGPR, PSYM, O("\x8D"), EN_RM}, /* LEA r32/64,rel32 */ +) +DEFINSTR2(Xadd, + {4|8, PGPR, PGPR, O("\x03"), EN_RR}, /* ADD r32/64, r32/64 */ + {4|8, PGPR, P1, O("\xFF"), EN_R, .ext=0}, /* INC r32/64 */ + {4|8, PGPR, PN1, O("\xFF"), EN_R, .ext=1}, /* DEC r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8}, /* ADD r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x05"), EN_I32}, /* ADD eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32}, /* ADD r32/64, imm */ + { 8, PGPR, PMEM, O("\x03"), EN_RM}, /* ADD r64, m64 */ +) +DEFINSTR2(Xaddf, + {4, PFPR, PFPR, O("\xF3\x0F\x58"), EN_RR}, /* ADDSS xmm, xmm */ + {8, PFPR, PFPR, O("\xF2\x0F\x58"), EN_RR}, /* ADDSD xmm, xmm */ + {4, PFPR, PMEM, O("\xF3\x0F\x58"), EN_RM}, /* ADDSS xmm, m32 */ + {8, PFPR, PMEM, O("\xF2\x0F\x58"), EN_RM}, /* ADDSD xmm, m64 */ +) +DEFINSTR2(Xsub, + {4|8, PGPR, PGPR, O("\x2B"), EN_RR}, /* SUB r32/64, r32/64 */ + {4|8, PGPR, P1, O("\xFF"), EN_R, .ext=1}, /* DEC r32/64 */ + {4|8, PGPR, PN1, O("\xFF"), EN_R, .ext=0}, /* INC r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=5}, /* SUB r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x2D"), EN_I32}, /* SUB eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=5}, /* SUB r32/64, imm */ + { 8, PGPR, PMEM, O("\x2B"), EN_RM}, /* SUB r64, m64 */ +) +DEFINSTR2(Xsubf, + {4, PFPR, PFPR, O("\xF3\x0F\x5C"), EN_RR}, /* SUBSS xmm, xmm */ + {8, PFPR, PFPR, O("\xF2\x0F\x5C"), EN_RR}, /* SUBSD xmm, xmm */ + {4, PFPR, PMEM, O("\xF3\x0F\x5C"), EN_RM}, /* SUBSS xmm, m32 */ + {8, PFPR, PMEM, O("\xF2\x0F\x5C"), EN_RM}, /* SUBSD xmm, m64 */ +) +DEFINSTR2(Xmulf, + {4, PFPR, PFPR, O("\xF3\x0F\x59"), EN_RR}, /* MULSS xmm, xmm */ + {8, PFPR, PFPR, O("\xF2\x0F\x59"), EN_RR}, /* MULSD xmm, xmm */ + {4, PFPR, PMEM, O("\xF3\x0F\x59"), EN_RM}, /* MULSS xmm, m32 */ + {8, PFPR, PMEM, O("\xF2\x0F\x59"), EN_RM}, /* MULSD xmm, m64 */ +) +DEFINSTR2(Xdivf, + {4, PFPR, PFPR, O("\xF3\x0F\x5E"), EN_RR}, /* DIVSS xmm, xmm */ + {8, PFPR, PFPR, O("\xF2\x0F\x5E"), EN_RR}, /* DIVSD xmm, xmm */ + {4, PFPR, PMEM, O("\xF3\x0F\x5E"), EN_RM}, /* DIVSS xmm, m32 */ + {8, PFPR, PMEM, O("\xF2\x0F\x5E"), EN_RM}, /* DIVSD xmm, m64 */ +) +DEFINSTR2(Xand, + {4|8, PGPR, PGPR, O("\x23"), EN_RR}, /* AND r32/64, r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=4}, /* AND r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x25"), EN_I32}, /* AND eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=4}, /* AND r32/64, imm */ + { 8, PGPR, PMEM, O("\x23"), EN_RM}, /* AND r64, m64 */ +) +DEFINSTR2(Xior, + {4|8, PGPR, PGPR, O("\x0B"), EN_RR}, /* OR r32/64, r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=1}, /* OR r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x0D"), EN_I32}, /* OR eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=1}, /* OR r32/64, imm */ + { 8, PGPR, PMEM, O("\x0B"), EN_RM}, /* OR r64, m64 */ + {4|8, PFPR, PFPR, O("\x0F\x57"), EN_RR}, /* ORPS xmm, xmm */ +) +DEFINSTR2(Xxor, + {4|8, PGPR, PGPR, O("\x33"), EN_RR}, /* XOR r32/64, r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=6}, /* XOR r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x35"), EN_I32}, /* XOR eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=6}, /* XOR r32/64, imm */ + { 8, PGPR, PMEM, O("\x33"), EN_RM}, /* XOR r64, m64 */ + {4|8, PFPR, PFPR, O("\x0F\x57"), EN_RR}, /* XORPS xmm, xmm */ + {4|8, PFPR, PMEM, O("\x0F\x57"), EN_RM}, /* XORPS xmm, m128 */ +) +DEFINSTR2(Xshl, + {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=4}, /* SHL r32/64, 1 */ + {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=4}, /* SHL r32/64, imm */ + {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=4}, /* SHL r32/64, CL */ +) +DEFINSTR2(Xsar, + {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=7}, /* SAR r32/64, 1 */ + {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=7}, /* SAR r32/64, imm */ + {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=7}, /* SAR r32/64, CL */ +) +DEFINSTR2(Xrolw, + {-1, PGPR, PI8, O("\x66\xC1"), EN_RI8}, /* ROL r16, imm */ +) +DEFINSTR2(Xshr, + {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=5}, /* SHR r32/64, 1 */ + {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=5}, /* SHR r32/64, imm */ + {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=5}, /* SHR r32/64, CL */ +) +DEFINSTR2(Xcvtss2sd, + {-1, PFPR, PFPR, O("\xF3\x0F\x5A"), EN_RR}, /* CVTSS2SD xmm, xmm */ + {-1, PFPR, PMEM, O("\xF3\x0F\x5A"), EN_RM}, /* CVTSS2SD xmm, m32/64 */ +) +DEFINSTR2(Xcvtsd2ss, + {-1, PFPR, PFPR, O("\xF2\x0F\x5A"), EN_RR}, /* CVTSD2SS xmm, xmm */ + {-1, PFPR, PMEM, O("\xF2\x0F\x5A"), EN_RM}, /* CVTSD2SS xmm, m32/64 */ +) +DEFINSTR2(Xcvtsi2ss, + {-1, PFPR, PGPR, O("\xF3\x0F\x2A"), EN_RR}, /* CVTSI2SS xmm, r32/64 */ + {-1, PFPR, PMEM, O("\xF3\x0F\x2A"), EN_RM}, /* CVTSI2SS xmm, m32/64 */ +) +DEFINSTR2(Xcvtsi2sd, + {-1, PFPR, PGPR, O("\xF2\x0F\x2A"), EN_RR}, /* CVTSI2SD xmm, r32/64 */ + {-1, PFPR, PMEM, O("\xF2\x0F\x2A"), EN_RM}, /* CVTSI2SD xmm, m32/64 */ +) +DEFINSTR2(Xcvttss2si, + {-1, PGPR, PFPR, O("\xF3\x0F\x2C"), EN_RR}, /* CVTTSS2SI r32/64, xmm */ + {-1, PGPR, PMEM, O("\xF3\x0F\x2C"), EN_RM}, /* CVTTSS2SI r32/64, m32 */ +) +DEFINSTR2(Xcvttsd2si, + {-1, PGPR, PFPR, O("\xF2\x0F\x2C"), EN_RR}, /* CVTTSD2SI r32/64, xmm */ + {-1, PGPR, PMEM, O("\xF2\x0F\x2C"), EN_RM}, /* CVTTSD2SI r32/64, m32 */ +) +DEFINSTR1(Xneg, + {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=3} /* NEG r32/64 */ +) +DEFINSTR1(Xnot, + {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=2} /* NOT r32/64 */ +) +DEFINSTR1(Xidiv, + {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=7}, /* IDIV r32/64 */ + {4|8, PMEM, 0, O("\xF7"), EN_M, .ext=7}, /* IDIV m32/64 */ +) +DEFINSTR1(Xdiv, + {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=6}, /* DIV r32/64 */ + {4|8, PMEM, 0, O("\xF7"), EN_M, .ext=6}, /* DIV m32/64 */ +) +DEFINSTR1(Xbswap, + {4|8, PGPR, 0, O("\x0F\xC8"), EN_O}, /* BSWAP r32/64 */ +) +DEFINSTR1(Xcall, + {-1, PSYM, 0, O("\xE8"), EN_R32, .norexw=1}, /* CALL rel32 */ + {-1, PGPR, 0, O("\xFF"), EN_R, .ext=2, .norexw=1}, /* CALL r64 */ + {-1, PMEM, 0, O("\xFF"), EN_M, .ext=2, .norexw=1}, /* CALL m64 */ +) +DEFINSTR2(Xcmp, + {4|8, PGPR, PGPR, O("\x3B"), EN_RR}, /* CMP r32/64, r32/64 */ + {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=7}, /* CMP r32/64, imm8 */ + {4|8, PRAX, PI32, O("\x3D"), EN_I32}, /* CMP eax/rax, imm */ + {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=7}, /* CMP r32/64, imm */ + { 8, PGPR, PMEM, O("\x3B"), EN_RM}, /* CMP r64, m64 */ + {4 , PFPR, PFPR, O("\x0F\x2E"), EN_RR}, /* UCOMISS xmm, xmm */ + {4 , PFPR, PMEM, O("\x0F\x2E"), EN_RM}, /* UCOMISS xmm, m32 */ + { 8, PFPR, PFPR, O("\x66\x0F\x2E"), EN_RR}, /* UCOMISD xmm, xmm */ + { 8, PFPR, PMEM, O("\x66\x0F\x2E"), EN_RM}, /* UCOMISD xmm, m64 */ +) +DEFINSTR2(Xtest, + {4|8, PRAX, PI8, O("\xA8"), EN_I8, .norexw=1}, /* TEST AL, imm8 */ + {4, PRAX, PI32, O("\xA9"), EN_I32}, /* TEST EAX, imm32 */ + { 8, PRAX, PU32, O("\xA9"), EN_I32, .norexw=1}, /* TEST EAX, imm32 */ + { 8, PRAX, PI32, O("\xA9"), EN_I32}, /* TEST RAX, imm32 */ + {4|8, PGPR, PI8, O("\xF6"), EN_RI8, .r8=1,.norexw=1}, /* TEST r8, imm8 */ + {4|8, PGPR, PI32, O("\xF7"), EN_RI32, .ext=0}, /* TEST r32/64, imm32 */ + {4|8, PGPR, PGPR, O("\x85"), EN_RR}, /* TEST r32/64, r32/64 */ + {4|8, PGPR, PMEM, O("\x85"), EN_RM}, /* TEST r32/64, m32/64 */ +) + +DEFINSTR2(Ximul2, + {4|8, PGPR, PGPR, O("\x0F\xAF"), EN_RR}, /* IMUL r32/64, r32/64 */ + {4|8, PGPR, PMEM, O("\x0F\xAF"), EN_RM}, /* IMUL r32/64, m32/64 */ +) +static const struct desc imul3_imm8tab[] = { + {4|8, PGPR, PGPR, O("\x6B"), EN_RR}, /* IMUL r32/64, r32/64, (imm8) */ + {4|8, PGPR, PMEM, O("\x6B"), EN_RM}, /* IMUL r32/64, m32/64, (imm8) */ +}, imul3_imm32tab[] = { + {4|8, PGPR, PGPR, O("\x69"), EN_RR}, /* IMUL r32/64, r32/64, (imm32) */ + {4|8, PGPR, PMEM, O("\x69"), EN_RM}, /* IMUL r32/64, m32/64, (imm32) */ +}; +#undef O +static void +Ximul(uchar **pcode, enum irclass k, struct oper dst, struct oper s1, struct oper s2) +{ + if (!memcmp(&dst, &s1, sizeof dst) && s2.t != OIMM) { + Ximul2(pcode, k, dst, s2); + return; + } + assert(s2.t == OIMM); + if (-128 <= s2.imm && s2.imm < 128) { + encode(pcode, imul3_imm8tab, countof(imul3_imm8tab), k, dst, s1); + B(s2.imm); + } else { + encode(pcode, imul3_imm32tab, countof(imul3_imm32tab), k, dst, s1); + I32(s2.imm); + } +} + +enum cc { + CCO = 0x0, /* OF = 1*/ + CCNO = 0x1, /* OF = 0*/ + CCB = 0x2, CCC = 0x2, CCNAE = 0x2, /* below; CF = 1; not above or equal */ + CCAE = 0x3, CCNB = 0x3, CCNC = 0x3, /* above or equal; not below; CF = 0 */ + CCE = 0x4, CCZ = 0x4, /* equal; ZF = 1 */ + CCNE = 0x5, CCNZ = 0x5, /* not equal; ZF = 0 */ + CCBE = 0x6, CCNA = 0x6, /* below or equal; not above; CF=1 or ZF=1 */ + CCA = 0x7, CCNBE = 0x7, /* above; not below or equal; CF=0 and ZF=0 */ + CCS = 0x8, /* ZS = 1; negative */ + CCNS = 0x9, /* ZS = 0; non-negative */ + CCP = 0xA, CCPE = 0xA, /* PF = 1; parity even */ + CCNP = 0xB, CCPO = 0xB, /* PF = 0; parity odd */ + CCL = 0xC, CCNGE = 0xC, /* lower; not greater or equal; SF != OF */ + CCGE = 0xD, CCNL = 0xD, /* greater or equal; not lower; SF == OF */ + CCLE = 0xE, CCNG = 0xE, /* less or equal; not greater; ZF=1 or SF != OF */ + CCG = 0xF, CCNLE = 0xF, /* greater; not less or equal; ZF=0 and SF = OF*/ + ALWAYS, +}; + +/* maps blk -> address when resolved; or to linked list of jump displacement + * relocations */ +static struct blkaddr { + bool resolved; + union { + uint addr; + uint relreloc; + }; +} *blkaddr; + +static void +Xjcc(uchar **pcode, enum cc cc, struct block *dst) +{ + int disp, insaddr = *pcode - objout.textbegin; + bool rel8 = 0; + + if (blkaddr[dst->id].resolved) { + disp = blkaddr[dst->id].addr - (insaddr + 2); + if ((uint)(disp + 128) < 256) /* can use 1-byte displacement? */ + rel8 = 1; + else { /* otherwise 4-byte displacement */ + disp -= 3; + disp -= cc != ALWAYS; /* 'Jcc rel32' has 2 opcode bytes */ + } + } else { + disp = blkaddr[dst->id].relreloc; + blkaddr[dst->id].relreloc = insaddr + 1 + (cc != ALWAYS); + } + if (cc == ALWAYS) { + B(rel8 ? 0xEB : 0xE9); /* JMP rel8/rel32 */ + } else { + assert(in_range(cc, 0, 0xF)); + if (rel8) B(0x70 + cc); /* Jcc rel8 */ + else B(0x0F), B(0x80 + cc); /* Jcc rel32 */ + } + if (rel8) B(disp); else I32(disp); +} + +static void +Xsetcc(uchar **pcode, enum cc cc, enum reg reg) +{ + int rex = 0; + assert(in_range(cc, 0x0, 0xF)); + assert(in_range(reg, RAX, R15)); + + if (in_range(reg, RSP, RDI)) rex = 0x40; + rex |= (reg >> 3); /* REX.B */ + if (rex) B(rex | 0x40); + B(0x0F), B(0x90+cc); /* SETcc */ + B(0xC0 + (reg & 7)); /* ModR/M with mod=11, rm=reg */ +} + +static void +Xpush(uchar **pcode, enum reg reg) +{ + if (in_range(reg, RAX, R15)) { + if (reg >> 3) B(0x41); /* REX.B */ + B(0x50 + (reg & 7)); /* PUSH reg */ + } else { + assert(in_range(reg, XMM0, XMM15)); + DS("\x48\x8d\x64\x24\xF8"); /* LEA RSP, [RSP-8] */ + Xmov(pcode, KF64, mkoper(OMEM, .base = RSP, .index = NOINDEX), reg2oper(reg)); /* MOVD [rsp],xmm0 */ + } +} + +static void +Xpop(uchar **pcode, enum reg reg) +{ + if (in_range(reg, RAX, R15)) { + if (reg >> 3) B(0x41); /* REX.B */ + B(0x58 + (reg & 7)); /* POP reg */ + } else { + assert(in_range(reg, XMM0, XMM15)); + Xmov(pcode, KF64, reg2oper(reg), mkoper(OMEM, .base = RSP, .index = NOINDEX)); /* MOVD xmm0,[rsp] */ + DS("\x48\x8d\x64\x24\x08"); /* LEA RSP, [RSP+8] */ + } +} + +/* are flags live at given instruction? */ +static bool +flagslivep(struct block *blk, int curi) +{ + int cmpi; + /* conditional branch that references a previous comparison instruction? */ + if (blk->jmp.t != Jb || !blk->jmp.arg[0].bits) + return 0; + assert(blk->jmp.arg[0].t == RTMP); + cmpi = blk->jmp.arg[0].i; + for (int i = blk->ins.n - 1; i > curi; --i) { + if (blk->ins.p[i] == cmpi) + /* flags defined after given instruction, dead here */ + return 0; + } + /* flags defined before given instruction, live here */ + return 1; +} + +/* Copy dst = val, with some peephole optimizations */ +static void +gencopy(uchar **pcode, enum irclass cls, struct block *blk, int curi, struct oper dst, union ref val) +{ + assert(dst.t == OREG); + if (val.bits == UNDREF.bits) { + /* can be generated by ssa construction, since value is undefined no move is needed */ + return; + } + if (val.t == RADDR) { + /* this is a LEA, but maybe it can be lowered to a 2-address instruction, + * which may clobber flags */ + const struct addr *addr = &addrtab.p[val.i]; + if (flagslivep(blk, curi)) goto Lea; + if (addr->base.t != RREG) goto Lea; + if (addr->base.bits && dst.reg == mkregoper(addr->base).reg) { /* base = dst */ + if (addr->index.bits && !addr->disp && !addr->shift){ + /* lea Rx, [Rx + Ry] -> add Rx, Ry */ + Xadd(pcode, cls, dst, mkregoper(addr->index)); + return; + } else if (!addr->index.bits) { + if (!addr->disp) /* lea Rx, [Rx] -> mov Rx, Rx */ + Xmov(pcode, cls, dst, dst); + else /* lea Rx, [Rx + Imm] -> add Rx, Imm */ + Xadd(pcode, cls, dst, mkoper(OIMM, .imm = addr->disp)); + return; + } + } else if (addr->index.bits && dst.reg == mkregoper(addr->index).reg) { /* index = dst */ + if (addr->base.bits && !addr->disp && !addr->shift) { + /* lea Rx, [Ry + Rx] -> add Rx, Ry */ + Xadd(pcode, cls, dst, mkregoper(addr->base)); + return; + } else if (!addr->base.bits) { + if (!addr->disp && !addr->shift) /* lea Rx, [Rx] -> mov Rx, Rx */ + Xmov(pcode, cls, dst, dst); + else if (!addr->shift) /* lea Rx, [Rx + Imm] -> add Rx, Imm */ + Xadd(pcode, cls, dst, mkoper(OIMM, .imm = addr->disp)); + else if (!addr->disp) /* lea Rx, [Rx LSL s] -> shl Rx, s */ + Xshl(pcode, cls, dst, mkoper(OIMM, .imm = addr->shift)); + else + goto Lea; + return; + } + } + /* normal (not 2-address) case */ + Lea: + if (isaddrcon(addr->base,0) && (ccopt.pic || (contab.p[addr->base.i].flag & SFUNC)) + && !(contab.p[addr->base.i].flag & SLOCAL)) { + assert(!addr->disp && !addr->index.bits); + val = addr->base; + goto GOTLoad; + } + Xlea(pcode, cls, dst, ref2oper(val)); + } else if (val.bits == ZEROREF.bits && dst.t == OREG && (kisflt(cls) || !flagslivep(blk, curi))) { + /* dst = 0 -> xor dst, dst; but only if it is ok to clobber flags */ + Xxor(pcode, kisint(cls) ? KI32 : cls, dst, dst); + } else if (isaddrcon(val,0)) { + if ((ccopt.pic || (contab.p[val.i].flag & SFUNC)) && (contab.p[val.i].flag & (SLOCAL|SFUNC)) != (SLOCAL|SFUNC)) { + GOTLoad: + /* for mov reg, [rip(sym@GOTPCREL)] */ + Xmov(pcode, cls, dst, mkoper(OSYMGOT, .con = val.i, .cindex = NOINDEX)); + } else { + /* for lea reg, [rip(sym)] */ + Xlea(pcode, cls, dst, mkoper(OSYM, .con = val.i, .cindex = NOINDEX)); + } + } else if (val.t == RXCON && in_range(concls(val), KI64, KPTR)) { + /* movabs */ + assert(dst.t == OREG && in_range(dst.reg, RAX, R15)); + B(0x48 | (dst.reg >> 3)); /* REX.W (+ REX.B) */ + B(0xB8 + (dst.reg & 0x7)); /* MOVABS r64, */ + wr64le(*pcode, intconval(val)); /* imm64 */ + *pcode += 8; + } else { + struct oper src = mkimmdatregoper(val); + if (memcmp(&dst, &src, sizeof dst) != 0) + Xmov(pcode, cls == KF64 && src.t == OREG && src.reg < XMM0 ? KI64 : cls, dst, src); + } +} + +static void +Xvaprologue(uchar **pcode, struct function *fn, struct oper sav) +{ + uint gpr0 = 0, fpr0 = 0, jmpaddr; + for (int i = 0; i < fn->nabiarg; ++i) { + struct abiarg abi = fn->abiarg[i]; + if (!abi.isstk) { + if (abi.reg < XMM0) ++gpr0; + else ++fpr0; + } + } + assert(sav.t == OMEM && sav.base == RBP); + /* save GPRS */ + for (int r = 0; r < 6; ++r) { + static const char reg[] = {RDI,RSI,RDX,RCX,R8,R9}; + if (r >= gpr0) + Xmov(pcode, KI64, sav, reg2oper(reg[r])); + sav.disp += 8; + } + + /* save FPRs, but only if al is non zero */ + if (fpr0 < 8) { + DS("\x84\xC0"); /* TEST al,al */ + jmpaddr = *pcode - objout.textbegin; + DS("\x74\xFE"); /* JE rel8 */ + } + for (int r = 0; r < 8; ++r) { + if (r >= fpr0) + Xmovaps(pcode, KF64, sav, reg2oper(XMM0 + r)); + sav.disp += 16; + } + if (fpr0 < 8) {/* patch relative jump */ + int off = (*pcode - objout.textbegin) - jmpaddr - 2; + objout.textbegin[jmpaddr+1] = off; + } +} + +/* condition code for CMP */ +static const uchar icmpop2cc[] = { + [Oequ] = CCE, [Oneq] = CCNE, + [Olth] = CCL, [Ogth] = CCG, [Olte] = CCLE, [Ogte] = CCGE, + [Oulth] = CCB, [Ougth] = CCA, [Oulte] = CCBE, [Ougte] = CCAE, + [Oand] = CCNE, [Osub] = CCNE, +}, fcmpop2cc[] = { + [Oequ] = CCE, [Oneq] = CCNE, + [Olth] = CCB, [Ogth] = CCA, [Olte] = CCBE, [Ogte] = CCAE, +}; +/* condition code for TEST reg,reg (compare with zero) */ +static const uchar icmpzero2cc[] = { + [Oequ] = CCE, [Oulte] = CCE, + [Oneq] = CCNE, [Ougth] = CCNE, + [Olth] = CCS, [Ogte] = CCNS, + [Olte] = CCLE, [Ogth] = CCG, + [Oulth] = CCB, [Ougte] = CCAE, /* actually constants */ +}; + +static void +emitinstr(uchar **pcode, struct function *fn, struct block *blk, int curi, struct instr *ins) +{ + struct oper dst, src; + bool regzeroed; + enum irclass cls = ins->cls; + void (*X)(uchar **, enum irclass, struct oper, struct oper) = NULL; + void (*X1)(uchar **, enum irclass, struct oper) = NULL; + + switch (ins->op) { + default: + fatal(NULL, "x86_64: in %y; unimplemented instr '%s'", fn->name, opnames[ins->op]); + case Onop: break; + case Omove: + dst = ref2oper(ins->l); + gencopy(pcode, cls, blk, curi, dst, ins->r); + break; + case Ocopy: + dst = reg2oper(ins->reg-1); + gencopy(pcode, cls, blk, curi, dst, ins->l); + break; + case Ostorei8: cls = KI32, X = Xmovb; goto Store; + case Ostorei16: cls = KI32, X = Xmovw; goto Store; + case Ostorei32: cls = KI32, X = Xmov; goto Store; + case Ostorei64: cls = KI64, X = Xmov; goto Store; + case Ostoref32: cls = KF32, X = Xmov; goto Store; + case Ostoref64: cls = KF64, X = Xmov; goto Store; + Store: + src = mkimmregoper(ins->r); + X(pcode, cls, mkmemoper(ins->l), src); + break; + case Oexts8: src = mkregoper(ins->l); goto Movsxb; + case Oextu8: src = mkregoper(ins->l); goto Movzxb; + case Oexts16: src = mkregoper(ins->l); goto Movsxw; + case Oextu16: src = mkregoper(ins->l); goto Movzxw; + case Oexts32: src = mkregoper(ins->l); goto Movsxl; + case Oextu32: src = mkregoper(ins->l); goto Movzxl; + case Oloads8: src = mkmemoper(ins->l); Movsxb: Xmovsxb(pcode, cls, reg2oper(ins->reg-1), src); break; + case Oloadu8: src = mkmemoper(ins->l); Movzxb: Xmovzxb(pcode, cls, reg2oper(ins->reg-1), src); break; + case Oloads16: src = mkmemoper(ins->l); Movsxw: Xmovsxw(pcode, cls, reg2oper(ins->reg-1), src); break; + case Oloadu16: src = mkmemoper(ins->l); Movzxw: Xmovzxw(pcode, cls, reg2oper(ins->reg-1), src); break; + case Oloads32: src = mkmemoper(ins->l); Movsxl: Xmovsxl(pcode, cls, reg2oper(ins->reg-1), src); break; + case Oloadu32: src = mkmemoper(ins->l); Movzxl: Xmov(pcode, KI32, reg2oper(ins->reg-1), src); break; + case Oloadf32: case Oloadf64: Xmov(pcode, cls, reg2oper(ins->reg-1), mkmemoper(ins->l)); break; + case Oloadi64: Xmov(pcode, KI64, reg2oper(ins->reg-1), mkmemoper(ins->l)); break; + case Ocvtf32f64: X = Xcvtss2sd; goto FloatsCvt; + case Ocvtf64f32: X = Xcvtsd2ss; goto FloatsCvt; + case Ocvtf32s: X = Xcvttss2si; goto FloatsCvt; + case Ocvtf64s: X = Xcvttsd2si; goto FloatsCvt; + case Ocvts32f: X = cls == KF32 ? Xcvtsi2ss : Xcvtsi2sd; cls = KI32; goto FloatsCvt; + case Ocvts64f: X = cls == KF32 ? Xcvtsi2ss : Xcvtsi2sd; cls = KI64; goto FloatsCvt; + FloatsCvt: + X(pcode, cls, reg2oper(ins->reg-1), mkdatregoper(ins->l)); + break; + case Oadd: + dst = mkregoper(ins->l); + if (kisflt(cls)) { + Xaddf(pcode, cls, dst, mkimmdatregoper(ins->r)); + } else if (ins->reg-1 == dst.reg) { /* two-address add */ + src = ref2oper(ins->r); + if (src.t == OIMM && src.imm < 0) /* ADD -imm -> SUB imm, for niceness */ + Xsub(pcode, cls, dst, (src.imm = -(uint)src.imm, src)); + else + Xadd(pcode, cls, dst, src); + } else if (isregref(ins->r) && ins->reg-1 == mkregoper(ins->r).reg) { + /* also two-address after swapping operands */ + Xadd(pcode, cls, reg2oper(ins->reg-1), mkimmdatregoper(ins->l)); + } else { /* three-address add (lea) */ + struct oper mem = { OMEM, .base = NOBASE, .index = NOINDEX }; + dst = reg2oper(ins->reg-1); + addmemoper(&mem, ref2oper(ins->l)); + addmemoper(&mem, ref2oper(ins->r)); + Xlea(pcode, cls, dst, mem); + } + break; + case Osub: + dst = mkregoper(ins->l); + if (kisflt(cls)) { + Xsubf(pcode, cls, dst, mkimmdatregoper(ins->r)); + } else if (!ins->reg) { + Xcmp(pcode, cls, mkregoper(ins->l), mkimmdatregoper(ins->r)); + } else if (ins->reg-1 == dst.reg) { /* two-address */ + Xsub(pcode, cls, dst, ref2oper(ins->r)); + } else { + assert(isintcon(ins->r)); + Xlea(pcode, cls, reg2oper(ins->reg-1), + mkoper(OMEM, .base = mkregoper(ins->l).reg, .index = NOINDEX, .disp = -intconval(ins->r))); + } + break; + case Oshl: + dst = reg2oper(ins->reg-1); + src = mkregoper(ins->l); + if (dst.reg == src.reg) + Xshl(pcode, cls, dst, mkimmdatregoper(ins->r)); + else { + uint sh = ins->r.i; + assert(ins->r.t == RICON && sh <= 3); + if (sh == 1) /* shl x, 1 -> lea [x + x] */ + Xlea(pcode, cls, dst, mkoper(OMEM, .base = src.reg, .index = src.reg)); + else /* shl x, n -> lea [x*(1<<n)+0x0] */ + Xlea(pcode, cls, dst, mkoper(OMEM, .base = NOBASE, .index = src.reg, .shift = sh)); + } + break; + case Osar: X = Xsar; goto ALU2; + case Oslr: X = Xshr; goto ALU2; + case Oand: + if (!ins->reg) { + Xtest(pcode, cls, mkregoper(ins->l), mkimmdatregoper(ins->r)); + break; + } + X = Xand; + goto ALU2; + case Oxor: X = Xxor; goto ALU2; + case Oior: X = Xior; goto ALU2; + ALU2: + dst = mkregoper(ins->l); + assert(ins->reg-1 == dst.reg); + X(pcode, cls, dst, mkimmdatregoper(ins->r)); + break; + case Oneg: X1 = Xneg; goto ALU1; + case Onot: X1 = Xnot; goto ALU1; + ALU1: + dst = mkregoper(ins->l); + assert(ins->reg-1 == dst.reg); + X1(pcode, cls, dst); + break; + case Obswap16: + dst = mkregoper(ins->l); + assert(ins->reg-1 == dst.reg); + if (dst.reg < 4) { /* AX,BX,CX,DX */ + /* XCHG rH, rL */ + B(0x86), B(0xC4 | dst.reg | (dst.reg)<<3); + } else { + /* ROL r16,8 */ + Xrolw(pcode, KI32, dst, mkoper(OIMM, .imm = 8)); + } + break; + case Obswap32: case Obswap64: X1 = Xbswap; goto ALU1; + case Omul: + if (kisint(cls)) + Ximul(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r)); + else + Xmulf(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->r)); + break; + case Odiv: + switch (cls) { + default: assert(0); + case KPTR: + case KI64: B(0x48); /* REX.W */ + case KI32: B(0x99); /* CDQ/CQO */ + assert(mkregoper(ins->l).reg == RAX); + Xidiv(pcode, cls, mkdatregoper(ins->r)); + break; + case KF32: case KF64: + Xdivf(pcode, cls, reg2oper(ins->reg-1), mkdatregoper(ins->r)); + break; + } + break; + case Oudiv: + DS("\x31\xD2"); /* XOR EDX,EDX */ + assert(mkregoper(ins->l).reg == RAX); + Xdiv(pcode, cls, mkdatregoper(ins->r)); + break; + case Oequ: case Oneq: + case Olth: case Ogth: case Olte: case Ogte: + case Oulth: case Ougth: case Oulte: case Ougte: + dst = mkregoper(ins->l); + src = ref2oper(ins->r); + regzeroed = 0; + if (ins->reg && dst.reg != ins->reg-1 && (src.t != OREG || src.reg != ins->reg-1)) { + /* can zero output reg before test instruction (differs from both inputs) */ + /* XXX this doesn't check if a source operand is an addr containing the register */ + struct oper dst = reg2oper(ins->reg-1); + Xxor(pcode, KI32, dst, dst); + regzeroed = 1; + } + if (kisint(ins->cls) && ins->r.bits == ZEROREF.bits) + Xtest(pcode, cls, dst, dst); + else + Xcmp(pcode, cls, dst, src); + if (ins->reg) { + enum cc cc; + dst = reg2oper(ins->reg-1); + if (ins->r.bits != ZEROREF.bits) { /* CMP */ + cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op]; + } else { /* TEST r,r (CMP r, 0) */ + assert(kisint(ins->cls)); + cc = icmpzero2cc[ins->op]; + } + if (kisflt(ins->cls)) { /* handle float unordered result */ + int unordres = ins->op == Oneq ? 1 : 0; + int rex = 0; + if (in_range(dst.reg, RSP, RDI)) rex = 0x40; + rex |= (dst.reg >> 3); /* REX.B */ + int jpoff = 3 + (rex != 0); + if (regzeroed && unordres == 0) { + /* if cmp unordered, just jump over the SETcc; result reg was already zeroed */ + B(0x7A), B(jpoff); /* JP <off> */ + } else { + /* JNP .a + * MOV r8, 0/1 + * JMP .b + * .a: SETcc r8 + * .b: MOVZX r, r8 + */ + B(0x7B), B(jpoff+1); /* JNP <off> */ + if (rex) B(rex | 0x40); + B(0xB0 + (dst.reg & 7)), B(unordres); /* MOV r8, 0/1 */ + B(0xEB), B(jpoff); /* JMP <off> */ + } + } + Xsetcc(pcode, cc, dst.reg); + if (!regzeroed) + Xmovzxb(pcode, KI32, dst, dst); + } + break; + case Oswap: + if (kisint(cls)) + Xxchg(pcode, cls, ref2oper(ins->l), mkregoper(ins->r)); + else { + struct oper l = mkregoper(ins->l), r = mkregoper(ins->r); + Xxor(pcode, cls, l, r); + Xxor(pcode, cls, r, l); + Xxor(pcode, cls, l, r); + } + break; + case Ocall: + Xcall(pcode, KPTR, ref2oper(ins->l)); + break; + case Oxvaprologue: + Xvaprologue(pcode, fn, mkmemoper(ins->l)); + break; + } +} + +static void +emitbranch(uchar **pcode, struct block *blk) +{ + enum cc cc = ALWAYS; + assert(blk->s1); + if (blk->s2) { + /* conditional branch.. */ + union ref arg = blk->jmp.arg[0]; + struct block *unord = NULL; + assert(arg.t == RTMP); + struct instr *ins = &instrtab[arg.i]; + if ((oiscmp(ins->op) || ins->op == Oand || ins->op == Osub)) { + if (ins->r.bits != ZEROREF.bits) { + /* for CMP instr */ + cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op]; + unord = ins->op == Oneq ? blk->s1 : blk->s2; + } else { + assert(kisint(ins->cls)); + /* for TEST instr, which modifies ZF and SF and sets CF = OF = 0 */ + cc = icmpzero2cc[ins->op]; + } + } else { + /* implicit by ZF */ + cc = CCNZ; + } + if (kisflt(ins->cls)) { + /* handle float unordered result */ + Xjcc(pcode, CCP, unord); + } + if (blk->s1 == blk->lnext) { + /* if s1 is next adjacent block, swap s1,s2 and flip condition to emit a + * single jump */ + struct block *tmp = blk->s1; + blk->s1 = blk->s2; + blk->s2 = tmp; + cc ^= 1; + } + } + /* make sure to fallthru if jumping to next adjacent block */ + if (blk->s2 || blk->s1 != blk->lnext) + Xjcc(pcode, cc, blk->s1); + if (blk->s2 && blk->s2 != blk->lnext) + Xjcc(pcode, ALWAYS, blk->s2); +} + +static bool +calleesave(int *npush, uchar **pcode, struct function *fn) +{ + bool any = 0; + if (rstest(fn->regusage, RBX)) { + Xpush(pcode, RBX); + ++*npush; + any = 1; + } + for (int r = R12; r <= R15; ++r) + if (rstest(fn->regusage, r)) { + Xpush(pcode, r); + ++*npush; + any = 1; + } + return any; +} + +static void +calleerestore(uchar **pcode, struct function *fn) +{ + for (int r = R15; r >= R12; --r) + if (rstest(fn->regusage, r)) + Xpop(pcode, r); + if (rstest(fn->regusage, RBX)) Xpop(pcode, RBX); +} + +/* align code using NOPs */ +static void +nops(uchar **pcode, int align) +{ + int rem; + while ((rem = (*pcode - objout.textbegin) & (align - 1)) != 0) { + switch (align - rem) { + case 15: case 14: case 13: case 12: case 11: case 10: + case 9: B(0x66); + case 8: DS("\x0f\x1f\x84\x00\x00\x00\x00\x00"); break; + case 7: DS("\x0f\x1f\x80\x00\x00\x00\x00"); break; + case 6: B(0x66); + case 5: DS("\x0f\x1f\x44\x00\x00"); break; + case 4: DS("\x0f\x1f\x40\x00"); break; + case 3: DS("\x0f\x1f\00"); break; + case 2: B(0x66); + case 1: B(0x90); break; + } + } +} + +static void +emitbin(struct function *fn) +{ + struct block *blk; + uchar **pcode = &objout.code; + int npush = 0; + bool saverestore; + + nops(pcode, 16); + fnstart = *pcode; + curfnsym = fn->name; + + /** prologue **/ + + /* only use frame pointer in non-leaf functions and functions that use the stack */ + usebp = 0; + if (!fn->isleaf || fn->stksiz) { + usebp = 1; + /* push rbp; mov rbp, rsp */ + DS("\x55\x48\x89\xE5"); + } + saverestore = calleesave(&npush, pcode, fn); + if (usebp) rbpoff = -npush*8; + + /* ensure stack is 16-byte aligned for function calls */ + if (!fn->isleaf && ((fn->stksiz + npush*8) & 0xF) != 0) { + assert(usebp); + if ((rbpoff & 0xF) == 0) { + rbpoff -= 16; + fn->stksiz += 24; + } else { + rbpoff -= 8; + fn->stksiz += 8; + } + } + + if (fn->stksiz != 0) { + /* sub rsp, <stack size> */ + if (fn->stksiz < 128) + DS("\x48\x83\xEC"), B(fn->stksiz); + else if (fn->stksiz == 128) + DS("\x48\x83\xC4\x80"); /* add rsp, -128 */ + else + DS("\x48\x81\xEC"), I32(fn->stksiz); + } + + if (*pcode - fnstart > 6) { + /* largue prologue -> largue epilogue -> transform to use single exit point */ + struct block *exit = NULL; + blk = fn->entry->lprev; + do { + if (blk->jmp.t == Jret) { + if (!exit) { + if (blk->ins.n == 0) { + exit = blk; + continue; + } else { + exit = newblk(fn); + exit->lnext = blk->lnext; + exit->lprev = blk; + blk->lnext = exit; + exit->lnext->lprev = exit; + exit->id = fn->nblk++; + exit->jmp.t = Jret; + } + } + blk->jmp.t = Jb; + memset(blk->jmp.arg, 0, sizeof blk->jmp.arg); + blk->s1 = exit; + } else if (exit) { + /* thread jumps to the exit block */ + if (blk->s1 && !blk->s1->ins.n && blk->s1->s1 == exit && !blk->s1->s2) blk->s1 = exit; + if (blk->s2 && !blk->s2->ins.n && blk->s2->s1 == exit && !blk->s2->s2) blk->s2 = exit; + } + } while ((blk = blk->lprev) != fn->entry); + } + + blkaddr = allocz(fn->passarena, fn->nblk * sizeof *blkaddr, 0); + + blk = fn->entry; + do { + struct blkaddr *bb = &blkaddr[blk->id]; + uint bbaddr = *pcode - objout.textbegin; + assert(!bb->resolved); + while (bb->relreloc) { + uint next; + memcpy(&next, objout.textbegin + bb->relreloc, 4); + int disp = bbaddr - bb->relreloc - 4; + wr32le(objout.textbegin + bb->relreloc, disp); + bb->relreloc = next; + } + bb->resolved = 1; + bb->addr = bbaddr; + + for (int i = 0; i < blk->ins.n; ++i) + emitinstr(pcode, fn, blk, i, &instrtab[blk->ins.p[i]]); + + if (blk->jmp.t == Jret) { + if (blk->lnext != fn->entry && blk->lnext->jmp.t == Jret && blk->lnext->ins.n == 0) + continue; /* fallthru to next blk's RET */ + /* epilogue */ + if (fn->stksiz && (saverestore || !usebp)) + Xadd(pcode, KPTR, mkoper(OREG, .reg = RSP), mkoper(OIMM, .imm = fn->stksiz)); + if (saverestore) + calleerestore(pcode, fn); + if (usebp) B(0xC9); /* leave */ + B(0xC3); /* ret */ + } else if (blk->jmp.t == Jtrap) { + DS("\x0F\x0B"); /* UD2 */ + } else emitbranch(pcode, blk); + } while ((blk = blk->lnext) != fn->entry); + objdeffunc(fn->name, fn->globl, fnstart - objout.textbegin, *pcode - fnstart); +} + +void +x86_64_emit(struct function *fn) +{ + fn->stksiz = alignup(fn->stksiz, 8); + if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name); + emitbin(fn); +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_x86-64_isel.c b/src/t_x86-64_isel.c new file mode 100644 index 0000000..4b4a099 --- /dev/null +++ b/src/t_x86-64_isel.c @@ -0,0 +1,652 @@ +#include "all.h" +#include "../endian.h" + +enum flag { + ZF = 1 << 0, + SF = 1 << 1, + CF = 1 << 2, + OF = 1 << 3, + CLOBF = 1 << 4, +}; + +/* flags modified by each integer op */ +static const uchar opflags[NOPER] = { + [Oneg] = ZF|CLOBF, + [Oadd] = ZF|CLOBF, + [Osub] = ZF|CLOBF, + [Omul] = CLOBF, + [Odiv] = CLOBF, + [Oudiv] = CLOBF, + [Orem] = CLOBF, + [Ourem] = CLOBF, + [Oand] = ZF|CLOBF, + [Oior] = ZF|CLOBF, + [Oxor] = ZF|CLOBF, + [Oshl] = ZF|CLOBF, + [Osar] = ZF|CLOBF, + [Oslr] = ZF|CLOBF, + [Oequ] = ZF|CLOBF, + [Oneq] = ZF|CLOBF, + [Olth] = ZF|CLOBF, + [Ogth] = ZF|CLOBF, + [Olte] = ZF|CLOBF, + [Ogte] = ZF|CLOBF, + [Oulth] = ZF|CLOBF, + [Ougth] = ZF|CLOBF, + [Oulte] = ZF|CLOBF, + [Ougte] = ZF|CLOBF, + [Ocall] = CLOBF, +}; + +static int iflagsrc = -1; + +#define inscopy(blk, pcuri, k, r) insertinstr((blk), (*(pcuri))++, mkinstr(Ocopy, k, .l = (r))) +static void +picfixsym(union ref *r, struct block *blk, int *curi) +{ + if (!ccopt.pic || !isaddrcon(*r,0)) return; + *r = inscopy(blk, curi, KPTR, *r); +} + +static void +fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi) +{ + int sh; + enum op op = ins ? ins->op : 0; + enum irclass cls = ins ? ins->cls : 0; + +Begin: + if (r->t == RXCON) { + struct xcon *con = &contab.p[r->i]; + if (in_range(op, Oshl, Oslr) && r == &ins->r) { + sh = con->i; + goto ShiftImm; + } else if (cls == KI32 && in_range(con->cls, KI64, KPTR)) { + *r = mkintcon(KI32, (int)con->i); + goto Begin; + } + if (in_range(op, Oadd, Osub) && con->i == 2147483648 && r == &ins->r) { + /* add X, INT32MAX+1 -> sub X, INT32MIN */ + ins->op = Oadd + (op == Oadd); + *r = mkintcon(KI32, -2147483648); + } else if (kisflt(con->cls) && con->i == 0) { + /* copy of positive float zero -> regular zero, that emit() will turn into xor x,x */ + if (in_range(op, Ocopy, Omove) || op == Ophi) + *r = ZEROREF; + else + *r = inscopy(blk, curi, con->cls, ZEROREF); + } else if (con->cls >= KI64) { + /* float immediates & 64bit immediates are loaded from memory */ + uchar data[8]; + uint ksiz = cls2siz[con->cls]; + union type ctype; + /* can't use memory arg in rhs if lhs is memory */ + bool docopy = ins && &ins->l != r && (oisstore(ins->op) || ins->l.t == RADDR); + if (con->cls <= KPTR && (in_range(op, Ocopy, Omove) || op == Ophi)) + /* in this case we can use movabs */ + return; + else if (!docopy || con->cls >= KF32) { + if (con->cls != KF32) { + wr64le(data, con->i); + ctype = mktype(con->cls == KF64 ? TYDOUBLE : TYVLONG); + } else { + union { float f; int i; } pun = { con->f }; + wr32le(data, pun.i); + ctype = mktype(TYFLOAT); + } + *r = mkdatref(NULL, ctype, ksiz, /*align*/ksiz, data, ksiz, /*deref*/1 , /*funclocal*/1); + } + if (docopy) + *r = inscopy(blk, curi, con->cls, *r); + } else if (op != Omove && ins && isaddrcon(*r,0) && r == &ins->r) { + *r = inscopy(blk, curi, KPTR, *r); + } else if (in_range(op, Odiv, Ourem) && kisint(ins->cls)) + goto DivImm; + } else if (r->t == RICON && in_range(op, Odiv, Ourem) && kisint(ins->cls) && r == &ins->r) { + DivImm: /* there is no division by immediate, must be copied to a register */ + *r = inscopy(blk, curi, ins->cls, *r); + } else if (r->t == RICON && in_range(op, Oshl, Oslr) && r == &ins->r) { + sh = r->i; + ShiftImm: /* shift immediate is always 8bit */ + *r = mkref(RICON, sh & 255); + } else if (r->t == RSTACK) { + struct instr adr = mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkintcon(KI32, -r->i)); + if (op == Ocopy) + *ins = adr; + else + *r = insertinstr(blk, (*curi)++, adr); + } else if (r->bits == UNDREF.bits && ins && !in_range(op, Ocopy, Omove) && op != Ophi) { + *r = inscopy(blk, curi, ins->cls, *r); + } + picfixsym(r, blk, curi); +} + +#define isimm32(r) (iscon(r) && concls(r) == KI32) + +static void +selcall(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + const struct call *call = &calltab.p[ins->r.i]; + int iarg = *curi - 1; + enum irclass cls; + uint argstksiz = alignup(call->argstksiz, 16); + int nsse = 0; + + for (int i = call->narg - 1; i >= 0; --i) { + struct abiarg abi = call->abiarg[i]; + struct instr *arg; + for (;; --iarg) { + assert(iarg >= 0 && i >= 0 && "arg?"); + if ((arg = &instrtab[blk->ins.p[iarg]])->op == Oarg) + break; + } + + if (!abi.isstk) { + assert(!abi.ty.isagg); + *arg = mkinstr(Omove, call->abiarg[i].ty.cls, mkref(RREG, abi.reg), arg->r); + if (abi.reg >= XMM0) ++nsse; + } else { + union ref adr = mkaddr((struct addr){mkref(RREG, RSP), .disp = abi.stk}); + int iargsave = iarg; + if (!abi.ty.isagg) { /* scalar arg in stack */ + *arg = mkinstr(cls2store[abi.ty.cls], 0, adr, arg->r); + if (isaddrcon(arg->r,1) || arg->r.t == RADDR) + arg->r = insertinstr(blk, iarg++, mkinstr(Ocopy, abi.ty.cls, arg->r)); + else + fixarg(&ins->r, ins, blk, &iarg); + } else { /* aggregate arg in stack, callee stack frame destination address */ + *arg = mkinstr(Ocopy, KPTR, adr); + } + *curi += iarg - iargsave; + } + } + if (call->argstksiz) { + union ref disp = mkref(RICON, argstksiz); + insertinstr(blk, iarg--, (struct instr){Osub, KPTR, .keep=1, .reg = RSP+1, .l=mkref(RREG,RSP), disp}); + ++*curi; + insertinstr(blk, *curi+1, (struct instr){Oadd, KPTR, .keep=1, .reg = RSP+1, .l=mkref(RREG,RSP), disp}); + } + if (isimm32(ins->l)) + ins->l = mkaddr((struct addr){.base = ins->l}); + else if (isintcon(ins->l)) + ins->l = inscopy(blk, curi, KPTR, ins->l); + + if (call->vararg >= 0) { + /* variadic calls write number of sse regs used to AL */ + insertinstr(blk, (*curi)++, mkinstr(Omove, KI32, mkref(RREG, RAX), mkref(RICON, nsse), .keep=1)); + } + cls = ins->cls; + ins->cls = 0; + if (cls) { + /* duplicate to reuse same TMP ref */ + insertinstr(blk, (*curi)++, *ins); + *ins = mkinstr(Ocopy, cls, mkref(RREG, call->abiret[0].reg)); + for (int i = 1; i <= 2; ++i) { + if (*curi + i >= blk->ins.n) break; + if (instrtab[blk->ins.p[*curi + i]].op == Ocall2r) { + ins = &instrtab[blk->ins.p[*curi += i]]; + *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, call->abiret[1].reg)); + break; + } + } + } +} + +static bool +aimm(struct addr *addr, vlong disp) +{ + vlong a = addr->disp; + a += disp; + if ((int)a == a) { + addr->disp = a; + return 1; + } + return 0; +} + +static bool +ascale(struct addr *addr, union ref a, union ref b) +{ + if (b.t != RICON) return 0; + if (addr->index.bits) return 0; + if ((unsigned)b.i > 3) return 0; + if (a.t == RREG) { + Scaled: + addr->index = a; + addr->shift = b.i; + return 1; + } else if (a.t == RTMP) { + struct instr *ins = &instrtab[a.i]; + /* factor out shifted immediate from 'shl {add %x, imm}, s' */ + /* XXX maybe we shouldn't do this here because it should be done by a generic + * arithemetic optimization pass ? */ + if (ins->op == Oadd && (ins->l.t == RREG || ins->l.t == RTMP) && isintcon(ins->r)) { + vlong a = ((vlong) addr->disp + intconval(ins->r)) * (1 << b.i); + if (a != (int) a) return 0; + addr->disp = a; + addr->index = ins->l; + addr->shift = b.i; + return 1; + } else { + goto Scaled; + } + } + return 0; +} + +static bool +aadd(struct addr *out, struct block *blk, int *curi, union ref r, bool recurring) +{ + if (r.t == RSTACK) { + if (out->base.bits || !aimm(out, -r.i)) { + r = insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, -r.i))); + goto Ref; + } + out->base = mkref(RREG, RBP); + } else if (r.t == RTMP) { + struct instr *ins = &instrtab[r.i]; + struct addr adr = {0}; + if (ins->op == Oadd) { + if (recurring) goto Ref; + if (aadd(&adr, blk, curi, ins->l, 1) && aadd(&adr, blk, curi, ins->r, 1)) { + Add2:; + int n1 = !!out->base.bits + !!out->index.bits; + int n2 = !!adr.base.bits + !!adr.index.bits; + vlong off = (vlong) out->disp + adr.disp; + if (n1+n2 > 2 || (int)off != off) goto Ref; + if (n1 == 0) { + *out = adr; + } else if (n1 == 1 && n2 == 1) { + if (!out->index.bits) { + out->index = adr.index.bits ? adr.index : adr.base; + out->shift = adr.index.bits ? adr.shift : 0; + } else { + if (adr.index.bits && adr.shift) return 0; + out->base = adr.index.bits ? adr.index : adr.base; + } + } else assert(n1 <= 2 && n2 == 0); + out->disp = off; + ins->skip = 1; + } else return 0; + } else if (ins->op == Ocopy && ins->l.t == RADDR) { + const struct addr *adr2 = &addrtab.p[ins->l.i]; + adr = *adr2; + goto Add2; + } else if (ins->op == Oshl) { + if (!ascale(out, ins->l, ins->r)) goto Ref; + ins->skip = 1; + } else goto Ref; + } else if (isnumcon(r)) { + assert(isintcon(r)); + return aimm(out, intconval(r)); + } else if (isaddrcon(r,1)) { + if (!out->base.bits && !isaddrcon(out->index,1)) out->base = r; + else return 0; + } else if (r.t == RREG) { + /* temporaries are single assignment, but register aren't, so they can't be * + * safely hoisted into an address value, unless they have global lifetime */ + if (!rstest(mctarg->rglob, r.i)) return 0; + Ref: + if (!out->base.bits) out->base = r; + else if (!out->index.bits) out->index = r; + else return 0; + } else return 0; + return 1; +} + +static bool +fuseaddr(union ref *r, struct block *blk, int *curi) +{ + struct addr addr = { 0 }; + + if (isaddrcon(*r,1)) return 1; + if (!aadd(&addr, blk, curi, *r, 0)) return 0; + + if (isaddrcon(addr.base,0) && (ccopt.pic || (ccopt.pie && addr.index.bits) || (contab.p[addr.base.i].flag & SFUNC))) { + /* pic needs to load from GOT */ + /* pie cannot encode RIP-relative address with index register */ + /* first load symbol address into a temp register */ + union ref temp = mkaddr((struct addr){.base = addr.base, .disp = ccopt.pic ? 0 : addr.disp}); + addr.base = inscopy(blk, curi, KPTR, temp); + if (!ccopt.pic) addr.disp = 0; + } + + if (!addr.base.bits) { + /* absolute int address in disp */ + if (addr.index.bits) return 0; + addr.base = mkintcon(KPTR, addr.disp); + addr.disp = 0; + } + + *r = mkaddr(addr); + return 1; +} + +/* is add instruction with this arg a candidate to transform into efective addr? */ +static bool +addarg4addrp(union ref r) +{ + struct instr *ins; + if (isaddrcon(r, 0)) return 1; + if (r.t == RSTACK) return 1; + if (r.t != RTMP) return 0; + ins = &instrtab[r.i]; + return (ins->op == Ocopy && ins->l.t == RADDR) || ins->op == Oadd || ins->op == Oshl; +} + +static void +loadstoreaddr(struct block *blk, union ref *r, int *curi) +{ + if (isimm32(*r)) { + *r = mkaddr((struct addr){.base = *r}); + } else if (isaddrcon(*r, 0)) { + picfixsym(r, blk, curi); + } else if (r->t == RSTACK || (r->t == RTMP && addarg4addrp(*r))) { + fuseaddr(r, blk, curi); + } else if (r->t != RREG) { + *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, *r)); + } +} + +static bool +arithfold(struct instr *ins) +{ + if (isnumcon(ins->l) && (!ins->r.t || isnumcon(ins->r))) { + union ref r; + bool ok = ins->r.t ? foldbinop(&r, ins->op, ins->cls, ins->l, ins->r) : foldunop(&r, ins->op, ins->cls, ins->l); + if (ok) { + *ins = mkinstr(Ocopy, insrescls(*ins), r); + return 1; + } + } + return 0; +} + +static void +sel(struct function *fn, struct instr *ins, struct block *blk, int *curi) +{ + int t = ins - instrtab; + struct instr temp = {0}; + enum op op = ins->op; + + if (oisarith(ins->op) && arithfold(ins)) { + fixarg(&ins->l, ins, blk, curi); + return; + } + + switch (op) { + default: assert(0); + case Onop: break; + case Oalloca1: case Oalloca2: case Oalloca4: case Oalloca8: case Oalloca16: + assert(!"unlowered alloca"); + break; + case Oparam: + assert(ins->l.t == RICON && ins->l.i < fn->nabiarg); + if (!fn->abiarg[ins->l.i].isstk) + *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, fn->abiarg[ins->l.i].reg)); + else /* stack */ + *ins = mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, 16+fn->abiarg[ins->l.i].stk)); + break; + case Oarg: + fixarg(&ins->r, ins, blk, curi); + break; + case Ocall: + selcall(fn, ins, blk, curi); + break; + case Ointrin: + break; + case Oshl: case Osar: case Oslr: + if (!isintcon(ins->r)) { + /* shift amount register is always CL */ + insertinstr(blk, (*curi)++, mkinstr(Omove, KI32, mkref(RREG, RCX), ins->r)); + ins->r = mkref(RREG, RCX); + } + goto ALU; + case Oequ: case Oneq: + case Olth: case Ogth: case Olte: case Ogte: + case Oulth: case Ougth: case Oulte: case Ougte: + if (iscon(ins->l) && !iscon(ins->r)) { + /* lth imm, x -> gth x, imm */ + if (!in_range(ins->op, Oequ, Oneq)) + ins->op = ((op - Olth) ^ 1) + Olth; + rswap(ins->l, ins->r); + } + if (ins->l.t != RTMP && ins->l.t != RREG && ins->l.t != RSTACK) + ins->l = inscopy(blk, curi, ins->cls, ins->l); + else + fixarg(&ins->l, ins, blk, curi); + fixarg(&ins->r, ins, blk, curi); + break; + case Odiv: case Oudiv: case Orem: case Ourem: + if (kisflt(ins->cls)) goto ALU; + /* TODO fuse div/rem pair */ + + /* (I)DIV dividend is always in RDX:RAX, output also in those regs */ + insertinstr(blk, (*curi)++, mkinstr(Omove, ins->cls, mkref(RREG, RAX), ins->l)); + /* mark RDX as clobbered. sign/zero-extending RAX into RDX is handled in emit() */ + insertinstr(blk, (*curi)++, mkinstr(Omove, ins->cls, mkref(RREG, RDX), mkref(RREG, RDX))); + fixarg(&ins->r, ins, blk, curi); /* make sure rhs is memory or reg */ + ins->l = mkref(RREG, RAX); + ins->keep = 1; + if (op == Orem) ins->op = Odiv; + else if (op == Ourem) ins->op = Oudiv; + insertinstr(blk, (*curi)++, *ins); /* duplicate ins to reuse tmp ref */ + *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, op < Orem ? RAX : RDX)); /* get output */ + temp = mkinstr(Ocopy, ins->cls, mkref(RREG, op < Orem ? RDX : RAX)); /* clobber other reg*/ + insertinstr(blk, ++(*curi), temp); + /* swap instrs so that clobber goes first */ + t = blk->ins.p[*curi - 1]; + blk->ins.p[*curi - 1] = blk->ins.p[*curi - 0]; + blk->ins.p[*curi - 0] = t; + break; + case Osub: + if (isintcon(ins->l)) { + /* sub imm, x -> sub x, imm; neg x */ + fixarg(&ins->l, ins, blk, curi); + ins->inplace = 1; + struct instr sub = *ins; + rswap(sub.l, sub.r); + ins->op = op = Oneg; + ins->l = insertinstr(blk, (*curi)++, sub); + ins->r = NOREF; + goto ALU; + } else if (kisint(ins->cls) && isintcon(ins->r)) { + ins->op = op = Oadd; + ins->r = mkintcon(concls(ins->r), -(uvlong)intconval(ins->r)); + } else { + goto ALU; + } + /* fallthru */ + case Oadd: + if (kisint(ins->cls) && (addarg4addrp(ins->l) || addarg4addrp(ins->r))) { + union ref it = mkref(RTMP, ins - instrtab); + if (fuseaddr(&it, blk, curi)) { + *ins = mkinstr(Ocopy, ins->cls, it); + break; + } + } + /* fallthru */ + case Omul: + case Oand: case Oxor: case Oior: + /* commutative ops */ + if (iscon(ins->l)) + rswap(ins->l, ins->r); + goto ALU; + case Oneg: + if (kisflt(ins->cls)) { + /* flip sign bit with XORPS/D */ + static const uvlong sd[2] = {0x8000000000000000,0x8000000000000000}; + static const uint sf[4] = {0x80000000,80000000,0x80000000,80000000}; + ins->op = Oxor; + ins->r = mkdatref(NULL, mktype(ins->cls == KF32 ? TYFLOAT : TYDOUBLE), /*siz*/16, + /*align*/16, ins->cls == KF32 ? (void *)sf : sd, /*siz*/16, /*deref*/1, /*funclocal*/1); + } + /* fallthru */ + case Onot: + ALU: + if (!(op == Oadd && kisint(ins->cls))) /* 3-address add is lea */ + if (!(op == Omul && kisint(ins->cls) && isimm32(ins->r))) /* for (I)MUL r,r/m,imm */ + if (!(op == Oshl && ins->r.t == RICON && ins->r.i <= 3)) /* can be lea */ + ins->inplace = 1; + if (iscon(ins->l) || ins->l.t == RSTACK) { + fixarg(&ins->l, ins, blk, curi); + ins->l = insertinstr(blk, (*curi)++, mkinstr(Ocopy, ins->cls, ins->l)); + } + if (ins->r.bits) + case Omove: + fixarg(&ins->r, ins, blk, curi); + if (op == Oadd && isaddrcon(ins->r,1)) /* no 3-address add if rhs is mem */ + ins->inplace = 1; + break; + case Oloads8: case Oloadu8: case Oloads16: case Oloadu16: + case Oloads32: case Oloadu32: case Oloadi64: case Oloadf32: case Oloadf64: + loadstoreaddr(blk, &ins->l, curi); + break; + case Ostorei8: case Ostorei16: case Ostorei32: case Ostorei64: case Ostoref32: case Ostoref64: + loadstoreaddr(blk, &ins->l, curi); + if (isaddrcon(ins->r,1) || ins->r.t == RADDR) + ins->r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, ins->r)); + else + fixarg(&ins->r, ins, blk, curi); + break; + case Ocvtu32f: + fixarg(&ins->l, ins, blk, curi); + ins->l = insertinstr(blk, (*curi)++, mkinstr(Oextu32, KI64, ins->l)); + ins->op = Ocvts64f; + break; + case Ocvtf32u: case Ocvtf64u: + fixarg(&ins->l, ins, blk, curi); + if (ins->cls == KI32) { + ins->l = insertinstr(blk, (*curi)++, mkinstr(ins->op == Ocvtf32u ? Ocvtf32s : Ocvtf64s, KI64, ins->l)); + ins->op = Oextu32; + } else assert(!"nyi flt -> u64"); + break; + case Oextu32: + if (ins->l.t == RTMP && insrescls(instrtab[ins->l.i]) == KI32 && instrtab[ins->l.i].op != Ocopy) { + /* no need to explicitly zero extend 32 -> 64bit regs in x86-64 */ + /* this copy can be optimized away in regalloc */ + ins->op = op = Ocopy; + ins->cls = KI32; + } + /* fallthru */ + case Ocvtf32f64: case Ocvtf64f32: case Ocvtf32s: case Ocvtf64s: case Ocvts32f: case Ocvts64f: + case Ocvtu64f: + case Oexts8: case Oextu8: case Oexts16: case Oextu16: case Oexts32: + if (isnumcon(ins->l)) { + union ref it; + bool ok = foldunop(&it, ins->op, ins->cls, ins->l); + assert(ok); + ins->op = Ocopy; + ins->l = it; + break; + } + case Ocopy: + fixarg(&ins->l, ins, blk, curi); + break; + case Obswap16: case Obswap32: case Obswap64: + ins->inplace = 1; + if (ins->l.t != RTMP) { + ins->l = insertinstr(blk, *curi, mkinstr(Ocopy, ins->cls, ins->l)); + fixarg(&instrtab[ins->l.i].l, ins, blk, curi); + ++*curi; + } + break; + case Oxvaprologue: + fuseaddr(&ins->l, blk, curi); + assert(ins->l.t == RADDR); + /* !this must be the first instruction */ + assert(*curi == 1); + assert(blk == fn->entry); + t = blk->ins.p[0]; + blk->ins.p[0] = blk->ins.p[1]; + blk->ins.p[1] = t; + break; + } +} + +static void +seljmp(struct function *fn, struct block *blk) +{ + if (blk->jmp.t == Jb && blk->jmp.arg[0].bits) { + int curi = blk->ins.n; + fixarg(&blk->jmp.arg[0], NULL, blk, &curi); + union ref c = blk->jmp.arg[0]; + if (c.t != RTMP) { + enum irclass cls = c.t == RICON ? KI32 : c.t == RXCON && contab.p[c.i].cls ? contab.p[c.i].cls : KPTR; + int curi = blk->ins.n; + + c = insertinstr(blk, blk->ins.n, mkinstr(Ocopy, cls, c)); + sel(fn, &instrtab[c.i], blk, &curi); + } + if (iflagsrc == c.i /* test cmp */ + && (oiscmp(instrtab[c.i].op) || instrtab[c.i].op == Oand || instrtab[c.i].op == Osub)) { + instrtab[c.i].keep = 1; + } else { + if (kisflt(instrtab[c.i].cls) || !(opflags[instrtab[c.i].op] & ZF) || blk->ins.n == 0 || c.i != blk->ins.p[blk->ins.n - 1]) { + struct instr *ins; + int curi = blk->ins.n; + blk->jmp.arg[0] = insertinstr(blk, blk->ins.n, mkinstr(Oneq, insrescls(instrtab[c.i]), c, ZEROREF)); + ins = &instrtab[blk->jmp.arg[0].i]; + if (kisflt(ins->cls)) { + ins->r = insertinstr(blk, curi, mkinstr(Ocopy, ins->cls, ZEROREF)); + } + ins->keep = 1; + } else if (instrtab[c.i].op == Oadd) { + /* prevent a 3-address add whose flag results are used from becoming a LEA */ + instrtab[c.i].inplace = 1; + } + } + } else if (blk->jmp.t == Jret) { + if (blk->jmp.arg[0].bits) { + int curi; + union ref r = mkref(RREG, fn->abiret[0].reg); + struct instr *ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[0].ty.cls, r, blk->jmp.arg[0])).i]; + curi = blk->ins.n-1; + fixarg(&ins->r, ins, blk, &curi); + blk->jmp.arg[0] = r; + if (blk->jmp.arg[1].bits) { + r = mkref(RREG, fn->abiret[1].reg); + ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[1].ty.cls, r, blk->jmp.arg[1])).i]; + curi = blk->ins.n-1; + fixarg(&ins->r, ins, blk, &curi); + blk->jmp.arg[1] = r; + } + } + } +} + +void +x86_64_isel(struct function *fn) +{ + struct block *blk = fn->entry; + + do { + for (int i = 0; i < blk->phi.n; ++i) { + struct instr *ins = &instrtab[blk->phi.p[i]]; + union ref *phi = phitab.p[ins->l.i]; + for (int i = 0; i < blk->npred; ++i) { + int curi = blkpred(blk, i)->ins.n; + fixarg(&phi[i], ins, blkpred(blk, i), &curi); + } + } + iflagsrc = -1; + for (int i = 0; i < blk->ins.n; ++i) { + struct instr *ins = &instrtab[blk->ins.p[i]]; + sel(fn, ins, blk, &i); + if (ins->op < countof(opflags) && kisint(insrescls(*ins))) { + if (opflags[ins->op] & ZF) iflagsrc = ins - instrtab; + else if (opflags[ins->op] & CLOBF) iflagsrc = -1; + } + } + seljmp(fn, blk); + } while ((blk = blk->lnext) != fn->entry); + + if (ccopt.dbg.i) { + bfmt(ccopt.dbgout, "<< After isel >>\n"); + irdump(fn); + } + + fn->prop = 0; +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/t_x86-64_sysv.c b/src/t_x86-64_sysv.c new file mode 100644 index 0000000..317f40f --- /dev/null +++ b/src/t_x86-64_sysv.c @@ -0,0 +1,310 @@ +#include "all.h" + +static int classify(uchar cls[2], const struct typedata *td, uint off); + +static void +clsscalar(uchar cls[2], uint off, union type ty) +{ + enum irclass k = type2cls[scalartypet(ty)]; + uchar *fcls = &cls[off/8]; + if (isflt(ty)) { /* SSE */ + if (!*fcls || (*fcls == KF32 && k > *fcls)) + *fcls = k; + } else { /* INTEGER */ + assert(isint(ty) || ty.t == TYPTR); + if (cls2siz[*fcls] < cls2siz[k]) + *fcls = k == KPTR ? KI64 : k; + } + if (off % 8 >= 4 && cls2siz[*fcls] < 8) + *fcls = kisint(*fcls) ? KI64 : KF64; +} + +static int +classifyarr(uchar cls[2], union type ty, uint off) +{ + union type chld = typechild(ty); + uint n = typearrlen(ty), siz = typesize(chld); + assert(n > 0); + for (uint i = 0; i < n; ++i) { + uint offx = off + i * siz; + if (isagg(chld)) { + if (!classify(cls, &typedata[chld.dat], offx)) + return cls[0] = cls[1] = 0; + } else if (chld.t == TYARRAY) { + if (!classifyarr(cls, chld, offx)) + return cls[0] = cls[1] = 0; + } else { + clsscalar(cls, offx, chld); + } + } + return !!cls[0] + !!cls[1]; +} + +static int +classify(uchar cls[2], const struct typedata *td, uint off) +{ + uint siz = alignup(td->siz, 4); + if (siz > 16) /* MEMORY */ + return 0; + for (int i = 0; i < td->nmemb; ++i) { + struct fielddata *fld = &td->fld[i].f; + uint align = typealign(fld->t); + if (alignup(fld->off, align) != fld->off) /* unaligned field -> MEMORY */ + return cls[0] = cls[1] = 0; + if (isagg(fld->t)) { + if (!classify(cls, &typedata[fld->t.dat], off + fld->off)) + return cls[0] = cls[1] = 0; + } else if (fld->t.t == TYARRAY) { + if (isincomplete(fld->t)) continue; + if (!classifyarr(cls, fld->t, off + fld->off)) + return cls[0] = cls[1] = 0; + } else { + clsscalar(cls, fld->off + off, fld->t); + } + } + return !!cls[0] + !!cls[1]; +} + +static int +abiarg(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype typ) +{ + static const uchar intregs[] = { RDI, RSI, RDX, RCX, R8, R9 }; + enum { NINT = countof(intregs), NFLT = 8 }; + + if (!typ.isagg) { + if (kisflt(cls[0] = typ.cls) && *nf < NFLT) { + r[0] = XMM0 + (*nf)++; + } else if (kisint(cls[0]) && *ni < NINT) { + r[0] = intregs[(*ni)++]; + } else { + r[0] = *ns; + *ns += 8; + return 0; /* MEMORY */ + } + return 1; + } + cls[0] = cls[1] = 0; + int ret = classify(cls, &typedata[typ.dat], 0); + if (!ret) { /*MEMORY*/ + r[0] = *ns; + *ns = alignup(*ns + typedata[typ.dat].siz, 8); + return 0; + } + assert(ret <= 2); + int ni_save = *ni, nf_save = *nf; + *r2off = 8; + for (int i = 0; i < ret; ++i) { + assert(cls[i]); + if (kisflt(cls[i]) && *nf < NFLT) + r[i] = XMM0 + (*nf)++; + else if (kisint(cls[i]) && *ni < NINT) + r[i] = intregs[(*ni)++]; + else { /* MEMORY */ + *ni = ni_save, *nf = nf_save; + r[0] = *ns; + *ns = alignup(*ns + typedata[typ.dat].siz, 8); + r[1] = -1; + return cls[0] = cls[1] = 0; + } + } + return ret; +} + +static int +abiret(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype typ) +{ + if (!typ.isagg) { + r[0] = kisflt(cls[0] = typ.cls) ? XMM0 : RAX; + return 1; + } + + cls[0] = cls[1] = 0; + int ret = classify(cls, &typedata[typ.dat], 0); + if (!ret) { /* MEMORY */ + assert(*ni == 0); + r[0] = RAX; /* on return should contain result location address */ + r[1] = RDI; /* register for caller-owned result location argument */ + ++*ni; + return 0; + } + assert(ret <= 2); + *r2off = 8; + for (int i = 0, ni = 0, nf = 0; i < ret; ++i) { + assert(cls[i]); + if (kisflt(cls[i])) /* SSE (XMM0, XMM1) */ + r[i] = XMM0 + nf++; + else if (kisint(cls[i])) /* INTEGER (RAX, RDX) */ + r[i] = ni++ == 0 ? RAX : RDX; + else assert(0); + } + return ret; +} + +/* Layout of va_list: + * struct { + * ( 0) unsigned int gp_offset; + * ( 4) unsigned int fp_offset; + * ( 8) void *overflow_arg_area; + * (16) void *reg_save_area; + * } + * Layout of register save area (align 16): + * reg off + * rdi 0 + * rsi 8 + * rdx 16 + * rcx 24 + * r8 32 + * r9 40 + * xmm0 48 + * xmm1 64 + * ... + * in x86_64/emit xvaprologue generates the code to save the registers to a stack slot + * there only needs to be one xvaprologue if there's any vastart instrs, and it has to be + * at the beginning of the function (before IR generated by regalloc can touch any registers) + * then vastart can initialize va_list.reg_save_area with a pointer to that + */ + +static void +vastart(struct function *fn, struct block *blk, int *curi) +{ + union ref rsave; /* register save area */ + int gpr0 = 0, fpr0 = 0, stk0 = 0; + struct instr *ins = &instrtab[blk->ins.p[*curi]]; + union ref ap = ins->l, src, dst; + assert(ins->op == Ovastart); + /* add xvaprologue if not there yet, which must be the first + * real instruction in the function (following alloca) */ + if (fn->entry->ins.n > 1 && instrtab[fn->entry->ins.p[1]].op == Oxvaprologue) { + rsave = mkref(RTMP, fn->entry->ins.p[0]); /* alloca instruction */ + assert(instrtab[rsave.i].op == Oalloca16); + } else { + rsave = insertinstr(fn->entry, 0, mkalloca(192, 16)); + insertinstr(fn->entry, 1, mkinstr(Oxvaprologue, 0, rsave, .keep=1)); + } + /* find first unnamed gpr and fpr */ + for (int i = 0; i < fn->nabiarg; ++i) { + struct abiarg abi = fn->abiarg[i]; + if (!abi.isstk){ + if (abi.reg < XMM0) ++gpr0; + else ++fpr0; + } else { + stk0 = abi.stk+8; + } + } + /* set ap->reg_save_area */ + *ins = mkinstr(Oadd, KPTR, ap, mkref(RICON, 16)); + dst = mkref(RTMP, ins - instrtab); + int i = *curi + 1; + insertinstr(blk, i++, mkinstr(Ostorei64, 0, dst, rsave)); + /* set ap->overflow_arg_area */ + src = insertinstr(blk, i++, mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, 16+stk0))); + dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 8))); + insertinstr(blk, i++, mkinstr(Ostorei64, 0, dst, src)); + /* set ap->gp_offset */ + insertinstr(blk, i++, mkinstr(Ostorei32, 0, ap, mkref(RICON, gpr0*8))); + /* set ap->fp_offset */ + dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4))); + insertinstr(blk, i++, mkinstr(Ostorei32, 0, dst, mkref(RICON, 6*8 + fpr0*16))); + *curi = i-1; +} + +static void +vaarg(struct function *fn, struct block *blk, int *curi) +{ + short r[2]; + uchar cls[2]; + union ref tmp; + int ni = 0, nf = 0, ns = 0; + uchar r2off; + int var = blk->ins.p[*curi]; + union ref ap = instrtab[var].l; + union irtype ty = ref2type(instrtab[var].r); + + assert(instrtab[var].op == Ovaarg); + blk->ins.p[*curi] = newinstr(blk, (struct instr){Onop}); + + int ret = abiarg(r, cls, &r2off, &ni, &nf, &ns, ty); + + if (ret == 2) assert(!"nyi"); + else if (ret == 1) { + struct block *merge; + union ref phi, phiargs[2]; + /* int: l->gp_offset < 48 - num_gp * 8 */ + /* sse: l->fp_offset < 304 - num_gp * 16 (why 304? ... 176) */ + tmp = ni ? ap : insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4))); + tmp = insertinstr(blk, (*curi)++, mkinstr(Oloadu32, KI32, tmp)); + tmp = insertinstr(blk, (*curi)++, mkinstr(Oulte, KI32, tmp, mkref(RICON, ni ? 48 - ni*8 : 176 - nf*16))); + merge = blksplitafter(fn, blk, *curi); + blk->jmp.t = 0; + useblk(fn, blk); + putcondbranch(fn, tmp, newblk(fn), newblk(fn)); + useblk(fn, blk->s1); + { + /* phi0: &l->reg_save_area[l->gp/fp_offset] */ + union ref sav = addinstr(fn, mkinstr(Oloadi64, KPTR, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 16)))); + union ref roff = addinstr(fn, mkinstr(Oloadu32, KI32, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4)))); + phiargs[0] = irbinop(fn, Oadd, KPTR, sav, roff); + /* l->gp/fp_offset += num_gp/fp * 8(16) */ + roff = irbinop(fn, Oadd, KI32, roff, mkref(RICON, ni ? ni * 8 : nf * 16)); + addinstr(fn, mkinstr(Ostorei32, 0, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4)), roff)); + assert(merge->npred == 1); + blkpred(merge, 0) = blk->s1; + blk->s1->jmp.t = Jb; + blk->s1->s1 = merge; + } + useblk(fn, blk->s2); + { + /* phi1: l->overflow_arg_area */ + union ref adr = irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 8)); + union ref ovf = addinstr(fn, mkinstr(Oloadi64, KPTR, adr)); + /* align no-op */ + + phiargs[1] = ovf; + /* update l->overflow_arg_area += size */ + int siz = 8; + addinstr(fn, mkinstr(Ostorei64, 0, adr, irbinop(fn, Oadd, KPTR, ovf, mkref(RICON, siz)))); + putbranch(fn, merge); + } + assert(merge->npred == 2); + vpush(&merge->ins, 0); + memmove(merge->ins.p+1, merge->ins.p, (merge->ins.n-1)*sizeof *merge->ins.p); + merge->ins.p[0] = var; + phi = insertphi(merge, KPTR); + memcpy(phitab.p[instrtab[phi.i].l.i], phiargs, sizeof phiargs); + if (!ty.isagg) { + instrtab[var] = mkinstr(cls2load[cls[0]], cls[0], phi); + } else { + instrtab[var] = mkalloca(8, 8); + tmp = insertinstr(merge, 1, mkinstr(Oloadi64, KI64, phi)); + insertinstr(merge, 2, mkinstr(Ostorei64, 0, mkref(RTMP, var), tmp)); + } + fn->prop &= ~FNUSE; + } else { + assert(!"nyi"); + } +} + +static const char x86_64_rnames[][6] = { +#define R(r) #r, + LIST_REGS(R) +#undef R +}; + +const struct mctarg t_x86_64_sysv = { + .gpr0 = RAX, .ngpr = R15 - RAX + 1, + .bpr = RBP, + .gprscratch = R11, .fprscratch = XMM15, + .fpr0 = XMM0, .nfpr = XMM15 - XMM0 + 1, + .rcallee = 1<<RBX | 1<<R12 | 1<<R13 | 1<<R14 | 1<<R15, + .rglob = 1<<RSP | 1<<RBP, + .rnames = x86_64_rnames, + .objkind = OBJELF, + .abiret = abiret, + .abiarg = abiarg, + .vastart = vastart, + .vaarg = vaarg, + .isel = x86_64_isel, + .emit = x86_64_emit +}; + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/u_endian.h b/src/u_endian.h new file mode 100644 index 0000000..34b7721 --- /dev/null +++ b/src/u_endian.h @@ -0,0 +1,189 @@ +#ifndef ENDIAN_H_ +#define ENDIAN_H_ + +#include "common.h" +extern bool targ_bigendian; + +/*** Macros and functions for endian specific memory access ***/ + +/** byte-swapping functions **/ + +#if HAS_BUILTIN(bswap16) +#define bswap16 __builtin_bswap16 +#else +static inline ushort +bswap16(ushort x) { + return x >> 8 | x << 8; +} +#endif + +#if HAS_BUILTIN(bswap32) +#define bswap32 __builtin_bswap32 +#else +static inline uint +bswap32(uint x) { + return x >> 24 & 0x000000FF | x >> 8 & 0x0000FF00 + | x << 8 & 0x00FF0000 | x << 24 & 0xFF000000; +} +#endif + +#if HAS_BUILTIN(bswap64) +#define bswap64 __builtin_bswap64 +#else +static inline uvlong +bswap64(uvlong x) { + return (uvlong) bswap32(x) << 32 | bswap32(x >> 32); +} +#endif + +#if (defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + defined __hppa__ || defined __m68k__ || defined mc68000 || defined _M_M68K || \ + (defined __MIPS__ && defined __MIPSEB__) || \ + defined __ppc__ || defined __POWERPC__ || defined __powerpc__ || defined __PPC__ || \ + defined __sparc__ +#define HOST_BIG_ENDIAN +#define hostntarg_sameendian() (targ_bigendian) +#else +#define HOST_LIL_ENDIAN +#define hostntarg_sameendian() (!targ_bigendian) +#endif + +/** little-endian memory writes **/ + +static inline void +wr16le(uchar *p, ushort x) +{ +#ifndef HOST_LIL_ENDIAN + x = bswap16(x); +#endif + memcpy(p, &x, sizeof x); +} + +static inline void +wr32le(uchar *p, uint x) +{ +#ifndef HOST_LIL_ENDIAN + x = bswap32(x); +#endif + memcpy(p, &x, sizeof x); +} + +static inline void +wr64le(uchar *p, uvlong x) +{ +#ifndef HOST_LIL_ENDIAN + x = bswap64(x); +#endif + memcpy(p, &x, sizeof x); +} + +/** big-endian memory writes **/ + +static inline void +wr16be(uchar *p, ushort x) +{ +#ifndef HOST_BIG_ENDIAN + x = bswap16(x); +#endif + memcpy(p, &x, sizeof x); +} + +static inline void +wr32be(uchar *p, uint x) +{ +#ifndef HOST_BIG_ENDIAN + x = bswap32(x); +#endif + memcpy(p, &x, sizeof x); +} + +static inline void +wr64be(uchar *p, uvlong x) +{ +#ifndef HOST_BIG_ENDIAN + x = bswap64(x); +#endif + memcpy(p, &x, sizeof x); +} + +/** target-endian memory read/write **/ + +static inline ushort +rd16targ(uchar *p) +{ + ushort x; + memcpy(&x, p, sizeof x); + if (!hostntarg_sameendian()) x = bswap16(x); + return x; +} + +static inline uint +rd32targ(uchar *p) +{ + uint x; + memcpy(&x, p, sizeof x); + if (!hostntarg_sameendian()) x = bswap32(x); + return x; +} + +static inline uvlong +rd64targ(uchar *p) +{ + uvlong x; + memcpy(&x, p, sizeof x); + if (!hostntarg_sameendian()) x = bswap64(x); + return x; +} + +static inline float +rdf32targ(uchar *p) +{ + union { uint i; float f; } u = { rd32targ(p) }; + return u.f; +} + +static inline double +rdf64targ(uchar *p) +{ + union { uvlong i; double f; } u = { rd64targ(p) }; + return u.f; +} + +static inline void +wr16targ(uchar *p, ushort x) +{ + if (!hostntarg_sameendian()) x = bswap16(x); + memcpy(p, &x, sizeof x); +} + +static inline void +wr32targ(uchar *p, uint x) +{ + if (!hostntarg_sameendian()) x = bswap32(x); + memcpy(p, &x, sizeof x); +} + +static inline void +wr64targ(uchar *p, uvlong x) +{ + if (!hostntarg_sameendian()) x = bswap64(x); + memcpy(p, &x, sizeof x); +} + +static inline void +wrf32targ(uchar *p, float x) +{ + union { float f; uint i; } u = { x }; + wr32targ(p, u.i); +} + +static inline void +wrf64targ(uchar *p, double x) +{ + union { double f; uvlong i; } u = { x }; + wr64targ(p, u.i); +} + +#endif /* ENDIAN_H_ */ + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/u_mem.c b/src/u_mem.c new file mode 100644 index 0000000..9ee8864 --- /dev/null +++ b/src/u_mem.c @@ -0,0 +1,390 @@ +#include "common.h" +#include <stdlib.h> +#include <errno.h> +#include <stdint.h> + +static void +allocerr(const char *f) +{ + efmt("%s: %s\n", f, strerror(errno)); + ioflush(&bstdout); + ioflush(&bstderr); + abort(); +} + +void * +(xmalloc)(size_t n, const char *f) +{ + void *p = malloc(n); + if (!p) allocerr(f); + return p; +} + +void * +(xcalloc)(size_t n, const char *f) +{ + void *p = calloc(n, 1); + if (!p) allocerr(f); + return p; +} + +void * +(xrealloc)(void *p, size_t n, const char *f) +{ + p = p ? realloc(p, n) : malloc(n); + if (!p) allocerr(f); + return p; +} + +/** string interning **/ +internstr +intern_(const char *s, uint len) +{ + static uint N, n; + static struct ht { + internstr s; + size_t h; + } *ht; + static struct { char m[sizeof(struct arena) + (2<<10)]; struct arena *_a; } amem; + static struct arena *arena; + + if (!N) { + ht = xcalloc((sizeof *ht) * (N = 1<<10)); + arena = (void *)amem.m, arena->cap = sizeof amem.m - sizeof(struct arena); + } + + for (size_t h = len ? hashb(0, s, len) : hashs(0, s), i = h;;) { + i &= N - 1; + if (!ht[i].s) { /* insert */ + if (n < N/4*3 /*load factor 75%*/) { + ++n; + ht[i].h = h; + ht[i].s = alloccopy(&arena, s, (len ? len : strlen(s))+1, 1); + if (len) ((char *)ht[i].s)[len] = 0; + return ht[i].s; + } + /* resize */ + size_t nnew = N * 2; + struct ht *new = xcalloc(sizeof *new * nnew); + for (uint i = 0; i < N; ++i) { /* rehash */ + if (!ht[i].s) continue; + uint j = ht[i].h; + while (new[j &= nnew-1].s) ++j; + new[j] = ht[i]; + } + free(ht); + ht = new; + N = nnew; + i = h; + continue; + } else if (h == ht[i].h) { + if (!len && !strcmp(s, &ht[i].s->c)) + return ht[i].s; + else if (len && !strncmp(s, &ht[i].s->c, len) && ht[i].s[len].c == 0) + return ht[i].s; + } + ++i; + } +} + +/** vec **/ +void +vinit_(struct vecbase *v, void *inlbuf, uint cap, uint siz) +{ + assert(!v->p); + v->cap = cap; + if (inlbuf) { + v->p = inlbuf; + v->dyn = 0; + } else if (cap) { + v->p = xmalloc(cap*siz); + v->dyn = 1; + } +} + +void +vpush_(struct vecbase *v, uint siz) +{ + if (v->n < v->cap) return; + if (!v->dyn && v->n >= v->cap) { /* empty or inline buffer */ + int cap = v->cap ? v->cap * 2 : 8; + void *old = v->p; + v->p = xmalloc(cap * siz); + if (old) memcpy(v->p, old, v->cap * siz); + v->cap = cap; + v->dyn = 1; + } else if (v->dyn && v->n >= v->cap) { /* dyn buf */ + v->p = xrealloc(v->p, (v->cap *= 2) * siz); + } + while (v->n >= v->cap) + v->p = xrealloc(v->p, (v->cap *= 2) * siz); + assert(v->cap > v->n && "overflow"); +} + +void * +vpushn_(struct vecbase *v, uint siz, const void *dat, uint ndat) +{ + void *beg; + + if (!ndat) return v->p; + v->n += ndat; + while (v->n >= v->cap) + vpush_(v, siz); + beg = (char *)v->p + (v->n - ndat) * siz; + assert(dat); + memcpy(beg, dat, ndat * siz); + return beg; +} + +void +vresize_(struct vecbase *v, uint siz, uint N) +{ + while (v->cap < N) { + vpush_(v, siz); + v->n = v->cap; + } + v->n = N; +} + +/** arena **/ +struct arena * +newarena(uint chunksiz) +{ + struct arena *ar = xmalloc(offsetof(struct arena, mem) + chunksiz); + assert(chunksiz < 1u<<31 && "toobig"); + ar->prev = NULL; + ar->cap = chunksiz; + ar->dyn = 1; + ar->n = 0; + return ar; +} + +void * +alloc(struct arena **par, uint siz, uint align) +{ + uint idx; + struct arena *new; + + if (siz > (*par)->cap) { + new = newarena(siz); + new->n = siz; + new->prev = (*par)->prev; + (*par)->prev = new; + return new->mem; + } + align = align ? align : sizeof(void *); + idx = (uchar *)alignup((uintptr_t)&(*par)->mem[(*par)->n], align) - (*par)->mem; + if ((*par)->cap - idx >= siz) { + (*par)->n = idx + siz; + return (*par)->mem + idx; + } + new = newarena((*par)->cap); + new->prev = *par; + *par = new; + new->n = siz; + return new->mem; +} + +void * +allocz(struct arena **par, uint siz, uint align) +{ + return memset(alloc(par, siz, align), 0, siz); +} + +void +freearena(struct arena **par) +{ + struct arena *prev; + for (; *par; *par = prev) { + prev = (*par)->prev; + if ((*par)->dyn) + free(*par); + else { + if (prev) freearena(&prev); + (*par)->prev = NULL; + (*par)->n = 0; + return; + } + } +} + +/** integer hashmap **/ +void +imap_init_(struct imapbase *m, void **v, uint vsiz, uint N) +{ + uint sizk = N*sizeof*m->k, + sizv = N*vsiz, + sizbs = BSSIZE(N)*sizeof(struct bitset); + + assert(ispo2(N)); + m->k = xcalloc(sizk + sizv + sizbs); + *v = (char *)m->k + sizk; + m->bs = (struct bitset *)((char *)*v + sizv); + m->N = N; +} + +int +imap_get_(struct imapbase *m, short k) +{ + if (!m->N) return -1; + for (int i = k;; ++i) { + bool notempty = bstest(m->bs, i &= m->N - 1); + if (bstest(m->bs, i) && m->k[i] == k) return i; + if (!notempty) return -1; + } +} + +static void +imap_rehash(struct imapbase *m, void **v, uint vsiz) +{ + short *newk, k; + int i, j; + void *newv; + struct bitset *newbs; + uint N2 = m->N ? m->N << 1 : 16, + sizk = N2*sizeof*m->k, + sizv = N2*vsiz, + sizbs = BSSIZE(N2)*sizeof(struct bitset); + + assert(N2); + newk = xcalloc(sizk + sizv + sizbs); + memset(newk, 0, sizk + sizv + sizbs); + newv = (char *)newk + sizk; + newbs = (struct bitset *)((char *)newv + sizv); + for (i = 0; i < m->N; ++i) { + if (!bstest(m->bs, i)) + continue; + j = k = m->k[i]; + for (;; ++j) { + j &= N2 - 1; + if (!bstest(newbs, j)) { + bsset(newbs, j); + newk[j] = k; + memcpy((char *)newv + j*vsiz, (char *)*v + i*vsiz, vsiz); + break; + } + } + } + free(m->k); + m->k = newk; + *v = newv; + m->bs = newbs; + m->N = N2; +} + +int +imap_set_(struct imapbase *m, void **v, uint vsiz, short k) +{ + if (m->n >= m->N/4*3 /*load factor 75*/) { + imap_rehash(m, v, vsiz); + assert(m->n < m->N); + } + for (int i = k;; ++i) { + bool notempty = bstest(m->bs, i &= m->N - 1); + if (notempty && m->k[i] == k) + return i; + if (!notempty) { + m->k[i] = k; + bsset(m->bs, i); + ++m->n; + return i; + } + } +} + +/** pointer hashmap **/ +void +pmap_init_(struct pmapbase *m, void **v, uint vsiz, uint N) +{ + assert(ispo2(N)); + uint sizk = N*sizeof*m->k, + sizv = N*vsiz; + + m->k = xcalloc(sizk + sizv); + *v = (char *)m->k + sizk; + m->N = N; +} + +int +pmap_get_(struct pmapbase *m, const void *k) +{ + assert(k && "null key"); + if (!m->N) return -1; + for (size_t i = ptrhash(k);; ++i) { + i &= m->N - 1; + if (m->k[i] == k) return i; + if (!m->k[i]) return -1; + } +} + +char pmap_tombstone_[1]; + +static void +pmap_rehash(struct pmapbase *m, void **v, uint vsiz) +{ + void **newk; + int i, j; + void *k; + void *newv; + uint N2 = m->N ? m->N << 1 : 16, + sizk = N2*sizeof*m->k, + sizv = N2*vsiz; + + assert(N2); + newk = xcalloc(sizk + sizv); + newv = (char *)newk + sizk; + for (i = 0; i < m->N; ++i) { + if (!m->k[i] || m->k[i] == pmap_tombstone_) + continue; + j = ptrhash(k = m->k[i]); + for (;; ++j) { + j &= N2 - 1; + if (!newk[j]) { + newk[j] = k; + memcpy((char *)newv + j*vsiz, (char *)*v + i*vsiz, vsiz); + break; + } + } + } + free(m->k); + m->k = newk; + *v = newv; + m->N = N2; +} + +int +pmap_set_(struct pmapbase *m, void **v, uint vsiz, const void *k) +{ + assert(k && "null key"); + if (m->n >= m->N/4*3 /*load factor 75%*/) { + pmap_rehash(m, v, vsiz); + assert(m->n < m->N); + } + for (size_t i = ptrhash(k);; ++i) { + i &= m->N - 1; + if (m->k[i] == k) + return i; + if (!m->k[i] || m->k[i] == pmap_tombstone_) { + m->k[i] = (void *)k; + ++m->n; + return i; + } + } +} + +void +pmap_del_(struct pmapbase *m, const void *k) +{ + assert(k && "null key"); + for (size_t i = ptrhash(k);; ++i) { + i &= m->N - 1; + if (m->k[i] == k) { + m->k[i] = pmap_tombstone_; + --m->n; + return; + } else if (!m->k[i]) + return; + } +} + +/* vim:set ts=3 sw=3 expandtab: */ diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..3abd4a1 --- /dev/null +++ b/src/version.h @@ -0,0 +1,13 @@ +#ifndef VERSION_H_ +#define VERSION_H_ + +/* 0.1.10 */ +#define ANTCC_VERSION_MAJOR 0 +#define ANTCC_VERSION_MINOR 1 +#define ANTCC_VERSION_PATCH 10 + +#define XSTR_(...) #__VA_ARGS__ +#define XSTR(...) XSTR_(__VA_ARGS__) +#define ANTCC_VERSION_STR XSTR(ANTCC_VERSION_MAJOR) "." XSTR(ANTCC_VERSION_MINOR) "." XSTR(ANTCC_VERSION_PATCH) + +#endif |