From a8d6f8bf30c07edb775e56889f568ca20240bedf Mon Sep 17 00:00:00 2001 From: lemon Date: Tue, 17 Mar 2026 13:22:00 +0100 Subject: REFACTOR: move sources to src/ --- src/a_common.h | 465 +++++ src/a_main.c | 710 ++++++++ src/a_targ.c | 121 ++ src/c.c | 4772 +++++++++++++++++++++++++++++++++++++++++++++++++ src/c.h | 139 ++ src/c_builtin.c | 177 ++ src/c_embedfilesdir.c | 106 ++ src/c_eval.c | 437 +++++ src/c_keywords.def | 76 + src/c_lex.c | 2496 ++++++++++++++++++++++++++ src/c_lex.h | 126 ++ src/c_type.c | 311 ++++ src/c_type.h | 177 ++ src/io.c | 1255 +++++++++++++ src/ir.c | 689 +++++++ src/ir.h | 353 ++++ src/ir_abi0.c | 462 +++++ src/ir_builder.c | 307 ++++ src/ir_cfg.c | 130 ++ src/ir_cse.c | 92 + src/ir_dump.c | 319 ++++ src/ir_fold.c | 133 ++ src/ir_inliner.c | 309 ++++ src/ir_intrin.c | 77 + src/ir_intrin.def | 2 + src/ir_mem2reg.c | 317 ++++ src/ir_op.def | 79 + src/ir_regalloc.c | 1417 +++++++++++++++ src/ir_simpl.c | 308 ++++ src/ir_ssa.c | 46 + src/ir_stack.c | 33 + src/o_elf.c | 572 ++++++ src/o_elf.h | 206 +++ src/obj.c | 118 ++ src/obj.h | 36 + src/t_aarch64.h | 16 + src/t_aarch64_aapcs.c | 77 + src/t_aarch64_emit.c | 1023 +++++++++++ src/t_aarch64_isel.c | 515 ++++++ src/t_x86-64.h | 18 + src/t_x86-64_emit.c | 1422 +++++++++++++++ src/t_x86-64_isel.c | 652 +++++++ src/t_x86-64_sysv.c | 310 ++++ src/u_endian.h | 189 ++ src/u_mem.c | 390 ++++ src/version.h | 13 + 46 files changed, 21998 insertions(+) create mode 100644 src/a_common.h create mode 100644 src/a_main.c create mode 100644 src/a_targ.c create mode 100644 src/c.c create mode 100644 src/c.h create mode 100644 src/c_builtin.c create mode 100644 src/c_embedfilesdir.c create mode 100644 src/c_eval.c create mode 100644 src/c_keywords.def create mode 100644 src/c_lex.c create mode 100644 src/c_lex.h create mode 100644 src/c_type.c create mode 100644 src/c_type.h create mode 100644 src/io.c create mode 100644 src/ir.c create mode 100644 src/ir.h create mode 100644 src/ir_abi0.c create mode 100644 src/ir_builder.c create mode 100644 src/ir_cfg.c create mode 100644 src/ir_cse.c create mode 100644 src/ir_dump.c create mode 100644 src/ir_fold.c create mode 100644 src/ir_inliner.c create mode 100644 src/ir_intrin.c create mode 100644 src/ir_intrin.def create mode 100644 src/ir_mem2reg.c create mode 100644 src/ir_op.def create mode 100644 src/ir_regalloc.c create mode 100644 src/ir_simpl.c create mode 100644 src/ir_ssa.c create mode 100644 src/ir_stack.c create mode 100644 src/o_elf.c create mode 100644 src/o_elf.h create mode 100644 src/obj.c create mode 100644 src/obj.h create mode 100644 src/t_aarch64.h create mode 100644 src/t_aarch64_aapcs.c create mode 100644 src/t_aarch64_emit.c create mode 100644 src/t_aarch64_isel.c create mode 100644 src/t_x86-64.h create mode 100644 src/t_x86-64_emit.c create mode 100644 src/t_x86-64_isel.c create mode 100644 src/t_x86-64_sysv.c create mode 100644 src/u_endian.h create mode 100644 src/u_mem.c create mode 100644 src/version.h (limited to 'src') 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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +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 + +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 \tPlace the output into \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 \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; +} diff --git a/src/c.c b/src/c.c new file mode 100644 index 0000000..63c3f7f --- /dev/null +++ b/src/c.c @@ -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 ('=' ) and + call pdecl() to advance state before checking .more */ + funcdef, /* caller should parse an func definition ('{' '}'). + 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 : ""; + 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 '(' [ ',' ] ')' ';' */ + 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< 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 ':' */ + 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, ':')) { + /*