aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/a_common.h465
-rw-r--r--src/a_main.c710
-rw-r--r--src/a_targ.c121
-rw-r--r--src/c.c4772
-rw-r--r--src/c.h139
-rw-r--r--src/c_builtin.c177
-rw-r--r--src/c_embedfilesdir.c106
-rw-r--r--src/c_eval.c437
-rw-r--r--src/c_keywords.def76
-rw-r--r--src/c_lex.c2496
-rw-r--r--src/c_lex.h126
-rw-r--r--src/c_type.c311
-rw-r--r--src/c_type.h177
-rw-r--r--src/io.c1255
-rw-r--r--src/ir.c689
-rw-r--r--src/ir.h353
-rw-r--r--src/ir_abi0.c462
-rw-r--r--src/ir_builder.c307
-rw-r--r--src/ir_cfg.c130
-rw-r--r--src/ir_cse.c92
-rw-r--r--src/ir_dump.c319
-rw-r--r--src/ir_fold.c133
-rw-r--r--src/ir_inliner.c309
-rw-r--r--src/ir_intrin.c77
-rw-r--r--src/ir_intrin.def2
-rw-r--r--src/ir_mem2reg.c317
-rw-r--r--src/ir_op.def79
-rw-r--r--src/ir_regalloc.c1417
-rw-r--r--src/ir_simpl.c308
-rw-r--r--src/ir_ssa.c46
-rw-r--r--src/ir_stack.c33
-rw-r--r--src/o_elf.c572
-rw-r--r--src/o_elf.h206
-rw-r--r--src/obj.c118
-rw-r--r--src/obj.h36
-rw-r--r--src/t_aarch64.h16
-rw-r--r--src/t_aarch64_aapcs.c77
-rw-r--r--src/t_aarch64_emit.c1023
-rw-r--r--src/t_aarch64_isel.c515
-rw-r--r--src/t_x86-64.h18
-rw-r--r--src/t_x86-64_emit.c1422
-rw-r--r--src/t_x86-64_isel.c652
-rw-r--r--src/t_x86-64_sysv.c310
-rw-r--r--src/u_endian.h189
-rw-r--r--src/u_mem.c390
-rw-r--r--src/version.h13
46 files changed, 21998 insertions, 0 deletions
diff --git a/src/a_common.h b/src/a_common.h
new file mode 100644
index 0000000..a24aa3d
--- /dev/null
+++ b/src/a_common.h
@@ -0,0 +1,465 @@
+#ifndef COMMON_H_
+#define COMMON_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+
+#ifdef __clang__ /* stop linter from complaining about "unused" inline functions */
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+
+#define bool _Bool
+typedef unsigned char uchar;
+typedef signed char schar;
+typedef unsigned short ushort;
+typedef unsigned long long uvlong;
+typedef signed long long vlong;
+typedef unsigned uint;
+
+#if __STDC_VERSION__ >= 202311L
+#define NORETURN [[noreturn]]
+#elif __STDC_VERSION__ >= 201112L
+#define NORETURN _Noreturn
+#else
+#define NORETURN
+#endif
+
+#ifdef __has_builtin
+#define HAS_BUILTIN(b) __has_builtin(__builtin_##b)
+#else
+#define HAS_BUILTIN(_) 0
+#endif
+
+#define static_assert(x) _Static_assert(x, #x)
+#define in_range(x, Lo, Hi) ((uint) (x) - (Lo) <= (Hi) - (Lo)) /* lo <= x <= hi; lo > 0, hi > 0 */
+#define alignup(x, A) (((x) + ((A) - 1)) & -(A))
+#define countof(a) (sizeof(a) / sizeof 0[a])
+
+enum { SPANFILEBITS = 10 };
+struct span {
+ struct span0 {
+ uint off;
+ uint len : 32-SPANFILEBITS,
+ file : SPANFILEBITS;
+ } sl, /* original source location */
+ ex; /* the location after #include/macro expansion */
+};
+
+void _assertfmt(const char *file, int line, const char *func, const char *expr);
+#if HAS_BUILTIN(trap)
+#define assert(x) (!(x) ? _assertfmt(__FILE__,__LINE__,__func__,#x), __builtin_trap() : (void)0)
+#else
+#define assert(x) (void)(!(x) ? _assertfmt(__FILE__,__LINE__,__func__,#x), *(volatile int *)0 : 0)
+#endif
+
+static inline size_t
+hashs(size_t h, const char *s)
+{
+ while (*s) h = (uchar)*s++ + h*65599;
+ return h;
+}
+static inline size_t
+hashb(size_t h, const void *d, size_t n)
+{
+ const uchar *b = d;
+ while (n--) h = *b++ + h*65599;
+ return h;
+}
+static inline size_t
+ptrhash(const void *p) {
+ return (size_t)p * 2654435761u;
+}
+static inline uint
+popcnt(uvlong x) {
+#if HAS_BUILTIN(popcountll)
+ return x ? __builtin_popcountll(x) : 0;
+#else
+ uint n = 0;
+ while (x) n += x&1, x >>= 1;
+ return n;
+#endif
+}
+static inline bool
+ispo2(uvlong x) {
+ return (x != 0) & ((x & (x - 1)) == 0);
+}
+static inline uint
+ilog2(uvlong x) { /* assumes x is a power of 2 */
+#if HAS_BUILTIN(ctzll)
+ return __builtin_ctzll(x);
+#else
+ uint n = 0;
+ while (x >>= 1) ++n;
+ return n;
+#endif
+}
+static inline uint
+lowestsetbit(uvlong x)
+{
+#if HAS_BUILTIN(ctzll)
+ return __builtin_ctzll(x);
+#else
+ int i = 0;
+ for (uvlong mask = 1;; ++i, mask <<= 1)
+ if (x & mask)
+ break;
+ return i;
+#endif
+}
+
+#define aisprint(c) in_range(c, ' ', '~')
+#define aisdigit(c) in_range(c, '0', '9')
+#define aisodigit(c) in_range(c, '0', '7')
+#define aisalpha(c) in_range((c)|0x20, 'a', 'z')
+static inline bool aisspace(int c) { return c == ' ' || in_range(c, '\t', '\r'); }
+static inline bool aisxdigit(int c) { return aisdigit(c) || in_range(c|0x20, 'a', 'f'); }
+
+/******************/
+/* COMPILER STATE */
+/******************/
+
+enum cstd {
+ STDC89,
+ STDC99,
+ STDC11,
+ STDC23,
+};
+struct option {
+ enum cstd cstd;
+ bool pedant;
+ bool trigraph;
+ bool nocolor;
+ bool pie, pic;
+ bool werror;
+ bool wnone;
+ enum optz {
+ OPT0 = -1,
+ OPT1 = 1,
+ OPT2 = 2,
+ } o;
+ union {
+ struct {
+ bool p : 1, /* after parsing */
+ a : 1, /* after abi0 */
+ m : 1, /* after mem */
+ y : 1, /* after inline */
+ o : 1, /* after optimizations */
+ s : 1, /* after stack */
+ i : 1, /* after isel */
+ l : 1, /* after liveness fixup */
+ r : 1; /* after regalloc */
+ };
+ uint any;
+ } dbg;
+ struct wbuf *dbgout;
+};
+extern struct option ccopt;
+extern struct cinclpaths {
+ struct inclpath {
+ struct inclpath *next;
+ const char *path;
+ } *list, **tail;
+} cinclpaths[5];
+enum { /* GCC include directory search order: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html#Options-for-Directory-Search */
+ CINCL_iquote,
+ CINCL_I,
+ CINCL_isystem,
+ CINCLsys,
+ CINCL_idirafter,
+};
+
+/**********/
+/* Target */
+/**********/
+
+struct targtriple {
+ enum mcarch { ISxxx, ISx86_64, ISaarch64 } arch;
+ enum mcos { OSunknown, OSlinux } os;
+ enum mcabi { ABInone, ABIgnu, ABImusl } abi;
+};
+extern const struct mctarg *mctarg;
+extern struct targtriple target;
+void targ_init(const char *);
+
+/*********/
+/** MEM **/
+/*********/
+
+/* libc *alloc wrappers */
+void *xmalloc(size_t n, const char *);
+void *xcalloc(size_t n, const char *);
+void *xrealloc(void *, size_t n, const char *);
+void free(void *);
+#define xmalloc(n) xmalloc(n, __func__)
+#define xcalloc(n) xcalloc(n, __func__)
+#define xrealloc(p,n) xrealloc(p, n, __func__)
+
+/* string interning */
+typedef const struct internstr {char c;} *internstr;
+internstr intern_(const char *, uint len);
+#define intern(s) intern_(s, 0)
+
+/* growable buffer that stores its capacity in the allocated memory */
+#define xbnew_(n) (void *)(1 + (size_t *)xcalloc(sizeof(size_t) + (n)))
+#define xbcap_(p) ((size_t *)(p))[-1]
+static inline void
+xbgrow_(void **p, size_t n)
+{
+ if (!n) return;
+ if (!*p) { *p = xbnew_(n); xbcap_(*p) = n; assert(n>0); }
+ else if (xbcap_(*p) < n) {
+ size_t k = xbcap_(*p);
+ assert(k > 0);
+ do k *= 2; while (k < n);
+ *p = 1 + (size_t *)xrealloc(&xbcap_(*(p)), sizeof(size_t) + k);
+ xbcap_(*p) = k;
+ };
+}
+#define xbgrow(p, n) xbgrow_((void **)(p), (n) * sizeof**(p))
+#define xbpush(p, n, x) (xbgrow(p, (*(n) + 1)), (*(p))[(*(n))++] = (x))
+#define xbfree(p) ((p) ? free(&xbcap_(p)) : (void)0)
+#define xbcap(p) ((p) ? xbcap_(p) / sizeof*(p) : 0)
+#define xbgrowz(p, n) do { \
+ size_t tmp = *(p) ? xbcap_(*(p)) : 0; \
+ xbgrow(p, n); \
+ memset((char*)*(p)+tmp, 0, xbcap_(*(p))-tmp); \
+} while (0)
+
+
+/** arenas **/
+struct arena {
+ uint cap : 31,
+ dyn : 1;
+ uint n;
+ struct arena *prev;
+ uchar mem[];
+};
+
+extern struct arena *globarena;
+
+struct arena *newarena(uint chunksiz);
+void *alloc(struct arena **, uint siz, uint align);
+void *allocz(struct arena **, uint siz, uint align);
+static inline void *
+alloccopy(struct arena **arena, const void *src, uint siz, uint align)
+{
+ if (!siz) return NULL;
+ return memcpy(alloc(arena, siz, align), src, siz);
+}
+void freearena(struct arena **);
+
+/** vec **/
+struct vecbase { void *p; uint n; uint cap : 31, dyn : 1; };
+#define vec_of(T) union { \
+ struct { T *p; uint n; }; \
+ struct vecbase _vb; \
+}
+void vinit_(struct vecbase *, void *inlbuf, uint cap, uint siz);
+void vpush_(struct vecbase *, uint siz);
+void *vpushn_(struct vecbase *, uint siz, const void *dat, uint ndat);
+void vresize_(struct vecbase *, uint siz, uint N);
+#define VINIT(inlbuf, Cap) { ._vb.p = (inlbuf), ._vb.cap = (Cap) }
+#define vfree(v) ((v)->_vb.dyn ? free((v)->p) : (void)0, memset((v), 0, sizeof*(v)))
+#define vinit(v, inlbuf, Cap) (vfree(v), vinit_(&(v)->_vb, inlbuf, (Cap), sizeof *(v)->p))
+#define vpush(v, x) (vpush_(&(v)->_vb, sizeof *(v)->p), (v)->p[(v)->n++] = (x))
+#define vpushn(v, xs, N) vpushn_(&(v)->_vb, sizeof *(v)->p, xs, N)
+#define vresize(v, N) vresize_(&(v)->_vb, sizeof *(v)->p, N)
+
+/** map of short -> T **/
+#define imap_of(T) struct { T *v; int tmp; struct imapbase mb; }
+struct imapbase { short *k; struct bitset *bs; uint n, N; };
+
+void imap_init_(struct imapbase *, void **v, uint vsiz, uint N);
+int imap_get_(struct imapbase *, short k);
+int imap_set_(struct imapbase *, void **v, uint vsiz, short k);
+#define imap_free(m) (free((m)->mb.k), memset((m), 0, sizeof *(m)))
+#define imap_init(m, N) (imap_free(m), imap_init_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, (N)))
+#define imap_clear(m) ((m)->mb.bs ? bszero((m)->mb.bs, BSSIZE((m)->mb.N)) : (void)0, \
+ (m)->mb.n = 0)
+#define imap_get(m, k) ((m)->tmp = imap_get_(&(m)->mb, k), (m)->tmp < 0 ? NULL : &(m)->v[(m)->tmp])
+#define imap_set(m, k, ...) ((m)->tmp = imap_set_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, k), \
+ (m)->v[(m)->tmp] = (__VA_ARGS__), &(m)->v[(m)->tmp])
+#define imap_each(m,kx,pvx) \
+ for (int _i = 0; _i < (m)->mb.N && ((kx) = (m)->mb.k[_i], (pvx) = &(m)->v[_i], 1); ++_i) \
+ if (bstest((m)->mb.bs, _i))
+#define imap_copy(dst,src) do { \
+ size_t N = (src)->mb.N; \
+ if (!N) break; \
+ (dst)->mb.n = (src)->mb.n; \
+ imap_init((dst), N); \
+ memcpy((dst)->mb.k, (src)->mb.k, \
+ N*(sizeof*(src)->mb.k + sizeof*(src)->v) + BSSIZE(N)*sizeof(struct bitset)); \
+} while (0)
+
+
+/** map of non-null ptr -> T **/
+#define pmap_of(T) struct { T *v; int tmp; struct pmapbase mb; }
+struct pmapbase { void **k; uint n, N; };
+
+void pmap_init_(struct pmapbase *, void **v, uint vsiz, uint N);
+int pmap_get_(struct pmapbase *, const void *k);
+int pmap_set_(struct pmapbase *, void **v, uint vsiz, const void *k);
+void pmap_del_(struct pmapbase *, const void *k);
+extern char pmap_tombstone_[];
+#define pmap_free(m) (free((m)->mb.k), memset((m), 0, sizeof *(m)))
+#define pmap_init(m, N) (pmap_free(m), pmap_init_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, (N)))
+#define pmap_get(m, k) (((m)->tmp = pmap_get_(&(m)->mb, k)) < 0 ? NULL : &(m)->v[(m)->tmp])
+#define pmap_set(m, k, x) ((m)->tmp = pmap_set_(&(m)->mb, (void **)&(m)->v, sizeof*(m)->v, k), \
+ (m)->v[(m)->tmp] = (x))
+#define pmap_del(m, k) pmap_del_(&(m)->mb, k)
+#define pmap_each(m,kx,pvx) \
+ for (size_t _i = 0; _i < (m)->mb.N && ((kx) = (m)->mb.k[_i], (pvx) = &(m)->v[_i], 1); ++_i) \
+ if (kx && kx != pmap_tombstone_)
+
+/** bitset **/
+struct bitset { size_t u; };
+enum { BSNBIT = 8 * sizeof(struct bitset) };
+#define BSSIZE(nbit) ((nbit)/BSNBIT + ((nbit)%BSNBIT != 0))
+
+static inline bool
+bstest(const struct bitset *bs, uint i)
+{
+ return bs[i/BSNBIT].u >> i%BSNBIT & 1;
+}
+
+static inline void
+bsset(struct bitset *bs, uint i)
+{
+ bs[i/BSNBIT].u |= 1ull << i%BSNBIT;
+}
+
+static inline void
+bsclr(struct bitset *bs, uint i)
+{
+ bs[i/BSNBIT].u &= ~(1ull << i%BSNBIT);
+}
+
+static inline void
+bszero(struct bitset bs[/*siz*/], uint siz)
+{
+ memset(bs, 0, siz * sizeof *bs);
+}
+
+static inline void
+bscopy(struct bitset dst[/*siz*/], const struct bitset src[/*siz*/], uint siz)
+{
+ while (siz--) dst++->u = src++->u;
+}
+
+static inline void
+bsunion(struct bitset dst[/*siz*/], const struct bitset src[/*siz*/], uint siz)
+{
+ while (siz--) dst++->u |= src++->u;
+}
+
+static inline uint
+bscount(struct bitset bs[/*siz*/], uint siz)
+{
+ uint n = 0;
+ while (siz--) n += popcnt(bs++->u);
+ return n;
+}
+
+static inline bool
+bsiter(uint *i, struct bitset bs[/*siz*/], uint siz)
+{
+ uint k = *i/BSNBIT, j = *i%BSNBIT;
+ if (k >= siz) return 0;
+ size_t t = bs[k].u & ~(((size_t)1 << j) - 1);
+ while (!t) {
+ if (++k >= siz) return 0;
+ t = bs[k].u;
+ }
+ *i = k*BSNBIT + lowestsetbit(t);
+ return 1;
+}
+#define bs_each(T, var, bs, siz) for (T (var) = 0; bsiter(&(var), (bs), (siz)); ++(var))
+
+static inline bool
+bsiterzr(uint *i, struct bitset bs[/*siz*/], uint siz)
+{
+ uint k = *i/BSNBIT, j = *i%BSNBIT;
+ if (k >= siz) return 0;
+ size_t t = ~bs[k].u & ~(((size_t)1 << j) - 1);
+ while (!t) {
+ if (++k >= siz) return 0;
+ t = ~bs[k].u;
+ }
+ *i = k*BSNBIT + lowestsetbit(t);
+ return 1;
+}
+
+/********/
+/** IO **/
+/********/
+
+struct wbuf {
+ union {
+ struct {
+ char *buf;
+ uint cap;
+ uint len;
+ int fd;
+ };
+ void *fp;
+ };
+ bool err;
+ bool isfp;
+};
+
+/* read-only file mapping that is at least 1 page larger than the real file
+ * so it's legal to read a few bytes beyond it to avoid some bounds checks */
+struct memfile {
+ const uchar *p;
+ uint n;
+ bool statik;
+};
+
+struct embedfile {
+ const char *name;
+ const char *s;
+ size_t len;
+};
+
+#define MEMBUF(buf_, cap_) { .buf = (buf_), .cap = (cap_), .fd = -1 }
+#define FDBUF(buf_, cap_, fd_) { .buf = (buf_), .cap = (cap_), .fd = (fd_) }
+extern struct wbuf bstdout, bstderr;
+void ioinit(void);
+void iowrite(struct wbuf *, const void *src, int n);
+void ioputc(struct wbuf *, uchar);
+void ioflush(struct wbuf *);
+int vbfmt(struct wbuf *, const char *, va_list ap);
+int bfmt(struct wbuf *, const char *, ...);
+#define pfmt(...) bfmt(&bstdout, __VA_ARGS__)
+#define efmt(...) bfmt(&bstderr, __VA_ARGS__)
+struct memfile mapopen(const char **err, const char *path);
+void mapclose(struct memfile *);
+void *mapzeros(uint);
+int munmap(void *, size_t);
+int getpredeffile(struct memfile **, const char *name);
+int openfile(const char **err, struct memfile **, const char *path);
+const char *getfilename(int id, uint atoff);
+struct memfile *getfile(int id);
+void addfileline(int id, uint off);
+void setfileline(int id, uint off, int line, const char *file);
+const char *getfilepos(int *line, int *col, int id, uint off);
+bool isoncefile(int id, internstr *guard);
+void markfileonce(int id, internstr guard);
+void markfileseen(int id);
+bool isfileseen(int id);
+void closefile(int id);
+
+enum diagkind { DGERROR, DGWARN, DGNOTE, };
+void vdiag(const struct span *, enum diagkind, const char *, va_list);
+NORETURN void fatal(const struct span *, const char *, ...);
+void error(const struct span *, const char *, ...);
+void warn(const struct span *, const char *, ...);
+void note(const struct span *, const char *, ...);
+ushort *utf8to16(uint *ulen, struct arena **, const uchar *s, size_t len);
+uint *utf8to32(uint *ulen, struct arena **, const uchar *s, size_t len);
+int utf8enc(char out[4], uint cp);
+
+#endif /* COMMON_H_ */
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/a_main.c b/src/a_main.c
new file mode 100644
index 0000000..9710814
--- /dev/null
+++ b/src/a_main.c
@@ -0,0 +1,710 @@
+#include "common.h"
+#include "version.h"
+#include "hostconfig.h" /* run ./configure */
+#include "obj/obj.h"
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+#include <time.h>
+
+struct option ccopt;
+struct cinclpaths cinclpaths[5];
+
+static void
+addinclpath(int ord, const char *path)
+{
+ struct inclpath *p = alloc(&globarena, sizeof *p, 0);
+ assert((uint)ord < countof(cinclpaths));
+ p->path = path;
+ p->next = NULL;
+ if (cinclpaths[ord].list) {
+ *cinclpaths[ord].tail = p;
+ } else {
+ cinclpaths[ord].list = p;
+ }
+ cinclpaths[ord].tail = &p->next;
+}
+
+/* parse an argument of the form 'opt=abcd'
+ * e.g. arg="foo=bar123"; opt="foo"; returns "bar123" */
+static const char *
+optval(const char *arg, const char *opt)
+{
+ uint n1 = strlen(arg), n2 = strlen(opt);
+ if (n1 < n2+1 || memcmp(arg, opt, n2) != 0 || arg[n2] != '=')
+ return NULL;
+ return arg + n2 + 1;
+}
+
+/* "foo.bar" -> "bar"; ".dotfile" -> "" */
+static const char *
+fileext(const char *path)
+{
+ const char *dot = NULL;
+ assert(path && *path && "empty");
+
+ for (++path; *path; ++path) {
+ if (*path == '.') dot = path;
+ }
+ return dot ? dot+1 : "";
+}
+
+enum inft { IFTauto, IFTc, IFTasm, IFTobj, IFTar, IFTdll };
+
+static enum inft
+ftdetect(const char *s)
+{
+ const char *ext = fileext(s);
+ if (!strcmp(ext, "c")) return IFTc;
+ if (!strcmp(ext, "o")) return IFTobj;
+ if (!strcmp(ext, "a")) return IFTar;
+ if (!strcmp(ext, "s")) return IFTasm;
+ if (!strcmp(ext, "so")) return IFTdll;
+ warn(NULL, "assuming %'s is C source file", s);
+ return IFTc;
+}
+
+static union {
+ struct arena a;
+ char mem[sizeof(struct arena) + (1<<10)];
+} _arenamem;
+struct arena *globarena = &_arenamem.a;
+
+/* withext("x/y.c", "o") -> "y.o"; withext("f9", "s") -> "f9.s" */
+static const char *
+withext(const char *path, const char *ext)
+{
+ char *res;
+ size_t len;
+ const char *oext, *file = path;
+
+ while (*path)
+ if (*path++ == '/')
+ file = path;
+ assert(*file && "no filename");
+ oext = fileext(file);
+ if (!*oext)
+ len = strlen(file);
+ else
+ len = oext - file - 1;
+ res = alloc(&globarena, len + 1 + (ext ? strlen(ext) + 1 : 0), 1);
+ memcpy(res, file, len);
+ if (ext) {
+ res[len] = '.';
+ memcpy(res + len + 1, ext, strlen(ext));
+ } else {
+ res[len] = 0;
+ }
+ return res;
+}
+
+struct infile {
+ enum inft ft;
+ const char *path, *temp;
+};
+static struct infile infilebuf[16];
+static struct task {
+ enum outft { OFTexe, OFTdll, OFTobj, OFTasm, OFTc } outft;
+ const char *out;
+ const char *targ;
+ vec_of(struct infile) inf;
+ char **runargs;
+ vec_of(const char *) linkargs;
+ bool verbose, run, syntaxonly;
+} task = { .inf = VINIT(infilebuf, countof(infilebuf)) };
+
+static void prihelp(void);
+
+static void
+optparse(char **args)
+{
+ const char *arg, *x;
+ enum inft ft = IFTauto;
+
+ while ((arg = *++args)) {
+ if (*arg++ != '-' || !*arg) {
+ vpush(&task.inf, ((struct infile) {
+ ft ? ft : ftdetect(arg-1),
+ arg[-1] != '-' ? arg-1 : "/dev/stdin"
+ }));
+ ft = IFTauto;
+ if (task.run) {
+ task.runargs = args+1;
+ return;
+ }
+ continue;
+ }
+ int cinclord;
+ if (!strcmp(arg, "help") || !strcmp(arg, "h") || !strcmp(arg, "-help")) {
+ prihelp();
+ exit(0);
+ } else if (!strcmp(arg, "dumpmachine")) {
+ pfmt("%s\n", HOST_TRIPLE);
+ exit(0);
+ } else if (!strcmp(arg, "-version")) {
+ pfmt("antcc version "ANTCC_VERSION_STR"\n"
+ "target: "HOST_TRIPLE"\n"
+ "include paths: "XSTR(HOST_INCLUDE_DIRS)"\n"
+ "host cc for linking: " HOST_CC "\n");
+ exit(0);
+ } else if (!strcmp(arg, "dumpversion")) {
+ pfmt("%s\n", ANTCC_VERSION_STR);
+ exit(0);
+ } else if ((x = optval(arg, "std"))) {
+ if (!strcmp(x, "c89") || !strcmp(x, "c90")) ccopt.cstd = STDC89;
+ else if (!strcmp(x, "c99")) ccopt.cstd = STDC99;
+ else if (!strcmp(x, "c11")) ccopt.cstd = STDC11;
+ else if (!strcmp(x, "c2x")) ccopt.cstd = STDC23;
+ else if (!strcmp(x, "c23")) ccopt.cstd = STDC23;
+ else goto Bad;
+ } else if (!strcmp(arg, "pedantic")) {
+ ccopt.pedant = 1;
+ } else if (!strcmp(arg, "trigraphs")) {
+ ccopt.trigraph = 1;
+ } else if (*arg == 'd' && arg[1]) {
+ /* see common.h§struct option */
+ while (*++arg) switch (*arg | 32) {
+ case 'p': ccopt.dbg.p = 1; break;
+ case 'a': ccopt.dbg.a = 1; break;
+ case 'm': ccopt.dbg.m = 1; break;
+ case 'o': ccopt.dbg.o = 1; break;
+ case 's': ccopt.dbg.s = 1; break;
+ case 'i': ccopt.dbg.i = 1; break;
+ case 'y': ccopt.dbg.y = 1; break;
+ case 'l': ccopt.dbg.l = 1; break;
+ case 'r': ccopt.dbg.r = 1; break;
+ default: warn(NULL, "-d: invalid debug flag %'c", *arg);
+ }
+ } else if (*arg == 'o') {
+ if (arg[1]) task.out = arg+1;
+ else if (args[1]) task.out = *++args;
+ else fatal(NULL, "missing path after `-o`");
+ } else if (!strcmp(arg, "fsyntax-only")) {
+ task.syntaxonly = 1;
+ } else if (*arg == 'f') {
+ /* -fabc / -fno-abc flags */
+ const char *flag = arg+1;
+ bool set = 1;
+ if (!strncmp(flag, "no-", 3)) {
+ set = 0;
+ flag += 3;
+ }
+ if (!strcmp(flag, "pie") || !strcmp(flag, "PIE")) ccopt.pie = set;
+ else if (!strcmp(flag, "pic") || !strcmp(flag, "PIC")) ccopt.pic = set;
+ else goto Bad;
+ } else if (!strcmp(arg, "target") || !strcmp(arg, "-target")) {
+ const char *s = *++args;
+ if (!s) fatal(NULL, "missing target name");
+ task.targ = s;
+ } else if (*arg == 'l' || *arg == 'L' || *arg == 'B' || !strcmp(arg, "shared") || !strcmp(arg, "pthread")) {
+ vpush(&task.linkargs, arg-1);
+ } else if (!strcmp(arg, "v") || !strcmp(arg, "-verbose")) {
+ task.verbose = 1;
+ } else if (!strcmp(arg, "c")) {
+ task.outft = OFTobj;
+ } else if (!strcmp(arg, "E")) {
+ task.outft = OFTc;
+ } else if (!strcmp(arg, "xc")) {
+ ft = IFTc;
+ } else if (!strcmp(arg, "xo")) {
+ ft = IFTobj;
+ } else if (!strcmp(arg, "run")) {
+ task.run = 1;
+ if (task.inf.n > 0) {
+ task.runargs = args+1;
+ return;
+ }
+ } else if (*arg == 'g') {
+ /* TODO debug info */
+ } else if (*arg == 'O') {
+ char o = arg[1];
+ if (!o || o == 'g') ccopt.o = 0; /* default opts */
+ else if (o == '1' || o == 's' || o == 'z') ccopt.o = OPT1;
+ else if ((uint)o - '1' < 9) ccopt.o = OPT2;
+ else if (o == '0') ccopt.o = OPT0;
+ else goto Bad;
+ } else if (*arg == 'D' || *arg == 'U') {
+ void cpppredef(bool undef, const char *cmd);
+ const char *def = arg[1] ? arg+1 : *++args;
+ if (!def) fatal(NULL, "macro name missing after `-%c`", *arg);
+ cpppredef(*arg == 'U', def);
+ } else if (*arg == 'O') {
+ /* TODO optimization level */
+ } else if (*arg == 'I' || !strcmp(arg, "-include-directory")) {
+ const char *p;
+ cinclord = CINCL_I;
+ if (*arg == 'I' && arg[1]) p = arg+1;
+ else CIncl: p = *++args;
+ if (!p) fatal(NULL, "missing path after `%s`", arg-1);
+ addinclpath(cinclord, p);
+ } else if (!strcmp(arg, "iquote")) {
+ cinclord = CINCL_iquote;
+ goto CIncl;
+ } else if (!strcmp(arg, "isystem")) {
+ cinclord = CINCL_isystem;
+ goto CIncl;
+ } else if (!strcmp(arg, "idirafter")) {
+ cinclord = CINCL_idirafter;
+ goto CIncl;
+ } else if (!strcmp(arg, "nostdinc") || !strcmp(arg, "-no-standard-includes")) {
+ cinclpaths[CINCLsys].list = NULL;
+ } else if (*arg == 'M') {
+ ++arg;
+ if (*arg == 'F' || *arg == 'T' || *arg == 'Q') {
+ const char *p = arg[1] ? arg+1 : *++args;
+ if (!p) fatal(NULL, "missing path after `-M%c`", *arg);
+ }
+ /* TODO depfiles */
+ } else if (*arg == 'W') {
+ if (!strcmp(arg+1, "error")) {
+ ccopt.werror = 1;
+ }
+ /* TODO warning switches */
+ } else if (*arg == 'w') {
+ ccopt.wnone = 1;
+ /* TODO warning switches */
+ } else Bad: warn(NULL, "unrecognized option: %'s", arg-1);
+ }
+
+ if (task.inf.n == 0) fatal(NULL, "no input files");
+
+ if (!task.out && !task.syntaxonly) {
+ switch (task.outft) {
+ case OFTdll:
+ case OFTexe: if (!task.run) task.out = "a.out"; break;
+ case OFTasm: task.out = withext(task.inf.p[0].path, "s"); break;
+ case OFTobj: task.out = withext(task.inf.p[0].path, "o"); break;
+ case OFTc: break;
+ }
+ }
+ if (!in_range(task.outft, OFTexe, OFTdll) && task.outft != OFTc && task.inf.n > 1)
+ fatal(NULL, "too many input files");
+}
+
+static const char *
+tempfile(const char *path, const char *ext)
+{
+ int id;
+ static int id2;
+ static char sbuf[1024];
+ const char *tmpdir;
+ const char *file = path;
+ struct wbuf fbuf = MEMBUF(sbuf, sizeof sbuf);
+
+ tmpdir = getenv("TMPDIR");
+ tmpdir = tmpdir ? tmpdir : "/tmp";
+ id = getpid();
+ id = id < 0 ? time(NULL) : id;
+
+ while (*path)
+ if (*path++ == '/')
+ file = path;
+ bfmt(&fbuf, "%s/antcc-%x@%u@", tmpdir, id, id2++);
+ while (*file++) {
+ char c = file[-1];
+ if (in_range(c, 'a', 'z') || in_range(c, 'A', 'Z') || in_range(c, '0', '9')
+ || c == '_' || c == '-')
+ {
+ ioputc(&fbuf, c);
+ } else if (c == '.') break;
+ else ioputc(&fbuf, '_');
+ }
+ bfmt(&fbuf, "%s%s", &"."[!ext], ext ? ext : "");
+ ioputc(&fbuf, 0);
+ assert(!fbuf.err);
+ return alloccopy(&globarena, fbuf.buf, fbuf.len, 1);
+}
+
+static int cc1(const char *out, const char *in);
+
+static const char *tempout;
+static pid_t rootp;
+static void
+mktemps(void) {
+ rootp = getpid();
+ for (int i = 0; i < task.inf.n; ++i) {
+ if (task.inf.p[i].ft == IFTc)
+ task.inf.p[i].temp = tempfile(task.inf.p[i].path, "o");
+ }
+ if (!task.out)
+ task.out = tempout = tempfile(task.inf.n > 1 ? "run" : withext(task.inf.p[0].path, NULL), NULL);
+}
+
+static void
+cleantemps(void)
+{
+ if (getpid() != rootp) return;
+ for (int i = 0; i < task.inf.n; ++i) {
+ if (task.inf.p[i].temp) {
+ unlink(task.inf.p[i].temp);
+ task.inf.p[i].temp = NULL;
+ }
+ }
+ if (tempout)
+ unlink(tempout), tempout = NULL;
+}
+static void
+sigcleantemps(int _)
+{
+ cleantemps();
+}
+
+static void
+compileobjs(void)
+{
+ int wstat;
+ pid_t p;
+
+ if (!ccopt.dbg.any && !task.syntaxonly) mktemps();
+ for (int i = 0; i < task.inf.n; ++i) {
+ enum inft ft = task.inf.p[i].ft;
+ if (ft == IFTc) {
+ if ((p = fork()) < 0) {
+ error(NULL, "fork(): %s\n", strerror(errno));
+ exit(1);
+ } else if (p == 0) {
+ exit(cc1(task.inf.p[i].temp, task.inf.p[i].path));
+ }
+ waitpid(p, &wstat, 0);
+ if (!WIFEXITED(wstat)) exit(127);
+ if (WEXITSTATUS(wstat) != 0) {
+ cleantemps();
+ exit(WEXITSTATUS(wstat));
+ }
+ } else if (ft == IFTobj || ft == IFTar || ft == IFTdll) {
+ // passthru
+ } else assert(!"not obj");
+ }
+ if (!ccopt.dbg.any && !task.syntaxonly) {
+ atexit(cleantemps);
+ signal(SIGINT, sigcleantemps);
+ signal(SIGABRT, sigcleantemps);
+ signal(SIGILL, sigcleantemps);
+ }
+}
+
+#include <fcntl.h>
+
+static bool
+hasprog(const char *prog)
+{
+ pid_t p;
+ if ((p = fork()) < 0) {
+ return 0;
+ } else if (p == 0) {
+ int nulfd = open("/dev/null", O_WRONLY);
+ if (nulfd < 0) return 0;
+ for (int fd = 0; fd <= 2; ++fd) {
+ dup2(fd, nulfd);
+ close(fd);
+ }
+ const char *cmd[] = {prog, NULL};
+ if (execvp(*cmd, (char **)cmd)) {
+ close(nulfd);
+ error(NULL, "execvp: %s", strerror(errno));
+ exit(125);
+ }
+ }
+ int wstat;
+ waitpid(p, &wstat, 0);
+ if (!WIFEXITED(wstat)) return 0;
+ return WEXITSTATUS(wstat) < 125;
+}
+
+static bool
+iscrosscc(void)
+{
+ return target.os != HOST_OS || target.arch != HOST_ARCH || target.abi != HOST_ABI;
+}
+
+struct cmdargs { vec_of(const char *); };
+
+static void
+findlinkcmd(struct cmdargs *cmd)
+{
+ if (task.targ && iscrosscc()) {
+ /* try to find a cross compiling toolchain, e.g. aarch64-linux-gnu-gcc */
+ static const char *ccs[] = {"cc", "gcc", "clang"};
+ char cross[1024];
+ for (int i = 0; i < countof(ccs); ++i) {
+ struct wbuf wbuf = MEMBUF(cross, sizeof cross);
+ int n = bfmt(&wbuf, "%s-%s", task.targ, ccs[i]);
+ assert(n < sizeof cross-1);
+ cross[n] = 0;
+ if (hasprog(cross)) {
+ vpush(cmd, alloccopy(&globarena, cross, n, 1));
+ return;
+ }
+ }
+ /* zig cc fallback, which works great as cross compiler toolchain */
+ if (hasprog("zig")) {
+ vpush(cmd, "zig");
+ vpush(cmd, "cc");
+ vpush(cmd, "-target");
+ vpush(cmd, task.targ);
+ } else {
+ fatal(NULL, "cannot link to cross-compilation target: no appropiate toolchain installed");
+ }
+ } else {
+ vpush(cmd, HOST_CC);
+ }
+}
+
+static int
+dolink(void)
+{
+ const char *cmdbuf[100];
+ pid_t p;
+ int wstat;
+ struct cmdargs cmd = VINIT(cmdbuf, countof(cmdbuf));
+
+ findlinkcmd(&cmd);
+ if (!strcmp(cmd.p[0], "zig")) {
+ note(NULL, "using 'zig cc' as a cross-compiler");
+ }
+ if (task.outft == OFTdll) {
+ vpush(&cmd, "-shared");
+ } else if (task.outft == OFTexe) {
+ vpush(&cmd, ccopt.pie ? "-pie" : "-no-pie");
+ }
+ vpush(&cmd, "-o");
+ vpush(&cmd, task.out);
+ assert(task.inf.n > 0);
+ for (int i = 0; i < task.inf.n; ++i) {
+ const char *o;
+ switch (task.inf.p[i].ft) {
+ case IFTc: o = task.inf.p[i].temp; break;
+ case IFTobj: case IFTar: case IFTdll:
+ o = task.inf.p[i].path; break;
+ default: assert(!"link obj?");
+ }
+ vpush(&cmd, o);
+ }
+ vpushn(&cmd, task.linkargs.p, task.linkargs.n);
+ if (task.verbose) {
+ efmt("> ");
+ for (int i = 0; i < cmd.n; ++i)
+ efmt("%s ", cmd.p[i]);
+ efmt("\n");
+ }
+ vpush(&cmd, NULL);
+ if ((p = fork()) < 0) {
+ error(NULL, "fork: %s\n", strerror(errno));
+ exit(1);
+ } else if (p == 0) {
+ if (execvp(cmd.p[0], (char **)cmd.p)) {
+ error(NULL, "execvp: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ vfree(&cmd);
+ waitpid(p, &wstat, 0);
+ if (!WIFEXITED(wstat)) return 127;
+ if (WEXITSTATUS(wstat) != 0) {
+ error(NULL, "link command failed");
+ return 1;
+ }
+ return 0;
+}
+
+static int
+dorun(void)
+{
+ if (target.arch != HOST_ARCH || target.os != HOST_OS) {
+ warn(NULL, "'-run' with cross-compiled binary");
+ }
+ if (task.verbose) {
+ efmt("> exec %s", task.out);
+ for (char **s = task.runargs; *s; ++s)
+ efmt(" %s", *s);
+ efmt("\n");
+ }
+#if _POSIX_C_SOURCE >= 200809L
+ /* use fexecve */
+ int fexecve(int fd, char *const argv[], char *const envp[]);
+ int fd = open(task.out, O_RDONLY);
+ if (fd < 0) {
+ error(NULL, "open: %s\n", strerror(errno));
+ return 1;
+ }
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
+ error(NULL, "fcntl: %s\n", strerror(errno));
+ return 1;
+ }
+ cleantemps();
+ extern char **environ;
+ fexecve(fd, task.runargs - 1, environ);
+ error(NULL, "fexecv: %s\n", strerror(errno));
+ return 1;
+#else
+ pid_t p;
+ if ((p = fork()) < 0) {
+ error(NULL, "fork(): %s\n", strerror(errno));
+ exit(1);
+ } else if (p == 0) {
+ if (!execv(task.out, task.runargs - 1)) {
+ error(NULL, "execv(): %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ int wstat;
+ waitpid(p, &wstat, 0);
+ if (!WIFEXITED(wstat)) return 127;
+ return WEXITSTATUS(wstat);
+#endif
+}
+
+static int
+driver(void)
+{
+ void cpp(struct wbuf *, const char *);
+ if (task.verbose)
+ efmt("# Target: %s\n", task.targ ? task.targ : HOST_TRIPLE);
+ if (task.syntaxonly)
+ task.out = "/dev/null"; // HACK
+ if (task.outft == OFTobj) {
+ assert(task.inf.n == 1);
+ if (task.inf.p[0].ft != IFTc)
+ fatal(NULL, "not a C source file: %s", task.inf.p[0].path);
+ return cc1(task.out, task.inf.p[0].path);
+ } else if (task.outft == OFTc) {
+ struct wbuf _buf = {0}, *buf = &bstdout;
+ if (task.out) {
+ buf = &_buf;
+ buf->buf = alloc(&globarena, buf->cap = 1<<12, 1);
+ buf->fd = open(task.out, O_CREAT | O_TRUNC | O_WRONLY, 0777);
+ if (buf->fd < 0) {
+ error(NULL, "open(%'s): %s", task.out, strerror(errno));
+ return 1;
+ }
+ }
+ bool ok = 1;
+ if (!task.out && task.inf.n == 1)
+ cpp(buf, task.inf.p[0].path);
+ else for (int i = 0; i < task.inf.n; ++i) {
+ pid_t p;
+ int wstat;
+
+ if ((p = fork()) < 0) {
+ error(NULL, "fork(): %s\n", strerror(errno));
+ ok = 0;
+ } else if (p == 0) {
+ cpp(buf, task.inf.p[i].path);
+ exit(0);
+ }
+ waitpid(p, &wstat, 0);
+ if (!WIFEXITED(wstat)) ok = 0;
+ ok = ok && WEXITSTATUS(wstat) == 0;
+ }
+ ioflush(buf);
+ if (task.out)
+ close(buf->fd);
+ return ok ? 0 : 1;
+ } else if (task.outft == OFTexe || task.outft == OFTdll) {
+ compileobjs();
+ if (ccopt.dbg.any || task.syntaxonly) return 0;
+ if (!task.run) return dolink();
+ int st = dolink();
+ if (st == 0) st = dorun();
+ return st;
+ }
+ assert(0);
+}
+
+static int
+cc1(const char *out, const char *in)
+{
+ void ccomp(const char *);
+ extern int nerror;
+
+ if (task.verbose) efmt("cc1(/*out*/ %'s, /*in*/ %'s)\n", out, in);
+ if (!ccopt.dbg.any && !task.syntaxonly) objini(in, out);
+ ccomp(in);
+ if (!ccopt.dbg.any && !task.syntaxonly && !nerror) objfini();
+ return !!nerror;
+}
+
+static void
+detectcolor(void)
+{
+ const char *s;
+ if (!isatty(STDERR_FILENO)
+ || ((s = getenv("NO_COLOR")) && *s)
+ || ((s = getenv("TERM")) && !strcmp(s, "dumb")))
+ ccopt.nocolor = 1;
+}
+
+static void
+sysinclpaths(void)
+{
+ static const char *paths[] = {
+ HOST_INCLUDE_DIRS
+ };
+ for (int i = 0; i < countof(paths); ++i)
+ addinclpath(CINCLsys, paths[i]);
+}
+
+static void
+prihelp(void)
+{
+ pfmt("antcc version "ANTCC_VERSION_STR"\n"
+ "Usage: antcc [options] infile(s)...\n"
+ " antcc [options] -run infile [arguments...]\n"
+ " antcc [options] infile(s)... -run [arguments...]\n"
+ "Options:\n"
+ " -help \tPrint this help message\n"
+ " -std=<..> \tSet C standard (c89, c99, c11, c23)\n"
+ " -pedantic \tWarnings for strict standards compliance\n"
+ " -d{pamyosilr} \tDebug print IR after {parse, abi, mem, inlining, opts, stack, isel, live, rega}\n"
+ " -o <file> \tPlace the output into <file>\n"
+ " -v \tVerbose output\n"
+ " -c \tEmit object file but do not link\n"
+ " -run \tRun compiled sources\n"
+ " -Idir \tAdd include path\n"
+ " -Dsym[=val] \tDefine macro\n"
+ " -Usym \tUndefine macro\n"
+ " -E \tPreprocess only\n"
+ " -llib \tLink with library\n"
+ " -fpie \tEmit code for position independent executable\n"
+ " -fpic \tEmit position independent code\n"
+ " -O<..> \tSet optimization level (0|g|1|2|s|z) (default: -Og)\n"
+ " -x<c|o> \tSpecify type of next input file (C, object)\n"
+ " -W[...] \tTurn on warnings (stub)\n"
+ " -Werror \tTurn warnings into errors\n"
+ " -w \tSuppress warnings\n"
+ " --version \tPrint version\n"
+ );
+}
+
+int
+main(int argc, char **argv)
+{
+ globarena->cap = sizeof(_arenamem.mem) - sizeof(struct arena);
+
+ ioinit();
+ /* setup defaults */
+ detectcolor();
+ sysinclpaths();
+ ccopt.cstd = STDC11;
+ ccopt.pie = 1;
+ ccopt.dbgout = &bstdout;
+
+ /* parse cli ags */
+ if (argc == 1) {
+ prihelp();
+ return 1;
+ }
+ optparse(argv);
+
+ /* global init */
+ targ_init(task.targ);
+ if (!target.arch)
+ fatal(NULL, "unsupported target: %s", task.targ ? task.targ : HOST_TRIPLE);
+
+ return driver();
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/a_targ.c b/src/a_targ.c
new file mode 100644
index 0000000..fdc11f8
--- /dev/null
+++ b/src/a_targ.c
@@ -0,0 +1,121 @@
+#include "common.h"
+#include "type.h"
+
+extern const struct mctarg t_x86_64_sysv, t_aarch64_aapcs;
+static const struct targ {
+ struct { enum mcarch arch; uint oss, abis; };
+ struct { uchar longsize, vlongsize, ptrsize, valistsize; };
+ struct { uchar longalign, vlongalign, doublealign, ptralign; };
+ bool charsigned;
+ uchar sizetype, ptrdifftype, wchartype;
+ const struct mctarg *mctarg;
+} targs[] = {
+ { {ISx86_64, -1, -1}, {8,8,8,24}, {8,8,8,8}, 1, TYULONG, TYLONG, TYINT, &t_x86_64_sysv },
+ { {ISaarch64, -1, -1}, {8,8,8,32}, {8,8,8,8}, 0, TYULONG, TYLONG, TYUINT, &t_aarch64_aapcs },
+};
+
+struct targtriple target;
+uchar targ_primsizes[TYPTR+1];
+uchar targ_primalign[TYPTR+1];
+uint targ_valistsize;
+enum typetag targ_sizetype, targ_ptrdifftype, targ_wchartype;
+bool targ_charsigned, targ_bigendian, targ_64bit;
+enum mcarch targ_arch;
+const struct mctarg *mctarg;
+
+static bool
+matchstr(const char **s, const char *pat)
+{
+ const char *p;
+ for (p = *s; *pat; ++p, ++pat) {
+ if (*pat == '$') { if (*p) return 0; else break; }
+ else if (*p != *pat) return 0;
+ }
+ *s = p;
+ return 1;
+}
+
+static bool
+parsetriple(struct targtriple *trg, const char *str)
+{
+ if (matchstr(&str, "x86_64-") || matchstr(&str, "amd64-"))
+ trg->arch = ISx86_64;
+ else if (matchstr(&str, "aarch64-") || matchstr(&str, "arm64-"))
+ trg->arch = ISaarch64;
+ else return 0;
+
+ if (matchstr(&str, "unknown-") || matchstr(&str, "pc-")) {}
+
+ if (matchstr(&str, "linux-")) {
+ trg->os = OSlinux;
+ } else if (matchstr(&str, "linux$")) {
+ trg->os = OSlinux;
+ trg->abi = ABIgnu;
+ } else return 0;
+
+ if (matchstr(&str, "gnu")) {
+ trg->abi = ABIgnu;
+ } else if (matchstr(&str, "musl")) {
+ trg->abi = ABImusl;
+ } else return 0;
+
+ return 1;
+}
+
+#include "hostconfig.h" /* run ./configure */
+
+void
+targ_init(const char *starg)
+{
+ const struct targ *t = NULL;
+ uchar *sizes = targ_primsizes, *align = targ_primalign;
+
+ if (!starg) {
+ target.arch = HOST_ARCH;
+ target.os = HOST_OS;
+ target.abi = HOST_ABI;
+ } else if (!parsetriple(&target, starg)) {
+ fatal(NULL, "unrecognized target: %s", starg);
+ }
+
+ for (size_t i = 0; i < countof(targs); ++i) {
+ if (targs[i].arch == target.arch)
+ if (targs[i].oss & (1 << target.os))
+ if (targs[i].abis & (1 << target.abi)) {
+ t = &targs[i];
+ break;
+ }
+ }
+ if (!t) fatal(NULL, "unsupported target: %s", starg ? starg : HOST_TRIPLE);
+
+ sizes[TYBOOL] = sizes[TYCHAR] = sizes[TYSCHAR] = sizes[TYUCHAR] = 1;
+ sizes[TYSHORT] = sizes[TYUSHORT] = 2;
+ sizes[TYUINT] = sizes[TYINT] = 4;
+ sizes[TYFLOAT] = 4;
+ sizes[TYDOUBLE] = 8;
+ sizes[TYLDOUBLE] = 8;
+ memcpy(align, sizes, sizeof targ_primalign);
+ sizes[TYULONG] = sizes[TYLONG] = t->longsize;
+ sizes[TYUVLONG] = sizes[TYVLONG] = t->vlongsize;
+ sizes[TYPTR] = t->ptrsize;
+ align[TYULONG] = align[TYLONG] = t->longalign;
+ align[TYUVLONG] = align[TYVLONG] = t->vlongalign;
+ align[TYDOUBLE] = t->doublealign;
+ align[TYLDOUBLE] = t->doublealign;
+ align[TYPTR] = t->ptralign;
+ sizes[TYCOMPLEXF] = sizes[TYFLOAT]*2;
+ sizes[TYCOMPLEX] = sizes[TYDOUBLE]*2;
+ sizes[TYCOMPLEXL] = sizes[TYLDOUBLE]*2;
+ align[TYCOMPLEXF] = align[TYFLOAT];
+ align[TYCOMPLEX] = align[TYDOUBLE];
+ align[TYCOMPLEXL] = align[TYLDOUBLE];
+ targ_valistsize = t->valistsize;
+ targ_sizetype = t->sizetype;
+ targ_ptrdifftype = t->ptrdifftype;
+ targ_wchartype = t->wchartype;
+ targ_charsigned = t->charsigned;
+ targ_bigendian = 0;
+ targ_64bit = t->ptrsize == 8;
+ mctarg = t->mctarg;
+ targ_arch = ISx86_64;
+}
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 ('=' <ini>) and
+ call pdecl() to advance state before checking .more */
+ funcdef, /* caller should parse an func definition ('{' <body> '}').
+ the declaration list is finished. */
+ bitf, /* caller should parse a bitfield size and
+ call pdecl() to advance state before checking .more */
+ tagdecl, /* declarator is a tagged type */
+ empty; /* nothing decl (';') */
+ internstr *pnames; /* param names for function definition */
+ struct span *pspans; /* param spans ditto */
+ uchar *pqual; /* param quals ditto */
+ int attr;
+};
+static struct decl pdecl(struct declstate *st, struct comp *cm);
+
+static struct decl *finddecl(struct comp *cm, internstr name);
+
+/* next token starts a decl? */
+static bool
+isdecltok(struct comp *cm)
+{
+ struct token tk;
+ if (peek(cm, &tk) == TKIDENT) {
+ struct decl *decl = finddecl(cm, tk.name);
+ return decl && decl->scls == SCTYPEDEF;
+ } else {
+ static const bool kws[] = {
+#define kw(x) [TKW##x-TKWBEGIN_] = 1
+ kw(auto), kw(extern), kw(static), kw(register), kw(typedef),
+ kw(_Thread_local), kw(thread_local), kw(_Static_assert),
+ kw(inline), kw(_Noreturn),
+ kw(const), kw(volatile), kw(restrict), kw(_Atomic),
+ kw(void), kw(float), kw(double), kw(_Complex),
+ kw(signed), kw(unsigned), kw(short), kw(long),
+ kw(int), kw(char), kw(_Bool), kw(bool),
+ kw(struct), kw(union), kw(enum),
+ kw(__typeof__), kw(typeof), kw(typeof_unqual),
+ kw(__attribute__)
+#undef kw
+ };
+ return ((uint)tk.t-TKWBEGIN_) < countof(kws) && kws[tk.t-TKWBEGIN_];
+ }
+}
+
+/* next token starts an expr? */
+static bool
+isexprtok(struct comp *cm)
+{
+ struct token tk;
+ if (peek(cm, &tk) == TKIDENT) {
+ struct decl *decl = finddecl(cm, tk.name);
+ return !decl || decl->scls != SCTYPEDEF;
+ } else {
+ static const bool tks[] = {
+#define tk(x) [x] = 1
+ tk('+'), tk('-'), tk('*'), tk('&'), tk('~'), tk('!'), tk(TKINC), tk(TKDEC),
+ tk(TKWsizeof), tk(TKW_Alignof), tk(TKWalignof), tk(TKWtrue), tk(TKWfalse),
+ tk('('), tk(TKNUMLIT), tk(TKCHRLIT), tk(TKSTRLIT), tk(TKW_Generic)
+#undef tk
+ };
+ return tk.t < countof(tks) && tks[tk.t];
+ }
+}
+
+/**********************************/
+/* Environment (scope) management */
+/**********************************/
+
+struct envdecls declsbuf;
+struct tagged { /* a tagged type declaration */
+ union type ty;
+ struct span span;
+};
+static struct tagged envtaggedbuf[1<<7];
+static vec_of(struct tagged) envtagged = VINIT(envtaggedbuf, countof(envtaggedbuf));
+struct env {
+ struct env *up;
+ /* list of decls is implicitly envdecls[decl..ndecl] */
+ ushort decl, ndecl;
+ /* ditto for envtagged[] */
+ ushort tagged, ntagged;
+};
+/* use a hashmap for lookups of top-level declarations, since there's usually many of those */
+static pmap_of(ushort) tldeclmap;
+
+static void
+envdown(struct comp *cm, struct env *e)
+{
+ assert(cm->env->decl + cm->env->ndecl == declsbuf.n);
+ assert(cm->env->tagged + cm->env->ntagged == envtagged.n);
+ e->decl = declsbuf.n;
+ e->tagged = envtagged.n;
+ e->ndecl = e->ntagged = 0;
+ e->up = cm->env;
+ cm->env = e;
+}
+
+static void
+envup(struct comp *cm)
+{
+ struct env *env = cm->env;
+ assert(env->decl + env->ndecl == declsbuf.n);
+ declsbuf.n -= env->ndecl;
+ envtagged.n -= env->ntagged;
+ assert(env->up);
+ cm->env = env->up;
+}
+
+int
+envadddecl(struct env *env, const struct decl *d)
+{
+ assert(env->decl + env->ndecl == declsbuf.n);
+ vpush(&declsbuf, *d);
+ assert(declsbuf.n < 1<<16);
+ ++env->ndecl;
+ if (!env->up) pmap_set(&tldeclmap, d->name, declsbuf.n-1);
+ return declsbuf.n - 1;
+}
+
+/* iters in reversed order of insertion (most to least recent) */
+/* use like so: for (d = NULL; enviterdecl(&d, env);) ... */
+static inline bool
+enviterdecl(struct decl **d, struct env *env)
+{
+ if (!env->ndecl) return 0;
+ if (!*d) *d = &declsbuf.p[env->decl + env->ndecl - 1];
+ else if (*d == &declsbuf.p[env->decl]) return 0;
+ else --*d;
+ return 1;
+}
+
+static struct tagged *
+envaddtagged(struct env *env, union type ty, const struct span *span)
+{
+ struct tagged tagged = { ty, *span };
+ assert(env->tagged + env->ntagged == envtagged.n);
+ vpush(&envtagged, tagged);
+ assert(envtagged.n < 1<<16);
+ ++env->ntagged;
+ return &envtagged.p[envtagged.n - 1];
+}
+
+/* like enviterdecl */
+static inline bool
+envitertagged(struct tagged **l, struct env *env)
+{
+ if (!env->ntagged) return 0;
+ if (!*l) *l = &envtagged.p[env->tagged + env->ntagged - 1];
+ else if (*l == &envtagged.p[env->tagged]) return 0;
+ else --*l;
+ return 1;
+}
+
+static bool
+redeclarationok(const struct decl *old, const struct decl *new)
+{
+ bool takeoldscls = 0;
+ if (old->scls != new->scls) {
+ if (old->scls == SCSTATIC && (new->scls &~ SCEXTERN) == SCNONE && old->ty.t == TYFUNC && new->ty.t == TYFUNC)
+ takeoldscls = 1;
+ else
+ return 0;
+ }
+ switch (old->scls) {
+ case SCSTATIC:
+ case SCEXTERN:
+ if (old->ty.bits == new->ty.bits) goto OkFuncs;
+ if (old->ty.t != new->ty.t) return 0;
+ if (old->ty.t == TYARRAY /* allow 'int x[]; int x[100];' */
+ && typechild(old->ty).bits == typechild(new->ty).bits
+ && (isincomplete(old->ty) || isincomplete(new->ty)))
+ {
+ return 1;
+ }
+ if (old->ty.t == TYFUNC /* allow 'int f(); int f(int);' (some K&R) */
+ && typedata[old->ty.dat].ret.bits == typedata[new->ty.dat].ret.bits
+ && (typedata[old->ty.dat].kandr || typedata[new->ty.dat].kandr))
+ { OkFuncs:
+ if (takeoldscls) ((struct decl *)new)->scls = old->scls;
+ return 1;
+ }
+ return 0;
+ case SCTYPEDEF:
+ return old->ty.bits == new->ty.bits;
+ }
+ return 0;
+}
+
+static int
+putdecl(struct comp *cm, const struct decl *decl)
+{
+ for (struct env *env = cm->env; env; env = env->up) {
+ struct decl *l;
+ if (!env->up) {
+ ushort *pi = pmap_get(&tldeclmap, decl->name);
+ if (pi) {
+ l = &declsbuf.p[*pi];
+ goto Match;
+ }
+ } else for (l = NULL; enviterdecl(&l, env);) {
+ if (decl->name == l->name) {
+ Match:
+ if ((cm->env->up != NULL && decl->scls == SCSTATIC) || (l->isdef && decl->isdef)) {
+ error(&decl->span, "redefinition of '%s'", decl->name);
+ note(&l->span, "previously defined here");
+ break;
+ } else if (!redeclarationok(l, decl)) {
+ error(&decl->span, "incompatible redeclaration of '%s'", decl->name);
+ note(&l->span, "previously declared here");
+ break;
+ }
+ if (l->isdef && !decl->isdef) return l - declsbuf.p;
+ break;
+ }
+ }
+ if (decl->scls != SCEXTERN) break;
+ }
+ return envadddecl(cm->env, decl);
+}
+
+static struct decl *
+finddecl(struct comp *cm, internstr name)
+{
+ assert(name);
+ for (struct env *e = cm->env; e; e = e->up) {
+ if (!e->up) {
+ ushort *pi = pmap_get(&tldeclmap, name);
+ if (pi) return &declsbuf.p[*pi];
+ } else for (struct decl *l = NULL; enviterdecl(&l, e);) {
+ if (name == l->name)
+ return l;
+ }
+ }
+ return NULL;
+}
+
+static union type
+gettagged(struct comp *cm, struct span *span, enum typetag tt, internstr name, bool dodef)
+{
+ struct typedata td = {0};
+ assert(name);
+ for (struct env *e = cm->env; e; e = e->up) {
+ for (struct tagged *l = NULL; envitertagged(&l, e);) {
+ if (name == ttypenames[typedata[l->ty.dat].id]) {
+ if (dodef && e != cm->env)
+ goto Break2;
+ *span = l->span;
+ return l->ty;
+ }
+ }
+ }
+ if (tt == TYENUM && ccopt.pedant) {
+ warn(span, "forward-declared enum is an extension");
+ }
+Break2:
+ td.t = tt;
+ return envaddtagged(cm->env, mktagtype(name, &td), span)->ty;
+}
+
+static union type
+deftagged(struct comp *cm, struct span *span, enum typetag tt, internstr name, union type ty)
+{
+ struct typedata td = {0};
+ assert(name);
+ for (struct tagged *l = NULL; envitertagged(&l, cm->env);) {
+ if (name == ttypenames[typedata[l->ty.dat].id]) {
+ *span = l->span;
+ return l->ty;
+ }
+ }
+ td.t = tt;
+ return envaddtagged(cm->env, ty.t ? ty : mktagtype(name, &td), span)->ty;
+}
+
+/*********************/
+/* Expr Typechecking */
+/*********************/
+
+#define iszero(ex) ((ex).t == ENUMLIT && isint((ex).ty) && (ex).u == 0)
+
+static bool
+islvalue(const struct expr *ex)
+{
+ if (ex->t == EGETF) return islvalue(ex->sub);
+ return ex->t == ESYM || ex->t == EDEREF || ex->t == EINIT || ex->t == ESTRLIT;
+}
+
+static union type /* 6.5.2.6 default argument promotions */
+argpromote(union type t)
+{
+ if (isint(t)) t.t = intpromote(t.t);
+ else if (t.t == TYFLOAT) t.t = TYDOUBLE;
+ else if (t.t == TYARRAY) return mkptrtype(typechild(t), t.flag & TFCHLDQUAL);
+ else if (t.t == TYFUNC) return mkptrtype(t, 0);
+ return t;
+}
+
+bool
+assigncheck(union type t, const struct expr *src)
+{
+ union type srcty = typedecay(src->ty);;
+ if (assigncompat(t, srcty)) {
+ if (t.t == TYPTR && srcty.t == TYPTR
+ && (t.flag & TFCHLDQUAL & srcty.flag & TFCHLDQUAL) != (srcty.flag & TFCHLDQUAL)) {
+ warn(&src->span, "usage of '%ty' discards pointer qualifiers", src->ty);
+ }
+ return 1;
+ } else if (t.t == TYPTR && srcty.t == TYPTR) {
+ warn(&src->span, "converting between incompatible pointer types ('%ty' -> '%ty')", srcty, t);
+ return 1;
+ } else if (t.t == TYPTR && iszero(*src)) return 1;
+ return 0;
+}
+
+static bool
+initcheck(union type t, const struct expr *src)
+{
+ if (assigncheck(t, src)) return 1;
+ if (t.bits == src->ty.bits && (src->t == EINIT || src->t == ESTRLIT)) return 1;
+ return 0;
+}
+
+static void
+incdeccheck(enum toktag tt, const struct expr *ex, const struct span *span)
+{
+ if (!isscalar(ex->ty))
+ error(&ex->span, "invalid operand to %tt '%ty'", tt, ex->ty);
+ else if (!islvalue(ex))
+ error(&ex->span, "operand to %tt is not an lvalue", tt);
+ else if (ex->ty.t == TYPTR && isincomplete(typechild(ex->ty)))
+ error(span, "arithmetic on pointer to incomplete type '%ty'", ex->ty);
+ else if (ex->ty.t == TYPTR && typechild(ex->ty).t == TYFUNC)
+ error(span, "arithmetic on function pointer '%ty'", ex->ty);
+}
+
+static bool /* 6.5.4 Cast operators */
+castcheck(union type to, const struct expr *ex)
+{
+ union type src = ex->ty;
+ if (to.t == TYVOID) return 1;
+ if (isagg(to)) return 0;
+ if (to.bits == src.bits) return 1;
+ if (isarith(to) && isarith(src)) return 1;
+ if (isint(to) && isptrcvt(src)) return 1;
+ if (to.t == TYPTR && isint(src)) return 1;
+ if (to.t == TYPTR && isptrcvt(src)) return 1;
+ return 0;
+}
+
+static union type /* 6.5.2.1 Array subscripting */
+subscriptcheck(const struct expr *ex, const struct expr *rhs, const struct span *span)
+{
+ union type ty;
+ if (ex->ty.t == TYPTR || ex->ty.t == TYARRAY) {
+ if (isincomplete(typedecay(ty = typechild(ex->ty)))) {
+ error(span, "cannot dereference pointer to incomplete type '%ty'", ty);
+ ty = mktype(TYINT);
+ } else if (ty.t == TYFUNC) {
+ error(span, "subscripted value is pointer to function");
+ ty = mktype(TYINT);
+ }
+ } else {
+ error(&ex->span, "subscripted value is not pointer-convertible '%ty'", ex->ty);
+ ty = mktype(TYINT);
+ }
+ if (!isint(rhs->ty))
+ error(&rhs->span, "array subscript is not integer ('%ty')", rhs->ty);
+ return ty;
+}
+
+static uint /* 6.5.3.4 The sizeof and _Alignof operators */
+sizeofalignofcheck(const struct span *span, enum toktag tt, union type ty, const struct expr *ex)
+{
+ uint r = (tt == TKWsizeof ? typesize : typealign)(ty);
+ if (ty.t == TYVOID) {
+ if (ccopt.pedant) warn(span, "applying %'tt to void type", tt);
+ r = 1;
+ } else if (isincomplete(ty)) {
+ error(span, "cannot apply %'tt to incomplete type '%ty'", tt, ty);
+ } else if (ty.t == TYFUNC) {
+ error(span, "cannot apply %'tt to function type '%ty'", tt, ty);
+ } else if (tt == TKWsizeof && ex && ex->t == EGETF && ex->fld.bitsiz) {
+ error(span, "cannot apply %'tt to bitfield", tt);
+ }
+ if (tt != TKWsizeof && ex && ccopt.pedant)
+ warn(span, "%'tt applied to an expression is a GNU extension", tt);
+ return r;
+}
+
+static bool /* 6.5.8 Relational operators */
+relationalcheck(const struct expr *a, const struct expr *b)
+{
+ union type t1 = a->ty, t2 = b->ty;
+ if (isarith(t1) && isarith(t2)) return 1;
+ if (isptrcvt(t1) && isptrcvt(t2)) {
+ t1 = typedecay(t1);
+ t2 = typedecay(t2);
+ return t1.dat == t2.dat;
+ }
+ return 0;
+}
+
+static bool
+isnullpo(const struct expr *ex) /* match '0' or '(void *) 0' */
+{
+ static const union type voidptr = {{ TYPTR, .flag = TFCHLDPRIM, .child = TYVOID }};
+ while (ex->t == ECAST && ex->ty.bits == voidptr.bits)
+ ex = ex->sub;
+ if (iszero(*ex)) return 1;
+ return eval((struct expr *)ex, EVINTCONST) /* GNU extension. should we warn? */
+ && iszero(*ex);
+}
+
+static bool /* 6.5.9 Equality operators */
+equalitycheck(const struct expr *a, const struct expr *b)
+{
+ union type t1 = a->ty, t2 = b->ty;
+ if (isarith(t1) && isarith(t2)) return 1;
+ if (isptrcvt(t1) && isptrcvt(t2)) {
+ t1 = typedecay(t1), t2 = typedecay(t2);
+ /* comparing .dat works for both TFCHLDPRIM and not, (checks equal child types)
+ * quals are ignored either way */
+ return t1.dat == t2.dat || typechild(t1).t == TYVOID || typechild(t2).t == TYVOID;
+ }
+ return (isptrcvt(t1) && isnullpo(b)) || (isptrcvt(t2) && isnullpo(a));
+}
+
+static union type /* 6.5.15 Conditional operator */
+condtype(const struct expr *a, const struct expr *b)
+{
+ union type t1 = typedecay(a->ty), t2 = typedecay(b->ty), s1, s2;
+ if (isarith(t1) && isarith(t2)) return cvtarith(t1, t2);
+ if (t1.bits == t2.bits) return t1;
+ if (t1.t == TYPTR && isnullpo(b)) return t1;
+ if (isnullpo(a) && t2.t == TYPTR) return t2;
+ if (t1.t == TYPTR && t2.t == TYPTR) {
+ s1 = typechild(t1), s2 = typechild(t2);
+ if (s1.bits == s2.bits || s2.t == TYVOID || s1.t == TYVOID) {
+ return mkptrtype(s1.t == TYVOID ? s1 : s2, (t1.flag | t2.flag) & TFCHLDQUAL);
+ }
+ }
+ return mktype(0);
+}
+
+static void
+bintypeerr(const struct span *span, enum toktag tt, union type lhs, union type rhs)
+{
+ error(span, "bad operands to %tt ('%ty', '%ty')", tt, lhs, rhs);
+}
+
+enum binopclass { /* binary operator type-checking classes */
+ BCSET = 1<<7, /* is a (compound) assignment operator? */
+ BCSEQ = 1, BCADDITIVE, BCARITH, BCINT, BCSHFT, BCEQL, BCCMP, BCLOG,
+};
+
+/* table indexed by binary op token;
+ * containing precedence level, expression kind and type-checking class */
+static const struct { uchar prec, t, k; } bintab[] = {
+ ['*'] = {13, EMUL, BCARITH},
+ ['/'] = {13, EDIV, BCARITH},
+ ['%'] = {13, EREM, BCINT},
+ ['+'] = {12, EADD, BCADDITIVE},
+ ['-'] = {12, ESUB, BCADDITIVE},
+ [TKSHL] = {11, ESHL, BCSHFT},
+ [TKSHR] = {11, ESHR, BCSHFT},
+ ['<'] = {10, ELTH, BCCMP},
+ ['>'] = {10, EGTH, BCCMP},
+ [TKLTE] = {10, ELTE, BCCMP},
+ [TKGTE] = {10, EGTE, BCCMP},
+ [TKEQU] = {9, EEQU, BCEQL},
+ [TKNEQ] = {9, ENEQ, BCEQL},
+ ['&'] = {8, EBAND, BCINT},
+ ['^'] = {7, EXOR, BCINT},
+ ['|'] = {6, EBIOR, BCINT},
+ [TKLOGAND] = {5, ELOGAND, BCLOG},
+ [TKLOGIOR] = {4, ELOGIOR, BCLOG},
+ ['?'] = {3, ECOND}, /* not actually a binop (special cased) */
+ ['='] = {2, ESET, BCSET},
+ [TKSETADD] = {2, ESETADD, BCSET|BCADDITIVE}, [TKSETSUB] = {2, ESETSUB, BCSET|BCADDITIVE},
+ [TKSETMUL] = {2, ESETMUL, BCSET|BCARITH}, [TKSETDIV] = {2, ESETDIV, BCSET|BCARITH},
+ [TKSETREM] = {2, ESETREM, BCSET|BCINT}, [TKSETAND] = {2, ESETAND, BCSET|BCINT},
+ [TKSETIOR] = {2, ESETIOR, BCSET|BCINT}, [TKSETXOR] = {2, ESETXOR, BCSET|BCINT},
+ [TKSETSHL] = {2, ESETSHL, BCSET|BCSHFT}, [TKSETSHR] = {2, ESETSHR, BCSET|BCSHFT},
+ [','] = {1, ESEQ, BCSEQ}
+};
+
+static union type
+bintypecheck(const struct span *span, enum toktag tt, struct expr *lhs, struct expr *rhs)
+{
+ enum binopclass k = bintab[tt].k;
+ union type ty = lhs->ty;
+
+ assert(k);
+ if (k & BCSET) {
+ if (!islvalue(lhs))
+ error(&lhs->span, "left-hand-side of assignment is not an lvalue");
+ else if (lhs->qual & QCONST)
+ error(&lhs->span, "cannot assign to const-qualified lvalue (%tq)", ty, lhs->qual);
+ else if (isincomplete(ty))
+ error(&lhs->span, "cannot assign to incomplete type '%ty'", ty);
+ else if (ty.t == TYARRAY)
+ error(&lhs->span, "cannot assign to array type '%ty'", ty);
+ else if (ty.t == TYFUNC)
+ error(&lhs->span, "cannot assign to function designator '%ty'", lhs->ty);
+ }
+ switch (k &~ BCSET) {
+ case 0:
+ if (isagg(ty) && !(lhs->qual & QCONST) && typedata[ty.dat].anyconst)
+ error(&lhs->span, "cannot assign to aggregate with const-qualified member");
+ if (!assigncheck(ty, rhs))
+ goto Error;
+ break;
+ case BCSEQ:
+ ty = rhs->ty;
+ break;
+ case BCADDITIVE:
+ if (tt == '+' && isptrcvt(rhs->ty)) {
+ /* int + ptr -> ptr + int (for convenience) */
+ const struct expr swaptmp = *lhs;
+ *lhs = *rhs;
+ *rhs = swaptmp;
+ ty = lhs->ty;
+ }
+ if (isarith(ty) && isarith(rhs->ty)) {
+ /* num +/- num */
+ ty = cvtarith(ty, rhs->ty);
+ assert(ty.t);
+ } else if ((ty.t == TYPTR || ty.t == TYARRAY) && isint(rhs->ty)) {
+ /* ptr +/- int */
+ union type pointee = typechild(ty);
+ if (isincomplete(pointee))
+ error(span, "arithmetic on pointer to incomplete type '%ty'", ty);
+ else if (pointee.t == TYFUNC)
+ error(span, "arithmetic on function pointer '%ty'", ty);
+ ty = typedecay(ty);
+ } else if (tt == '-' && isptrcvt(ty) && isptrcvt(rhs->ty)) {
+ /* ptr - ptr */
+ union type pointee1 = typechild(typedecay(ty)),
+ pointee2 = typechild(typedecay(rhs->ty));
+ if (isincomplete(pointee1))
+ error(span, "arithmetic on pointer to incomplete type '%ty'", ty);
+ else if (pointee1.t == TYFUNC)
+ error(span, "arithmetic on function pointer '%ty'", lhs->ty);
+ else if (pointee1.bits != pointee2.bits) {
+ error(span, "arithmetic on incompatible pointer types: '%ty', '%ty'",
+ ty, rhs->ty);
+ }
+ ty = mktype(targ_ptrdifftype);
+ } else goto Error;
+ break;
+ case BCARITH:
+ ty = cvtarith(ty, rhs->ty);
+ if (!ty.t) {
+ ty.t = TYINT;
+ Error:
+ bintypeerr(span, tt, lhs->ty, rhs->ty);
+ }
+ break;
+ case BCINT:
+ if (!isint(ty) || !isint(rhs->ty))
+ goto Error;
+ ty = cvtarith(ty, rhs->ty);
+ assert(ty.t);
+ break;
+ case BCSHFT: /* 6.5.7 Bitwise shift operators */
+ if (!isint(ty) || !isint(rhs->ty)) {
+ ty = mktype(TYINT);
+ goto Error;
+ }
+ ty.t = intpromote(ty.t);
+ assert(ty.t);
+ break;
+ case BCEQL:
+ ty = mktype(TYINT);
+ if (!equalitycheck(lhs, rhs)) {
+ if (isptrcvt(lhs->ty) && isptrcvt(rhs->ty))
+ warn(span, "comparison of distinct pointer types ('%ty' and '%ty')", lhs->ty, rhs->ty);
+ else
+ goto Error;
+ }
+ break;
+ case BCCMP:
+ ty = mktype(TYINT);
+ if (!relationalcheck(lhs, rhs)) {
+ if (isptrcvt(lhs->ty) && isptrcvt(rhs->ty))
+ warn(span, "comparison of distinct pointer types ('%ty' and '%ty')", lhs->ty, rhs->ty);
+ else
+ goto Error;
+ }
+ break;
+ case BCLOG: /* 6.5.13-14 Logical AND/OR operator */
+ ty = mktype(TYINT);
+ if (!isscalar(typedecay(ty)) || !isscalar(typedecay(rhs->ty)))
+ goto Error;
+ break;
+ }
+ return (k & BCSET) || !ty.t ? lhs->ty : ty;
+}
+
+/****************/
+/* Expr Parsing */
+/****************/
+
+#define mkexpr(t_,span_,ty_,...) ((struct expr){.t=(t_), .ty=(ty_), .span=(span_), __VA_ARGS__})
+
+static struct expr *
+exprdup(struct comp *cm, const struct expr *e)
+{
+ return alloccopy(&cm->exarena, e, sizeof *e, 0);
+}
+static struct expr *
+exprdup2(struct comp *cm, const struct expr *e1, const struct expr *e2)
+{
+ struct expr *r = alloc(&cm->exarena, 2*sizeof *r, 0);
+ r[0] = *e1, r[1] = *e2;
+ return r;
+}
+
+static struct expr expr(struct comp *cm);
+static struct expr commaexpr(struct comp *cm);
+
+enum { IMPLICITSYMTY = 0xFF, };
+
+static struct expr /* 6.5.2.2 Function calls */
+callexpr(struct comp *cm, const struct span *span_, const struct expr *callee)
+{
+ struct token tk;
+ struct expr ex, arg;
+ struct span span = callee->span;
+ union type ty = callee->ty;
+ const struct typedata *td = NULL;
+ struct expr argbuf[10];
+ vec_of(struct expr) args = VINIT(argbuf, countof(argbuf));
+ bool spanok = joinspan(&span.ex, span_->ex);
+ bool printsig = 0;
+ const struct builtin *builtin = NULL;
+
+ if (callee->t == ESYM && !callee->ty.t && declsbuf.p[callee->decl].isbuiltin) {
+ builtin = declsbuf.p[callee->decl].builtin;
+ assert(!ty.t);
+ }
+
+ if (callee->t == ESYM && ty.t == IMPLICITSYMTY) { /* implicit function decl.. */
+ internstr name = callee->implicitsym;
+ struct decl decl = {
+ (ty = mkfntype(mktype(TYINT), 0, NULL, /* kandr */ 1, 0)),
+ .scls = SCEXTERN, .span = span, .name = name, .sym = name
+ };
+ warn(&span, "call to undeclared function '%s'", name);
+ ((struct expr *)callee)->ty = decl.ty;
+ ((struct expr *)callee)->decl = putdecl(cm, &decl);
+ }
+
+ if (!builtin) {
+ if (ty.t == TYPTR) /* auto-deref when calling a function pointer */
+ ty = typechild(ty);
+ if (ty.t != TYFUNC)
+ error(&span, "calling a value of type '%ty'", callee->ty);
+ else
+ td = &typedata[ty.dat];
+ if (ty.t == TYFUNC && td->ret.t != TYVOID && isincomplete(td->ret))
+ error(&span, "cannot call function with incomplete return type '%ty'", td->ret);
+ }
+
+ if (!match(cm, &tk, ')')) for (;;) {
+ arg = expr(cm);
+ spanok = spanok && joinspan(&span.ex, callee->span.ex);
+ if (td && args.n == td->nmemb && !td->variadic && !td->kandr) {
+ error(&arg.span, "too many args to function taking %d params", td->nmemb);
+ printsig = 1;
+ }
+ if (arg.ty.t == TYVOID) {
+ error(&arg.span, "invalid use of void expression");
+ } else if (td && args.n < td->nmemb && !td->kandr) {
+ if (!assigncheck(td->param[args.n], &arg)) {
+ error(&arg.span, "arg #%d of type '%ty' is incompatible with '%ty'",
+ args.n+1, arg.ty, td->param[args.n]);
+ printsig = 1;
+ }
+ }
+ vpush(&args, arg);
+ peek(cm, &tk);
+ if (match(cm, &tk, ',')) {
+ spanok = spanok && joinspan(&span.ex, tk.span.ex);
+ } else if (expect(cm, ')', "or ',' after arg")) {
+ break;
+ }
+ }
+ if (!spanok || !joinspan(&span.ex, tk.span.ex)) span = *span_;
+
+ if (td && !td->variadic && !td->kandr && args.n < td->nmemb) {
+ error(&tk.span, "not enough args to function taking %d param%s",
+ td->nmemb, td->nmemb != 1 ? "s" : "");
+ printsig = 1;
+ }
+ if (printsig) note(&callee->span, "function signature is '%ty'", ty);
+
+ ex = mkexpr(ECALL, span, ty.t == TYFUNC ? td->ret : ty, .narg = args.n,
+ .sub = alloc(&cm->exarena, (args.n+1)*sizeof(struct expr), 0));
+ ex.sub[0] = *callee;
+ memcpy(ex.sub+1, args.p, args.n*sizeof(struct expr));
+ vfree(&args);
+ if (builtin) {
+ builtin->sema(cm, &ex);
+ }
+ return ex;
+}
+
+static void
+ppostfixopers(struct comp *cm, struct expr *ex)
+{
+ struct expr tmp, rhs;
+ struct token tk, tk2;
+ struct span span;
+ union type ty;
+
+ for (;;) switch (peek(cm, &tk)) {
+ default: return;
+ case TKINC:
+ case TKDEC:
+ lex(cm, &tk);
+ span = ex->span;
+ if (!joinspan(&span.ex, tk.span.ex)) span = tk.span;
+ incdeccheck(tk.t, ex, &span);
+ *ex = mkexpr(tk.t == TKINC ? EPOSTINC : EPOSTDEC, span, ex->ty, .sub = exprdup(cm, ex));
+ continue;
+ case '[': /* a[subscript] */
+ lex(cm, NULL);
+ rhs = commaexpr(cm);
+ span = ex->span;
+ if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, ex->span.ex)
+ || (peek(cm, &tk), !joinspan(&span.ex, tk.span.ex)))
+ span = tk.span;
+ expect(cm, ']', NULL);
+
+ if (isint(ex->ty) && isptrcvt(rhs.ty)) {
+ /* swap idx[ptr] -> ptr[idx] */
+ tmp = *ex;
+ *ex = rhs;
+ rhs = tmp;
+ }
+
+ ty = subscriptcheck(ex, &rhs, &span);
+ assert(ty.t);
+ if (!iszero(rhs)) {
+ tmp.sub = exprdup2(cm, ex, &rhs);
+ tmp.t = EADD;
+ tmp.span = span;
+ tmp.ty = typedecay(ex->ty);
+ }
+ tmp.sub = exprdup(cm, iszero(rhs) ? ex : &tmp);
+ tmp.span = span;
+ tmp.t = EDEREF;
+ tmp.qual = ex->ty.flag & TFCHLDQUAL;
+ tmp.ty = ty;
+ *ex = tmp;
+ continue;
+ case '(': /* call(args) */
+ lex(cm, &tk);
+ span = ex->span;
+ *ex = callexpr(cm, &span, ex);
+ continue;
+ case TKARROW:
+ if (ex->ty.t != TYPTR && ex->ty.t != TYARRAY)
+ error(&ex->span, "operand to -> is not a pointer: '%ty'", ex->ty);
+ else
+ *ex = mkexpr(EDEREF, ex->span, typechild(ex->ty), .qual = ex->ty.flag & TFCHLDQUAL,
+ .sub = exprdup(cm, ex));
+ /* fallthru */
+ case '.':
+ lex(cm, &tk);
+ span = ex->span;
+ peek(cm, &tk2); /* field name */
+ if (!expect(cm, TKIDENT, NULL)) tk2.s = "";
+ if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, tk2.span.ex))
+ span = tk.span;
+ if (!isagg(ex->ty)) {
+ error(&span, "member access operand is not an aggregate: '%ty'%s", ex->ty,
+ ex->ty.t == TYPTR && isagg(typechild(ex->ty)) ? "; did you mean to use '->'?" : "");
+ } else {
+ struct fielddata fld = {.t = mktype(TYINT)};
+ if (*tk2.s && !getfield(&fld, ex->ty, tk2.name))
+ error(&span, "'%ty' has no such field: '%s'", ex->ty, tk2.name);
+ if (ex->t == EGETF && ex->qual == fld.qual) { /* accumulate */
+ ex->span = span;
+ ex->ty = fld.t;
+ ex->fld.off += fld.off;
+ ex->fld.bitoff = fld.bitoff;
+ ex->fld.bitsiz = fld.bitsiz;
+ } else {
+ *ex = mkexpr(EGETF, span, fld.t, .qual = ex->qual | fld.qual, .sub = exprdup(cm, ex),
+ .fld = { fld.off, fld.bitsiz, fld.bitoff });
+ }
+ }
+ continue;
+ }
+}
+
+static struct expr
+vaargexpr(struct comp *cm, struct span *span)
+{
+ struct token tk;
+ struct expr ex = mkexpr(EXXX, *span, mktype(TYVOID), );
+ if (expect(cm, '(', "after __builtin_va_arg")) {
+ struct expr arg = expr(cm);
+ struct decl decl;
+ union type ty;
+ expect(cm, ',', NULL);
+ decl = pdecl(&(struct declstate){DCASTEXPR}, cm);
+ ty = decl.ty;
+ peek(cm, &tk);
+ if (expect(cm, ')', NULL))
+ joinspan(&span->ex, tk.span.ex);
+ if (ty.t == TYARRAY)
+ warn(&decl.span, "va_arg type argument is array type '%ty', which is undefined behavior", decl.ty);
+ else if (ty.t == TYFUNC)
+ error(&decl.span, "va_arg type argument is function type '%ty'", decl.ty);
+ else {
+ ty = argpromote(ty);
+ if (ty.bits != decl.ty.bits) {
+ warn(&decl.span,
+ "va_arg type argument is promotable type '%ty', which has undefined behavior"
+ " (it will be promoted to '%ty')", decl.ty, ty);
+ }
+ }
+ ex = mkexpr(EVAARG, *span, decl.ty, .sub = exprdup(cm, &arg));
+ }
+ return ex;
+}
+
+static struct expr
+genericexpr(struct comp *cm, struct span *span)
+{
+ struct token tk;
+ if (expect(cm, '(', "after _Generic")) {
+ struct expr control = expr(cm), dfault = {0}, ex = {0};
+ expect(cm, ',', NULL);
+ for (;;) {
+ if (match(cm, &tk, TKWdefault)) {
+ expect(cm, ':', NULL);
+ if (dfault.t) {
+ error(&tk.span, "duplicate 'default' specifier in generic selection expression");
+ (void)expr(cm);
+ } else {
+ dfault = expr(cm);
+ }
+ } else {
+ struct decl decl = pdecl(&(struct declstate){DCASTEXPR}, cm);
+ union type ty = decl.ty;
+ expect(cm, ':', NULL);
+ if (!ex.t &&
+ (typedecay(ty).bits == typedecay(control.ty).bits
+ || ((ty.t == TYENUM || control.ty.t == TYENUM) && scalartypet(ty) == scalartypet(control.ty))))
+ ex = expr(cm);
+ else
+ (void)expr(cm);
+ }
+ if (match(cm, &tk, ')')) break;
+ else if (!expect(cm, ',', "or `)'")) {
+ if (!isdecltok(cm)) {
+ peek(cm, &tk); /* want the span */
+ break;
+ }
+ }
+ }
+ if (!ex.t) ex = dfault;
+ if (!ex.t) {
+ error(&control.span,
+ "controlling type '%ty' not compatible with any generic association type",
+ control.ty);
+ ex.ty.t = TYINT;
+ }
+ ex.span = *span;
+ joinspan(&ex.span.ex, tk.span.ex);
+ return ex;
+ }
+ return mkexpr(ENUMLIT,*span,mktype(TYINT),);
+}
+
+static inline int
+tkprec(int tt)
+{
+ return ((uint)tt < countof(bintab)) ? bintab[tt].prec : 0;
+}
+
+static struct expr initializer(struct comp *cm, union type *ty, enum evalmode ev,
+ bool globl, enum qualifier qual, internstr name);
+
+static internstr istr__func__, istr_main, istr_memset;
+
+static internstr mkhiddensym(const char *fnname, const char *name, int id);
+
+/* parse an expression with the given operator precedence */
+/* param ident is a kludge to support block labels without backtracking or extra lookahead
+ * see stmt() */
+enum exprctx { EFROMSTMT = 1, EARRAYCOUNT, EATTRARG };
+static struct expr
+exprparse(struct comp *cm, int prec, const struct token *ident, enum exprctx ctx)
+{
+ struct token tk;
+ struct span span;
+ struct expr ex;
+ union type ty;
+ struct {
+ struct span span;
+ union {
+ union type ty; /* cast type */
+ struct {
+ uchar t0; /* t == 0 */
+ short tt; /* token */
+ };
+ };
+ } unops[4];
+ int nunop = 0;
+
+ if (ident) {
+ assert(ident->t == TKIDENT);
+ tk = *ident;
+ ident = NULL;
+ goto Ident;
+ }
+
+Unary:
+ switch (lex(cm, &tk)) {
+ /* unary operators (gather) */
+ case '*':
+ if (ctx == EARRAYCOUNT && peek(cm, NULL) == ']') {
+ /* kludge for C99 `int x[*]` (unk VLA size) */
+ return (struct expr) { 0, .span = tk.span };
+ }
+ /* fallthru */
+ case '+': case '-': case '~': case '!':
+ case '&': case TKINC: case TKDEC:
+ Unops:
+ unops[nunop].span = tk.span;
+ unops[nunop].t0 = 0;
+ unops[nunop].tt = tk.t;
+ if (++nunop >= countof(unops)) {
+ ex = exprparse(cm, 999, NULL, 0);
+ break;
+ }
+ goto Unary;
+
+ /* might be unary op (cast) or primary expr */
+ case '(':
+ if (!isdecltok(cm)) { /* (expr) */
+ struct span span = tk.span;
+ ex = commaexpr(cm);
+ joinspan(&span.ex, ex.span.ex);
+ peek(cm, &tk);
+ if (expect(cm, ')', NULL)) joinspan(&span.ex, tk.span.ex);
+ ex.span = span;
+ } else { /* (type) expr */
+ struct declstate st = { DCASTEXPR };
+ struct decl decl = pdecl(&st, cm);
+ struct span span = tk.span;
+ assert(decl.ty.t);
+ peek(cm, &tk);
+ if (expect(cm, ')', NULL))
+ joinspan(&span.ex, tk.span.ex);
+ if (peek(cm, NULL) == '{') {
+ if (ccopt.cstd < STDC99)
+ warn(&tk.span, "compound literals are a c99 feature");
+ ex = initializer(cm, &decl.ty, (decl.scls & SCSTATIC) ? EVSTATICINI : EVFOLD,
+ /* globl */ 0, decl.qual, NULL);
+ break;
+ }
+ unops[nunop].span = span;
+ unops[nunop].ty = decl.ty;
+ if (++nunop >= countof(unops)) {
+ ex = exprparse(cm, 999, NULL, 0);
+ break;
+ }
+ goto Unary;
+ }
+ break;
+ /* base exprs */
+ case TKNUMLIT:
+ case TKCHRLIT:
+ ex = mkexpr(ENUMLIT, tk.span, mktype(0), );
+ if (!(ty.t = parsenumlit(&ex.u, &ex.f, &tk, 0)))
+ error(&tk.span, "bad %s literal %'tk", tk.t == TKNUMLIT ? "number" : "character", &tk);
+ ex.ty.t = ty.t ? ty.t : TYINT;
+ break;
+ case TKWtrue: case TKWfalse:
+ ex = mkexpr(ENUMLIT, tk.span, mktype(TYBOOL), .u = tk.t == TKWtrue);
+ break;
+ case TKSTRLIT:
+ ty = mktype(((const char []){TYCHAR, TYSHORT, TYINT})[tk.wide]);
+ ex = mkexpr(ESTRLIT, tk.span, mkarrtype(ty, 0, tk.len+1), { .s.p = (void *)tk.s, .s.n = tk.len });
+ break;
+ case TKIDENT: Ident: {
+ struct decl *decl = finddecl(cm, tk.name);
+ if (!decl) {
+ if (cm->env->up && tk.name->c == '_'
+ && (!strcmp(&tk.name->c, "__FUNCTION__") || !strcmp(&tk.name->c, "__PRETTY_FUNCTION__"))) {
+ /* hack: treat these identifiers as __func__ synonym to support the GNU extension */
+ warn(&tk.span, "%'tk is a GNU extension", &tk);
+ decl = finddecl(cm, istr__func__);
+ assert(decl && decl->scls == SCSTATIC);
+ goto Sym;
+ } else if (ctx == EATTRARG && nunop == 0 && (peek(cm, NULL) == ',' || peek(cm, NULL) == ')')) {
+ return ex = mkexpr(ESYM, tk.span, mktype(IMPLICITSYMTY), .implicitsym = tk.name);
+ } else if (peek(cm, NULL) == '(') { /* implicit function decl? */
+ ex = mkexpr(ESYM, tk.span, mktype(IMPLICITSYMTY), .implicitsym = tk.name);
+ } else {
+ error(&tk.span, "undeclared identifier %'tk", &tk);
+ ex = mkexpr(ESYM, tk.span, mktype(TYINT), .implicitsym = NULL);
+ }
+ } else if (decl->scls == SCTYPEDEF) {
+ error(&tk.span, "unexpected typename %'tk (expected expression)", &tk);
+ ex = mkexpr(ESYM, tk.span, decl->ty, .implicitsym = NULL);
+ } else if (decl->isenum) {
+ ex = mkexpr(ENUMLIT, tk.span, decl->ty, .i = decl->value);
+ } else Sym: {
+ if (decl->name == istr__func__ && decl->isbuiltin) { /* lazy __func__ */
+ internstr fnname = decl->sym;
+ decl->isbuiltin = 0;
+ decl->sym = mkhiddensym(&fnname->c, "__func__", 1);
+ uint off = objnewdat(decl->sym, objout.code ? Stext : Srodata, 0, typesize(decl->ty), typealign(decl->ty));
+ uchar *p = objout.code ? objout.textbegin + off : objout.rodata.p + off;
+ memcpy(p, fnname, typearrlen(decl->ty)-1);
+ }
+ ex = mkexpr(ESYM, tk.span, decl->ty, .qual = decl->qual, .decl = decl - declsbuf.p);
+ }
+ break; }
+ case TKWsizeof: case TKW_Alignof: case TKWalignof: {
+ enum toktag tt = tk.t;
+ uint res;
+ span = tk.span;
+ if (!match(cm, NULL, '(')) /* sizeof/alignof expr */
+ goto Unops;
+ else if (isdecltok(cm)) { /* sizeof/alignof (type) */
+ struct declstate st = { DCASTEXPR };
+ ty = pdecl(&st, cm).ty;
+ peek(cm, &tk);
+ if (expect(cm, ')', NULL))
+ joinspan(&span.ex, tk.span.ex);
+ res = sizeofalignofcheck(&span, tt, ty, NULL);
+ } else { /* sizeof/alignof expr */
+ struct expr tmp = commaexpr(cm);
+ peek(cm, &tk);
+ if (expect(cm, ')', NULL))
+ joinspan(&span.ex, tk.span.ex);
+ ppostfixopers(cm, &tmp);
+ ty = tmp.ty;
+ res = sizeofalignofcheck(&span, tt, ty, &tmp);
+ }
+ ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype), .u = res);
+ break; }
+ case TKW__builtin_va_arg:
+ span = tk.span;
+ ex = vaargexpr(cm, &span);
+ break;
+ case TKW_Generic:
+ span = tk.span;
+ ex = genericexpr(cm, &span);
+ break;
+ default:
+ fatal(&tk.span, "expected %s (near %'tk)", ctx == EFROMSTMT ? "statement" : "expression", &tk);
+ }
+
+ ppostfixopers(cm, &ex);
+
+ /* unary operators (process) */
+ while (nunop-- > 0) {
+ enum exprkind ek;
+ span = unops[nunop].span;
+ joinspan(&span.ex, ex.span.ex);
+ if (unops[nunop].t0 == 0) {
+ switch (unops[nunop].tt) {
+ case '+':
+ ek = EPLUS;
+ goto Alu;
+ case '-':
+ ek = ENEG;
+ goto Alu;
+ case '~':
+ ek = ECOMPL;
+ goto Alu;
+ case '!':
+ ek = ELOGNOT;
+ Alu:
+ ty = ek == ELOGNOT ? mktype(TYINT) : cvtarith(ex.ty, ex.ty);
+ if (!ty.t || (ek == ECOMPL && !isint(ty))) {
+ error(&tk.span, "invalid operand to %'tk '%ty'", &tk, ex.ty);
+ ty = mktype(TYINT);
+ }
+ ex = mkexpr(ek, span, ty, .sub = exprdup(cm, &ex));
+ break;
+ case TKINC: case TKDEC:
+ ty = ex.ty;
+ incdeccheck(tk.t, &ex, &span);
+ ex = mkexpr(unops[nunop].tt == TKINC ? EPREINC : EPREDEC, span, ty,
+ .sub = exprdup(cm, &ex));
+ break;
+ case '*':
+ if (ex.ty.t == TYPTR || ex.ty.t == TYARRAY) {
+ ty = typechild(ex.ty);
+ if (ty.t != TYVOID && isincomplete(typedecay(ty))) {
+ error(&span, "cannot dereference pointer to incomplete type '%ty'", ty);
+ ty = mktype(TYINT);
+ }
+ } else {
+ error(&span, "invalid operand to unary * '%ty'", ex.ty);
+ ty = mktype(TYINT);
+ }
+ ex = mkexpr(EDEREF, span, ty, .qual = ex.ty.flag & TFCHLDQUAL,
+ .sub = exprdup(cm, &ex));
+ break;
+ case '&':
+ if (!islvalue(&ex))
+ error(&span, "operand to unary & is not an lvalue");
+ if (ex.t == EGETF && ex.fld.bitsiz)
+ error(&span, "cannot take address of bitfield");
+ ex = mkexpr(EADDROF, span, mkptrtype(ex.ty, ex.qual), .sub = exprdup(cm, &ex));
+ break;
+ case TKWsizeof: case TKW_Alignof: case TKWalignof:
+ ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype),
+ .u = sizeofalignofcheck(&span, unops[nunop].tt, ex.ty, &ex));
+ break;
+ default: assert(0);
+ }
+ } else { /* cast */
+ ty = unops[nunop].ty;
+ if (!castcheck(ty, &ex))
+ error(&span, "cannot cast value of type '%ty' to '%ty'", ex.ty, ty);
+ if (ex.t == ENUMLIT && isint(ex.ty) && ty.t == TYPTR)
+ ex.ty = ty;
+ else
+ ex = mkexpr(ECAST, span, ty, .sub = exprdup(cm, &ex));
+ }
+ }
+
+ /* binary operators */
+ for (int opprec; (opprec = tkprec(peek(cm, &tk))) >= prec;) {
+ enum exprkind ek = bintab[tk.t].t;
+ struct expr rhs, tmp;
+ lex(cm, NULL);
+ if (ek != ECOND) {
+ /* only the assignment operators are right-associative */
+ bool leftassoc = (bintab[tk.t].k & BCSET) == 0;
+ /* ex OP rhs */
+ span.sl = tk.span.sl;
+ span.ex = ex.span.ex;
+ rhs = exprparse(cm, opprec + leftassoc, NULL, 0);
+ if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, rhs.span.ex))
+ span.ex = tk.span.ex;
+ ty = bintypecheck(&span, tk.t, &ex, &rhs);
+ assert(ty.t);
+ ex = mkexpr(ek, span, ty, .sub = exprdup2(cm, &ex, &rhs));
+ } else {
+ /* logical-OR-expression ? expression : conditional-expression */
+ struct expr *sub;
+ span.sl = tk.span.sl;
+ span.ex = ex.span.ex;
+ if (!isscalar(ex.ty) && !isptrcvt(ex.ty))
+ error(&ex.span, "?: condition is not a scalar type: '%ty'", ex.ty);
+ tmp = commaexpr(cm);
+ joinspan(&tk.span.ex, tmp.span.ex);
+ expect(cm, ':', NULL);
+ rhs = exprparse(cm, opprec, NULL, 0);
+ if (!joinspan(&span.ex, tk.span.ex) || !joinspan(&span.ex, tmp.span.ex)
+ || !joinspan(&span.ex, rhs.span.ex))
+ span.ex = tk.span.ex;
+ ty = condtype(&tmp, &rhs);
+ if (!ty.t) {
+ error(&span, "incompatible types in conditional expression: '%ty', '%ty'", tmp.ty, rhs.ty);
+ ty = tmp.ty;
+ }
+ sub = alloc(&cm->exarena, 3 * sizeof*sub, 0);
+ sub[0] = ex, sub[1] = tmp, sub[2] = rhs;
+ ex = mkexpr(ECOND, span, ty, .sub = sub);
+ }
+ }
+
+ return ex;
+}
+
+static struct expr
+expr(struct comp *cm)
+{
+ return exprparse(cm, bintab['='].prec, NULL, 0); /* non-comma expr */
+}
+
+static struct expr
+arraycountexpr(struct comp *cm)
+{
+ return exprparse(cm, bintab['='].prec, NULL, EARRAYCOUNT); /* non-comma expr, or lone '*' */
+}
+
+static struct expr
+constantexpr(struct comp *cm)
+{
+ return exprparse(cm, bintab['?'].prec, NULL, 0); /* conditional-expr */
+}
+
+static struct expr
+commaexpr(struct comp *cm)
+{
+ return exprparse(cm, 1, NULL, 0);
+}
+
+/****************/
+/* Initializers */
+/****************/
+
+static uint
+nmemb(union type ty)
+{
+ switch (ty.t) {
+ case TYARRAY: return typearrlen(ty) ? typearrlen(ty) : -1u;
+ case TYUNION: case TYSTRUCT: return typedata[ty.dat].nmemb;
+ default: return 1;
+ }
+}
+
+static bool
+objectp(union type ty)
+{
+ return isagg(ty) || ty.t == TYARRAY;
+}
+
+static bool
+chrarrayof(union type ty, union type chld)
+{
+ assert(isint(chld));
+ return ty.t == TYARRAY && isint(typechild(ty)) && typesize(typechild(ty)) == typesize(chld);
+}
+
+static union type
+membertype(uint *off, uint *bitsiz, uint *bitoff, union type ty, uint idx)
+{
+ *bitsiz = *bitoff = 0;
+ if (!objectp(ty)) {
+ *off = 0;
+ return ty;
+ } else if (ty.t == TYARRAY) {
+ *off = typesize(typechild(ty)) * idx;
+ return typechild(ty);
+ } else if (idx < typedata[ty.dat].nmemb) {
+ struct fielddata fld = typedata[ty.dat].fld[idx].f;
+ *off = fld.off;
+ *bitsiz = fld.bitsiz, *bitoff = fld.bitoff;
+ return fld.t;
+ }
+ *off = ~0u;
+ return mktype(0);
+}
+
+struct initparser {
+ struct initcur {
+ union type ty;
+ uint idx;
+ uint off;
+ short prev;
+ } buf[32], *cur, *sub;
+ struct arena **arena;
+ uint arrlen;
+ enum evalmode ev;
+ bool dyn; /* when set, data is written to a temporary buffer first, because either:
+ - size is not known until parsing done (implicit array size)
+ - data section is not known until parsing done (to avoid relocs in .rodata)
+ otherwise write to the corresponding object data section buffer directly */
+ union {
+ struct init *init; /* for initializer with automatic storage */
+ struct { /* for static storage (dyn = 0) */
+ enum section sec;
+ uint off;
+ };
+ struct { /* for static storage (dyn = 1) */
+ vec_of(uchar) ddat;
+ struct dreloc {
+ struct dreloc *link;
+ internstr sym;
+ vlong addend;
+ uint off;
+ } *drel;
+ };
+ };
+};
+
+static void
+excesscheck(struct initparser *ip, const struct span *span)
+{
+ union type sub = ip->sub->ty;
+ uint n = nmemb(sub);
+ if (ip->sub->idx == n) {
+ if (sub.t == TYARRAY)
+ warn(span, "excess elements in array initializer for '%ty'", sub);
+ else if (sub.t == TYSTRUCT)
+ warn(span, "excess elements in initializer; '%ty' has %u member%s", sub, n, &"s"[n==1]);
+ else if (sub.t == TYUNION)
+ warn(span, "excess elements in union initializer");
+ else
+ warn(span, "excess elements in scalar initializer");
+ }
+}
+
+#if 1
+#define dumpini(_)
+#else
+/* debugging */
+static void
+dumpini(struct initparser *ip)
+{
+ efmt(">>>\n");
+ for (struct initcur *s = ip->buf; s < ip->sub+1; ++s) {
+ efmt(" ");
+ efmt("%d. [%ty, %u]", s- ip->buf, s->ty, s->idx);
+ if (s == ip->cur) efmt(" <-- cursor");
+ ioputc(&bstderr, '\n');
+ }
+ efmt("<<<\n");
+}
+#endif
+
+static vlong /* -> returns addend */
+expr2reloc(internstr *psym, const struct expr *ex)
+{
+ if (ex->t == ESSYMREF) {
+ *psym = ex->ssym.sym;
+ return ex->ssym.off;
+ } else if (ex->t == ESTRLIT || ex->t == EINIT) {
+ if (ex->t == ESTRLIT) assert(ex->ty.t == TYARRAY);
+ *psym = xcon2sym(expraddr(NULL, ex).i);
+ return 0;
+ }
+ fatal(&ex->span, "internal bug: non static reloc?");
+}
+
+static bool
+rodatarelocok(void)
+{
+ return !(ccopt.pie | ccopt.pic);
+}
+
+static void
+iniwrite(struct comp *cm, struct initparser *ip, uint off, uint bitsiz, uint bitoff, union type ty, struct expr *ex)
+{
+ if (ex->ty.t == TYSTRUCT && ip->ev == EVSTATICINI) {
+ assert(ty.bits == ex->ty.bits);
+ for (uint i = 0, n = nmemb(ex->ty); i < n; ++i) {
+ uint suboff;
+ union type sub = membertype(&suboff, &bitsiz, &bitoff, ex->ty, i);
+ iniwrite(cm, ip, off + suboff, bitsiz, bitoff, sub, exprdup(cm, &mkexpr(EGETF, ex->span, sub, .sub = ex)));
+ }
+ } else if (ip->ev == EVSTATICINI) {
+ uchar *p;
+ uint siz = typesize(ty);
+ if (nerror) return;
+ if (ip->dyn) {
+ if (ip->ddat.n < off + siz) {
+ uint old = ip->ddat.n;
+ vresize(&ip->ddat, off + siz);
+ memset(ip->ddat.p + old, 0, ip->ddat.n - old);
+ assert(off + siz == ip->ddat.n);
+ }
+ p = ip->ddat.p + off;
+ } else {
+ p = (ip->sec == Sdata ? objout.data.p : objout.rodata.p) + ip->off + off;
+ }
+
+ if (ex->t == ENUMLIT) {
+ struct expr *e = ex, tmp;
+ if (ex->ty.bits != ty.bits && ty.t != TYPTR) {
+ tmp = mkexpr(ECAST, ex->span, ty, .sub = ex);
+ e = &tmp;
+ assert(eval(e, EVSTATICINI));
+ assert(e->t == ENUMLIT);
+ }
+ if (!bitsiz) switch (siz) {
+ default: assert(0);
+ case 1: *p = e->u; break;
+ case 2: wr16targ(p, e->u); break;
+ case 4: isint(ty) ? wr32targ(p, e->u) : wrf32targ(p, e->f); break;
+ case 8: isint(ty) ? wr64targ(p, e->u) : wrf64targ(p, e->f); break;
+ } else {
+ uvlong mask = (bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1) << bitoff;
+ if (bitoff + bitsiz > siz*8) siz <<= 1; /* straddles an allocation boundary */
+ switch (siz) {
+ default: assert(0);
+ case 1: *p = (*p &~ mask) | (e->u << bitoff & mask); break;
+ case 2: wr16targ(p, (rd16targ(p) &~ mask) | (e->u << bitoff & mask)); break;
+ case 4: wr32targ(p, (rd32targ(p) &~ mask) | (e->u << bitoff & mask)); break;
+ case 8: wr64targ(p, (rd64targ(p) &~ mask) | (e->u << bitoff & mask)); break;
+ }
+ }
+ } else if (ty.t == TYARRAY && ex->t == ESTRLIT) {
+ uint n = ex->s.n * typesize(typechild(ty));
+ if (siz < n) n = siz;
+ /* XXX endian for wide strs */
+ memcpy(p, ex->s.p, n);
+ } else {
+ internstr sym;
+ vlong addend = expr2reloc(&sym, ex);
+ if (!ip->dyn) {
+ assert(ip->sec != Srodata || rodatarelocok());
+ objreloc(sym, targ_64bit ? REL_ABS64 : REL_ABS32,
+ ip->sec, ip->off + off, addend);
+ } else {
+ struct dreloc *rel = alloc(ip->arena, sizeof *rel, 0);
+ rel->link = ip->drel;
+ rel->sym = sym;
+ rel->off = off;
+ rel->addend = addend;
+ ip->drel = rel;
+ }
+ }
+ } else {
+ assert(cm != NULL);
+ struct init *init = ip->init;
+ struct initval val = {
+ .off = off,
+ .bitsiz = bitsiz,
+ .bitoff = bitoff,
+ .ex = *ex
+ }, *new = alloccopy(&cm->exarena, &val, sizeof val, 0);
+ *init->tail = new;
+ init->tail = &new->next;
+ if (!bitsiz) for (uint i = off, end = i + typesize(ex->ty); i < end; ++i) {
+ if (BSSIZE(end) > countof(init->zero)) break;
+ bsclr(init->zero, i);
+ }
+ }
+}
+
+static bool
+iniwriterec(struct comp *cm, struct initparser *ip, uint off, struct expr *ex)
+{
+ assert(ex->t == EINIT);
+ for (struct initval *v = ex->init->vals; v; v = v->next) {
+ if (v->ex.t == EINIT) iniwriterec(cm, ip, off + v->off, &v->ex);
+ else if (ip->ev && !eval(&v->ex, ip->ev) && ip->ev != EVFOLD) return 0;
+ else iniwrite(cm, ip, off + v->off, v->bitsiz, v->bitoff, v->ex.ty, &v->ex);
+ }
+ return 1;
+}
+
+static struct initcur *
+iniadvance(struct initparser *ip, struct initcur *c, const struct span *span)
+{
+ if (c - ip->buf >= countof(ip->buf) - 1)
+ fatal(span, "too many nested initializers");
+ return c + 1;
+}
+
+/* set the initializer cursor object */
+static void
+inifocus(struct initparser *ip, struct comp *cm, const struct span *span, uint idx)
+{
+ while (idx >= nmemb(ip->sub->ty) && ip->sub != ip->cur) {
+ --ip->sub;
+ idx = ip->sub->idx;
+ }
+ uint off, bitsiz, bitoff;
+ union type targ = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, idx);
+ struct initcur *next = iniadvance(ip, ip->cur, span);
+ assert(!bitsiz);
+
+ if (isagg(ip->sub->ty) && targ.t == TYARRAY && !typearrlen(targ))
+ error(span, "cannot initialize flexible array member");
+ excesscheck(ip, span);
+
+ next->ty = targ;
+ next->idx = 0;
+ next->off = ip->sub->off + off;
+ next->prev = ip->cur - ip->buf;
+ ++ip->cur->idx;
+ ip->sub = ip->cur = next;
+}
+
+/* initialize a character array with a string literal */
+static void
+inistrlit(struct comp *cm, struct expr *ex, union type *ty)
+{
+ if (isincomplete(*ty)) {
+ *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, ex->s.n + 1);
+ } else if (typearrlen(*ty) < ex->s.n) {
+ warn(&ex->span, "string literal in initializer is truncated from %u to %u bytes",
+ (ex->s.n+1)*typesize(typechild(*ty)), typesize(*ty));
+ }
+ ex->ty = *ty;
+}
+
+/* read scalar initializer into initializer list and avance */
+static void
+ininext(struct initparser *ip, struct comp *cm)
+{
+ uint off, bitsiz, bitoff;
+ union type targ;
+ struct expr ex = expr(cm);
+
+Retry:
+ targ = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx);
+
+ if (isagg(ip->sub->ty) && targ.t == TYARRAY && !typearrlen(targ)) {
+ error(&ex.span, "cannot initialize flexible array member");
+ ++ip->sub->idx;
+ return;
+ }
+ if (ex.t == ESTRLIT && chrarrayof(targ, typechild(ex.ty))) {
+ assert(!isincomplete(targ));
+ inistrlit(cm, &ex, &targ);
+ iniwrite(cm, ip, ip->sub->off + off, 0,0, targ, &ex);
+ ++ip->sub->idx;
+ return;
+ } else if (ex.t == ESTRLIT && ip->sub->idx == 0 && chrarrayof(ip->sub->ty, typechild(ex.ty))) {
+ /* handle e.g. (char []){"foo"} */
+ assert(off == 0);
+ targ = ip->sub->ty;
+ inistrlit(cm, &ex, &targ);
+ iniwrite(cm, ip, ip->sub->off, 0,0, targ, &ex);
+ if (ip->sub == ip->buf && ip->arrlen < ex.s.n+1)
+ ip->arrlen = ex.s.n+1;
+ --ip->sub;
+ return;
+ } else if (ip->sub->idx >= nmemb(ip->sub->ty) && ip->sub != ip->cur) {
+ --ip->sub;
+ goto Retry;
+ } else if (objectp(targ) && targ.bits != ex.ty.bits) {
+ struct initcur *next = iniadvance(ip, ip->sub, &ex.span);
+ if (ip->sub - ip->buf == countof(ip->buf) - 1)
+ fatal(&ex.span, "too many nested initializers");
+ ++ip->sub->idx;
+ *next = (struct initcur) { targ, .off = ip->sub->off + off };
+ ip->sub = next;
+ goto Retry;
+ }
+ excesscheck(ip, &ex.span);
+
+ if (targ.t) {
+ if (!initcheck(targ, &ex))
+ error(&ex.span, "cannot initialize '%ty' with expression of type '%ty'", targ, ex.ty);
+ else {
+ if (targ.bits == ex.ty.bits && ex.t == EINIT) {
+ if (!iniwriterec(cm, ip, ip->sub->off + off, &ex))
+ goto CannotEval;
+ } else if (ip->ev && !eval(&ex, ip->ev) && ip->ev != EVFOLD) {
+ CannotEval:
+ error(&ex.span, "cannot evaluate expression statically");
+ } else {
+ struct expr *pex = &ex;
+ if (ip->ev != EVSTATICINI) {
+ if (ex.ty.bits != targ.bits)
+ ex = mkexpr(ECAST, ex.span, targ, .sub = exprdup(cm, &ex));
+ pex = exprdup(cm, &ex);
+ }
+ iniwrite(cm, ip, ip->sub->off + off, bitsiz, bitoff, targ, pex);
+ }
+ }
+ }
+ if (ip->sub == ip->buf && ip->arrlen < ip->sub->idx+1)
+ ip->arrlen = ip->sub->idx+1;
+
+ if (++ip->sub->idx == 0) {
+ error(&ex.span, "element makes object too large");
+ --ip->sub->idx;
+ }
+}
+
+static int
+aggdesignator(struct initparser *ip, union type ty, internstr name, const struct span *span)
+{
+ const struct typedata *td = &typedata[ty.dat];
+ for (int i = 0; i < td->nmemb; ++i) {
+ struct namedfield *fld = &td->fld[i];
+ if (fld->name == name) {
+ return i;
+ } else if (!fld->name) {
+ int save, sub;
+ struct initcur *next = iniadvance(ip, ip->sub, span);
+ save = ip->sub->idx;
+ ip->sub->idx = i+1;
+ *next = (struct initcur) { fld->f.t, .off = ip->sub->off + fld->f.off };
+ ip->sub = next;
+ sub = aggdesignator(ip, fld->f.t, name, span);
+ if (sub == -1) {
+ --ip->sub;
+ ip->sub->idx = save;
+ } else return sub;
+ }
+ }
+ return -1;
+}
+
+static bool
+designators(struct initparser *ip, struct comp *cm)
+{
+ struct token tk;
+ struct span span;
+ bool some = 0;
+
+ for (;;) {
+ uint off, bitsiz, bitoff;
+ uvlong idx = ~0ull;
+ if (match(cm, &tk, '[')) {
+ struct expr ex = commaexpr(cm);
+ span = tk.span;
+ joinspan(&span.ex, ex.span.ex);
+ peek(cm, &tk);
+ if (some) {
+ union type ty = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx++);
+ struct initcur *next = iniadvance(ip, ip->sub, &tk.span);
+ assert(!bitsiz);
+ *next = (struct initcur) { ty, .off = ip->sub->off + off };
+ ip->sub = next;
+ dumpini(ip);
+ }
+ if (expect(cm, ']', NULL)) joinspan(&span.ex, tk.span.ex);
+ if (ip->sub->ty.t != TYARRAY)
+ error(&ex.span, "array designator used with non-array type '%ty'", ip->sub->ty);
+ if (!eval(&ex, EVINTCONST))
+ error(&ex.span, "array designator index is not an integer constant");
+ else if (issigned(ex.ty) && ex.i < 0)
+ error(&ex.span, "negative array designator index");
+ else if (ex.i > ~0u - 1)
+ error(&ex.span, "index too large");
+ else {
+ idx = ex.u;
+ ip->sub->idx = idx;
+ if (ip->sub == ip->buf && ip->arrlen < idx+1)
+ ip->arrlen = idx+1;
+ dumpini(ip);
+ }
+ some = 1;
+ } else if (match(cm, &tk, '.')) {
+ span = tk.span;
+ peek(cm, &tk);
+ if (some) {
+ union type ty = membertype(&off, &bitsiz, &bitoff, ip->sub->ty, ip->sub->idx++);
+ struct initcur *next = iniadvance(ip, ip->sub, &tk.span);
+ *next = (struct initcur) { ty, .off = ip->sub->off + off };
+ ip->sub = next;
+ dumpini(ip);
+ }
+ if (expect(cm, TKIDENT, NULL)) joinspan(&span.ex, tk.span.ex);
+ if (!isagg(ip->sub->ty))
+ error(&span, "member designator used with non-aggregate type '%ty'", ip->sub->ty);
+ else if (tk.t == TKIDENT) {
+ int idx;
+ for (;;) {
+ idx = aggdesignator(ip, ip->sub->ty, tk.name, &span);
+ if (idx >= 0 || ip->sub == ip->cur) break;
+ --ip->sub;
+ }
+ ip->sub->idx = idx;
+ if (idx < 0)
+ error(&span, "%ty has no such field: '%s'", ip->cur->ty, tk.name);
+ dumpini(ip);
+ }
+ some = 1;
+ } else {
+ if (some) {
+ expect(cm, '=', NULL);
+ }
+ return some;
+ }
+ }
+}
+
+static struct expr
+initializer(struct comp *cm, union type *ty, enum evalmode ev, bool globl,
+ enum qualifier qual, internstr sym)
+{
+ struct token tk;
+ struct span span;
+ struct init res = {0};
+ struct initparser ip[1] = {0};
+
+ ip->arena = &cm->exarena;
+ ip->ev = ev;
+ if (ev == EVSTATICINI) {
+ if (ty->t == TYARRAY && isincomplete(*ty)) {
+ ip->dyn = 1;
+ } else if (qual & QCONST && !rodatarelocok()) {
+ ip->dyn = 1;
+ vresize(&ip->ddat, typesize(*ty));
+ memset(ip->ddat.p, 0, typesize(*ty));
+ } else {
+ ip->sec = qual & QCONST ? Srodata : Sdata;
+ if (!nerror)
+ ip->off = objnewdat(sym, ip->sec, globl, typesize(*ty), typealign(*ty));
+ }
+ } else {
+ ip->init = &res;
+ res.tail = &res.vals;
+ }
+
+ if (!match(cm, &tk, '{')) {
+ struct expr ex = expr(cm);
+ if (ex.t == ESTRLIT && chrarrayof(*ty, typechild(ex.ty))) {
+ inistrlit(cm, &ex, ty);
+ iniwrite(cm, ip, 0, 0, 0, *ty, &ex);
+ } else if (!initcheck(*ty, &ex)) {
+ error(&ex.span, "cannot initialize '%ty' with expression of type '%ty'", *ty, ex.ty);
+ } else {
+ if (ev && !eval(&ex, ev) && ev != EVFOLD)
+ error(&ex.span, "cannot evaluate expression statically");
+ else
+ iniwrite(cm, ip, 0, 0, 0, *ty, &ex);
+ }
+ if (ip->dyn)
+ goto Dynfix;
+ return ex;
+ }
+
+ assert(countof(res.zero) == BSSIZE(64));
+ if (ev != EVSTATICINI) {
+ memset(res.zero, 0xFF, sizeof res.zero);
+ }
+
+ span = tk.span;
+ ip->sub = ip->cur = ip->buf;
+ ip->cur->ty = *ty;
+ for (;;) {
+ peek(cm, &tk);
+ joinspan(&span.ex, tk.span.ex);
+ if (tk.t == '[' || tk.t == '.') {
+ designators(ip, cm);
+ }
+ if (match(cm, &tk, '}')) {
+ if (ip->cur == ip->buf) break;
+ ip->sub = ip->cur = ip->buf + ip->cur->prev;
+ dumpini(ip);
+ } else if (match(cm, &tk, '{')) {
+ struct span span = tk.span;
+ inifocus(ip, cm, &tk.span, ip->sub->idx);
+ if (peek(cm, &tk) == '}') {
+ if (!joinspan(&span.ex, tk.span.ex)) span = tk.span;
+ if (!objectp(ip->sub->ty)) {
+ error(&span, "scalar initializer cannot be empty");
+ } else if (ccopt.cstd < STDC23 && ccopt.pedant) {
+ warn(&span, "empty initializer in %M is an extension");
+ }
+ } else if (ip->sub->ty.t && !objectp(ip->sub->ty)) {
+ warn(&span, "brace initializer for scalar object '%ty'", ip->sub->ty);
+ }
+ continue;
+ } else {
+ dumpini(ip);
+ ininext(ip, cm);
+ }
+ match(cm, NULL, ',');
+ if (peek(cm, &tk) != '}' && ip->sub->ty.t == TYUNION) {
+ if (ip->sub == ip->cur) {
+ warn(&tk.span, "excess elements in union initializer");
+ } else while (ip->sub != ip->cur && ip->sub->ty.t == TYUNION) {
+ --ip->sub;
+ }
+ }
+ }
+ if (ip->dyn) {
+ enum section sec;
+ uint off, siz, align;
+ uchar *p;
+
+ if (isincomplete(*ty)) {
+ uint len = ip->arrlen > ip->cur->idx ? ip->arrlen : ip->cur->idx;
+ if (len == 0)
+ error(&span, "array cannot have zero length");
+ *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, len);
+ }
+ Dynfix:
+ if (qual & QCONST && (ip->drel == NULL || rodatarelocok()))
+ sec = Srodata;
+ else
+ sec = Sdata;
+ if (!nerror) {
+ off = objnewdat(sym, sec, globl, siz = typesize(*ty), align = typealign(*ty));
+ p = sec == Srodata ? objout.rodata.p : objout.data.p;
+ memcpy(p + off, ip->ddat.p, ip->ddat.n);
+ memset(p + off + ip->ddat.n, 0, typesize(*ty) - ip->ddat.n);
+ for (struct dreloc *rel = ip->drel; rel; rel = rel->link) {
+ objreloc(rel->sym, targ_64bit ? REL_ABS64 : REL_ABS32, sec, off + rel->off, rel->addend);
+ }
+ }
+ vfree(&ip->ddat);
+ }
+ dumpini(ip);
+
+ if (ev == EVSTATICINI) {
+ return (struct expr){.span = span};
+ } else {
+ uint siz;
+ if (isincomplete(*ty)) {
+ uint len = ip->arrlen > ip->cur->idx ? ip->arrlen : ip->cur->idx;
+ if (!len)
+ error(&span, "initializer creates a zero-sized array");
+ *ty = mkarrtype(typechild(*ty), ty->flag & TFCHLDQUAL, len);
+ }
+
+ assert(countof(res.zero) == 1);
+ siz = typesize(*ty);
+ if (siz && siz <= 64)
+ res.zero->u &= ~0ull >> (64 - siz);
+
+ return mkexpr(EINIT, span, *ty, .init = alloccopy(&cm->exarena, &res, sizeof res, 0));
+ }
+}
+
+/* debugging */
+void
+dumpexpr(const struct expr *ex, bool prity)
+{
+ static const char *name[] = {
+ [EXXX] = "xxx", [ENUMLIT] = "numlit", [ESTRLIT] = "strlit",
+ [ESSYMREF] = "ssymref", [ESYM] = "sym", [EVAARG] = "vaarg",
+ [EINIT] = "init", [EGETF] = "getf", [ECALL] = "call",
+ [ECOND] = "cond", [EPLUS] = "plus", [ENEG] = "neg",
+ [ECOMPL] = "compl", [ELOGNOT] = "lognot", [EDEREF] = "deref",
+ [EADDROF] = "addrof", [ECAST] = "cast", [EPREINC] = "preinc",
+ [EPOSTINC] = "postinc", [EPREDEC] = "predec", [EPOSTDEC] = "postdec",
+ [EADD] = "add", [ESUB] = "sub", [EMUL] = "mul",
+ [EDIV] = "div", [EREM] = "rem", [EBAND] = "band",
+ [EBIOR] = "bior", [EXOR] = "xor", [ESHL] = "shl",
+ [ESHR] = "shr", [ELOGAND] = "logand", [ELOGIOR] = "logior",
+ [EEQU] = "equ", [ENEQ] = "neq", [ELTH] = "lth",
+ [EGTH] = "gth", [ELTE] = "lte", [EGTE] = "gte",
+ [ESET] = "set", [ESETADD] = "setadd", [ESETSUB] = "setsub",
+ [ESETMUL] = "setmul", [ESETDIV] = "setdiv", [ESETREM] = "setrem",
+ [ESETAND] = "setand", [ESETIOR] = "setior", [ESETXOR] = "setxor",
+ [ESETSHL] = "setshl", [ESETSHR] = "setshr", [ESEQ] = "seq",
+ };
+ ioputc(&bstderr, '(');
+ efmt("%s ", name[ex->t]);
+ if (ex->ty.t && (prity || ex->t == EVAARG || ex->t == ECAST))
+ efmt("<%ty> ", ex->ty);
+ int nsub = 0;
+ switch (ex->t) {
+ case ENUMLIT: if (!isflt(ex->ty)) efmt(isunsigned(ex->ty) ? "%lu" : "%ld", ex->u);
+ else efmt("%f", ex->f);
+ break;
+ case ESTRLIT: efmt("%'S", ex->s.p, ex->s.n); break; /* XXX widestr */
+ case ESYM: efmt("%y", declsbuf.p[ex->decl].name); break;
+ case ESSYMREF: efmt("%y%+d", ex->ssym.sym, ex->ssym.off);
+ if (ex->ssym.func) efmt(" @func");
+ if (ex->ssym.local) efmt(" @local");
+ break;
+ case EVAARG: dumpexpr(ex->sub, prity); break;
+ case EGETF: dumpexpr(ex->sub, prity); efmt(" #+%u", ex->fld.off);
+ if (ex->fld.bitsiz) efmt("[%d:%d]", ex->fld.bitoff, ex->fld.bitsiz);
+ break;
+ case ECALL: nsub = ex->narg+1; goto Sub;
+ case ECOND: nsub = ex->narg+1; goto Sub;
+ default:
+ if (in_range(ex->t, EPLUS, EPOSTDEC)) nsub = 1;
+ else nsub = 2;
+ Sub:
+ for (int i = 0; i < nsub; ++i) {
+ if (i) ioputc(&bstderr, ' ');
+ dumpexpr(&ex->sub[i], prity);
+ }
+ break;
+ case EINIT: assert(!"nyi");
+ }
+ ioputc(&bstderr, ')');
+}
+
+/*****************/
+/* Decls Parsing */
+/*****************/
+
+static union type
+buildagg(struct comp *cm, enum typetag tt, internstr name, int id)
+{
+ struct token tk;
+ union type t;
+ struct span flexspan;
+ struct namedfield fbuf[32];
+ vec_of(struct namedfield) fld = VINIT(fbuf, countof(fbuf));
+ struct typedata td = {tt};
+ bool isunion = tt == TYUNION;
+ const char *tag = isunion ? "union" : "struct";
+ uint bitsiz = 0, bitfbyteoff = 0,
+ bitoff = 0, bitftypesiz = 0;
+
+ while (!match(cm, &tk, '}')) {
+ struct declstate st = { DFIELD };
+ do {
+ struct decl decl = pdecl(&st, cm);
+ uint tysize;
+ if (st.empty) {
+ if (ccopt.pedant)
+ warn(&decl.span, "extra semicolon in aggregate");
+ continue;
+ }
+ tysize = typesize(decl.ty);
+ if (fld.n && td.flexi) {
+ td.flexi = 0;
+ error(&flexspan, "flexible array member is not at end of struct");
+ }
+ if (!isunion && decl.ty.t == TYARRAY && !typearrlen(decl.ty)) {
+ td.flexi = 1;
+ flexspan = decl.span;
+ } else if (isincomplete(decl.ty)) {
+ error(&decl.span, "field has incomplete type '%ty'", decl.ty);
+ } else if (decl.ty.t == TYFUNC) {
+ error(&decl.span, "field has function type '%ty'", decl.ty);
+ }
+ bitsiz = 0;
+ if (st.bitf) {
+ struct expr ex = constantexpr(cm);
+ const char *name = decl.name ? &decl.name->c : "<anonymous>";
+ if (!isint(decl.ty)) {
+ error(&decl.span, "bit-field '%s' has non-integer type '%ty'", name, decl.ty);
+ } else if (!isint(ex.ty)) {
+ error(&ex.span, "integer constant expression has non-integer type '%ty'", decl.ty);
+ } else if (!eval(&ex, EVINTCONST)) {
+ error(&ex.span, "cannot evaluate integer constant expression");
+ } else if (ex.i < 0) {
+ error(&ex.span, "bit-field '%s' has negative width '%ld'", name, ex.i);
+ } else if (ex.i > 8*tysize) {
+ error(&ex.span, "width of bit-field '%s' (%ld) exceeds width of type (%d)",
+ name, ex.i, 8*tysize);
+ } else if (ex.i == 0 && decl.name) {
+ error(&ex.span, "named bit-field '%s' has zero width", name);
+ } else {
+ bitsiz = ex.i;
+ if (bitsiz == 0) {
+ if (bitftypesiz) {
+ bitfbyteoff += bitftypesiz;
+ bitfbyteoff = alignup(bitfbyteoff, typealign(decl.ty));
+ }
+ bitoff = 0;
+ } else if (bitftypesiz && bitftypesiz < tysize) {
+ /* end of previous bitfield */
+ bitoff = 0;
+ bitfbyteoff += bitftypesiz;
+ } else if (!bitftypesiz) {
+ bitoff = 0;
+ bitfbyteoff = alignup(td.siz, typealign(decl.ty));
+ } else if (bitoff + bitsiz > 8*bitftypesiz) {
+ /* no straddling boundaries */
+ bitoff = 0;
+ bitfbyteoff += bitftypesiz;
+ }
+ if (tysize > bitftypesiz) bitftypesiz = tysize;
+ }
+ pdecl(&st, cm);
+ } else {
+ bitftypesiz = bitoff = bitsiz = 0;
+ }
+ if (decl.ty.t) {
+ uint align = typealign(decl.ty);
+ uint siz = tysize;
+ uint off = bitftypesiz ? bitfbyteoff : isunion ? 0 : alignup(td.siz, align);
+ struct namedfield f = { decl.name, { decl.ty, off, bitsiz, bitoff, .qual = decl.qual }};
+ if (bitftypesiz && siz != bitftypesiz) while (f.f.bitoff + f.f.bitsiz > 8*siz) {
+ /* adjust bitfields narrower than container type */
+ f.f.off += siz;
+ f.f.bitoff -= 8*siz;
+ }
+ if (!decl.name && !bitftypesiz) {
+ if (!isagg(decl.ty) || ttypenames[typedata[decl.ty.dat].id]) {
+ warn(&decl.span, "declaration does not declare anything");
+ continue;
+ } else if (ccopt.cstd < STDC11 && ccopt.pedant) {
+ warn(&decl.span, "anonymous %s in %M is an extension",
+ decl.ty.t == TYUNION ? "union" : "struct");
+ }
+ }
+ if (decl.name || !bitftypesiz)
+ vpush(&fld, f);
+ td.anyconst |= decl.qual & QCONST;
+ if (isagg(decl.ty)) {
+ td.anyconst |= typedata[decl.ty.dat].anyconst;
+ if (typedata[decl.ty.dat].flexi && !isunion)
+ error(&decl.span, "nested aggregate has flexible array member");
+ }
+ if (isunion)
+ td.siz = td.siz < siz ? siz : td.siz;
+ else
+ td.siz = off + siz;
+ td.align = td.align < align ? align : td.align;
+ bitoff += bitsiz;
+ }
+ } while (st.more);
+ }
+ if (td.flexi && fld.n == 1)
+ error(&flexspan, "flexible array member in otherwise empty aggregate");
+ if (td.flexi && ccopt.cstd < STDC99 && ccopt.pedant)
+ warn(&flexspan, "flexible array member in %M is an extension");
+ if (fld.n == 0) {
+ struct namedfield dummy = { intern(""), { mktype(TYCHAR), 0 }};
+ error(&tk.span, "%s cannot have zero members", tag);
+ vpush(&fld, dummy);
+ td.siz = td.align = 1;
+ }
+ td.siz = alignup(td.siz, td.align);
+ td.fld = fld.p;
+ td.nmemb = fld.n;
+ if (id != -1)
+ t = completetype(name, id, &td);
+ else
+ t = mktagtype(name, &td);
+ vfree(&fld);
+ return t;
+}
+
+static inline void
+inttyminmax(vlong *min, uvlong *max, enum typetag tt)
+{
+ uint bits = 8*targ_primsizes[tt];
+ *min = isunsignedt(tt) ? 0 : -(1ull << (bits - 1));
+ *max = isunsignedt(tt) ? ~0ull >> (64 - bits) : bits == 64 ? ~0ull>>1 : (1ll << (bits - 1)) - 1;
+}
+
+/* the backing type of enum (without a C23 fixed backing type) is int or the
+ * smallest-rank type that all the enumerators fit in, or if it doesn't exist,
+ * then the biggest signed type. the type of enumeration constants is the type of
+ * its defining expression when present or the type of the previous enumerator
+ * or in case of overflow the smallest type that fits (previous value + 1)
+ * this isn't strictly conforming since pre C23 enums are pretty loosely defined,
+ * and this is similar to existing compiler's de-facto behaviour (though gcc
+ * prefers to use unsigned types when possible). should add support for -fshort-enums
+ */
+static union type
+buildenum(struct comp *cm, internstr name, const struct span *span, int id)
+{
+ struct token tk;
+ vlong tymin, minv = 0;
+ uvlong tymax, maxv = 0;
+ struct typedata td = {TYENUM, .backing = TYINT};
+ union type ty = mktype(td.backing);
+ struct span maxvspan;
+ vlong iota = 0;
+ bool somelonglong = 0;
+
+ inttyminmax(&tymin, &tymax, td.backing);
+ while (!match(cm, &tk, '}')) {
+ struct decl decl = {0};
+ peek(cm, &tk);
+ expect(cm, TKIDENT, NULL);
+ if (match(cm, NULL, '=') || (peek(cm, NULL) == TKNUMLIT && !expect(cm, '=', NULL))) {
+ struct expr ex = expr(cm);
+ if (eval(&ex, EVINTCONST)) {
+ iota = ex.i;
+ if (ex.ty.t != ty.t)
+ inttyminmax(&tymin, &tymax, ex.ty.t);
+ ty = ex.ty;
+ } else {
+ error(&ex.span, "enum value is not an integer constant");
+ }
+ } else if (tk.t != TKIDENT) {
+ lex(cm, NULL);
+ continue;
+ }
+ while (issigned(ty) ? (iota > (vlong)tymax || iota < tymin) : iota > tymax)
+ inttyminmax(&tymin, &tymax, ++ty.t);
+ somelonglong |= ty.t >= TYVLONG;
+ if ((isunsigned(ty) || iota > 0) && iota > maxv)
+ maxv = iota, maxvspan = tk.span;
+ else if (issigned(ty) && iota < minv)
+ minv = iota;
+
+ decl.name = tk.name;
+ decl.ty = ty;
+ decl.isenum = 1;
+ decl.value = iota++;
+ putdecl(cm, &decl);
+ if (!match(cm, &tk, ',')) {
+ if (expect(cm, '}', "or `,'"))
+ break;
+ else lex(cm, NULL);
+ }
+ }
+
+ td.backing = 0;
+ if (minv >= 0 && maxv <= ~0u) {
+ td.backing = TYUINT;
+ } else for (int t = TYINT; t <= TYUVLONG; ++t) {
+ inttyminmax(&tymin, &tymax, t);
+ if (minv >= tymin && maxv <= tymax) {
+ td.backing = t;
+ break;
+ }
+ }
+ if (!td.backing) {
+ td.backing = !somelonglong && ccopt.cstd == STDC89 && ccopt.pedant ? TYLONG : TYVLONG;
+ warn(&maxvspan, "enumerators exceed range of enum's backing type '%ty'", mktype(td.backing));
+ }
+ if (td.backing >= TYVLONG && !somelonglong && ccopt.cstd == STDC89 && ccopt.pedant)
+ warn(span, "enum backing type is '%ty' in %M", mktype(td.backing));
+
+ if (id != -1)
+ ty = completetype(name, id, &td);
+ else
+ ty = mktagtype(name, &td);
+ ty.backing = td.backing;
+ return ty;
+}
+
+static union type
+tagtype(struct comp *cm, enum toktag kind)
+{
+ struct token tk;
+ union type t;
+ struct span span;
+ enum typetag tt = kind == TKWenum ? TYENUM : kind == TKWstruct ? TYSTRUCT : TYUNION;
+ internstr tag = NULL;
+
+ peek(cm, &tk);
+ if (match(cm, &tk, TKIDENT))
+ tag = tk.name;
+ span = tk.span;
+ if (!match(cm, NULL, '{')) {
+ if (!tag) {
+ error(&tk.span, "expected %tt name or '{'", kind);
+ return mktype(0);
+ }
+ t = gettagged(cm, &span, tt, tag, /* def? */ peek(cm, NULL) == ';');
+ } else {
+ if (tag) {
+ t = deftagged(cm, &span, tt, tag, mktype(0));
+ if (t.t != tt || !isincomplete(t)) {
+ if (t.t != tt)
+ error(&tk.span,
+ "defining tagged type %'tk as %tt clashes with previous definition",
+ &tk, kind);
+ else
+ error(&tk.span, "redefinition of '%tt %s'", kind, tag);
+ note(&span, "previous definition:");
+ }
+ }
+ if (tt == TYENUM)
+ t = buildenum(cm, tag, &span, tag ? typedata[t.dat].id : -1);
+ else
+ t = buildagg(cm, tt, tag, tag ? typedata[t.dat].id : -1);
+ }
+
+ if (t.t != tt) {
+ error(&tk.span, "declaring tagged type %'tk as %tt clashes with previous definition",
+ &tk, kind);
+ note(&span, "previous definition:");
+ }
+ return t;
+}
+
+static bool
+attrspec(struct comp *cm, int *attr)
+{ /* __attribute__ (( attribute-list )) */
+ if (!match(cm, NULL, TKW__attribute__)) return 0;
+ if (!expect(cm, '(', "after __attribute__") || !expect(cm, '(', "after __attribute__")) {
+ Bad:
+ fatal(NULL, NULL);
+ }
+ while (!match(cm, NULL, ')')) {
+ struct token tk;
+ lex(cm, &tk);
+ if (tk.t != TKIDENT && !in_range(tk.t, TKWBEGIN_, TKWEND_)) {
+ fatal(&tk.span, "expected attribute name");
+ }
+ internstr name = tk.name;
+ int ltrim = name[0].c == '_' && name[1].c == '_',
+ rtrim = tk.len > 2 && name[tk.len-1].c == '_' && name[tk.len-2].c == '_';
+ if (ltrim || rtrim) { /* trim surrounding '__' */
+ name = intern_(&name->c + ltrim*2, tk.len - ltrim*2 - rtrim*2);
+ }
+ if (match(cm, NULL, '(')) {
+ while (!match(cm, NULL, ')')) {
+ (void)exprparse(cm, bintab['='].prec, NULL, EATTRARG);
+ if (!match(cm, NULL, ',')) {
+ if (expect(cm, ')', NULL)) break;
+ else goto Bad;
+ }
+ }
+ }
+ (void)name;
+ if (!match(cm, NULL, ',')) {
+ if (expect(cm, ')', NULL)) break;
+ else goto Bad;
+ }
+ }
+ if (!expect(cm, ')', NULL))
+ goto Bad;
+ return 1;
+}
+
+static union type
+ptypeof(struct comp *cm)
+{
+ union type ty;
+ expect(cm, '(', NULL);
+ if (isdecltok(cm)) { /* typeof (type) */
+ struct declstate st = { DCASTEXPR };
+ ty = pdecl(&st, cm).ty;
+ } else { /* typeof (expr) */
+ ty = commaexpr(cm).ty;
+ }
+ expect(cm, ')', NULL);
+ return ty;
+}
+
+static void
+declspec(struct declstate *st, struct comp *cm, struct span *pspan)
+{
+ struct token tk;
+ struct decl *decl;
+ enum arith {
+ KSIGNED = 1<<0,
+ KUNSIGNED = 1<<1,
+ KBOOL = 1<<2,
+ KCHAR = 1<<3,
+ KSHORT = 1<<4,
+ KLONG = 1<<5,
+ KLONGLONG = 1<<6,
+ KINT = 1<<7,
+ KFLOAT = 1<<8,
+ KDOUBLE = 1<<9,
+ KCOMPLEX = 1<<10,
+ } arith = 0;
+ struct span span = {0};
+ union type ty = st->base;
+ bool properdecl = st->kind == DFUNCVAR || st->kind == DTOPLEVEL;
+
+ for (bool first = 1;; first = 0) {
+ enum storageclass scls = 0;
+ peek(cm, &tk);
+ if (first) span = tk.span;
+ switch (tk.t) {
+ /* storage-class-specifier */
+ case TKWtypedef: scls = SCTYPEDEF; break;
+ case TKWextern: scls = SCEXTERN; break;
+ case TKWstatic: scls = SCSTATIC; break;
+ case TKWauto: scls = SCAUTO; break;
+ case TKWregister: scls = SCREGISTER; break;
+ case TKWthread_local:
+ case TKW_Thread_local: scls = SCTHREADLOCAL; break;
+
+ /* function-specifier */
+ case TKWinline:
+ if (!properdecl) BadFnSpec: {
+ error(&tk.span, "function specifier %'tk is not allowed here", &tk);
+ break;
+ }
+ st->fninline = 1;
+ break;
+ case TKW_Noreturn:
+ if (!properdecl) goto BadFnSpec;
+ st->fnnoreturn = 1;
+ break;
+
+ /* alignment-specifier */
+ case TKW_Alignas: assert(!"nyi alignas"); break;
+
+ /* type-qualifier */
+ case TKWconst: st->qual |= QCONST; break;
+ case TKWvolatile: st->qual |= QVOLATILE; break;
+ case TKWrestrict: /* unimplemented */ break;
+ case TKW_Atomic: /* unimplemented */ break;
+
+ /* type-specifier */
+ case TKWvoid:
+ if (st->base.t) {
+ DupBase:
+ error(&tk.span, "more than one data type in declaration specifier");
+ } else {
+ st->base = mktype(TYVOID);
+ }
+ break;
+ case TKWsigned:
+ arith |= KSIGNED;
+ break;
+ case TKWunsigned:
+ arith |= KUNSIGNED;
+ break;
+ case TKW_Bool: case TKWbool:
+ if (arith & KBOOL) goto DupArith;
+ arith |= KBOOL;
+ break;
+ case TKWchar:
+ if (arith & KCHAR) {
+ DupArith:
+ error(&tk.span, "duplicate %tk specifier", &tk);
+ }
+ arith |= KCHAR;
+ break;
+ case TKWshort:
+ arith |= KSHORT;
+ break;
+ case TKWlong:
+ if ((arith & (KLONG | KLONGLONG)) == KLONG)
+ arith = (arith &~ KLONG) | KLONGLONG;
+ else if ((arith & (KLONG | KLONGLONG)) == 0)
+ arith |= KLONG;
+ else
+ error(&tk.span, "too long");
+ break;
+ case TKWint:
+ if (arith & KINT) goto DupArith;
+ arith |= KINT;
+ break;
+ case TKWfloat:
+ if (arith & KFLOAT) goto DupArith;
+ arith |= KFLOAT;
+ break;
+ case TKWdouble:
+ if (arith & KDOUBLE) goto DupArith;
+ arith |= KDOUBLE;
+ break;
+ case TKW_Complex:
+ if (arith & KCOMPLEX) goto DupArith;
+ arith |= KCOMPLEX;
+ break;
+ case TKWenum:
+ case TKWstruct:
+ case TKWunion:
+ lex(cm, &tk);
+ ty = tagtype(cm, tk.t);
+ st->tagdecl = 1;
+ joinspan(&span.ex, tk.span.ex);
+ if (st->base.t) goto DupBase;
+ st->base = ty;
+ continue;
+ case TKW__typeof__: case TKWtypeof:
+ lex(cm, &tk);
+ ty = ptypeof(cm);
+ joinspan(&span.ex, tk.span.ex);
+ if (st->base.t) goto DupBase;
+ st->base = ty;
+ continue;
+ case TKIDENT:
+ if (!st->base.t && !arith && (decl = finddecl(cm, tk.name))
+ && decl->scls == SCTYPEDEF) {
+ lex(cm, &tk);
+ st->base = decl->ty;
+ continue;
+ }
+ /* fallthru */
+ default:
+ goto End;
+ case TKW_BitInt:
+ case TKW_Decimal128: case TKW_Decimal32:
+ case TKW_Decimal64: case TKW_Imaginary:
+ error(&tk.span, "%'tk is unsupported", &tk);
+ arith = arith ? arith : KINT;
+ }
+
+ if ((!properdecl && scls && !(st->kind == DFUNCPARAM && scls == SCREGISTER)) || (scls == SCAUTO && st->kind == DTOPLEVEL))
+ error(&tk.span, "storage class specifier %'tk is not allowed here", &tk);
+ else
+ st->scls |= scls;
+
+ joinspan(&span.ex, tk.span.ex);
+ lex(cm, &tk);
+ }
+End:
+ if (pspan) *pspan = span;
+
+ if (st->scls && properdecl) {
+ if (popcnt(st->scls) > 1)
+ error(&span, "invalid combination of storage class specifiers");
+ }
+ if (st->base.t && arith) {
+ /* combining arith type specifiers and other types */
+ Bad:
+ error(&span, "invalid type specifiers");
+ st->base = mktype(TYINT);
+ } else if (!st->base.t && arith) {
+ enum typetag t;
+ if (arith == KFLOAT)
+ t = TYFLOAT;
+ else if (arith == KDOUBLE)
+ t = TYDOUBLE;
+ else if (arith == (KLONG | KDOUBLE))
+ t = TYLDOUBLE;
+ else if (arith == KBOOL)
+ t = TYBOOL;
+ else if (arith == KCHAR)
+ t = TYCHAR;
+ else if (arith == (KSIGNED | KCHAR))
+ t = TYSCHAR;
+ else if (arith == (KUNSIGNED | KCHAR))
+ t = TYUCHAR;
+ else if ((arith & ~KINT & ~KSIGNED) == KSHORT)
+ t = TYSHORT;
+ else if ((arith & ~KINT) == (KUNSIGNED | KSHORT))
+ t = TYUSHORT;
+ else if ((arith & ~KINT & ~KSIGNED) == 0)
+ t = TYINT;
+ else if ((arith & ~KINT) == KUNSIGNED)
+ t = TYUINT;
+ else if ((arith & ~KINT & ~KSIGNED) == KLONG)
+ t = TYLONG;
+ else if ((arith & ~KINT) == (KUNSIGNED | KLONG))
+ t = TYULONG;
+ else if ((arith & ~KINT & ~KSIGNED) == KLONGLONG)
+ t = TYVLONG;
+ else if ((arith & ~KINT) == (KUNSIGNED | KLONGLONG))
+ t = TYUVLONG;
+ else if (arith == (KCOMPLEX | KFLOAT))
+ t = TYCOMPLEXF;
+ else if (arith == (KCOMPLEX | KDOUBLE))
+ t = TYCOMPLEX;
+ else if (arith == (KCOMPLEX | KLONG | KDOUBLE))
+ t = TYCOMPLEXL;
+ else
+ goto Bad;
+ st->base = mktype(t ? t : TYINT);
+ } else if (!st->base.t) {
+ if (ccopt.cstd < STDC23 && peek(cm, NULL) == TKIDENT) {
+ if (ccopt.cstd > STDC89)
+ warn(&tk.span, "type implicitly declared as int");
+ st->base = mktype(TYINT);
+ } else {
+ error(&tk.span, "type specifier missing");
+ }
+ }
+}
+
+/* circular doubly linked list used to parse declarators */
+enum { EARRAYUNSIZED = 0xFF /* for count.t */ };
+static struct decllist {
+ struct decllist *prev, *next;
+ uchar t; /* TYPTR, TYARRAY or TYFUNC */
+ union {
+ uchar qual; /* TYPTR */
+ struct expr count; /* TYARRAY */
+ struct { /* TYFUNC */
+ union type *param;
+ internstr *pnames;
+ struct span *pspans;
+ uchar *pqual;
+ short npar;
+ bool kandr : 1, variadic : 1;
+ };
+ };
+ struct span span;
+} decltmp[64], *declfreelist;
+static bool usingdeclparamtmp;
+static union type declparamtmp[16];
+static internstr declpnamestmp[16];
+static struct span declpspanstmp[16];
+static uchar declpqualtmp[16];
+
+static void
+declinsert(struct decllist *list, const struct decllist *node)
+{
+ struct decllist *pnode = declfreelist;
+ if (!pnode) fatal(NULL, "too many nested declarators");
+ declfreelist = declfreelist->next;
+ *pnode = *node;
+ pnode->next = list->next;
+ pnode->prev = list;
+ list->next->prev = pnode;
+ list->next = pnode;
+}
+
+static int
+cvqual(struct comp *cm)
+{
+ struct token tk;
+ int q = 0;
+ while (match(cm, &tk, TKWconst) || match(cm, &tk, TKWvolatile) || match(cm, &tk, TKWrestrict))
+ q |= tk.t == TKWconst ? QCONST : tk.t == TKWvolatile ? QVOLATILE : 0;
+ return q;
+}
+
+static void
+decltypes(struct comp *cm, struct decllist *list, internstr *name, struct span *span, struct span *namespan)
+{
+ struct token tk;
+ struct decllist *ptr, node;
+
+ while (match(cm, &tk, '*')) {
+ node.t = TYPTR;
+ node.qual = cvqual(cm);
+ node.span = tk.span;
+ declinsert(list, &node);
+ joinspan(&span->ex, tk.span.ex);
+ }
+ ptr = list->next;
+ switch (peek(cm, &tk)) {
+ case '(':
+ lex(cm, &tk);
+ if (isdecltok(cm)) {
+ goto Func;
+ } else if (match(cm, &tk, ')')) {
+ /* T () is K&R func proto */
+ node.span = tk.span;
+ node.t = TYFUNC;
+ node.param = NULL;
+ node.pqual = NULL;
+ node.pnames = NULL;
+ node.pspans = NULL;
+ node.variadic = 0;
+ node.kandr = 1;
+ node.npar = 0;
+ declinsert(ptr->prev, &node);
+ joinspan(&span->ex, tk.span.ex);
+ break;
+ } else {
+ decltypes(cm, list, name, span, namespan);
+ expect(cm, ')', NULL);
+ joinspan(&span->ex, tk.span.ex);
+ }
+ break;
+ case TKIDENT:
+ if (!name)
+ error(&tk.span, "unexpected identifier in type name");
+ else {
+ *name = tk.name;
+ *namespan = tk.span;
+ }
+ lex(cm, &tk);
+ joinspan(&span->ex, tk.span.ex);
+ break;
+ default:
+ if (name)
+ *name = NULL;
+ }
+ for (;;) {
+ if (match(cm, &tk, '[')) {
+ node.span = tk.span;
+ int q = 0;
+ bool statik = 0;
+ if (in_range(peek(cm, &tk), TKWBEGIN_, TKWEND_)) {
+ q = cvqual(cm);
+ statik = match(cm, NULL, TKWstatic);
+ q |= cvqual(cm);
+ }
+ (void)q, (void)statik; /* stub */
+
+ if (match(cm, &tk, ']')) {
+ node.count.t = EARRAYUNSIZED;
+ } else {
+ node.count = arraycountexpr(cm);
+ peek(cm, &tk);
+ joinspan(&node.span.ex, tk.span.ex);
+ expect(cm, ']', NULL);
+ }
+ node.t = TYARRAY;
+ declinsert(ptr->prev, &node);
+ joinspan(&span->ex, node.span.ex);
+ } else if (match(cm, &tk, '(')) Func: {
+ vec_of(union type) params = {0};
+ vec_of(uchar) qual = {0};
+ vec_of(internstr) names = {0};
+ vec_of(struct span) spans = {0};
+
+ if (!usingdeclparamtmp) {
+ usingdeclparamtmp = 1;
+ vinit(&params, 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(&params, 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(&params, 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(&params);
+ vfree(&names);
+ vfree(&spans);
+ vfree(&qual);
+ }
+ node.t = TYFUNC;
+ node.param = params.n ? params.p : NULL;
+ node.pqual = qual.n ? qual.p : NULL;
+ node.pnames = params.n ? names.p : NULL;
+ node.pspans = params.n ? spans.p : NULL;
+ node.npar = params.n;
+ declinsert(ptr->prev, &node);
+ joinspan(&span->ex, node.span.ex);
+ } else break;
+ }
+}
+
+static struct decl
+declarator(struct declstate *st, struct comp *cm, struct span span0) {
+ struct decl decl = { st->base, st->scls, .qual = st->qual, .align = st->align, .span = span0 };
+ struct decllist list = { &list, &list }, *l;
+ static bool inidecltmp = 0;
+ struct span namespan ={0};
+ if (!inidecltmp) {
+ inidecltmp = 1;
+ for (int i = 0; i < countof(decltmp); ++i) {
+ decltmp[i].next = declfreelist;
+ declfreelist = &decltmp[i];
+ }
+ }
+
+ decltypes(cm, &list, st->kind == DCASTEXPR ? NULL : &decl.name, &decl.span, &namespan);
+ if (!decl.name && st->kind != DCASTEXPR && st->kind != DFUNCPARAM) {
+ if (list.prev == &list) lex(cm, NULL);
+ error(&decl.span, "expected `(', `*' or identifier");
+ }
+ for (l = list.prev; l != &list; l = l->prev) {
+ switch (l->t) {
+ case TYPTR:
+ decl.ty = mkptrtype(decl.ty, decl.qual);
+ decl.qual = l->qual;
+ break;
+ case TYARRAY:
+ if (isincomplete(decl.ty))
+ error(&l->span, "array has incomplete element type '%ty'", decl.ty);
+ else if (decl.ty.t == TYFUNC)
+ error(&l->span, "array has element has function type '%ty'", decl.ty);
+ if (l->count.t == EARRAYUNSIZED) /* unsized '[]' */
+ decl.ty = mkarrtype(decl.ty, decl.qual, 0);
+ else {
+ uint n = 0;
+ struct expr *ex = &l->count;
+ if (!ex->t) { /* ['*'] */
+ if (l->prev != &list) error(&l->span, "[*] array declarator is not allowed here");
+ } else if (!eval(ex, EVINTCONST)) {
+ error(&ex->span, "array length is not an integer constant");
+ } else if (issigned(ex->ty) && ex->i < 0) {
+ error(&ex->span, "array length is negative");
+ } else if (ex->u > (1ull << (8*sizeof n)) - 1) {
+ error(&ex->span, "array too long (%ul)", ex->u);
+ } else if (ex->u == 0) {
+ /* when struct field, silently accept zero-length as synonym of '[]' for flexible array member */
+ if (l->prev != &list || st->kind != DFIELD) {
+ warn(&ex->span, "array cannot have zero length");
+ }
+ } else {
+ n = ex->u;
+ }
+ decl.ty = mkarrtype(decl.ty, decl.qual, n);
+ }
+ break;
+ case TYFUNC:
+ if (decl.ty.t == TYFUNC)
+ error(&decl.span, "function cannot return function type '%ty'", decl.ty);
+ else if (decl.ty.t == TYARRAY)
+ error(&decl.span, "function cannot return array type '%ty'", decl.ty);
+ if (l->kandr && ccopt.cstd > STDC89 && (ccopt.cstd >= STDC23 || ccopt.pedant))
+ warn(&l->span, "function declaration without a prototype is deprecated");
+ decl.ty = mkfntype(decl.ty, l->npar, l->param, l->kandr, l->variadic);
+ if (l->param != declparamtmp) free(l->param);
+ if (l->prev == &list && l->npar) { /* last */
+ st->pnames = alloccopy(&cm->fnarena, l->pnames, l->npar * sizeof(char *), 0);
+ st->pspans = alloccopy(&cm->fnarena, l->pspans, l->npar * sizeof(struct span), 0);
+ st->pqual = l->pqual ? alloccopy(&cm->fnarena, l->pqual, l->npar, 1) : NULL;
+ decl.inlin = st->fninline;
+ decl.noret = st->fnnoreturn;
+ }
+ if (l->pqual != declpqualtmp) free(l->pqual);
+ if (l->pnames != declpnamestmp) free(l->pnames);
+ if (l->pspans != declpspanstmp) free(l->pspans);
+ if (l->param == declparamtmp) usingdeclparamtmp = 0;
+ decl.qual = 0;
+ break;
+ }
+
+ l->next = declfreelist;
+ declfreelist = l;
+ }
+ if (st->kind != DCASTEXPR && decl.name)
+ decl.span = namespan;
+ return decl;
+}
+
+static void
+pstaticassert(struct comp *cm, struct span *span)
+{
+ struct expr ex;
+ struct token tk, msg = {0};
+
+ /* _Static_assert '(' <expr> [ ',' <strlit> ] ')' ';' */
+ expect(cm, '(', NULL);
+ ex = expr(cm);
+ peek(cm, &tk);
+ if (match(cm, &tk, ',')) {
+ peek(cm, &msg);
+ expect(cm, TKSTRLIT, NULL);
+ }
+ peek(cm, &tk);
+ expect(cm, ')', NULL);
+ expect(cm, ';', NULL);
+
+ joinspan(&span->ex, tk.span.ex);
+ if (!msg.t && ccopt.cstd == STDC11)
+ warn(span, "static assert without message is a C23 extension");
+ if (!eval(&ex, EVINTCONST)) {
+ error(&ex.span, "static assert expression is not an integer constant");
+ } else if (iszero(ex)) {
+ if (msg.t)
+ error(&ex.span, "static assertion failed: %'S", msg.s, msg.len);
+ else
+ error(&ex.span, "static assertion failed");
+ }
+}
+
+static struct decl
+pdecl(struct declstate *st, struct comp *cm) {
+ struct token tk;
+ struct decl decl;
+ bool properdecl = st->kind == DTOPLEVEL || st->kind == DFUNCVAR;
+ bool first = 0;
+
+ assert(!st->funcdef);
+
+ if (st->varini || st->bitf) {
+ memset(&decl, 0, sizeof decl);
+ goto AfterIniBitf;
+ }
+ decl.sym = NULL;
+
+ if (st->base0) goto DeclSpec;
+ if (!st->base.t) {
+ if (properdecl && (match(cm, &tk, TKW_Static_assert) || match(cm, &tk, TKWstatic_assert))) {
+ pstaticassert(cm, &tk.span);
+ return (struct decl){0};
+ }
+ first = 1;
+ if (match(cm, &tk, ';')) {
+ st->empty = 1;
+ return (struct decl){.span = tk.span};
+ }
+
+ DeclSpec:
+ st->base0 = 0;
+ while (attrspec(cm, &st->attr)) ;
+ declspec(st, cm, &decl.span);
+ } else {
+ peek(cm, &tk);
+ decl.span = tk.span;
+ }
+ if (st->scls == SCTYPEDEF) properdecl = 0;
+
+ if (first && st->tagdecl && match(cm, &tk, ';')) {
+ decl = (struct decl) { st->base, st->scls, st->qual, .align = st->align, .span = decl.span };
+ return decl;
+ } else if (st->kind == DFIELD && match(cm, &tk, ':')) {
+ decl = (struct decl) { st->base, st->scls, st->qual, .align = st->align, .span = decl.span };
+ st->bitf = 1;
+ return decl;
+ }
+ decl = declarator(st, cm, decl.span);
+ if (decl.ty.t != TYFUNC && st->fninline)
+ error(&decl.span, "`inline' used on non-function declaration");
+ if (decl.ty.t != TYFUNC && st->fnnoreturn)
+ error(&decl.span, "`_Noreturn' used on non-function declaration");
+ /* trailing attributes */
+ if (st->kind == DTOPLEVEL || st->kind == DFUNCVAR) {
+ while (attrspec(cm, &st->attr)) ;
+ if (match(cm, NULL, TKW__asm__) && expect(cm, '(', NULL)) {
+ peek(cm, &tk);
+ if (expect(cm, TKSTRLIT, "asm symbol name")) {
+ decl.sym = intern_(tk.s, tk.len);
+ }
+ expect(cm, ')', NULL);
+ }
+ while (attrspec(cm, &st->attr)) ;
+ }
+
+ if (properdecl && match(cm, &tk, '=')) {
+ st->varini = 1;
+ return decl;
+ } else if (first && decl.ty.t == TYFUNC && match(cm, &tk, '{')) {
+ st->funcdef = 1;
+ return decl;
+ } else if (st->kind == DFIELD && match(cm, &tk, ':')) {
+ st->bitf = 1;
+ return decl;
+ }
+
+AfterIniBitf:
+ st->varini = st->bitf = 0;
+ st->more = 0;
+ if (st->kind != DCASTEXPR && st->kind != DFUNCPARAM) {
+ if (match(cm, &tk, ','))
+ st->more = 1;
+ else expect(cm, st->kind == DFUNCPARAM ? ')' : ';', "or `,'");
+ }
+
+ return decl;
+}
+
+/*****************/
+/* IR Generation */
+/*****************/
+
+static inline union ref
+exprvalue(struct function *fn, const struct expr *ex)
+{
+ return compileexpr(fn, ex, /*discard*/ 0);
+}
+static inline void
+expreffects(struct function *fn, const struct expr *ex)
+{
+ compileexpr(fn, ex, /*discard*/ 1);
+}
+
+static void
+structcopy(struct function *fn, union type ty, union ref dst, union ref src)
+{
+ union irtype typ = mkirtype(ty);
+ addinstr(fn, mkarginstr(typ, dst));
+ addinstr(fn, mkarginstr(typ, src));
+ addinstr(fn, mkintrin(INstructcopy, 0, 2));
+}
+
+static union ref
+structreturn(struct function *fn, const struct expr *src)
+{
+ return expraddr(fn, src);
+}
+
+static union ref compilecall(struct function *fn, const struct expr *ex);
+
+static internstr
+mkhiddensym(const char *fnname, const char *name, int id)
+{
+ char buf[200];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ assert(id > 0);
+ if (fnname)
+ bfmt(&wbuf, "%s.%s.%d", fnname, name, id-1);
+ else
+ bfmt(&wbuf, "%s.%d", name, id-1);
+ ioputc(&wbuf, 0);
+ assert(!wbuf.err);
+ return intern(buf);
+}
+
+static void geninit(struct function *fn, union type t, union ref dst, const struct expr *src);
+static union ref condexprvalue(struct function *fn, const struct expr *ex, bool discard);
+
+union ref
+expraddr(struct function *fn, const struct expr *ex)
+{
+ struct decl *decl;
+ union ref r;
+
+ switch (ex->t) {
+ case ESYM:
+ decl = &declsbuf.p[ex->decl];
+ assert(decl != NULL);
+ switch (decl->scls) {
+ case SCAUTO: case SCREGISTER:
+ assert(decl->id >= 0);
+ return mkref(RTMP, decl->id);
+ case SCEXTERN: case SCNONE: case SCSTATIC:
+ return mksymref(decl->sym, (SFUNC & -(decl->ty.t == TYFUNC)) | (SLOCAL & -(decl->scls == SCSTATIC || decl->isdef)));
+ default:
+ assert(0);
+ }
+ break;
+ case ESSYMREF:
+ return irbinop(fn, Oadd, KPTR,
+ mksymref(ex->ssym.sym, (SFUNC & -ex->ssym.func) | (SLOCAL & -ex->ssym.local)),
+ mkintcon(KPTR, ex->ssym.off));
+ case ESTRLIT:
+ /* XXX endian for wide strs */
+ return mkdatref(NULL, ex->ty, typesize(ex->ty), typealign(ex->ty), ex->s.p, ex->s.n * typesize(typechild(ex->ty)), /*deref*/0, fn != NULL);
+ case EDEREF:
+ return exprvalue(fn, ex->sub);
+ case EGETF:
+ r = expraddr(fn, ex->sub);
+ assert(ex->fld.bitsiz == 0);
+ return irbinop(fn, Oadd, KPTR, r, mkintcon(KI32, ex->fld.off));
+ case ESET:
+ assert(isagg(ex->ty));
+ r = expraddr(fn, &ex->sub[1]);
+ structcopy(fn, ex->ty, expraddr(fn, &ex->sub[0]), r);
+ return r;
+ case ECALL:
+ assert(isagg(ex->ty));
+ return compilecall(fn, ex);
+ case EVAARG:
+ assert(isagg(ex->ty));
+ return builtin_va_arg_comp(fn, ex, 0);
+ case EINIT:
+ if (fn) {
+ /* compound literal, allocate temp */
+ r = addinstr(fn, mkalloca(typesize(ex->ty), typealign(ex->ty)));
+ geninit(fn, ex->ty, r, ex);
+ return r;
+ } else {
+ /* emit static dat */
+ static int id;
+ struct initparser ip[1] = {0};
+ union type ty = ex->ty;
+ internstr sym = mkhiddensym(NULL, ".LC", ++id);
+ ip->sec = Sdata; /* TODO put in rodata if possible */
+ ip->ev = EVSTATICINI;
+ assert(!isincomplete(ty));
+ ip->off = objnewdat(sym, ip->sec, 0, typesize(ty), typealign(ty));
+ if (!iniwriterec(NULL, ip, 0, (struct expr *)ex))
+ error(&ex->span, "cannot not evaluate expression statically");
+ return mksymref(sym, 0);
+ }
+ case ESEQ:
+ expreffects(fn, &ex->sub[0]);
+ return expraddr(fn, &ex->sub[1]);
+ case ECOND:
+ assert(isagg(ex->ty));
+ return condexprvalue(fn, ex, 0);
+ default:
+ assert(!"lvalue?>");
+ }
+
+}
+
+static union ref
+genload(struct function *fn, union type t, union ref ref, bool volatyl)
+{
+ struct instr ins = {0};
+
+ assert(isscalar(t));
+ ins.cls = type2cls[scalartypet(t)];
+ assert(ins.cls);
+ switch (typesize(t)) {
+ case 1: ins.op = issigned(t) ? Oloads8 : Oloadu8; break;
+ case 2: ins.op = issigned(t) ? Oloads16 : Oloadu16; break;
+ case 4: ins.op = isflt(t) ? Oloadf32 : issigned(t) ? Oloads32 : Oloadu32; break;
+ case 8: ins.op = isflt(t) ? Oloadf64 : Oloadi64; break;
+ default: assert(0);
+ }
+ ins.l = ref;
+ ins.keep = volatyl;
+ return addinstr(fn, ins);
+}
+
+static union ref
+genstore(struct function *fn, union type t, union ref ptr, union ref val)
+{
+ struct instr ins = {0};
+
+ assert(isscalar(t));
+ switch (typesize(t)) {
+ case 1: ins.op = Ostorei8; break;
+ case 2: ins.op = Ostorei16; break;
+ case 4: ins.op = isflt(t) ? Ostoref32 : Ostorei32; break;
+ case 8: ins.op = isflt(t) ? Ostoref64 : Ostorei64; break;
+ default: assert(0);
+ }
+ ins.l = ptr;
+ ins.r = val;
+ return addinstr(fn, ins);
+}
+
+static void genbitfstore(struct function *fn, const union type ty, union ref addr,
+ const struct exgetfld *fld, union ref tmp, union ref val);
+
+static void
+geninit(struct function *fn, union type t, union ref dst, const struct expr *src)
+{
+ union ref adr;
+ if (src->t == EINIT) {
+ struct init *ini = src->init;
+ uint siz = typesize(t);
+ uint align = typealign(t);
+ struct bitset azero[1] = {0};
+
+ if (BSSIZE(siz) <= countof(ini->zero)) {
+ for (int i = 0; i < siz; i += align) {
+ for (int j = 0; j < align; ++j) {
+ if (bstest(ini->zero, i + j)) {
+ bsset(azero, i);
+ break;
+ }
+ }
+ }
+ if (bscount(azero, countof(azero)) < 32) {
+ /* write individual zeros at non initialized gaps */
+ for (uint i = 0; bsiter(&i, azero, countof(azero)) && i < siz; i += align) {
+ adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, i));
+ addinstr(fn, mkinstr(Ostorei8 + ilog2(align), 0, .l = adr, .r = ZEROREF));
+ }
+ } else {
+ goto Memset0;
+ }
+ } else Memset0: {
+ /* memset(dst,0,siz) */
+ /* TODO make it into an intrinsic */
+ struct instr call = { Ocall, KPTR };
+ addinstr(fn, mkarginstr(cls2type(KPTR), dst));
+ addinstr(fn, mkarginstr(cls2type(KI32), ZEROREF));
+ addinstr(fn, mkarginstr(cls2type(type2cls[targ_sizetype]), mkintcon(type2cls[targ_sizetype], siz)));
+ call.l = mksymref(istr_memset, 1);
+ call.r = mkcallarg(cls2type(KPTR), 3, -1);
+ addinstr(fn, call);
+ }
+ for (struct initval *val = ini->vals; val; val = val->next) {
+ uint off = val->off;
+ struct expr *ex = &val->ex;
+ adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, off));
+ if (ex->t == EINIT || ex->t == ESTRLIT) {
+ geninit(fn, ex->ty, adr, ex);
+ } else if (isagg(ex->ty)) {
+ structcopy(fn, ex->ty, adr, expraddr(fn, ex));
+ } else if (!val->bitsiz) {
+ genstore(fn, ex->ty, adr, exprvalue(fn, ex));
+ } else {
+ union ref q = exprvalue(fn, ex);
+ genbitfstore(fn, ex->ty, adr, &(struct exgetfld){0, val->bitsiz, val->bitoff}, NOREF, q);
+ }
+ }
+ } else if (src->t == ESTRLIT) {
+ union type ctyp = typechild(src->ty);
+ uint csiz = typesize(ctyp);
+ adr = dst;
+ for (uint i = 0; i < src->s.n; ++i) {
+ if (csiz == 1)
+ genstore(fn, ctyp, adr, mkref(RICON, src->s.p[i]));
+ else if (csiz == 2)
+ genstore(fn, ctyp, adr, mkref(RICON, src->s.w16[i]));
+ else
+ genstore(fn, ctyp, adr, mkintcon(KI32, src->s.w32[i]));
+ adr = irbinop(fn, Oadd, KPTR, dst, mkref(RICON, (i+1)*csiz));
+ }
+ genstore(fn, ctyp, adr, ZEROREF); /* null term */
+ } else assert(0);
+}
+
+static bool
+isboollike(struct function *fn, union ref r)
+{
+ struct instr *ins;
+ if (r.t == RICON && (r.i == 0 || r.i == 1)) return 1;
+ if (r.t != RTMP) return 0;
+ ins = &instrtab[r.i];
+ if (oiscmp(ins->op)) /* these instrs already have output range of [0,1] */
+ return 1;
+ if (ins->op == Ophi) { /* check if all the phi args are boollike */
+ struct block *blk;
+ union ref *phi = NULL;
+ for (blk = fn->curblk; phi == NULL; blk = blk->lprev) {
+ /* find blk that defines phi */
+ assert(blk != fn->entry);
+ for (int i = 0; i < blk->phi.n; ++i){
+ if (blk->phi.p[i] == r.i) {
+ phi = phitab.p[ins->l.i];
+ break;
+ }
+ }
+ }
+ for (int i = 0; i < blk->npred; ++i) {
+ if (!isboollike(fn, phi[i])) {
+ return 0;
+ }
+ }
+ return 1;
+ }
+ if (in_range(ins->op, Oand, Oxor))
+ return isboollike(fn, ins->l) && isboollike(fn, ins->r);
+ if (ins->op == Ocopy || in_range(ins->op, Oexts8, Oextu32))
+ return isboollike(fn, ins->l);
+ if (ins->op == Oparam)
+ return typedata[fn->fnty.dat].param[ins->l.i].t == TYBOOL;
+ return 0;
+}
+
+union ref
+scalarcvt(struct function *fn, union type to, union type from, union ref ref)
+{
+ enum irclass kto = type2cls[scalartypet(to)], kfrom = type2cls[scalartypet(from)];
+ enum op op;
+ if (to.bits == from.bits) return ref;
+ assert(kto && kfrom);
+ if (kto == kfrom && to.t != TYBOOL) return ref;
+
+ if (kisflt(kto) || kisflt(kfrom)) {
+ if (ref.t == RICON) {
+ assert(kisflt(kto) && kisint(kfrom));
+ return mkfltcon(kto, kto == KF32 ? (float)ref.i : (double)ref.i);
+ }
+ if (kisflt(kto) && kfrom == KI32) op = issigned(from) ? Ocvts32f : Ocvtu32f;
+ else if (to.t == TYBOOL && kisflt(kfrom)) return irbinop(fn, Oneq, kfrom, ref, mkfltcon(kfrom, 0.0));
+ else if (kisflt(kto) && kfrom == KI64) op = issigned(from) ? Ocvts64f : Ocvtu64f;
+ else if (kto == KF64 && kfrom == KF32) op = Ocvtf32f64;
+ else if (kto == KF32 && kfrom == KF64) op = Ocvtf64f32;
+ else if (kfrom == KF32) op = issigned(to) ? Ocvtf32s : Ocvtf32u;
+ else if (kfrom == KF64) op = issigned(to) ? Ocvtf64s : Ocvtf64u;
+ else assert(0);
+ } else {
+ if (to.t == TYBOOL) {
+ if (from.t == TYBOOL) return ref;
+ if (isboollike(fn, ref))
+ return kfrom == KI32 ? ref : scalarcvt(fn, mktype(TYINT), from, ref);
+ return irbinop(fn, Oneq, kfrom, ref, ZEROREF);
+ }
+ else if (kfrom == KI32 && issigned(from)) op = Oexts32;
+ else if (kfrom == KI32) op = Oextu32;
+ else if (kto == KI32 && isintcon(ref))
+ return issigned(to) ? mkintcon(kto, (int)intconval(ref)) : mkintcon(kto, (uint)intconval(ref));
+ else op = Ocopy;
+ }
+ return irunop(fn, op, kto, ref);
+}
+
+static union ref
+narrow(struct function *fn, enum irclass to, union type t, union ref ref, uint bitsiz)
+{
+ enum typetag tt = scalartypet(t);
+ assert(isscalar(t));
+ if (targ_primsizes[tt] < cls2siz[to]) {
+ enum op op;
+ if (isfltt(tt)) {
+ assert(to == KF32 && tt >= TYDOUBLE);
+ op = Ocvtf64f32;
+ } else {
+ static const enum op ext[5][2] = {
+ [1] = {Oextu8, Oexts8}, [2] = {Oextu16, Oexts16}, [4] = {Oextu32, Oexts32}
+ };
+ op = ext[targ_primsizes[tt]][issignedt(tt)];
+ }
+ ref = irunop(fn, op, to, ref);
+ }
+ if (bitsiz) {
+ assert(kisint(to) && isintt(tt) && bitsiz < 8*targ_primsizes[tt]);
+ if (!issignedt(tt)) {
+ ref = irbinop(fn, Oand, to, ref, mkintcon(to, (1ull<<bitsiz)-1));
+ } else {
+ uint sh = 8*cls2siz[to] - bitsiz;
+ ref = irbinop(fn, Oshl, to, ref, mkref(RICON, sh));
+ ref = irbinop(fn, Osar, to, ref, mkref(RICON, sh));
+ }
+ }
+ return ref;
+}
+
+union ref
+genptroff(struct function *fn, enum op op, uint siz, union ref ptr,
+ union type t, union ref idx)
+{
+ uint cls = type2cls[targ_sizetype];
+ union ref off;
+ assert(siz);
+
+ idx = scalarcvt(fn, mktype(targ_sizetype), t, idx);
+ if (siz == 1) off = idx;
+ else if (idx.t == RICON) {
+ if (op == Osub) op = Oadd, idx.i = -idx.i;
+ off = mkintcon(cls, idx.i * (int)siz);
+ } else {
+ off = irbinop(fn, Omul, cls, idx, mkintcon(cls, siz));
+ }
+ assert(in_range(op, Oadd, Osub));
+ return irbinop(fn, op, KPTR, ptr, off);
+}
+
+union ref
+genptrdiff(struct function *fn, uint siz, union ref a, union ref b)
+{
+ uint cls = type2cls[targ_ptrdifftype];
+ assert(siz > 0);
+ a = irbinop(fn, Osub, cls, a, b);
+ if (siz == 1) return a;
+ else if (ispo2(siz))
+ return irbinop(fn, Osar, cls, a, mkintcon(cls, ilog2(siz)));
+ else
+ return irbinop(fn, Odiv, cls, a, mkintcon(cls, siz));
+}
+
+/* used to emit the jumps in an in if (), while (), etc condition */
+static void
+condjump(struct function *fn, const struct expr *ex, struct block *tr, struct block *fl)
+{
+ struct block *next, *next2;
+Recur:
+ for (; ex->t == ESEQ; ex = &ex->sub[1])
+ expreffects(fn, &ex->sub[0]);
+ if (ex->t == ELOGAND) {
+ next = newblk(fn);
+ condjump(fn, &ex->sub[0], next, fl);
+ useblk(fn, next);
+ ex = &ex->sub[1];
+ goto Recur;
+ } else if (ex->t == ELOGIOR) {
+ next = newblk(fn);
+ condjump(fn, &ex->sub[0], tr, next);
+ useblk(fn, next);
+ ex = &ex->sub[1];
+ goto Recur;
+ } else if (ex->t == ECOND) {
+ next = newblk(fn);
+ next2 = newblk(fn);
+ condjump(fn, &ex->sub[0], next, next2);
+ useblk(fn, next);
+ condjump(fn, &ex->sub[1], tr, fl);
+ useblk(fn, next2);
+ condjump(fn, &ex->sub[2], tr, fl);
+ } else if (ex->t == ELOGNOT) {
+ Negate:
+ /* swap tr,fl */
+ next = tr;
+ tr = fl;
+ fl = next;
+ ex = &ex->sub[0];
+ goto Recur;
+ } else if (ex->t == EEQU && isnullpo(&ex->sub[1])) { /* == 0 */
+ goto Negate;
+ } else if (ex->t == ENEQ && isnullpo(&ex->sub[1])) { /* != 0 */
+ ex = &ex->sub[0];
+ goto Recur;
+ } else {
+ putcondbranch(fn, exprvalue(fn, ex), tr, fl);
+ }
+}
+
+struct condphis {
+ union type typ;
+ vec_of(union ref) ref;
+};
+
+static void
+condexprrec(struct function *fn, const struct expr *ex, struct condphis *phis, struct block *end)
+{
+Recur:
+ for (; ex->t == ESEQ; ex = &ex->sub[1])
+ expreffects(fn, &ex->sub[0]);
+ int prevpred = end->npred;
+ if (ex->t == ELOGAND) {
+ struct block *tr = newblk(fn);
+ condjump(fn, &ex->sub[0], tr, end);
+ assert(prevpred <= end->npred);
+ if (phis) for (int n = end->npred - prevpred; n > 0; --n) {
+ vpush(&phis->ref, mkref(RICON, 0));
+ }
+ useblk(fn, tr);
+ ex = &ex->sub[1];
+ goto Recur;
+ } else if (ex->t == ELOGIOR) {
+ struct block *fl = newblk(fn);
+ condjump(fn, &ex->sub[0], end, fl);
+ assert(prevpred <= end->npred);
+ if (phis) for (int n = end->npred - prevpred; n > 0; --n) {
+ vpush(&phis->ref, mkref(RICON, 1));
+ }
+ useblk(fn, fl);
+ ex = &ex->sub[1];
+ goto Recur;
+ } else if (ex->t == ECOND) {
+ struct block *tr = newblk(fn), *fl = newblk(fn);
+ condjump(fn, &ex->sub[0], tr, fl);
+ useblk(fn, tr);
+ condexprrec(fn, &ex->sub[1], phis, end);
+ useblk(fn, fl);
+ ex = &ex->sub[2];
+ goto Recur;
+ } else {
+ if (!phis) {
+ expreffects(fn, ex);
+ } else {
+ union ref val = exprvalue(fn, ex);
+ if (isscalar(phis->typ))
+ val = scalarcvt(fn, phis->typ, ex->ty, val);
+ else assert(ex->ty.bits == phis->typ.bits);
+ vpush(&phis->ref, val);
+ }
+ putbranch(fn, end);
+ }
+}
+
+/* the naive way to generate something like a ? b : c ? d : e, uses multiple phis,
+ * this code reduces such nested conditional expressions into one phi */
+static union ref
+condexprvalue(struct function *fn, const struct expr *ex, bool discard)
+{
+ union ref refbuf[8];
+ struct condphis phis = { ex->t == ECOND ? ex->ty : mktype(TYBOOL), VINIT(refbuf, countof(refbuf)) };
+ struct block *dst = newblk(fn);
+ condexprrec(fn, ex, discard ? NULL : &phis, dst);
+ useblk(fn, dst);
+ if (discard) return NOREF;
+ enum irclass k;
+ if (isscalar(ex->ty)) {
+ k = type2cls[scalartypet(ex->ty)];
+ assert(k);
+ } else {
+ assert(isagg(ex->ty) || isptrcvt(ex->ty));
+ k = KPTR;
+ }
+ union ref r = addphi(fn, k, phis.ref.p);
+ vfree(&phis.ref);
+ return r;
+}
+
+static union ref
+compilecall(struct function *fn, const struct expr *ex)
+{
+ struct instr ins = {0};
+ struct expr *sub = ex->sub;
+ const struct typedata *td = &typedata[sub[0].ty.dat];
+ struct instr insnsbuf[10];
+ vec_of(struct instr) insns = VINIT(insnsbuf, countof(insnsbuf));
+
+ if (sub[0].t == ESYM && declsbuf.p[sub[0].decl].isbuiltin) {
+ return declsbuf.p[sub[0].decl].builtin->comp(fn, (struct expr *)ex, 0);
+ }
+ ins.op = Ocall;
+ if (isagg(ex->ty)) {
+ ins.cls = KPTR;
+ } else {
+ assert(isscalar(ex->ty) || ex->ty.t == TYVOID);
+ ins.cls = type2cls[scalartypet(ex->ty)];
+ assert(ins.cls || ex->ty.t == TYVOID);
+ }
+ ins.l = exprvalue(fn, &sub[0]);
+ for (int i = 0; i < ex->narg; ++i) {
+ struct expr *arg = &sub[i+1];
+ union type ty = i < td->nmemb ? td->param[i] : argpromote(arg->ty);
+ union ref r = scalarcvt(fn, ty, typedecay(arg->ty), exprvalue(fn, arg));
+ vpush(&insns, mkarginstr(mkirtype(ty), r));
+ }
+ for (int i = 0; i < insns.n; ++i)
+ addinstr(fn, insns.p[i]);
+ vfree(&insns);
+ ins.r = mkcallarg(mkirtype(ex->ty), ex->narg, td->variadic ? td->nmemb : td->kandr ? 0 : -1);
+ union ref r = addinstr(fn, ins);
+ if (sub[0].t == ESYM && declsbuf.p[sub[0].decl].noret) /* trap if noreturn func returns */
+ puttrap(fn);
+ return r;
+}
+
+static union ref
+genbitfload(struct function *fn, union ref *tmpval, const union type ty, union ref *addr,
+ const struct exgetfld *fld, bool volatyl)
+{
+ enum irclass k = type2cls[scalartypet(ty)];
+ uint off = fld->off, bitsiz = fld->bitsiz, bitoff = fld->bitoff;
+ union ref tmp;
+ uvlong mask;
+
+ assert(k);
+ *addr = irbinop(fn, Oadd, KPTR, *addr, mkintcon(KI32, off));
+ tmp = genload(fn, ty, *addr, volatyl);
+ if (tmpval) *tmpval = tmp;
+ if (!issigned(ty)) {
+ /* shift right and mask */
+ tmp = irbinop(fn, Oslr, k, tmp, mkref(RICON, bitoff));
+ if (bitsiz < 8*typesize(ty)) {
+ mask = bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1;
+ tmp = irbinop(fn, Oand, k, tmp, mkintcon(k, mask));
+ }
+ } else {
+ /* shift left and shift right arithmetic to propagate sign bit */
+ int sh = 8*cls2siz[k] - bitsiz - bitoff;
+ tmp = irbinop(fn, Oshl, k, tmp, mkref(RICON, sh));
+ sh += bitoff;
+ tmp = irbinop(fn, Osar, k, tmp, mkref(RICON, sh));
+ }
+ return tmp;
+}
+
+static void
+genbitfstore(struct function *fn, const union type ty, union ref addr,
+ const struct exgetfld *fld, union ref tmp, union ref val)
+{
+ enum irclass k = type2cls[scalartypet(ty)];
+ uint off = fld->off, bitsiz = fld->bitsiz, bitoff = fld->bitoff;
+ uint bittypesize = 8*typesize(ty);
+ uvlong mask;
+
+ assert(k);
+ if (!tmp.bits) {
+ addr = irbinop(fn, Oadd, KPTR, addr, mkintcon(KPTR, off));
+ tmp = genload(fn, ty, addr, 0);
+ }
+ mask = (bitsiz == 64 ? -1ull : (1ull << bitsiz) - 1) << bitoff;
+
+ /* mask out bits in existing container */
+ tmp = irbinop(fn, Oand, k, tmp, mkintcon(k, ~mask));
+
+ /* shift and mask source value */
+ if (isintcon(val)) {
+ val = mkintcon(k, ((uvlong)intconval(val) << bitoff) & mask);
+ } else {
+ val = irbinop(fn, Oshl, k, val, mkref(RICON, bitoff));
+ if (bitsiz < bittypesize)
+ val = irbinop(fn, Oand, k, val, mkintcon(k, mask));
+ }
+ /* combine and write */
+ if (bitsiz < bittypesize)
+ val = irbinop(fn, Oior, k, tmp, val);
+ genstore(fn, ty, addr, val);
+}
+
+static bool
+knowntruthy(bool *t, struct expr *ex)
+{
+ if (!eval(ex, EVFOLD)) return 0;
+
+ switch (ex->t) {
+ default: assert(0 && "!scalar?");
+ case ENUMLIT:
+ *t = isflt(ex->ty) ? ex->f != 0.0 : ex->u != 0;
+ break;
+ case ESTRLIT: case ESSYMREF:
+ /* string literals & symbol addresses are always truthy */
+ *t = 1;
+ break;
+ }
+ return 1;
+}
+
+union ref
+compileexpr(struct function *fn, const struct expr *ex, bool discard)
+{
+ union type ty;
+ union ref l, r, q, adr;
+ uint bitsiz;
+ enum op op;
+ enum irclass cls = type2cls[scalartypet(ex->ty)];
+ int swp = 0;
+ struct expr *sub;
+
+ //eval((struct expr *)ex, EVFOLD);
+ sub = ex->sub;
+
+ if (ex->ty.t != TYVOID && !isscalar(ex->ty)) {
+ /* fn & array designators evaluate to their address;
+ * so do aggregates for the purpose of code generation */
+ if (isagg(ex->ty) && isincomplete(ex->ty))
+ error(&ex->span, "use of incomplete type '%ty'", ex->ty);
+ return expraddr(fn, ex);
+ }
+ switch (ex->t) {
+ case ENUMLIT:
+ if (discard) return NOREF;
+ if (isflt(ex->ty))
+ return mkfltcon(cls, ex->f);
+ return mkintcon(cls, ex->i);
+ case ESYM:
+ if (discard && !(ex->qual & QVOLATILE)) return NOREF;
+ return genload(fn, ex->ty, expraddr(fn, ex), ex->qual & QVOLATILE);
+ case ESSYMREF:
+ return expraddr(fn, ex);
+ case EVAARG:
+ return builtin_va_arg_comp(fn, ex, discard);
+ case EGETF:
+ if (discard && !(ex->qual & QVOLATILE)) return NOREF;
+ if (ex->fld.bitsiz) {
+ /* bit-field */
+ r = expraddr(fn, ex->sub);
+ return genbitfload(fn, NULL, ex->ty, &r, &ex->fld, ex->qual & QVOLATILE);
+ }
+ return genload(fn, ex->ty, expraddr(fn, ex), ex->qual & QVOLATILE);
+ case ECAST:
+ if (ex->ty.t == TYVOID) {
+ expreffects(fn, sub);
+ return NOREF;
+ }
+ /* fallthru */
+ case EPLUS:
+ r = compileexpr(fn, sub, discard);
+ if (discard) return NOREF;
+ r = scalarcvt(fn, ex->ty, sub->ty, r);
+ if (isint(ex->ty) && (typesize(ex->ty) < typesize(sub->ty) || issigned(ex->ty) != issigned(sub->ty)))
+ return narrow(fn, type2cls[scalartypet(ex->ty)], ex->ty, r, 0);
+ return r;
+ case ENEG:
+ op = Oneg;
+ goto Unary;
+ case ECOMPL:
+ op = Onot;
+ Unary:
+ l = compileexpr(fn, sub, discard);
+ if (discard) return NOREF;
+ l = scalarcvt(fn, ex->ty, sub->ty, l);
+ return irunop(fn, op, cls, l);
+ case ELOGNOT:
+ for (; sub->t == ELOGNOT; ex = sub, sub = sub->sub)
+ swp ^= 1;
+ op = Oequ + swp;
+ l = compileexpr(fn, sub, discard);
+ if (discard) return NOREF;
+ l = scalarcvt(fn, ex->ty, sub->ty, l);
+ r = mkintcon(cls, 0);
+ return irbinop(fn, op, cls, l, r);
+ case EDEREF:
+ discard &= (ex->qual & QVOLATILE) == 0;
+ r = compileexpr(fn, sub, discard);
+ if (discard) return NOREF;
+ return genload(fn, ex->ty, r, ex->qual & QVOLATILE);
+ case EADDROF:
+ return expraddr(fn, sub);
+ case EMUL:
+ op = Omul;
+ goto BinArith;
+ case EDIV:
+ op = isunsigned(ex->ty) ? Oudiv : Odiv;
+ goto BinArith;
+ case EREM:
+ op = issigned(ex->ty) ? Orem : Ourem;
+ goto BinArith;
+ case EBAND:
+ op = Oand;
+ goto BinArith;
+ case EXOR:
+ op = Oxor;
+ goto BinArith;
+ case EBIOR:
+ op = Oior;
+ goto BinArith;
+ case ESHL:
+ op = Oshl;
+ goto BinArith;
+ case ESHR:
+ op = issigned(ex->ty) ? Osar : Oslr;
+ goto BinArith;
+ case ESUB:
+ op = Osub;
+ goto BinArith;
+ case EADD:
+ op = Oadd;
+ BinArith:
+ l = compileexpr(fn, &sub[0], discard);
+ r = compileexpr(fn, &sub[1], discard);
+ if (discard) return NOREF;
+ if (op == Osub && isptrcvt(sub[0].ty) && isptrcvt(sub[1].ty)) {
+ /* ptr - ptr */
+ return genptrdiff(fn, typesize(typechild(sub[0].ty)), l, r);
+ } else if ((op != Oadd && op != Osub) || cls != KPTR) {
+ /* num OP num */
+ l = scalarcvt(fn, ex->ty, sub[0].ty, l);
+ r = scalarcvt(fn, ex->ty, sub[1].ty, r);
+ } else {
+ assert(isptrcvt(sub[0].ty));
+ /* ptr +/- num */
+ return genptroff(fn, op, typesize(typechild(sub[0].ty)), l, sub[1].ty, r);
+ }
+ return irbinop(fn, op, cls, l, r);
+ case EPOSTINC:
+ case EPOSTDEC:
+ op = ex->t == EPOSTINC ? Oadd : Osub;
+ if (ex->ty.t == TYPTR)
+ r = mkintcon(type2cls[targ_sizetype], typesize(typechild(ex->ty)));
+ else
+ r = isflt(ex->ty) ? mkfltcon(type2cls[ex->ty.t], 1.0) : mkref(RICON, 1);
+ bitsiz = 0;
+ if (sub[0].t == EGETF && (bitsiz = sub->fld.bitsiz)) {
+ union ref tmp;
+ adr = expraddr(fn, &sub[0].sub[0]);
+ l = genbitfload(fn, &tmp, sub[0].ty, &adr, &sub[0].fld, sub[0].qual & QVOLATILE);
+ q = irbinop(fn, op, cls, l, r);
+ genbitfstore(fn, sub[0].ty, adr, &sub[0].fld, tmp, q);
+ } else {
+ adr = expraddr(fn, sub);
+ l = genload(fn, sub->ty, adr, sub->qual & QVOLATILE);
+ q = irbinop(fn, op, cls, l, r);
+ genstore(fn, sub->ty, adr, q);
+ }
+ return discard ? NOREF : l;
+ case EPREINC:
+ case EPREDEC:
+ op = ex->t == EPREINC ? Oadd : Osub;
+ if (ex->ty.t == TYPTR)
+ r = mkintcon(type2cls[targ_sizetype], typesize(typechild(ex->ty)));
+ else
+ r = isflt(ex->ty) ? mkfltcon(type2cls[ex->ty.t], 1.0) : mkref(RICON, 1);
+ if (sub[0].t == EGETF && (bitsiz = sub->fld.bitsiz)) {
+ ty = ex->ty;
+ goto CompoundBitf;
+ }
+ adr = expraddr(fn, sub);
+ l = genload(fn, sub->ty, adr, sub->qual & QVOLATILE);
+ q = irbinop(fn, op, cls, l, r);
+ genstore(fn, sub->ty, adr, q);
+ if (discard) return NOREF;
+ return narrow(fn, cls, ex->ty, q, 0);
+ case EEQU:
+ op = Oequ;
+ goto Cmp;
+ case ENEQ:
+ op = Oneq;
+ goto Cmp;
+ case ELTH:
+ op = Olth;
+ goto Cmp;
+ case ELTE:
+ op = Olte;
+ goto Cmp;
+ case EGTH:
+ op = Ogth;
+ goto Cmp;
+ case EGTE:
+ op = Ogte;
+ Cmp:
+ ty = cvtarith(sub[0].ty, sub[1].ty);
+ if (!ty.t) ty.t = TYPTR;
+ if (isunsigned(ty) && in_range(op, Olth, Ogte))
+ op += Oulth - Olth;
+ l = compileexpr(fn, &sub[0], discard);
+ r = compileexpr(fn, &sub[1], discard);
+ if (discard) return NOREF;
+ l = scalarcvt(fn, ty, sub[0].ty, l);
+ r = scalarcvt(fn, ty, sub[1].ty, r);
+ cls = type2cls[ty.t];
+ return irbinop(fn, op, cls, l, r);
+ case ESET:
+ assert(isscalar(ex->ty));
+ q = scalarcvt(fn, sub[0].ty, sub[1].ty, exprvalue(fn, &sub[1]));
+ if (sub[0].t == EGETF && (bitsiz = sub[0].fld.bitsiz)) {
+ /* bit-field */
+ adr = expraddr(fn, &sub[0].sub[0]);
+ genbitfstore(fn, ex->ty, adr, &sub[0].fld, NOREF, q);
+ } else {
+ bitsiz = 0;
+ adr = expraddr(fn, &sub[0]);
+ genstore(fn, ex->ty, adr, q);
+ }
+ if (discard) return NOREF;
+ return bitsiz ? narrow(fn, cls, sub[0].ty, q, bitsiz) : q;
+ case ESETMUL:
+ op = Omul;
+ goto Compound;
+ case ESETDIV:
+ op = isunsigned(ex->ty) ? Oudiv : Odiv;
+ goto Compound;
+ case ESETREM:
+ op = issigned(ex->ty) ? Orem : Ourem;
+ goto Compound;
+ case ESETAND:
+ op = Oand;
+ goto Compound;
+ case ESETXOR:
+ op = Oxor;
+ goto Compound;
+ case ESETIOR:
+ op = Oior;
+ goto Compound;
+ case ESETSHL:
+ op = Oshl;
+ goto Compound;
+ case ESETSHR:
+ op = issigned(ex->ty) ? Osar : Oslr;
+ goto Compound;
+ case ESETSUB:
+ op = Osub;
+ goto Compound;
+ case ESETADD:
+ op = Oadd;
+ Compound:
+ ty = in_range(ex->t, ESETSHL, ESETSHR) ? mktype(intpromote(ex->ty.t))
+ : cvtarith(sub[0].ty, sub[1].ty);
+ r = exprvalue(fn, &sub[1]);
+ if (sub[0].t == EGETF && (bitsiz = sub[0].fld.bitsiz)) {
+ /* bit-field */
+ union ref tmp;
+ CompoundBitf:
+ adr = expraddr(fn, &sub[0].sub[0]);
+ l = genbitfload(fn, &tmp, sub[0].ty, &adr, &sub[0].fld, sub[0].qual & QVOLATILE);
+ q = irbinop(fn, op, cls, l, r);
+ genbitfstore(fn, sub[0].ty, adr, &sub[0].fld, tmp, q);
+ } else {
+ bitsiz = 0;
+ adr = expraddr(fn, &sub[0]);
+ l = genload(fn, ex->ty, adr, ex->qual & QVOLATILE);
+ if ((op != Oadd && op != Osub) || cls != KPTR) {
+ l = scalarcvt(fn, ty, sub[0].ty, l);
+ r = scalarcvt(fn, ty, sub[1].ty, r);
+ q = irbinop(fn, op, type2cls[ty.t], l, r);
+ q = scalarcvt(fn, ex->ty, ty, q);
+ } else {
+ q = genptroff(fn, op, typesize(typechild(ex->ty)), l, sub[1].ty, r);
+ }
+ genstore(fn, ex->ty, adr, q);
+ }
+ if (discard) return NOREF;
+ return bitsiz ? narrow(fn, cls, ex->ty, q, bitsiz) : q;
+ case ECALL:
+ r = compilecall(fn, ex);
+ if (isint(ex->ty))
+ return narrow(fn, cls, ex->ty, r, 0);
+ return r;
+ case ECOND:
+ for (bool c; knowntruthy(&c, &ex->sub[0]);) {
+ r = compileexpr(fn, &ex->sub[2-c], discard);
+ if (discard) return NOREF;
+ return scalarcvt(fn, ex->ty, ex->sub[2-c].ty, r);
+ }
+
+ if (ex->ty.t == TYVOID || discard) {
+ struct block *tr, *fl, *end;
+ condjump(fn, &sub[0], tr = newblk(fn), fl = newblk(fn));
+ useblk(fn, tr);
+ expreffects(fn, &sub[1]);
+ end = newblk(fn);
+ if (fn->curblk)
+ putbranch(fn, end);
+ useblk(fn, fl);
+ expreffects(fn, &sub[2]);
+ if (fn->curblk)
+ putbranch(fn, end);
+ useblk(fn, end);
+ return NOREF;
+ }
+ return condexprvalue(fn, ex, discard);
+ case ELOGAND:
+ case ELOGIOR:
+ for (bool c; knowntruthy(&c, &ex->sub[0]);) {
+ c ^= ex->t == ELOGIOR;
+ r = compileexpr(fn, &ex->sub[c], discard);
+ if (discard) return NOREF;
+ return scalarcvt(fn, mktype(TYBOOL), ex->sub[c].ty, r);
+ }
+ return condexprvalue(fn, ex, discard);
+ case ESEQ:
+ expreffects(fn, &sub[0]);
+ return compileexpr(fn, &sub[1], discard);
+ default: assert(!"nyi expr");
+ }
+}
+
+/************************************/
+/* Statements parsing & compilation */
+/************************************/
+
+static void
+stmtterm(struct comp *cm)
+{
+ expect(cm, ';', "to terminate previous statement");
+}
+
+static void block(struct comp *cm, struct function *fn);
+static bool stmt(struct comp *cm, struct function *fn);
+static void localdecl(struct comp *cm, struct function *fn, bool forinit);
+
+struct label {
+ struct label *link;
+ internstr name;
+ struct block *blk;
+ struct span usespan;
+ /* if usespan.ex.len == 0, this label is resolved and blk is the block that
+ * the label starts, otherwise the label is unresolved and blk is the head
+ * of a linked list of relocations, the next list entry is in blk->s1, etc,
+ * terminated by NULL */
+};
+
+static struct label *
+findlabel(struct comp *cm, internstr name)
+{
+ for (struct label *l = cm->labels; l; l = l->link)
+ if (l->name == name) return l;
+ return NULL;
+}
+
+static void
+deflabel(struct comp *cm, struct function *fn, const struct span *span, internstr name)
+{
+ struct label *label = findlabel(cm, name);
+ if (label && label->usespan.ex.len == 0) {
+ error(span, "redefinition of label '%s'", name);
+ } else if (label) {
+ struct block *new = NULL;
+ if (!nerror) {
+ new = newblk(fn);
+ if (fn->curblk) putbranch(fn, new);
+ }
+ /* fix up relocations */
+ for (struct block *list = label->blk, *next; list; list = next) {
+ next = list->s1;
+ if (!nerror) {
+ useblk(fn, list);
+ putbranch(fn, new);
+ }
+ }
+ label->usespan = (struct span){0};
+ label->blk = new;
+ if (!nerror) useblk(fn, new);
+ } else {
+ struct label l = { cm->labels, name };
+ if (!nerror) {
+ struct block *new = newblk(fn);
+ if (fn->curblk) putbranch(fn, new);
+ useblk(fn, new);
+ }
+ l.blk = fn->curblk;
+ cm->labels = alloccopy(fn->arena, &l, sizeof l, 0);
+ }
+}
+
+static bool
+loopbody(struct comp *cm, struct function *fn, struct block *brk, struct block *cont)
+{
+ struct block *save[2];
+ bool terminates = 0;
+
+ save[0] = cm->breakto, save[1] = cm->loopcont;
+ cm->breakto = brk, cm->loopcont = cont;
+ ++cm->loopdepth;
+
+ terminates = stmt(cm, fn);
+
+ --cm->loopdepth;
+ cm->breakto = save[0], cm->loopcont = save[1];
+
+ return terminates;
+}
+
+#define EMITS if (doemit && !nerror)
+
+struct swcase {
+ vlong val;
+ struct block *blk;
+ struct span span;
+};
+struct switchstmt {
+ struct block *bdefault;
+ union type condtype;
+ vec_of(struct swcase) cases;
+};
+
+static int
+cmpswcase(const void *aa, const void *bb)
+{
+ const struct swcase *a = aa, *b = bb;
+ vlong v1 = a->val, v2 = b->val;
+ if (v1 != v2) return v1 < v2 ? -1 : 1;
+ return (a > b) - (a < b); /* preserve original order */
+}
+
+static void
+swsortcases(struct swcase *cs, uint n)
+{
+ void qsort(void *, size_t n, size_t size, int (*)(const void *, const void *));
+ qsort(cs, n, sizeof *cs, cmpswcase);
+}
+
+static bool
+genswitch(struct comp *cm, struct function *fn, const struct expr *ex)
+{
+ union ref sel;
+ bool doemit = fn->curblk;
+ struct block *begin = NULL, *end = NULL, *breaksave = cm->breakto;
+ struct switchstmt *stsave = cm->switchstmt, st = {.condtype = ex->ty};
+ enum irclass k = type2cls[scalartypet(ex->ty)];
+ struct swcase casebuf[8];
+ vinit(&st.cases, casebuf, countof(casebuf));
+
+ assert(k);
+ end = newblk(fn);
+ EMITS {
+ sel = exprvalue(fn, ex);
+ assert(isint(ex->ty));
+ }
+ cm->switchstmt = &st;
+ cm->breakto = end;
+ begin = fn->curblk;
+ fn->curblk = NULL;
+ ++cm->switchdepth;
+ stmt(cm, fn);
+ --cm->switchdepth;
+ doemit = fn->curblk;
+ cm->switchstmt = stsave;
+ cm->breakto = breaksave;
+
+ EMITS putbranch(fn, end);
+ useblk(fn, begin);
+ swsortcases(st.cases.p, st.cases.n);
+ doemit = 1;
+ if (!st.bdefault) st.bdefault = end;
+ /* TODO: optimize instead of generating the equivalent of if == .. else if .. chain
+ * XX 1. sort by case values (also for easy duplicates checking)
+ * 2. contiguous ranges (case a..b: -> x >= && x <= b)
+ * 3. binary search
+ * 4. jump tables? (harder, backend refactoring)
+ */
+ vlong prev;
+ for (int i = 0; i < st.cases.n; ++i) {
+ const struct swcase *c = &st.cases.p[i];
+ if (i > 0) {
+ assert(c->val >= prev);
+ if (c->val == prev) {
+ error(&c->span, "duplicate case value");
+ note(&c[-1].span, "previously defined here");
+ }
+ }
+ EMITS {
+ struct block *next = i < st.cases.n - 1 ? newblk(fn) : st.bdefault;
+ putcondbranch(fn, irbinop(fn, Oequ, k, sel, mkintcon(k, c->val)), c->blk, next);
+ if (next != st.bdefault) useblk(fn, next);
+ }
+ prev = c->val;
+ }
+ vfree(&st.cases);
+ if (fn->curblk != end) {
+ if (fn->curblk) EMITS putbranch(fn, end);
+ if (end->npred > 0) {
+ useblk(fn, end);
+ } else {
+ fn->curblk = NULL;
+ freeblk(fn, end);
+ }
+ }
+
+ return fn->curblk == NULL;
+}
+
+static bool /* return 1 if stmt is terminating (ends with a jump) */
+stmt(struct comp *cm, struct function *fn)
+{
+ struct block *tr, *fl, *end, *begin;
+ union {
+ struct arena a;
+ char mem[sizeof(struct arena) + sizeof(struct expr)*4];
+ } atmp = { .a.cap = sizeof(struct expr)*4 };
+ struct arena *atmpp;
+ struct expr ex;
+ struct env e;
+ union ref r;
+ struct token tk;
+ bool terminates = 0;
+ bool doemit = fn->curblk;
+
+ while (match(cm, &tk, TKIDENT) || match(cm, &tk, TKWcase) || match(cm, &tk, TKWdefault)) {
+ if (tk.t == TKWcase) {
+ /* case <expr> ':' */
+ if (!cm->switchstmt) error(&tk.span, "'case' outside of switch statement");
+ ex = constantexpr(cm);
+ if (!eval(&ex, EVINTCONST))
+ error(&ex.span, "not an integer constant expression");
+ else if (cm->switchstmt && ex.ty.bits != cm->switchstmt->condtype.bits) {
+ struct expr tmp = ex;
+ ex = mkexpr(ECAST, ex.span, cm->switchstmt->condtype, .sub = &tmp);
+ bool ok = eval(&ex, EVINTCONST);
+ assert(ok && "cast const int?");
+ if (ex.i != tmp.i)
+ warn(&ex.span, "overflow converting case value to switch condition type");
+ }
+ expect(cm, ':', NULL);
+ if (!fn->curblk || (fn->curblk->phi.n > 0 || fn->curblk->ins.n > 0)) {
+ begin = newblk(fn);
+ EMITS putbranch(fn, begin);
+ useblk(fn, begin);
+ }
+ if (cm->switchstmt)
+ vpush(&cm->switchstmt->cases, ((struct swcase) {ex.i, fn->curblk, ex.span}));
+ } else if (tk.t == TKWdefault) {
+ /* default ':' */
+ if (!cm->switchstmt) error(&tk.span, "'default' outside of switch statement");
+ expect(cm, ':', NULL);
+ if (!fn->curblk || (fn->curblk->phi.n > 0 || fn->curblk->ins.n > 0)) {
+ begin = newblk(fn);
+ EMITS putbranch(fn, begin);
+ useblk(fn, begin);
+ }
+ if (cm->switchstmt) {
+ if (cm->switchstmt->bdefault) error(&tk.span, "multiple 'default' labels in one switch");
+ cm->switchstmt->bdefault = fn->curblk;
+ }
+ } else if (tk.t == TKIDENT && match(cm, NULL, ':')) {
+ /* <label> ':' */
+ deflabel(cm, fn, &tk.span, tk.name);
+ } else {
+ assert(tk.t == TKIDENT);
+ /* kludge for no backtracking and no lookahead */
+ ex = exprparse(cm, 1, &tk, EFROMSTMT);
+ stmtterm(cm);
+ EMITS expreffects(fn, &ex);
+ return fn->curblk == NULL;
+ }
+ doemit = 1;
+ }
+
+ switch (peek(cm, NULL)) {
+ case '{':
+ lex(cm, NULL);
+ envdown(cm, &e);
+ block(cm, fn);
+ envup(cm);
+ break;
+ case ';':
+ lex(cm, NULL);
+ break;
+ case TKWif:
+ lex(cm, NULL);
+ expect(cm, '(', NULL);
+ ex = commaexpr(cm);
+ expect(cm, ')', NULL);
+ if (!isscalar(ex.ty))
+ error(&ex.span, "'if' condition is not a scalar '%ty'", ex.ty);
+ tr = fl = end = NULL;
+ EMITS {
+ tr = newblk(fn);
+ fl = newblk(fn);
+ condjump(fn, &ex, tr, fl);
+ useblk(fn, tr);
+ }
+ terminates = stmt(cm, fn);
+ if (!match(cm, NULL, TKWelse)) {
+ end = fl;
+ EMITS if (!terminates) putbranch(fn, end);
+ terminates = 0;
+ } else {
+ EMITS {
+ end = newblk(fn);
+ if (!terminates) putbranch(fn, end);
+ useblk(fn, fl);
+ }
+ terminates &= stmt(cm, fn);
+ EMITS {
+ if (fn->curblk) putbranch(fn, end);
+ }
+ }
+ EMITS if (!terminates) useblk(fn, end);
+ break;
+ case TKWelse:
+ lex(cm, &tk);
+ error(&tk.span, "'else' without matching 'if'");
+ break;
+ case TKWwhile: /* while ( <cond> ) <body> */
+ lex(cm, NULL);
+ expect(cm, '(', NULL);
+ ex = commaexpr(cm);
+ expect(cm, ')', NULL);
+ if (!isscalar(ex.ty))
+ error(&ex.span, "'while' condition is not a scalar '%ty'", ex.ty);
+ tr = begin = end = NULL;
+ /* @begin:
+ * <cond>
+ * b <cond>, @tr, @end
+ * @tr:
+ * <body>
+ * b @begin
+ * @end:
+ * <-
+ */
+ doemit = 1;
+ EMITS {
+ begin = newblk(fn);
+ if (fn->curblk)
+ putbranch(fn, begin);
+ useblk(fn, begin);
+ condjump(fn, &ex, tr = newblk(fn), end = newblk(fn));
+ useblk(fn, tr);
+ }
+ terminates = loopbody(cm, fn, end, begin);
+ EMITS {
+ if (fn->curblk) putbranch(fn, begin);
+ useblk(fn, end);
+ }
+ break;
+ case TKWdo: /* do <body> while ( <cond> ) ; */
+ lex(cm, NULL);
+ begin = tr = end = NULL;
+ /* @begin:
+ * <body>
+ * b @tr
+ * @tr: <- necessary for continue stmt
+ * <cond>
+ * b <cond>, @begin, @end
+ * @end:
+ * <-
+ */
+ doemit = 1;
+ EMITS {
+ begin = newblk(fn);
+ if (fn->curblk)
+ putbranch(fn, begin);
+ useblk(fn, begin);
+ tr = newblk(fn);
+ end = newblk(fn);
+ }
+ terminates = loopbody(cm, fn, end, tr);
+ expect(cm, TKWwhile, NULL);
+ expect(cm, '(', NULL);
+ ex = commaexpr(cm);
+ expect(cm, ')', NULL);
+ if (!isscalar(ex.ty))
+ error(&ex.span, "'while' condition is not a scalar '%ty'", ex.ty);
+ stmtterm(cm);
+ EMITS {
+ if (!terminates) putbranch(fn, tr);
+ useblk(fn, tr);
+ condjump(fn, &ex, begin, end);
+ useblk(fn, end);
+ }
+ break;
+ case TKWfor: /* for ( <init>? ; <cond>? ; <iter>? ) <body> */
+ lex(cm, NULL);
+ begin = tr = end = fl = NULL;
+ expect(cm, '(', NULL);
+ /* ->
+ * <init>
+ * b @begin
+ * @begin:
+ * <cond>
+ * b <cond>, @tr, @fl
+ * @tr:
+ * <body>
+ * b @end
+ * @end: <- necessary for continue stmt
+ * <iter>
+ * b @begin
+ * @fl:
+ * <-
+ *
+ * if cond omitted, tr = begin
+ * if iter omitted, end = begin
+ */
+ envdown(cm, &e);
+ if (!match(cm, NULL, ';')) { /* init */
+ if (isdecltok(cm)) {
+ localdecl(cm, fn, 1);
+ } else {
+ ex = commaexpr(cm);
+ EMITS expreffects(fn, &ex);
+ expect(cm, ';', NULL);
+ }
+ }
+ doemit = 1;
+ EMITS {
+ end = tr = begin = newblk(fn);
+ if (fn->curblk)
+ putbranch(fn, begin);
+ useblk(fn, begin);
+ fl = newblk(fn);
+ }
+ if (!match(cm, NULL, ';')) { /* cond */
+ ex = commaexpr(cm);
+ expect(cm, ';', NULL);
+ if (!isscalar(ex.ty))
+ error(&ex.span, "'for' condition is not a scalar type ('%ty')", ex.ty);
+ EMITS {
+ tr = newblk(fn);
+ condjump(fn, &ex, tr, fl);
+ useblk(fn, tr);
+ }
+ }
+ atmpp = NULL;
+ if (!match(cm, NULL, ')')) { /* iter */
+ /* since exarena is free'd at the end of each stmt, create a new temporary
+ * arena to parse this expression because loop body statements would free it
+ * otherwise */
+ struct arena *tmp = cm->exarena;
+ cm->exarena = &atmp.a;
+ ex = commaexpr(cm);
+ atmpp = cm->exarena;
+ cm->exarena = tmp;
+
+ end = newblk(fn);
+ expect(cm, ')', NULL);
+ }
+
+ terminates = loopbody(cm, fn, fl, end);
+
+ EMITS {
+ if (end != begin) { /* have iter */
+ if (!terminates) putbranch(fn, end);
+ useblk(fn, end);
+ expreffects(fn, &ex);
+ putbranch(fn, begin);
+ } else if (!terminates) putbranch(fn, begin);
+ if (fl->npred > 0) {
+ useblk(fn, fl);
+ } else {
+ freeblk(fn, fl);
+ terminates = 1;
+ }
+ }
+ if (atmpp && atmpp != cm->exarena) freearena(&atmpp);
+ envup(cm);
+ break;
+ case TKWswitch:
+ lex(cm, NULL);
+ expect(cm, '(', NULL);
+ ex = commaexpr(cm);
+ expect(cm, ')', NULL);
+ if (!isint(ex.ty))
+ error(&ex.span, "'switch' value is not an integer: '%ty'", ex.ty);
+ terminates = genswitch(cm, fn, &ex);
+ break;
+ case TKWbreak:
+ lex(cm, &tk);
+ if (!cm->loopdepth && !cm->switchdepth)
+ error(&tk.span, "'break' outside of loop or switch statement");
+ EMITS putbranch(fn, cm->breakto);
+ stmtterm(cm);
+ break;
+ case TKWcontinue:
+ lex(cm, &tk);
+ if (!cm->loopdepth)
+ error(&tk.span, "'continue' outside of loop");
+ EMITS putbranch(fn, cm->loopcont);
+ stmtterm(cm);
+ break;
+ case TKWgoto:
+ lex(cm, &tk);
+ peek(cm, &tk);
+ if (expect(cm, TKIDENT, NULL)) {
+ struct label *label = findlabel(cm, tk.name);
+ if (!label) {
+ /* create reloc list */
+ struct label l = { cm->labels, tk.name, fn->curblk, tk.span };
+ assert(l.usespan.ex.len);
+ cm->labels = alloccopy(fn->arena, &l, sizeof l, 0);
+ fn->curblk = NULL;
+ } else if (label && label->usespan.ex.len != 0) {
+ /* append to relocs list */
+ struct block *next = label->blk;
+ label->blk = fn->curblk;
+ EMITS {
+ fn->curblk->s1 = next;
+ fn->curblk = NULL;
+ }
+ } else {
+ EMITS {
+ assert(label->blk);
+ putbranch(fn, label->blk);
+ }
+ }
+ }
+ stmtterm(cm);
+ break;
+ case TKWreturn:
+ lex(cm, &tk);
+ if (isexprtok(cm)) {
+ ex = commaexpr(cm);
+ if (fn->retty.t == TYVOID) {
+ if (ex.ty.t != TYVOID) error(&ex.span, "void function should not return a value");
+ else if (ccopt.pedant) warn(&ex.span, "returning void expression is an extension");
+ } else if (!assigncheck(fn->retty, &ex)) {
+ error(&ex.span,
+ "cannot return '%ty' value from function with return type '%ty'",
+ ex.ty, fn->retty);
+ }
+ EMITS {
+ if (isscalar(fn->retty))
+ r = scalarcvt(fn, fn->retty, ex.ty, exprvalue(fn, &ex));
+ else if (fn->retty.t == TYVOID)
+ r = (expreffects(fn, &ex), NOREF);
+ else
+ r = structreturn(fn, &ex);
+ putreturn(fn, r, NOREF);
+ }
+ } else {
+ if (fn->retty.t != TYVOID)
+ error(&tk.span, "non-void function should return a value");
+ EMITS putreturn(fn, NOREF, NOREF);
+ }
+ stmtterm(cm);
+ break;
+ default:
+ ex = exprparse(cm, 1, NULL, EFROMSTMT);
+ stmtterm(cm);
+ EMITS expreffects(fn, &ex);
+ break;
+ }
+ freearena(&cm->exarena);
+ lexerfreetemps(cm->lx);
+ return fn->curblk == NULL;
+}
+
+/* parse and compile a function-local declaration */
+static void
+localdecl(struct comp *cm, struct function *fn, bool forini)
+{
+ struct expr ini;
+ struct token tk;
+ const bool doemit = fn->curblk;
+ struct declstate st = { DFUNCVAR };
+
+ if (!forini && match(cm, &tk, TKIDENT)) {
+ if (match(cm, NULL, ':')) {
+ /* <label> ':' */
+ deflabel(cm, fn, &tk.span, tk.name);
+ stmt(cm, fn);
+ return;
+ }
+ /* finddecl() -> non null because localdecl() is called when isdecltok() */
+ st.base = finddecl(cm, tk.name)->ty;
+ st.base0 = 1;
+ }
+ do {
+ struct decl decl = pdecl(&st, cm);
+ if (decl.name) {
+ static int staticid;
+ bool put = 0;
+ bool dynarr = 0;
+
+ switch (decl.scls) {
+ case SCSTATIC:
+ if (forini)
+ error(&decl.span, "static declaration in 'for' loop initializer");
+ if (!decl.sym)
+ decl.sym = mkhiddensym(&fn->name->c, &decl.name->c, ++staticid);
+ goto Initz;
+ case SCNONE:
+ if (decl.ty.t == TYFUNC) {
+ decl.scls = SCEXTERN;
+ if (!decl.sym) decl.sym = decl.name;
+ break;
+ }
+ decl.scls = SCAUTO;
+ /* fallthru */
+ case SCAUTO:
+ case SCREGISTER:
+ if (decl.ty.t == TYFUNC) {
+ error(&decl.span, "declaring variable '%s' with function type '%ty'", decl.name, decl.ty);
+ goto Err;
+ } else if (isincomplete(decl.ty) && !(dynarr = (decl.ty.t == TYARRAY && st.varini))) {
+ error(&decl.span, "declaring variable '%s' with incomplete type '%ty'", decl.name, decl.ty);
+ goto Err;
+ }
+ decl.id = -1;
+ if (!nerror) {
+ struct instr alloc = mkalloca(typesize(decl.ty), typealign(decl.ty));
+ if (fn->curblk) decl.id = addinstr(fn, alloc).i;
+ else decl.id = insertinstr(fn->entry, fn->entry->ins.n, alloc).i;
+ }
+ Initz:
+ if (st.varini) {
+ int d = putdecl(cm, &decl);
+ union type ty = decl.ty;
+ bool statik = decl.scls & (SCSTATIC | SCEXTERN);
+ ini = initializer(cm, &ty, statik ? EVSTATICINI : EVFOLD,
+ /* globl? */ decl.scls == SCEXTERN, decl.qual, statik ? decl.sym : NULL);
+ declsbuf.p[d].ty = ty;
+ put = 1;
+ pdecl(&st, cm);
+ if (!statik) {
+ /* fix alloca for actual size, for implicitly sized arrays */
+ assert(!isincomplete(ty));
+ EMITS instrtab[decl.id] = mkalloca(typesize(ty), typealign(ty));
+
+ if (!initcheck(ty, &ini)) {
+ struct span span = decl.span;
+ joinspan(&span.ex, ini.span.ex);
+ error(&span, "cannot initialize '%ty' variable with '%ty'",
+ ty, ini.ty);
+ }
+ EMITS {
+ if (ini.t == EINIT || (ty.t == TYARRAY && ini.t == ESTRLIT))
+ geninit(fn, ty, mkref(RTMP, decl.id), &ini);
+ else if (isagg(ty))
+ structcopy(fn, ty, mkref(RTMP, decl.id), exprvalue(fn, &ini));
+ else {
+ genstore(fn, ty, mkref(RTMP, decl.id),
+ scalarcvt(fn, ty, ini.ty, exprvalue(fn, &ini)));
+ }
+ }
+ } else if (decl.scls == SCEXTERN) {
+ struct span span = decl.span;
+ joinspan(&span.ex, ini.span.ex);
+ error(&span, "block local 'extern' variable cannot have an initializer");
+ }
+ } else if (decl.scls == SCSTATIC) {
+ /* zero-initialized static */
+ if (decl.ty.t == TYARRAY && isincomplete(decl.ty))
+ error(&decl.span, "definition of variable with array type needs size or initializer");
+ else if (isincomplete(decl.ty))
+ error(&decl.span, "definition of static variable with incomplete type");
+ else
+ objnewdat(decl.sym, Sbss, 0, typesize(decl.ty), typealign(decl.ty));
+ }
+ break;
+ case SCTYPEDEF:
+ if (forini)
+ error(&decl.span, "typedef in 'for' loop initializer");
+ break;
+ case SCEXTERN:
+ if (!decl.sym) decl.sym = decl.name;
+ if (forini)
+ error(&decl.span, "extern declaration in 'for' loop initializer");
+ if (st.varini) goto Initz;
+ break;
+ default: assert(0);
+ }
+ if (st.funcdef) {
+ struct span span = decl.span;
+ joinspan(&span.ex, (peek(cm, &tk), tk.span.ex));
+ error(&span, "function definition not allowed here");
+ int bal = 1;
+ do switch (lex(cm, NULL)) {
+ case TKEOF: break;
+ case '{': ++bal; break;
+ case '}': --bal; break;
+ } while (bal);
+ }
+ Err:
+ if (!put) putdecl(cm, &decl);
+ } else if (forini) {
+ error(&decl.span, "non-variable declaration in 'for' loop initializer");
+ }
+ } while (st.more);
+}
+
+static void
+block(struct comp *cm, struct function *fn)
+{
+ struct token tk;
+
+ while (!match(cm, &tk, '}')) {
+ if (isdecltok(cm))
+ localdecl(cm, fn, 0);
+ else
+ stmt(cm, fn);
+ }
+ cm->fnblkspan = tk.span;
+}
+
+static void
+function(struct comp *cm, struct function *fn, internstr *pnames, const struct span *pspans, uchar *pquals)
+{
+ const struct typedata *td = &typedata[fn->fnty.dat];
+ const bool doemit = fn->curblk;
+ struct env e;
+ struct token tk;
+ envdown(cm, &e);
+
+ /* emit Oparam instructions */
+ EMITS {
+ for (int i = 0; i < td->nmemb; ++i) {
+ union irtype pty = mkirtype(td->param[i]);
+ union ref r = addinstr(fn, mkinstr(Oparam, pty.isagg ? KPTR : pty.cls,
+ mkref(RICON, i), mktyperef(pty)));
+ assert(r.t == RTMP && r.i == i);
+ }
+ }
+ /* add parameters to symbol table and create prologue (arguments) block */
+ for (int i = 0; i < td->nmemb; ++i) {
+ if (pnames[i]) {
+ struct decl arg = { .ty = td->param[i], .qual = pquals ? pquals[i] : 0,
+ .name = pnames[i], .scls = SCAUTO, .span = pspans[i] };
+ EMITS {
+ if (isscalar(arg.ty)) {
+ arg.id = addinstr(fn, mkalloca(typesize(arg.ty), typealign(arg.ty))).i;
+ genstore(fn, arg.ty, mkref(RTMP, arg.id), mkref(RTMP, i));
+ } else {
+ arg.id = addinstr(fn, mkinstr(Ocopy, KPTR, mkref(RTMP, i))).i;
+ }
+ }
+ putdecl(cm, &arg);
+ } else if (ccopt.cstd < STDC23) {
+ warn(&pspans[i], "missing name of parameter #%d", i+1);
+ }
+ }
+
+ /* put __func__, though its data is generated lazily the first time it is encountered */
+ putdecl(cm, &(struct decl) {
+ .ty = mkarrtype(mktype(TYCHAR), QCONST, strlen(&fn->name->c) + 1), .qual = QCONST,
+ .name = istr__func__, .scls = SCSTATIC, .span = (peek(cm, &tk), tk.span),
+ .isbuiltin = 1, .sym = fn->name,
+ });
+
+ /* end prologue */
+ EMITS {
+ struct block *blk;
+ putbranch(fn, blk = newblk(fn));
+ useblk(fn, blk);
+ }
+ cm->labels = NULL;
+ block(cm, fn);
+ envup(cm);
+ for (struct label *l = cm->labels; l; l = l->link) {
+ if (l->usespan.ex.len) {
+ error(&l->usespan, "label '%s' used but never defined", l->name);
+ }
+ }
+ if (fn->curblk) {
+ if (fn->retty.t == TYINT && fn->name == istr_main) {
+ /* implicit return 0 for main function (ISO C standard behavior) */
+ putreturn(fn, ZEROREF, NOREF);
+ } else {
+ if (fn->retty.t != TYVOID && !nerror) {
+ /* it may not actually be reachable after constant-folding
+ * peephole optimizations (from code like assert(0 && "x")) */
+ if (blkreachable(fn, fn->curblk)) {
+ warn(&cm->fnblkspan, "non-void function '%s' may not return a value", fn->name);
+ }
+ }
+ putreturn(fn, NOREF, NOREF);
+ }
+ }
+}
+
+/* top-level declaration */
+static void
+tldecl(struct comp *cm)
+{
+ struct declstate st = { DTOPLEVEL };
+ do {
+ bool noscls = 0;
+ int nerr = nerror;
+ struct decl decl = pdecl(&st, cm);
+
+ if (nerror != nerr && st.varini) {
+ (void)expr(cm);
+ pdecl(&st, cm);
+ continue;
+ }
+ if (st.empty) break;
+ if (!decl.scls) {
+ noscls = 1;
+ decl.scls = SCEXTERN;
+ }
+ if (!decl.sym) decl.sym = decl.name;
+ decl.isdef = st.varini;
+ if (st.funcdef) {
+ const struct typedata *td = &typedata[decl.ty.dat];
+ if (td->ret.t != TYVOID && isincomplete(td->ret))
+ error(&decl.span, "function definition with incomplete return type '%ty'", td->ret);
+ for (int i = 0; i < td->nmemb; ++i) {
+ if (td->param[i].t != TYVOID && isincomplete(td->param[i]))
+ error(&st.pspans[i], "parameter has incomplete type '%ty'", td->param[i]);
+ }
+ decl.isdef = 1;
+ int idecl = putdecl(cm, &decl);
+ struct decl *d = &declsbuf.p[idecl];
+ if (d->inlin && decl.scls != SCSTATIC) fatal(&d->span, "non-static inline is unimplemented");
+ struct function fn = { &cm->fnarena, .name = d->sym, .globl = d->scls != SCSTATIC, .fnty = decl.ty, .retty = td->ret, .inlin = d->inlin };
+ irinit(&fn);
+ function(cm, &fn, st.pnames, st.pspans, st.pqual);
+ if (!nerror && ccopt.dbg.p)
+ irdump(&fn);
+ irfini(&fn);
+ } else if (decl.name) {
+ int idecl = putdecl(cm, &decl);
+ struct decl *d = &declsbuf.p[idecl];
+ if (st.varini) {
+ if (isagg(decl.ty) && isincomplete(decl.ty))
+ error(&decl.span, "initialization of variable with incomplete type '%ty'", decl.ty);
+ struct expr ini = initializer(cm, &decl.ty, EVSTATICINI, d->scls != SCSTATIC, d->qual, d->sym);
+ d = &declsbuf.p[idecl];
+ d->ty = decl.ty;
+ if (d->scls == SCEXTERN && !noscls) {
+ struct span span = decl.span;
+ joinspan(&span.ex, ini.span.ex);
+ warn(&span, "'extern' variable has initializer");
+ }
+ pdecl(&st, cm);
+ } else if (d->ty.t != TYFUNC && d->scls != SCTYPEDEF && (d->scls != SCEXTERN || noscls)) {
+ /* tentative definitions */
+ if (!objhassym(d->sym, NULL)) {
+ uint size = typesize(d->ty);
+ if (isincomplete(d->ty)) {
+ if (d->ty.t == TYARRAY) {
+ warn(&d->span, "tentative array definition assumed to have one element");
+ size = typesize(typechild(d->ty));
+ assert(size != 0);
+ } else if (isagg(d->ty)) {
+ warn(&d->span, "tentative definition with incomplete type '%ty'", d->ty);
+ assert(size == 0);
+ } else assert(0);
+ }
+ if (size) objnewdat(d->sym, Sbss, d->scls == SCEXTERN, size, typealign(d->ty));
+ }
+ }
+ if (ccopt.dbg.p) bfmt(ccopt.dbgout, "var %s : %tq\n", d->name, d->ty, d->qual);
+ } else {
+ if (ccopt.dbg.p && decl.ty.t) bfmt(ccopt.dbgout, "type %ty\n", decl.ty);
+ }
+ freearena(&cm->fnarena);
+ freearena(&cm->exarena);
+ lexerfreetemps(cm->lx);
+ } while (st.more);
+}
+
+union type cvalistty;
+void
+docomp(struct comp *cm)
+{
+ static struct env toplevel;
+ struct token tk[1];
+
+ istr__func__ = intern("__func__");
+ istr_main = intern("main");
+ istr_memset = intern("memset");
+ if (!cm->env) {
+ vinit(&declsbuf, NULL, 1<<10);
+ pmap_init(&tldeclmap, 1<<8);
+ cm->env = &toplevel;
+ }
+ if (!cvalistty.t) {
+ struct typedata td = {
+ .t = TYSTRUCT, .siz = targ_valistsize, .align = targ_primalign[TYPTR], .nmemb = 1,
+ .fld = &(struct namedfield){intern("-"), {mkarrtype(mktype(TYPTR), 0, 3)}}
+ };
+ cvalistty = mkarrtype(mktagtype(intern("__builtin_va_list"), &td), 0, 1);
+ }
+ peek(cm, tk);
+ envadddecl(cm->env, &(struct decl) { cvalistty, SCTYPEDEF, .span = tk->span, .name = intern("__builtin_va_list") });
+ putbuiltins(cm->env);
+
+ while (peek(cm, tk) != TKEOF) {
+ if (tk->t == ';') {
+ lex(cm, tk);
+ } else if (!isdecltok(cm) && tk->t != TKIDENT) {
+ error(&tk->span, "expected declaration");
+ do lex(cm, tk); while (tk->t != TKEOF && !isdecltok(cm));
+ } else {
+ tldecl(cm);
+ }
+ }
+}
+
+static void
+initcm(struct comp *cm, const char *file)
+{
+ enum { N = 1<<12 };
+ static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem[2];
+ const char *err;
+ switch (initlexer(cm->lx, &err, file)) {
+ default: assert(0);
+ case LXERR:
+ fatal(NULL, "Cannot open %'s: %s", file, err);
+ case LXOK:
+ cm->fnarena = (void *)amem[0].m;
+ cm->fnarena->cap = N;
+ cm->exarena = (void *)amem[1].m;
+ cm->exarena->cap = N;
+ }
+}
+
+void
+ccomp(const char *file)
+{
+ struct comp cm = {&(struct lexer){0}};
+ initcm(&cm, file);
+ docomp(&cm);
+}
+
+void
+cpp(struct wbuf *out, const char *file)
+{
+ struct comp cm = {&(struct lexer){0}};
+ initcm(&cm, file);
+ lexerdump(cm.lx, out);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c.h b/src/c.h
new file mode 100644
index 0000000..0214db5
--- /dev/null
+++ b/src/c.h
@@ -0,0 +1,139 @@
+#include "../common.h"
+#include "../type.h"
+
+/*************/
+/* EXPR TREE */
+/*************/
+
+enum exprkind {
+ EXXX, ENUMLIT, ESTRLIT, ESYM, ESSYMREF, EVAARG, EINIT, EGETF, ECALL, ECOND,
+ /* unary */
+ EPLUS, ENEG, ECOMPL, ELOGNOT, EDEREF, EADDROF, ECAST,
+ EPREINC, EPOSTINC, EPREDEC, EPOSTDEC,
+ /* binary */
+ EADD, ESUB, EMUL, EDIV, EREM, EBAND, EBIOR, EXOR, ESHL, ESHR,
+ ELOGAND, ELOGIOR,
+ EEQU, ENEQ, ELTH, EGTH, ELTE, EGTE,
+ ESET, ESETADD, ESETSUB, ESETMUL, ESETDIV, ESETREM, ESETAND, ESETIOR, ESETXOR, ESETSHL, ESETSHR,
+ ESEQ,
+};
+#define isunop(t) in_range(t, EPLUS, EPOSTDEC)
+#define isbinop(t) in_range(t, EADD, ESEQ)
+#define isassign(t) in_range(t, ESET, ESETSHR)
+#define assigntobinop(t) ((t) - ESETADD + EADD)
+
+struct expr {
+ uchar t;
+ uchar qual;
+ ushort narg; /* ECALL */
+ union type ty;
+ struct span span;
+ union {
+ struct {
+ struct expr *sub; /* child(ren) */
+ struct exgetfld {
+ ushort off;
+ uchar bitsiz, bitoff;
+ } fld; /* EGETF */
+ };
+ uvlong u; vlong i; double f; /* ENUMLIT */
+ struct {
+ union {
+ uchar *p;
+ ushort *w16;
+ uint *w32;
+ };
+ uint n;
+ } s; /* ESTRLIT */
+ int decl; /* ESYM, index into declsbuf */
+ internstr implicitsym; /* ESYM (undeclared) */
+ struct {
+ internstr sym;
+ int off;
+ bool func : 1, local : 1;
+ } ssym; /* ESSYMREF (static symbol addr + off) */
+ struct init *init; /* EINIT */
+ };
+};
+
+struct init {
+ struct bitset zero[BSSIZE(64)]; /* bytes to zero out up to 64 */
+ struct initval {
+ struct initval *next;
+ uint off;
+ uchar bitoff, bitsiz;
+ struct expr ex;
+ } *vals, **tail;
+};
+
+/** C compiler state **/
+struct comp {
+ struct lexer *lx;
+ struct env *env;
+ struct arena *fnarena, *exarena;
+ struct span fnblkspan;
+ uint loopdepth, switchdepth;
+ struct block *breakto, *loopcont;
+ struct switchstmt *switchstmt;
+ struct label *labels;
+};
+
+enum storageclass {
+ SCNONE,
+ SCTYPEDEF = 1<<0,
+ SCEXTERN = 1<<1,
+ SCSTATIC = 1<<2,
+ SCTHREADLOCAL = 1<<3,
+ SCAUTO = 1<<4,
+ SCREGISTER = 1<<5,
+};
+
+struct decl {
+ union type ty;
+ uchar scls;
+ uchar qual : 2,
+ noret : 1,
+ inlin : 1,
+ isenum : 1,
+ isdef : 1,
+ isbuiltin : 1;
+ struct span span;
+ internstr name;
+ union {
+ internstr sym;
+ struct { ushort align; int id; };
+ vlong value;
+ const struct builtin *builtin;
+ };
+};
+
+extern struct envdecls {vec_of(struct decl);} declsbuf;
+extern union type cvalistty;
+struct function;
+int envadddecl(struct env *env, const struct decl *d);
+bool assigncheck(union type t, const struct expr *src);
+union ref expraddr(struct function *, const struct expr *);
+union ref scalarcvt(struct function *, union type to, union type from, union ref);
+union ref compileexpr(struct function *, const struct expr *, bool discard);
+void dumpexpr(const struct expr *, bool printtypes);
+
+/** builtin.c **/
+struct builtin {
+ bool (*sema)(struct comp *, struct expr *);
+ union ref (*comp)(struct function *, struct expr *, bool discard);
+};
+void putbuiltins(struct env *);
+union ref builtin_va_arg_comp(struct function *, const struct expr *, bool discard);
+
+/** eval.c **/
+enum evalmode {
+ EVNONE,
+ EVINTCONST,
+ EVARITH,
+ EVSTATICINI,
+ EVFOLD,
+};
+
+bool eval(struct expr *, enum evalmode);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_builtin.c b/src/c_builtin.c
new file mode 100644
index 0000000..5c59857
--- /dev/null
+++ b/src/c_builtin.c
@@ -0,0 +1,177 @@
+#include "c.h"
+#include "../ir/ir.h"
+
+static bool
+callcheck(const struct span *span, int nparam, const union type *param, int narg, struct expr *args)
+{
+ bool ok = 1;
+ for (int i = 0, n = narg < nparam ? narg : nparam; i < n; ++i) {
+ if (!assigncheck(typedecay(param[i]), &args[i])) {
+ ok = 0;
+ error(&args[i].span, "arg #%d of type '%ty' is incompatible with '%ty'",
+ i, args[i].ty, param[i]);
+ }
+ }
+
+ if (narg > nparam) {
+ error(&args[nparam].span, "too many args to builtin function taking %d params", nparam);
+ ok = 0;
+ } else if (narg < nparam) {
+ error(span, "not enough args to builtin function taking %d param%s", nparam,
+ nparam != 1 ? "s" : "");
+ ok = 0;
+ }
+ return ok;
+}
+
+#define DEF_FNLIKE_SEMA(name, retty, ...) \
+ static bool \
+ name##_sema(struct comp *cm, struct expr *ex) { \
+ union type par[] = { {{0}}, __VA_ARGS__ }; \
+ ex->ty = retty; \
+ return callcheck(&ex->span, countof(par)-1, par+1, ex->narg, ex->sub+1); \
+ }
+
+/* __builtin_va_start */
+static bool
+va_start_sema(struct comp *cm, struct expr *ex)
+{
+ ex->ty = mktype(TYVOID);
+ return callcheck(&ex->span, 1, &cvalistty, ex->narg, ex->sub+1);
+}
+static union ref
+va_start_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ assert(ex->t == ECALL && ex->narg == 1);
+ assert(typedecay(ex->sub[1].ty).bits == typedecay(cvalistty).bits);
+ if (!typedata[fn->fnty.dat].variadic)
+ error(&ex->span, "va_start used in non-variadic function");
+ addinstr(fn, mkinstr(Ovastart, 0, compileexpr(fn, &ex->sub[1], 0)));
+ return NOREF;
+}
+
+/* __builtin_va_end */
+static bool
+va_end_sema(struct comp *cm, struct expr *ex)
+{
+ ex->ty = mktype(TYVOID);
+ return callcheck(&ex->span, 1, &cvalistty, ex->narg, ex->sub+1);
+}
+
+static union ref
+va_end_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ return NOREF;
+}
+
+/* __builtin_va_copy */
+DEF_FNLIKE_SEMA(va_copy, mktype(TYVOID), cvalistty, cvalistty)
+static union ref
+va_copy_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ union irtype typ = mkirtype(cvalistty.t == TYARRAY ? typechild(cvalistty) : cvalistty);
+ for (int i = 1; i <= 2; ++i)
+ assert(typedecay(ex->sub[i].ty).bits == typedecay(cvalistty).bits);
+ union ref dst = compileexpr(fn, &ex->sub[1], 0), src = compileexpr(fn, &ex->sub[2], 0);
+ addinstr(fn, mkarginstr(typ, dst));
+ addinstr(fn, mkarginstr(typ, src));
+ addinstr(fn, mkintrin(INstructcopy, 0, 2));
+ return NOREF;
+}
+
+/* __builtin_trap */
+DEF_FNLIKE_SEMA(trap, mktype(TYVOID), )
+static union ref
+trap_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ puttrap(fn);
+ useblk(fn, newblk(fn)); /* unreachable block, but simplifies expr codegen */
+ return NOREF;
+}
+
+static inline union ref
+cvtintref(struct function *fn, enum irclass dst, union ref src)
+{
+ if (src.t == RTMP) {
+ if (insrescls(instrtab[src.i]) != dst)
+ return addinstr(fn, mkinstr(Ocopy, dst, src));
+ return src;
+ } else if (isintcon(src)) {
+ vlong x = intconval(src);
+ return mkintcon(dst, cls2siz[dst] == 4 ? (int)x : x);
+ }
+ assert(!"int ref?");
+}
+
+/* __builtin_bswap16 */
+DEF_FNLIKE_SEMA(bswap16, mktype(TYUSHORT), mktype(TYUSHORT))
+static union ref
+bswap16_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ assert(isint(ex->ty));
+ return irunop(fn, Obswap16, KI32, scalarcvt(fn, ex->ty, ex->sub[1].ty,
+ compileexpr(fn, &ex->sub[1], 0)));
+}
+/* __builtin_bswap32 */
+DEF_FNLIKE_SEMA(bswap32, mktype(TYUINT), mktype(TYUINT))
+static union ref
+bswap32_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ assert(isint(ex->ty));
+ return irunop(fn, Obswap32, KI32, scalarcvt(fn, ex->ty, ex->sub[1].ty,
+ compileexpr(fn, &ex->sub[1], 0)));
+}
+/* __builtin_bswap64 */
+DEF_FNLIKE_SEMA(bswap64, mktype(TYUVLONG), mktype(TYUVLONG))
+static union ref
+bswap64_comp(struct function *fn, struct expr *ex, bool discard)
+{
+ assert(isint(ex->ty));
+ return irunop(fn, Obswap64, KI64, scalarcvt(fn, ex->ty, ex->sub[1].ty,
+ compileexpr(fn, &ex->sub[1], 0)));
+}
+
+#define LIST_BUILTINS(_) \
+ _(va_start) _(va_copy) _(va_end) \
+ _(trap) _(bswap16) _(bswap32) _(bswap64)
+
+static const struct {
+ const char *name;
+ struct builtin b;
+} tab[] = {
+#define FNS(x) { "__builtin_" #x, { x##_sema, x##_comp } },
+ LIST_BUILTINS(FNS)
+#undef FNS
+};
+
+void
+putbuiltins(struct env *env)
+{
+ for (int i = 0; i < countof(tab); ++i) {
+ envadddecl(env, &(struct decl) {
+ .name = intern(tab[i].name),
+ .isbuiltin = 1,
+ .builtin = &tab[i].b,
+ });
+ }
+}
+
+/* this is separate because it's a keyword */
+union ref
+builtin_va_arg_comp(struct function *fn, const struct expr *ex, bool discard)
+{
+ assert(ex->t == EVAARG && ex->ty.t);
+ enum irclass k = isagg(ex->ty) ? KPTR : type2cls[scalartypet(ex->ty)];
+ return addinstr(fn, mkinstr(Ovaarg, k, compileexpr(fn, ex->sub, 0), mktyperef(mkirtype(ex->ty))));
+}
+
+bool
+hasbuiltin(const char *name, uint len)
+{
+ for (int i = 0; i < countof(tab); ++i)
+ if (!strncmp(name, tab[i].name, len))
+ return 1;
+ return 0;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_embedfilesdir.c b/src/c_embedfilesdir.c
new file mode 100644
index 0000000..d1c8dd4
--- /dev/null
+++ b/src/c_embedfilesdir.c
@@ -0,0 +1,106 @@
+#include <stddef.h>
+
+struct embedfile {
+ const char *name;
+ const char *s;
+ size_t len;
+};
+
+#define S(s) s"\0\0\0\0\0", (sizeof s) - 1
+
+struct embedfile embedfilesdir[] = {
+{"stddef.h", S("\
+#pragma once\n\
+typedef __typeof__((char*)0 - (char*)0) ptrdiff_t;\n\
+typedef __typeof__(sizeof 0) size_t;\n\
+typedef __typeof__(L'a') wchar_t;\n\
+#undef NULL\n\
+#define NULL ((void *)0)\n\
+#undef offsetof\n\
+#define offsetof(type, memb) ((size_t)((char *)&((type *)0)->memb - (char *)0))\n\
+")},
+
+{"stdarg.h", S("\
+#pragma once\n\
+typedef __builtin_va_list va_list;\n\
+#ifndef __GNUC_VA_LIST\n\
+#define __GNUC_VA_LIST\n\
+typedef __builtin_va_list __gnuc_va_list;\n\
+#endif\n\
+#define va_start(ap,n) __builtin_va_start(ap)\n\
+#define va_arg(ap,type) __builtin_va_arg(ap, type)\n\
+#define va_copy(dst,src) __builtin_va_copy(dst, src)\n\
+#define va_end(ap) __builtin_va_end(ap)\n\
+")},
+
+{"stdbool.h", S("\
+#pragma once\n\
+#if __STDC_VERSION__ < 202311L /* in C23 they are keywords */\n\
+#define bool _Bool \n\
+#define true 1\n\
+#define false 0\n\
+#endif\n\
+#define __bool_true_false_are_defined 1\n\
+")},
+
+{"float.h", S("\
+#pragma once\n\
+#define FLT_ROUNDS (-1)\n\
+#define FLT_EVAL_METHOD (-1)\n\
+#define FLT_HAS_SUBNORM (-1)\n\
+#define DBL_HAS_SUBNORM (-1)\n\
+#define LDBL_HAS_SUBNORM (-1)\n\
+#define FLT_RADIX 2\n\
+#define FLT_MANT_DIG 24\n\
+#define DBL_MANT_DIG 53\n\
+#define LDBL_MANT_DIG 53\n\
+#define FLT_DECIMAL_DIG 9\n\
+#define DBL_DECIMAL_DIG 17\n\
+#define LDBL_DECIMAL_DIG 17\n\
+#define DECIMAL_DIG 17\n\
+#define FLT_DIG 6\n\
+#define DBL_DIG 15\n\
+#define LDBL_DIG 15\n\
+#define FLT_MIN_EXP (-125)\n\
+#define DBL_MIN_EXP (-1021)\n\
+#define LDBL_MIN_EXP (-1021)\n\
+#define FLT_MIN_10_EXP (-37)\n\
+#define DBL_MIN_10_EXP (-307)\n\
+#define LDBL_MIN_10_EXP (-307)\n\
+#define FLT_MAX_EXP 128\n\
+#define DBL_MAX_EXP 1024\n\
+#define LDBL_MAX_EXP 1024\n\
+#define FLT_MAX_10_EXP 38\n\
+#define DBL_MAX_10_EXP 308\n\
+#define LDBL_MAX_10_EXP 308\n\
+#define FLT_MAX 3.40282e+38\n\
+#define DBL_MAX 1.79769e+308\n\
+#define LDBL_MAX 1.79769e+308\n\
+#define FLT_EPSILON 1.19209e-07\n\
+#define DBL_EPSILON 2.22045e-16\n\
+#define LDBL_EPSILON 2.22045e-16\n\
+#define FLT_MIN 1.17549e-38\n\
+#define DBL_MIN 2.22507e-308\n\
+#define LDBL_MIN 2.22507e-308\n\
+#define FLT_TRUE_MIN 1.4013e-45\n\
+#define DBL_TRUE_MIN 4.94066e-324\n\
+#define LDBL_TRUE_MIN 4.94066e-324\n\
+")},
+
+{"stdnoreturn.h", S("\
+#define noreturn _Noreturn\n\
+")},
+
+{"stdalign.h", S("\
+#if __STDC_VERSION__ < 202311L\n\
+#define alignas _Alignas\n\
+#define alignof _Alignof\n\
+#define __alignas_is_defined 1\n\
+#define __alignof_is_defined 1\n\
+#endif\n\
+")},
+
+ {NULL}
+};
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_eval.c b/src/c_eval.c
new file mode 100644
index 0000000..3dfbbfb
--- /dev/null
+++ b/src/c_eval.c
@@ -0,0 +1,437 @@
+#include "c.h"
+#include "../ir/ir.h"
+#include <limits.h>
+
+static int
+targ2hosttype(enum typetag t)
+{
+ if (t == TYPTR) t = targ_64bit ? TYUVLONG : TYUINT;
+ if (isintt(t)) {
+ int siz = targ_primsizes[t];
+ int sgn = issignedt(t);
+#define U(Ty,Tag) if (!sgn & (siz == sizeof(unsigned Ty))) return Tag;
+#define S(Ty,Tag) if ( sgn & (siz == sizeof(signed Ty))) return Tag;
+ U(char, TYUCHAR)
+ S(char, TYSCHAR)
+ U(short, TYUSHORT)
+ S(short, TYSHORT)
+ U(int, TYUINT)
+ S(int, TYINT)
+ U(long long, TYUVLONG)
+ S(long long, TYVLONG)
+#undef U
+#undef S
+ } else if (t == TYLDOUBLE) return TYDOUBLE;
+ else if (isfltt(t) || iscomplext(t)) return t;
+ return 0;
+}
+
+static bool
+numcast(union type ty, struct expr *dst, const struct expr *src)
+{
+ enum typetag td = targ2hosttype(scalartypet(ty)),
+ ts = targ2hosttype(scalartypet(src->ty));
+ vlong isrc;
+ struct expr tmp;
+ if (src == dst) tmp = *src, src = &tmp;
+
+ assert(src->t == ENUMLIT);
+#define TT(d,s) (td == d && ts == s)
+#define TF(d) (td == d && isfltt(ts))
+ if (!ts || !td) return 0;
+ else if (TT(TYFLOAT, TYFLOAT)) dst->f = (float) src->f;
+ else if (TT(TYFLOAT, TYDOUBLE)) dst->f = (float) src->f;
+ else if (TT(TYDOUBLE, TYFLOAT)) dst->f = src->f;
+ else if (TT(TYDOUBLE, TYDOUBLE)) dst->f = src->f;
+ else if (TT(TYFLOAT, TYUVLONG)) dst->f = (float) src->u;
+ else if (TT(TYDOUBLE, TYUVLONG)) dst->f = (double) src->u;
+ else if (td == TYFLOAT) dst->f = (float) src->i;
+ else if (td == TYDOUBLE) dst->f = (double) src->i;
+ else if (TF(TYUVLONG)) dst->u = src->f;
+ else if (TF(TYBOOL)) dst->i = (bool) src->f;
+ else if (isfltt(ts)) { isrc = src->f; goto Narrow; }
+ else {
+ isrc = src->i;
+ Narrow:
+ switch (td) {
+#define I(Ty, Tag) case Tag: dst->i = (Ty) isrc; break;
+ I(bool, TYBOOL)
+ I(signed char, TYSCHAR)
+ I(unsigned char, TYUCHAR)
+ I(signed short, TYSHORT)
+ I(unsigned short, TYUSHORT)
+ I(signed int, TYINT)
+ I(unsigned int, TYUINT)
+ I(signed long long, TYVLONG)
+ I(unsigned long long, TYUVLONG)
+#undef I
+ case TYFLOAT: dst->f = (float) src->f; break;
+ case TYDOUBLE: dst->f = src->f; break;
+ default: assert(0 && "bad cast?");
+ }
+ }
+#undef TT
+#undef TF
+
+ dst->t = ENUMLIT;
+ dst->ty = ty;
+ return 1;
+}
+
+static struct expr *
+lit2ssym(struct expr *ex)
+{
+ ex->ssym.sym = xcon2sym(expraddr(NULL, ex).i);
+ ex->ssym.local = 1;
+ ex->ssym.func = ex->ssym.off = 0;
+ ex->t = ESSYMREF;
+ return ex;
+}
+
+static struct expr
+staticaddrof(struct expr *ex, enum evalmode mode)
+{
+ struct expr ret = { .ty = mkptrtype(ex->ty, ex->qual), .span = ex->span };
+ if (ex->t == ESYM && ex->ty.t < NTYPETAG) {
+ const struct decl *decl = &declsbuf.p[ex->decl];
+ if (decl->sym && (decl->scls & (SCAUTO|SCREGISTER)) == 0) {
+ ret.t = ESSYMREF;
+ ret.ssym.sym = decl->sym;
+ ret.ssym.off = 0;
+ ret.ssym.func = decl->ty.t == TYFUNC;
+ ret.ssym.local = (decl->scls == SCSTATIC || decl->isdef);
+ }
+ } else if (ex->t == EDEREF && eval(ex->sub, EVSTATICINI)) {
+ ret = *ex->sub;
+ } else if (ex->t == EGETF && (ret = staticaddrof(ex->sub, mode)).t) {
+ if (ret.t == ESSYMREF) {
+ ex->t = ESSYMREF;
+ vlong off = (vlong) ret.ssym.off + ex->fld.off;
+ if ((int) off != off) return ret.t = 0, ret;
+ ret.ssym.off = off;
+ } else if (ret.t == ENUMLIT) {
+ ret.u += ex->fld.off;
+ } else assert(0);
+ } else if (ex->t == ESTRLIT || (mode == EVSTATICINI && ex->t == EINIT)) {
+ ret = *lit2ssym(ex);
+ } else if (ex->t == ENUMLIT || ex->t == ESSYMREF) ret = *ex;
+ return ret;
+}
+
+static bool
+isstaticlval(const struct expr *ex, enum evalmode mode)
+{
+ return ex->t == ESTRLIT || ex->t == ESSYMREF || (mode == EVSTATICINI && ex->t == EINIT);
+}
+
+static bool
+truthy(const struct expr *ex)
+{
+ switch (ex->t) {
+ default: assert(0 && "!scalar?");
+ case ENUMLIT:
+ return isflt(ex->ty) ? ex->f != 0.0 : ex->u != 0;
+ case ESTRLIT: case ESSYMREF:
+ return 1;
+ }
+}
+
+static bool
+unop(struct expr *ex, enum evalmode mode)
+{
+ struct expr *sub = ex->sub;
+
+ if (mode >= EVSTATICINI && ex->t == EDEREF) {
+ uvlong off;
+ uchar *p;
+ uint len;
+ uint csiz;
+ /* HACK */
+ if (sub->t == ESTRLIT) {
+ /* *"s" */
+ off = 0;
+ p = sub->s.p, len = sub->s.n;
+ csiz = typesize(typechild(sub->ty));
+ StrRead:
+ if (off > len) return 0;
+ ex->t = ENUMLIT;
+ ex->ty = mktype(TYINT);
+ if (off == len) ex->u = 0;
+ else if (csiz == 1) ex->u = p[off];
+ else if (csiz == 2) ex->u = ((short *)p)[off];
+ else if (csiz == 4) ex->u = ((int *)p)[off];
+ return 1;
+ } else if (sub->t == EADD && sub->sub[0].t == ESTRLIT && eval(&sub->sub[1], EVINTCONST)) {
+ /* "s"[0] */
+ assert(sub->sub[1].t == ENUMLIT && isint(sub->sub[1].ty));
+ off = sub->sub[1].u;
+ p = sub->sub[0].s.p, len = sub->sub[0].s.n;
+ csiz = typesize(typechild(sub->sub[0].ty));
+ goto StrRead;
+ } else if (sub->t == EADD && sub->sub[1].t == ESTRLIT && eval(&sub->sub[0], EVINTCONST)) {
+ /* 0["s"] */
+ assert(sub->sub[0].t == ENUMLIT && isint(sub->sub[0].ty));
+ off = sub->sub[0].u;
+ p = sub->sub[1].s.p, len = sub->sub[1].s.n;
+ csiz = typesize(typechild(sub->sub[1].ty));
+ goto StrRead;
+ } else return 0;
+ } else if (ex->t == EADDROF) {
+ assert(ex->ty.t == TYPTR);
+ struct expr ex2 = staticaddrof(ex->sub, mode);
+ if (!ex2.t) return 0;
+ ex2.span = ex->span;
+ ex2.ty = ex->ty;
+ *ex = ex2;
+ return 1;
+ } else if (ex->t == EGETF && !ex->fld.bitsiz) {
+ /* <lvalue>.memb -> is an address lvalue if 'memb' is of array type */
+ if (ex->ty.t == TYARRAY) {
+ struct expr ex2;
+ if ((ex2 = staticaddrof(ex->sub, mode)).t) {
+ if (ex2.t == ENUMLIT) {
+ ex->t = ENUMLIT;
+ ex->u = ex2.u + ex->fld.off;
+ return 1;
+ } else {
+ assert(ex2.t == ESSYMREF);
+ ex->t = ESSYMREF;
+ vlong off = (vlong) sub->ssym.off + ex->fld.off;
+ if ((int) off != off) return 0;
+ ex->ssym = ex2.ssym;
+ ex->ssym.off = off;
+ return mode >= EVSTATICINI;
+ }
+ }
+ }
+ return 0;
+ }
+ if (!eval(sub, mode)) return 0;
+ switch (ex->t) {
+ case ECAST:
+ if (ex->ty.t == TYPTR && sub->t == ENUMLIT) {
+ ex->t = ENUMLIT;
+ ex->u = sub->u;
+ return 1;
+ } else if (isstaticlval(sub, mode)
+ && (ex->ty.t == TYPTR || (isint(ex->ty) && typesize(ex->ty) == targ_primsizes[TYPTR]))) {
+ /* ptr -> int */
+ if (sub->t == ESTRLIT || sub->t == EINIT)
+ lit2ssym(sub);
+ sub->span = ex->span, sub->ty = ex->ty;
+ *ex = *sub;
+ return 1;
+ }
+ break;
+ case EPLUS:
+ break;
+ case ENEG:
+ if (sub->t != ENUMLIT) return 0;
+ if (isint(sub->ty)) sub->u = -sub->u;
+ else assert(isflt(sub->ty)), sub->f = -sub->f;
+ break;
+ case ECOMPL:
+ if (sub->t != ENUMLIT) return 0;
+ assert(isint(sub->ty));
+ sub->u = ~sub->u;
+ break;
+ case ELOGNOT:
+ sub->u = !truthy(sub);
+ sub->t = ENUMLIT;
+ break;
+ default:
+ return 0;
+ }
+ if (sub->t != ENUMLIT || !numcast(ex->ty, ex, sub)) return 0;
+ return 1;
+}
+
+static bool
+binop(struct expr *ex, enum evalmode mode)
+{
+ struct expr *a = &ex->sub[0], *b = &ex->sub[1];
+ if (!eval(a, mode)) return 0;
+ union type opty;
+ if (in_range(ex->t, EADD, ESHR))
+ opty = ex->ty;
+ else /* compare, logical, set (result type != operation type) */
+ opty = cvtarith(a->ty, b->ty);
+ if (isstaticlval(a, mode)) {
+ if ((ex->t == EADD || ex->t == ESUB) && eval(b, mode) && b->t == ENUMLIT) {
+ assert(isint(b->ty));
+ assert(in_range(a->ty.t, TYPTR, TYARRAY));
+ if (a->t == ESTRLIT) {
+ lit2ssym(a);
+ } else assert(a->t == ESSYMREF);
+ vlong addend = b->i * typesize(typechild(a->ty)),
+ off = a->ssym.off + (uvlong) (ex->t == EADD ? addend : -addend);
+ ex->t = ESSYMREF;
+ if ((int) off != off) return 0;
+ ex->ssym = a->ssym;
+ ex->ssym.off = off;
+ return 1;
+ }
+ return 0;
+ }
+
+ enum { U = 0, S = 1<<8 , F = 1<<9 };
+ int op = issigned(opty)<<8 | isflt(opty)<<9 | ex->t;
+ bool c;
+ if (ex->t != ELOGAND && ex->t != ELOGIOR) {
+ if (!numcast(opty, a, a)) return 0;
+ if (!eval(b, mode) || !numcast(opty, b, b)) return 0;
+ }
+ switch (op) {
+ case EADD|U:
+ case EADD|S: a->u += opty.t == TYPTR ? b->u * typesize(typechild(opty)) : b->u; break;
+ case EADD|F: a->f += b->f; break;
+
+ case ESUB|U:
+ case ESUB|S: if (opty.t == TYPTR) {
+ assert(a->t == ENUMLIT && b->t == ENUMLIT);
+ assert(!isincomplete(typechild(ex->ty)));
+ a->u = (a->u - b->u) / typesize(typechild(ex->ty));
+ } else a->u -= b->u;
+ break;
+ case ESUB|F: a->f -= b->f; break;
+
+ case EMUL|U:
+ case EMUL|S: a->u *= b->u; break;
+ case EMUL|F: a->f *= b->f; break;
+
+ case EDIV|U: if (!b->u) return 0;
+ a->u /= b->u;
+ break;
+ case EDIV|S: if (!b->i) return 0;
+ if (a->i == LLONG_MIN && b->i == -1) break;
+ a->i /= b->i;
+ break;
+ case EDIV|F: a->f /= b->f; break;
+
+ case EREM|U: if (!b->u) return 0;
+ a->u %= b->u;
+ break;
+ case EREM|S: if (!b->i) return 0;
+ if (a->i == LLONG_MIN && b->i == -1) a->i = 0;
+ a->i %= b->i;
+ break;
+
+ case EBAND|U:
+ case EBAND|S: a->u &= b->u; break;
+
+ case EBIOR|U:
+ case EBIOR|S: a->u |= b->u; break;
+
+ case EXOR|U:
+ case EXOR|S: a->u ^= b->u; break;
+
+ case ESHL|S: if (a->i < 0) return 0;
+ case ESHL|U: if (b->u >= 8*targ_primsizes[opty.t]) return 0;
+ a->u <<= b->u;
+ break;
+
+ case ESHR|U: if (b->u >= 8*targ_primsizes[opty.t]) return 0;
+ a->u >>= b->i;
+ break;
+ case ESHR|S: if (b->u >= 8*targ_primsizes[opty.t]) return 0;
+ a->i >>= b->i;
+ break;
+
+ case EEQU|U:
+ case EEQU|S: a->u = a->u == b->u; break;
+ case EEQU|F: a->u = a->f == b->f; break;
+
+ case ENEQ|U:
+ case ENEQ|S: a->u = a->u != b->u; break;
+ case ENEQ|F: a->u = a->f != b->f; break;
+
+ case ELTH|U: a->u = a->u < b->u; break;
+ case ELTH|S: a->u = a->i < b->i; break;
+ case ELTH|F: a->u = a->f < b->f; break;
+
+ case EGTH|U: a->u = a->u > b->u; break;
+ case EGTH|S: a->u = a->i > b->i; break;
+ case EGTH|F: a->u = a->f > b->f; break;
+
+ case ELTE|U: a->u = a->u <= b->u; break;
+ case ELTE|S: a->u = a->i <= b->i; break;
+ case ELTE|F: a->u = a->f <= b->f; break;
+
+ case EGTE|U: a->u = a->u >= b->u; break;
+ case EGTE|S: a->u = a->i >= b->i; break;
+ case EGTE|F: a->u = a->f >= b->f; break;
+
+ case ELOGAND|U:
+ case ELOGAND|S:
+ case ELOGAND|F:
+ c = (op & F) ? a->f : a->u;
+ if (c) {
+ if (!eval(b, mode) || !numcast(opty, b, b)) return 0;
+ c = (op & F) ? b->f : b->u;
+ }
+ a->u = c;
+ break;
+
+ case ELOGIOR|U:
+ case ELOGIOR|S:
+ case ELOGIOR|F:
+ c = op & F ? a->f : a->u;
+ if (!c) {
+ if (!eval(b, mode) || !numcast(opty, b, b)) return 0;
+ c = (op & F) ? b->f : b->u;
+ }
+ a->u = c;
+ break;
+ default: return 0;
+ }
+
+ if (!in_range(ex->t, EADD, ESHR)) {
+ a->t = ENUMLIT;
+ a->ty = mktype(TYINT);
+ }
+ return numcast(ex->ty, ex, a);
+}
+
+bool
+eval(struct expr *ex, enum evalmode mode)
+{
+ switch (ex->t) {
+ case EGETF: goto Unop;
+ case ESEQ:
+ if (!eval(&ex->sub[0], mode)) return 0;
+ *ex = ex->sub[1];
+ return eval(ex, mode);
+ case ECOND:
+ if (!eval(&ex->sub[0], mode)) return 0;
+ *ex = ex->sub[2-truthy(&ex->sub[0])];
+ return eval(ex, mode);
+ case EINIT:
+ for (struct initval *v = ex->init->vals; v; v = v->next) {
+ if (!eval(&v->ex, mode)) return 0;
+ }
+ return 1;
+ case ENUMLIT:
+ if (mode <= EVINTCONST) return !isflt(ex->ty);
+ return 1;
+ case ESYM:
+ if (in_range(ex->ty.t, TYARRAY, TYFUNC)
+ && mode >= EVSTATICINI) {
+ struct expr ex2 = staticaddrof(ex, mode);
+ if (ex2.t) {
+ union type ty = ex->ty;
+ *ex = ex2;
+ ex->ty = ty;
+ return 1;
+ }
+ }
+ return 0;
+ case ESTRLIT: case ESSYMREF:
+ return mode >= EVSTATICINI;
+ default:
+ if (isunop(ex->t)) Unop: return unop(ex, mode);
+ if (isbinop(ex->t)) return binop(ex, mode);
+ }
+ return 0;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_keywords.def b/src/c_keywords.def
new file mode 100644
index 0000000..e71f25f
--- /dev/null
+++ b/src/c_keywords.def
@@ -0,0 +1,76 @@
+/* !SORTED */
+/* token cstd (alias) */
+_(_Alignas, STDC11, )
+_(_Alignof, STDC11, )
+_(_Atomic, STDC11, )
+_(_BitInt, STDC23, )
+_(_Bool, STDC99, )
+_(_Complex, STDC99, "__complex", "__complex__")
+_(_Decimal128, STDC23, )
+_(_Decimal32, STDC23, )
+_(_Decimal64, STDC23, )
+_(_Generic, STDC11, )
+_(_Imaginary, STDC99, )
+_(_Noreturn, STDC11, )
+_(_Static_assert, STDC11, )
+_(_Thread_local, STDC11, )
+_(__asm__, 0, )
+_(__attribute__, 0, )
+_(__builtin_va_arg, 0, )
+_(__extension__, 0, )
+_(__typeof__, 0, "__typeof")
+_(alignas, STDC23, )
+_(alignof, STDC23, )
+_(auto, 0, )
+_(bool, STDC23, )
+_(break, 0, )
+_(case, 0, )
+_(char, 0, )
+_(const, 0, "__const", "__const__")
+_(constexpr, STDC23, )
+_(continue, 0, )
+_(default, 0, )
+_(do, 0, )
+_(double, 0, )
+_(else, 0, )
+_(enum, 0, )
+_(extern, 0, )
+_(false, STDC23, )
+_(float, 0, )
+_(for, 0, )
+_(goto, 0, )
+_(if, 0, )
+_(inline, STDC99, "__inline", "__inline__")
+_(int, 0, )
+_(long, 0, )
+_(nullptr, STDC23, )
+_(register, 0, )
+_(restrict, STDC99, "__restrict", "__restrict__")
+_(return, 0, )
+_(short, 0, )
+_(signed, 0, "__signed", "__signed__")
+_(sizeof, 0, )
+_(static, 0, )
+_(static_assert, STDC23, )
+_(struct, 0, )
+_(switch, 0, )
+_(thread_local, STDC23, )
+_(true, STDC23, )
+_(typedef, 0, )
+_(typeof, STDC23, )
+_(typeof_unqual, STDC23, )
+_(union, 0, )
+_(unsigned, 0, )
+_(void, 0, )
+_(volatile, 0, "__volatile", "__volatile__")
+_(while, 0, )
+
+#ifndef TKWBEGIN_
+# define TKWBEGIN_ TKW_Alignas
+#endif
+#ifndef TKWEND_
+# define TKWEND_ TKWwhile
+#endif
+#ifndef TKWMAXLEN_
+# define TKWMAXLEN_ (sizeof "__builtin_va_arg" - 1)
+#endif
diff --git a/src/c_lex.c b/src/c_lex.c
new file mode 100644
index 0000000..c196a21
--- /dev/null
+++ b/src/c_lex.c
@@ -0,0 +1,2496 @@
+#include "lex.h"
+#include "../version.h"
+#include <string.h>
+#include <stdlib.h>
+
+/* fill internal circular character buffer with input after translation phase 1 & 2
+ * (trigraph substitution and backslash-newline deletion */
+static void
+fillchrbuf(struct lexer *lx)
+{
+ const uchar *p = lx->dat + lx->idx;
+ int i = lx->chrbuf0, idx = lx->idx;
+ int rem = countof(lx->chrbuf) - i;
+ assert(rem >= 0);
+ if (rem > 0) {
+ memmove(lx->chrbuf, lx->chrbuf+i, rem * sizeof *lx->chrbuf);
+ memmove(lx->chridxbuf, lx->chridxbuf+i, rem * sizeof *lx->chridxbuf);
+ }
+ lx->chrbuf0 = 0;
+ i = rem;
+
+ for (; i < countof(lx->chrbuf); ++i) {
+ uchar c;
+ /* skip backslash-newline* */
+ for (;;) {
+ if (p[0] == '\\') {
+ if (p[1] == '\n') {
+ idx += 2;
+ p += 2;
+ } else if (p[1] == '\r' && p[2] == '\n') {
+ idx += 3;
+ p += 3;
+ } else break;
+ } else if (ccopt.trigraph && !memcmp(p, "\?\?/\n", 4)) {
+ idx += 4;
+ p += 4;
+ } else if (ccopt.trigraph && !memcmp(p, "\?\?/\r\n", 5)) {
+ idx += 5;
+ p += 5;
+ } else break;
+ addfileline(lx->fileid, idx);
+ }
+
+ if (idx >= lx->ndat) {
+ c = 0;
+ } else if (ccopt.trigraph && ((p[0] == '?') & (p[1] == '?'))) {
+ switch (p[2]) {
+ case '=': c = '#'; break;
+ case '(': c = '['; break;
+ case ')': c = ']'; break;
+ case '!': c = '|'; break;
+ case '<': c = '{'; break;
+ case '>': c = '}'; break;
+ case '-': c = '~'; break;
+ case '/': c = '\\'; break;
+ case '\'': c = '^'; break;
+ default: goto NoTrigraph;
+ }
+ p += 3;
+ idx += 3;
+ } else {
+ NoTrigraph:
+ ++idx;
+ if ((c = *p++) == '\n')
+ addfileline(lx->fileid, idx);
+ }
+ lx->chrbuf[i] = c;
+ lx->chridxbuf[i] = idx;
+ }
+ lx->idx = idx;
+}
+
+static uchar
+next(struct lexer *lx)
+{
+ if (lx->chrbuf0 >= countof(lx->chrbuf))
+ fillchrbuf(lx);
+ lx->chridx = lx->chridxbuf[lx->chrbuf0];
+ uchar c = lx->chrbuf[lx->chrbuf0];
+ lx->eof = lx->chridx >= lx->ndat;
+ ++lx->chrbuf0;
+ return c;
+}
+
+static uchar
+peek(struct lexer *lx, int off)
+{
+ assert(off < countof(lx->chrbuf));
+ if (lx->chrbuf0 + off >= countof(lx->chrbuf))
+ fillchrbuf(lx);
+ return lx->chrbuf[lx->chrbuf0 + off];
+}
+
+static bool
+match(struct lexer *lx, uchar c)
+{
+ if (!lx->eof && peek(lx, 0) == c) {
+ next(lx);
+ return 1;
+ }
+ return 0;
+}
+
+static bool
+aissep(int c) {
+ static const bool tab[] = {
+ ['('] = 1, [')'] = 1, ['['] = 1, [']'] = 1,
+ ['{'] = 1, ['}'] = 1, ['.'] = 1, [','] = 1,
+ [';'] = 1, ['?'] = 1, ['+'] = 1, ['-'] = 1,
+ ['*'] = 1, ['/'] = 1, ['&'] = 1, ['|'] = 1,
+ ['^'] = 1, ['~'] = 1, ['='] = 1, ['\''] = 1,
+ ['"'] = 1, ['<'] = 1, ['>'] = 1, [':'] = 1,
+ ['@'] = 1, ['#'] = 1, ['%'] = 1, ['\\'] = 1,
+ ['`'] = 1, ['!'] = 1,
+ };
+ if (!aisprint(c) || aisspace(c))
+ return 1;
+ return (uint)c < sizeof(tab) && tab[c];
+}
+
+enum typetag
+parsenumlit(uvlong *outi, double *outf, const struct token *tk, bool ispp)
+{
+ if (tk->t == TKCHRLIT) {
+ uvlong n = 0;
+ if (!tk->wide) {
+ for (int i = 0; i < tk->len; ++i)
+ n = n << 8 | (uchar)tk->s[i];
+ } else if (tk->wide == 1) {
+ n = tk->ws16[0];
+ } else {
+ assert(tk->wide == 2);
+ n = tk->ws32[0];
+ }
+ if (outi) *outi = n;
+ return TYINT;
+ } else if (memchr(tk->s, '.', tk->len)) {
+ extern double strtod(const char *, char **);
+ double f;
+ char buf[80], *suffix;
+ Float: /* float literal */
+ assert(tk->len < sizeof buf - 1 && "numlit too big");
+ memcpy(buf, tk->s, tk->len);
+ buf[tk->len] = 0;
+ f = strtod(buf, &suffix);
+ if (suffix == buf)
+ return 0;
+ if (!*suffix) {
+ if (outf) *outf = f;
+ return TYDOUBLE;
+ } else if ((suffix[0]|0x20) == 'f' && !suffix[1]) {
+ if (outf) *outf = f;
+ return TYFLOAT;
+ } else if ((suffix[0]|0x20) == 'l' && !suffix[1]) {
+ if (outf) *outf = f;
+ return TYLDOUBLE;
+ }
+ return 0;
+ } else { /* int literal */
+ static uvlong max4typ[TYUVLONG-TYINT+1];
+ uvlong n = 0;
+ int base = 10, nsx;
+ bool dec, u = 0, longlongok = ccopt.cstd >= STDC99 || !ccopt.pedant;
+ enum typetag ty = 0;
+ const char *sx; /*suffix*/
+ char c;
+
+ if (!max4typ[0])
+ for (ty = TYINT; ty <= TYUVLONG; ++ty)
+ max4typ[ty-TYINT] = ((1ull << (8*targ_primsizes[ty]-1))-1) << isunsignedt(ty) | 1;
+
+ sx = tk->s;
+ if (tk->len > 2 && sx[0] == '0') {
+ if ((sx[1]|32) == 'x') sx += 2, base = 16; /* 0x.. */
+ else if ((sx[1]|32) == 'b') sx += 2, base = 2; /* 0b.. */
+ else base = 8; /* 0.. */
+ }
+ for (; sx < tk->s + tk->len; ++sx) {
+ if (base < 16) {
+ if (!in_range(c = *sx, '0', '0'+base-1)) break;
+ n = n*base + c - '0';
+ } else {
+ if (in_range(c = *sx, '0', '9')) n = n*base + c - '0';
+ else if (in_range(c|32, 'a', 'f')) n = n*base + 0xa + (c|32) - 'a';
+ else break;
+ }
+ }
+ dec = base == 10;
+ nsx = tk->len - (sx - tk->s);
+
+ if (nsx == 0) /* '' */ {}
+ else if ((sx[0]|32) == 'u') {
+ u = 1;
+ if (nsx == 1) /* 'u' */ {}
+ else if ((sx[1]|32) == 'l') {
+ if (nsx == 2) /* 'ul' */ goto L;
+ if (sx[1] == sx[2] && nsx == 3) /* 'ull' */ goto LL;
+ return 0;
+ } else return 0;
+ } else if ((sx[0]|32) == 'l') {
+ if (nsx == 1) /* 'l' */ goto L;
+ if ((sx[1]|32) == 'u' && nsx == 2) /* 'lu' */ { u=1; goto L; }
+ if (sx[1] == sx[0]) {
+ if (nsx == 2) /* 'll' */ goto LL;
+ if ((sx[2]|32) == 'u' && nsx == 3) /* 'llu' */ { u=1; goto LL; }
+ }
+ return 0;
+ } else if ((sx[0]|32) == 'e' || (sx[0]|32) == 'p')
+ goto Float;
+ else return 0;
+
+#define I(T) if (n <= max4typ[T - TYINT]) { ty = T; goto Ok; }
+ I(TYINT)
+ if (u || !dec) I(TYUINT)
+ L:
+ I(TYLONG)
+ if (u || !dec || !longlongok) I(TYULONG)
+ if (longlongok) {
+ LL:
+ I(TYVLONG)
+ if (u || !dec) I(TYUVLONG)
+ }
+ if (ispp) { ty = TYUVLONG; goto Ok; }
+#undef I
+ /* too big */
+ if (outi) *outi = n;
+ return 0;
+ Ok:
+ if (u && issignedt(ty)) ++ty; /* make unsigned */
+ if (outi) *outi = n;
+ if (ispp) {
+ if (u) return TYUVLONG;
+ else if (n <= max4typ[TYVLONG-TYINT]) return TYVLONG;
+ }
+ if (ty >= TYVLONG && !longlongok)
+ warn(&tk->span, "'long long' in %M is an extension");
+ return ty;
+ }
+}
+
+static void
+readstrchrlit(struct lexer *lx, struct token *tk, char delim, int wide)
+{
+ int c, i;
+ uchar tmp[200];
+ vec_of(uchar) b = VINIT(tmp, sizeof tmp);
+ struct span span = {0};
+ uint n, beginoff, idx;
+ beginoff = idx = lx->chridx;
+
+ while ((c = next(lx)) != delim) {
+ static uint wmax[] = {0xFF, 0xFFFF, 0xFFFFFFFFu};
+ if (c == '\n' || c == TKEOF) {
+ Noterm:
+ span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid };
+ error(&span, "missing terminating %c character", delim);
+ break;
+ } else if (c == '\\') {
+ span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid };
+ switch (c = next(lx)) {
+ case '\n': case TKEOF:
+ goto Noterm;
+ case '\'': c = '\''; break;
+ case '\\': c = '\\'; break;
+ case '"': c = '"'; break;
+ case '?': c = '?'; break;
+ case 'a': c = '\a'; break;
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case 'x': case 'X': /* hex */
+ n = 0;
+ if (!aisxdigit(peek(lx, 0))) goto Badescseq;
+ do {
+ c = next(lx);
+ if (c-'0' < 10) n = n<<4 | (c-'0');
+ else n = n<<4 | (10 + (c|0x20)-'a');
+ } while (aisxdigit(peek(lx, 0)));
+ if (n > wmax[wide]) {
+ span.sl.len = lx->chridx - span.sl.off;
+ error(&span, "hex escape sequence out of range");
+ }
+ c = n;
+ break;
+ default:
+ if (aisodigit(c)) { /* octal */
+ n = c-'0';
+ for (i = 2; i--;) {
+ if (!aisodigit(peek(lx, 0))) break;
+ n = n<<3 | ((c = next(lx))-'0');
+ }
+ if (n > wmax[wide]) {
+ span.sl.len = lx->chridx - span.sl.off;
+ error(&span, "octal escape sequence out of range");
+ }
+ c = n;
+ break;
+ }
+ Badescseq:
+ span.sl.len = lx->chridx - span.sl.off;
+ error(&span, "invalid escape sequence");
+ }
+ }
+ if (!wide || c <= 0xFF) {
+ vpush(&b, c);
+ } else {
+ /* XXX this doesn't work for non-utf sequences, UTF-16 surrogates, etc
+ * the source utf8 -> utf16/32 conversion should be done on the fly, then
+ * these can also be appended directly, rather than doing the conversion at the end */
+ char p[4];
+ int n = utf8enc(p, c);
+ vpushn(&b, p, n);
+ }
+ idx = lx->chridx;;
+ }
+ if (delim == '"') {
+ tk->t = TKSTRLIT;
+ tk->len = b.n;
+ if ((tk->wide = wide)) {
+ tk->litlit = 0;
+ if (wide == 1)
+ tk->ws16 = utf8to16(&tk->len, lx->tmparena, b.p, b.n);
+ else
+ tk->ws32 = utf8to32(&tk->len, lx->tmparena, b.p, b.n);
+ } else if (lx->chridx - beginoff == tk->len + 1) {
+ tk->litlit = 1;
+ tk->s = (char *)&lx->dat[beginoff];
+ } else {
+ tk->litlit = 0;
+ vpush(&b, 0);
+ tk->s = alloccopy(lx->tmparena, b.p, b.n, 1);
+ }
+ } else {
+ if (b.n == 0) {
+ span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid };
+ error(&span, "empty character literal");
+ } else if (b.n > targ_primsizes[TYINT]) {
+ span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid };
+ error(&span, "multicharacter literal too long");
+ }
+ tk->t = TKCHRLIT;
+ tk->len = b.n;
+ if ((tk->wide = wide)) {
+ tk->litlit = 0;
+ if (wide == 1)
+ tk->ws16 = utf8to16(&tk->len, lx->tmparena, b.p, b.n);
+ else
+ tk->ws32 = utf8to32(&tk->len, lx->tmparena, b.p, b.n);
+ } else if (lx->chridx - beginoff == tk->len + 1) {
+ tk->litlit = 1;
+ tk->s = (char *)&lx->dat[beginoff];
+ } else {
+ tk->litlit = 0;
+ tk->s = alloccopy(lx->tmparena, b.p, tk->len, 1);
+ }
+ }
+ vfree(&b);
+}
+
+/* for #include directive, read "header" or <header> */
+static void
+readheadername(struct lexer *lx, struct token *tk, char delim)
+{
+ int c;
+ uchar tmp[200];
+ vec_of(uchar) b = VINIT(tmp, sizeof tmp);
+ struct span span = {0};
+ uint beginoff, idx;
+ beginoff = idx = lx->chridx;
+
+ while ((c = next(lx)) != delim) {
+ if (c == '\n' || lx->eof) {
+ span.sl = (struct span0) { idx, lx->chridx - idx, lx->fileid };
+ error(&span, "missing terminating %c character", delim);
+ break;
+ }
+ vpush(&b, c);
+ idx = lx->chridx;;
+ }
+ tk->t = delim == '"' ? TKPPHDRQ : TKPPHDRH;
+ tk->len = b.n;
+ if (lx->chridx - beginoff == tk->len + 1) {
+ tk->litlit = 1;
+ tk->s = (char *)&lx->dat[beginoff];
+ } else {
+ tk->litlit = 0;
+ vpush(&b, 0);
+ tk->s = alloccopy(lx->tmparena, b.p, b.n, 1);
+ }
+ vfree(&b);
+}
+
+/* matches "<digit> | <identifier-nondigit> | '.' | ([eEpP][+-])" */
+static bool
+isppnum(char prev, char c)
+{
+ if (!aissep(c) || c == '.')
+ return 1;
+ if (c == '+' || c == '-')
+ return (prev|0x20) == 'e' || (prev|0x20) == 'p';
+ return 0;
+}
+
+enum { MAXLITLEN = 256 }; /* maximum length of num literals and identifiers */
+static int
+lex0(struct lexer *lx, struct token *tk, bool includeheader)
+{
+ int idx,q;
+ bool space = 0;
+Begin:
+ idx = lx->chridx;
+ if (lx->chrbuf0+4 >= countof(lx->chrbuf))
+ fillchrbuf(lx);
+ lx->chridx = lx->chridxbuf[lx->chrbuf0];
+ uchar *p = &lx->chrbuf[lx->chrbuf0++],
+ c = p[0];
+ switch (c) {
+
+#define RET(t_) do { tk->t = (t_); goto End; } while (0)
+#define TK2(c2,t) if (p[1] == c2) { \
+ lx->chridx = lx->chridxbuf[lx->chrbuf0]; \
+ ++lx->chrbuf0; \
+ RET(t); \
+ }
+#define TK3(c2,c3,t) if (p[1] == c2 && p[2] == c3) { \
+ lx->chridx = lx->chridxbuf[++lx->chrbuf0]; \
+ ++lx->chrbuf0; \
+ RET(t); \
+ }
+
+ case ' ': case '\t': case '\f': case '\v': case '\r':
+ space = 1;
+ goto Begin;
+ break;
+ case '(': case ')': case ',': case ':':
+ case ';': case '?': case '[': case ']':
+ case '{': case '}': case '~': case '$':
+ case '@': case '`': case '\\': case '\n':
+ RET(c);
+ case '!':
+ TK2('=', TKNEQ);
+ RET(c);
+ case '#':
+ TK2('#', TKPPCAT);
+ RET(c);
+ case '+':
+ TK2('+', TKINC);
+ TK2('=', TKSETADD);
+ RET(c);
+ case '-':
+ TK2('-', TKDEC);
+ TK2('=', TKSETSUB);
+ TK2('>', TKARROW);
+ RET(c);
+ case '*':
+ TK2('=', TKSETMUL);
+ RET(c);
+ case '/':
+ TK2('=', TKSETDIV);
+ if (match(lx, '/')) {
+ /* // single line comment */
+ for (;;) {
+ do {
+ if (lx->chrbuf[lx->chrbuf0] == '\n') {
+ lx->chridx = lx->chridxbuf[lx->chrbuf0++];
+ lx->eof = lx->chridx >= lx->ndat;
+ RET('\n');
+ } else if (lx->eof) RET(TKEOF);
+ } while (++lx->chrbuf0 < countof(lx->chrbuf));
+ fillchrbuf(lx);
+ lx->chridx = lx->chridxbuf[lx->chrbuf0];
+ lx->eof = lx->chridx >= lx->ndat;
+ }
+ } else if (match(lx, '*')) {
+ // /* multi line comment */
+ if (lx->chrbuf0+1 >= countof(lx->chrbuf)) fillchrbuf(lx);
+ for (;;) {
+ do {
+ if (lx->chrbuf[lx->chrbuf0] == '*' && lx->chrbuf[lx->chrbuf0+1] == '/') {
+ lx->chridx = lx->chridxbuf[lx->chrbuf0+1];
+ lx->chrbuf0 += 2;
+ lx->eof = lx->chridx >= lx->ndat;
+ space = 1;
+ goto Begin;
+ }
+ } while (++lx->chrbuf0+1 < countof(lx->chrbuf));
+ fillchrbuf(lx);
+ lx->chridx = lx->chridxbuf[lx->chrbuf0];
+ if ((lx->eof = (lx->chridx >= lx->ndat))) {
+ struct span span = {{ idx, lx->chridx - idx, lx->fileid }};
+ fatal(&span, "unterminated comment");
+ }
+ }
+ }
+ RET(c);
+ case '%':
+ TK2('=', TKSETREM);
+ RET(c);
+ case '^':
+ TK2('=', TKSETXOR);
+ RET(c);
+ case '=':
+ TK2('=', TKEQU);
+ RET(c);
+ case '<':
+ if (includeheader) {
+ readheadername(lx, tk, '>');
+ goto End;
+ }
+ TK2('=', TKLTE);
+ TK3('<','=', TKSETSHL)
+ TK2('<', TKSHL);
+ RET(c);
+ case '>':
+ TK2('=', TKGTE);
+ TK3('>','=', TKSETSHR)
+ TK2('>', TKSHR);
+ RET(c);
+ case '&':
+ TK2('&', TKLOGAND);
+ TK2('=', TKSETAND);
+ RET(c);
+ case '|':
+ TK2('|', TKLOGIOR);
+ TK2('=', TKSETIOR);
+ RET(c);
+ case '"':
+ if (includeheader) {
+ readheadername(lx, tk, '"');
+ } else {
+ case '\'':
+ tk->wideuni = 0;
+ readstrchrlit(lx, tk, c, 0);
+ }
+ goto End;
+ case '.':
+ TK3('.','.',TKDOTS)
+ if (aisdigit(p[1])) goto Numlit;
+ RET(c);
+ case 'L':
+ if (match(lx, (q = '\'')) || match(lx, (q = '"'))) {
+ tk->wideuni = 0;
+ readstrchrlit(lx, tk, q, /* wide */ targ_primsizes[targ_wchartype] == 2 ? 1 : 2);
+ goto End;
+ }
+ /* fallthru */
+ default:
+ if (aisdigit(c)) Numlit: {
+ --lx->chrbuf0;
+ if (lx->chrbuf0 + MAXLITLEN >= countof(lx->chrbuf))
+ fillchrbuf(lx);
+ int n = 1;
+ uchar *p = &lx->chrbuf[lx->chrbuf0];
+ for (; isppnum(p[n-1], p[n]); ++n) {
+ if (n >= MAXLITLEN) {
+ lx->chridx = lx->chridxbuf[lx->chrbuf0+n-1];
+ TooLong:
+ fatal(&(struct span) {{ idx, lx->chridx - idx, lx->fileid }},
+ "token is too long");
+ }
+ }
+ tk->len = n;
+ lx->chridx = lx->chridxbuf[(lx->chrbuf0 += n) - 1];
+ if (n == lx->chridx - idx) {
+ tk->litlit = 1;
+ tk->s = (char *)&lx->dat[idx];
+ } else {
+ tk->litlit = 0;
+ tk->s = alloccopy(lx->tmparena, p, n, 1);
+ }
+ RET(TKNUMLIT);
+ } else if (c == '_' || aisalpha(c)) {
+ --lx->chrbuf0;
+ if (lx->chrbuf0 + MAXLITLEN >= countof(lx->chrbuf))
+ fillchrbuf(lx);
+ uchar *p = &lx->chrbuf[lx->chrbuf0];
+ int n = 1;
+ for (; !aissep(p[n]); ++n) {
+ if (n >= MAXLITLEN) {
+ lx->chridx = lx->chridxbuf[lx->chrbuf0+n-1];
+ goto TooLong;
+ }
+ }
+ tk->blue = 0;
+ tk->len = n;
+ tk->name = intern_((char *)p, n);
+ lx->chridx = lx->chridxbuf[(lx->chrbuf0 += n) - 1];
+ RET(TKIDENT);
+ }
+ /* fallthru */
+ case 0: if (lx->idx >= lx->ndat) RET(TKEOF);
+#undef TK2
+ }
+ fatal(&(struct span) {{ idx, lx->chridx - idx, lx->fileid }},
+ "unexpected character %'c at %d (%d)", c, idx, lx->idx);
+End:
+ tk->space = space;
+ tk->span.sl.file = lx->fileid;
+ tk->span.sl.off = idx;
+ tk->span.sl.len = lx->chridx - idx;
+ tk->span.ex = tk->span.sl;
+ return tk->t;
+#undef RET
+}
+
+/****************/
+/* PREPROCESSOR */
+/****************/
+
+static bool
+tokequ(const struct token *a, const struct token *b)
+{
+ if (a->t != b->t) return 0;
+ if (a->t == TKNUMLIT || a->t == TKSTRLIT || a->t == TKCHRLIT) {
+ if (a->len != b->len) return 0;
+ return !memcmp(a->s, b->s, a->len);
+ } else if (a->t == TKIDENT) {
+ return a->name == b->name;
+ } else if (a->t == TKPPMACARG || a->t == TKPPMACSTR) {
+ return a->argidx == b->argidx;
+ }
+ return 1;
+}
+
+static vec_of(struct token) mtoksbuf, /* buffers for macro replacement list tokens */
+ mdyntoksbuf; /* for function-like macros after parameter substitution */
+
+struct macro {
+ internstr *param;
+ struct span0 span;
+ uchar nparam;
+ bool predef : 1,
+ special : 1,
+ fnlike : 1,
+ variadic : 1;
+ short id;
+ union {
+ void (*handler)(struct lexer *, struct token *);
+ struct rlist {
+ uint off; /* mtoksbuf[] */
+ int n;
+ } rl;
+ const struct token *single; /* predef */
+ void (*handlerfn)(struct lexer *, struct token *ret, const struct token *arg, int narg);
+ };
+};
+
+static bool
+macroequ(const struct macro *a, const struct macro *b)
+{
+ if (a->special != b->special) return 0;
+ if (a->fnlike != b->fnlike || a->variadic != b->variadic) return 0;
+ if (a->fnlike) {
+ if (a->nparam != b->nparam) return 0;
+ for (int i = 0; i < a->nparam; ++i)
+ if (a->param[i] != b->param[i])
+ return 0;
+ }
+ if (a->special) return a->handler == b->handler;
+ if (a->rl.n != b->rl.n) return 0;
+ const struct token *tka = &mtoksbuf.p[a->rl.off], *tkb = &mtoksbuf.p[b->rl.off];
+ for (int i = 0; i < a->rl.n; ++i) {
+ if (!tokequ(&tka[i], &tkb[i]))
+ return 0;
+ if (i > 0 && tka[i].space != tkb[i].space)
+ return 0;
+ }
+ return 1;
+}
+
+static void
+freemac(struct macro *mac)
+{
+ if (mac->special) return;
+ free(mac->param);
+}
+
+static pmap_of(struct macro) macroht;
+
+static void
+putmac(internstr name, struct macro *mac)
+{
+ static short id;
+ if (!macroht.v) pmap_init(&macroht, 1<<10);
+ struct macro *slot = pmap_get(&macroht, name);
+ mac->id = id++;
+ if (slot) {
+ if (!macroequ(slot, mac)) {
+ if (slot->predef)
+ warn(&(struct span){mac->span}, "redefining builtin macro");
+ else {
+ warn(&(struct span){mac->span}, "redefining macro");
+ note(&(struct span){slot->span}, "previous definition:");
+ }
+ freemac(slot);
+ *slot = *mac;
+ } else {
+ freemac(mac);
+ }
+ } else {
+ pmap_set(&macroht, name, *mac);
+ }
+}
+
+static void
+delmac(internstr name)
+{
+ struct macro *slot = pmap_get(&macroht, name);
+ if (!slot) return;
+ freemac(slot);
+ pmap_del(&macroht, name);
+}
+
+static inline internstr
+macname(struct macro *mac)
+{
+ return macroht.mb.k[mac - macroht.v];
+}
+
+static inline struct macro *
+findmac(internstr name)
+{
+ return pmap_get(&macroht, name);
+}
+
+static void popmac(struct lexer *, bool all);
+
+static struct macrostack {
+ struct {
+ union {
+ uint off; /* mtoksbuf[]/mdyntoksbuf[] */
+ const struct token *p;
+ };
+ int n;
+ } rl;
+ struct span0 exspan;
+ int idx;
+ short macid; /* -1 for argument undergoing expansion */
+ bool space : 1, stop : 1, dyn;
+} mstk[1200];
+
+static void NORETURN
+lxfatal(struct lexer *lx, const struct span *span, const char *fmt, ...)
+{
+ if (fmt) {
+ va_list ap;
+ va_start(ap, fmt);
+ vdiag(span, DGERROR, fmt, ap);
+ va_end(ap);
+ }
+ int n = lx->macstk ? lx->macstk - mstk : 0, i = 0;
+ for (struct macrostack *l = lx->macstk; l && l > mstk; --l, ++i) {
+ if (i < 4 || i > n - 5) {
+ note(&(struct span){l->exspan}, "expanded from here");
+ } else if (i == 5) {
+ efmt(" (...) \n");
+ }
+ }
+ for (struct lexer *sv = lx->save; sv; sv = sv->save) {
+ int line;
+ const char *f = getfilepos(&line, NULL, sv->fileid, sv->chridx-2);
+ note(NULL, "in file included from %s:%d", f, line);
+ }
+ if (!fmt || span) efmt("Aborting due to previous error.\n");
+ exit(1);
+}
+
+static void
+ppskipline(struct lexer *lx)
+{
+ while (lx->macstk) popmac(lx, 1);
+ for (int c; (c = peek(lx, 0)) != '\n' && !lx->eof; next(lx)) {
+ if (c == '/' && peek(lx, 1) == '*') { /* comment */
+ next(lx), next(lx);
+ bool done = 0;
+ while (!((c = peek(lx, 0)) == '*' && peek(lx, 1) == '/')) {
+ if (lx->eof) {
+ struct span span = {{ lx->idx, lx->chridx - lx->idx, lx->fileid }};
+ lxfatal(lx, &span, "unterminated comment");
+ }
+ done = c == '\n';
+ next(lx);
+ }
+ next(lx);
+ if (done) return;
+ }
+ }
+}
+
+#define isppident(tk) in_range((tk).t, TKIDENT, TKWEND_)
+
+static bool
+tokpaste(struct lexer *lx, struct token *dst, const struct token *l, const struct token *r)
+{
+ int t;
+ if (isppident(*l) && (isppident(*r) || r->t == TKNUMLIT)) {
+ /* foo ## bar ; foo ## 123 */
+ t = TKIDENT;
+ } else if (l->t == TKNUMLIT && (isppident(*r) || r->t == TKNUMLIT)) {
+ /* 0x ## abc ; 213 ## 456 */
+ t = TKNUMLIT;
+ } else if (l->t && !r->t) {
+ if (dst) *dst = *l;
+ return 1;
+ } else if (!l->t && r->t) {
+ if (dst) *dst = *r;
+ return 1;
+ } else {
+ static const struct { char s[2]; char t; } tab[] = {
+ {"==", TKEQU}, {"!=", TKNEQ}, {"<=", TKLTE}, {">=", TKGTE},
+ {">>", TKSHR}, {"<<", TKSHL}, {"++", TKINC}, {"--", TKDEC},
+ {"->", TKARROW}, {"##", TKPPCAT}, {"&&", TKLOGAND}, {"||", TKLOGIOR},
+ {"+=", TKSETADD}, {"-=", TKSETSUB}, {"*=", TKSETMUL}, {"/=", TKSETDIV},
+ {"%=", TKSETREM}, {"|=", TKSETIOR}, {"^=", TKSETXOR}, {"&=", TKSETAND},
+ {{TKSHL,'='}, TKSETSHL}, {{TKSHR,'='}, TKSETSHR}
+ };
+ for (int i = 0; i < countof(tab); ++i) {
+ if (tab[i].s[0] == l->t && tab[i].s[1] == r->t) {
+ if (dst) dst->t = tab[i].t;
+ return 1;
+ }
+ }
+
+ if (dst) {
+ error(&l->span, "pasting %'tk and %'tk does not form a valid preprocessing token", l, r);
+ note(&r->span, "right-hand side");
+ }
+ return 0;
+ }
+
+ if (!dst) return 1;
+ char buf[200];
+ memset(dst, 0, sizeof *dst);
+ dst->span = l->span;
+ if (dst->span.ex.file == r->span.ex.file && dst->span.ex.off < r->span.ex.off)
+ joinspan(&dst->span.ex, r->span.ex);
+ dst->t = t;
+ dst->len = l->len + r->len;
+ char *s = (isppident(*dst) && dst->len + 1 < sizeof buf) ? buf : alloc(lx->tmparena, dst->len + 1, 1);
+ memcpy(s, l->s, l->len);
+ memcpy(s + l->len, r->s, r->len);
+ s[dst->len] = 0;
+ dst->space = l->space;
+ if (isppident(*dst)) {
+ dst->blue = 0;
+ dst->name = intern(s);
+ } else {
+ dst->s = s;
+ }
+ return 1;
+}
+
+enum { MAXMACROARGS = 128 };
+
+static void
+ppdefine(struct lexer *lx)
+{
+ struct token tk0, tk;
+ internstr mname;
+ struct macro mac = {0};
+ struct bitset usedparams[BSSIZE(MAXMACROARGS)] = {0};
+
+ lex0(lx, &tk0, 0);
+ if (tk0.t != TKIDENT) {
+ error(&tk0.span, "macro name missing");
+ ppskipline(lx);
+ return;
+ }
+ mname = tk0.name;
+ mac.span = tk0.span.sl;
+
+ if (match(lx, '(')) {
+ /* gather params for function-like macro */
+ vec_of(internstr) params = {0};
+ vinit(&params, NULL, 4);
+ mac.fnlike = 1;
+ while (lex0(lx, &tk, 0) != ')') {
+ if (mac.variadic) {
+ error(&tk.span, "expected `)' after `...'");
+ if (tk.t == TKEOF || tk.t == '\n') return;
+ break;
+ }
+ if (params.n > 0) {
+ if (tk.t == TKDOTS) { /* GNU extension 'args...' */
+ mac.variadic = 1;
+ continue;
+ } if (tk.t != ',') {
+ error(&tk.span, "expected `,' or `)'");
+ if (tk.t == TKEOF || tk.t == '\n') return;
+ break;
+ }
+ lex0(lx, &tk, 0);
+ }
+ if (tk.t == TKIDENT)
+ vpush(&params, tk.name);
+ else if (tk.t == TKDOTS) {
+ mac.variadic = 1;
+ vpush(&params, intern("__VA_ARGS__"));
+ } else {
+ error(&tk.span, "expected parameter name or `)'");
+ if (tk.t == TKEOF || tk.t == '\n') return;
+ break;
+ }
+ }
+ if (!params.n) vfree(&params);
+ mac.param = params.p;
+ mac.nparam = params.n;
+ }
+
+ /* gather replacement list */
+ mac.rl.off = mtoksbuf.n;
+ for (int n = 0; lex0(lx, &tk, 0) != '\n' && tk.t != TKEOF;) {
+ if (n == 0 && !tk.space)
+ warn(&tk.span, "no whitespace after macro name");
+ struct token *prev = n ? &mtoksbuf.p[mtoksbuf.n-1] : NULL;
+ if (mac.fnlike && tk.t == TKIDENT) {
+ for (int i = 0; i < mac.nparam; ++i) {
+ if (tk.name == mac.param[i]) {
+ bsset(usedparams, i);
+ tk.argidx = i;
+ if (prev && prev->t == '#') {
+ tk.t = TKPPMACSTR;
+ *prev = tk;
+ goto Next;
+ } else {
+ tk.t = TKPPMACARG;
+ break;
+ }
+ }
+ }
+ }
+ if (n > 1 && prev->t == TKPPCAT) {
+ struct token new;
+ if (prev[-1].t != TKPPMACARG && tk.t != TKPPMACARG
+ && tokpaste(lx, &new, &prev[-1], &tk))
+ {
+ /* trivial concatenations */
+ prev[-1] = new;
+ --mtoksbuf.n;
+ --n;
+ continue;
+ }
+ }
+ if (in_range(tk.t, TKNUMLIT, TKSTRLIT) && !tk.litlit)
+ tk.s = alloccopy(&globarena, tk.s, tk.len << tk.wide, 1);
+ vpush(&mtoksbuf, tk);
+ ++n;
+ Next:;
+ }
+ mac.rl.n = mtoksbuf.n - mac.rl.off;
+ /* mark unused params as such by nulling out param name,
+ * this way they aren't expanded when unused in the macro body */
+ for (uint i = 0; bsiterzr(&i, usedparams, countof(usedparams)) && i < mac.nparam; ++i) {
+ mac.param[i] = NULL;
+ }
+ putmac(mname, &mac);
+}
+
+static void
+expecteol(struct lexer *lx, const char *ppname)
+{
+ struct token tk;
+ assert(!lx->macstk);
+ if (lex0(lx, &tk, 0) != '\n' && tk.t != TKEOF) {
+ (ccopt.pedant ? error : warn)(&tk.span, "extra tokens after #%s", ppname);
+ ppskipline(lx);
+ }
+}
+static void
+ppundef(struct lexer *lx)
+{
+ struct token tk;
+
+ lex0(lx, &tk, 0);
+ if (tk.t != TKIDENT) {
+ error(&tk.span, "macro name missing");
+ ppskipline(lx);
+ return;
+ }
+ expecteol(lx, "undef");
+ delmac(tk.name);
+}
+
+static void
+pushmacstk(struct lexer *lx, const struct span *span, const struct macrostack *m)
+{
+ struct macrostack *l = lx->macstk;
+ if (!l) l = mstk;
+ else if ((++l == mstk+countof(mstk))) lxfatal(lx, span, "macro expansion depth limit reached");
+ *l = *m;
+ l->idx = 0;
+ l->exspan = span->ex;
+ lx->macstk = l;
+}
+
+static void
+popmac(struct lexer *lx, bool all)
+{
+ struct macrostack *stk;
+
+ assert(stk = lx->macstk);
+ do {
+ if (stk->dyn)
+ mdyntoksbuf.n -= stk->rl.n;
+ if (lx->macstk == mstk) lx->macstk = NULL;
+ else --lx->macstk;
+ if (!all) break;
+ } while ((stk = lx->macstk) && stk->idx >= stk->rl.n && !stk->stop);
+}
+
+
+static inline const struct token *
+stkgetrl(struct macrostack *s)
+{
+ if (s->macid < 0) return s->rl.p;
+ return (s->dyn ? mdyntoksbuf.p : mtoksbuf.p) + s->rl.off;
+}
+
+static void expandfnmacro(struct lexer *lx, struct span *span, internstr mname, struct macro *mac);
+
+static enum expandres { EXPNONE, EXPINL, EXPSTACK }
+tryexpand(struct lexer *lx, struct token *tk)
+{
+ struct span span = tk->span;
+ struct macro *mac = NULL;
+ internstr mname = tk->name;
+
+ if (tk->t != TKIDENT || tk->blue || !(mac = findmac(mname)))
+ return EXPNONE;
+
+ /* prevent infinite recursion */
+ for (struct macrostack *l = lx->macstk; l && l+1 > mstk; --l) {
+ if (l->macid == mac->id) {
+ tk->blue = 1;
+ return EXPNONE;
+ }
+ }
+
+ struct macrostack *stkprev = lx->macstk;
+ if (mac->special && !mac->fnlike) {
+ mac->handler(lx, tk);
+ return EXPINL;
+ } else if (mac->fnlike) {
+ /* look if there is a '(' token ahead, expand if so */
+ struct macrostack *s = lx->macstk;
+ if (s && s->idx >= s->rl.n && !s->stop) {
+ popmac(lx, 1);
+ s = lx->macstk;
+ }
+ if (!s) { /* top-level context: looking ahead in file data */
+ struct token tk;
+ int t;
+ for (;;) { /* skip whitespace and comments */
+ if (aisspace(t = peek(lx, 0))) next(lx);
+ else if (t == '/') {
+ int idx = lx->chridx;
+ switch (peek(lx, 1)) {
+ case '/':
+ while (!lx->eof && next(lx) != '\n') ;
+ continue;
+ case '*':
+ next(lx), next(lx);
+ while (peek(lx, 0) != '*' || peek(lx, 1) != '/') {
+ if (lx->eof) {
+ struct span span = {{ idx, lx->chridx - idx, lx->fileid }};
+ lxfatal(lx, &span, "unterminated comment");
+ }
+ next(lx);
+ }
+ next(lx), next(lx);
+ continue;
+ }
+ break;
+ } else break;
+ }
+ if (t != '(') return 0;
+ lex0(lx, &tk, 0);
+ } else { /* expansion context: look ahead in macro stack */
+ if (s->idx >= s->rl.n || stkgetrl(s)[s->idx].t != '(') return 0;
+ ++s->idx;
+ }
+ expandfnmacro(lx, &span, mname, mac);
+ } else if (mac->predef && mac->single) {
+ struct span span = tk->span;
+ *tk = *mac->single;
+ tk->span = span;
+ return EXPINL;
+ } else if (mac->rl.n) {
+ pushmacstk(lx, &span, &(struct macrostack){
+ .rl = { .off = mac->rl.off, .n = mac->rl.n },
+ .macid = mac->id,
+ .space = tk->space,
+ });
+ }
+ if (lx->macstk != stkprev) {
+ lx->macstk->space = tk->space;
+ }
+ return EXPSTACK;
+}
+
+static bool
+advancemacstk(struct lexer *lx, struct token *tk)
+{
+ struct macrostack *s = lx->macstk;
+ assert(s != NULL);
+ if (s->idx >= s->rl.n) {
+ if (s->stop) {
+ tk->t = TKEOF;
+ return 1;
+ }
+ popmac(lx, 1);
+ return 0;
+ }
+ *tk = stkgetrl(s)[s->idx];
+ if (s->idx == 0) {
+ /* the first token of the replaced expansion gets its space from the
+ * context in which it is expanded */
+ tk->space = s->space;
+ }
+ ++s->idx;
+ assert(tk->t && tk->t != TKEOF);
+ tk->span.ex = s->exspan;
+ return tryexpand(lx, tk) != EXPSTACK;
+}
+
+static void
+expandfnmacro(struct lexer *lx, struct span *span, internstr mname, struct macro *mac)
+{
+ struct token _argsbuf[30];
+ vec_of(struct token) argsbuf = VINIT(_argsbuf, countof(_argsbuf)); /* buffer for argument tokens */
+ struct span excessspan;
+ int cur, len, i, bal, narg;
+ struct token tk;
+ bool toomany = 0;
+ struct argtks {
+ int idx, n; /* slices of argsbuf */
+ int idx2, n2;
+ ushort nfirstx, /* for concatenation to work properly with expanded arguments, */
+ nlastx; /* length of expanded first and last tokens of the unexpanded argument */
+ } _args0[4],
+ *args = mac->nparam < countof(_args0) ? _args0 : alloc(lx->tmparena, sizeof *args * mac->nparam, 0);
+
+ cur = i = bal = len = narg = 0;
+ for (struct macrostack *s = lx->macstk;;) {
+ if (!s) {
+ bool nl = 0;
+ for (;; nl = 1) {
+ lex0(lx, &tk, 0);
+ if (tk.t != '\n') break;
+ }
+ tk.space |= nl;
+ }
+ else {
+ tk = s->idx < s->rl.n ? stkgetrl(s)[s->idx++] : (struct token){TKEOF};
+ }
+ if (((tk.t == ')' && bal == 0) || tk.t == TKEOF)) break;
+ if (tk.t == ',' && bal == 0) {
+ ++narg;
+ if (i == mac->nparam-1 && !mac->variadic) {
+ excessspan = tk.span;
+ toomany = 1;
+ } else if (i < mac->nparam - mac->variadic) {
+ assert(i < MAXMACROARGS);
+ args[i].idx = cur;
+ args[i].n = len;
+ cur = argsbuf.n;
+ len = 0;
+ ++i;
+ } else if (mac->variadic) {
+ vpush(&argsbuf, tk);
+ ++len;
+ }
+ } else if (!toomany) {
+ if (tk.t == '(') ++bal;
+ else if (tk.t == ')') --bal;
+ vpush(&argsbuf, tk);
+ ++len;
+ }
+ }
+
+ if (tk.t == TKEOF) {
+ joinspan(&span->ex, tk.span.ex);
+ lxfatal(lx, span, "unterminated function-like macro invocation");
+ } else if (i < mac->nparam) {
+ ++narg;
+ args[i].idx = cur;
+ args[i].n = len;
+ cur = argsbuf.n;
+ len = 0;
+ ++i;
+ }
+ joinspan(&span->ex, tk.span.ex);
+ int expargs0 = argsbuf.n;
+ for (int i = 0; i < mac->nparam; ++i) {
+ struct argtks *arg = &args[i];
+ if (i >= narg) {
+ memset(arg, 0, sizeof *arg);
+ } else if (!mac->param || (mac->param[i] && arg->n > 0)) {
+ /* expand args used in the macro body */
+ pushmacstk(lx, &tk.span, &(struct macrostack) {
+ .rl = { .p = argsbuf.p + arg->idx, .n = arg->n },
+ .macid = -1,
+ .stop = 1,
+ });
+ struct macrostack *l = lx->macstk;
+ arg->idx2 = argsbuf.n;
+ arg->nfirstx = arg->nlastx = 1;
+ int ilastx = -1;
+ for (bool pad = 0;;) {
+ struct macrostack *sprev = lx->macstk;
+ if (!advancemacstk(lx, &tk)) {
+ pad |= tk.space && lx->macstk == sprev; /* preserve whitespace empty macro */
+ if (lx->macstk == l && l->idx == 1)
+ arg->nfirstx = argsbuf.n - arg->idx2;
+ if (lx->macstk == l+1 && lx->macstk->idx == 0 && l->idx == l->rl.n)
+ ilastx = argsbuf.n - arg->idx2;
+ continue;
+ }
+ if (tk.t == TKEOF) break;
+ size_t off = l->rl.p - argsbuf.p;
+ tk.space |= pad;
+ vpush(&argsbuf, tk);
+ l->rl.p = argsbuf.p + off;
+ pad = 0;
+ }
+ arg->n2 = argsbuf.n - arg->idx2;
+ arg->nlastx = ilastx < 0 ? 1 : args->n2 - ilastx;
+ assert(lx->macstk == l);
+ popmac(lx, 0);
+ } else {
+ memset(arg, 0, sizeof *arg);
+ }
+ }
+ if (narg < mac->nparam - mac->variadic) {
+ warn(span, "macro `%s' passed %d arguments, but takes %d", mname, narg, mac->nparam);
+ } else if (toomany) {
+ joinspan(&excessspan.ex, tk.span.ex);
+ warn(&excessspan, "macro `%s' passed %d arguments, but takes just %d", mname, narg, mac->nparam);
+ }
+ if (mac->special) {
+ mac->handlerfn(lx, &tk, argsbuf.p+expargs0, argsbuf.n-expargs0);
+ vpush(&mdyntoksbuf, tk);
+ pushmacstk(lx, span, &(struct macrostack){
+ .rl = { .off = mdyntoksbuf.n-1, .n = 1 },
+ .dyn = 1,
+ .macid = mac->id,
+ });
+ } else if (mac->nparam > 0) { /* make new rlist with args replaced */
+ bool vaoptskip = 0, spacepad = 0;
+ int vaoptbal = 0;
+ uint off = mdyntoksbuf.n;
+ for (int i = 0; i < mac->rl.n; ++i) {
+ struct argtks *arg;
+ const struct token *tki = &mtoksbuf.p[mac->rl.off+i];
+ if (vaoptskip) {
+ assert(vaoptbal > 0);
+ if (tki->t == '(') ++vaoptbal;
+ else if (tki->t == ')') {
+ if (--vaoptbal == 0) vaoptskip = 0;
+ }
+ continue;
+ }
+ if (tki->t == TKPPCAT && i > 0 && i < mac->rl.n-1) { /* concatenation */
+ const struct token *lhs = tki-1,
+ *rhs = tki+1;
+ bool space = lhs->space | spacepad;
+ if (lhs->t == ',' && mac->variadic
+ && rhs->t == TKPPMACARG && rhs->argidx == mac->nparam-1) {
+ /* handle GNU extension: ', ## __VA_ARGS__' */
+ arg = &args[rhs->argidx];
+ if (narg < mac->nparam) { /* no vaargs -> skip comma */
+ assert(arg->n == 0);
+ --mdyntoksbuf.n;
+ } else { /* otherwise put comma and substitute vaargs */
+ vpushn(&mdyntoksbuf, argsbuf.p+arg->idx2, arg->n2);
+ mdyntoksbuf.p[mdyntoksbuf.n - arg->n2].space |= rhs->space | tk.space;
+ }
+ ++i; /* we already handled rhs (__VA_ARGS__) */
+ continue;
+ }
+ if (i > 2 && tki[-2].t == TKPPCAT) {
+ /* handles chained concatenations: xyz ## arg ## c
+ * lhs ^ rhs */
+ lhs = (off < mdyntoksbuf.n) ? &mdyntoksbuf.p[--mdyntoksbuf.n] : NULL;
+ } else if (lhs->t == TKPPMACARG) {
+ arg = &args[lhs->argidx];
+ lhs = arg->n ? &argsbuf.p[arg->idx + arg->n-1] : NULL;
+ if (lhs && arg->n > 1) space |= lhs->space;
+ } else {
+ --mdyntoksbuf.n;
+ }
+ if (rhs->t == TKPPMACARG) {
+ arg = &args[rhs->argidx];
+ rhs = arg->n ? &argsbuf.p[arg->idx] : NULL;
+ } else {
+ ++i;
+ }
+ if (!lhs && !rhs) continue;
+ spacepad = 0;
+ if (!lhs) vpush(&mdyntoksbuf, *rhs);
+ else if (!rhs) vpush(&mdyntoksbuf, *lhs);
+ else {
+ struct token new;
+ if (tokpaste(lx, &new, lhs, rhs)) {
+ new.span.sl = tki->span.sl;
+ }
+ vpush(&mdyntoksbuf, new);
+ }
+ mdyntoksbuf.p[mdyntoksbuf.n-1].space = space;
+ } else if (tki->t != TKPPMACARG && tki->t != TKPPMACSTR) { /* regular token */
+ if (tki->t == TKIDENT && mac->variadic) {
+ /* handle GNUC __VA_OPT__(...) */
+ static internstr istr_vaopt;
+ if (!istr_vaopt) istr_vaopt = intern("__VA_OPT__");
+ if (tki->name == istr_vaopt && i+2 < mac->rl.n && tki[1].t == '(') {
+ vaoptbal = 1;
+ vaoptskip = args[mac->nparam-1].n == 0;
+ ++i; /* skip open paren */
+ continue;
+ }
+ }
+ if (vaoptbal) {
+ if (tki->t == '(') ++vaoptbal;
+ else if (tki->t == ')') {
+ /* skip closing paren of __VA_OPT__ invocation */
+ if (--vaoptbal == 0) continue;
+ }
+ }
+ vpush(&mdyntoksbuf, *tki);
+ mdyntoksbuf.p[mdyntoksbuf.n-1].space |= spacepad;
+ spacepad = 0;
+ } else if (tki->t == TKPPMACARG) {
+ arg = &args[tki->argidx];
+ if (arg->n == 0) {
+ spacepad = 1;
+ continue;
+ }
+ struct token *rl = argsbuf.p + arg->idx2;
+ int n = arg->n2;
+ bool skipfirst = 0;
+ if (i > 0 && tki[-1].t == TKPPCAT) {
+ /* skip first unexpanded token, was pasted */
+ rl += arg->nfirstx;
+ n -= arg->nfirstx;
+ skipfirst = 1;
+ }
+ if (i < mac->rl.n-2 && tki[1].t == TKPPCAT) {
+ /* skip last unexpanded token, will be pasted */
+ n -= arg->nlastx;
+ }
+ if (n > 0) {
+ vpushn(&mdyntoksbuf, rl, n);
+ if (!skipfirst)
+ /* the first token of the expanded body gets its space from the replacement list */
+ mdyntoksbuf.p[mdyntoksbuf.n - n].space = tki->space | spacepad;
+ }
+ spacepad = 0;
+ } else { /* PPMACSTR */
+ char tmp[200];
+ struct wbuf buf = MEMBUF(tmp, sizeof tmp);
+ int n = 0;
+
+ arg = &args[tki->argidx];
+ // XXX this is wrong bc the string literal produced should be re-parsed later
+ // i.e. stringifying the token sequence '\n' should ultimately produce a
+ // string with an actual newline, not {'\\','n'}
+ Redo:
+ for (int i = 0; i < arg->n; ++i) {
+ struct token *tk = &argsbuf.p[arg->idx + i];
+ if (i > 0 && tk->space)
+ n += bfmt(&buf, " ");
+ n += bfmt(&buf, "%tk", tk);
+ }
+ ioputc(&buf, 0);
+ if (buf.err) {
+ struct wbuf new = MEMBUF(alloc(lx->tmparena, n+1, 1), n+1);
+ assert(buf.buf == tmp);
+ memcpy(&buf, &new, sizeof buf);
+ goto Redo;
+ }
+ vpush(&mdyntoksbuf, ((struct token) {
+ .t = TKSTRLIT,
+ .wide = 0,
+ .space = tki->space | spacepad,
+ .s = buf.buf != tmp ? buf.buf : alloccopy(lx->tmparena, buf.buf, buf.len, 1),
+ .len = buf.len-1,
+ }));
+ spacepad = 0;
+ }
+ }
+ uint n = mdyntoksbuf.n - off;
+
+ if (n) {
+ pushmacstk(lx, span, &(struct macrostack){
+ .rl = { .off = off, .n = n },
+ .macid = mac->id,
+ .dyn = 1,
+ });
+ }
+ } else if (mac->rl.n) {
+ pushmacstk(lx, span, &(struct macrostack){
+ .rl = { .off = mac->rl.off, .n = mac->rl.n },
+ .macid = mac->id,
+ });
+ }
+ vfree(&argsbuf);
+}
+
+static struct token epeektk;
+static int
+elex(struct lexer *lx, struct token *tk)
+{
+ assert(tk);
+ if (epeektk.t) {
+ int tt = epeektk.t;
+ if (tk) *tk = epeektk;
+ epeektk.t = 0;
+ return tt;
+ }
+ if (lx->macstk) {
+ if (!advancemacstk(lx, tk))
+ return elex(lx, tk);
+ return tk->t;
+ }
+
+ lex0(lx, tk, 0);
+ return tk->t;
+}
+
+static int
+epeek(struct lexer *lx, struct token *tk)
+{
+ if (!epeektk.t) elex(lx, &epeektk);
+ if (tk) *tk = epeektk;
+ return epeektk.t;
+}
+
+static int
+tkprec(int tt)
+{
+ static const char tab[] = {
+ ['*'] = 12, ['/'] = 12, ['%'] = 12,
+ ['+'] = 11, ['-'] = 11,
+ [TKSHL] = 10, [TKSHR] = 10,
+ ['<'] = 9, ['>'] = 9, [TKLTE] = 9, [TKGTE] = 9,
+ [TKEQU] = 8, [TKNEQ] = 8,
+ ['&'] = 7,
+ ['^'] = 6,
+ ['|'] = 5,
+ [TKLOGAND] = 4,
+ [TKLOGIOR] = 3,
+ ['?'] = 2,
+ };
+ if ((uint)tt < countof(tab))
+ return tab[tt] - 1;
+ return -1;
+}
+
+static vlong
+expr(struct lexer *lx, bool *pu, int prec, bool ignore)
+{
+ struct token tk;
+ enum typetag ty;
+ char unops[16];
+ int nunop = 0;
+ vlong x, y;
+ bool xu = 0, yu; /* x unsigned?; y unsigned? */
+
+Unary:
+ elex(lx, &tk);
+Switch:
+ switch (tk.t) {
+ case '-': case '~': case '!':
+ unops[nunop++] = tk.t;
+ if (nunop >= countof(unops)) {
+ x = expr(lx, &xu, 999, ignore);
+ break;
+ }
+ /* fallthru */
+ case '+': goto Unary;
+ case '(':
+ x = expr(lx, &xu, 1, ignore);
+ if (elex(lx, &tk) != ')') {
+ error(&tk.span, "expected ')'");
+ goto Err;
+ }
+ break;
+ case TKNUMLIT:
+ case TKCHRLIT:
+ ty = parsenumlit((uvlong *)&x, NULL, &tk, 1);
+ if (!ty) {
+ error(&tk.span, "bad number literal");
+ goto Err;
+ } else if (isfltt(ty)) {
+ error(&tk.span, "float literal in preprocessor expresion");
+ goto Err;
+ }
+ xu = isunsignedt(ty);
+ break;
+ default:
+ if (tk.t == TKIDENT) {
+ xu = 0;
+ if (!strcmp(tk.s, "defined")) {
+ /* 'defined' ppident */
+ bool paren = 0;
+ lex0(lx, &tk, 0);
+ if ((paren = tk.t == '(')) lex0(lx, &tk, 0);
+ if (!isppident(tk)) {
+ error(&tk.span, "expected macro name");
+ goto Err;
+ }
+ if (paren && lex0(lx, &tk, 0) != ')') {
+ error(&tk.span, "expected `)'");
+ goto Err;
+ }
+ x = findmac(tk.name) != NULL;
+ } else {
+ switch (tryexpand(lx, &tk)) {
+ case EXPSTACK: goto Unary;
+ case EXPINL: goto Switch;
+ case EXPNONE: x = 0; break; /* non defined pp name -> 0 */
+ }
+ }
+ break;
+ }
+ error(&tk.span, "expected preprocessor integer expression (near %'tk)", &tk);
+ goto Err;
+ }
+
+ while (nunop > 0) switch (unops[--nunop]) {
+ case '-': x = -(uvlong)x; break;
+ case '~': x = ~x; break;
+ case '!': x = !x; break;
+ default: assert(0);
+ }
+
+ for (int opprec; (opprec = tkprec(epeek(lx, &tk))) >= prec;) {
+ elex(lx, &tk);
+ if (tk.t == TKLOGAND) {
+ x = !!x & !!expr(lx, &yu, opprec+1, ignore || !x);
+ xu = 0;
+ } else if (tk.t == TKLOGIOR) {
+ x = !!x | !!expr(lx, &yu, opprec+1, ignore || x);
+ xu = 0;
+ } else if (tk.t == '?') {
+ struct span span = tk.span;
+ vlong m = expr(lx, &xu, 1, ignore || !x);
+ if (elex(lx, &tk) != ':') {
+ error(&tk.span, "expected ':'");
+ note(&span, "to match conditional expression here");
+ goto Err;
+ }
+ y = expr(lx, &yu, 1, ignore || x);
+ x = x ? m : y;
+ xu |= yu;
+ } else {
+ y = expr(lx, &yu, opprec + 1, ignore);
+ bool u = xu | yu;
+ switch ((int) tk.t) {
+ case '+': x += (uvlong) y; break;
+ case '-': x -= (uvlong) y; break;
+ case '*': x = u ? (uvlong) x * y : x * y; break;
+ case '&': x &= y; break;
+ case '^': x ^= y; break;
+ case '|': x |= y; break;
+ case '/': if (y) x = u ? (uvlong) x / y : x / y;
+ else if (ignore) x = 0;
+ else goto Div0;
+ break;
+ case '%': if (y) x = u ? (uvlong) x % y : x % y;
+ else if (ignore) x = 0;
+ else Div0: error(&tk.span, "division by zero");
+ break;
+ case TKSHL: if ((uvlong)y < 64) x <<= y;
+ else if (ignore) x = 0;
+ else goto BadShift;
+ break;
+ u = xu;
+ case TKSHR: if ((uvlong)y < 64) x = u ? (uvlong) x >> y : x >> y;
+ else if (ignore) x = 0;
+ else BadShift: error(&tk.span, "bad shift by %ld", y);
+ u = xu;
+ break;
+ case '<': x = u ? (uvlong) x < y : x < y; u = 0; break;
+ case '>': x = u ? (uvlong) x > y : x > y; u = 0; break;
+ case TKLTE: x = u ? (uvlong) x <= y : x <= y; u = 0; break;
+ case TKGTE: x = u ? (uvlong) x >= y : x >= y; u = 0; break;
+ case TKEQU: x = x == y; u = 0; break;
+ case TKNEQ: x = x != y; u = 0; break;
+ default: assert(0);
+ }
+ xu = u;
+ }
+ }
+ if (!prec) { /* not a sub expr */
+ if (elex(lx, &tk) != '\n' && tk.t != TKEOF) {
+ error(&tk.span, "extra tokens after preprocessor expression");
+ ppskipline(lx);
+ }
+ }
+ if (pu) *pu = xu;
+ return x;
+
+Err:
+ ppskipline(lx);
+ if (pu) *pu = xu;
+ return 0;
+}
+
+enum {
+ PPCNDFALSE, /* the condition was zero, skip until #else/#elif */
+ PPCNDTRUE, /* the condition was non-zero, emit until #else/#elif */
+ PPCNDTAKEN /* some branch was already taken, skip until #else */
+};
+static struct ppcnd {
+ struct span0 ifspan;
+ int filedepth;
+ uchar cnd;
+ bool elsep;
+} ppcndstk[32];
+static int nppcnd;
+
+static int includedepth;
+
+static void
+ppif(struct lexer *lx, const struct span *span)
+{
+ vlong v = expr(lx, NULL, 0, 0);
+ assert(nppcnd < countof(ppcndstk) && "too many nested #if");
+ ppcndstk[nppcnd].ifspan = span->sl;
+ ppcndstk[nppcnd].filedepth = includedepth;
+ ppcndstk[nppcnd].cnd = v ? PPCNDTRUE : PPCNDFALSE;
+ ppcndstk[nppcnd++].elsep = 0;
+}
+
+static void
+ppifxdef(struct lexer *lx, bool defp, const struct span *span)
+{
+ struct token tk;
+
+ lex0(lx, &tk, 0);
+ if (tk.t != TKIDENT) {
+ error(&tk.span, "macro name missing");
+ ppskipline(lx);
+ return;
+ }
+ expecteol(lx, defp ? "ifdef" : "ifndef");
+ if (!defp && lx->firstdirective) lx->inclguard = tk.name;
+ assert(nppcnd < countof(ppcndstk) && "too many nested #if");
+ ppcndstk[nppcnd].ifspan = span->sl;
+ ppcndstk[nppcnd].filedepth = includedepth;
+ ppcndstk[nppcnd].cnd = (findmac(tk.name) == NULL) ^ defp ? PPCNDTRUE : PPCNDFALSE;
+ ppcndstk[nppcnd++].elsep = 0;
+}
+
+static void
+ppelif(struct lexer *lx, const struct span *span)
+{
+ vlong v;
+ struct ppcnd *cnd;
+
+ if (!nppcnd) {
+ error(span, "#elif without matching #if");
+ ppif(lx, span);
+ return;
+ }
+ v = expr(lx, NULL, 0, 0);
+ cnd = &ppcndstk[nppcnd-1];
+ if (cnd->elsep) {
+ error(span, "#elif after #else");
+ return;
+ }
+ switch (cnd->cnd) {
+ case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break;
+ case PPCNDFALSE: cnd->cnd = v ? PPCNDTRUE : PPCNDFALSE; break;
+ }
+}
+static void
+ppelifxdef(struct lexer *lx, bool defp, const struct span *span)
+{
+ struct token tk;
+ struct ppcnd *cnd;
+
+ if (!nppcnd) {
+ error(span, "#elif%sdef without matching #if", &"n"[defp]);
+ ppif(lx, span);
+ return;
+ }
+ cnd = &ppcndstk[nppcnd-1];
+ if (cnd->elsep) {
+ error(span, "#elif%sdef after #else", &"n"[defp]);
+ return;
+ }
+ lex0(lx, &tk, 0);
+ if (tk.t != TKIDENT) {
+ error(&tk.span, "macro name missing");
+ ppskipline(lx);
+ return;
+ }
+ expecteol(lx, defp ? "elifdef" : "elifndef");
+ switch (cnd->cnd) {
+ case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break;
+ case PPCNDFALSE: cnd->cnd = (findmac(tk.name) == NULL) ^ defp ? PPCNDTRUE : PPCNDFALSE; break;
+ case PPCNDTAKEN: assert(0);
+ }
+}
+
+static void
+ppendif(struct lexer *lx, const struct span *span)
+{
+ expecteol(lx, "endif");
+ if (!nppcnd) {
+ error(span, "#endif without matching #if");
+ return;
+ }
+ --nppcnd;
+}
+
+static void
+ppelse(struct lexer *lx, const struct span *span)
+{
+ struct ppcnd *cnd;
+ expecteol(lx, "else");
+ if (!nppcnd) {
+ error(span, "#else without matching #if");
+ return;
+ }
+ cnd = &ppcndstk[nppcnd-1];
+ if (cnd->elsep)
+ error(span, "#else after #else");
+ switch (cnd->cnd) {
+ case PPCNDFALSE: cnd->cnd = PPCNDTRUE; break;
+ case PPCNDTRUE: cnd->cnd = PPCNDTAKEN; break;
+ }
+ cnd->elsep = 1;
+}
+
+enum { MAXINCLUDE = 200 };
+static bool
+tryincludepath(struct lexer *lx, const struct span *span, char *path)
+{
+ struct lexer new;
+ const char *err;
+ switch (initlexer(&new, &err, path)) {
+ default: assert(0);
+ case LXERR: return 0;
+ case LXFILESEEN:
+ xbfree(path);
+ /* fallthru */
+ case LXOK:
+ new.save = xmalloc(sizeof *new.save);
+ lx->inclnerror = nerror;
+ lx->inclnwarn = nwarn;
+ memcpy(new.save, lx, sizeof *lx);
+ *lx = new;
+
+ if (++includedepth == MAXINCLUDE)
+ lxfatal(lx, span, "Maximum nested include depth of %d reached", includedepth);
+ break;
+ case LXFILESKIP:
+ xbfree(path);
+ break;
+ }
+ return 1;
+}
+
+static bool
+doinclude(struct lexer *lx, const struct span *span, bool quote, const char *str, size_t slen)
+{
+ char *path = NULL;
+ const char *base, *end;
+ if (quote) {
+ if (str[0] == '/') {
+ /* try absolute path */
+ xbgrow(&path, slen + 1);
+ memcpy(path, str, slen);
+ path[slen] = 0;
+ if (tryincludepath(lx, span, path)) return 1;
+ goto NotFound;
+ }
+
+ /* try relative to current file's directory */
+ base = getfilename(lx->fileid, 0);
+ for (end = base; *end != 0; ++end) {}
+ for (--end; *end != '/' && end != base; --end) {}
+ if (*end == '/') ++end;
+ xbgrow(&path, end - base + slen + 1);
+ memcpy(path, base, end - base);
+ memcpy(path + (end - base), str, slen);
+ path[end - base + slen] = 0;
+ if (tryincludepath(lx, span, path)) return 1;
+ }
+ /* try system paths. order:
+ * 1. -iquote
+ * 2. -I
+ * 3. -isystem
+ * 4. embedded include files
+ * 5. standard system includes
+ * 6. -idirafter
+ */
+ for (int i = quote ? CINCL_iquote : CINCL_I; i < countof(cinclpaths); ++i) {
+ for (struct inclpath *p = cinclpaths[i].list; p; p = p->next) {
+ if (i == CINCLsys) {
+ /* try embedded files pseudo-path */
+ xbgrow(&path, slen + 3);
+ path[0] = '@', path[1] = ':';
+ memcpy(path+2, str, slen);
+ path[slen+2] = 0;
+ if (tryincludepath(lx, span, path)) return 1;
+ }
+ int ndir = strlen(p->path);
+ xbgrow(&path, ndir + slen + 2);
+ memcpy(path, p->path, ndir);
+ path[ndir++] = '/';
+ memcpy(path + ndir, str, slen);
+ path[ndir + slen] = 0;
+ if (tryincludepath(lx, span, path)) return 1;
+ }
+ }
+NotFound:
+ error(span, "file not found: %'S", str, slen);
+ xbfree(path);
+ return 0;
+}
+
+static bool
+ppinclude(struct lexer *lx, const struct span *span0)
+{
+ struct token tk;
+ struct span span = *span0;
+
+ if (in_range(lex0(lx, &tk, 1), TKPPHDRH, TKPPHDRQ)) {
+ expecteol(lx, "include");
+ joinspan(&span.ex, tk.span.ex);
+ return doinclude(lx, &span, tk.t == TKPPHDRQ, tk.s, tk.len);
+ } else if (tk.t == '\n' || tk.t == TKEOF) {
+ goto BadSyntax;
+ } else {
+ /* '#include pp-tokens'
+ * gather and expand pp-tokens */
+ struct token tksbuf[8];
+ vec_of(struct token) tks = VINIT(tksbuf, countof(tksbuf));
+ for (;;) {
+ if (!lx->macstk) {
+ if (tryexpand(lx, &tk) == EXPSTACK) continue;
+ vpush(&tks, tk);
+ } else if (advancemacstk(lx, &tk)) {
+ vpush(&tks, tk);
+ continue;
+ }
+ if (lex0(lx, &tk, 0) == '\n' || tk.t == TKEOF) break;
+ }
+ if (tks.n >= 1 && tks.p[0].t == TKSTRLIT) { /* "header.h" */
+ if (tks.n > 1)
+ (ccopt.pedant ? error : warn)(&tks.p[1].span, "extra tokens after #include");
+ joinspan(&span.ex, tks.p[0].span.ex);
+ return doinclude(lx, &span, 1, tks.p[0].s, tks.p[0].len);
+ } else if (tks.n > 2 && tks.p[0].t == '<' && tks.p[tks.n-1].t == '>') { /* <header.h> */
+ /* this is multiple tokens, concatenate them together */
+ char buf[4096];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ for (int i = 1; i < tks.n-1; ++i) {
+ struct token *tk = &tks.p[i];
+ bfmt(&wbuf, &" %tk"[!tk->space], tk);
+ }
+ joinspan(&span.ex, tks.p[tks.n-1].span.ex);
+ if (wbuf.err) error(&span, "path too long");
+ else {
+ return doinclude(lx, &span, 0, buf, wbuf.len);
+ }
+ } else {
+ BadSyntax:
+ error(&tk.span, "expected \"header\" or <header>");
+ ppskipline(lx);
+ }
+ vfree(&tks);
+ }
+ return 1;
+}
+
+static void
+ppline(struct lexer *lx, struct token *tk0)
+{
+ struct token tk, tks[2];
+ int ntk = 0;
+ struct span span = tk0->span;
+ bool ext = 0;
+ if (tk0->t == TKNUMLIT) { /* handles GNU-style post preprocessing directive '# n ...' */
+ tks[ntk++] = *tk0;
+ ext = 1;
+ }
+ while (ntk < 2) {
+ if (lx->macstk && advancemacstk(lx, &tk)) {
+ tks[ntk++] = tk;
+ if (lx->macstk->idx >= lx->macstk->rl.n) popmac(lx, 1);
+ } else if (!lx->macstk && (lex0(lx, &tk, 0) == '\n' || tk.t == TKEOF)) {
+ break;
+ } else if (tk.t == TKIDENT && tryexpand(lx, &tk) == EXPSTACK) {
+ continue;
+ } else {
+ tks[ntk++] = tk;
+ }
+ }
+ uvlong lineno = 0;
+ char *file = NULL;
+ if (ntk > 0 && tks[0].t == TKNUMLIT) {
+ if (!parsenumlit(&lineno, NULL, &tks[0], 1) || (lineno == 0 && !ext))
+ goto BadNum;
+ if (lineno >= 1<<(32-SPANFILEBITS)) {
+ warn(&tks[0].span, "ignoring #line number that is too big");
+ lineno = 0;
+ goto Err;
+ }
+ } else {
+ BadNum:
+ error(ntk ? &tks[0].span : &span, "#line requires a positive integer argument");
+ Err:
+ if (lx->macstk || (tk.t != '\n' && tk.t != TKEOF)) ppskipline(lx);
+ return;
+ }
+ if (ntk > 1) {
+ if (tks[1].t == TKSTRLIT && !tks[1].wide) {
+ file = alloc(&globarena, tks[1].len+1, 0);
+ memcpy(file, tks[1].s, tks[1].len);
+ file[tks[1].len] = 0;
+ } else {
+ error(&tks[1].span, "invalid filename for #line directive");
+ }
+ }
+ if (lineno) setfileline(lx->fileid, lx->chridx, lineno, file);
+ if (lx->macstk) {
+ span.sl.off = span.ex.off = lx->chridx;
+ span.sl.len = span.ex.len = 1;
+ ppskipline(lx);
+ if (!ext)
+ (ccopt.pedant ? error : warn)(&span, "extra tokens after #line");
+ } else if (tk.t != '\n' && tk.t != TKEOF) {
+ if (ext) ppskipline(lx);
+ else expecteol(lx, "line");
+ }
+}
+
+static void
+pppragma(struct lexer *lx, const struct span *span0)
+{
+ struct token tk;
+ struct span span = *span0;
+ if (lex0(lx, &tk, 0) == TKIDENT && !strcmp(tk.s, "once")) {
+ markfileonce(lx->fileid, NULL);
+ } else {
+ joinspan(&span.ex, tk.span.ex);
+ warn(&span, "unknown pragma ignored");
+ ppskipline(lx);
+ return;
+ }
+ expecteol(lx, "pragma");
+}
+
+static void
+ppdiag(struct lexer *lx, const struct span *span0, bool err)
+{
+ const uchar *p = getfile(lx->fileid)->p;
+ uint off = lx->chridx, end;
+ ppskipline(lx);
+ end = lx->chridx;
+ while (off < end && aisspace(p[off])) ++off;
+ (err ? error : warn)(span0, "%S", p + off, end - off);
+}
+
+enum directive {
+ PPXXX,
+ /* !sorted */
+ PPDEFINE,
+ PPELIF,
+ PPELIFDEF,
+ PPELIFNDEF,
+ PPELSE,
+ PPENDIF,
+ PPERROR,
+ PPIF,
+ PPIFDEF,
+ PPIFNDEF,
+ PPINCLUDE,
+ PPLINE,
+ PPPRAGMA,
+ PPUNDEF,
+ PPWARNING,
+};
+
+static enum directive
+findppcmd(const struct token *tk)
+{
+ static const char *tab[] = {
+ /* !sorted */
+ "define",
+ "elif",
+ "elifdef",
+ "elifndef",
+ "else",
+ "endif",
+ "error",
+ "if",
+ "ifdef",
+ "ifndef",
+ "include",
+ "line",
+ "pragma",
+ "undef",
+ "warning",
+ };
+ int l = 0, h = countof(tab) - 1, i, cmp;
+ const char *s = tk->s;
+
+ if (tk->t == TKWif) return PPIF;
+ if (tk->t == TKWelse) return PPELSE;
+ /* binary search over sorted array */
+ while (l <= h) {
+ i = (l + h) / 2;
+ cmp = strcmp(tab[i], s);
+ if (cmp < 0) l = i + 1;
+ else if (cmp > 0) h = i - 1;
+ else return i + 1;
+ }
+ return PPXXX;
+}
+
+static void
+identkeyword(struct token *tk)
+{
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-braces"
+#endif
+ static const struct {
+ const char *s;
+ struct kw { uchar t, cstd : 4, ext : 1; } kw;
+ const char *alias[2];
+ } kwtab[] = {
+#define _(kw, cstd, ...) { #kw, {TKW##kw, cstd}, __VA_ARGS__ },
+#include "keywords.def"
+#undef _
+ };
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+ static pmap_of(struct kw) kwmap;
+ if (!kwmap.v) {
+ pmap_init(&kwmap, 128);
+ for (int i = 0; i < countof(kwtab); ++i) {
+ struct kw kw = kwtab[i].kw;
+ /* allow future keywords but only if they begin with _ */
+ if (kw.cstd <= ccopt.cstd || kwtab[i].s[0] == '_') {
+ kw.ext = kw.cstd > ccopt.cstd;
+ pmap_set(&kwmap, intern(kwtab[i].s), kw);
+ }
+ for (const char *const *palias = kwtab[i].alias, *const *end = palias+2;
+ palias != end && *palias; ++palias)
+ {
+ pmap_set(&kwmap, intern(*palias), kw);
+ }
+ }
+ }
+ struct kw *kw = pmap_get(&kwmap, tk->name);
+ if (kw) {
+ tk->t = kw->t;
+ tk->extwarn = kw->ext;
+ }
+}
+
+int
+lex(struct lexer *lx, struct token *tk_)
+{
+ struct token tkx[1], *tk;
+ int t;
+
+Begin:
+ assert(tk_ != &lx->peektok);
+ tk = tk_ ? tk_ : tkx;
+ if (lx->peektok.t) {
+ *tk = lx->peektok;
+ memset(&lx->peektok, 0, sizeof lx->peektok);
+ return tk->t;
+ }
+
+ if (lx->macstk) {
+ if (!advancemacstk(lx, tk))
+ goto Begin;
+ if (tk->t == TKIDENT) identkeyword(tk);
+ return tk->t;
+ }
+ bool linebegin = 1,
+ skip = nppcnd ? ppcndstk[nppcnd-1].cnd != PPCNDTRUE : 0,
+ inclerror = 0; /* set when #include header file not found: process other directives then abort */
+ enum directive lastcmd = 0;
+ for (;;) {
+ while ((t = lex0(lx, tk, 0)) == '\n') linebegin = 1;
+ if (t == '#' && linebegin) {
+ if (lex0(lx, tk, 0) == '\n') { }
+ else if (tk->t == TKNUMLIT || tk->t == TKIDENT) {
+ lastcmd = tk->t == TKNUMLIT ? PPLINE : findppcmd(tk);
+ if (nppcnd == lx->nppcnd0) lx->inclguard = NULL;
+ if (!skip) {
+ switch (lastcmd) {
+ case PPXXX: goto BadPP;
+ case PPDEFINE: ppdefine(lx); break;
+ case PPUNDEF: ppundef(lx); break;
+ case PPIF: ppif(lx, &tk->span); break;
+ case PPIFDEF: ppifxdef(lx, 1, &tk->span); break;
+ case PPIFNDEF: ppifxdef(lx, 0, &tk->span); break;
+ case PPELIF: ppelif(lx, &tk->span); break;
+ case PPELIFDEF: ppelifxdef(lx, 1, &tk->span); break;
+ case PPELIFNDEF: ppelifxdef(lx, 0, &tk->span); break;
+ case PPELSE: ppelse(lx, &tk->span); break;
+ case PPENDIF: ppendif(lx, &tk->span); break;
+ case PPLINE: ppline(lx, tk); break;
+ case PPPRAGMA: pppragma(lx, &tk->span); break;
+ case PPWARNING: ppdiag(lx, &tk->span, 0); break;
+ case PPERROR: ppdiag(lx, &tk->span, 1); break;
+ case PPINCLUDE: inclerror |= !ppinclude(lx, &tk->span); break;
+ default: assert(0&&"nyi");
+ }
+ } else {
+ switch (lastcmd) {
+ case PPIF: /* increment nesting level */
+ case PPIFDEF:
+ case PPIFNDEF:
+ assert(nppcnd < countof(ppcndstk) && "too many nested #if");
+ ppcndstk[nppcnd].ifspan = tk->span.sl;
+ ppcndstk[nppcnd].cnd = PPCNDTAKEN;
+ ppcndstk[nppcnd++].elsep = 0;
+ break;
+ case PPELIF: ppelif(lx, &tk->span); break;
+ case PPELIFDEF: ppelifxdef(lx, 1, &tk->span); break;
+ case PPELIFNDEF: ppelifxdef(lx, 0, &tk->span); break;
+ case PPELSE: ppelse(lx, &tk->span); break;
+ case PPENDIF: ppendif(lx, &tk->span); break;
+ default: ppskipline(lx); break;
+ }
+ }
+ if (lastcmd != PPINCLUDE)
+ lx->firstdirective = 0;
+ skip = nppcnd ? ppcndstk[nppcnd-1].cnd != PPCNDTRUE : 0;
+ } else {
+ if (!skip) {
+ BadPP:
+ error(&tk->span, "invalid preprocessor directive");
+ }
+ ppskipline(lx);
+ }
+ linebegin = 1;
+ } else {
+ lx->firstdirective = 0;
+ linebegin = 0;
+ if (skip && t != TKEOF)
+ continue;
+ if (tryexpand(lx, tk) == EXPSTACK)
+ goto Begin;
+ if (t == TKEOF && nppcnd && ppcndstk[nppcnd-1].filedepth == includedepth) {
+ struct span span = { ppcndstk[nppcnd-1].ifspan };
+ error(&span, "#if is not matched by #endif");
+ }
+ if (t == TKEOF && lx->save) {
+ /* end of #include'd file, restore previous state */
+ if (lastcmd == PPENDIF && lx->inclguard) {
+ markfileonce(lx->fileid, lx->inclguard);
+ }
+ struct lexer *sv = lx->save;
+ if (sv->inclnerror != nerror || sv->inclnwarn != nwarn) {
+ int line;
+ const char *f = getfilepos(&line, NULL, sv->fileid, sv->chridx-2);
+ note(NULL, "in file included from %s:%d", f, line);
+ }
+ memcpy(lx, sv, sizeof *lx);
+ free(sv);
+ --includedepth;
+ linebegin = 1;
+ lx->firstdirective = 0;
+ } else if (t == TKEOF && inclerror) {
+ break;
+ } else {
+ if (nppcnd == lx->nppcnd0) lx->inclguard = NULL;
+ if (t == TKIDENT) identkeyword(tk);
+ if (!inclerror) return tk->t;
+ }
+ }
+ }
+ assert(inclerror);
+ efmt("Aborting due to previous error(s).\n");
+ exit(1);
+ assert(0);
+}
+
+int
+lexpeek(struct lexer *lx, struct token *tk_)
+{
+ struct token tkx[1], *tk;
+ uint t;
+
+ tk = tk_ ? tk_ : tkx;
+ if ((t = lx->peektok.t)) {
+ *tk = lx->peektok;
+ return t;
+ }
+ t = lex(lx, tk);
+ lx->peektok = *tk;
+ return t;
+}
+
+/* Predefined/builtin macros */
+
+static vec_of(uchar) ppcmdline;
+
+void
+cpppredef(bool undef, const char *cmd)
+{
+ const char *sep = strchr(cmd, '='), *body = sep ? sep+1 : "1";
+ uint namelen = sep ? sep - cmd : strlen(cmd);
+ char line[1024];
+ struct wbuf wbuf = MEMBUF(line, sizeof line);
+ if (!ppcmdline.p) vinit(&ppcmdline, NULL, 1<<10);
+ int n;
+ if (undef)
+ n = bfmt(&wbuf, "#undef %S\n", cmd, namelen);
+ else
+ n = bfmt(&wbuf, "#define %S %s\n", cmd, namelen, body);
+ assert(n <= sizeof line);
+ vpushn(&ppcmdline, line, n);
+}
+
+static void
+mac__file__(struct lexer *lx, struct token *tk)
+{
+ tk->t = TKSTRLIT;
+ tk->s = getfilename(lx->fileid, lx->chridx);
+ tk->wide = 0;
+ tk->len = strlen(tk->s);
+}
+
+static void
+mac__line__(struct lexer *lx, struct token *tk)
+{
+ char buf[20];
+ int line;
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ getfilepos(&line, NULL, lx->fileid, lx->chridx);
+ bfmt(&wbuf, "%d", line), buf[wbuf.len++] = 0;
+ tk->t = TKNUMLIT;
+ tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1);
+ tk->len = wbuf.len-1;
+}
+
+#include <time.h>
+
+static void
+mac__date__(struct lexer *lx, struct token *tk)
+{
+ char buf[20];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ time_t tm = time(NULL);
+ struct tm *ts = localtime(&tm);
+ tk->t = TKSTRLIT;
+ tk->wide = 0;
+ tk->len = 11;
+ if (ts) {
+ bfmt(&wbuf, "%S %2d %4d%c",
+ &"JanFebMarAprMayJunJulAugSepOctNovDec"[ts->tm_mon*3], 3,
+ ts->tm_mday, 1900+ts->tm_year, 0);
+ assert(wbuf.len == 11+1);
+ tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1);
+ } else {
+ tk->s = "\?\?\? \?\? \?\?\?\?";
+ }
+}
+
+static void
+mac__time__(struct lexer *lx, struct token *tk)
+{
+ char buf[20];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ time_t tm = time(NULL);
+ struct tm *ts = localtime(&tm);
+ tk->t = TKSTRLIT;
+ tk->wide = 0;
+ tk->len = 8;
+ if (ts) {
+ bfmt(&wbuf, "%.2d:%.2d:%.2d%c", ts->tm_hour, ts->tm_min, ts->tm_sec, 0);
+ tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1);
+ assert(wbuf.len == 8+1);
+ } else {
+ tk->s = "\?\?:\?\?:\?\?";
+ }
+}
+
+static void
+mac__counter__(struct lexer *lx, struct token *tk)
+{
+ char buf[20];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+ static int counter;
+ bfmt(&wbuf, "%d", counter++), buf[wbuf.len++] = 0;
+ tk->t = TKNUMLIT;
+ tk->s = alloccopy(lx->tmparena, buf, wbuf.len, 1);
+ tk->len = wbuf.len-1;
+}
+
+static void
+mac__has_builtin(struct lexer *lx, struct token *tk, const struct token *args, int narg)
+{
+ extern bool hasbuiltin(const char *, uint n);
+ bool has = 0;
+ tk->t = TKNUMLIT, tk->len = 1;
+ if (narg >= 1) {
+ if (args[0].t == TKIDENT)
+ has = hasbuiltin(args[0].s, args[0].len);
+ else if (in_range(args[0].t, TKWBEGIN_, TKWEND_))
+ has = args[0].len >= sizeof "__builtin_" && !memcmp(args[0].s, "__builtin_", 10);
+ else goto Bad;
+ if (narg != 1)
+ error(&args[1].span, "expected `)' after '%tk'", &args[0]);
+ } else Bad: {
+ error(narg ? &args[0].span : &tk->span, "'__has_builtin' requires an identifier");
+ }
+ tk->s = &"01"[has];
+}
+
+
+static void
+putdef1(const char *name)
+{
+ static const struct token tok_1 = { TKNUMLIT, .s = "1", .len = 1, .litlit = 1 };
+ putmac(intern(name), &(struct macro) {
+ .predef = 1,
+ .single = &tok_1
+ });
+}
+
+static void
+putdefs1(const char *s)
+{
+ for (; *s; s += strlen(s) + 1) putdef1(s);
+}
+
+static void
+addpredefmacros(struct arena **tmparena)
+{
+ static struct token tok_stdc = {TKNUMLIT},
+ tok_major = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_MAJOR),
+ .len = sizeof XSTR(ANTCC_VERSION_MAJOR) - 1},
+ tok_minor = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_MINOR),
+ .len = sizeof XSTR(ANTCC_VERSION_MINOR) - 1},
+ tok_patch = {TKNUMLIT, .s = XSTR(ANTCC_VERSION_PATCH),
+ .len = sizeof XSTR(ANTCC_VERSION_PATCH) - 1};
+ static struct { const char *name; struct macro m; } macs[] = {
+ { "__FILE__", { .predef = 1, .special = 1, .handler = mac__file__ }},
+ { "__LINE__", { .predef = 1, .special = 1, .handler = mac__line__ }},
+ { "__DATE__", { .predef = 1, .special = 1, .handler = mac__date__ }},
+ { "__TIME__", { .predef = 1, .special = 1, .handler = mac__time__ }},
+ { "__COUNTER__", { .predef = 1, .special = 1, .handler = mac__counter__ }},
+ { "__has_builtin", { .predef = 1, .nparam = 1, .fnlike = 1, .special = 1, .handlerfn = mac__has_builtin }},
+ { "__STDC_VERSION__", { .predef = 1, .single = &tok_stdc }},
+ { "__antcc_major__", { .predef = 1, .single = &tok_major }},
+ { "__antcc_minor__", { .predef = 1, .single = &tok_minor }},
+ { "__antcc_patch__", { .predef = 1, .single = &tok_patch }},
+ { "__extension__", { .predef = 1, .single = NULL }},
+ };
+ static const char
+ cpredefs[] =
+ "__antcc__\0__STDC__\0__STDC_NO_ATOMICS__\0__STDC_NO_COMPLEX__\0__STDC_NO_THREADS__\0__STDC_NO_VLA__\0",
+ *ospredefs[] = {
+ [OSlinux] = "__linux\0__linux__\0linux\0unix\0__unix\0__unix__\0"
+ }, *archpredefs[] = {
+ [ISx86_64] = "__x86_64__\0__x86_64\0",
+ [ISaarch64] = "__aarch64__\0__aarch64\0",
+ }, cstdver[][8] = {
+ [STDC89] = "199409L",
+ [STDC99] = "199901L",
+ [STDC11] = "201112L",
+ [STDC23] = "202311L",
+ };
+
+ tok_stdc.s = cstdver[ccopt.cstd];
+ tok_stdc.len = 7;
+
+ for (int i = 0; i < countof(macs); ++i)
+ putmac(intern(macs[i].name), &macs[i].m);
+ putdefs1(cpredefs);
+ if (target.os != OSunknown) putdef1("__STDC_HOSTED__");
+ putdefs1(ospredefs[target.os]);
+ putdefs1(archpredefs[target.arch]);
+
+ if (ppcmdline.n) {
+ struct memfile *f;
+ struct lexer lx[1] = {0};
+ lx->fileid = getpredeffile(&f, "<command line>");
+ assert(!f->p);
+ lx->ndat = f->n = ppcmdline.n;
+ vpushn(&ppcmdline, "\0\0\0\0\0\0", 6);
+ lx->dat = f->p = ppcmdline.p;
+ lx->tmparena = tmparena;
+ lx->chrbuf0 = countof(lx->chrbuf);
+ lx->firstdirective = 1;
+ while (lex(lx, NULL) != TKEOF) ;
+ }
+}
+
+enum initlexer
+initlexer(struct lexer *lx, const char **err, const char *file)
+{
+ enum { NARENA = 1<<12 };
+ static union { char m[sizeof(struct arena) + NARENA]; struct arena *_align; } amem;
+ static struct arena *tmparena = (void *)amem.m;
+
+ if (!tmparena->cap) tmparena->cap = NARENA;
+ if (!mtoksbuf.p) vinit(&mtoksbuf, NULL, 1024);
+ if (!mdyntoksbuf.p) vinit(&mdyntoksbuf, NULL, 256);
+ if (!macroht.v) addpredefmacros(&tmparena);
+
+ struct memfile *f;
+ int fileid = openfile(err, &f, file);
+ if (fileid < 0)
+ return LXERR;
+ internstr guard;
+ if (isfileseen(fileid) && isoncefile(fileid, &guard) && (!guard || findmac(guard))) {
+ //efmt("skipping %s .. guard %s\n", file, guard ? guard : "<none>");
+ return LXFILESKIP;
+ }
+ memset(lx, 0, sizeof *lx);
+ lx->fileid = fileid;
+ markfileseen(fileid);
+
+ lx->dat = f->p;
+ lx->ndat = f->n;
+ lx->tmparena = &tmparena;
+ lx->chrbuf0 = countof(lx->chrbuf);
+ lx->firstdirective = 1;
+ lx->nppcnd0 = nppcnd;
+ return getfilename(fileid, 0) != file ? LXFILESEEN : LXOK;
+}
+
+/* callback to let lexer release temp memory for arena allocated token data */
+void
+lexerfreetemps(struct lexer *lx)
+{
+ if (!lx->macstk) {
+ /* some of the tokens could be somewhere in the macro stack */
+ freearena(lx->tmparena);
+ }
+}
+
+void
+lexerdump(struct lexer *lx, struct wbuf *out)
+{
+ struct token prev = {0}, tok;
+ int file = lx->fileid, line = 1, col = 1;
+ const char *lastfile = getfilename(file, 0);
+ bfmt(out, "# %d %'s\n", 1, lastfile);
+ while (lex(lx, &tok) != TKEOF) {
+ int tkline, tkcol;
+ const char *fname = getfilepos(&tkline, &tkcol, tok.span.ex.file, tok.span.ex.off);
+ if (tok.span.ex.file != file || fname != lastfile) {
+ file = tok.span.ex.file;
+ bfmt(out, "\n# %d %'s\n", tkline, fname);
+ col = 1;
+ lexerfreetemps(lx);
+ lastfile = fname;
+ } else if (line < tkline && tkline - line < 5) {
+ do
+ ioputc(out, '\n');
+ while (++line != tkline);
+ col = 1;
+ } else if (line != tkline) {
+ bfmt(out, "\n# %d\n", tkline);
+ line = tkline;
+ col = 1;
+ lexerfreetemps(lx);
+ } else if (prev.t && (tok.space || tokpaste(lx, NULL, &prev, &tok))) {
+ /* preserve whitespace & paste avoidance */
+ ioputc(out, ' ');
+ ++col;
+ }
+ if (col == 1)
+ for (; col < tkcol; ++col)
+ ioputc(out, ' ');
+ line = tkline;
+ bfmt(out, "%tk", &tok);
+ col += tok.span.ex.len;
+ prev = tok;
+ }
+ bfmt(out, "\n");
+ ioflush(out);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_lex.h b/src/c_lex.h
new file mode 100644
index 0000000..e70bc78
--- /dev/null
+++ b/src/c_lex.h
@@ -0,0 +1,126 @@
+#include "../common.h"
+#include "../type.h"
+
+static inline bool
+joinspan(struct span0 *dst, struct span0 snd)
+{
+ if (dst->file != snd.file) return 0;
+ if (dst->off > snd.off) return 0;
+ dst->len = snd.off + snd.len - dst->off;
+ return 1;
+}
+
+enum toktag { /* single-character tokens' tag value is the character itself */
+ TKEOF = 0xFF,
+ TKXXX = 0,
+ TKNUMLIT,
+ TKCHRLIT,
+ TKSTRLIT,
+ TKPPHDRH, /* <hdr> (for #include) */
+ TKPPHDRQ, /* "hdr" (for #include) */
+ TKPPMACARG, /* macro param, in repl list */
+ TKPPMACSTR, /* stringify macro param, in repl list */
+ TKEQU = '@', /* == */
+ TKNEQ, /* != */
+ TKLTE, /* <= */
+ TKGTE, /* >= */
+ TKSHR, /* >> */
+ TKSHL, /* << */
+ TKINC, /* ++ */
+ TKDEC, /* -- */
+ TKDOTS, /* ... */
+ TKARROW, /* -> */
+ TKPPCAT, /* ## */
+ TKLOGAND, /* && */
+ TKLOGIOR, /* || */
+ TKSETADD, /* += */
+ TKSETSUB, /* -= */
+ TKSETMUL, /* *= */
+ TKSETDIV, /* /= */
+ TKSETREM, /* %= */
+ TKSETIOR, /* |= */
+ TKSETXOR, /* ^= */
+ TKSETAND, /* &= */
+ TKSETSHL, /* <<= */
+ TKSETSHR, /* >>= */
+ TKIDENT = 0x80,
+#define _(kw, stdc, ...) TKW##kw,
+#include "keywords.def"
+#undef _
+ NTOKTAG,
+};
+static_assert(NTOKTAG < 256);
+
+struct token {
+ uchar t; /* toktag */
+ bool litlit : 1,
+ blue : 1, /* preprocessor token painted blue */
+ extwarn : 1; /* warn this keyword token is an extension */
+ uchar wide : 2, /* for CHRLIT & STRLIT; 1 -> 16bit, 2 -> 32bit */
+ wideuni : 1, /* ditto, 0 -> 'L', 1 -> 'u'/'U' (C11) */
+ space : 1; /* preceded by whitespace? */
+ union {
+ uint len;
+ ushort argidx;
+ };
+ struct span span;
+ union {
+ internstr name;
+ const char *s;
+ const ushort *ws16;
+ const uint *ws32;
+ };
+ /* for (multi-)character tokens s & len are unused
+ * for keywords, s is constant cstring, len = strlen(s)
+ * for idents, s is interned cstring, len = strlen(s)
+ * for strlit and chrlit:
+ * when litlit : s points to start of string within file buffer (after the ")
+ * len == span.sl.len - 2 (string data appears literally in source code)
+ * otherwise s is heap allocated buffer of len bytes
+ * when wide, litlit = 0 and use ws16/ws32
+ * for numlit:
+ * when litlit : s points to start of token within file buffer (normal case)
+ * len == span.sl.len (number literal appears literally in source code)
+ * otherwise s is heap allocated buffer of len bytes
+ * for macro arg/stringify:
+ * s is like keyword/ident
+ * argidx is index in macro param list,
+ * macidx is macro id of which it is a parameter
+ */
+};
+
+extern int nerror, nwarn;
+struct lexer {
+ struct lexer *save;
+ short fileid;
+ const uchar *dat;
+ uint ndat;
+ uint idx, chridx;
+ ushort chrbuf0;
+ struct macrostack *macstk;
+ struct token peektok;
+ bool eof, err;
+ struct arena **tmparena;
+ bool firstdirective;
+ short nppcnd0;
+ short inclnerror, inclnwarn;
+ internstr inclguard;
+ uchar chrbuf[1<<10];
+ uint chridxbuf[1<<10];
+};
+
+enum initlexer {
+ LXOK,
+ LXFILESEEN,
+ LXFILESKIP,
+ LXERR,
+};
+
+int lex(struct lexer *, struct token *);
+int lexpeek(struct lexer *, struct token *);
+enum typetag parsenumlit(uvlong *, double *, const struct token *, bool ispp);
+enum initlexer initlexer(struct lexer *, const char **err, const char *file);
+void lexerdump(struct lexer *, struct wbuf *out);
+void lexerfreetemps(struct lexer *);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_type.c b/src/c_type.c
new file mode 100644
index 0000000..e8a5b1e
--- /dev/null
+++ b/src/c_type.c
@@ -0,0 +1,311 @@
+#include "type.h"
+
+struct typedata typedata[1<<13];
+internstr ttypenames[1<<10];
+
+static ushort
+hashtd(const struct typedata *td)
+{
+ uint h = td->t*33;
+ bool t;
+ switch (td->t) {
+ case TYARRAY:
+ h = hashb(h, &td->arrlen, sizeof td->arrlen);
+ /* fallthru */
+ case TYPTR:
+ h = hashb(h, &td->child, sizeof td->child);
+ break;
+ case TYSTRUCT:
+ case TYUNION:
+ case TYENUM:
+ h = hashb(h, &td->id, sizeof td->id);
+ break;
+ case TYFUNC:
+ h = hashb(h, &td->ret, sizeof td->ret);
+ h = hashb(h, &td->nmemb, sizeof td->nmemb);
+ h = hashb(h, (t = td->kandr, &t), sizeof t);
+ h = hashb(h, (t = td->variadic, &t), sizeof t);
+ for (int i = 0; i < td->nmemb; ++i) {
+ h = hashb(h, &td->param[i], sizeof *td->param);
+ }
+ break;
+ default:
+ assert(0 && "bad typedata tag");
+ }
+ return h ^ h>>16;
+}
+
+static bool
+tdequ(const struct typedata *a, const struct typedata *b)
+{
+ if (a->t != b->t) return 0;
+ switch (a->t) {
+ case TYARRAY:
+ return a->arrlen == b->arrlen && a->child.bits == b->child.bits;
+ case TYPTR:
+ return a->child.bits == b->child.bits;
+ case TYSTRUCT:
+ case TYUNION:
+ case TYENUM:
+ return a->id == b->id;
+ case TYFUNC:
+ if (a->ret.bits != b->ret.bits) return 0;
+ if (a->nmemb != b->nmemb) return 0;
+ if (a->variadic != b->variadic) return 0;
+ if (a->kandr != b->kandr) return 0;
+ for (int i = 0; i < a->nmemb; ++i) {
+ if (a->param[i].bits != b->param[i].bits)
+ return 0;
+ }
+ return 1;
+ default:
+ assert(0 && "bad typedata tag");
+ }
+}
+
+static ushort
+interntd(const struct typedata *td)
+{
+ uint h, i, n = countof(typedata);
+ for (i = h = hashtd(td); n--; ++i) {
+ struct typedata *slot = &typedata[i &= countof(typedata) - 1];
+ if (!slot->t) {
+ uint nmemb;
+ static struct arena *datarena;
+ if (!datarena) {
+ enum { N = 1<<12 };
+ static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem;
+ datarena = (void *)amem.m, datarena->cap = N;
+ }
+
+ Copy:
+ nmemb = td->nmemb;
+ *slot = *td;
+ switch (slot->t) {
+ case TYENUM:
+ if (slot->var)
+ slot->var = alloccopy(&datarena, td->var, nmemb * sizeof *slot->var, 0);
+ break;
+ case TYSTRUCT:
+ case TYUNION:
+ if (slot->fld)
+ slot->fld = alloccopy(&datarena, td->fld, nmemb * sizeof *slot->fld, 0);
+ break;
+ case TYFUNC:
+ if (slot->param)
+ slot->param = alloccopy(&datarena, td->param, nmemb * sizeof *slot->param, 0);
+ }
+ return i;
+ } else if (tdequ(slot, td)) {
+ if (td->t == TYSTRUCT || td->t == TYUNION || td->t == TYENUM)
+ goto Copy;
+ return i;
+ }
+ }
+ assert(0 && "typedata[] full");
+}
+
+bool
+isincomplete(union type t)
+{
+ switch (t.t) {
+ case TYVOID: return 1;
+ case TYARRAY: return !typearrlen(t);
+ case TYSTRUCT:
+ case TYUNION:
+ return typedata[t.dat].nmemb == 0;
+ case TYENUM:
+ return !typedata[t.dat].backing;
+ }
+ return 0;
+}
+
+uint
+typesize(union type t)
+{
+ if (isprim(t) || t.t == TYPTR) return targ_primsizes[t.t];
+ switch (t.t) {
+ case TYENUM:
+ return targ_primsizes[typedata[t.dat].backing];
+ case TYARRAY:
+ if (t.flag & TFCHLDPRIM)
+ return targ_primsizes[t.child] * t.arrlen;
+ /* fallthru */
+ case TYSTRUCT:
+ case TYUNION:
+ return typedata[t.dat].siz;
+ }
+ return 0;
+}
+
+uint
+typealign(union type t)
+{
+ if (isprim(t) || t.t == TYPTR) return targ_primalign[t.t];
+ switch (t.t) {
+ case TYENUM:
+ return targ_primalign[typedata[t.dat].backing];
+ case TYARRAY:
+ return typealign(typechild(t));
+ case TYSTRUCT:
+ case TYUNION:
+ return typedata[t.dat].align;
+ }
+ return 0;
+}
+
+union type
+mkptrtype(union type t, int qual)
+{
+ if (isprim(t))
+ return mktype(TYPTR, .flag = TFCHLDPRIM | (qual & TFCHLDQUAL), .child = t.t);
+ else if (t.t == TYENUM || t.t == TYFUNC || isagg(t))
+ return mktype(TYPTR, .flag = TFCHLDISDAT | (qual & TFCHLDQUAL), .dat = t.dat);
+ return mktype(TYPTR, .flag = qual & TFCHLDQUAL,
+ .dat = interntd(&(struct typedata) { TYPTR, .child = t }));
+}
+
+union type
+mkarrtype(union type t, int qual, uint n)
+{
+ if (isprim(t) && n < 256)
+ return mktype(TYARRAY, .flag = TFCHLDPRIM | (qual & TFCHLDQUAL), .child = t.t, .arrlen = n);
+ return mktype(TYARRAY, .flag = qual & TFCHLDQUAL,
+ .dat = interntd(&(struct typedata) { TYARRAY, .child = t, .arrlen = n, .siz = n * typesize(t) }));
+}
+
+union type
+mkfntype(union type ret, uint n, const union type *par, bool kandr, bool variadic)
+{
+ struct typedata td = { TYFUNC, .ret = ret, .nmemb = n, .param = par };
+ td.kandr = kandr, td.variadic = variadic;
+ return mktype(TYFUNC, .dat = interntd(&td));
+}
+
+union type
+completetype(internstr name, int id, struct typedata *td)
+{
+ assert(td->t == TYENUM || td->t == TYSTRUCT || td->t == TYUNION);
+ td->id = id;
+ assert(id < countof(ttypenames) && "too many tag types");
+ if (ttypenames[id])
+ assert(ttypenames[id] == name && "bad redefn");
+ else
+ ttypenames[id] = name;
+ return mktype(td->t, .dat = interntd(td), .backing = td->t == TYENUM ? td->backing : 0);
+}
+
+union type
+mktagtype(internstr name, struct typedata *td)
+{
+ static int id;
+ return completetype(name, id++, td);
+}
+
+static bool
+getfieldrec(struct fielddata *res, uint off, const struct typedata *td, internstr name)
+{
+Begin:
+ for (int i = 0; i < td->nmemb; ++i) {
+ struct namedfield *fld = &td->fld[i];
+ if (fld->name == name) { /* match */
+ *res = fld->f;
+ res->off += off;
+ return 1;
+ } else if (!fld->name) { /* anonymous struct/union */
+ const struct typedata *ftd = &typedata[fld->f.t.dat];
+ assert(isagg(fld->f.t));
+ if (i == td->nmemb - 1) { /* last field, tail recurse */
+ off += fld->f.off;
+ td = ftd;
+ goto Begin;
+ } else if (getfieldrec(res, off + fld->f.off, ftd, name))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+bool
+getfield(struct fielddata *res, union type ty, internstr name)
+{
+ assert(isagg(ty));
+ return getfieldrec(res, 0, &typedata[ty.dat], name);
+}
+
+union type
+typedecay(union type t)
+{
+ if (t.t == TYARRAY)
+ return mkptrtype(typechild(t), t.flag & TFCHLDQUAL);
+ if (t.t == TYFUNC)
+ return mkptrtype(t, 0);
+ return t;
+}
+
+bool /* 6.5.16.1 Simple assignment Constraints */
+assigncompat(union type dst, union type src)
+{
+ if (dst.bits == src.bits) return 1;
+ if (isarith(dst) && isarith(src)) return 1;
+ if (dst.t == TYPTR && src.t == TYPTR) {
+ union type ds = typechild(dst), ss = typechild(src);
+ if (ds.bits == ss.bits) return 1; /* T* with different qualifiers */
+ if (ss.t == TYVOID || ds.t == TYVOID) return 1; /* T* <-> void* */
+ enum typetag dt = scalartypet(ds), /* handle enums */
+ st = scalartypet(ss);
+ if (!dt || !st) return 0; /* unequal incomplete enums */
+ /* plain char exception */
+ if (st == TYCHAR && in_range(dt, TYUCHAR, TYSCHAR)) return 1;
+ if (dt == TYCHAR && in_range(st, TYUCHAR, TYSCHAR)) return 1;
+ } else if (dst.t == TYBOOL && src.t == TYPTR)
+ return 1;
+ return 0;
+}
+
+enum typetag
+intpromote(enum typetag t)
+{
+ static int intisshort = -1;
+ if (intisshort < 0) intisshort = targ_primsizes[TYINT] == targ_primsizes[TYSHORT];
+ if (intisshort && t == TYUSHORT) return TYUINT;
+ return t < TYINT ? TYINT : t;
+}
+
+union type /* 6.3.1.8 Usual arithmetic conversions */
+cvtarith(union type a, union type b)
+{
+ const union type none = {0};
+
+ if (!isarith(a) || !isarith(b)) return none;
+ if (a.t == TYENUM) a = typechild(a);
+ if (b.t == TYENUM) b = typechild(b);
+ if (isflt(a) || isflt(b)) {
+ /* when one type is float, choose type with greatest rank */
+ /* enumeration order of type tags reflects arithmetic type rank */
+ return a.t > b.t ? a : b;
+ }
+ a.t = intpromote(a.t);
+ b.t = intpromote(b.t);
+ if (a.bits == b.bits) return a;
+
+ if (issigned(a) == issigned(b)) {
+ /* when both are integers with same signage, choose type with greatest rank */
+ return a.t > b.t ? a : b;
+ }
+ /* if the signed type can represent all values of the unsigned type,
+ * choose it, otherwise choose its corresponding unsigned type */
+ /* so long long + unsigned = long long;
+ * but long long + unsigned long = unsigned long long */
+ if (issigned(a)) {
+ if (targ_primsizes[a.t] <= targ_primsizes[b.t])
+ a.t += 1; /* make unsigned */
+ return a.t > b.t ? a : b;
+ } else {
+ if (targ_primsizes[b.t] <= targ_primsizes[a.t])
+ b.t += 1; /* make unsigned */
+ return b.t > a.t ? b : a;
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/c_type.h b/src/c_type.h
new file mode 100644
index 0000000..ad8e1b1
--- /dev/null
+++ b/src/c_type.h
@@ -0,0 +1,177 @@
+#ifndef TYPE_H_
+#define TYPE_H_
+#include "common.h"
+
+enum qualifier {
+ QCONST = 1<<0,
+ QVOLATILE = 1<<1,
+};
+
+enum typetag { /* ordering is important here! */
+ TYXXX,
+ TYENUM,
+ TYBOOL, TYCHAR, TYSCHAR, TYUCHAR, TYSHORT, TYUSHORT, TYINT, TYUINT, TYLONG, TYULONG, TYVLONG, TYUVLONG,
+ TYFLOAT, TYDOUBLE, TYLDOUBLE, TYCOMPLEXF, TYCOMPLEX, TYCOMPLEXL,
+ TYVOID,
+ TYPTR, TYARRAY, TYFUNC,
+ TYSTRUCT, TYUNION,
+ NTYPETAG,
+ TYSIGNEDSET_ = 1<<TYSCHAR | 1<<TYSHORT | 1<<TYINT | 1<<TYLONG | 1<<TYVLONG,
+ TYUNSIGNEDSET_ = 1<<TYUCHAR | 1<<TYUSHORT | 1<<TYUINT | 1<<TYULONG | 1<<TYUVLONG,
+ TYSCALARSET_ = ((1u << (TYCOMPLEXL - TYENUM + 1)) - 1) << TYENUM | 1<<TYPTR
+};
+
+enum typeflagmask {
+ TFCHLDQUAL = 3,
+ TFCHLDPRIM = 1<<2,
+ TFCHLDISDAT = 1<<3,
+};
+
+union type {
+ struct {
+ uchar t; /* type tag */
+ union {
+ uchar flag;
+ uchar backing; /* type tag for enum backing int */
+ };
+ union {
+ struct {
+ uchar child; /* prim type tag */
+ uchar arrlen; /* small array */
+ };
+ ushort dat; /* index into typedata */
+ };
+ };
+ uint bits;
+};
+static_assert(sizeof(union type) == 4);
+
+#define isprimt(t) in_range((t), TYBOOL, TYVOID)
+#define isintt(t) in_range((t), TYENUM, TYUVLONG)
+#define issignedt(t) ((TYSIGNEDSET_ | targ_charsigned << TYCHAR) >> (t) & 1)
+#define isunsignedt(t) ((TYUNSIGNEDSET_ | !targ_charsigned << TYCHAR) >> (t) & 1)
+#define isfltt(t) in_range((t), TYFLOAT, TYLDOUBLE)
+#define isaritht(t) in_range((t), TYENUM, TYCOMPLEXL)
+#define isscalart(t) (TYSCALARSET_ >> (t) & 1)
+#define isptrcvtt(t) in_range((t), TYPTR, TYFUNC)
+#define isaggt(t) in_range((t), TYSTRUCT, TYUNION)
+#define isprim(ty) isprimt((ty).t)
+#define isint(ty) isintt((ty).t)
+#define issigned(ty) issignedt(scalartypet(ty))
+#define isunsigned(ty) isunsignedt(scalartypet(ty))
+#define isflt(ty) isfltt((ty).t)
+#define isarith(ty) isaritht((ty).t)
+#define isscalar(ty) isscalart((ty).t)
+#define isptrcvt(ty) isptrcvtt((ty).t)
+#define isagg(ty) isaggt((ty).t)
+#define iscomplext(t) in_range((t), TYCOMPLEXF, TYCOMPLEXL)
+#define iscomplex(ty) iscomplext((ty).t)
+#define mktype(...) ((union type) {{ __VA_ARGS__ }})
+
+struct enumvar {
+ internstr name;
+ union { vlong i; uvlong u; };
+};
+
+struct fielddata {
+ union type t;
+ ushort off;
+ uchar bitsiz,
+ bitoff : 6,
+ qual : 2;
+};
+struct namedfield {
+ internstr name;
+ struct fielddata f;
+};
+
+struct typedata {
+ uchar t;
+ ushort id;
+ union {
+ union type child;
+ const union type *param; /* functions */
+ struct { /* aggregates */
+ struct namedfield *fld;
+ };
+ struct { /* enum */
+ uchar backing;
+ struct enumvar *var;
+ };
+ };
+ union {
+ uint arrlen; /* array */
+ struct {
+ short nmemb; /* functions, aggregates, enums */
+ uchar align;
+ union {
+ struct { /* function */
+ bool kandr : 1, variadic : 1;
+ };
+ struct { /* aggregate */
+ bool anyconst : 1, flexi : 1;
+ };
+ };
+ };
+ };
+ union {
+ uint siz; /* aggregate & array */
+ union type ret; /* function */
+ };
+};
+
+extern struct typedata typedata[];
+extern internstr ttypenames[/*id*/];
+
+bool isincomplete(union type);
+uint typesize(union type);
+uint typealign(union type);
+union type mkptrtype(union type, int qual);
+union type mkarrtype(union type t, int qual, uint n);
+union type mkfntype(union type ret, uint n, const union type *, bool kandr, bool variadic);
+union type mktagtype(internstr name, struct typedata *td);
+bool getfield(struct fielddata *res, union type, internstr);
+union type completetype(internstr name, int id, struct typedata *td);
+union type typedecay(union type);
+bool assigncompat(union type dst, union type src);
+enum typetag intpromote(enum typetag);
+union type cvtarith(union type a, union type b);
+static inline union type
+typechild(union type t)
+{
+ if (t.t == TYENUM) return mktype(t.backing ? t.backing : typedata[t.dat].backing);
+ if (t.flag & TFCHLDPRIM) return mktype(t.child);
+ if (t.flag & TFCHLDISDAT) {
+ union type chld = mktype(typedata[t.dat].t, .dat = t.dat);
+ if (chld.t == TYENUM) chld.backing = typedata[t.dat].backing;
+ return chld;
+ }
+ return typedata[t.dat].child;
+}
+static inline enum typetag
+scalartypet(union type t)
+{
+ if (t.t == TYENUM) return t.backing ? t.backing : typedata[t.dat].backing;
+ if (isptrcvt(t)) return TYPTR;
+ assert(!iscomplex(t));
+ return t.t;
+}
+static inline uint
+typearrlen(union type t)
+{
+ return (t.flag & TFCHLDPRIM) ? t.arrlen : typedata[t.dat].arrlen;
+}
+
+/**********/
+/* Target */
+/**********/
+
+extern uint targ_valistsize;
+extern uchar targ_primsizes[];
+extern uchar targ_primalign[];
+extern enum typetag targ_sizetype, targ_ptrdifftype, targ_wchartype;
+extern bool targ_charsigned, targ_bigendian, targ_64bit;
+
+#endif
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/io.c b/src/io.c
new file mode 100644
index 0000000..d33afb1
--- /dev/null
+++ b/src/io.c
@@ -0,0 +1,1255 @@
+#include "c/lex.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+struct wbuf bstdout, bstderr;
+
+void
+ioinit(void)
+{
+ bstdout.fp = stdout;
+ bstdout.isfp = 1;
+ bstderr.fp = stderr;
+ bstderr.isfp = 1;
+}
+
+void
+iowrite(struct wbuf *buf, const void *Src, int n)
+{
+ const uchar *src = Src;
+
+ if (buf->isfp) {
+ fwrite(Src, 1, n, buf->fp);
+ buf->err = ferror(buf->fp) != 0;
+ return;
+ }
+ while (n > 0) {
+ int avail = buf->cap - buf->len;
+ int amt = avail < n ? avail : n;
+
+ memcpy(buf->buf + buf->len, src, amt);
+ n -= amt;
+ src += amt;
+ buf->len += amt;
+ if (n > 0 && buf->len == buf->cap) {
+ if (buf->fd < 0) {
+ buf->err = 1;
+ return;
+ }
+ ioflush(buf);
+ }
+ }
+}
+
+void
+ioflush(struct wbuf *buf)
+{
+ int i, ret;
+
+ if (buf->isfp) {
+ fflush(buf->fp);
+ buf->err = ferror(buf->fp) != 0;
+ return;
+ }
+ buf->err = 0;
+ if (buf->fd < 0) {
+ buf->len = 0;
+ return;
+ }
+ for (i = 0; buf->len > 0;) {
+ ret = write(buf->fd, buf->buf + i, buf->len);
+ if (ret > 0) {
+ assert(ret <= buf->len);
+ buf->len -= ret;
+ i += ret;
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ continue;
+ } else {
+ buf->err = 1;
+ break;
+ }
+ }
+}
+
+void
+ioputc(struct wbuf *buf, uchar c)
+{
+ if (buf->isfp) {
+ buf->err = fputc(c, buf->fp) != EOF;
+ return;
+ }
+ if (buf->len == buf->cap) {
+ if (buf->fd < 0) {
+ buf->err = 1;
+ return;
+ }
+ ioflush(buf);
+ }
+ buf->buf[buf->len++] = c;
+}
+
+static int
+putquoted(struct wbuf *buf, uchar c, uchar qchar, int next)
+{
+ if (c == qchar || c == '\\' || !aisprint(c)) {
+ int n = (ioputc(buf, '\\'), 1);
+ uchar cseq;
+
+ switch (c) {
+ case '\\':
+ case '\'':
+ case '"':
+ cseq = c;
+ Charseq:
+ n += (ioputc(buf, cseq), 1);
+ break;
+ case '\a': cseq = 'a'; goto Charseq;
+ case '\b': cseq = 'b'; goto Charseq;
+ case '\f': cseq = 'f'; goto Charseq;
+ case '\r': cseq = 'r'; goto Charseq;
+ case '\t': cseq = 't'; goto Charseq;
+ case '\v': cseq = 'v'; goto Charseq;
+ case '\n': cseq = 'n'; goto Charseq;
+ default:
+ if (!next || in_range(next, '0', '7'))
+ n += bfmt(buf, "%.3o", c);
+ else
+ n += bfmt(buf, "%o", c);
+ }
+ return n;
+ }
+ if (c == '?' && (!next || next == '?')) {
+ return ioputc(buf, c), ioputc(buf, '\\'), 2;
+ }
+ return ioputc(buf, c), 1;
+}
+
+static int
+putuint(struct wbuf *buf, uvlong x, int base, bool lower)
+{
+ uchar tmp[64];
+ uchar *end = tmp + sizeof(tmp);
+ uchar *s = end;
+ switch (base) {
+ case 2:
+ do *--s = '0' + x%2; while (x >>= 1);
+ break;
+ case 8:
+ do *--s = '0' + x%8; while (x >>= 3);
+ break;
+ case 10:
+ do *--s = '0' + x%10; while (x /= 10);
+ break;
+ case 16:
+ do *--s = "0123456789ABCDEF"[x&15] | (-lower & 0x20); while (x >>= 4);
+ break;
+ default:
+ assert(0&&"base");
+ }
+ iowrite(buf, s, end - s);
+ return end - s;
+}
+
+static void
+fmterr(const char *fmt, ...)
+{
+ va_list ap;
+
+ efmt("fmt Error: ");
+ va_start(ap, fmt);
+ vbfmt(&bstderr, fmt, ap);
+ va_end(ap);
+ ioputc(&bstderr, '\n');
+ ioflush(&bstderr);
+ ioflush(&bstdout);
+ abort();
+}
+
+#define bwriteS(buf, S) (iowrite(buf, S, sizeof S - 1), sizeof S - 1)
+#define bputc(B, C) (ioputc(B, C), 1)
+
+static int
+priquals(struct wbuf *buf, int q)
+{
+ const char s[] = " const volatile", *p = s;
+ int m = sizeof s - 1;
+ if (!q) return 0;
+ else if (q == QCONST) m -= 9;
+ else if (q == QVOLATILE) p += 6, m -= 6;
+ else assert(q == (QCONST | QVOLATILE));
+ iowrite(buf, p, m);
+ return m;
+}
+static int
+pritypebefore(struct wbuf *buf, union type ty, int qual)
+{
+ const char *s, *s2;
+ union type chld;
+ int n;
+ switch (ty.t) {
+ case TYVOID: s = "void"; Prim: n = bfmt(buf, "%s", s); return n + priquals(buf, qual);
+ case TYBOOL: s = "bool"; goto Prim;
+ case TYCHAR: s = "char"; goto Prim;
+ case TYSCHAR: s = "signed char"; goto Prim;
+ case TYUCHAR: s = "unsigned char"; goto Prim;
+ case TYSHORT: s = "short"; goto Prim;
+ case TYUSHORT: s = "unsigned short"; goto Prim;
+ case TYINT: s = "int"; goto Prim;
+ case TYUINT: s = "unsigned int"; goto Prim;
+ case TYLONG: s = "long"; goto Prim;
+ case TYULONG: s = "unsigned long"; goto Prim;
+ case TYVLONG: s = "long long"; goto Prim;
+ case TYUVLONG: s = "unsigned long long"; goto Prim;
+ case TYFLOAT: s = "float"; goto Prim;
+ case TYDOUBLE: s = "double"; goto Prim;
+ case TYLDOUBLE:s = "long double"; goto Prim;
+ case TYCOMPLEXF:s = "float complex"; goto Prim;
+ case TYCOMPLEX: s = "double complex"; goto Prim;
+ case TYCOMPLEXL:s = "long double complex"; goto Prim;
+ case TYPTR:
+ chld = typechild(ty);
+ n = pritypebefore(buf, chld, ty.flag & TFCHLDQUAL);
+ if (!isptrcvtt(chld.t))
+ n += bputc(buf, ' ');
+ if (chld.t == TYARRAY || chld.t == TYFUNC)
+ n += bputc(buf, '(');
+ n += bputc(buf, '*');
+ n += priquals(buf, qual);
+ return n;
+ case TYARRAY:
+ return pritypebefore(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ case TYFUNC:
+ return pritypebefore(buf, typedata[ty.dat].ret, 0);
+ case TYSTRUCT:
+ s = "struct";
+ Tagged:
+ n = bfmt(buf, "%s %s", s, (s2 = (char *)ttypenames[typedata[ty.dat].id]) ? s2 : "(anonymous)");
+ return n + priquals(buf, qual);
+ case TYUNION:
+ s = "union";
+ goto Tagged;
+ case TYENUM:
+ s = "enum";
+ goto Tagged;
+ default:
+ return bfmt(buf, "?\?%d?",ty.t);
+ }
+}
+
+static int
+pritypeafter(struct wbuf *buf, union type ty, int qual)
+{
+ const struct typedata *td;
+ int n = 0;
+ switch (ty.t) {
+ case TYPTR:
+ if (typechild(ty).t == TYARRAY || typechild(ty).t == TYFUNC)
+ n += bputc(buf, ')');
+ n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ break;
+ case TYARRAY:
+ n += bputc(buf, '[');
+ if (typearrlen(ty))
+ n += bfmt(buf, "%u", typearrlen(ty));
+ n += bputc(buf, ']');
+ n += pritypeafter(buf, typechild(ty), ty.flag & TFCHLDQUAL);
+ break;
+ case TYFUNC:
+ td = &typedata[ty.dat];
+ n += bputc(buf, '(');
+ for (int i = 0; i < td->nmemb; ++i) {
+ n += bfmt(buf, "%ty", td->param[i]);
+ if (i < td->nmemb - 1 || td->variadic)
+ n += bwriteS(buf, ", ");
+ }
+ if (td->variadic) n += bwriteS(buf, "...");
+ else if (td->nmemb == 0 && !td->kandr) n += bwriteS(buf, "void");
+ n += bwriteS(buf, ")");
+ n += pritypeafter(buf, td->ret, 0);
+ break;
+ }
+ return n;
+}
+
+static int
+fmttype(struct wbuf *buf, union type ty, int qual)
+{
+ int n = pritypebefore(buf, ty, qual);
+ n += pritypeafter(buf, ty, qual);
+ return n;
+}
+
+static int
+putdouble(struct wbuf *buf, double x)
+{
+ char tmp[200];
+ int n = snprintf(tmp, sizeof tmp, "%f", x);
+ if (n >= sizeof tmp-1) n = snprintf(tmp, sizeof tmp, "%g", x);
+ assert(n < sizeof tmp-1);
+ iowrite(buf, tmp, n);
+ return n;
+}
+
+int
+vbfmt(struct wbuf *out, const char *fmt, va_list ap)
+{
+ bool quote, umod, lmod, zmod, lower, possign;
+ int base;
+ vlong i;
+ int pad, prec, q;
+ const char *s;
+ void *p;
+ struct token *tok;
+ union type ty;
+ double f;
+ char tmpbuf1[70], tmpbuf2[70];
+ struct wbuf tmp1 = MEMBUF(tmpbuf1, sizeof tmpbuf1);
+ struct wbuf tmp2 = MEMBUF(tmpbuf2, sizeof tmpbuf2);
+ struct wbuf *buf = out;
+ int n = 0, prevn;
+
+ while (*fmt) {
+ buf = out;
+ if (*fmt++ != '%') {
+ n += bputc(buf, fmt[-1]);
+ continue;
+ }
+ if (*fmt == '%') {
+ n += bputc(buf, *fmt++);
+ continue;
+ }
+ fmt += quote = *fmt == '\'';
+ fmt += possign = *fmt == '+';
+ pad = 0;
+ if (aisdigit(*fmt)) { /* left pad */
+ for (; aisdigit(*fmt); ++fmt)
+ pad = pad*10 + *fmt-'0';
+ if (pad) {
+ tmp1.len = 0;
+ buf = &tmp1;
+ }
+ } else if (*fmt == '-') { /* right pad */
+ if (!aisdigit(*++fmt))
+ fmterr("padding amount expected");
+ for (; aisdigit(*fmt); ++fmt)
+ pad = pad*10 - (*fmt-'0');
+ }
+ prec = -1;
+ if (*fmt == '.') {
+ if (!aisdigit(*++fmt))
+ fmterr("precision expected");
+ prec = 0;
+ for (; aisdigit(*fmt); ++fmt)
+ prec = prec*10 + *fmt-'0';
+ }
+ fmt += umod = *fmt == 'u';
+ if ((zmod = *fmt == 'z'))
+ ++fmt, lmod = 0;
+ else
+ fmt += lmod = *fmt == 'l';
+ lower = 0;
+ prevn = n;
+ switch (*fmt++) {
+ case 'c': /* character */
+ if (quote) {
+ n += bputc(buf, '\'');
+ n += putquoted(buf, va_arg(ap, int), '\'', -1);
+ n += bputc(buf, '\'');
+ } else {
+ n += bputc(buf, va_arg(ap, int));
+ }
+ break;
+ case 's': /* nullterminated string */
+ s = va_arg(ap, const char *);
+ if (quote) {
+ if (!s) {
+ n += bwriteS(buf, "(null)");
+ break;
+ }
+ QuotedStr:
+ n += bputc(buf, '"');
+ if (lmod) /* lower */
+ for (; *s; ++s) n += putquoted(buf, aisalpha(*s) ? *s|32 : *s, '"', s[1]);
+ else
+ for (; *s; ++s) n += putquoted(buf, *s, '"', s[1]);
+ n += bputc(buf, '"');
+ } else {
+ assert(s && "%s null!");
+ if (lmod) /* lower */
+ for (; *s; ++s) n += bputc(buf, aisalpha(*s) ? *s|32 : *s);
+ else
+ while (*s) n += bputc(buf, *s++);
+ }
+ break;
+ case 'S': /* string ptr + len */
+ s = va_arg(ap, const char *);
+ i = va_arg(ap, uint);
+ PriS:
+ assert(s && "%S null");
+ if (quote) {
+ n += bputc(buf, '"');
+ for (; i--; ++s) n += putquoted(buf, *s, '"', i ? s[1] : -1);
+ n += bputc(buf, '"');
+ } else {
+ iowrite(buf, s, i);
+ n += i;
+ }
+ break;
+ case 'y': /* symbol: print string literally if valid identifier, or quote it */
+ s = va_arg(ap, const char *);
+ assert(s && "%y null");
+ for (i = 0; s[i]; ++i) {
+ if (aisalpha(s[i]) || s[i] == '_' || (i > 0 && aisdigit(s[i])))
+ continue;
+ goto QuotedStr;
+ }
+ /* valid identifier */
+ while (*s) n += bputc(buf, *s++);
+ break;
+ case 'd': /* decimal */
+ base = 10;
+ Int:
+ if (base != 10) umod = 1;
+ i = lmod ? va_arg(ap, vlong)
+ : umod ? va_arg(ap, uint)
+ : zmod && sizeof(&i-&i) > sizeof(int) ? va_arg(ap, vlong)
+ : (vlong)va_arg(ap, int);
+ tmp2.len = 0;
+ if (!umod && i < 0) {
+ n += bputc(buf, '-');
+ i = -(uvlong)i;
+ } else if (possign) {
+ n += bputc(buf, '+');
+ }
+ if (quote) {
+ switch (base) {
+ case 2: n += bwriteS(buf, "0b"); break;
+ case 8: n += bwriteS(buf, "0"); break;
+ case 16: n += bwriteS(buf, "0x"); break;
+ }
+ }
+ n += putuint(prec > 0 ? &tmp2 : buf, i, base, lower);
+ if (prec > 0) {
+ int fil = prec - tmp2.len;
+ while (fil-- > 0) n += bputc(buf, '0');
+ iowrite(buf, tmp2.buf, tmp2.len);
+ }
+ break;
+ case 'o': /* octal */
+ base = 8;
+ goto Int;
+ case 'b': /* binary */
+ base = 2;
+ goto Int;
+ case 'x': case 'X': /* hexadecimal */
+ base = 16;
+ lower = fmt[-1] == 'x';
+ goto Int;
+ case 'p': /* pointer */
+ p = va_arg(ap, void *);
+ if (!p && quote) {
+ n += bwriteS(buf, "NULL");
+ } else {
+ n += bwriteS(buf, "0x");
+ tmp2.len = 0;
+ n += putuint(prec > 0 ? &tmp2 : buf, (uvlong)p, 16, 1);
+ if (prec > 0) {
+ int fil = prec - tmp2.len;
+ while (fil-- > 0) n += bputc(buf, '0');
+ iowrite(buf, tmp2.buf, tmp2.len);
+ }
+ }
+ break;
+ case 'f': /* float */
+ f = va_arg(ap, double);
+ n += putdouble(buf, f);
+ break;
+ case 't': /* token/tokentag/type */
+ switch (*fmt++) {
+ case 'k': /* tk token */
+ tok = va_arg(ap, struct token *);
+ Tok:
+ switch (tok->t) {
+ case TKXXX:
+ n += bwriteS(buf, "\?\?\?");
+ break;
+ case TKNUMLIT:
+ if (quote) {
+ n += bputc(buf, '`');
+ iowrite(buf, tok->s, tok->len);
+ n += tok->len;
+ n += bputc(buf, '\'');
+ } else {
+ s = tok->s;
+ i = tok->len;
+ goto PriS;
+ }
+ break;
+ case TKCHRLIT:
+ if (tok->wide) n += bputc(buf, tok->wideuni ? tok->wide == 1 ? 'u' : 'U' : 'L');
+ n += bputc(buf, '\'');
+ if (tok->wide == 0)
+ for (int i = 0; i < tok->len; ++i)
+ n += putquoted(buf, tok->s[i], '\'', i < tok->len - 1 ? tok->s[i+1] : -1);
+ else {
+ char p[4];
+ uint c = tok->wide == 1 ? tok->ws16[0] : tok->ws32[0];
+ int l = utf8enc(p, c);
+ if (l == 1)
+ n += putquoted(buf, *p, '\'', -1);
+ else
+ n += (iowrite(buf, p, l), l);
+ }
+ n += bputc(buf, '\'');
+ break;
+ case TKSTRLIT:
+ if (tok->wide == 0) {
+ s = tok->s;
+ i = tok->len;
+ quote = 1;
+ goto PriS;
+ } else {
+ n += bputc(buf, tok->wideuni ? tok->wide == 1 ? 'u' : 'U' : 'L');
+ n += bputc(buf, '\"');
+ for (int i = 0; i < tok->len; ++i) {
+ char p[4];
+ uint c = tok->wide == 1 ? tok->ws16[i] : tok->ws32[i];
+ int l = utf8enc(p, c);
+ if (l == 1)
+ n += putquoted(buf, *p, '\"', 0);
+ else
+ n += (iowrite(buf, p, l), l);
+ }
+ n += bputc(buf, '\"');
+ }
+ break;
+ case TKPPMACSTR:
+ if (quote) n += bputc(buf, '`');
+ n += bfmt(buf, "#%s", tok->s);
+ if (quote) n += bputc(buf, '\'');
+ break;
+ case TKPPMACARG:
+ case TKIDENT:
+ if (quote) n += bputc(buf, '`');
+ n += bfmt(buf, "%s", tok->name);
+ if (quote) n += bputc(buf, '\'');
+ break;
+ case TKEOF:
+ n += bwriteS(buf, "<end-of-file>");
+ break;
+ case TKEQU: s = "=="; C2: iowrite(buf, s, 2); n += 2; break;
+ case TKNEQ: s = "!="; goto C2;
+ case TKLTE: s = "<="; goto C2;
+ case TKGTE: s = ">="; goto C2;
+ case TKSHR: s = ">>"; goto C2;
+ case TKSHL: s = "<<"; goto C2;
+ case TKINC: s = "++"; goto C2;
+ case TKDEC: s = "--"; goto C2;
+ case TKDOTS: n += bwriteS(buf, "..."); break;
+ case TKARROW: s = "->"; goto C2;
+ case TKPPCAT: s = "##"; goto C2;
+ case TKLOGAND: s = "&&"; goto C2;
+ case TKLOGIOR: s = "||"; goto C2;
+ case TKSETADD: s = "+="; goto C2;
+ case TKSETSUB: s = "-="; goto C2;
+ case TKSETMUL: s = "*="; goto C2;
+ case TKSETDIV: s = "/="; goto C2;
+ case TKSETREM: s = "%="; goto C2;
+ case TKSETIOR: s = "|="; goto C2;
+ case TKSETXOR: s = "^="; goto C2;
+ case TKSETAND: s = "&="; goto C2;
+ case TKSETSHR: n += bwriteS(buf, ">>="); break;
+ case TKSETSHL: n += bwriteS(buf, "<<="); break;
+ default:
+ if (quote) n += bputc(buf, '`');
+ if (in_range(tok->t, TKWBEGIN_, TKWEND_)) {
+ iowrite(buf, tok->name, tok->len);
+ n += tok->len;
+ } else if (aisprint(tok->t)) {
+ n += bputc(buf, tok->t);
+ } else {
+ n += bwriteS(buf, "??");
+ }
+ if (quote) n += bputc(buf, '\'');
+ break;
+ }
+ break;
+ case 't': /* tt token tag */
+ tok = &(struct token) { va_arg(ap, int) };
+ switch (tok->t) {
+ case TKNUMLIT:
+ n += bwriteS(buf, "numeric literal");
+ break;
+ case TKSTRLIT:
+ n += bwriteS(buf, "string literal");
+ break;
+ case TKIDENT:
+ n += bwriteS(buf, "identifier");
+ break;
+ case TKEOF:
+ n += bwriteS(buf, "<end-of-file>");
+ break;
+ default:
+ if (tok->t >= TKWBEGIN_ && tok->t <= TKWEND_) {
+ static const char *tab[] = {
+ #define _(kw, c, ...) #kw,
+ #include "c/keywords.def"
+ #undef _
+ };
+ tok->s = tab[tok->t - TKWBEGIN_];
+ tok->len = strlen(tok->s);
+ }
+ goto Tok;
+ }
+ break;
+ case 'y': /* ty type */
+ ty = va_arg(ap, union type);
+ n += fmttype(buf, ty, 0);
+ break;
+ case 'q': /* tq qualified type */
+ ty = va_arg(ap, union type);
+ q = va_arg(ap, int);
+ n += fmttype(buf, ty, q);
+ break;
+ default:
+ if (fmt[-1] == ' ' || !aisprint(fmt[-1]))
+ fmterr("expected format specifier");
+ else
+ fmterr("unknown format specifier 't%c'", fmt[-1]);
+ }
+ break;
+ case 'g': /* graphics rendition (color) */
+ if (!ccopt.nocolor) n += bwriteS(buf, "\033[");
+ while (*fmt++ != '.') {
+ if (ccopt.nocolor) continue;
+ n += bputc(buf, fmt[-1]);
+ }
+ if (!ccopt.nocolor) n += bputc(buf, 'm');
+ break;
+ case 'M': /* cc mode */
+ iowrite(buf, &"C89\0C99\0C11\0C23"[ccopt.cstd*4], 3);
+ n += 3;
+ n += bwriteS(buf, " mode");
+ break;
+ default:
+ if (umod || lmod) {
+ --fmt;
+ base = 10;
+ goto Int;
+ }
+ if (fmt[-1] == ' ' || !aisprint(fmt[-1]))
+ fmterr("expected format specifier");
+ else
+ fmterr("unknown format specifier '%c'", fmt[-1]);
+ }
+ if (pad > 0) { /* left pad */
+ while (pad-- > buf->len)
+ n += bputc(out, ' ');
+ assert(buf != out);
+ iowrite(out, buf->buf, buf->len);
+ out->err |= buf->err;
+ } else if (pad < 0) { /* right pad */
+ int len = n - prevn;
+ while (pad++ < -len)
+ n += bputc(out, ' ');
+ }
+ }
+ return n;
+}
+
+int
+bfmt(struct wbuf *buf, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vbfmt(buf, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+void
+gpritype(union type ty)
+{
+ efmt("%ty\n", ty);
+ ioflush(&bstderr);
+}
+
+static uint pagesiz;
+
+extern struct embedfile embedfilesdir[];
+
+struct memfile
+mapopen(const char **err, const char *path)
+{
+ struct stat stat;
+ int fd = -1;
+ void *p = NULL;
+ struct memfile f = {0};
+ uint mapsiz;
+
+ assert("nullp" && err && path);
+
+ if (!pagesiz) pagesiz = sysconf(_SC_PAGESIZE);
+ *err = NULL;
+
+ if (*path == '@' && path[1] == ':') {
+ for (struct embedfile *e = embedfilesdir; e->name; ++e) {
+ if (!strcmp(e->name, path+2)) {
+ return (struct memfile) { (const uchar *)e->s, e->len, .statik = 1 };
+ }
+ }
+ }
+
+ if ((fd = open(path, O_RDONLY)) < 0)
+ goto Err;
+ if (fstat(fd, &stat) != 0)
+ goto Err;
+
+ if (S_ISREG(stat.st_mode)) {
+ if (stat.st_size > UINT_MAX) {
+ Big:
+ errno = EFBIG;
+ goto Err;
+ }
+ mapsiz = alignup(stat.st_size, pagesiz);
+ if ((p = mmap(NULL, mapsiz + pagesiz, PROT_READ, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+ goto Err;
+ if (mapsiz > 0 && mmap(p, mapsiz, PROT_READ, MAP_FIXED|MAP_PRIVATE, fd, 0) == MAP_FAILED)
+ goto Err;
+
+ close(fd);
+ f.p = p;
+ f.n = stat.st_size;
+ return f;
+ } else if (S_ISFIFO(stat.st_mode) || S_ISCHR(stat.st_mode)) {
+ uint cap = 0;
+ int ret;
+
+ do {
+ enum { CHUNKSIZ = 1<<10 };
+ if (f.n + CHUNKSIZ >= cap && (cap += CHUNKSIZ) < CHUNKSIZ) {
+ /* overflow */
+ free(p);
+ goto Big;
+ }
+ if (!(f.p = p ? realloc(p, cap) : malloc(cap))) {
+ free(p);
+ goto Err;
+ }
+ p = (void *)f.p;
+ ret = read(fd, (char *)p + f.n, CHUNKSIZ);
+ if (ret >= 0)
+ f.n += ret;
+ else if (errno != EAGAIN && errno != EWOULDBLOCK)
+ goto Err;
+ } while (ret != 0);
+
+ close(fd);
+ fd = -1;
+ mapsiz = alignup(f.n, pagesiz);
+ if ((f.p = mmap(NULL, mapsiz + pagesiz, PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) {
+ free(p);
+ goto Err;
+ }
+ memcpy((void *)f.p, p, f.n);
+ free(p);
+ mprotect((void *)f.p, mapsiz + pagesiz, PROT_READ);
+ return f;
+ } else {
+ *err = "Not a file";
+ }
+
+Err:
+ if (fd >= 0) close(fd);
+ if (!*err) *err = strerror(errno);
+ return f;
+}
+
+void
+mapclose(struct memfile *f)
+{
+ assert(f->p);
+ if (!f->statik)
+ munmap((void *)f->p, alignup(f->n, pagesiz) + pagesiz);
+ memset(f, 0, sizeof *f);
+}
+
+void *
+mapzeros(uint N)
+{
+ void *p = mmap(NULL, N, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ return p == MAP_FAILED ? NULL : p;
+}
+
+void
+_assertfmt(const char *file, int line, const char *func, const char *expr)
+{
+ ioflush(&bstdout);
+ efmt("%s:%d: %s: Assertion `%s' failed.\n", file, line, func, expr);
+ ioflush(&bstderr);
+}
+
+struct fileuid {
+ long dev;
+ union {
+ long ino;
+ const char *str;
+ };
+};
+
+/* one entry per #line */
+struct linemap {
+ int phys;
+ int toline;
+ const char *tofile;
+};
+
+static struct file {
+ struct fileuid uid;
+ const char *path;
+ struct memfile f;
+ vec_of(uint) lineoffs;
+ vec_of(struct linemap) linemap;
+ bool once;
+ bool seen;
+ internstr guardmac;
+} *fileht[1<<SPANFILEBITS];
+static int nfiles;
+
+int
+getpredeffile(struct memfile **pf, const char *name)
+{
+ struct file *f;
+ struct fileuid uid;
+ uint h, id, n = countof(fileht);
+
+ uid.dev = -11;
+ uid.ino = hashs(0, name);
+ for (id = h = uid.dev ^ uid.ino;; ++id) {
+ id &= countof(fileht) - 1;
+ f = fileht[id];
+ if (f && f->uid.dev == uid.dev && f->uid.ino == uid.ino) {
+ break;
+ } else if (!f) {
+ f = allocz(&globarena, sizeof *f, 0);
+ f->uid = uid;
+ f->path = name;
+ f->f = (struct memfile) { .statik = 1 };
+ fileht[id] = f;
+ vinit(&f->lineoffs, NULL, 10);
+ vpush(&f->lineoffs, 0);
+ ++nfiles;
+ break;
+ }
+ assert(--n > 0 && "fileht full");
+ }
+ *pf = &f->f;
+ return id;
+}
+
+int
+openfile(const char **err, struct memfile **pf, const char *path)
+{
+ struct stat st;
+ struct file *f;
+ struct fileuid uid;
+ size_t h, id, n = countof(fileht);
+
+ if (*path == '@' && path[1] == ':') {
+ /* fast path to rule out filenames we know for sure aren't builtin */
+ /* !KEEP SYNC with embedfilesdir */
+ if (path[2] != 's' /* std*.h */
+ && path[2] != 'f' /* float.h */) return -1;
+ uid.dev = -1;
+ uid.str = path;
+ h = hashs(0, path+2);
+ } else {
+ if (stat(path, &st) != 0) {
+ *err = strerror(errno);
+ return -1;
+ }
+ uid.dev = st.st_dev, uid.ino = st.st_ino;
+ h = uid.dev ^ uid.ino;
+ }
+ for (id = h;; ++id) {
+ id &= countof(fileht) - 1;
+ f = fileht[id];
+ if (f && f->uid.dev == uid.dev && (uid.dev >= 0 ? f->uid.ino == uid.ino : !strcmp(f->uid.str, uid.str))) {
+ break;
+ } else if (!f) {
+ struct memfile m;
+ m = mapopen(err, path);
+ if (*err) return -1;
+ f = allocz(&globarena, sizeof *f, 0);
+ f->uid = uid;
+ f->path = path;
+ f->f = m;
+ fileht[id] = f;
+ vinit(&f->lineoffs, NULL, 50);
+ vpush(&f->lineoffs, 0);
+ ++nfiles;
+ break;
+ }
+ assert(--n > 0 && "fileht full");
+ }
+ *pf = &f->f;
+ return id;
+}
+
+const char *
+getfilename(int id, uint atoff)
+{
+ assert((uint)id < countof(fileht) && fileht[id]);
+ if (!fileht[id]->linemap.n || !atoff)
+ return fileht[id]->path;
+ return getfilepos(NULL, NULL, id, atoff);
+}
+
+struct memfile *
+getfile(int id)
+{
+ assert((uint)id < countof(fileht) && fileht[id]);
+ return &fileht[id]->f;
+}
+
+void
+addfileline(int id, uint off)
+{
+ assert((uint)id < countof(fileht) && fileht[id]);
+ vec_of(uint) *lineoffs = (void *)&fileht[id]->lineoffs;
+ if (lineoffs->n && off > lineoffs->p[lineoffs->n-1])
+ vpush(lineoffs, off);
+}
+
+void
+setfileline(int id, uint off, int line, const char *file)
+{
+ assert((uint)id < countof(fileht) && fileht[id]);
+ vec_of(struct linemap) *linemap = (void *)&fileht[id]->linemap;
+ vec_of(uint) *lineoffs = (void *)&fileht[id]->lineoffs;
+ int phys = 2;
+ for (int i = lineoffs->n-1; i >= 0; --i) {
+ if (lineoffs->p[i] < off) {
+ phys = i+2;
+ break;
+ }
+ }
+ if (linemap->n > 0) {
+ assert(linemap->p[linemap->n-1].phys < phys);
+ if (!file) file = linemap->p[linemap->n-1].tofile;
+ }
+ vpush(linemap, ((struct linemap){ phys, line, file }));
+}
+
+const char *
+getfilepos(int *pline, int *pcol, int id, uint off)
+{
+ assert((uint)id < countof(fileht) && fileht[id]);
+ uint *offs = fileht[id]->lineoffs.p;
+ uint n = fileht[id]->lineoffs.n;
+ /* binary search over offsets array */
+ int l = 0, h = n - 1, i = 0;
+ while (l <= h) {
+ i = (l + h) / 2;
+ if (offs[i] < off) l = i + 1;
+ else if (offs[i] > off) h = i - 1;
+ else break;
+ }
+ i -= offs[i] > off;
+ int line = i + 1, col = off - offs[i] + 1;
+ const char *file = fileht[id]->path;
+ vec_of(struct linemap) *linemap = (void *)&fileht[id]->linemap;
+ if (linemap->n) {
+ /* binary search over linemap array */
+ l = 0, h = linemap->n - 1, i = 0;
+ while (l <= h) {
+ i = (l + h) / 2;
+ if (linemap->p[i].phys < line) l = i + 1;
+ else if (linemap->p[i].phys > line) h = i - 1;
+ else break;
+ }
+ i -= linemap->p[i].phys > line;
+ if (i >= 0) {
+ line = linemap->p[i].toline + (line - linemap->p[i].phys);
+ if (linemap->p[i].tofile) file = linemap->p[i].tofile;
+ }
+ }
+ if (pline) *pline = line;
+ if (pcol) *pcol = col;
+ return file;
+}
+
+bool
+isoncefile(int id, internstr *guard)
+{
+ assert(id < countof(fileht) && fileht[id]);
+ *guard = fileht[id]->guardmac;
+ return fileht[id]->once;
+}
+
+void
+markfileonce(int id, internstr guard)
+{
+ assert(id < countof(fileht) && fileht[id]);
+ fileht[id]->once = 1;
+ fileht[id]->guardmac = guard;
+}
+
+void
+markfileseen(int id)
+{
+ assert(id < countof(fileht) && fileht[id]);
+ fileht[id]->seen = 1;
+}
+
+bool
+isfileseen(int id)
+{
+ assert(id < countof(fileht) && fileht[id]);
+ return fileht[id]->seen;
+}
+
+void
+closefile(int id)
+{
+ assert(id < countof(fileht) && fileht[id]);
+ mapclose(&fileht[id]->f);
+}
+
+void
+vdiag(const struct span *span, enum diagkind kind, const char *fmt, va_list ap)
+{
+ /* to avoid concurrent invocations of the compiler mixing up the diagnostics
+ * in the unbuffered stderr output, use a separate buffer here and write()
+ * it all out bypassing stdio */
+ static char ebuf[4096];
+ static struct wbuf out = FDBUF(ebuf, sizeof ebuf, STDERR_FILENO);
+ static int depth = 0; /* needed for nested note() calls */
+
+ static const char *label[] = { "error", "warning", "note" };
+ static const char *color[] = { "%g1;31.", "%g1;35.", "%g1;36." };
+ int line, col;
+ struct memfile *f;
+ const struct span0 *loc;
+
+ ++depth;
+ if (span) {
+ loc = span->ex.len ? &span->ex : &span->sl;
+ f = getfile(loc->file);
+ const char *file = getfilepos(&line, &col, loc->file, loc->off);
+ bfmt(&out, "%s:%d:%d: ", file, line, col);
+ }
+ bfmt(&out, color[kind]);
+ bfmt(&out, "%s: %g.", label[kind]);
+ vbfmt(&out, fmt, ap);
+ bfmt(&out, "\n");
+ if (span) {
+ uint i;
+ int nmark;
+ char mark = '^';
+
+ /* find start of line */
+ for (i = loc->off - 1; i + 1 > 0 && f->p[i] != '\n'; --i) ;
+ if (i || f->p[i] == '\n') ++i;
+
+ nmark = loc->len;
+ while (i < loc->off + loc->len) {
+ int j, end;
+ int curoff = bfmt(&out, "%5d | ", line);
+ const uchar *linep = &f->p[i];
+ bool begintabs = 1;
+ for (end = 0; f->p[i] != '\n' && i < f->n; ++i, ++end) {
+ uchar c = f->p[i];
+ if (c == '\t') {
+ if (!begintabs) c = ' ';
+ } else {
+ begintabs = 0;
+ }
+ ioputc(&out, c);
+ }
+ ioputc(&out, '\n');
+ ++i;
+
+ for (j = -curoff; j < 0; ++j)
+ ioputc(&out, j == -2 ? '|' : ' ');
+ for (begintabs = 1; j < col-1; ++j) {
+ uchar c = *linep++;
+ if (c == '\t') {
+ if (!begintabs) c = ' ';
+ } else {
+ c = ' ';
+ begintabs = 0;
+ }
+ ioputc(&out, c);
+ }
+ bfmt(&out, color[kind]);
+ do {
+ ioputc(&out, mark);
+ mark = '~';
+ } while (--nmark > 0 && ++j < end);
+ col = 1;
+ ++line;
+ bfmt(&out, "%g.\n");
+ --nmark;
+ }
+ ioputc(&out, '\n');
+ }
+
+ if (span && loc == &span->ex && span->sl.len)
+ if (span->ex.file != span->sl.file || !((uint) span->sl.off - span->ex.off < span->ex.len))
+ note(&(struct span){ span->sl }, "expanded from here");
+
+ if (--depth == 0) ioflush(&out);
+}
+
+void _Noreturn
+fatal(const struct span *span, const char *fmt, ...)
+{
+ if (fmt) {
+ va_list ap;
+ va_start(ap, fmt);
+ vdiag(span, DGERROR, fmt, ap);
+ va_end(ap);
+ }
+ if (!fmt || span) efmt("Aborting due to previous error.\n");
+ exit(1);
+}
+
+int nerror, nwarn;
+enum { MAXERROR = 20 };
+
+void
+error(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ ++nerror;
+ va_start(ap, fmt);
+ vdiag(span, DGERROR, fmt, ap);
+ va_end(ap);
+ if (nerror > MAXERROR) {
+ efmt("Too many errors emitted, stopping now.\n");
+ exit(1);
+ }
+}
+
+void
+warn(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (ccopt.wnone) return;
+ if (ccopt.werror) ++nerror;
+ else ++nwarn;
+ va_start(ap, fmt);
+ vdiag(span, ccopt.werror ? DGERROR : DGWARN, fmt, ap);
+ va_end(ap);
+ if (nerror > MAXERROR) {
+ efmt("Too many errors emitted, stopping now.\n");
+ exit(1);
+ }
+}
+
+void
+note(const struct span *span, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdiag(span, DGNOTE, fmt, ap);
+ va_end(ap);
+}
+
+/*** UTF util ***/
+
+ushort *
+utf8to16(uint *ulen, struct arena **arena, const uchar *s, size_t len)
+{
+ assert(0 && "nyi");
+}
+
+uint *
+utf8to32(uint *ulen, struct arena **arena, const uchar *s, size_t len)
+{
+ uint *ret, *w;
+ const uchar *p, *end;
+ size_t n = 0;
+ bool istrunc;
+
+ if (!len) return NULL;
+
+ for (p = end = s; p < s + len; ++n) {
+ end = p;
+ if ((*p & 0xF8) == 0xF0) /* 11110xxx */
+ p += 4;
+ else if ((*p & 0xF0) == 0xE0) /* 1110xxxx */
+ p += 3;
+ else if ((*p & 0xE0) == 0xC0) /* 110xxxxx */
+ p += 2;
+ else p += 1;
+ }
+ istrunc = p > s+len;
+ if (!istrunc) end += 1;
+
+ ret = allocz(arena, n * sizeof *ret, sizeof *ret);
+ for (w = ret, p = s; p < end; ++w) {
+ if ((*p & 0xF8) == 0xF0) { /* 11110xxx */
+ *w = (uint)(p[0] & 0x07) << 18
+ | (uint)(p[1] & 0x3F) << 12
+ | (uint)(p[2] & 0x3F) << 6
+ | (uint)(p[3] & 0x3F);
+ p += 4;
+ } else if ((*p & 0xF0) == 0xE0) { /* 1110xxxx */
+ *w = (uint)(p[0] & 0x07) << 12
+ | (uint)(p[1] & 0x3F) << 6
+ | (uint)(p[2] & 0x3F);
+ p += 3;
+ } else if ((*p & 0xE0) == 0xC0) { /* 110xxxxx */
+ *w = (uint)(p[0] & 0x07) << 6
+ | (uint)(p[1] & 0x3F);
+ p += 2;
+ } else {
+ *w = *p;
+ p += 1;
+ }
+ }
+ if (istrunc) *w++ = 0xFFFD;
+ *ulen = n;
+
+ return ret;
+}
+
+int
+utf8enc(char p[4], uint cp)
+{
+ if ((cp & 0xffffff80) == 0) {
+ p[0] = cp;
+ return 1;
+ } else if ((cp & 0xfffff800) == 0) {
+ p[0] = 0xC0 | (cp >> 6 & 0x1F);
+ p[1] = 0x80 | (cp & 0x3F);
+ return 2;
+ } else if ((cp & 0xffff0000) == 0) {
+ p[0] = 0xE0 | (cp >> 12 & 0x0F);
+ p[1] = 0x80 | (cp >> 6 & 0x3F);
+ p[2] = 0x80 | (cp & 0x3F);
+ return 3;
+ } else {
+ p[0] = 0xF0 | (cp >> 18 & 0x07);
+ p[1] = 0x80 | (cp >> 12 & 0x3F);
+ p[2] = 0x80 | (cp >> 6 & 0x3F);
+ p[3] = 0x80 | (cp & 0x3F);
+ return 4;
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir.c b/src/ir.c
new file mode 100644
index 0000000..b612143
--- /dev/null
+++ b/src/ir.c
@@ -0,0 +1,689 @@
+#include "ir.h"
+#include "../obj/obj.h"
+
+uchar type2cls[NTYPETAG];
+uchar cls2siz[] = { [KI32] = 4, [KI64] = 8, [KF32] = 4, [KF64] = 8 };
+uchar cls2load[] = {
+ [KI32] = Oloadu32, [KI64] = Oloadi64,
+ [KF32] = Oloadf32, [KF64] = Oloadf64, [KPTR] = -1
+}, cls2store[] = {
+ [KI32] = Ostorei32, [KI64] = Ostorei64,
+ [KF32] = Ostoref32, [KF64] = Ostoref64, [KPTR] = -1
+};
+const uchar siz2intcls[] = { [1] = KI32, [2] = KI32, [4] = KI32, [8] = KI64 };
+
+const char *opnames[] = {
+ "?\??",
+#define _(o,...) #o,
+#include "op.def"
+#undef _
+};
+
+const uchar opnarg[] = {
+ 0,
+#define _(o,n) n,
+#include "op.def"
+#undef _
+};
+
+struct instr instrtab[MAXINSTR];
+struct use *instruse[MAXINSTR];
+int ninstr;
+static int instrfreelist;
+static struct use *usefreelist;
+static struct arena **usearena;
+struct calltab calltab;
+struct phitab phitab;
+struct dattab dattab;
+struct contab contab;
+struct addrtab addrtab;
+static ushort *addrht;
+static int naddrht;
+int visitmark;
+
+void
+irinit(struct function *fn)
+{
+ static struct call callsbuf[64];
+ static union ref *phisbuf[64];
+ static struct irdat datsbuf[64];
+ static struct xcon consbuf[64];
+ static struct addr addrsbuf[64];
+
+ assert(fn->arena && !fn->passarena);
+
+ ninstr = 0;
+ instrfreelist = -1;
+ usefreelist = NULL;
+ usearena = fn->arena;
+ vinit(&calltab, callsbuf, countof(callsbuf));
+ for (int i = 0; i < phitab.n; ++i) xbfree(phitab.p[i]);
+ vinit(&phitab, phisbuf, countof(phisbuf));
+ vinit(&contab, consbuf, countof(consbuf));
+ if (!dattab.p) vinit(&dattab, datsbuf, countof(datsbuf));
+ naddrht = 128;
+ if (!addrht) xbgrowz(&addrht, naddrht);
+ else if (addrtab.n) memset(addrht, 0, xbcap(addrht) * sizeof *addrht);
+ vinit(&addrtab, addrsbuf, countof(addrsbuf));
+
+ if (!type2cls[TYINT]) {
+ for (int i = TYBOOL; i <= TYUVLONG; ++i) {
+ int siz = targ_primsizes[i];
+ type2cls[i] = siz < 8 ? KI32 : KI64;
+ }
+ type2cls[TYFLOAT] = KF32;
+ type2cls[TYDOUBLE] = KF64;
+ type2cls[TYLDOUBLE] = KF64;
+ type2cls[TYPTR] = KPTR;
+ type2cls[TYARRAY] = KPTR;
+ cls2siz[KPTR] = targ_primsizes[TYPTR];
+ cls2load[KPTR] = targ_64bit ? Oloadi64 : Oloads32;
+ cls2store[KPTR] = targ_64bit ? Ostorei64 : Ostorei32;
+ }
+ fn->entry = fn->curblk = allocz(fn->arena, sizeof(struct block), 0);
+ fn->nblk = 1;
+ fn->entry->lprev = fn->entry->lnext = fn->entry;
+ fn->prop = FNUSE; /* builder keeps this */
+}
+
+static int
+newaddr(const struct addr *addr)
+{
+ if (addrtab.n >= naddrht/4*3 /*75% load factor */) {
+ xbgrowz(&addrht, naddrht*2);
+ memset(addrht, 0, naddrht * sizeof *addrht);
+ naddrht *= 2;
+ for (int i = 0; i < addrtab.n; ++i) { /* rehash */
+ const struct addr *addr = &addrtab.p[i];
+ for (uint h = hashb(0, addr, sizeof *addr), j = h;; ++j) {
+ if (!addrht[j &= naddrht - 1]) {
+ addrht[j] = i+1;
+ break;
+ }
+ }
+ }
+ }
+ for (uint h = hashb(0, addr, sizeof *addr), i = h;; ++i) {
+ i &= naddrht - 1;
+ if (!addrht[i]) {
+ vpush(&addrtab, *addr);
+ return (addrht[i] = addrtab.n) - 1;
+ } else if (!memcmp(&addrtab.p[addrht[i]-1], addr, sizeof *addr)) {
+ return addrht[i] - 1;
+ }
+ }
+}
+
+union ref
+newxcon(const struct xcon *con)
+{
+ assert((con->issym ^ con->isdat) || con->cls);
+ vpush(&contab, *con);
+ return mkref(RXCON, contab.n-1);
+}
+
+union irtype
+mkirtype(union type t)
+{
+ if (t.t == TYVOID || isscalar(t))
+ return (union irtype) { .cls = type2cls[scalartypet(t)] };
+ assert(isagg(t));
+ return (union irtype) { .isagg = 1, .dat = t.dat };
+}
+
+union ref
+mkintcon(enum irclass k, vlong i)
+{
+ if (i < 1l << 28 && i >= -(1l << 28)) {
+ return mkref(RICON, i);
+ } else {
+ struct xcon con = { .cls = k, .i = i };
+ if (cls2siz[k] == 4) /* check upper half is zero or -1 */
+ assert(in_range((i >> 32) + 1, 0, 1));
+ return newxcon(&con);
+ }
+}
+
+union ref
+mkfltcon(enum irclass k, double f)
+{
+ struct xcon con = { .cls = k, .f = k == KF32 ? (float) f : f };
+ return newxcon(&con);
+}
+
+union ref
+mksymref(internstr s, enum symflags symflags)
+{
+ struct xcon con = { .issym = 1, .sym = s, .flag = symflags };
+ return newxcon(&con);
+}
+
+union ref
+mkdatref(internstr name, union type ctype, uint siz, uint align,
+ const void *bytes, uint n, bool deref, bool funclocal)
+{
+ struct irdat dat = { .ctype = ctype, .align = align, .siz = siz, .name = name, .section = Srodata };
+ if (funclocal && objout.code && align >= 4 && align <= targ_primsizes[TYPTR] && siz <= 16)
+ dat.section = Stext;
+
+ assert(n <= siz && siz && align);
+ if (!name) {
+ char buf[32];
+ struct wbuf wbuf = MEMBUF(buf, sizeof buf);
+
+ bfmt(&wbuf, ".L%c.%d", dat.section == Stext ? 'L' : 'D', dattab.n);
+ ioputc(&wbuf, 0);
+ assert(!wbuf.err);
+ dat.name = name = intern(buf);
+ }
+ dat.off = objnewdat(name, dat.section, 0, siz, align);
+ uchar *p = (dat.section == Stext ? objout.textbegin : objout.rodata.p) + dat.off;
+ if (n) memcpy(p, bytes, n);
+ if (dat.section != Stext) memset(p+n, 0, siz - n);
+ vpush(&dattab, dat);
+ return newxcon(&(struct xcon){.isdat = 1, .deref = deref, .dat = dattab.n - 1, .flag = SLOCAL});
+}
+
+internstr
+xcon2sym(int ref)
+{
+ struct xcon con = contab.p[ref];
+ assert(con.issym ^ con.isdat);
+ return con.issym ? con.sym : dattab.p[con.dat].name;
+}
+
+struct instr
+mkalloca(uint siz, uint align)
+{
+ struct instr ins = { .cls = KPTR };
+ assert(ispo2(align) && align <= 16);
+ ins.op = Oalloca1 + ilog2(align);
+ ins.l = mkref(RICON, siz/align + (siz%align != 0));
+ return ins;
+}
+
+union ref
+mkcallarg(union irtype ret, uint narg, int vararg)
+{
+ struct call call = { .ret=ret, .narg=narg, .vararg=vararg };
+ assert(vararg == -1 || (uint)vararg <= narg);
+ vpush(&calltab, call);
+ return mkref(RXXX, calltab.n-1);
+}
+
+union ref
+mkaddr(struct addr addr)
+{
+ return mkref(RADDR, newaddr(&addr));
+}
+
+void
+addpred(struct block *blk, struct block *p)
+{
+ if (blk->npred == 0) {
+ blk->_pred0 = p;
+ ++blk->npred;
+ return;
+ }
+ if (blk->npred == 1) {
+ struct block *p0 = blk->_pred0;
+ blk->_pred = NULL;
+ xbgrow(&blk->_pred, 4);
+ *blk->_pred = p0;
+ }
+ xbpush(&blk->_pred, &blk->npred, p);
+}
+
+void
+delpred(struct block *blk, struct block *p)
+{
+ for (int i = 0; i < blk->npred; ++i) {
+ if (blkpred(blk, i) == p) {
+ for (int j = 0; j < blk->phi.n; ++j) {
+ union ref *phiargs = phitab.p[instrtab[blk->phi.p[j]].l.i];
+ for (int k = i; k < blk->npred - 1; ++k) {
+ phiargs[k] = phiargs[k + 1];
+ }
+ }
+ for (int k = i; k < blk->npred - 1; ++k) {
+ blkpred(blk, k) = blkpred(blk, k + 1);
+ }
+ if (--blk->npred == 1) {
+ struct block *p0 = blk->_pred[0];
+ xbfree(blk->_pred);
+ blk->_pred0 = p0;
+ }
+ return;
+ }
+ }
+ //assert(0&&"blk not in p");
+}
+
+struct block *
+newblk(struct function *fn)
+{
+ struct block *blk = allocz(fn->arena, sizeof(struct block), 0);
+ blk->id = -1;
+ return blk;
+}
+
+void
+freeblk(struct function *fn, struct block *blk)
+{
+ if (blk->npred > 1)
+ xbfree(blk->_pred);
+ blk->npred = 0;
+ blk->_pred = NULL;
+
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int ui = blk->phi.p[i];
+ union ref *r = phitab.p[instrtab[ui].l.i];
+ for (int j = 0; j < blk->npred; ++j) {
+ deluse(blk, ui, *r);
+ }
+ }
+ for (int i = 0; i < blk->ins.n; ++i) {
+ int ui = blk->ins.p[i];
+ struct instr *ins = &instrtab[ui];
+ if (ins->l.t == RTMP) deluse(blk, ui, ins->l);
+ if (ins->r.t == RTMP) deluse(blk, ui, ins->r);
+ }
+ for (int i = 0; i < 2; ++i) {
+ if (blk->jmp.arg[i].t == RTMP) deluse(blk, USERJUMP, blk->jmp.arg[i]);
+ }
+ if (blk->s1) delpred(blk->s1, blk);
+ if (blk->s2) delpred(blk->s2, blk);
+ vfree(&blk->phi);
+ vfree(&blk->ins);
+ if (blk->id != -1)
+ --fn->nblk;
+ if (blk->lprev) blk->lprev->lnext = blk->lnext;
+ if (blk->lnext) blk->lnext->lprev = blk->lprev;
+ blk->id = 1u<<31;
+}
+
+struct block *
+insertblk(struct function *fn, struct block *pred, struct block *subst)
+{
+ struct block *new = newblk(fn);
+ struct block **s = pred->s1 == subst ? &pred->s1 : &pred->s2;
+ assert(*s == subst);
+ new->lnext = pred->lnext;
+ new->lprev = pred;
+ pred->lnext->lprev = new;
+ pred->lnext = new;
+ *s = new;
+ new->jmp.t = Jb;
+ new->s1 = subst;
+ addpred(new, pred);
+ for (int i = 0; i < subst->npred; ++i) {
+ if (blkpred(subst, i) == pred) {
+ blkpred(subst, i) = new;
+ ++fn->nblk;
+ return new;
+ }
+ }
+ assert(0);
+}
+
+struct block *
+blksplitafter(struct function *fn, struct block *blk, int idx)
+{
+ struct block *new = newblk(fn);
+ ++fn->nblk;
+ new->lprev = blk;
+ new->lnext = blk->lnext;
+ blk->lnext = new;
+ new->lnext->lprev = new;
+ if (idx == -1) {
+ new->ins = blk->ins;
+ memset(&blk->ins, 0, sizeof blk->ins);
+ } else {
+ if (idx < blk->ins.n-1)
+ vpushn(&new->ins, &blk->ins.p[idx+1], blk->ins.n-idx-1);
+ blk->ins.n -= new->ins.n;
+ }
+ new->jmp = blk->jmp;
+ blk->jmp.t = Jb;
+ memset(blk->jmp.arg, 0, sizeof blk->jmp.arg);
+ for (int i = 0; i < 2; ++i) {
+ struct block *s = (&blk->s1)[i];
+ if (s) for (int i = 0; i < s->npred; ++i) {
+ if (blkpred(s, i) == blk)
+ blkpred(s, i) = new;
+ }
+ }
+ new->s1 = blk->s1, new->s2 = blk->s2;
+ blk->s1 = new, blk->s2 = NULL;
+ addpred(new, blk);
+ fn->prop &= ~FNUSE;
+ return new;
+}
+
+int
+allocinstr(void)
+{
+ int t;
+ if (instrfreelist != -1) {
+ t = instrfreelist;
+ memcpy(&instrfreelist, &instrtab[t], sizeof(int));
+ if (instruse[t]) deluses(t);
+ } else {
+ assert(ninstr < countof(instrtab));
+ t = ninstr++;
+ instruse[t] = NULL;
+ }
+ return t;
+}
+
+void
+adduse(struct block *ublk, int ui, union ref r) {
+ if (r.t != RTMP) return;
+ struct use *use;
+ if (usefreelist) {
+ use = usefreelist;
+ usefreelist = usefreelist->next;
+ } else {
+ use = alloc(usearena, sizeof *use, 0);
+ }
+ assert(use != instruse[r.i]);
+ use->next = instruse[r.i];
+ use->blk = ublk,
+ use->u = ui;
+ instruse[r.i] = use;
+}
+
+bool
+deluse(struct block *ublk, int ui, union ref r) {
+ if (r.t != RTMP) return 0;
+
+ for (struct use **puse = &instruse[r.i]; *puse; puse = &(*puse)->next) {
+ struct use *use = *puse;
+ if (use->blk == ublk && use->u == ui) {
+ *puse = use->next;
+ use->blk = 0;
+ use->u = 0;
+ use->next = usefreelist;
+ usefreelist = use;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+filluses(struct function *fn)
+{
+ struct block *blk = fn->entry;
+
+ for (int i = 0; i < ninstr; ++i)
+ deluses(i);
+
+ do {
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int ins = blk->phi.p[i];
+ union ref *phi = phitab.p[instrtab[ins].l.i];
+ for (int i = 0; i < blk->npred; ++i)
+ adduse(blk, ins, phi[i]);
+ }
+ for (int i = 0; i < blk->ins.n; ++i) {
+ int ins = blk->ins.p[i];
+ adduse(blk, ins, instrtab[ins].l);
+ adduse(blk, ins, instrtab[ins].r);
+ }
+ adduse(blk, USERJUMP, blk->jmp.arg[0]);
+ adduse(blk, USERJUMP, blk->jmp.arg[1]);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ fn->prop |= FNUSE;
+}
+
+int
+newinstr(struct block *at, struct instr ins)
+{
+ int new = allocinstr();
+ instrtab[new] = ins;
+ if (at) {
+ adduse(at, new, ins.l);
+ adduse(at, new, ins.r);
+ }
+ return new;
+}
+
+union ref
+insertinstr(struct block *blk, int idx, struct instr ins)
+{
+ int new = newinstr(blk, ins);
+ if (idx == blk->ins.n) vpush(&blk->ins, new);
+ else {
+ assert(idx >= 0 && idx < blk->ins.n);
+ vpush_(&blk->ins._vb, sizeof *blk->ins.p);
+ vresize(&blk->ins, blk->ins.n);
+ for (int i = blk->ins.n++; i > idx; --i)
+ blk->ins.p[i] = blk->ins.p[i - 1];
+ blk->ins.p[idx] = new;
+ }
+ return mkref(RTMP, new);
+}
+
+union ref
+insertphi(struct block *blk, enum irclass cls)
+{
+ int new = allocinstr();
+ union ref *refs = NULL;
+ assert(blk->npred > 0);
+ xbgrowz(&refs, blk->npred);
+ vpush(&phitab, refs);
+ instrtab[new] = mkinstr(Ophi, cls, mkref(RXXX, phitab.n - 1));
+ vpush(&blk->phi, new);
+ return mkref(RTMP, new);
+}
+
+uint
+numberinstrs(struct function *fn)
+{
+ struct block *blk = fn->entry;
+ int start = 0;
+ do {
+ blk->inumstart = start;
+ start += blk->ins.n+2;
+ } while ((blk = blk->lnext) != fn->entry);
+ return start-1;
+}
+
+static bool
+reachablerec(struct function *fn, struct block *blk)
+{
+ if (blk == fn->entry) return 1;
+ markvisited(blk);
+ if (blk->npred == 1 && !wasvisited(blkpred(blk, 0)))
+ return reachablerec(fn, blkpred(blk, 0));
+ else for (int i = 0; i < blk->npred; ++i) {
+ struct block *p = blkpred(blk, i);
+ if (!wasvisited(p) && reachablerec(fn, p)) return 1;
+ }
+ return 0;
+}
+
+bool
+blkreachable(struct function *fn, struct block *blk)
+{
+ startbbvisit();
+ return reachablerec(fn, blk);
+}
+
+/* require use */
+void
+replcuses(union ref from, union ref to)
+{
+ assert(from.t == RTMP);
+ for (struct use *use = instruse[from.i], *next; use; use = next) {
+ union ref *u;
+ int n, j;
+ next = use->next;
+ if (use->u == from.i) continue;
+ if (use->u == USERJUMP) {
+ u = &use->blk->jmp.arg[0];
+ n = 2;
+ } else if (instrtab[use->u].op == Ophi) {
+ u = phitab.p[instrtab[use->u].l.i];
+ n = use->blk->npred;
+ if (use->blk->phi.n == 0) continue; /* shouldn't happen */
+ } else {
+ u = &instrtab[use->u].l;
+ n = 2;
+ }
+
+ for (j = 0; j < n; ++j) {
+ if (u[j].bits == from.bits) {
+ u[j].bits = to.bits;
+ adduse(use->blk, use->u, to);
+ next = use;
+ break;
+ }
+ }
+ }
+}
+
+void
+deluses(int ins)
+{
+ for (struct use *use = instruse[ins], *next; use; use = next) {
+ next = use->next;
+ use->blk = 0;
+ use->u = 0;
+ use->next = usefreelist;
+ usefreelist = use;
+ }
+ instruse[ins] = NULL;
+}
+
+void
+delinstr(struct block *blk, int idx)
+{
+ int t = blk->ins.p[idx];
+ assert(idx >= 0 && idx < blk->ins.n);
+ for (int i = 0; i < 2; ++i) {
+ deluse(blk, t, (&instrtab[t].l)[i]);
+ }
+ memcpy(&instrtab[t], &instrfreelist, sizeof(int));
+ instrfreelist = t;
+ deluses(t);
+ for (int i = idx; i < blk->ins.n - 1; ++i)
+ blk->ins.p[i] = blk->ins.p[i + 1];
+ --blk->ins.n;
+}
+
+void
+delphi(struct block *blk, int idx)
+{
+ int t = blk->phi.p[idx];
+ assert(idx >= 0 && idx < blk->phi.n);
+ memcpy(&instrtab[t], &instrfreelist, sizeof(int));
+ instrfreelist = t;
+ deluses(t);
+ for (int i = idx; i < blk->phi.n - 1; ++i)
+ blk->phi.p[i] = blk->phi.p[i + 1];
+ --blk->phi.n;
+}
+
+void
+delnops(struct block *blk)
+{
+ int i, n, t;
+ /* delete trailing nops */
+ while (blk->ins.n > 0 && instrtab[t = blk->ins.p[blk->ins.n - 1]].op == Onop) {
+ --blk->ins.n;
+ deluses(t);
+ memcpy(&instrtab[t], &instrfreelist, sizeof(int));
+ instrfreelist = t;
+ }
+ /* delete rest of nops */
+ for (i = blk->ins.n - 2, n = 0; i >= 0; --i) {
+ if (instrtab[t = blk->ins.p[i]].op == Onop) {
+ deluses(t);
+ memcpy(&instrtab[t], &instrfreelist, sizeof(int));
+ instrfreelist = t;
+ ++n;
+ } else if (n) {
+ memmove(blk->ins.p+i+1, blk->ins.p+i+1+n, (blk->ins.n - n - i - 1)*sizeof *blk->ins.p);
+ blk->ins.n -= n;
+ n = 0;
+ }
+ }
+ if (n)
+ memmove(blk->ins.p, blk->ins.p + n, (blk->ins.n -= n)*sizeof *blk->ins.p);
+}
+
+void
+fillblkids(struct function *fn)
+{
+ int i = 0;
+ struct block *blk = fn->entry;
+ do blk->id = i++; while ((blk = blk->lnext) != fn->entry);
+
+ fn->prop |= FNBLKID;
+}
+
+/** Misc **/
+
+static void
+freefn(struct function *fn)
+{
+ struct block *blk = fn->entry;
+ do {
+ if (blk->npred > 1) xbfree(blk->_pred);
+ vfree(&blk->phi);
+ vfree(&blk->ins);
+ } while ((blk = blk->lnext) != fn->entry);
+}
+
+void
+irfini(struct function *fn)
+{
+ extern int nerror;
+ static union { char m[sizeof(struct arena) + (4<<10)]; struct arena *_align; } amem;
+ struct arena *passarena = (void *)&amem.m;
+ fn->passarena = &passarena;
+ if (nerror) {
+ freefn(fn);
+ return;
+ }
+
+ abi0(fn);
+ lowerintrin(fn);
+ if (ccopt.o > OPT0) {
+ mem2reg(fn);
+ freearena(fn->passarena);
+ copyopt(fn);
+ }
+ if (ccopt.o >= OPT1) {
+ doinline(fn);
+ filldom(fn);
+ if (!(fn->prop & FNUSE)) filluses(fn);
+ cselim(fn);
+ freearena(fn->passarena);
+ simpl(fn);
+ freearena(fn->passarena);
+ }
+ if (maybeinlinee(fn)) {
+ // goto Fin; XXX do this by having inline function rematerialization when symbol is actually referenced
+ }
+ lowerstack(fn);
+ freearena(fn->passarena);
+ if (ccopt.dbg.o) {
+ bfmt(ccopt.dbgout, "<< Before isel >>\n");
+ irdump(fn);
+ }
+ mctarg->isel(fn);
+ regalloc(fn);
+ freearena(fn->passarena);
+ if (objout.code)
+ mctarg->emit(fn);
+
+//Fin:
+ freearena(fn->passarena);
+ freefn(fn);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir.h b/src/ir.h
new file mode 100644
index 0000000..ab3e474
--- /dev/null
+++ b/src/ir.h
@@ -0,0 +1,353 @@
+#include "../common.h"
+#include "../type.h"
+
+enum irclass {
+ KXXX,
+ KI32, KI64, KPTR,
+ KF32, KF64,
+};
+
+#define kisint(k) in_range((k), KI32, KPTR)
+#define kisflt(k) in_range((k), KF32, KF64)
+
+union irtype {
+ struct { ushort _ : 1, cls : 15; };
+ struct { ushort isagg : 1, dat : 15; };
+ ushort bits;
+};
+
+struct irdat {
+ uchar align : 6, globl : 1;
+ uchar section;
+ union type ctype;
+ uint siz;
+ uint off;
+ internstr name;
+};
+
+enum symflags {
+ SLOCAL = 1,
+ SFUNC = 2,
+};
+struct xcon {
+ bool issym, isdat, deref;
+ uchar cls;
+ uchar flag;
+ union {
+ internstr sym;
+ int dat;
+ vlong i;
+ double f;
+ };
+};
+
+struct abiarg {
+ union irtype ty;
+ union {
+ struct { ushort _ : 1, reg : 15; };
+ struct { ushort isstk : 1, stk : 15; };
+ };
+};
+
+struct call {
+ union irtype ret;
+ ushort narg;
+ short vararg; /* first variadic arg or -1 */
+ ushort argstksiz;
+ struct abiarg *abiarg;
+ struct abiarg abiret[2];
+ uchar r2off;
+};
+
+enum refkind {
+ RXXX, /* used for empty ref (zeros), undef, and the special args of call,phi,etc */
+ RTMP, /* reference to another instruction's result */
+ RREG, /* machine register */
+ RICON, /* small integer constants */
+ RXCON, /* other constants (incl. external symbols) */
+ RADDR, /* target-specific addressing mode */
+ RTYPE, /* irtype */
+ RSTACK, /* stack base offset */
+};
+
+union ref {
+ struct { unsigned t : 3; signed i : 29; };
+ uint bits;
+};
+static_assert(sizeof(union ref) == 4);
+
+struct addr {
+ union ref base, index;
+ int shift, disp;
+};
+
+#define insrescls(ins) (oiscmp((ins).op) ? KI32 : (ins).cls)
+#define NOREF ((union ref) {0})
+#define UNDREF ((union ref) {{ 0, -1 }})
+#define ZEROREF ((union ref) {{ RICON, 0 }})
+#define mkref(t, x) ((union ref) {{ (t), (x) }})
+#define mktyperef(t) ((union ref) {{ RTYPE, (t).bits }})
+#define ref2type(r) ((union irtype) {.bits = (r).i})
+#define rswap(a,b) do { union ref _t = (a); (a) = (b); (b) = _t; } while (0)
+
+enum op {
+ Oxxx,
+#define _(o,...) O##o,
+#include "op.def"
+#undef _
+ NOPER,
+};
+
+#define oiscmp(o) in_range(o, Oequ, Ougte)
+#define oisarith(o) in_range(o, Oneg, Ougte)
+#define oisalloca(o) in_range(o, Oalloca1, Oalloca16)
+#define oisstore(o) in_range(o, Ostorei8, Ostoref64)
+#define oisload(o) in_range(o, Oloads8, Oloadf64)
+extern const char *opnames[];
+extern const uchar opnarg[];
+
+enum intrin {
+ INxxx,
+#define _(b,...) IN##b,
+#include "intrin.def"
+#undef _
+};
+
+struct instr {
+ uchar op,
+ cls; /* operation data class; also result class except for cmp ops (always i32) */
+ uchar skip : 1, /* ignore during codegen: forms part of one machine instruction */
+ keep : 1; /* for codegen, keep instr even if result seems unused */
+ uchar inplace : 1; /* set (by isel) for instructions which modify its first arg in place */
+ uchar reg; /* 0 -> no reg; else reg + 1 */
+ union ref l, r; /* args */
+};
+static_assert(sizeof(struct instr) == 4*3);
+
+enum jumpkind { JXXX, Jb, Jret, Jtrap, };
+
+struct block {
+ int id;
+ int npred;
+ int visit;
+ ushort loop;
+ int inumstart;
+ union {
+ struct block *_pred0;
+ struct block **_pred;
+ };
+ struct block *lprev, *lnext;
+ struct block *s1, *s2;
+ struct block *idom;
+ vec_of(ushort) phi;
+ vec_of(ushort) ins;
+ struct { uchar t; union ref arg[2]; } jmp;
+};
+
+#define blkpred(blk, i) 0[(blk)->npred < 2 ? &(blk)->_pred0 : &(blk)->_pred[i]]
+
+enum { USERJUMP = 0xFFFF };
+struct use { struct use *next; struct block *blk; ushort u; };
+
+enum { MAXREGS = 64 };
+/** register set **/
+typedef uvlong regset;
+#define BIT(x) (1ull<<(x))
+#define rsset(pS, r) (*(pS) |= 1ull << (r))
+#define rsclr(pS, r) (*(pS) &=~ (1ull << (r)))
+#define rstest(S, r) ((S) >> (r) & 1)
+static inline bool
+rsiter(int *i, uvlong rs)
+{
+ if (*i > 63) return 0;
+ uvlong mask = -(1ull << *i);
+ if ((rs & mask) == 0) return 0;
+ *i = lowestsetbit(rs & mask);
+ return 1;
+}
+
+enum {
+ FNBLKID = 1<<0,
+ FNUSE = 1<<1,
+ FNRPO = 1<<2,
+ FNDOM = 1<<3,
+};
+struct function {
+ struct arena **arena, **passarena;
+ internstr name;
+ struct block *entry, *curblk;
+ struct use *use;
+ short *nuse;
+ union type fnty, retty;
+ struct abiarg *abiarg, abiret[2];
+ uint prop;
+ uint nblk;
+ int stksiz;
+ ushort nabiarg, nabiret;
+ bool globl;
+ bool isleaf;
+ bool inlin;
+ regset regusage;
+};
+
+#define FREQUIRE(_prop) assert((fn->prop & (_prop)) == (_prop) && "preconditions not met")
+
+enum objkind { OBJELF };
+
+struct mctarg {
+ short gpr0, /* first gpr */
+ ngpr, /* gpr count */
+ bpr, /* frame/base pointer reg */
+ gprscratch, fprscratch, /* scratch registers for regalloc */
+ fpr0, /* first fpr */
+ nfpr; /* fpr count */
+ regset rcallee, /* callee-saved */
+ rglob; /* globally live (never used for regalloc) */
+ const char (*rnames)[6];
+ enum objkind objkind;
+ /* abiret: lower return type:
+ * scalar/small struct -> returns number of regs (1..2),
+ * r & cls filled with reg and irclass of each scalar return,
+ * for struct, r2off filled with byte offset within struct for 2nd reg
+ * big struct -> returns 0, is passed via hidden pointer argument,
+ * r[0] contains register for returning said pointer or -1,
+ * r[1] contains register for hidden pointer argument,
+ * *ni is set to 1 if said register is the first ABI integer argument
+ */
+ int (*abiret)(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype);
+ /* abiarg: lower argument type:
+ * scalar/small struct -> returns number of regs (1..2),
+ * r & cls filled with reg and irclass of each scalar arg
+ * for struct, r2off filled with byte offset within struct for 2nd reg
+ * if reg == -1 -> stack
+ * big struct -> returns 0,
+ * if passed in stack cls[0] == 0, r[0] == negative SP offset
+ * if passed by pointer cls[0] == KPTR, r[0] contains integer register
+ * or negative SP offset if stack
+ */
+ int (*abiarg)(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype);
+ void (*vastart)(struct function *, struct block *, int *curi);
+ void (*vaarg)(struct function *, struct block *, int *curi);
+ void (*isel)(struct function *);
+ void (*emit)(struct function *);
+};
+
+enum { MAXINSTR = 1<<15 };
+
+/** ir.c **/
+extern uchar type2cls[];
+extern uchar cls2siz[];
+extern uchar cls2load[];
+extern uchar cls2store[];
+extern const uchar siz2intcls[];
+extern struct instr instrtab[];
+extern struct use *instruse[];
+extern struct calltab {vec_of(struct call);} calltab;
+extern struct phitab {vec_of(union ref *);} phitab;
+extern struct dattab {vec_of(struct irdat);} dattab;
+extern struct contab {vec_of(struct xcon);} contab;
+extern struct addrtab {vec_of(struct addr);} addrtab;
+extern int visitmark;
+#define mkinstr(O, C, ...) ((struct instr) { .op = (O), .cls = (C), .reg=0, __VA_ARGS__ })
+#define mkarginstr(ty, x) mkinstr(Oarg, 0, mktyperef(ty), (x))
+void irinit(struct function *);
+void irfini(struct function *);
+#define cls2type(k) ((union irtype){.cls=(k)})
+union irtype mkirtype(union type);
+union ref newxcon(const struct xcon *);
+union ref mkintcon(enum irclass, vlong);
+union ref mkfltcon(enum irclass, double);
+#define iscon(r) in_range((r).t, RICON, RXCON)
+#define concls(r) ((r).t == RICON ? KI32 : contab.p[(r).i].cls)
+#define isintcon(r) (iscon(r) && kisint(concls(r)))
+#define isfltcon(r) ((r).t == RXCON && kisflt(contab.p[(r).i].cls))
+#define isnumcon(r) ((r).t == RICON || ((r).t == RXCON && contab.p[(r).i].cls))
+#define isaddrcon(r,derefok) ((r).t == RXCON && !contab.p[(r).i].cls && (derefok || !contab.p[(r).i].deref))
+#define intconval(r) ((r).t == RICON ? (r).i : contab.p[(r).i].i)
+#define fltconval(r) ((r).t == RICON ? (r).i : contab.p[(r).i].f)
+union ref mksymref(internstr, enum symflags);
+union ref mkdatref(internstr sym, union type ctype, uint siz, uint align,
+ const void *, uint n, bool deref, bool funclocal);
+internstr xcon2sym(int ref);
+struct instr mkalloca(uint siz, uint align);
+union ref mkcallarg(union irtype ret, uint narg, int vararg);
+#define mkintrin(B, C, N) mkinstr(Ointrin, C, {{.t=RICON,B}}, mkcallarg((union irtype){{0}},N,-1))
+union ref mkaddr(struct addr);
+void addpred(struct block *blk, struct block *p);
+
+struct block *newblk(struct function *);
+void freeblk(struct function *, struct block *);
+struct block *insertblk(struct function *, struct block *pred, struct block *subst);
+struct block *blksplitafter(struct function *, struct block *, int idx);
+void adduse(struct block *ublk, int ui, union ref r);
+int newinstr(struct block *at, struct instr ins);
+union ref insertinstr(struct block *, int idx, struct instr);
+union ref insertphi(struct block *, enum irclass cls);
+void replcuses(union ref from, union ref to);
+bool deluse(struct block *ublk, int ui, union ref r);
+void deluses(int ins);
+void filluses(struct function *);
+void delinstr(struct block *, int idx);
+void delphi(struct block *, int idx);
+void delnops(struct block *blk);
+void delpred(struct block *blk, struct block *p);
+void fillblkids(struct function *);
+#define startbbvisit() (void)(++visitmark)
+#define wasvisited(blk) ((blk)->visit == visitmark)
+#define markvisited(blk) ((blk)->visit = visitmark)
+uint numberinstrs(struct function *);
+bool blkreachable(struct function *fn, struct block *blk);
+
+/** builder.c **/
+union ref irbinop(struct function *, enum op, enum irclass, union ref lhs, union ref rhs);
+union ref irunop(struct function *, enum op, enum irclass, union ref);
+union ref addinstr(struct function *, struct instr);
+union ref addphi(struct function *, enum irclass, union ref []);
+void useblk(struct function *, struct block *);
+void putbranch(struct function *, struct block *);
+void putcondbranch(struct function *, union ref arg, struct block *t, struct block *f);
+void putreturn(struct function *, union ref r0, union ref r1);
+void puttrap(struct function *);
+
+/** fold.c **/
+bool foldbinop(union ref *to, enum op, enum irclass, union ref l, union ref r);
+bool foldunop(union ref *to, enum op, enum irclass, union ref);
+
+/** irdump.c **/
+void irdump(struct function *);
+
+/** mem2reg.c **/
+void mem2reg(struct function *);
+
+/** ssa.c **/
+void copyopt(struct function *);
+
+/** cfg.c **/
+void sortrpo(struct function *);
+void filldom(struct function *);
+void fillloop(struct function *);
+
+/** abi0.c **/
+void abi0(struct function *);
+void abi0_call(struct function *, struct instr *, struct block *blk, int *curi);
+
+/** simpl.c **/
+void simpl(struct function *);
+
+/** cselim.c **/
+void cselim(struct function *);
+
+/** inliner.c **/
+bool maybeinlinee(struct function *);
+void doinline(struct function *);
+
+/** intrin.c **/
+void lowerintrin(struct function *);
+
+/** stack.c **/
+void lowerstack(struct function *);
+
+/** regalloc.c **/
+void regalloc(struct function *);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_abi0.c b/src/ir_abi0.c
new file mode 100644
index 0000000..a3687d9
--- /dev/null
+++ b/src/ir_abi0.c
@@ -0,0 +1,462 @@
+#include "ir.h"
+
+/** This pass adds in ABI arguments/returns register mappings
+ ** and lowers aggregate params/args/returns into scalars
+ **
+ ** invariant: all `call` instructions when doing this pass shall be preceded by
+ ** exactly narg `arg` instructions with no other instructions in between
+ **/
+
+struct abiargsvec { vec_of(struct abiarg); };
+
+static int
+abiret(struct abiarg abiret[2], struct abiargsvec *abiargs, uchar *r2off, int *ni, union irtype retty)
+{
+ short r[2];
+ uchar cls[2];
+ int retreg = 0;
+ retreg = mctarg->abiret(r, cls, r2off, ni, retty);
+ if (retty.isagg) {
+ if (!retreg) {
+ vpush(abiargs, ((struct abiarg) { cls2type(KPTR), .reg = r[1] }));
+ if (r[0] == -1) {
+ memset(abiret, 0, 2*sizeof *abiret);
+ } else {
+ abiret[0].ty = cls2type(KPTR);
+ abiret[0].reg = r[0];
+ }
+ }
+ } else if (retty.cls) {
+ assert(retreg == 1);
+ }
+ for (int i = 0; i < retreg; ++i) {
+ abiret[i].ty = cls2type(cls[i]);
+ abiret[i].isstk = 0;
+ abiret[i].reg = r[i];
+ }
+ return retreg;
+}
+
+static int
+abiarg(struct abiargsvec *abiargs, uchar *r2off, int *ni, int *nf, int *ns, union irtype ty)
+{
+ short r[2];
+ uchar cls[2];
+ int ret = mctarg->abiarg(r, cls, r2off, ni, nf, ns, ty);
+ if (!ret) { /* in stack */
+ vpush(abiargs, ((struct abiarg) { ty, .isstk = 1, .stk = r[0] }));
+ } else if (ret == 1 && ty.isagg && cls[0] == KPTR) { /* aggregate by pointer */
+ vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] }));
+ } else { /* by regs */
+ vpush(abiargs, ((struct abiarg) { cls2type(cls[0]), .reg = r[0] }));
+ if (ret == 2)
+ vpush(abiargs, ((struct abiarg) { cls2type(cls[1]), .reg = r[1] }));
+ }
+ return ret;
+}
+
+static struct instr
+copyparam(struct function *fn, int *curi, int param, struct abiarg abi)
+{
+ struct instr par = mkinstr(Oparam, abi.ty.cls, mkref(RICON, param), mktyperef(abi.ty));
+ if (!abi.isstk) { /* reg */
+ assert(!abi.ty.isagg);
+ return par;
+ }
+ par.r = mktyperef((union irtype){.cls = KPTR});
+ if (!abi.ty.isagg) { /* scalar in stack */
+ enum op ld;
+ par.cls = KPTR;
+ if (abi.ty.cls == KPTR) abi.ty.cls = siz2intcls[cls2siz[abi.ty.cls]];
+ switch (abi.ty.cls) {
+ default: assert(0);
+ case KI32: ld = Oloads32; break;
+ case KI64: ld = Oloadi64; break;
+ case KF32: ld = Oloadf32; break;
+ case KF64: ld = Oloadf64; break;
+ }
+ return mkinstr(ld, abi.ty.cls, insertinstr(fn->entry, (*curi)++, par));
+ } else { /* aggregate in stack */
+ par.cls = KPTR;
+ return par;
+ }
+}
+
+static void
+patchparam(struct function *fn, int *curi, int *param, int tydat, int nabi, struct abiarg abi[2], uchar r2off)
+{
+ struct block *blk = fn->entry;
+ assert(in_range(nabi,1,2));
+
+ for (; *curi < blk->ins.n; ++*curi) {
+ struct instr *ins = &instrtab[blk->ins.p[*curi]];
+ if (ins->op != Oparam) continue;
+ assert(ins->r.t == RTYPE
+ && ins->r.i == (tydat < 0 ? abi[0].ty : (union irtype){.isagg=1, .dat=tydat}).bits);
+ if (abi[0].ty.isagg || tydat < 0) {
+ /* aggregate in stack or scalar, just copy */
+ assert(nabi == 1);
+ *ins = copyparam(fn, curi, *param, abi[0]);
+ } else { /* aggregate in registers, materialize */
+ union ref alloc, r[2];
+ struct instr st;
+ const struct typedata *td;
+ uint nalloc;
+ uint align;
+
+ assert(tydat >= 0);
+ td = &typedata[tydat];
+ assert(td->siz <= 16 && td->align <= 16);
+ align = td->siz <= 4 ? 4 : alignup(td->align, 8);
+ nalloc = td->siz/align + (td->siz%align != 0);
+ *ins = mkinstr(Oalloca1 + ilog2(align), KPTR, mkref(RICON, nalloc));
+ alloc = mkref(RTMP, ins - instrtab);
+ r[0] = insertinstr(blk, ++*curi, copyparam(fn, NULL, *param, abi[0]));
+ if (nabi > 1)
+ r[1] = insertinstr(blk, ++*curi, copyparam(fn, NULL, ++*param, abi[1]));
+ /* transform
+ * %x = copy %p
+ * into
+ * %x = alloca...
+ * store* %x, %a
+ * store* %x + N, %b
+ */
+ st = mkinstr(cls2store[abi[0].ty.cls], 0, alloc, r[0]);
+ insertinstr(blk, ++*curi, st);
+ if (nabi > 1) {
+ struct instr tmp = mkinstr(Oadd, KPTR, alloc, mkref(RICON, r2off));
+ st = mkinstr(cls2store[abi[1].ty.cls], 0, insertinstr(blk, ++*curi, tmp), r[1]);
+ insertinstr(blk, ++*curi, st);
+ }
+ }
+ ++*param;
+ ++*curi;
+ break;
+ }
+}
+
+static void
+load2regs(union ref out[2], union irtype typ, union ref src, int nabi, struct abiarg abi[2], uchar r2off, struct block *blk, int *curi)
+{
+ uint align = typedata[typ.dat].align;
+ uint siz = typedata[typ.dat].siz;
+ if (src.t == RTMP && oisalloca(instrtab[src.i].op)) {
+ /* use actual alignment as opposed to min required type alignment */
+ uint aalign = 1 << (instrtab[src.i].op - Oalloca1);
+ assert(aalign >= align);
+ align = aalign;
+ }
+ /* deconstruct into
+ * %a = load* %x
+ * (%b = load* %x + N)
+ */
+ /* XXX this generates pretty bad code for small-alignment structs even on platforms where unaligned loads are available.. */
+ if (align >= 4) {
+ for (int i = 0; i < nabi; ++i) {
+ struct instr ins = {0};
+ union ref temp;
+ switch (ins.cls = abi[i].ty.cls) {
+ default: assert(0);
+ case KI32: ins.op = Oloadu32; break;
+ case KI64: ins.op = Oloadi64; break;
+ case KF32: ins.op = Oloadf32; break;
+ case KF64: ins.op = Oloadf64; break;
+ }
+ if (i == 0)
+ ins.l = src;
+ else {
+ struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, r2off));
+ ins.l = insertinstr(blk, (*curi)++, adr);
+ }
+ temp = insertinstr(blk, (*curi)++, ins);
+ //insertinstr(blk, (*curi)++, mkarginstr(abi[i].ty, temp));
+ out[i] = temp;
+ }
+ } else {
+ for (int i = 0; i < nabi; ++i) {
+ struct instr ld = {0};
+ union ref reg, temp;
+ uint n = cls2siz[abi[i].ty.cls] / align;
+ assert(n > 0);
+ ld.op = Oloadu8 + ilog2(align)*2;
+ ld.cls = abi[i].ty.cls;
+ for (int o = 0; o < n && (i*cls2siz[ld.cls])+o*align < siz; ++o) {
+ if (i+o == 0)
+ ld.l = src;
+ else {
+ struct instr adr = mkinstr(Oadd, KPTR, src, mkref(RICON, (i == 0 ? 0 : r2off) + o*align));
+ ld.l = insertinstr(blk, (*curi)++, adr);
+ }
+ temp = insertinstr(blk, (*curi)++, ld);
+ if (o > 0) {
+ union ref t = insertinstr(blk, (*curi)++, mkinstr(Oshl, ld.cls, temp, mkref(RICON, o*align*8)));
+ reg = insertinstr(blk, (*curi)++, mkinstr(Oior, ld.cls, reg, t));
+ } else {
+ reg = temp;
+ }
+ }
+ //insertinstr(blk, arginst++, mkarginstr(abi[i].ty, reg));
+ out[i] = reg;
+ }
+ }
+}
+
+static int
+patcharg(struct block *blk, int *icall, struct call *call,
+ int argidx, int nabi, struct abiarg abi[2], uchar r2off)
+{
+ int arginst = *icall - (call->narg - argidx);
+ struct instr *arg = &instrtab[blk->ins.p[arginst]];
+ assert(arg->op == Oarg && arg->l.t == RTYPE);
+ if (ref2type(arg->l).isagg) { /* aggregate argument */
+ if (abi[0].ty.isagg) { /* aggregate in stack */
+ /* XXX do this better.. */
+ /* ptr %dst = arg <stk dst> */
+ /* (blit %dst, %src) */
+ union ref dst = mkref(RTMP, arg - instrtab);
+ uint align = typedata[abi->ty.dat].align, siz = typedata[abi->ty.dat].siz;
+ union ref src = arg->r;
+ if (src.t == RTMP && oisalloca(instrtab[src.i].op)) {
+ align = 1 << (instrtab[src.i].op - Oalloca1);
+ }
+ assert(align <= 8);
+ arg->cls = KPTR;
+ arg->r = mkref(RICON, abi->stk);
+ for (uint off = 0; off < siz; off += align) {
+ union ref sadr = off == 0 ? src : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, src, mkref(RICON, off)));
+ union ref tmp = insertinstr(blk, ++arginst, mkinstr(Oloads8+2*ilog2(align), align < 8 ? KI32 : KI64, sadr));
+ union ref dadr = off == 0 ? dst : insertinstr(blk, ++arginst, mkinstr(Oadd, KPTR, dst, mkref(RICON, off)));
+ insertinstr(blk, ++arginst, mkinstr(Ostorei8+ilog2(align), 0, dadr, tmp));
+ }
+ *icall = arginst + (call->narg - argidx);
+ return 1;
+ } else if (abi[0].ty.cls == KPTR) { /* aggregate by pointer */
+ arg->cls = KPTR;
+ return 1;
+ } else { /* aggregate in registers */
+ union ref r[2];
+ delinstr(blk, arginst);
+ load2regs(r, ref2type(arg->l), arg->r, nabi, abi, r2off, blk, &arginst);
+ for (int i = 0; i < nabi; ++i)
+ insertinstr(blk, arginst++, mkinstr(Oarg, 0, mktyperef(abi[i].ty), r[i]));
+ *icall = arginst + (call->narg - argidx - 1);
+ return nabi;
+ }
+ } else { /* normal scalar argument */
+ return 1;
+ }
+}
+void
+abi0_call(struct function *fn, struct instr *ins, struct block *blk, int *curi)
+{
+ union ref retmem;
+ struct abiarg abiargsbuf[32];
+ struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))};
+ bool sretarghidden = 0;
+ int ni, nf, ns, vararg, nret = 0;
+ struct call *call = &calltab.p[ins->r.i];
+
+ vararg = call->vararg;
+ ni = nf = ns = 0;
+ assert(!ins->cls == !call->ret.bits);
+ nret = abiret(call->abiret, &abiargs, &call->r2off, &ni, call->ret);
+ if (call->ret.isagg) { /* adjust struct return */
+ union irtype retty = call->ret;
+ struct typedata *td = &typedata[retty.dat];
+ uint align = td->align, ralign;
+ struct instr alloca;
+ int ialloca;
+ for (int i = 0; i < nret; ++i)
+ align = align < (ralign = cls2siz[call->abiret[i].ty.cls]) ? ralign : align;
+ alloca = mkalloca(td->siz, align);
+
+ sretarghidden = ni == 0;
+ /* swap alloca and call temps so users of original call point to alloca */
+ retmem = insertinstr(blk, ialloca = (*curi)++ - call->narg, *ins);
+ *ins = alloca;
+ blk->ins.p[ialloca] = ins - instrtab;
+ blk->ins.p[*curi] = retmem.i;
+ ins = &instrtab[retmem.i];
+ retmem.i = blk->ins.p[ialloca];
+
+ if (!nret) /* hidden pointer argument */
+ insertinstr(blk, (*curi)++ - call->narg,
+ mkinstr(Oarg, 0, mktyperef((union irtype){.cls=KPTR}), retmem));
+ }
+
+ /* adjust args */
+ for (int i = 0, i2 = ni + sretarghidden; i < call->narg; ++i) {
+ int arginst = *curi - (call->narg - i);
+ struct instr *arg = &instrtab[blk->ins.p[arginst]];
+ union irtype pty = ref2type(arg->l);
+ uchar r2off;
+ int first = abiargs.n;
+ int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty);
+ ret = patcharg(blk, curi, call, i, ret, &abiargs.p[first], r2off);
+ if (call->vararg == i) vararg = i2;
+ i2 += ret;
+ }
+ call->argstksiz = ns;
+ /* adjust return */
+ if (call->ret.isagg) {
+ ins->cls = 0;
+ if (!nret) { /* hidden pointer argument */
+ ins->cls = 0;
+ if (!call->abiret[0].isstk) {
+ /* the result location pointer is also returned by the callee, e.g. in x86 */
+ ins->cls = KPTR;
+ ++nret;
+ /* even if this is not used, the register copy
+ * must be emitted for the register allocator to know */
+ }
+ } else { /* aggregate returned in regs */
+ union ref r[2];
+ struct instr ret2;
+ assert(in_range(nret, 1, 2));
+ ins->cls = call->abiret[0].ty.cls;
+ r[0] = mkref(RTMP, ins - instrtab);
+ if (nret == 2) {
+ ret2 = mkinstr(Ocall2r, call->abiret[1].ty.cls, r[0]);
+ r[1] = insertinstr(blk, ++*curi, ret2);
+ }
+ for (int i = 0; i < nret; ++i) {
+ struct instr store = { cls2store[call->abiret[i].ty.cls] };
+ if (i == 0) {
+ store.l = retmem;
+ } else {
+ struct instr addr = mkinstr(Oadd, KPTR, retmem, mkref(RICON, call->r2off));
+ store.l = insertinstr(blk, ++*curi, addr);
+ }
+ store.r = r[i];
+ insertinstr(blk, ++*curi, store);
+ }
+ }
+ }
+
+ if (call->ret.isagg) call->ret = (union irtype){0};
+ call->vararg = vararg;
+ call->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof(struct abiarg), 0);
+ call->narg = abiargs.n;
+ vfree(&abiargs);
+}
+
+void
+abi0(struct function *fn)
+{
+ struct abiarg abiargsbuf[32];
+ uint nparam = typedata[fn->fnty.dat].nmemb;
+ const union type *paramty = typedata[fn->fnty.dat].param;
+ struct abiargsvec abiargs = {VINIT(abiargsbuf, countof(abiargsbuf))};
+ int rvovar = -1;
+ int ni = 0, nf = 0, ns = 0, istart = 0;
+ uchar r2off;
+ struct block *blk;
+ union ref sret = {0};
+
+ FREQUIRE(FNUSE);
+
+ if (fn->retty.t == TYVOID) {
+ fn->nabiret = 0;
+ } else {
+ fn->nabiret = abiret(fn->abiret, &abiargs, &r2off, &ni, mkirtype(fn->retty));
+ if (!fn->nabiret && isagg(fn->retty)) { /* ret agg by hidden pointer */
+ struct instr param = copyparam(fn, NULL, 0, abiargs.p[0]);
+ sret = insertinstr(fn->entry, 0, param);
+ ++istart;
+ /* increment real param ordinals */
+ for (int i = 1; i < fn->entry->ins.n; ++i) {
+ struct instr *ins = &instrtab[fn->entry->ins.p[i]];
+ if (ins->op == Oparam) ++ins->l.i;
+ }
+ }
+ }
+
+ /* adjust params */
+ for (int i = 0, param = abiargs.n; i < nparam; ++i) {
+ union irtype pty = mkirtype(paramty[i]);
+ int first = abiargs.n;
+ uchar r2off;
+ int ret = abiarg(&abiargs, &r2off, &ni, &nf, &ns, pty);
+ patchparam(fn, &istart, &param, pty.isagg ? pty.dat : -1, ret+!ret, &abiargs.p[first], r2off);
+ }
+ fn->abiarg = alloccopy(fn->arena, abiargs.p, abiargs.n * sizeof *abiargs.p, 0);
+ fn->nabiarg = abiargs.n;
+ vfree(&abiargs);
+
+ if (!fn->nabiret && isagg(fn->retty)) {
+ /* for structures returned by hidden pointer argument,
+ * if all return instrs return local var X, make X point to the result location,
+ * (return value optimization (RVO)) */
+ blk = fn->entry;
+ do {
+ union ref arg = blk->jmp.arg[0];
+ if (blk->jmp.t != Jret) continue;
+ if (!arg.bits) continue;
+ if (arg.t != RTMP || !oisalloca(instrtab[arg.i].op)) {
+ rvovar = -1;
+ break;
+ }
+ if (rvovar == -1) {
+ rvovar = arg.i;
+ } else if (arg.i != rvovar) {
+ rvovar = -1;
+ break;
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+ if (rvovar != -1)
+ instrtab[rvovar] = mkinstr(Ocopy, KPTR, sret);
+ }
+
+ blk = fn->entry->lnext;
+ int id = 1;
+ do {
+ /* adjust vaargs and calls */
+ for (int iinstr = 0; iinstr < blk->ins.n; ++iinstr) {
+ struct instr *ins = &instrtab[blk->ins.p[iinstr]];
+ if (ins->op == Ovastart) mctarg->vastart(fn, blk, &iinstr);
+ else if (ins->op == Ovaarg) mctarg->vaarg(fn, blk, &iinstr);
+ else if (ins->op == Ocall) abi0_call(fn, ins, blk, &iinstr);
+ }
+
+ /* adjust returns */
+ if (isagg(fn->retty) && blk->jmp.t == Jret && blk->jmp.arg[0].bits) {
+ assert(!blk->jmp.arg[1].bits);
+ if (fn->nabiret) { /* aggregate return in register(s) */
+ union ref r[2];
+ int curi = blk->ins.n;
+ load2regs(r, mkirtype(fn->retty), blk->jmp.arg[0], fn->nabiret, fn->abiret, r2off, blk, &curi);
+ for (int i = 0; i < fn->nabiret; ++i) {
+ blk->jmp.arg[i] = r[i];
+ adduse(blk, USERJUMP, r[i]);
+ }
+ } else {
+ /* aggregate return (arg[0] is pointer to return value) */
+ if (rvovar == -1) {
+ /* blit %sret, %arg */
+ union irtype typ = mkirtype(fn->retty);
+ insertinstr(blk, blk->ins.n, mkarginstr(typ, sret));
+ insertinstr(blk, blk->ins.n, mkarginstr(typ, blk->jmp.arg[0]));
+ insertinstr(blk, blk->ins.n, mkintrin(INstructcopy, 0, 2));
+ } else assert(blk->jmp.arg[0].bits == mkref(RTMP, rvovar).bits);
+ if (fn->abiret[0].ty.cls) {
+ blk->jmp.arg[0] = rvovar == -1 ? sret : mkref(RTMP, rvovar);
+ adduse(blk, USERJUMP, blk->jmp.arg[0]);
+ }
+ else memset(blk->jmp.arg, 0, sizeof blk->jmp.arg);
+ }
+ }
+ blk->id = id++;
+ } while ((blk = blk->lnext) != fn->entry);
+
+
+ /* vaargs might break these */
+ if (!(fn->prop & FNUSE)) filluses(fn);
+ fn->prop &= ~(FNBLKID | FNRPO);
+
+ if (ccopt.dbg.a) {
+ bfmt(ccopt.dbgout, "<< After abi0 >>\n");
+ irdump(fn);
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_builder.c b/src/ir_builder.c
new file mode 100644
index 0000000..206e66d
--- /dev/null
+++ b/src/ir_builder.c
@@ -0,0 +1,307 @@
+#include "ir.h"
+
+/* binary arithmetic builder with peephole optimizations */
+union ref
+irbinop(struct function *fn, enum op op, enum irclass k, union ref l, union ref r)
+{
+ static const union ref ONE = {.t=RICON, .i=1};
+ vlong iv;
+ union ref c;
+
+ if (foldbinop(&c, op, k, l, r))
+ return c;
+
+ switch (op) {
+ case Oadd:
+ if (l.bits == ZEROREF.bits) return r; /* 0 + x ==> x */
+ if (r.bits == ZEROREF.bits) return l; /* x + 0 ==> x */
+ break;
+ case Osub:
+ if (r.bits == ZEROREF.bits) return l; /* x - 0 ==> x */
+ if (kisint(k) && l.bits == r.bits) return ZEROREF; /* x - x ==> 0 */
+ break;
+ case Omul:
+ if (isnumcon(l)) rswap(l, r); /* put const in rhs */
+ if (kisflt(k)) break;
+ if (r.bits == ZEROREF.bits) /* x * 0 ==> 0 */
+ return ZEROREF;
+ if (r.bits == ONE.bits) /* x * 1 ==> x */
+ return l;
+ if (isintcon(r) && ispo2(iv = intconval(r))) {
+ /* x * 2^y ==> x << y */
+ op = Oshl;
+ r = mkref(RICON, ilog2(iv));
+ }
+ break;
+ case Odiv:
+ break;
+ case Oudiv:
+ if (kisflt(k)) break;
+ if (isintcon(r) && ispo2(iv = intconval(r))) {
+ /* x / 2^y ==> x >> y */
+ op = Oslr;
+ r = mkref(RICON, ilog2(iv));
+ }
+ break;
+ case Orem:
+ break;
+ case Ourem:
+ if (kisflt(k)) break;
+ if (isintcon(r) && ispo2(iv = intconval(r))) {
+ /* x % 2^y ==> x & 2^y-1 */
+ op = Oand;
+ r = mkintcon(k, iv - 1);
+ }
+ break;
+ case Oand:
+ if (isnumcon(l)) rswap(l, r); /* put const in rhs */
+ if (r.bits == ZEROREF.bits) /* x & 0 ==> 0 */
+ return ZEROREF;
+ break;
+ case Oior:
+ if (isnumcon(l)) rswap(l, r); /* put const in rhs */
+ if (r.bits == ZEROREF.bits) /* x | 0 ==> x */
+ return l;
+ case Oxor:
+ if (isnumcon(l)) rswap(l, r); /* put const in rhs */
+ if (r.bits == ZEROREF.bits) /* x ^ 0 ==> x */
+ return l;
+ case Oshl: case Osar: case Oslr:
+ if (r.bits == ZEROREF.bits) /* x shift 0 ==> x */
+ return l;
+ break;
+ case Oequ:
+ if (r.bits == l.bits && kisint(k))
+ return ONE;
+ break;
+ case Oneq:
+ if (r.bits == l.bits && kisint(k))
+ return ZEROREF;
+ break;
+ case Olth: case Ogth:
+ case Olte: case Ogte:
+ break;
+ case Oulth:
+ if (r.bits == ZEROREF.bits) /* x u< 0 ==> f */
+ return ZEROREF;
+ break;
+ case Ougth:
+ if (l.bits == ZEROREF.bits) /* 0 u> x ==> f */
+ return ZEROREF;
+ break;
+ case Oulte:
+ if (l.bits == ZEROREF.bits) /* 0 u<= x ==> t */
+ return ONE;
+ break;
+ case Ougte:
+ if (r.bits == ZEROREF.bits) /* x u>= 0 ==> t */
+ return ONE;
+ break;
+ default:
+ assert(!"binop?");
+ }
+ return fn ? addinstr(fn, mkinstr(op, k, l, r)) : NOREF;
+}
+
+/* implements f32/f64 -> u64 conversion */
+static union ref
+cvtfu64(struct function *fn, enum irclass from, union ref x)
+{
+ struct block *t, *f, *merge;
+ union ref tmp, phiarg[2];
+ /* if (x < 2p63) cvtfXs(x) else (cvtfXs(x - 2p63) | (1<<63)) */
+ union ref max = mkfltcon(from, 0x1.0p63);
+ enum op cvt = from == KF32 ? Ocvtf32s : Ocvtf64s;
+ putcondbranch(fn, irbinop(fn, Olth, from, x, max), t = newblk(fn), f = newblk(fn));
+
+ useblk(fn, t);
+ phiarg[0] = irunop(fn, cvt, KI64, x);
+ putbranch(fn, merge = newblk(fn));
+
+ useblk(fn, f);
+ tmp = irbinop(fn, Osub, from, x, max);
+ tmp = irunop(fn, cvt, KI64, tmp);
+ phiarg[1] = irbinop(fn, Oior, KI64, tmp, mkintcon(KI64, 1ull<<63));
+ putbranch(fn, merge);
+
+ useblk(fn, merge);
+ return addphi(fn, KI64, phiarg);
+}
+
+/* implements u64 -> f32/f64 conversion */
+static union ref
+cvtu64f(struct function *fn, enum irclass to, union ref x)
+{
+ struct block *t, *f, *merge;
+ union ref t1, t2, phiarg[2];
+
+ /* if ((s64)x >= 0) cvts64f(x) else cvts64f((x>>1)|(x&1))*2 */
+
+ putcondbranch(fn, irbinop(fn, Ogte, KI64, x, ZEROREF), t = newblk(fn), f = newblk(fn));
+
+ useblk(fn, t);
+ phiarg[0] = irunop(fn, Ocvts64f, to, x);
+ putbranch(fn, merge = newblk(fn));
+
+ useblk(fn, f);
+ t1 = irbinop(fn, Oslr, KI64, x, mkref(RICON, 1));
+ t2 = irbinop(fn, Oand, KI64, x, mkref(RICON, 1));
+ t1 = irbinop(fn, Oior, KI64, t1, t2);
+ t1 = irunop(fn, Ocvts64f, to, t1);
+ phiarg[1] = irbinop(fn, Oadd, to, t1, t1);
+ putbranch(fn, merge);
+
+ useblk(fn, merge);
+ return addphi(fn, to, phiarg);
+}
+
+union ref
+irunop(struct function *fn, enum op op, enum irclass k, union ref a)
+{
+ union ref c;
+ struct instr *ins = NULL;
+ if (foldunop(&c, op, k, a))
+ return c;
+ if (a.t == RTMP) ins = &instrtab[a.i];
+ switch (op) {
+ case Oneg:
+ if (ins && ins->op == Oneg) /* -(-x) ==> x */
+ return ins->l;
+ break;
+ case Onot:
+ if (ins && ins->op == Onot) /* ~(~x) ==> x */
+ return ins->l;
+ break;
+ case Ocvtf32s: case Ocvtf32f64: case Ocvtf64s:
+ case Ocvtf64f32: case Ocvts32f: case Ocvtu32f:
+ case Ocvts64f:
+ break;
+ case Ocvtf32u: case Ocvtf64u:
+ if (target.arch == ISx86_64 && k == KI64 && fn) {
+ /* this should probably be handled in a separate "arithmetic-lowering" pass, earlier than isel
+ */
+ return cvtfu64(fn, op == Ocvtf32u ? KF32 : KF64, a);
+ }
+ break;
+ case Ocvtu64f:
+ /* XXX see above */
+ if (target.arch == ISx86_64 && fn)
+ return cvtu64f(fn, k, a);
+ case Oexts8: case Oextu8: case Oexts16: case Oextu16:
+ case Oexts32: case Oextu32:
+ case Ocopy:
+ break;
+ case Obswap16: case Obswap32: case Obswap64:
+ break;
+ default: assert(!"unop?");
+ }
+ return fn ? addinstr(fn, mkinstr(op, k, a)) : NOREF;
+}
+
+int allocinstr(void);
+
+union ref
+addinstr(struct function *fn, struct instr ins)
+{
+ int new = allocinstr();
+ assert(fn->curblk != NULL);
+ instrtab[new] = ins;
+ adduse(fn->curblk, new, ins.l);
+ adduse(fn->curblk, new, ins.r);
+ vpush(&fn->curblk->ins, new);
+ return mkref(RTMP, new);
+}
+
+void
+useblk(struct function *fn, struct block *blk)
+{
+ extern int nerror;
+ if (fn->curblk && nerror == 0) assert(fn->curblk->jmp.t && "never finished block");
+ if (blk) assert(!blk->jmp.t && "reusing built block");
+ if (blk && !blk->lprev) { /* initialize */
+ blk->lnext = fn->entry;
+ blk->lprev = fn->entry->lprev;
+ blk->lprev->lnext = blk;
+ blk->id = fn->nblk++;
+ fn->entry->lprev = blk;
+ }
+ fn->curblk = blk;
+}
+
+union ref
+addphi(struct function *fn, enum irclass cls, union ref *r)
+{
+ assert(fn->curblk);
+ if (fn->curblk->npred == 0) return UNDREF;
+ if (fn->curblk->npred == 1) /* 1-argument phi is identity */
+ return *r;
+
+ union ref *refs = NULL;
+ xbgrow(&refs, fn->curblk->npred);
+ memcpy(refs, r, fn->curblk->npred * sizeof *r);
+ vpush(&phitab, refs);
+ /*assert(fn->curblk->ins.n == 0);*/
+ int new = allocinstr();
+ instrtab[new] = mkinstr(Ophi, cls, .l.i = phitab.n-1);
+ for (int i = 0; i < fn->curblk->npred; ++i) {
+ adduse(fn->curblk, new, r[i]);
+ }
+ vpush(&fn->curblk->phi, new);
+ return mkref(RTMP, new);
+}
+
+#define putjump(fn, j, arg0, arg1, T, F) \
+ fn->curblk->jmp.t = j; \
+ fn->curblk->jmp.arg[0] = arg0; \
+ fn->curblk->jmp.arg[1] = arg1; \
+ fn->curblk->s1 = T; \
+ fn->curblk->s2 = F; \
+ fn->curblk = NULL;
+
+void
+putbranch(struct function *fn, struct block *blk)
+{
+ assert(fn->curblk && blk);
+ addpred(blk, fn->curblk);
+ putjump(fn, Jb, NOREF, NOREF, blk, NULL);
+}
+
+void
+putcondbranch(struct function *fn, union ref arg, struct block *t, struct block *f)
+{
+ assert(fn->curblk && t && f);
+ if (iscon(arg)) {
+ bool truthy;
+ if (isintcon(arg)) truthy = intconval(arg) != 0;
+ else if (isfltcon(arg)) truthy = fltconval(arg) != 0.0;
+ else if (isaddrcon(arg,0)) truthy = 1; /* XXX ok to assume symbols have non null addresses? */
+ else goto Cond;
+ putbranch(fn, truthy ? t : f);
+ } else {
+ Cond:
+ adduse(fn->curblk, USERJUMP, arg);
+ addpred(t, fn->curblk);
+ addpred(f, fn->curblk);
+ putjump(fn, Jb, arg, NOREF, t, f);
+ }
+}
+
+void
+putreturn(struct function *fn, union ref r0, union ref r1)
+{
+ assert(fn->curblk);
+ adduse(fn->curblk, USERJUMP, r0);
+ adduse(fn->curblk, USERJUMP, r1);
+ putjump(fn, Jret, r0, r1, NULL, NULL);
+}
+
+void
+puttrap(struct function *fn)
+{
+ assert(fn->curblk);
+ putjump(fn, Jtrap, NOREF, NOREF, NULL, NULL);
+}
+
+#undef putjump
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_cfg.c b/src/ir_cfg.c
new file mode 100644
index 0000000..86af3f3
--- /dev/null
+++ b/src/ir_cfg.c
@@ -0,0 +1,130 @@
+#include "ir.h"
+
+static void
+porec(int *nblk, struct block ***rpo, struct block *b)
+{
+ if (wasvisited(b)) return;
+ assert(*nblk > 0 && "nblk bad");
+ --*nblk;
+ markvisited(b);
+ if (b->s2) porec(nblk, rpo, b->s2);
+ if (b->s1) porec(nblk, rpo, b->s1);
+ *--*rpo = b;
+}
+
+/* also blkid */
+void
+sortrpo(struct function *fn)
+{
+ static struct block **rpobuf;
+ struct block **rpoend, **rpo, *blk, *next;
+ int i, ndead;
+
+ xbgrow(&rpobuf, fn->nblk);
+ rpo = rpoend = rpobuf + fn->nblk,
+
+ startbbvisit();
+ fn->entry->id = 0;
+ int nblk = fn->nblk;
+ porec(&nblk, &rpo, fn->entry);
+ ndead = rpo - rpobuf;
+ if (ndead > 0) for (blk = fn->entry->lprev; blk != fn->entry; blk = next) {
+ next = blk->lprev;
+ if (!wasvisited(blk)) {
+ for (int i = 0; i < blk->ins.n; ++i) {
+ /* if unreachable block has alloca pseudo-instrs, move them to the entry
+ * to be able to delete it */
+ if (oisalloca(instrtab[blk->ins.p[i]].op)) {
+ vpush(&fn->entry->ins, blk->ins.p[i]);
+ }
+ }
+ for (int i = 0; i < blk->npred; ++i)
+ assert(!wasvisited(blkpred(blk, i)));
+ freeblk(fn, blk);
+ --ndead;
+ }
+ }
+ for (i = 1, ++rpo; rpo < rpoend; ++rpo, ++i) {
+ rpo[-1]->lnext = rpo[0];
+ rpo[0]->lprev = rpo[-1];
+ rpo[0]->id = i;
+ }
+ fn->entry->lprev = rpo[-1];
+ rpo[-1]->lnext = fn->entry;
+
+ fn->prop |= FNBLKID | FNRPO;
+}
+
+/* also blkid */
+void
+filldom(struct function *fn)
+{
+ struct block *blk = fn->entry;
+ int i = 0;
+
+ FREQUIRE(FNRPO);
+
+ /* Implements 'A Simple, Fast Dominance Algorithm' by K. Cooper, T. Harvey, and K. Kennedy */
+ do blk->id = i++, blk->idom = NULL; while ((blk = blk->lnext) != fn->entry);
+ fn->entry->idom = fn->entry;
+ for (bool changed = 1; changed;) {
+ changed = 0;
+ do {
+ int j;
+ struct block *new = NULL;
+ if (blk->npred == 0) continue;
+ for (j = 0; j < blk->npred; ++j)
+ if ((new = blkpred(blk, j))->id < blk->id) break;
+ assert(new);
+ for (int i = 0; i < blk->npred; ++i) {
+ if (i == j) continue;
+ struct block *p = blkpred(blk, i);
+ if (p->idom) { /* new = intersect(p, new) */
+ while (p != new) {
+ while (p->id > new->id) p = p->idom;
+ while (p->id < new->id) new = new->idom;
+ }
+ }
+ }
+ if (blk->idom != new) {
+ blk->idom = new;
+ changed = 1;
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+ }
+ fn->prop |= FNBLKID | FNDOM;
+}
+
+static void
+loopmark(struct block *head, struct block *blk)
+{
+ if (blk->id < head->id || blk->visit == head->id) return;
+ blk->visit = head->id;
+ ++blk->loop;
+ for (int i = 0; i < blk->npred; ++i)
+ loopmark(head, blkpred(blk, i));
+}
+
+void
+fillloop(struct function *fn)
+{
+ struct block *b = fn->entry;
+ int id = 0;
+ FREQUIRE(FNRPO);
+ do {
+ b->id = id++;
+ b->visit = -1u;
+ b->loop = 0;
+ } while ((b = b->lnext) != fn->entry);
+ do {
+ for (int i = 0; i < b->npred; ++i) {
+ struct block *p = blkpred(b, i);
+ if (p->id > b->id) { /* b is loop header */
+ loopmark(b, p);
+ }
+ }
+ } while ((b = b->lnext) != fn->entry);
+ fn->prop |= FNBLKID;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_cse.c b/src/ir_cse.c
new file mode 100644
index 0000000..23c5cf9
--- /dev/null
+++ b/src/ir_cse.c
@@ -0,0 +1,92 @@
+#include "ir.h"
+
+static inline bool
+pure(const struct instr *ins)
+{
+ return oisarith(ins->op) || (oisload(ins->op) && !ins->keep);
+}
+
+static inline bool
+insequ(const struct instr *a, const struct instr *b)
+{
+ if (a->op != b->op) return 0;
+ enum op op = a->op;
+ switch (opnarg[op]) {
+ default: assert(0);
+ case 2: if (a->r.bits != b->r.bits) return 0;
+ case 1: if (a->l.bits != b->l.bits) return 0;
+ }
+ return 1;
+}
+
+static struct ht {
+ uint t;
+ ushort memno, cutoff;
+ struct block *b;
+} *insht;
+static uint ninsht;
+
+static inline size_t
+hashins(const struct instr *ins)
+{
+ return hashb(0, ins, sizeof *ins);
+}
+
+static bool
+doms(struct block *blk, struct block *b)
+{
+ for (;; b = b->idom) {
+ if (blk == b) return 1;
+ if (blk == b->idom) return 1;
+ if (blk->id > b->id) return 0;
+ }
+}
+
+static int
+uniq(int t, struct block *blk, int cutoff, int memno)
+{
+ assert((uint)t < MAXINSTR);
+ struct instr *ins = &instrtab[t];
+ if (!pure(ins)) return t;
+ for (size_t h = hashins(&instrtab[t]), i = h;; ++i) {
+ struct ht *p = &insht[i &= (ninsht-1)];
+ if (!p->b) Put: {
+ p->b = blk;
+ p->memno = memno;
+ p->cutoff = cutoff;
+ return p->t = t;
+ } else if (insequ(&instrtab[p->t], ins)) {
+ if (p->cutoff == cutoff && (!oisload(ins->op) || p->memno == memno) && doms(p->b, blk))
+ return p->t;
+ goto Put;
+ }
+ }
+}
+
+void
+cselim(struct function *fn)
+{
+ FREQUIRE(FNUSE | FNRPO | FNDOM | FNBLKID);
+ extern int ninstr;
+ for (ninsht = 32; ninsht <= ninstr; ninsht *= 2) ;
+ insht = allocz(fn->passarena, ninsht * sizeof *insht, 0);
+ int memno = 0, cutoff = 0;
+ struct block *blk = fn->entry;
+ do {
+ ++memno;
+ for (int i = 0; i < blk->ins.n; ++i) {
+ int t = blk->ins.p[i], q;
+ if ((q = uniq(t, blk, cutoff, memno)) != t) {
+ replcuses(mkref(RTMP, t), mkref(RTMP, q));
+ delinstr(blk, i--);
+ } else if (oisstore(instrtab[t].op)) {
+ /* assume everything alias everything */
+ ++memno;
+ } else if (instrtab[t].op == Ocall) {
+ ++cutoff;
+ }
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_dump.c b/src/ir_dump.c
new file mode 100644
index 0000000..b0ce603
--- /dev/null
+++ b/src/ir_dump.c
@@ -0,0 +1,319 @@
+#include "ir.h"
+#include "../obj/obj.h"
+#include "../endian.h"
+
+static int nextdat;
+
+static struct wbuf *out = &bstdout;
+
+static bool
+prilitdat(const struct irdat *dat, const char *prefix)
+{
+ uchar *p;
+ switch (dat->section) {
+ default: assert(0);
+ case Sdata: p = objout.data.p + dat->off; break;
+ case Srodata: p = objout.rodata.p + dat->off; break;
+ case Stext: p = objout.textbegin + dat->off; break;
+ }
+ if (dat->ctype.t == TYARRAY && typechild(dat->ctype).t == TYCHAR && dat->siz-1 < 60 && p[dat->siz-1] == 0) {
+ bfmt(out, "%s%'S", prefix, p, dat->siz-1);
+ } else if (dat->ctype.t == TYFLOAT) {
+ bfmt(out, "%s%f", prefix, rdf32targ(p));
+ } else if (dat->ctype.t == TYDOUBLE) {
+ bfmt(out, "%s%f", prefix, rdf64targ(p));
+ } else if (dat->ctype.t == TYVLONG) {
+ bfmt(out, "%s0x%lx", prefix, rd64targ(p));
+ } else return 0;
+ return 1;
+}
+
+static void
+pridat(const struct irdat *dat)
+{
+ static const char *snames[] = { [Sdata] = ".data", [Srodata] = ".rodata", [Stext] = ".text" };
+ uchar *p;
+ switch (dat->section) {
+ default: assert(0);
+ case Sdata: p = objout.data.p + dat->off; break;
+ case Srodata: p = objout.rodata.p + dat->off; break;
+ case Stext: p = objout.textbegin + dat->off; break;
+ }
+ enum {
+ MINZERO = 4,
+ MAXLINE = 60,
+ };
+ int npri = 0;
+ int strbegin = 0, nstr = 0;
+ bfmt(out, "%s %ty %y(align %d, size %d):\n\t", snames[dat->section], dat->ctype, dat->name, dat->align, dat->siz);
+ if (!prilitdat(dat, "lit: ")) {
+ for (int i = 0; i < dat->siz; ++i) {
+ int c = p[i];
+ if (npri > MAXLINE) {
+ npri = 0;
+ bfmt(out, "\n\t");
+ }
+ if (aisprint(c)) {
+ if (!nstr++) strbegin = i;
+ } else {
+ if (nstr) {
+ npri += bfmt(out, "asc %'S,", p+strbegin, nstr);
+ nstr = 0;
+ bfmt(out, "b ");
+ }
+ npri += bfmt(out, "%d,", c);
+ }
+ }
+ if (nstr) npri += bfmt(out, "asc %'S,", p+strbegin, nstr);
+ }
+ bfmt(out, "\n");
+}
+
+static const char *clsname[] = {
+ "?", "i32", "i64", "ptr", "f32", "f64"
+};
+
+static void
+prityp(union irtype typ)
+{
+ if (!typ.isagg)
+ bfmt(out, clsname[typ.cls]);
+ else {
+ const struct typedata *td = &typedata[typ.dat];
+ const char *tag = td->t == TYSTRUCT ? "struct" : "union";
+ if (ttypenames[td->id])
+ bfmt(out, "%s.%s.%d", tag, ttypenames[td->id], td->id);
+ else
+ bfmt(out, "%s.%d", tag, td->id);
+ }
+}
+
+static const char *intrinname[] = {
+ "?\??",
+#define _(b,...) #b,
+#include "intrin.def"
+#undef _
+};
+
+static void
+dumpref(enum op o, union ref ref)
+{
+ struct xcon *con;
+ switch (ref.t) {
+ case RXXX:
+ if (ref.bits == UNDREF.bits)
+ bfmt(out, "undef");
+ else
+ bfmt(out, "??");
+ break;
+ case RTMP:
+ bfmt(out, "%%%d", ref.i);
+ if (instrtab[ref.i].reg)
+ bfmt(out, "(%s)", mctarg->rnames[instrtab[ref.i].reg-1]);
+ break;
+ case RREG:
+ bfmt(out, "%s", mctarg->rnames[ref.i]);
+ break;
+ case RICON:
+ if (o == Ointrin) bfmt(out, "\"%s\"", intrinname[ref.i]);
+ else bfmt(out, "%d", ref.i);
+ break;
+ case RXCON:
+ con = &contab.p[ref.i];
+ if (con->deref) bfmt(out, "*[");
+ if (con->issym || con->isdat) {
+ bfmt(out, "$%y", xcon2sym(ref.i));
+ if (con->isdat) {
+ struct irdat *dat = &dattab.p[con->dat];
+ if (prilitdat(dat, " (= ")) {
+ if (isscalar(dat->ctype)) {
+ struct wbuf tmp = MEMBUF((char [1]){0}, 1);
+ bfmt(&tmp, "%ty", dat->ctype);
+ ioputc(out, *tmp.buf);
+ }
+ ioputc(out, ')');
+ }
+ }
+ } else switch (con->cls) {
+ case KI32: bfmt(out, "%d", (int)con->i); break;
+ case KI64: bfmt(out, "%ld", con->i); break;
+ case KPTR: bfmt(out, "%'lx", con->i); break;
+ case KF32: bfmt(out, "%fs", con->f); break;
+ case KF64: bfmt(out, "%fd", con->f); break;
+ default: assert(0);
+ }
+ if (con->deref) bfmt(out, "]");
+ break;
+ case RTYPE:
+ prityp(ref2type(ref));
+ break;
+ case RADDR:
+ {
+ const struct addr *addr = &addrtab.p[ref.i];
+ bool k = 0;
+ bfmt(out, "addr [");
+ if ((k = addr->base.bits)) dumpref(0, addr->base);
+ if (addr->index.bits) {
+ if (k) bfmt(out, " + ");
+ dumpref(0, addr->index);
+ if (addr->shift)
+ bfmt(out, " * %d", 1<<addr->shift);
+ k = 1;
+ }
+ if (k && addr->disp) {
+ bfmt(out, " %c %d", "-+"[addr->disp > 0], addr->disp < 0 ? -addr->disp : addr->disp);
+ }
+ assert(k);
+ bfmt(out, "]");
+ }
+ break;
+ case RSTACK:
+ bfmt(out, "[stack %d]", ref.i);
+ break;
+ default: assert(!"ref");
+ }
+}
+
+static void
+dumpcall(struct call *call)
+{
+ if (call->ret.isagg) {
+ bfmt(out, "sret ");
+ prityp(call->ret);
+ bfmt(out, ", ");
+ }
+ if (call->vararg < 0) {
+ bfmt(out, "#%d", call->narg);
+ } else {
+ assert(call->vararg <= call->narg);
+ bfmt(out, "#%d, ... #%d", call->vararg, call->narg - call->vararg);
+ }
+}
+
+static void
+dumpinst(const struct instr *ins)
+{
+ int i;
+ if (ins->op == Omove) {
+ bfmt(out, "move %s ", clsname[ins->cls]);
+ } else {
+ enum irclass cls = insrescls(*ins);
+ if (ins->reg) {
+ if (cls)
+ bfmt(out, "%s ", clsname[cls]);
+ bfmt(out, "(%%%d)%s = ", ins - instrtab, mctarg->rnames[ins->reg - 1]);
+ } else if (cls) {
+ bfmt(out, "%s %%%d", clsname[cls], ins - instrtab);
+ bfmt(out, " = ");
+ }
+ bfmt(out, "%s ", opnames[ins->op]);
+ if (oiscmp(ins->op))
+ bfmt(out, "%s ", clsname[ins->cls]);
+ }
+ for (i = 0; i < opnarg[ins->op]; ++i) {
+ if (i) bfmt(out, ", ");
+ if (i == 1 && (ins->op == Ocall || ins->op == Ointrin)) {
+ dumpcall(&calltab.p[ins->r.i]);
+ } else {
+ dumpref(ins->op, (&ins->l)[i]);
+ }
+ }
+ if (oisalloca(ins->op) && ins->l.t == RICON) {
+ bfmt(out, " \t; %d bytes", ins->l.i << (ins->op - Oalloca1));
+ }
+ bfmt(out, "\n");
+}
+
+void
+dumpblk(struct function *fn, struct block *blk)
+{
+ static const char *jnames[] = { 0, "b", "ret", "trap" };
+ int i;
+ bfmt(out, " @%d:", blk->id);
+ if (blk->npred) {
+ bfmt(out, " \t; preds:");
+ for (i = 0; i < blk->npred; ++i) {
+ if (i) ioputc(out, ',');
+ bfmt(out, " @%d", blkpred(blk, i)->id);
+ }
+ }
+ if (fn->prop & FNDOM && blk->idom)
+ bfmt(out, "\t; idom: @%d", blk->idom->id);
+ if (blk->loop)
+ bfmt(out, "\t; loop depth: %d", blk->loop);
+ ioputc(out, '\n');
+ for (i = 0; i < blk->phi.n; ++i) {
+ struct instr *phi = &instrtab[blk->phi.p[i]];
+ union ref *refs = phitab.p[phi->l.i];
+ if (i == 0) bfmt(out, "%-4d", blk->inumstart);
+ else bfmt(out, " |> ");
+ bfmt(out, " %s ", clsname[phi->cls]);
+ if (!phi->reg) bfmt(out, "%%%d = %s ", blk->phi.p[i], opnames[phi->op]);
+ else bfmt(out, "(%%%d)%s = %s ", phi - instrtab, mctarg->rnames[phi->reg-1], opnames[phi->op]);
+ for (int i = 0; i < blk->npred; ++i) {
+ if (i) bfmt(out, ", ");
+ bfmt(out, "@%d ", blkpred(blk, i)->id);
+ dumpref(0, refs[i]);
+ }
+ ioputc(out, '\n');
+ }
+ for (i = 0; i < blk->ins.n; ++i) {
+ bfmt(out, "%-4d ", blk->inumstart + 1 + i);
+ dumpinst(&instrtab[blk->ins.p[i]]);
+ }
+ bfmt(out, "%-4d %s ", blk->inumstart + 1 + i, jnames[blk->jmp.t]);
+ if (blk->jmp.t == Jret && blk->jmp.arg[0].bits && !fn->nabiret && isagg(fn->retty)) {
+ /* un-lowered struct return */
+ dumpref(0, mktyperef(mkirtype(fn->retty)));
+ bfmt(out, " ");
+ }
+ for (i = 0; i < 2; ++i) {
+ if (!blk->jmp.arg[i].bits) break;
+ if (i > 0) bfmt(out, ", ");
+ dumpref(0, blk->jmp.arg[i]);
+ }
+ if (i && blk->s1) bfmt(out, ", ");
+ if (blk->s1 && blk->s2) bfmt(out, "@%d, @%d", blk->s1->id, blk->s2->id);
+ else if (blk->s1) bfmt(out, "@%d", blk->s1->id);
+ bfmt(out, "\n");
+}
+
+void
+irdump(struct function *fn)
+{
+ struct block *blk;
+
+ /* print datas that have never been printed before */
+ while (nextdat < dattab.n) pridat(&dattab.p[nextdat++]);
+
+ bfmt(out, "function %s : %ty\n", fn->name, fn->fnty);
+ if (fn->abiarg || fn->nabiret) {
+ bfmt(out, "abi: (");
+ for (int i = 0; i < fn->nabiarg; ++i) {
+ if (i > 0) bfmt(out, ", ");
+ if (!fn->abiarg[i].isstk) {
+ bfmt(out, "%s", mctarg->rnames[fn->abiarg[i].reg]);
+ } else {
+ prityp(fn->abiarg[i].ty);
+ bfmt(out, " <stk>");
+ }
+ }
+ bfmt(out, ")");
+ if (fn->retty.t != TYVOID) {
+ bfmt(out, " -> %s", mctarg->rnames[fn->abiret[0].reg]);
+ if (fn->nabiret > 1)
+ bfmt(out, ", %s", mctarg->rnames[fn->abiret[1].reg]);
+ }
+ bfmt(out, "\n");
+ }
+ numberinstrs(fn);
+ blk = fn->entry;
+ do {
+ assert(blk->lprev->lnext == blk);
+ dumpblk(fn, blk);
+ assert(blk->lnext != NULL);
+ } while ((blk = blk->lnext) != fn->entry);
+ bfmt(out, "\n");
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_fold.c b/src/ir_fold.c
new file mode 100644
index 0000000..4c9861e
--- /dev/null
+++ b/src/ir_fold.c
@@ -0,0 +1,133 @@
+#include "ir.h"
+#include "../endian.h"
+#include <limits.h>
+
+#ifdef __clang__
+__attribute__((no_sanitize("float-cast-overflow"))) /* silence UBsan for float->int overflow */
+#endif
+static union ref
+foldint(enum op op, enum irclass k, union ref lr, union ref rr)
+{
+ vlong x;
+ union {
+ vlong s;
+ uvlong u;
+ } l = {.s = intconval(lr)}, r = {.s = intconval(rr)};
+ bool w = cls2siz[k] == 8;
+ if (in_range(op, Odiv, Ourem)) assert(r.u != 0);
+ switch (op) {
+ case Ocopy: x = l.s; break;
+ case Oneg: x = -l.s; break;
+ case Onot: x = ~l.s; break;
+#define CVTF2I(TF, TI) do { \
+ TF f = fltconval(lr); \
+ if (f != f) x = 0; \
+ else x = (TI)f; \
+} while (0)
+ case Ocvtf32s: if (w) CVTF2I(float, vlong); else CVTF2I(float, int); break;
+ case Ocvtf32u: if (w) CVTF2I(float, uvlong); else CVTF2I(float, uint); break;
+ case Ocvtf64s: if (w) CVTF2I(double, vlong); else CVTF2I(double, int); break;
+ case Ocvtf64u: if (w) CVTF2I(double, uvlong); else CVTF2I(double, uint); break;
+#undef CVTF2I
+ case Oexts8: x = (schar)l.s; break;
+ case Oextu8: x = (uchar)l.s; break;
+ case Oexts16: x = (short)l.s; break;
+ case Oextu16: x = (ushort)l.s; break;
+ case Oexts32: x = (int)l.s; break;
+ case Oextu32: x = (uint)l.s; break;
+ case Obswap16: x = bswap16(l.u); break;
+ case Obswap32: x = bswap32(l.u); break;
+ case Obswap64: x = bswap64(l.u); break;
+ case Oadd: x = l.u + r.u; break;
+ case Osub: x = l.u - r.u; break;
+ case Omul: x = l.u * r.u; break;
+ case Odiv: if (r.s == -1 && (w ? l.s == LLONG_MIN : (int)l.s == INT_MIN)) x = l.s;
+ else x = w ? l.s / r.s : (int)l.s / (int)r.s;
+ break;
+ case Oudiv: x = w ? l.u / r.u : (uint)l.u / (uint)r.u; break;
+ case Orem: if (r.s == -1 && (w ? l.s == LLONG_MIN : (int)l.s == INT_MIN)) x = 0;
+ else x = w ? l.s % r.s : (int)l.s % (int)r.s;
+ break;
+ case Ourem: x = w ? l.u % r.u : (uint)l.u % (uint)r.u; break;
+ case Oand: x = l.u & r.u; break;
+ case Oior: x = l.u | r.u; break;
+ case Oxor: x = l.u ^ r.u; break;
+ case Oshl: x = l.u << (r.u & (w ? 63 : 31)); break;
+ case Oslr: x = w ? l.u >> (r.u&63) : (int)l.s >> (r.u&31); break;
+ case Osar: x = w ? l.s >> (r.u&63) : (int)l.s >> (r.u&31); break;
+ case Oequ: x = l.s == r.s; break;
+ case Oneq: x = l.s != r.s; break;
+ case Olth: x = l.s < r.s; break;
+ case Ogth: x = l.s > r.s; break;
+ case Olte: x = l.s <= r.s; break;
+ case Ogte: x = l.s >= r.s; break;
+ case Oulth: x = l.u < r.u; break;
+ case Ougth: x = l.u > r.u; break;
+ case Oulte: x = l.u <= r.u; break;
+ case Ougte: x = l.u >= r.u; break;
+ default: assert(0);
+ }
+ if (cls2siz[k] < 8) x = (int)x;
+ return mkintcon(k, x);
+}
+
+static union ref
+foldflt(enum op op, enum irclass k, union ref lr, union ref rr)
+{
+ int xi;
+ double x, l = fltconval(lr), r = fltconval(rr);
+ bool w = k == KF64;
+ switch (op) {
+ case Ocopy: x = l; break;
+ case Oneg: x = -l; break;
+ case Ocvtf32f64: x = (float)l; break;
+ case Ocvtf64f32: x = (float)l; break;
+ case Ocvts32f: x = (int)intconval(lr); break;
+ case Ocvtu32f: x = (int)intconval(lr); break;
+ case Ocvts64f: x = (vlong)intconval(lr); break;
+ case Ocvtu64f: x = (uvlong)intconval(lr); break;
+ case Oadd: x = l + r; break;
+ case Osub: x = l - r; break;
+ case Omul: x = l * r; break;
+ case Odiv: x = l / r; break;
+ case Oequ: xi = l == r; goto Cmp;
+ case Oneq: xi = l != r; goto Cmp;
+ case Olth: xi = l < r; goto Cmp;
+ case Ogth: xi = l > r; goto Cmp;
+ case Olte: xi = l <= r; goto Cmp;
+ case Ogte: xi = l >= r; Cmp: return mkref(RICON, xi);
+ default: assert(0);
+ }
+ if (!w) x = (float)x;
+ return mkfltcon(k, x);
+}
+
+bool
+foldbinop(union ref *to, enum op op, enum irclass k, union ref l, union ref r)
+{
+ if (!oisarith(op))
+ return 0;
+ if (!isnumcon(l) || !isnumcon(r)) return 0;
+ if (in_range(op, Odiv, Ourem) && kisint(k) && intconval(r) == 0)
+ return 0;
+ if (kisint(k))
+ *to = foldint(op, k, l, r);
+ else
+ *to = foldflt(op, k, l, r);
+ return 1;
+}
+
+bool
+foldunop(union ref *to, enum op op, enum irclass k, union ref a)
+{
+ if (!isnumcon(a)) return 0;
+ if (op != Ocopy && !oisarith(op))
+ return 0;
+ if (kisint(k))
+ *to = foldint(op, k, a, ZEROREF);
+ else
+ *to = foldflt(op, k, a, ZEROREF);
+ return 1;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_inliner.c b/src/ir_inliner.c
new file mode 100644
index 0000000..b99d3dc
--- /dev/null
+++ b/src/ir_inliner.c
@@ -0,0 +1,309 @@
+#include "ir.h"
+
+struct savedfunc {
+ uint ninstr;
+ struct instr *instrtab;
+ struct xcon *contab;
+ struct call *calltab;
+ union ref **phitab;
+ struct block *entry;
+ union type fnty, retty;
+ struct abiarg *abiarg, abiret[2];
+ ushort nabiarg, nabiret;
+ ushort nretpoints;
+};
+
+enum { MAX_INLINED_FN_NINS = 50,
+ MAX_INLINED_FN_NBLK = 16, };
+
+static pmap_of(struct savedfunc *) savedfns;
+
+bool
+maybeinlinee(struct function *fn)
+{
+ static struct arena *savearena;
+ extern int ninstr;
+
+ // TODO better heuristics
+ if (ccopt.o < OPT1) return 0;
+ if (!fn->inlin && ccopt.o < OPT2) return 0;
+ if (ninstr > MAX_INLINED_FN_NINS) return 0;
+ if (fn->nblk > MAX_INLINED_FN_NBLK) return 0;
+ for (int i = 0; i < fn->nabiarg; ++i) {
+ /* TODO inlining functions with stack args */
+ if (fn->abiarg[i].isstk) return 0;
+ }
+ if (fn->nabiret > 1) return 0; /* TODO 2reg scalar return */
+
+ if (!savearena) {
+ enum { N = 1<<12 };
+ static union { char m[sizeof(struct arena) + N]; struct arena *_align; } amem;
+ savearena = (void *)amem.m;
+ savearena->cap = N;
+ }
+
+ if (ccopt.dbg.y) {
+ bfmt(ccopt.dbgout, "> stashing '%s' for inlining\n", fn->name);
+ }
+ struct savedfunc *sv = allocz(&savearena, sizeof *sv, 0);
+ sv->fnty = fn->fnty, sv->retty = fn->retty;
+ if (fn->abiarg)
+ sv->abiarg = alloccopy(&savearena, fn->abiarg, sizeof *sv->abiarg * fn->nabiarg, 0);
+ sv->nabiarg = fn->nabiarg;
+ if ((sv->nabiret = fn->nabiret) > 0)
+ memcpy(sv->abiret, fn->abiret, sizeof sv->abiret);
+ struct block *bmap[MAX_INLINED_FN_NBLK];
+ struct block *b = fn->entry;
+ int id = 0;
+ do {
+ b->id = id++;
+ struct block *q = alloccopy(&savearena, b, sizeof *b, 0);
+ if (q->phi.n)
+ q->phi.p = alloccopy(&savearena, q->phi.p, sizeof *q->phi.p * q->phi.n, 0);
+ if (q->ins.n)
+ q->ins.p = alloccopy(&savearena, q->ins.p, sizeof *q->ins.p * q->ins.n, 0);
+ if (q->npred > 1)
+ q->_pred = alloccopy(&savearena, q->_pred, sizeof *q->_pred * q->npred, 0);
+ q->lprev = NULL;
+ q->idom = NULL;
+ bmap[b->id] = q;
+ sv->nretpoints += b->jmp.t == Jret;
+ } while ((b = b->lnext) != fn->entry);
+ b = sv->entry = bmap[0];
+ do {
+ if (b->s1) b->s1 = bmap[b->s1->id];
+ if (b->s2) b->s2 = bmap[b->s2->id];
+ if (b->npred == 1)
+ b->_pred0 = bmap[b->_pred0->id];
+ else for (int i = 0; i < b->npred; ++i)
+ b->_pred[i] = bmap[b->_pred[i]->id];
+ b->lnext = b->lnext == fn->entry ? NULL : bmap[b->lnext->id];
+ } while ((b = b->lnext));
+
+ sv->instrtab = alloccopy(&savearena, instrtab, sizeof *instrtab * (sv->ninstr = ninstr), 0);
+ sv->contab = alloccopy(&savearena, contab.p, sizeof *contab.p * contab.n, 0);
+ if (calltab.n) {
+ sv->calltab = alloccopy(&savearena, calltab.p, sizeof *calltab.p * calltab.n, 0);
+ for (int i = 0; i < calltab.n; ++i) {
+ if (sv->calltab[i].abiarg)
+ sv->calltab[i].abiarg = alloccopy(&savearena, sv->calltab[i].abiarg,
+ sv->calltab[i].narg * sizeof *sv->calltab[i].abiarg, 0);
+ }
+ }
+ if (phitab.n) {
+ sv->phitab = alloc(&savearena, sizeof *phitab.p * phitab.n, 0);
+ for (int i = 0; i < phitab.n; ++i) {
+ sv->phitab[i] = alloccopy(&savearena, phitab.p[i], sizeof *phitab.p[i] * xbcap(phitab.p[i]), 0);
+ }
+ }
+ pmap_set(&savedfns, fn->name, sv);
+ return 1;
+}
+
+static union ref
+mapref(short *instrmap, struct savedfunc *sv, union ref r)
+{
+ assert(r.bits);
+ if (r.t == RTMP) return r.i = instrmap[r.i], r;
+ if (r.t == RXCON) return newxcon(&sv->contab[r.i]);
+ assert(r.t != RADDR);
+ assert(r.t != RSTACK);
+ return r;
+}
+
+static struct block *
+inlcall(struct function *fn, struct block *blk, int curi, struct savedfunc *sv)
+{
+ int res = blk->ins.p[curi], res2;
+ struct instr *ins = &instrtab[res];
+ struct call *call = &calltab.p[ins->r.i];
+ union ref retvals[64];
+ union ref args[64];
+ assert(sv->nabiret < 2 && sv->nretpoints < countof(retvals));
+ for (int n = call->narg, i = curi-1; n > 0; --i) {
+ assert(i >= 0);
+ struct instr *ins = &instrtab[blk->ins.p[i]];
+ if (ins->op == Oarg) {
+ args[--n] = ins->r;
+ *ins = mkinstr(Onop,0,);
+ }
+ }
+ if (call->abiret[1].ty.bits) {
+ assert(curi+1 < blk->ins.n);
+ res2 = blk->ins.p[curi+1];
+ assert(instrtab[res2].op == Ocall2r);
+ }
+ struct block *exit = blksplitafter(fn, blk, curi-1);
+ if (!ins->cls) {
+ *ins = mkinstr(Onop,0,);
+ } else {
+ if (sv->nretpoints == 1) {
+ *ins = mkinstr(Ocopy, ins->cls, );
+ } else {
+ /* turn into a phi */
+ *ins = mkinstr(Ophi, ins->cls, );
+ exit->ins.p[0] = newinstr(blk, mkinstr(Onop,0,));
+ vpush(&exit->phi, res);
+ }
+ }
+
+ struct block *bmap[MAX_INLINED_FN_NBLK];
+ short instrmap[MAX_INLINED_FN_NINS];
+ for (struct block *b = sv->entry; b; b = b->lnext) {
+ bmap[b->id] = newblk(fn);
+ }
+ for (int i = 0; i < sv->ninstr; ++i) {
+ /* TODO don't wastefully allocate for tombstone instructions
+ * - mark instructions some way when they are freed (put in instrfreelist)
+ * */
+ int allocinstr(void);
+ instrmap[i] = allocinstr();
+ }
+
+ exit->npred = 0;
+ exit->_pred0 = NULL;
+ blk->s1 = bmap[0];
+ int iret = 0;
+ for (struct block *b = sv->entry, *prev = blk, *new; b; prev = new, b = b->lnext) {
+ new = bmap[b->id];
+ new->id = fn->nblk++;
+ new->lprev = prev;
+ prev->lnext = new;
+ new->lnext = exit;
+ exit->lprev = new;
+ if (b->npred == 1) new->_pred0 = bmap[b->_pred0->id];
+ else if (b->npred > 0) {
+ xbgrow(&new->_pred, b->npred);
+ for (int i = 0; i < b->npred; ++i)
+ new->_pred[i] = bmap[b->_pred[i]->id];
+ }
+ new->npred = b->npred;
+ vresize(&new->phi, b->phi.n);
+ for (int i = 0; i < b->phi.n; ++i) {
+ int t = b->phi.p[i];
+ union ref *refs = NULL,
+ *src = sv->phitab[sv->instrtab[t].l.i];
+ xbgrow(&refs, b->npred);
+ for (int i = 0; i < b->npred; ++i)
+ refs[i] = mapref(instrmap, sv, src[i]);
+ vpush(&phitab, refs);
+ instrtab[instrmap[t]] = mkinstr(Ophi, sv->instrtab[t].cls, .l.i = phitab.n-1);
+ new->phi.p[i] = instrmap[t];
+ }
+ vresize(&new->ins, b->ins.n);
+ for (int i = 0; i < b->ins.n; ++i) {
+ int t = b->ins.p[i];
+ struct instr *ins = &instrtab[instrmap[t]];
+ *ins = sv->instrtab[t];
+ if (ins->op == Oparam) {
+ ins->op = Ocopy;
+ assert(ins->l.t == RICON && (uint) ins->l.i < call->narg);
+ ins->l = args[ins->l.i];
+ } else if (ins->op == Ocall || ins->op == Ointrin) {
+ ins->l = mapref(instrmap, sv, ins->l);
+ for (struct instr *ins2;
+ ins->l.t == RTMP && (ins2 = &instrtab[ins->l.i])->op == Ocopy
+ && (isaddrcon(ins2->l,0) || (ins2->l.t == RTMP && instrtab[ins->l.i].cls == KPTR));) {
+ /* for an indirect function call, eagerly copy-propagate the callee. this allows
+ * subsequent inlining of function pointers statically known after the inlining */
+ ins->l = ins2->l;
+ }
+ vpush(&calltab, sv->calltab[ins->r.i]);
+ ins->r.i = calltab.n-1;
+ } else {
+ if (ins->l.t) ins->l = mapref(instrmap, sv, ins->l);
+ if (ins->r.t) ins->r = mapref(instrmap, sv, ins->r);
+ }
+ new->ins.p[i] = instrmap[t];
+ }
+ if (b->jmp.t == Jret) {
+ new->jmp.t = Jb;
+ new->s1 = exit;
+ retvals[iret++] = b->jmp.arg[0].bits ? mapref(instrmap, sv, b->jmp.arg[0]) : UNDREF;
+ addpred(exit, new);
+ } else {
+ new->jmp.t = b->jmp.t;
+ for (int i = 0; i < 2; ++i) {
+ if (!b->jmp.arg[i].bits) break;
+ new->jmp.arg[i] = mapref(instrmap, sv, b->jmp.arg[i]);
+ }
+ }
+ if (b->s1) {
+ new->s1 = bmap[b->s1->id];
+ if (b->s2) new->s2 = bmap[b->s2->id];
+ }
+ }
+ exit->id = fn->nblk;
+ fn->prop &= ~FNUSE;
+ if (ins->cls && sv->nretpoints > 0) {
+ assert(sv->nretpoints == exit->npred);
+ if (sv->nretpoints == 1) {
+ /* fill copy */
+ ins->l = retvals[0];
+ } else {
+ /* fill phi */
+ union ref *refs = NULL;
+ xbgrow(&refs, sv->nretpoints);
+ memcpy(refs, retvals, sizeof *refs * sv->nretpoints);
+ vpush(&phitab, refs);
+ ins->l = mkref(RXXX, phitab.n-1);
+ }
+ }
+ bmap[0]->_pred0 = blk;
+ bmap[0]->npred = 1;
+ return exit;
+}
+
+enum { MAX_REC_INLINE = 16 };
+void
+doinline(struct function *fn)
+{
+ if (calltab.n == 0) return;
+ struct block *b = fn->entry;
+ struct stack { /* stack of callees being inline expanded */
+ struct block *b; /* block after the end of expansion */
+ struct savedfunc *sv;
+ } stkbuf[MAX_REC_INLINE], *stk = stkbuf + MAX_REC_INLINE, *stkend = stk;
+ bool dumpbefore = 0;
+ do {
+ if (stk != stkend && b == stk->b)
+ ++stk; /* pop */
+ else if (stk == stkbuf) /* stack full? */
+ continue;
+ for (int i = 0; i < b->ins.n; ++i) {
+ struct instr *ins = &instrtab[b->ins.p[i]];
+ if (ins->op != Ocall) continue;
+ if (!isaddrcon(ins->l,0)) continue;
+ struct call *call = &calltab.p[ins->r.i];
+ internstr fname = xcon2sym(ins->l.i);
+ struct savedfunc **pcallee, *sv;
+ if ((pcallee = pmap_get(&savedfns, fname))
+ && (sv = *pcallee)->nabiarg == call->narg && call->vararg == -1
+ && call->narg == sv->nabiarg
+ && (!call->narg || !memcmp(sv->abiarg, call->abiarg, sizeof *sv->abiarg * sv->nabiarg))
+ && !memcmp(sv->abiret, call->abiret, sizeof sv->abiret)) {
+ for (struct stack *s = stk; s != stkend; ++s) {
+ if (s->sv == sv) goto Skip; /* recursion encountered */
+ }
+ if (ccopt.dbg.y) {
+ if (!dumpbefore) {
+ bfmt(ccopt.dbgout, "<< Before inlining >>\n");
+ irdump(fn);
+ dumpbefore = 1;
+ }
+ }
+
+ (--stk)->b = inlcall(fn, b, i, *pcallee);
+ stk->sv = sv;
+ if (ccopt.dbg.y) {
+ bfmt(ccopt.dbgout, "<< After inlining '%s' (@%d-@%d) >>\n", fname, b->lnext->id, stk->b->id);
+ irdump(fn);
+ }
+ break;
+ }
+ Skip:;
+ }
+ } while ((b = b->lnext) != fn->entry);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_intrin.c b/src/ir_intrin.c
new file mode 100644
index 0000000..ca49341
--- /dev/null
+++ b/src/ir_intrin.c
@@ -0,0 +1,77 @@
+#include "ir.h"
+
+struct arg { union ref *arg, *ty; };
+
+static int
+intrin(struct block *blk, int *curi, enum intrin in, struct arg *args, int narg, union irtype ret)
+{
+ struct instr *this = &instrtab[blk->ins.p[*curi]];
+ const struct typedata *td;
+ union irtype ty;
+ uint ncopy, step;
+
+ switch (in) {
+ case 0: assert(0);
+ case INstructcopy:
+ assert(narg == 2 && args[0].ty->bits == args[1].ty->bits);
+ ty = ref2type(*args[0].ty);
+ assert(ty.isagg);
+ td = &typedata[ty.cls];
+ step = td->align <= 8 ? td->align : 8;
+ ncopy = td->siz / step;
+ if (ncopy > 4) {
+ enum irclass cls = siz2intcls[cls2siz[KPTR]];
+ /* memcpy */
+ *args[1].ty = *args[0].ty = mktyperef(cls2type(KPTR));
+ insertinstr(blk, (*curi)++, mkarginstr(cls2type(cls), mkintcon(cls, td->siz)));
+ *this = mkinstr(Ocall, 0, mksymref(intern("memcpy"), SFUNC), this->r);
+ calltab.p[this->r.i].narg = 3;
+ calltab.p[this->r.i].ret = cls2type(0);
+ return 0;
+ } else {
+ delinstr(blk, (*curi)--);
+ for (int off = 0; off < td->siz; off += step) {
+ union ref psrc = *args[1].arg, pdst = *args[0].arg, src;
+ if (off) {
+ pdst = insertinstr(blk, ++*curi, mkinstr(Oadd, KPTR, *args[0].arg, mkref(RICON, off)));
+ psrc = insertinstr(blk, ++*curi, mkinstr(Oadd, KPTR, *args[1].arg, mkref(RICON, off)));
+ }
+ src = insertinstr(blk, ++*curi, mkinstr(Oloads8 + 2*ilog2(step), step < 8 ? KI32 : KI64, psrc));
+ insertinstr(blk, ++*curi, mkinstr(Ostorei8 + ilog2(step), 0, pdst, src));
+ }
+ return 1;
+ }
+ }
+ assert(0);
+}
+
+void
+lowerintrin(struct function *fn)
+{
+ struct block *blk = fn->entry;
+ struct arg argsbuf[32];
+ vec_of(struct arg) args = VINIT(argsbuf, countof(argsbuf));
+
+ do {
+ for (int i = 0; i < blk->ins.n; ++i) {
+ struct instr *ins = &instrtab[blk->ins.p[i]];
+ if (ins->op == Oarg)
+ vpush(&args, ((struct arg){ &ins->r, &ins->l }));
+ else if (ins->op == Ocall)
+ vinit(&args, argsbuf, countof(argsbuf));
+ else if (ins->op == Ointrin) {
+ int arg0 = i - args.n;
+ assert(calltab.p[ins->r.i].narg == args.n);
+ if (intrin(blk, &i, ins->l.i, args.p, args.n, calltab.p[ins->r.i].ret))
+ for (int j = args.n; j > 0; --j, --i)
+ delinstr(blk, arg0);
+ else
+ abi0_call(fn, ins, blk, &i);
+ vinit(&args, argsbuf, countof(argsbuf));
+ }
+ }
+ assert(args.n == 0);
+ } while ((blk = blk->lnext) != fn->entry);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_intrin.def b/src/ir_intrin.def
new file mode 100644
index 0000000..2ccc3b5
--- /dev/null
+++ b/src/ir_intrin.def
@@ -0,0 +1,2 @@
+/* NAME NARG */
+_(structcopy, 2)
diff --git a/src/ir_mem2reg.c b/src/ir_mem2reg.c
new file mode 100644
index 0000000..7a5874c
--- /dev/null
+++ b/src/ir_mem2reg.c
@@ -0,0 +1,317 @@
+#include "ir.h"
+#include <stdlib.h> /* qsort */
+
+static const uchar loadszcls[] = {
+ [Oloads8 - Oloads8] = 1|KI32<<4, [Oloadu8 - Oloads8] = 1|KI32<<4,
+ [Oloads16 - Oloads8] = 2|KI32<<4, [Oloadu16 - Oloads8] = 2|KI32<<4,
+ [Oloads32 - Oloads8] = 4|KI32<<4, [Oloadu32 - Oloads8] = 4|KI32<<4,
+ [Oloadi64 - Oloads8] = 8|KI64<<4,
+ [Oloadf32 - Oloads8] = 4|KF32<<4,
+ [Oloadf64 - Oloads8] = 8|KF64<<4,
+};
+static const uchar load2ext[] = {
+ [Oloads8 - Oloads8] = Oexts8, [Oloadu8 - Oloads8] = Oextu8,
+ [Oloads16 - Oloads8] = Oexts16, [Oloadu16 - Oloads8] = Oextu16,
+ [Oloads32 - Oloads8] = Oexts32, [Oloadu32 - Oloads8] = Oextu32,
+ [Oloadi64 - Oloads8] = Ocopy,
+};
+static const uchar storesz[] = {
+ [Ostorei8 - Ostorei8] = 1,
+ [Ostorei16 - Ostorei8] = 2,
+ [Ostorei32 - Ostorei8] = 4,
+ [Ostorei64 - Ostorei8] = 8,
+ [Ostoref32 - Ostorei8] = 4,
+ [Ostoref64 - Ostorei8] = 8,
+};
+#define loadsz(o) (loadszcls[(o) - Oloads8] & 0xF)
+#define loadcls(o) (loadszcls[(o) - Oloads8] >> 4)
+#define load2ext(o) (load2ext[(o) - Oloads8])
+#define storesz(o) (storesz[(o) - Ostorei8])
+
+/* Implements algorithm in 'Simple and Efficient Construction of Static Single Assignment' (Braun et al) */
+
+struct ssabuilder {
+ struct arena **arena;
+ imap_of(union ref *) curdefs; /* map of var to (map of block to def of var) */
+ struct bitset *sealed, /* set of sealed blocks */
+ *marked; /* blocks marked, for 'Marker Algorithm' in the paper */
+ int lastvisit;
+ int nblk;
+};
+
+static union ref readvar(struct ssabuilder *, int var, enum irclass, struct block *);
+
+static union ref
+deltrivialphis(struct ssabuilder *sb, int var, struct block *blk, union ref phiref)
+{
+ assert(instrtab[phiref.i].op == Ophi);
+ union ref *args = phitab.p[instrtab[phiref.i].l.i];
+ union ref same = {0};
+ for (int i = 0; i < blk->npred; ++i) {
+ if (args[i].bits == same.bits || args[i].bits == phiref.bits) {
+ continue; /* unique value or self-reference */
+ } if (same.bits != 0) {
+ return phiref; /* non-trivial */
+ }
+ same = args[i];
+ }
+
+ if (same.bits == 0)
+ same = UNDREF; /* the phi is unreachable or in the start block */
+
+ /* replace uses */
+ replcuses(phiref, same);
+ union ref **pcurdefs = imap_get(&sb->curdefs, var);
+ assert (pcurdefs);
+ for (int i = blk->id; i < sb->nblk; ++i) {
+ if ((*pcurdefs)[i].bits == phiref.bits)
+ (*pcurdefs)[i] = same;
+ }
+
+ /* stops infinite recursion and marks phi for removal */
+ instrtab[phiref.i].op = Onop;
+
+ /* recursively try to remove all phi users as they might have become trivial */
+Redo:
+ for (struct use *use = instruse[phiref.i]; use; use = use->next) {
+ if (use->u != USERJUMP && instrtab[use->u].op == Ophi && use->u != phiref.i) {
+ union ref it = mkref(RTMP, use->u);
+ union ref vphi2 = deltrivialphis(sb, var, use->blk, it);
+ if (vphi2.bits != it.bits) {
+ same = vphi2;
+ /* deletion happened so phiref use may have changed */
+ goto Redo;
+ }
+ }
+ }
+ deluses(phiref.i);
+
+ return same;
+}
+
+static union ref
+addphiargs(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk, union ref phiref)
+{
+ union ref *args = phitab.p[instrtab[phiref.i].l.i];
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *pred = blkpred(blk, i);
+ args[i] = readvar(sb, var, cls, pred);
+ adduse(blk, phiref.i, args[i]);
+ }
+ instrtab[phiref.i].r.bits = 0;
+ return deltrivialphis(sb, var, blk, phiref);
+}
+
+static void
+writevar(struct ssabuilder *sb, int var, struct block *blk, union ref val)
+{
+ union ref **pcurdefs;
+ if (!(pcurdefs = imap_get(&sb->curdefs, var))) {
+ pcurdefs = imap_set(&sb->curdefs, var, allocz(sb->arena, sb->nblk * sizeof(union ref), 0));
+ }
+ if (val.t == RTMP) assert(instrtab[val.i].op != Onop);
+ (*pcurdefs)[blk->id] = val;
+}
+
+enum { RPENDINGPHI = 7 };
+static union ref
+readvarrec(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk)
+{
+ union ref val;
+ assert(blk->npred > 0);
+ if (!bstest(sb->sealed, blk->id)) { /* unsealed block */
+ /* add pending phi */
+ val = insertphi(blk, cls);
+ instrtab[val.i].r = mkref(RPENDINGPHI, var);
+ } else if (blk->npred == 1) { /* trivial case when block has a single predecessor */
+ val = readvar(sb, var, cls, blkpred(blk, 0));
+ } else if (!bstest(sb->marked, blk->id)) {
+ /* Marker Algorithm to optimize the common case where a control flow join doesn't
+ * need a phi function for the variable: mark the block in case we recursively reach
+ * it again, collect defs from predecessors, only create a phi if they differ.
+ * This avoids creating temporary trivial phi functions in acyclic data flow
+ */
+ bsset(sb->marked, blk->id);
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *pred = blkpred(blk, i);
+ union ref it = readvar(sb, var, cls, pred);
+ if (!bstest(sb->marked, blk->id)) {
+ /* recursion reached this blk again, use its phi */
+ union ref **pcurdefs = imap_get(&sb->curdefs, var);
+ /* must have called writevar */
+ assert(*pcurdefs && (*pcurdefs)[blk->id].bits);
+ return (*pcurdefs)[blk->id];
+ }
+ if (i == 0) val = it;
+ else if (it.bits != val.bits) /* found a different definition, must add phi */
+ goto AddPhi;
+ }
+ bsclr(sb->marked, blk->id);
+ } else { /* reached block while recursing */
+ AddPhi:
+ val = insertphi(blk, cls);
+ writevar(sb, var, blk, val);
+ val = addphiargs(sb, var, cls, blk, val);
+ bsclr(sb->marked, blk->id);
+ return val;
+ }
+ writevar(sb, var, blk, val);
+ return val;
+}
+
+static union ref
+readvar(struct ssabuilder *sb, int var, enum irclass cls, struct block *blk)
+{
+ union ref **pcurdefs;
+ if ((pcurdefs = imap_get(&sb->curdefs, var)) && (*pcurdefs)[blk->id].bits)
+ return (*pcurdefs)[blk->id];
+ if (blk->npred == 0) /* entry block, var is read before being written to */
+ return UNDREF;
+ return readvarrec(sb, var, cls, blk);
+}
+
+static bool
+trysealrec(struct ssabuilder *sb, struct block *blk)
+{
+Recur:
+ if (bstest(sb->sealed, blk->id)) return 1;
+ if (blk->id > sb->lastvisit) return 0;
+ markvisited(blk);
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *p = blkpred(blk, i);
+ if (wasvisited(p)) continue;
+ if (p->id > sb->lastvisit) return 0;
+ }
+
+ bsset(sb->sealed, blk->id);
+ for (int i = 0; i < blk->phi.n; ++i) {
+ struct instr *ins = &instrtab[blk->phi.p[i]];
+ if (ins->r.t == RPENDINGPHI)
+ addphiargs(sb, ins->r.i, ins->cls, blk, mkref(RTMP, blk->phi.p[i]));
+ }
+ if (blk->s1 && !blk->s2) {
+ blk = blk->s1;
+ goto Recur;
+ } else if (blk->s1 && blk->s2) {
+ trysealrec(sb, blk->s1);
+ blk = blk->s2;
+ goto Recur;
+ }
+ return 1;
+}
+
+static void
+tryseal(struct ssabuilder *sb, struct block *blk)
+{
+ startbbvisit();
+ trysealrec(sb, blk);
+}
+
+static int
+blkfindins(struct block *blk, int ins)
+{
+ for (int i = 0; i < blk->phi.n; ++i)
+ if (blk->phi.p[i] == ins) return -1;
+ for (int i = 0; i < blk->ins.n; ++i)
+ if (blk->ins.p[i] == ins) return i;
+ assert(0 && "ins not in blk");
+}
+
+static int
+rcmpuse(const void *a, const void *b)
+{
+ /* postorder sort */
+ const struct use *ua = *(struct use **)a, *ub = *(struct use **)b;
+ struct block *blk = ua->blk;
+ if (ua->blk != ub->blk) return ub->blk->id - ua->blk->id;
+ assert(ua->u != USERJUMP && ub->u != USERJUMP);
+ return blkfindins(blk, ub->u) - blkfindins(blk, ua->u);
+}
+
+void
+mem2reg(struct function *fn)
+{
+ struct ssabuilder sb = { fn->passarena, .nblk = fn->nblk };
+
+ FREQUIRE(FNUSE);
+
+ sb.sealed = allocz(sb.arena, BSSIZE(fn->nblk) * sizeof *sb.sealed, 0);
+ sb.marked = allocz(sb.arena, BSSIZE(fn->nblk) * sizeof *sb.sealed, 0);
+ sortrpo(fn);
+
+ struct block *blk = fn->entry;
+ do {
+ for (int i = 0; i < blk->ins.n; ++i) {
+ enum irclass k = 0;
+ int sz = 0;
+ enum op ext = Ocopy;
+ int var = blk->ins.p[i];
+ struct instr *ins = &instrtab[var];
+ struct use *use;
+
+ /* find allocas only used in loads/stores of uniform size */
+ if (!oisalloca(ins->op) || !(use = instruse[var])) continue;
+ struct use *usesbuf[64];
+ vec_of(struct use *) uses = VINIT(usesbuf, countof(usesbuf));
+ do {
+ if (use->u == USERJUMP) goto Skip;
+ struct instr *m = &instrtab[use->u];
+ if (oisload(m->op) && (!sz || sz == loadsz(m->op))) {
+ sz = loadsz(m->op);
+ k = loadcls(m->op);
+ if (sz < 4) ext = load2ext(m->op);
+ } else if (oisstore(m->op) && m->l.bits == mkref(RTMP, var).bits
+ && (!sz || sz == storesz(m->op))) {
+ sz = storesz(m->op);
+ } else goto Skip;
+ vpush(&uses, use);
+ } while ((use = use->next));
+
+ /* visit uses in rpo */
+ qsort(uses.p, uses.n, sizeof *uses.p, rcmpuse);
+ for (int i = uses.n-1; i >= 0; --i) {
+ use = uses.p[i];
+ ins = &instrtab[use->u];
+
+ if (oisstore(ins->op)) {
+ writevar(&sb, var, use->blk, ins->r);
+ *ins = mkinstr(Onop,0,);
+ } else if (oisload(ins->op)) {
+ union ref val = readvar(&sb, var, k = ins->cls, use->blk);
+ adduse(use->blk, use->u, val);
+ if (ext != Ocopy && isintcon(val)) { /* fold constant int extension */
+ val = irunop(NULL, ext, k, val);
+ assert(val.bits);
+ ext = Ocopy;
+ }
+ *ins = mkinstr(ext, k, val);
+ }
+ }
+ /* remove alloca */
+ delinstr(blk, i--);
+
+ Skip:
+ vfree(&uses);
+ }
+
+ assert(sb.lastvisit == blk->id);
+ ++sb.lastvisit;
+ tryseal(&sb, blk);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ do {
+ /* remove phis marked for deletion */
+ for (int i = 0; i < blk->phi.n; ++i)
+ if (instrtab[blk->phi.p[i]].op == Onop)
+ delphi(blk, i--);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ imap_free(&sb.curdefs);
+ if (ccopt.dbg.m) {
+ bfmt(ccopt.dbgout, "<< After mem2reg >>\n");
+ irdump(fn);
+ }
+}
+
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_op.def b/src/ir_op.def
new file mode 100644
index 0000000..4a18b4b
--- /dev/null
+++ b/src/ir_op.def
@@ -0,0 +1,79 @@
+/* OP NARG */
+_(nop, 0)
+_(copy, 1)
+_(move, 2)
+_(neg, 1)
+_(not, 1)
+_(cvtf32s, 1)
+_(cvtf32u, 1)
+_(cvtf32f64, 1)
+_(cvtf64s, 1)
+_(cvtf64u, 1)
+_(cvtf64f32, 1)
+_(cvts32f, 1)
+_(cvtu32f, 1)
+_(cvts64f, 1)
+_(cvtu64f, 1)
+_(exts8, 1)
+_(extu8, 1)
+_(exts16, 1)
+_(extu16, 1)
+_(exts32, 1)
+_(extu32, 1)
+_(bswap16, 1)
+_(bswap32, 1)
+_(bswap64, 1)
+_(add, 2)
+_(sub, 2)
+_(mul, 2)
+_(div, 2)
+_(udiv, 2)
+_(rem, 2)
+_(urem, 2)
+_(and, 2)
+_(ior, 2)
+_(xor, 2)
+_(shl, 2)
+_(sar, 2)
+_(slr, 2)
+_(equ, 2)
+_(neq, 2)
+_(lth, 2)
+_(gth, 2)
+_(lte, 2)
+_(gte, 2)
+_(ulth, 2)
+_(ugth, 2)
+_(ulte, 2)
+_(ugte, 2)
+_(alloca1, 1)
+_(alloca2, 1)
+_(alloca4, 1)
+_(alloca8, 1)
+_(alloca16, 1)
+_(loads8, 1)
+_(loadu8, 1)
+_(loads16, 1)
+_(loadu16, 1)
+_(loads32, 1)
+_(loadu32, 1)
+_(loadi64, 1)
+_(loadf32, 1)
+_(loadf64, 1)
+_(storei8, 2)
+_(storei16, 2)
+_(storei32, 2)
+_(storei64, 2)
+_(storef32, 2)
+_(storef64, 2)
+_(param, 2)
+_(arg, 2)
+_(call, 2)
+_(call2r, 1)
+_(intrin, 2)
+_(phi, 1)
+_(swap, 2)
+_(vastart, 1)
+_(vaarg, 2)
+/* machine-specific instructions */
+_(xvaprologue, 1)
diff --git a/src/ir_regalloc.c b/src/ir_regalloc.c
new file mode 100644
index 0000000..1200c77
--- /dev/null
+++ b/src/ir_regalloc.c
@@ -0,0 +1,1417 @@
+#include "ir.h"
+
+/** Implements linear scan register allocation **/
+/* Some references:
+ * Linear Scan Register Allocation on SSA Form (Wimmer 2010)
+ - https://c9x.me/compile/bib/Wimmer10a.pdf
+ * Linear Scan Register Allocation in the Context of SSA Form
+ and Register Constraints (Mössenböck 2002)
+ - https://bernsteinbear.com/assets/img/linear-scan-ra-context-ssa.pdf
+ */
+
+#if 1
+#define DBG(...) if(ccopt.dbg.r) bfmt(ccopt.dbgout, __VA_ARGS__)
+#else
+#define DBG(...) ((void)0)
+#endif
+
+/* The algorithm used here to introduce phis for temporaries whose definitions
+ * appear later than some of its uses is very similar to that in mem2reg() */
+
+static int livelastblk;
+struct pendingphi { ushort var, phi; };
+static vec_of(struct pendingphi) *pendingphis;
+static int npendingphi;
+static ushort **curdefs;
+static union ref readvar(struct bitset *defined, enum irclass cls, int var, struct block *blk);
+
+static void
+fillphi(struct bitset *defined, union ref phi, enum irclass cls, int var, struct block *blk)
+{
+ union ref *args = phitab.p[instrtab[phi.i].l.i];
+ assert(blk->npred > 0);
+ for (int i = 0; i < blk->npred; ++i)
+ args[i] = readvar(defined, cls, var, blk);
+}
+
+static union ref
+readvar(struct bitset *defined, enum irclass cls, int var, struct block *blk)
+{
+ union ref val;
+
+ if (bstest(defined, var)) return mkref(RTMP, var);
+ assert(cls && "?");
+
+ /* memoed definition */
+ if (xbcap(curdefs) > blk->id && xbcap(curdefs[blk->id]) > var && curdefs[blk->id][var])
+ return mkref(RTMP, curdefs[blk->id][var]);
+
+ xbgrowz(&curdefs, blk->id + 1);
+ if (blk->id > livelastblk) {
+ ++npendingphi;
+ val = insertphi(blk, cls);
+ xbgrowz(&pendingphis, blk->id + 1);
+ vpush(&pendingphis[blk->id], ((struct pendingphi) { var, val.i }));
+ } else if (blk->npred == 1) {
+ val = readvar(defined, cls, var, blkpred(blk, 0));
+ } else {
+ val = insertphi(blk, cls);
+ fillphi(defined, val, cls, var, blk);
+ }
+ xbgrowz(&curdefs[blk->id], var + 1);
+ assert(val.i > 0);
+ curdefs[blk->id][var] = val.i;
+ return val;
+}
+
+static void
+liveuse(struct bitset *defined, struct instr *ins, union ref *r, struct block *blk)
+{
+ int var;
+ if (r->t == RADDR) {
+ liveuse(defined, ins, &addrtab.p[r->i].base, blk);
+ liveuse(defined, ins, &addrtab.p[r->i].index, blk);
+ return;
+ } else if (r->t != RTMP) return;
+ var = r->i;
+ if (bstest(defined, var)) return;
+
+ *r = readvar(defined, insrescls(instrtab[r->i]), var, blk);
+}
+
+/* regalloc() assumes every use of a temporary is visited before its definition
+ * so this pass fixes cases where that would not apply by introducing phi functions */
+static void
+fixlive(struct function *fn)
+{
+ extern int ninstr;
+ struct block *blk = fn->entry;
+ struct bitset definedbuf[4] = {0};
+ struct bitset *defined = definedbuf;
+
+ if (BSSIZE(ninstr) >= countof(definedbuf))
+ defined = xcalloc(sizeof *defined * BSSIZE(ninstr));
+ npendingphi = 0;
+
+ do {
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int var = blk->phi.p[i];
+ bsset(defined, var);
+ }
+
+ for (int i = 0; i < blk->ins.n; ++i) {
+ int var = blk->ins.p[i];
+ struct instr *ins = &instrtab[var];
+ if (ins->l.t) liveuse(defined, ins, &ins->l, blk);
+ if (ins->r.t) liveuse(defined, ins, &ins->r, blk);
+ bsset(defined, var);
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+
+ do {
+ vec_of(struct pendingphi) *pphi;
+
+ if (!npendingphi) break;
+ if (xbcap(pendingphis) <= blk->id) break;
+
+ pphi = (void *)&pendingphis[blk->id];
+ npendingphi -= pphi->n;
+ for (int i = 0; i < pphi->n; ++i) {
+ fillphi(defined, mkref(RTMP, pphi->p[i].phi), instrtab[pphi->p[i].phi].cls, pphi->p[i].var, blk);
+ }
+ vfree(pphi);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (ccopt.dbg.l) {
+ bfmt(ccopt.dbgout, "<< After liveness fixup >>\n");
+ irdump(fn);
+ }
+ if (defined != definedbuf) free(defined);
+}
+
+static regset gpregset, fpregset;
+
+#define isfpr(reg) in_range((reg), mctarg->fpr0, mctarg->fpr0 + mctarg->nfpr - 1)
+#define isgpr(reg) in_range((reg), mctarg->gpr0, mctarg->gpr0 + mctarg->nfpr - 1)
+
+/* an allocated physical register or stack slot */
+enum { ADEAD, AREG, ASTACK };
+union alloc { struct { ushort t : 2, a : 14; }; ushort bits; };
+#define afree() ((union alloc) { .t=ADEAD })
+#define areg(r) ((union alloc) { .t=AREG, .a=(r) })
+#define astack(s) ((union alloc) { .t=ASTACK, .a=(s) })
+
+enum { MAXSPILL = 512 };
+
+/* half-closed instr range [from, to) */
+struct range { ushort from, to; };
+#define mkrange(f,t) ((struct range){(f), (t)})
+
+/* a temporary's lifetime interval */
+struct interval {
+ struct interval *next; /* for linked list of active,inactive,handled sets in linear scan */
+ union alloc alloc;
+ schar rhint : 7; /* register hint */
+ bool fpr : 1; /* needs float register? */
+ uint cost; /* spilling cost estimate */
+
+ /* sorted ranges array */
+ ushort nrange;
+ union {
+ struct range _rinl[2];
+ struct range *_rdyn;
+ };
+};
+
+struct rega {
+ struct function *fn;
+ struct arena **arena;
+
+ int intercount; /* number of actual intervals */
+ int ninter; /* size of inter */
+ struct interval *inter; /* map of tmp -> interval */
+ struct fixinterval {
+ struct fixinterval *next;
+ regset rs;
+ struct range range;
+ } *fixed; /* linked list of fixed intervals, always sorted */
+
+ struct bitset freestk[BSSIZE(MAXSPILL)]; /* free stack slots */
+ int maxstk, /* highest stack slot used */
+ stktop;
+};
+
+#define stkslotref(fn, off) \
+ (mkaddr((struct addr){.base = mkref(RREG, mctarg->bpr), .disp = -(fn)->stksiz - 8 - (off)}))
+
+/* Parallel moves algorithm from QBE: https://c9x.me/git/qbe.git/tree/rega.c?id=e493a7f23352f51acc0a1e12284ab19d7894488a#n201 */
+
+enum { PMTOMOVE, PMMOVING, PMDONE };
+struct pmstate {
+ struct function *fn;
+ int npmove;
+ struct pmove {
+ uchar k; /* enum irclass */
+ uchar stat; /* PMTOMOVE|MOVING|DONE */
+ union alloc dst, src;
+ } pmove[MAXREGS];
+};
+
+static void
+pmadd(struct pmstate *pms, enum irclass k, union alloc dst, union alloc src)
+{
+ if (!memcmp(&dst, &src, sizeof dst)) return;
+ assert(pms->npmove < MAXREGS);
+ pms->pmove[pms->npmove++] = (struct pmove) { k, PMTOMOVE, dst, src };
+}
+
+#define mkmove(k, rd, rs) mkinstr(Omove, k, mkref(RREG, rd), mkref(RREG, rs))
+static void
+emitmove(struct function *fn, enum irclass k, union alloc dst, union alloc src, struct block *blk, int curi)
+{
+ struct instr mv = {.keep = 1};
+ int reg;
+ if (dst.t == AREG && src.t == AREG) {
+ insertinstr(blk, curi, mkmove(k, dst.a, src.a));
+ return;
+ }
+ if (src.t == ASTACK) {
+ switch (mv.cls = k) {
+ default: assert(0);
+ case KI32: mv.op = Oloads32; break;
+ case KI64: mv.op = Oloadi64; break;
+ case KPTR: mv.op = targ_64bit ? Oloadi64 : Oloads32; break;
+ case KF32: mv.op = Oloadf32; break;
+ case KF64: mv.op = Oloadf64; break;
+ }
+ if (dst.t == AREG)
+ reg = dst.a;
+ else
+ reg = kisint(k) ? mctarg->gprscratch : mctarg->fprscratch;
+ mv.reg = reg+1;
+ mv.l = stkslotref(fn, src.a*8);
+ insertinstr(blk, curi++, mv);
+ } else reg = src.a;
+ if (dst.t == ASTACK) {
+ mv = mkinstr(cls2store[k], 0, stkslotref(fn, dst.a*8), mkref(RREG, reg));
+ insertinstr(blk, curi, mv);
+ }
+}
+
+static int
+pmrec(struct pmstate *pms, int i, struct block *blk, int curi, enum irclass *k)
+{
+ struct pmove *pm = &pms->pmove[i];
+ if (pm->dst.bits == pm->src.bits) {
+ pm->stat = PMDONE;
+ return -1;
+ }
+
+ /* widen when necessary */
+ assert(kisint(pm->k) == kisint(*k));
+ if (cls2siz[pm->k] > cls2siz[*k])
+ *k = pm->k;
+
+ int j, c;
+ for (j = 0; j < pms->npmove; ++j) {
+ if (pms->pmove[j].dst.bits == pm->src.bits)
+ break;
+ }
+ if (j == pms->npmove) goto Done;
+ switch (pms->pmove[j].stat) {
+ default: assert(0);
+ case PMMOVING:
+ c = j;
+ Swap:
+ if (pm->src.t == AREG && pm->dst.t == AREG) {
+ insertinstr(blk, curi,
+ mkinstr(Oswap, *k, mkref(RREG, pm->dst.a), mkref(RREG, pm->src.a), .keep = 1));
+ } else if (pm->src.t != pm->dst.t) {
+ union alloc reg, stk, regtmp;
+ if (pm->src.t == AREG)
+ reg = pm->src, stk = pm->dst;
+ else
+ stk = pm->src, reg = pm->dst;
+ assert(reg.t == AREG && stk.t == ASTACK);
+ regtmp = areg(kisint(*k) ? mctarg->gprscratch : mctarg->fprscratch);
+ emitmove(pms->fn, *k, regtmp, stk, blk, curi++);
+ insertinstr(blk, curi++,
+ mkinstr(Oswap, *k, mkref(RREG, reg.a), mkref(RREG, regtmp.a), .keep = 1));
+ emitmove(pms->fn, *k, stk, regtmp, blk, curi++);
+ } else {
+ /* FIXME using scratch gpr and fpr for this is hackish */
+ assert(pm->src.t == ASTACK && pm->dst.t == ASTACK);
+ int r1 = mctarg->gprscratch, r2 = mctarg->fprscratch;
+ enum irclass k1 = siz2intcls[cls2siz[*k]], k2 = KF32 + (cls2siz[*k] == 8);
+ emitmove(pms->fn, k1, areg(r1), pm->src, blk, curi++);
+ emitmove(pms->fn, k2, areg(r2), pm->dst, blk, curi++);
+ emitmove(pms->fn, k1, pm->dst, areg(r1), blk, curi++);
+ emitmove(pms->fn, k2, pm->src, areg(r2), blk, curi++);
+ }
+ break;
+ case PMTOMOVE:
+ pm->stat = PMMOVING;
+ c = pmrec(pms, j, blk, curi, k);
+ if (c == i) {
+ c = -1;
+ break;
+ } else if (c != -1) {
+ goto Swap;
+ }
+ /* fallthru */
+ case PMDONE:
+ Done:
+ c = -1;
+ emitmove(pms->fn, *k, pm->dst, pm->src, blk, curi);
+ break;
+ }
+
+ pm->stat = PMDONE;
+ return c;
+}
+
+static void
+emitpm(struct pmstate *pms, struct block *blk)
+{
+ int curi = blk->ins.n;
+ for (int i = 0; i < pms->npmove; ++i) {
+ if (pms->pmove[i].stat == PMTOMOVE) {
+ pmrec(pms, i, blk, curi, &(enum irclass) { pms->pmove[i].k });
+ }
+ }
+}
+
+/* remove phis by inserting parallel moves */
+static void
+lowerphis(struct rega *ra, struct block *blk, struct block *suc)
+{
+ int predno;
+ struct block *n = NULL;
+
+ if (!blk->npred && blk != ra->fn->entry) {
+ assert(!blk->phi.n);
+ blk->ins.n = 0;
+ return;
+ }
+ if (!blk->s2) n = blk;
+
+ for (predno = 0; predno < suc->npred; ++predno)
+ if (blkpred(suc, predno) == blk)
+ break;
+ assert(predno < suc->npred);
+
+ struct pmstate pms;
+ pms.fn = ra->fn;
+ pms.npmove = 0;
+ /* ensure phi args go to the same slot as phi with parallel copies */
+ for (int i = 0; i < suc->phi.n; ++i) {
+ struct instr *phi = &instrtab[suc->phi.p[i]];
+ union ref *arg = &phitab.p[phi->l.i][predno];
+ union alloc from, to;
+
+ if (arg->t == RREG) continue;
+ assert(arg->t == RTMP);
+ DBG("resolve phi @%d, @%d, %%%d <- %%%d\n", blk->id, suc->id, phi - instrtab, arg->i);
+ if (instrtab[arg->i].reg) {
+ from = areg(instrtab[arg->i].reg - 1);
+ DBG(" it had R%d\n", from.a);
+ } else {
+ from = ra->inter[arg->i].alloc;
+ assert(from.t != ADEAD);
+ DBG(" found %c%d\n", " RS"[from.t], from.a);
+ if (from.t == AREG)
+ instrtab[arg->i].reg = from.a+1;
+ }
+ if (phi->reg) {
+ to = areg(phi->reg - 1);
+ DBG(" phi had R%d\n", to.a);
+ } else {
+ to = ra->inter[phi - instrtab].alloc;
+ if (to.t == ADEAD) {
+ DBG(" skip dead phi\n");
+ continue;
+ }
+ DBG(" found phi %c%d\n", " RS"[to.t], to.a);
+ if (to.t == AREG)
+ phi->reg = to.a+1;
+ }
+ DBG(" > phi move %c%d -> %c%d\n", " RS"[from.t], from.a, " RS"[to.t], to.a);
+ if (!n) n = insertblk(ra->fn, blk, suc);
+ pmadd(&pms, phi->cls, to, from);
+ }
+ if (n) emitpm(&pms, n);
+}
+
+/* generate copies for phi operands to transform into conventional-SSA */
+static void
+fixcssa(struct function *fn)
+{
+ struct block *blk = fn->entry;
+ do {
+ if (!blk->phi.n) continue;
+ for (int p = 0; p < blk->npred; ++p) {
+ struct block *n, *pred = blkpred(blk, p);
+ if (!pred->s2) {
+ /* pred only has 1 successor (blk), so insert move directly in it */
+ n = pred;
+ } else {
+ n = insertblk(fn, pred, blk);
+ assert(n->jmp.t == Jb && n->s1 == blk);
+ }
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int phi = blk->phi.p[i];
+ union ref *args = phitab.p[instrtab[phi].l.i];
+ args[p] = insertinstr(n, n->ins.n, mkinstr(Ocopy, instrtab[phi].cls, args[p]));
+ }
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+
+ fn->prop &= ~FNBLKID;
+}
+
+static inline bool
+rangeoverlap(struct range a, struct range b)
+{
+ return a.from < b.to && b.from < a.to;
+}
+
+static void
+pushrange(struct interval *it, struct range r)
+{
+ if (it->nrange < 2) it->_rinl[it->nrange++] = r;
+ else if (it->nrange > 2) xbpush(&it->_rdyn, &it->nrange, r);
+ else {
+ struct range *d = NULL;
+ xbgrow(&d, 4);
+ memcpy(d, it->_rinl, 2*sizeof *d);
+ d[it->nrange++] = r;
+ it->_rdyn = d;
+ }
+}
+#define itrange(it, i) ((it)->nrange <= 2 ? (it)->_rinl : (it)->_rdyn)[i]
+
+static inline int
+intervalbeg(struct interval *it)
+{
+ assert(it->nrange);
+ return itrange(it, 0).from;
+}
+
+static inline int
+intervalend(struct interval *it)
+{
+ assert(it->nrange);
+ return itrange(it, it->nrange-1).to;
+}
+
+static bool
+intersoverlap(struct interval *a, struct interval *b)
+{
+ for (int i = 0, j = 0; i < a->nrange && j < b->nrange; ) {
+ struct range r1 = itrange(a, i), r2 = itrange(b, j);
+ if (rangeoverlap(r1, r2)) return 1;
+ if (r1.to <= r2.from) ++i;
+ else ++j;
+ }
+ return 0;
+}
+
+static inline void
+incrcost(struct interval *it, struct block *blk)
+{
+ /* treat each loop as executing instr 8 times */
+ it->cost += 1 << (blk->loop * 3);
+}
+
+static bool
+intervaldef(struct rega *ra, int t, struct block *blk, int pos, int reghint)
+{
+ struct interval *it = &ra->inter[t];
+ if (it->nrange) {
+ ushort *beg = &itrange(it, 0).from;
+ assert(*beg <= pos);
+ incrcost(it, blk);
+ *beg = pos;
+ return 1;
+ }
+ return 0;
+}
+
+static void
+addrange(struct rega *ra, int t, struct range new, int reghint)
+{
+ struct interval *it = &ra->inter[t];
+ struct range *fst;
+ int n;
+
+ if (!it->nrange) {
+ ++ra->intercount;
+ it->rhint = reghint;
+ it->fpr = kisflt(insrescls(instrtab[t]));
+ pushrange(it, new);
+ return;
+ }
+
+ fst = &itrange(it, 0);
+ /* fully covered by first range? */
+ if (fst->from <= new.from && fst->to >= new.to) return;
+ /* overlaps with first range ? */
+ if (fst->from <= new.to && new.to < fst->to) {
+ fst->from = new.from;
+ } else {
+ /* put new range at the start */
+ pushrange(it, new);
+ memmove(&itrange(it, 1), &itrange(it, 0), sizeof(struct range) * (it->nrange - 1));
+ itrange(it, 0) = new;
+ }
+
+ /* new range might cover existing ranges (loop header lives),
+ * check and succesively merge */
+ fst = &itrange(it, 0);
+ n = 0;
+ for (int i = 1; i < it->nrange; ++i) {
+ struct range other = itrange(it, i);
+ if (fst->to >= other.from) {
+ fst->to = fst->to > other.to ? fst->to : other.to;
+ ++n;
+ } else break;
+ }
+
+ if (n > 0) {
+ for (int i = 1; i + n < it->nrange; ++i)
+ itrange(it, i) = itrange(it, i+n);
+ if (it->nrange > 2 && it->nrange - n <= 2) {
+ struct range *dyn = it->_rdyn;
+ memcpy(it->_rinl, dyn, (it->nrange - n) * sizeof *dyn);
+ xbfree(dyn);
+ }
+ it->nrange -= n;
+ }
+}
+
+static void
+usereg(struct rega *ra, int reg, struct block *blk, int pos)
+{
+ struct fixinterval *fxit;
+ if (rstest(mctarg->rglob, reg)) return; /* regalloc never allocates globally live regs, so don't need intervals for those */
+ for (struct fixinterval *prev = NULL, *fxit = ra->fixed; fxit; prev = fxit, fxit = fxit->next) {
+ if (fxit->range.from > pos) break;
+ if (fxit->rs == BIT(reg) && fxit->range.from <= pos && pos < fxit->range.to) {
+ /* contained by existing interval */
+ fxit->range.from = blk->inumstart;
+ /* insert at head */
+ //DBG(">>>extend REG %s range %d-%d\n", mctarg->rnames[reg], fxit->range.from, fxit->range.to);
+ if (prev) {
+ prev->next = fxit->next;
+ fxit->next = ra->fixed;
+ ra->fixed = fxit;
+ }
+ return;
+ }
+ }
+ fxit = alloc(ra->arena, sizeof *fxit, 0);
+ fxit->next = ra->fixed;
+ fxit->range = (struct range) {blk->inumstart, pos};
+ fxit->rs = BIT(reg);
+ ra->fixed = fxit;
+}
+
+static bool
+defreg(struct rega *ra, int reg, int pos) {
+ if (rstest(mctarg->rglob, reg)) return 1;
+ for (struct fixinterval *prev = NULL, *fxit = ra->fixed; fxit; prev = fxit, fxit = fxit->next) {
+ if (fxit->rs == BIT(reg)) {
+ if (fxit->range.from <= pos) {
+ fxit->range.from = pos;
+ struct fixinterval **at = &ra->fixed;
+ if ((*at)->range.from > pos) {
+ /* keep sorted */
+ //DBG("moved %s\n", mctarg->rnames[reg]);
+ if (prev) prev->next = fxit->next;
+ while ((*at)->range.from < pos) at = &(*at)->next;
+ fxit->next = *at;
+ *at = fxit;
+ }
+ //DBG(">>>def REG %s range %d-%d\n", mctarg->rnames[reg], fxit->range.from, fxit->range.to);
+ return 1;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+/* lifetime interval construction */
+static void
+buildintervals(struct rega *ra)
+{
+ extern int ninstr;
+ struct block *blk, *last;
+ struct bitset **livein = alloc(ra->arena, ra->fn->nblk * sizeof *livein, 0);
+ size_t bssize = BSSIZE(ninstr);
+ struct loops { /* list of loops */
+ struct loops *next;
+ struct block *hdr, *end;
+ } *loops = NULL;
+ for (int i = 0; i < ra->fn->nblk; ++i)
+ livein[i] = allocz(ra->arena, bssize * sizeof *livein[i], 0);
+ ra->inter = allocz(ra->arena, ninstr * sizeof *ra->inter, 0);
+ ra->ninter = ninstr;
+
+ uint n = numberinstrs(ra->fn);
+ assert((ushort)n == n && "too many instrs for struct range");
+ /* visit blocks in reverse, to build lifetime intervals */
+ blk = last = ra->fn->entry->lprev;
+ do {
+ struct bitset *live = livein[blk->id];
+ /* live = union of successor.liveIn for each successor of b */
+ if (blk->s1) bsunion(live, livein[blk->s1->id], bssize);
+ if (blk->s2) bsunion(live, livein[blk->s2->id], bssize);
+
+ /* for each phi function phi of successors of b do
+ * live.add(phi.inputOf(b))
+ */
+ for (struct block *suc = blk->s1; suc; suc = blk->s2) {
+ int predno;
+ for (predno = 0; blkpred(suc, predno) != blk; ++predno) ;
+ for (int i = 0; i < suc->phi.n; ++i) {
+ struct instr *phi = &instrtab[suc->phi.p[i]];
+ union ref *arg = &phitab.p[phi->l.i][predno];
+ assert(arg->t == RTMP);
+ bsset(live, arg->i);
+ incrcost(&ra->inter[arg->i], blk);
+ }
+ if (suc == blk->s2) break;
+ }
+
+ /* for each opd in live do
+ * intervals[opd].addRange(b.from, b.to)
+ */
+ for (uint i = 0; bsiter(&i, live, bssize); ++i) {
+ addrange(ra, i, mkrange(blk->inumstart, blk->inumstart + blk->ins.n + 2), -1);
+ }
+
+ /* for each operation op of b in reverse order do */
+ struct instr *ins = NULL;
+ union ref queue[8] = { blk->jmp.arg[0], blk->jmp.arg[1] };
+ goto Branchopd;
+ for (int curi, pos ; curi >= 0; --curi) {
+ int out = blk->ins.p[curi], reghint;
+ ins = &instrtab[out];
+ pos = blk->inumstart + 1 + curi;
+ /* for each output operand opd of op do
+ * intervals[opd].setFrom(op.id)
+ * live.remove(opd)
+ */
+ reghint = ins && ins->op == Ocopy && ins->l.t == RREG ? ins->l.i : -1;
+ if (!intervaldef(ra, out, blk, pos, reghint)) {
+ if (insrescls(*ins) && ins->op != Omove && !ins->keep && !(ins->op == Ocopy && ins->l.t == RREG)) {
+ /* dead */
+ *ins = mkinstr(Onop,0,);
+ }
+ }
+ bsclr(live, out);
+
+ /* gather fixed intervals */
+ if (ins->op == Omove) {
+ assert(ins->l.t == RREG);
+ if (ins->l.bits == ins->r.bits) {/* special case `move Rx,Rx`: clobber reg, not a real use */
+ usereg(ra, ins->l.i, blk, pos);
+ assert(defreg(ra, ins->l.i, pos));
+ } else if (!defreg(ra, ins->l.i, pos)) {
+ if (ins->keep) { /* clobber here */
+ usereg(ra, ins->l.i, blk, pos);
+ assert(defreg(ra, ins->l.i, pos));
+ } else {
+ /* dead register use. for example if
+ * move RCX, %1
+ * %2 = shl 1, RCX
+ * and %2 is dead, the move to RCX can be killed */
+ *ins = mkinstr(Onop,0,);
+ }
+ } else {
+ rsset(&ra->fn->regusage, ins->l.i);
+ }
+ if (ins->l.bits == ins->r.bits)
+ continue;
+ } else if (ins->op == Ocall) {
+ struct call *call = &calltab.p[ins->r.i];
+ regset rclob = (gpregset | fpregset) &~ (mctarg->rglob | mctarg->rcallee);
+ ra->fn->isleaf = 0;
+
+ for (int i = 0; i < 2; ++i) {
+ if (call->abiret[i].ty.bits) {
+ int reg = call->abiret[i].reg;
+ rsclr(&rclob, reg);
+ defreg(ra, reg, pos);
+ }
+ }
+ if (rclob) {
+ struct fixinterval *fxit = alloc(ra->arena, sizeof *fxit, 0);
+ fxit->next = ra->fixed;
+ fxit->range = mkrange(pos, pos);
+ fxit->rs = rclob;
+ ra->fixed = fxit;
+ }
+ for (int j = call->narg - 1; j >= 0; --j) {
+ struct abiarg abi = call->abiarg[j];
+ if (!abi.isstk) {
+ usereg(ra, abi.reg, blk, pos);
+ }
+ }
+ }
+
+ /* for each input operand opd of op do
+ * intervals[opd].addRange(b.from, op.id)
+ * live.add(opd)
+ */
+ reghint = (ins && ins->op == Omove && ins->l.t == RREG) ? ins->l.i : -1;
+ queue[0] = ins->r, queue[1] = ins->l;
+ if (0) {
+ Branchopd:
+ reghint = -1;
+ curi = blk->ins.n;
+ pos = blk->inumstart + blk->ins.n + 1;
+ }
+ for (int nqueue = ins && ins->op == Omove ? 1 : 2; nqueue > 0;) {
+ union ref r = queue[--nqueue];
+
+ /* do not allocate a reg for a cmp op used as branch argument, since it's a pseudo op */
+ if (curi == blk->ins.n && blk->jmp.t == Jb && r.t == RTMP && instrtab[r.i].keep)
+ continue;
+
+ if (r.t == RTMP) {
+ assert(instrtab[r.i].op != Onop);
+ incrcost(&ra->inter[r.i], blk);
+ addrange(ra, r.i, mkrange(blk->inumstart, pos), reghint);
+ bsset(live, r.i);
+ } else if (r.t == RREG) {
+ usereg(ra, r.i, blk, pos);
+ } else if (r.t == RADDR) {
+ reghint = -1;
+ queue[nqueue++] = addrtab.p[r.i].base;
+ queue[nqueue++] = addrtab.p[r.i].index;
+ }
+ }
+ }
+
+ /* for each phi function phi of b do
+ * live.remove(phi.output)
+ */
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int phi = blk->phi.p[i];
+ bsclr(live, phi);
+ for (int i = 0; i < blk->npred; ++i)
+ incrcost(&ra->inter[phi], blkpred(blk, i));
+ }
+
+ /* if b is loop header then
+ * loopEnd = last block of the loop starting at b
+ * for each opd in live do
+ * intervals[opd].addRange(b.from, loopEnd.to)
+ */
+ struct block *loopend = NULL;
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *pred = blkpred(blk, i);
+ if (pred->id > blk->id)
+ loopend = loopend && loopend->id > pred->id ? loopend : pred;
+ }
+ if (loopend) {
+ if (loops) DBG("@lp @%d\n", blk->id);
+ for (struct loops *l = loops; l; l = l->next) {
+ /* a nested loop might end later than loopend, which lengthens this outer loop. */
+ /* XXX is this correct? more loop analysis might be required? */
+ if (l->hdr->id > loopend->id) break;
+ DBG(" check <@%d-@%d>\n", l->hdr->id, l->end->id);
+ if (l->hdr->id > blk->id && l->hdr->id < loopend->id && l->end->id > loopend->id)
+ loopend = l->end;
+ }
+ DBG("loop header @%d (to @%d)\n", blk->id, loopend->id);
+ /* append to loop list */
+ loops = alloccopy(ra->arena, &(struct loops){loops, blk, loopend}, sizeof *loops, 0);
+ for (uint opd = 0; bsiter(&opd, live, bssize); ++opd) {
+ // DBG(" i have live %%%d\n", opd);
+ addrange(ra, opd, mkrange(blk->inumstart, loopend->inumstart + loopend->ins.n+1), -1);
+ }
+ }
+ } while ((blk = blk->lprev) != last);
+
+ if (ccopt.dbg.r) {
+ for (int var = 0; var < ninstr; ++var) {
+ struct interval *it = &ra->inter[var];
+ if (!it->nrange) continue;
+ DBG("lifetime of %%%d: ", var);
+ for (int i = 0; i < it->nrange; ++i) {
+ struct range r = itrange(it, i);
+ DBG("[%d,%d)%s", r.from, r.to, i < it->nrange-1 ? ", " : "");
+ }
+ DBG(" spill cost: %d\n", it->cost);
+ }
+ for (struct fixinterval *fx = ra->fixed; fx; fx = fx->next) {
+ DBG("fixed {");
+ for (int r = 0, f=1; rsiter(&r, fx->rs); ++r, f=0)
+ DBG(&" %s"[f], mctarg->rnames[r]);
+ DBG("}: [%d,%d)\n", fx->range.from, fx->range.to);
+ }
+ }
+}
+
+static bool
+itcontainspos(struct interval *it, int pos)
+{
+ for (int i = 0; i < it->nrange; ++i) {
+ struct range r = itrange(it, i);
+ if (r.from > pos) return 0;
+ if (pos < r.to) return 1;
+ }
+ return 0;
+}
+
+/* quicksort */
+static void
+sortintervals(struct interval **xs, int lo, int hi)
+{
+ assert(lo >= 0 && hi >= 0);
+ while (lo < hi) {
+ /* partition */
+ int i = lo - 1, p = hi + 1,
+ pivot = intervalbeg(xs[lo]);
+ for (;;) {
+ struct interval *tmp;
+ do ++i; while (intervalbeg(xs[i]) < pivot);
+ do --p; while (intervalbeg(xs[p]) > pivot);
+ if (i >= p) break;
+ /* swap */
+ tmp = xs[i];
+ xs[i] = xs[p];
+ xs[p] = tmp;
+ }
+ /* recur */
+ if (p + 1 >= hi) {
+ hi = p;
+ } else {
+ if (lo < p)
+ sortintervals(xs, lo, p);
+ lo = p + 1;
+ }
+ }
+}
+
+static union alloc
+allocstk(struct rega *ra)
+{
+ uint s = 0;
+ if (bsiter(&s, ra->freestk, BSSIZE(MAXSPILL))) {
+ bsclr(ra->freestk, s);
+ if (ra->stktop < s) ra->stktop = s+1;
+ } else {
+ s = ra->stktop++;
+ }
+ if (ra->maxstk < s+1) ra->maxstk = s+1;
+ return astack(s);
+}
+
+static void
+freestk(struct rega *ra, int slot)
+{
+ DBG("FREE stk %d\n",slot);
+ if (slot < MAXSPILL)
+ bsset(ra->freestk, slot);
+ else if (slot == ra->stktop - 1)
+ --ra->stktop;
+}
+
+#define interval2temp(it) (int)(it - ra->inter)
+
+static void
+linearscan(struct rega *ra)
+{
+ if (!ra->intercount) return;
+
+ /* sort intervals */
+ struct interval **unhandled = alloc(ra->arena, sizeof *unhandled * ra->intercount, 0);
+ int nunhandled = 0;
+ for (int i = 0; i < ra->ninter; ++i) {
+ if (!ra->inter[i].nrange) continue;
+ unhandled[nunhandled++] = &ra->inter[i];
+ }
+ assert(nunhandled == ra->intercount);
+ sortintervals(unhandled, 0, nunhandled-1);
+
+ regset freeregs = (gpregset | fpregset) &~ (mctarg->rglob | (1ull<<mctarg->gprscratch) | (1ull<<mctarg->fprscratch));
+ memset(ra->freestk, 0xFF, sizeof ra->freestk);
+
+ /* LINEAR SCAN */
+ struct interval *actives[2] = {0}, /* gpr set and fpr set */
+ *inactives[2] = {0},
+ *spilled = NULL, **spilled_tail = &spilled;
+ for (struct interval **pcurrent = unhandled; nunhandled > 0; ++pcurrent, --nunhandled) {
+ struct interval *current = *pcurrent;
+ int pos = intervalbeg(current);
+
+ struct interval **active = &actives[current->fpr], **inactive = &inactives[current->fpr],
+ **lnk, *it, *next;
+ /* Expire old intervals */
+ /* check for intervals in active that are handled or inactive */
+ for (lnk = active, it = *lnk; (next = it?it->next:0), it; it = next) {
+ assert(it->alloc.t == AREG);
+ /* ends before position? */
+ if (intervalend(it) <= pos) {
+ /* move from active to handled */
+ *lnk = next;
+ //DBG(" unblock %s %X\n", mctarg->rnames[it->alloc.a], ra->free);
+ rsset(&freeregs, it->alloc.a);
+ } else if (!itcontainspos(it, pos)) { /* it does not cover position? */
+ /* move from active to inactive */
+ *lnk = next;
+ it->next = *inactive;
+ *inactive = it;
+ rsset(&freeregs, it->alloc.a);
+ DBG(" >> %%%zd unblock %s\n", interval2temp(it), mctarg->rnames[it->alloc.a]);
+ } else lnk = &it->next;
+ }
+ /* check for intervals in inactive that are handled or active */
+ for (lnk = inactive, it = *lnk; (next = it?it->next:0), it; it = next) {
+ assert(it->alloc.t == AREG);
+ /* ends before position? */
+ if (intervalend(it) <= pos) {
+ /* move from inactive to handled */
+ *lnk = next;
+ } else if (itcontainspos(it, pos)) { /* it covers position? */
+ /* move from inactive to active */
+ *lnk = next;
+ it->next = *active;
+ *active = it;
+ assert(it->alloc.t == AREG);
+ assert(rstest(freeregs, it->alloc.a));
+ rsclr(&freeregs, it->alloc.a);
+ DBG(" << %%%zd reblock %s\n", interval2temp(it), mctarg->rnames[it->alloc.a]);
+ } else lnk = &it->next;
+ }
+
+ /** find a register for current **/
+
+ int this = interval2temp(current);
+ regset avail = freeregs & (current->fpr ? fpregset : gpregset),
+ fixexcl = 0, excl = 0;
+ struct instr *ins = &instrtab[this];
+ int reg = 0;
+
+ /* exclude regs from overlapping fixed intervals */
+ int end = intervalend(current);
+ for (struct fixinterval *last = NULL, *fxit = ra->fixed; fxit;
+ last = fxit, fxit = fxit->next) {
+ if (last) assert(last->range.from <= fxit->range.from && "unsorted fixintervals");
+ if (fxit->range.to <= pos) {
+ ra->fixed = fxit->next;
+ continue;
+ } else if (fxit->range.from >= end) {
+ break;
+ }
+
+ for (int i = 0; i < current->nrange; ++i) {
+ if (rangeoverlap(fxit->range, itrange(current, i))) {
+ fixexcl |= fxit->rs;
+ }
+ }
+ }
+ /* exclude regs from overlapping inactive intervals */
+ for (struct interval *it = *inactive; it; it = it->next) {
+ if (it->alloc.t == AREG && intersoverlap(it, current)) {
+ rsset(&excl, it->alloc.a);
+ }
+ }
+ /* for 2-address instrs, exclude reg from 2nd arg (unless arg#1 == arg#2) */
+ if (ins->inplace && opnarg[ins->op] == 2) {
+ int xreg;
+ if (ins->r.t == RREG) rsset(&excl, ins->r.i);
+ else if (ins->r.t == RTMP && (xreg = instrtab[ins->r.i].reg)) {
+ if (ins->r.bits != ins->l.bits)
+ rsset(&fixexcl, xreg-1);
+ }
+ }
+ excl |= fixexcl;
+ avail &= ~excl;
+
+ if (!avail) { /* no regs left, must spill */
+ struct interval **ptospill = NULL, *tospill = current;
+ /* heuristic: look for longest-lived active interval with lower spill cost */
+ int curend = intervalend(current);
+ for (lnk = active, it = *lnk; (next = it ? it->next : 0), it; it = next) {
+ int end = intervalend(it);
+ if (it->cost < tospill->cost && end > curend && !rstest(fixexcl, it->alloc.a)) {
+ ptospill = lnk;
+ tospill = it;
+ reg = tospill->alloc.a;
+ }
+ lnk = &it->next;
+ }
+
+ /* insert in spilled, keep sorted */
+ if (ptospill) {
+ *ptospill = tospill->next; /* remove from active */
+ int from = intervalbeg(tospill);
+ lnk = &spilled;
+ /* XXX potentially slow linear search */
+ while (*lnk && intervalbeg(*lnk) < from)
+ lnk = &(*lnk)->next;
+ tospill->next = *lnk;
+ *lnk = tospill;
+ } else { /* tospill == current, so we can just append and keep it sorted */
+ *spilled_tail = tospill;
+ tospill->next = NULL;
+ }
+ if (!tospill->next) /* update spilled list tail */
+ spilled_tail = &tospill->next;
+
+ assert(spilled != NULL);
+ if (tospill == current) {
+ DBG("spilled %%%d\n", this);
+ continue;
+ } else {
+ instrtab[interval2temp(tospill)].reg = 0;
+ DBG("%%%d takes %s from %%%d (spilled)\n", this, mctarg->rnames[reg],
+ interval2temp(tospill));
+ goto GotReg;
+ }
+ }
+
+ /* have free regs, try to use hint */
+ if (current->rhint >= 0)
+ DBG("have hint %s for %%%zd\n",
+ mctarg->rnames[current->rhint], interval2temp(current));
+ if (current->rhint >= 0 && rstest(avail, current->rhint)) {
+ DBG(" (used hint)\n");
+ reg = current->rhint;
+ goto GotReg;
+ } else {
+ /* for two-address instructions, try to use the reg of left arg */
+ if (ins->op != Ophi && (opnarg[ins->op] == 1 || (opnarg[ins->op] == 2 && ins->inplace))) {
+ DBG(" %%%d try %d,%d\n", this, ins->l.t,ins->l.i);
+ if (ins->l.t == RREG && rstest(avail, reg = ins->l.i))
+ goto GotReg;
+ if (ins->l.t == RTMP)
+ if ((reg = instrtab[ins->l.i].reg-1) >= 0)
+ if (rstest(avail, reg))
+ goto GotReg;
+ /* for phi, try to use reg of any arg */
+ } else if (ins->op == Ophi) {
+ union ref *arg = phitab.p[ins->l.i];
+ for (int i = 0; i < xbcap(arg); ++i) {
+ if (arg->t == RREG && rstest(avail, reg = arg->i)) goto GotReg;
+ if (arg->t == RTMP)
+ if ((reg = instrtab[arg->i].reg-1) >= 0)
+ if (rstest(avail, reg))
+ goto GotReg;
+ }
+ }
+
+ /* no hints to use */
+ if (avail &~ mctarg->rcallee) /* prefer caller-saved regs */
+ avail &=~ mctarg->rcallee;
+ /* and pick first available reg */
+ reg = lowestsetbit(avail);
+ }
+ GotReg:
+ current->alloc = areg(reg);
+ ins->reg = reg + 1;
+ DBG("%%%d got %s\n", this, mctarg->rnames[reg]);
+ rsclr(&freeregs, reg);
+ rsset(&ra->fn->regusage, reg);
+
+ /* add current to active */
+ current->next = *active;
+ *active = current;
+ }
+
+ if (ccopt.dbg.r) {
+ DBG("regusage: ");
+ for (int r = 0; r < MAXREGS; ++r) {
+ if (rstest(ra->fn->regusage, r)) DBG(" %s", mctarg->rnames[r]);
+ }
+ DBG("\n");
+ }
+ /* allocate stack slots for spilled intervals
+ * this is like another (simplified) linear scan pass */
+ struct interval *active = NULL;
+ int prevpos = -1;
+ if (spilled) DBG("spilled:\n");
+ for (struct interval *current = spilled, *next; current; current = next) {
+ int pos = intervalbeg(current);
+ DBG(" %%%zd: [%d,%d)\n", interval2temp(current), pos, intervalend(current));
+ assert(pos >= prevpos && "unsorted spilled?");
+ prevpos = pos;
+ /* Expire old intervals */
+ struct interval **lnk, *it, *lnext;
+ for (lnk = &active, it = *lnk; (lnext = it ? it->next : 0), it; it = lnext) {
+ /* ends before position? */
+ if (intervalend(it) <= pos) {
+ /* move from active to handled */
+ *lnk = lnext;
+ freestk(ra, it->alloc.a);
+ } else lnk = &it->next;
+ }
+ /* allocate a stack slot for current and move to active */
+ current->alloc = allocstk(ra);
+ DBG(" got stk%d\n", current->alloc.a);
+ next = current->next;
+ current->next = active;
+ active = current;
+ }
+}
+
+static bool
+isstoreimm(union ref r)
+{
+ if (r.t == RTMP) return 1; /* register OK */
+ if (isintcon(r)) switch (target.arch) {
+ case ISxxx: assert(0);
+ /* TODO don't hard code this architecture dependent dispatch */
+ case ISx86_64: return concls(r) == KI32; /* x86: MOV [addr], imm32 */
+ case ISaarch64: return r.i == 0; /* arm doesn't have STR <imm>, but has zero register */
+ }
+ return 0;
+}
+
+/* replace temps with physical regs, add loads & stores for spilled temps */
+static bool
+devirt(struct rega *ra, struct block *blk)
+{
+ bool allnops = 1;
+ struct function *fn = ra->fn;
+ union alloc spillsave[4] = {0};
+ memset(ra->freestk, 0, BSSIZE(MAXSPILL) * sizeof *ra->freestk);
+
+ for (int curi = 0; curi < blk->ins.n; ++curi) {
+ int temp = blk->ins.p[curi];
+ struct instr *ins = &instrtab[temp];
+ struct interval *it;
+ union alloc *alloc;
+ struct addr newaddr;
+ union ref *argref[4];
+ int curi0;
+ int naddr = 0;
+ int nargref = 0;
+ int nspill = 0;
+
+ /** devirtualize operands **/
+ for (int i = 0; i < 2; ++i) {
+ union ref *r = &i[&ins->l];
+ if (r->t == RADDR) {
+ struct addr *a = &addrtab.p[r->i];
+ ++naddr;
+ newaddr = *a;
+ argref[nargref++] = &newaddr.base;
+ argref[nargref++] = &newaddr.index;
+ } else {
+ argref[nargref++] = r;
+ }
+ }
+ for (int i = 0; i < nargref; ++i) {
+ union ref *r = argref[i];
+ int tr;
+ if (r->t == RTMP && (it = &ra->inter[r->i])->nrange > 0) {
+ alloc = &it->alloc;
+ if (alloc->t == ASTACK && ins->op == Omove && kisint(ins->cls) == kisint(instrtab[r->i].cls)) {
+ /* move [reg], [stk] -> [reg] = load [stk] */
+ assert(r == &ins->r && ins->l.t == RREG);
+ ins->reg = ins->l.i+1;
+ ins->op = cls2load[instrtab[r->i].cls];
+ ins->l = stkslotref(fn, alloc->a*8);
+ ins->r = NOREF;
+ } else if (alloc->t == ASTACK && ins->op == Ocopy && r == &ins->l && ins->reg && kisint(ins->cls) == kisint(instrtab[r->i].cls)) {
+ /* [reg] = copy [stk] -> [reg] = load [stk] */
+ ins->op = cls2load[instrtab[r->i].cls];
+ ins->l = stkslotref(fn, alloc->a*8);
+ } else if (alloc->t == ASTACK) {
+ /* ref was spilled, gen load to scratch register and use it */
+ struct instr ld = {.cls = insrescls(instrtab[r->i])};
+ int reg = kisint(ld.cls) ? mctarg->gprscratch : mctarg->fprscratch;
+ bool dosave = 0;
+ /* pick scratch register, or any register that doesn't conflict with this instr's srcs/dst */
+ if (nspill > 0) {
+ regset avail = (kisflt(ld.cls) ? fpregset : gpregset) &~ mctarg->rglob;
+ if (ins->reg) rsclr(&avail, ins->reg-1);
+ for (int j = 0; j < nargref; ++j) {
+ struct interval *it;
+ if (argref[j]->t == RREG) rsclr(&avail, argref[j]->i);
+ else if (argref[j]->t == RTMP) {
+ it = &ra->inter[argref[j]->i];
+ if (it->alloc.t == AREG) rsclr(&avail, it->alloc.a);
+ }
+ }
+ assert(avail != 0);
+ if (avail &~ (fn->regusage | mctarg->rcallee)) avail &= ~(fn->regusage | mctarg->rcallee);
+ reg = lowestsetbit(avail);
+ /* if not the designated scratch register, we need to save+restore */
+ if (rstest(fn->regusage, reg) || rstest(mctarg->rcallee, reg)) {
+ dosave = 1;
+ if (!spillsave[nspill-1].t) spillsave[nspill-1] = allocstk(ra);
+ emitmove(fn, isgpr(reg) ? KPTR : KF64, spillsave[nspill-1], areg(reg), blk, curi++);
+ }
+ }
+ ld.reg = reg+1;
+ ld.op = cls2load[ld.cls];
+ ld.l = stkslotref(fn, alloc->a*8);
+ insertinstr(blk, curi++, ld);
+ *r = mkref(RREG, reg);
+ if (nspill > 0 && dosave) {
+ emitmove(fn, isgpr(reg) ? KPTR : KF64, areg(reg), spillsave[nspill-1], blk, curi+1);
+ }
+ ++nspill;
+ } else if ((tr = instrtab[r->i].reg)) {
+ assert(alloc && alloc->t == AREG && alloc->a == tr-1);
+ *r = mkref(RREG, tr-1);
+ }
+ }
+ }
+ if (nspill > 1) assert(ins->op != Ocall);
+ if (naddr) {
+ union ref *r = ins->l.t == RADDR ? &ins->l : &ins->r;
+ *r = mkaddr(newaddr);
+ }
+
+ /* devirtualize destination */
+ curi0 = curi;
+ alloc = temp < ra->ninter && (it = &ra->inter[temp]) && it->nrange ? &it->alloc : NULL;
+ if (alloc && alloc->t == ASTACK) {
+ enum irclass cls = insrescls(*ins);
+ int store = cls2store[cls];
+ /* t was spilled, gen store */
+ if (ins->op == Ocopy && (ins->l.t == RREG || isstoreimm(ins->l))) {
+ ins->op = store;
+ ins->r = ins->l;
+ ins->l = stkslotref(fn, alloc->a*8);
+ } else {
+ bool dosave = 0;
+ int reg = kisint(insrescls(*ins)) ? mctarg->gprscratch : mctarg->fprscratch;
+ if (nspill > 0) {
+ regset avail = (kisflt(cls) ? fpregset : gpregset) &~ mctarg->rglob;
+ for (int j = 0; j < nargref; ++j) {
+ if (argref[j]->t == RREG) rsclr(&avail, argref[j]->i);
+ }
+ assert(avail != 0);
+ if (avail &~ (fn->regusage | mctarg->rcallee)) avail &= ~(fn->regusage | mctarg->rcallee);
+ /* if not the designated scratch register, we need to save+restore */
+ reg = lowestsetbit(avail);
+ if (rstest(fn->regusage, reg) || rstest(mctarg->rcallee, reg)) {
+ dosave = 1;
+ if (!spillsave[nspill-1].t) spillsave[nspill-1] = allocstk(ra);
+ emitmove(fn, isgpr(reg) ? KPTR : KF64, spillsave[nspill-1], areg(reg), blk, curi++);
+ curi0 = curi;
+ }
+ }
+ ins->reg = reg+1;
+ insertinstr(blk, ++curi,
+ mkinstr(store, 0, stkslotref(fn, alloc->a*8), mkref(RREG, reg)));
+ if (nspill > 0 && dosave) {
+ emitmove(fn, isgpr(reg) ? KPTR : KF64, areg(reg),
+ spillsave[nspill-1], blk, ++curi);
+ }
+ }
+ }
+ if (!ins->reg && insrescls(*ins) && ins->op != Omove && !ins->keep && !oisstore(ins->op)) {
+ /* dead */
+ Nop:
+ ins->op = Onop;
+ } else if (ins->op == Omove && ins->r.t == RREG && ins->l.i == ins->r.i) {
+ /* move r1,r2 / r1=r2 */
+ goto Nop;
+ } else if (ins->op == Ocopy && ins->l.t == RREG && ins->reg-1 == ins->l.i) {
+ /* r1 = copy r2 / r1=r2 */
+ goto Nop;
+ } else if (ins->op != Onop) {
+ allnops = 0;
+ }
+ if (ins->inplace && ins->l.t == RREG && ins->reg && ins->reg-1 != ins->l.i) {
+ /* fixup in-place (two-address) instructions */
+ allnops = 0;
+ insertinstr(blk, curi0, mkmove(ins->cls, ins->reg-1, ins->l.i));
+ ++curi;
+ ins->l.i = ins->reg-1;
+ }
+ if (!ins->reg && in_range(ins->op, Oloads8, Oloadf64)) {
+ assert(ins->keep);
+ ins->reg = kisint(ins->cls) ? mctarg->gprscratch+1 : mctarg->fprscratch+1;
+ }
+ }
+
+ if (allnops) vfree(&blk->ins);
+ return allnops;
+}
+
+static void
+fini(struct rega *ra)
+{
+ int id = 0;
+ struct function *fn = ra->fn;
+ struct block *blk = fn->entry;
+
+ do {
+ blk->id = id++;
+ bool allnops = devirt(ra, blk);
+ if (allnops && !blk->s2 && blk->npred > 0) { /* remove no-op blocks */
+ bool delet = 1;
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *p = blkpred(blk, i);
+ if (p == blk || (p->s2 && !blk->s1))
+ delet = 0;
+ }
+ for (int i = 0; i < blk->npred; ++i) {
+ struct block *p = blkpred(blk, i);
+ if (!p->s2 && !blk->s1) {
+ /* simplify:
+ *
+ * @p:
+ * ...
+ * b @blk
+ * @blk:
+ * NOP
+ * ret/trap
+ */
+ assert(p->s1 == blk);
+ p->jmp.t = blk->jmp.t;
+ p->s1 = NULL;
+ } else if (blk->s1 && blk->s1 != blk) {
+ /* simplify:
+ *
+ * @p:
+ * ...
+ * b %x, @blk, @other
+ * @blk:
+ * NOP
+ * b @next
+ */
+ struct block *next = blk->s1;
+ if (p->s1 == blk) p->s1 = next;
+ else if (p->s2 == blk) p->s2 = next;
+ else continue;
+ for (int i = 0; i < next->npred; ++i) {
+ if (blkpred(next, i) == blk) {
+ blkpred(next, i) = p;
+ goto NextPred;
+ }
+ }
+ addpred(next, p);
+ }
+ NextPred:;
+ }
+ if (delet) {
+ freeblk(fn, blk);
+ --id;
+ }
+ } else if (allnops) {
+ vfree(&blk->ins);
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+}
+
+void
+regalloc(struct function *fn)
+{
+ struct rega ra = {fn, .arena = fn->passarena};
+ struct block *blk, *last;
+
+ /* setup */
+ if (!fpregset || !gpregset) {
+ for (int r = 0; r < MAXREGS; ++r) {
+ if (isfpr(r))
+ rsset(&fpregset, r);
+ else if (isgpr(r))
+ rsset(&gpregset, r);
+ }
+ }
+ fn->regusage = 0;
+ fn->stksiz = alignup(fn->stksiz, 8);
+ fn->isleaf = 1;
+
+ /* put into reverse post order */
+ sortrpo(fn);
+
+ /* fix liveness ranges */
+ fixlive(fn);
+
+ /* transform into CSSA */
+ fixcssa(fn);
+
+ fillblkids(fn);
+ fillloop(fn);
+
+ if (ccopt.dbg.r) {
+ bfmt(ccopt.dbgout, "<< Before linear scan >>\n");
+ irdump(fn);
+ }
+
+ /* linear scan: build lifetime intervals */
+ buildintervals(&ra);
+
+ /* linear scan: assign physical registers and stack slots */
+ linearscan(&ra);
+
+ /* get out of SSA */
+ blk = last = fn->entry->lprev;
+ do {
+ if (blk->id < 0) continue;
+ for (int i = 0; i < blk->npred; ++i) {
+ lowerphis(&ra, blkpred(blk, i), blk);
+ }
+ vfree(&blk->phi);
+ } while ((blk = blk->lprev) != last);
+
+ /* devirtualize & final cleanup */
+ fini(&ra);
+ fn->stksiz += ra.maxstk*8;
+ if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name);
+
+ for (struct interval *it = ra.inter; ra.intercount > 0; ++it) {
+ if (it->nrange > 2) xbfree(it->_rdyn);
+ if (it->nrange > 0) --ra.intercount;
+ }
+
+ if (ccopt.dbg.r) {
+ bfmt(ccopt.dbgout, "<< After regalloc >>\n");
+ irdump(fn);
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_simpl.c b/src/ir_simpl.c
new file mode 100644
index 0000000..a01879a
--- /dev/null
+++ b/src/ir_simpl.c
@@ -0,0 +1,308 @@
+#include "ir.h"
+
+static int
+mulk(struct instr *ins, struct block *blk, int *curi)
+{
+ vlong iv = intconval(ins->r);
+ enum irclass cls = ins->cls;
+ assert((uvlong)iv > 1 && "trivial mul not handled by irbinop() ?");
+ bool neg = iv < 0;
+ if (neg) iv = -iv;
+ /* This can be generalized to any sequence of shifts and
+ * adds/subtracts, but whether that's worth it depends on the number of them
+ * and the microarchitecture.. clang seems to stop after two shifts. Should
+ * we compute approximate cost of instrs to determine? For now just handle
+ * the po2 (+/- 1) case */
+ if (ispo2(iv)) {
+ /* x * 2^y ==> x << y */
+ ins->op = Oshl;
+ ins->r = mkref(RICON, ilog2(iv));
+ } else if (ispo2(iv-1)) {
+ /* x * 5 ==> (x << 2) + x */
+ ins->op = Oadd;
+ ins->r = ins->l;
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(Oshl, cls, ins->l, mkref(RICON, ilog2(iv-1))));
+ } else if (ispo2(iv+1)) {
+ /* x * 7 ==> (x << 3) - x */
+ ins->op = Osub;
+ ins->r = ins->l;
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(Oshl, cls, ins->l, mkref(RICON, ilog2(iv+1))));
+ } else return 0;
+ if (neg) {
+ ins->l = insertinstr(blk, (*curi)++, *ins);
+ ins->op = Oneg;
+ ins->r = NOREF;
+ }
+ return 1;
+}
+
+static int
+divmodk(struct instr *ins, struct block *blk, int *curi)
+{
+ enum op op = ins->op;
+ enum irclass cls = ins->cls;
+ vlong iv = intconval(ins->r);
+ uint nbit = 8 * cls2siz[cls];
+ bool neg = (op == Odiv || op == Orem) && iv < 0;
+ if (ispo2(iv) || (neg && ispo2(-iv))) { /* simple po2 cases */
+ union ref temp;
+ uint s = ilog2(neg ? -iv : iv);
+ switch (op) {
+ default: assert(0);
+ case Oudiv: /* x / 2^s ==> x >> s */
+ ins->op = Oslr, ins->r = mkref(RICON, s);
+ return 1;
+ case Ourem: /* x % 2^s ==> x & 2^s-1 */
+ ins->op = Oand, ins->r = mkintcon(cls, iv - 1);
+ return 1;
+ case Odiv: case Orem:
+ /* have to adjust to round negatives toward zero */
+ /* x' = (((x < 0 ? -1 : 0) >>> (Nbit - s)) + x) */
+ temp = insertinstr(blk, (*curi)++, mkinstr(Osar, cls, ins->l, mkref(RICON, nbit - 1)));
+ temp = insertinstr(blk, (*curi)++, mkinstr(Oslr, cls, temp, mkref(RICON, nbit - s)));
+ temp = insertinstr(blk, (*curi)++, mkinstr(Oadd, cls, ins->l, temp));
+ if (op == Odiv) {
+ /* (-) (x' >> s) */
+ struct instr sar = mkinstr(Osar, cls, temp, mkref(RICON, s));
+ if (!neg) *ins = sar;
+ else {
+ temp = insertinstr(blk, (*curi)++, sar);
+ ins->op = Oneg, ins->l = temp, ins->r = NOREF;
+ }
+ } else {
+ /* x - (x' & -(2^s)) */
+ temp = insertinstr(blk, (*curi)++, mkinstr(Oand, cls, temp, mkintcon(cls, neg ? iv : -iv)));
+ ins->op = Osub, ins->r = temp;
+ }
+ break;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int
+doins(struct instr *ins, struct block *blk, int *curi)
+{
+ int narg = opnarg[ins->op];
+ if (oisarith(ins->op)) {
+ union ref r = narg == 1 ? irunop(NULL, ins->op, ins->cls, ins->l)
+ : irbinop(NULL, ins->op, ins->cls, ins->l, ins->r);
+ if (r.bits) {
+ *ins = mkinstr(Onop,0,);
+ replcuses(mkref(RTMP, ins - instrtab), r);
+ return 1;
+ }
+ }
+ enum irclass k = ins->cls;
+ switch (ins->op) {
+ case Ocopy:
+ if ((ins->l.t == RTMP && k == instrtab[ins->l.i].cls)
+ || (isnumcon(ins->l) && k == concls(ins->l))
+ || (kisint(k) && ins->l.t == RICON)) {
+ union ref it = ins->l;
+ *ins = mkinstr(Onop,0,);
+ replcuses(mkref(RTMP, ins - instrtab), it);
+ return 1;
+ }
+ break;
+ case Omul:
+ if (kisflt(k)) break;
+ if (isnumcon(ins->l)) rswap(ins->l, ins->r); /* put const in rhs */
+ if (isintcon(ins->r)) return mulk(ins, blk, curi);
+ break;
+ case Odiv: case Oudiv: case Orem: case Ourem:
+ if (kisflt(k)) break;
+ if (isintcon(ins->r)) return divmodk(ins, blk, curi);
+ break;
+ case Oequ: case Oneq:
+ if (ins->l.t == RTMP && isintcon(ins->r)) {
+ /* optimize `x <op> C <cmp> Q` */
+ /* could apply with add/sub to lth/lte/gth/gte iff no signed wraparound, which isn't assumed */
+ struct instr *lhs = &instrtab[ins->l.i];
+ enum op o = lhs->op;
+ if (ins->cls == lhs->cls && (o == Oadd || o == Osub || o == Oxor) && isintcon(lhs->r)) {
+ uvlong c = intconval(ins->r), q = intconval(lhs->r);
+ switch (o) { default: assert(0);
+ case Oadd: c -= q; break; /* x + 3 == C ==> x == C - 3 */
+ case Osub: c += q; break; /* x - 3 == C ==> x == C + 3 */
+ case Oxor: c ^= q; break; /* x ^ 3 == C ==> x == C ^ 3 */
+ }
+ ins->l = lhs->l, ins->r = mkintcon(ins->cls, c);
+ deluse(blk, ins - instrtab, mkref(RTMP, lhs - instrtab));
+ if (!instruse[lhs - instrtab]) *lhs = mkinstr(Onop,0,);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void
+jmpfind(struct block **final, struct block **pblk)
+{
+ struct block **p2 = &final[(*pblk)->id];
+ if (*p2 && !(*p2)->phi.n) {
+ jmpfind(final, p2);
+ *pblk = *p2;
+ }
+}
+
+static void
+fillpredsrec(struct block *blk)
+{
+ while (blk && !wasvisited(blk)) {
+ markvisited(blk);
+ if (!blk->s1) return;
+ if (!blk->s1->phi.n) addpred(blk->s1, blk);
+ if (!blk->s2) {
+ blk = blk->s1;
+ continue;
+ }
+ if (!blk->s2->phi.n) addpred(blk->s2, blk);
+ fillpredsrec(blk->s1);
+ blk = blk->s2;
+ }
+}
+
+static void
+fillpreds(struct function *fn)
+{
+ struct block *blk = fn->entry, *next;
+ do {
+ if (blk->phi.n) continue;
+ if (blk->npred > 1)
+ xbfree(blk->_pred);
+ blk->npred = 0;
+ blk->_pred = NULL;
+ } while ((blk = blk->lnext) != fn->entry);
+ startbbvisit();
+ fillpredsrec(fn->entry);
+ blk = fn->entry->lnext;
+ do { /* remove dead blocks */
+ next = blk->lnext;
+ if (!blk->npred) freeblk(fn, blk);
+ } while ((blk = next) != fn->entry);
+}
+
+static void
+mergeblks(struct function *fn, struct block *p, struct block *s)
+{
+ assert(s->npred == 1 && !s->phi.n);
+ vpushn(&p->ins, s->ins.p, s->ins.n);
+ p->jmp = p->s1->jmp;
+ for (int i = 0; i < 2; ++i) {
+ struct block *ss = (&s->s1)[i];
+ if (ss) for (int j = 0; j < ss->npred; ++j) {
+ if (blkpred(ss, j) == s) {
+ blkpred(ss, j) = p;
+ break;
+ }
+ }
+ }
+ /* breaks use for temps used in s */
+ if (s->ins.n || s->jmp.arg[0].t == RTMP || s->jmp.arg[1].t == RTMP)
+ fn->prop &= ~FNUSE;
+ p->s1 = s->s1;
+ p->s2 = s->s2;
+ freeblk(fn, s);
+}
+
+void
+simpl(struct function *fn)
+{
+ FREQUIRE(FNUSE);
+ int inschange = 0,
+ blkchange = 0;
+ struct block **jmpfinal = allocz(fn->passarena, fn->nblk * sizeof *jmpfinal, 0);
+ struct block *blk = fn->entry;
+
+ do {
+ /* merge blocks:
+ * @blk:
+ * ... (1)
+ * b @s
+ * @s:
+ * ... (2)
+ * b {...}
+ *=>
+ * @blk:
+ * ... (1)
+ * ... (2)
+ * b {...}
+ * */
+ if (blk != fn->entry) while (blk->s1 && !blk->s2 && blk->s1->npred == 1 && !blk->phi.n) {
+ mergeblks(fn, blk, blk->s1);
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (!(fn->prop & FNUSE)) filluses(fn);
+
+ do {
+ for (int i = 0; i < blk->phi.n; ++i) {
+ int phi = blk->phi.p[i];
+ /* delete trivial phis */
+ union ref *args = phitab.p[instrtab[phi].l.i],
+ same = *args;
+ if (same.t == RTMP && instrtab[same.i].op == Ophi) goto Next;
+ if (blk->npred > 1) for (int j = 1; j < blk->npred; ++j) {
+ if (args[j].bits != same.bits) goto Next;
+ }
+ if (!(fn->prop & FNUSE)) filluses(fn);
+ replcuses(mkref(RTMP, phi), same);
+ delphi(blk, i);
+ Next:;
+ }
+
+ int curi = 0;
+ DoIns:
+ for (; curi < blk->ins.n; ++curi) {
+ struct instr *ins = &instrtab[blk->ins.p[curi]];
+ if (ins->op != Onop) {
+ if (!(fn->prop & FNUSE)) filluses(fn);
+ inschange += doins(ins, blk, &curi);
+ }
+ }
+
+ if (blk->s2 && isintcon(blk->jmp.arg[0])) {
+ /* simplify known conditional branch */
+ struct block *s = intconval(blk->jmp.arg[0]) ? blk->s1 : blk->s2;
+ delpred(s == blk->s1 ? blk->s2 : blk->s1, blk);
+ blk->s1 = s, blk->s2 = NULL;
+ blk->jmp.arg[0] = NOREF;
+ if (blk->s1 && !blk->s2 && blk->s1->npred == 1 && blk->s1->phi.n == 0) {
+ mergeblks(fn, blk, blk->s1);
+ goto DoIns;
+ }
+ }
+
+ if (blk != fn->entry && blk->npred == 0) {
+ freeblk(fn, blk);
+ } else if (!blk->phi.n && !blk->ins.n) { /* thread jumps.. */
+ if (blk->jmp.t == Jb && !blk->s2) {
+ jmpfind(jmpfinal, &blk->s1);
+ if (blk->s1 != blk) {
+ ++blkchange;
+ jmpfinal[blk->id] = blk->s1;
+ }
+ }
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (blkchange) {
+ do {
+ if (blk->s1) jmpfind(jmpfinal, &blk->s1);
+ if (blk->s2) jmpfind(jmpfinal, &blk->s2);
+ if (blk->s1 && blk->s1 == blk->s2) {
+ memset(blk->jmp.arg, 0, sizeof blk->jmp.arg);
+ blk->s2 = NULL;
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+ fillpreds(fn);
+ sortrpo(fn);
+ }
+ if (!(fn->prop & FNUSE)) filluses(fn);
+ fn->prop &= ~FNDOM;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_ssa.c b/src/ir_ssa.c
new file mode 100644
index 0000000..6598fba
--- /dev/null
+++ b/src/ir_ssa.c
@@ -0,0 +1,46 @@
+#include "ir.h"
+
+void
+copyopt(struct function *fn)
+{
+ struct block *blk = fn->entry;
+
+ FREQUIRE(FNUSE);
+ do {
+ for (int i = 0; i < blk->phi.n; ++i) {
+ /* simplify same-arg phi */
+ int phi = blk->phi.p[i];
+ union ref *arg = phitab.p[instrtab[phi].l.i];
+ for (int j = 1; j < blk->npred; ++j) {
+ if (arg[j].bits != arg->bits) goto Next;
+ }
+ /* being conservative here because phis could have circular dependencies? */
+ if (arg->t != RTMP || instrtab[arg->i].op != Ophi) {
+ replcuses(mkref(RTMP, phi), *arg);
+ delphi(blk, i--);
+ }
+ Next:;
+ }
+ for (int i = 0; i < blk->ins.n; ++i) {
+ union ref var = mkref(RTMP, blk->ins.p[i]);
+ struct instr *ins = &instrtab[var.i];
+ enum irclass k;
+
+ if (ins->op == Ocopy) {
+ union ref arg = ins->l;
+ if (arg.t == RTMP) k = insrescls(instrtab[arg.i]);
+ else if (arg.t == RICON) k = cls2siz[ins->cls] == 4 ? KI32 : KI64;
+ else if (arg.t == RXCON) k = isnumcon(arg) ? concls(arg) : KPTR;
+ else continue;
+ if (ins->cls != k) continue;
+
+ replcuses(var, arg);
+ *ins = mkinstr(Onop,0,);
+ deluses(var.i);
+ }
+ }
+ delnops(blk);
+ } while ((blk = blk->lnext) != fn->entry);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/ir_stack.c b/src/ir_stack.c
new file mode 100644
index 0000000..40a7b1d
--- /dev/null
+++ b/src/ir_stack.c
@@ -0,0 +1,33 @@
+#include "ir.h"
+
+void
+lowerstack(struct function *fn)
+{
+ fn->stksiz = 0;
+ FREQUIRE(FNUSE);
+
+ struct block *blk = fn->entry;
+ do {
+ for (int i = 0; i < blk->ins.n; ++i) {
+ int t = blk->ins.p[i];
+ struct instr *ins = &instrtab[t];
+ if (oisalloca(ins->op)) {
+ uint alignlog2 = ins->op - Oalloca1;
+ assert(ins->l.i > 0);
+ uint siz = ins->l.i << alignlog2;
+ fn->stksiz += siz;
+ fn->stksiz = alignup(fn->stksiz, 1 << alignlog2);
+ if (fn->stksiz > (1<<20)-1) error(NULL, "'%s' stack frame too big", fn->name);
+ *ins = mkinstr(Onop,0,);
+ replcuses(mkref(RTMP, t), mkref(RSTACK, fn->stksiz));
+ }
+ }
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (ccopt.dbg.s) {
+ bfmt(ccopt.dbgout, "<< After stack >>\n");
+ irdump(fn);
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/o_elf.c b/src/o_elf.c
new file mode 100644
index 0000000..5044e56
--- /dev/null
+++ b/src/o_elf.c
@@ -0,0 +1,572 @@
+#include "elf.h"
+#include "obj.h"
+#include "../ir/ir.h" /* mctarg */
+#include "../endian.h"
+#include <unistd.h>
+#include <stdlib.h> /* qsort */
+
+static union {
+ ELF_HDRIDENT;
+ struct elf32hdr h32;
+ struct elf64hdr h64;
+} hdr;
+static vec_of(uchar) strs;
+struct sym {
+ uint name;
+ uchar bind : 4,
+ type : 4;
+ ushort shndx;
+ uvlong value,
+ size;
+};
+static vec_of(struct sym) symtab;
+static pmap_of(ushort) symht;
+static uint ntextrel, nrodatarel, ndatarel;
+struct reloc {
+ uchar section;
+ ushort kind;
+ uint off;
+ vlong addend;
+ union {
+ uint symidx;
+ internstr symname;
+ };
+};
+static vec_of(struct reloc) relocs;
+
+#define O objout
+
+void
+elfinit(void)
+{
+ memcpy(hdr.i_mag, ELFMAG, 4);
+ hdr.i_class = ELFCLASS32 + targ_64bit;
+ hdr.i_data = ELFDATA2LSB + targ_bigendian;
+ hdr.i_version = ELFVERSION;
+ hdr.i_osabi = ELFOSABI_SYSV;
+ hdr.i_abiversion = 0;
+ hdr.h32.type = ET_REL;
+ switch (target.arch) {
+ default: assert(!"arch?");
+ case ISx86_64: hdr.h32.machine = EM_X86_64; break;
+ case ISaarch64: hdr.h32.machine = EM_ARM64; break;
+ }
+ hdr.h32.version = ELFVERSION;
+ if (targ_64bit) {
+ hdr.h64.ehsize = sizeof(struct elf64hdr);
+ hdr.h64.shentsize = sizeof(struct elf64shdr);
+ } else {
+ hdr.h32.ehsize = sizeof(struct elf32hdr);
+ hdr.h32.shentsize = sizeof(struct elf32shdr);
+ }
+ vinit(&strs, NULL, 4<<10);
+ vpush(&symtab, ((struct sym){0}));
+ vpush(&symtab, ((struct sym){ .type = STT_FILE, .shndx = SHN_ABS}));
+}
+
+uint
+str2idx(const char *s)
+{
+ static pmap_of(uint) ht;
+ uint *p, i;
+
+ if (!ht.v) pmap_init(&ht, 1<<8);
+ if ((p = pmap_get(&ht, s))) return *p;
+ if (!strs.n) vpush(&strs, 0);
+ i = strs.n;
+ vpushn(&strs, s, strlen(s)+1);
+ pmap_set(&ht, s, i);
+ return i;
+}
+
+static struct sym *
+findsym(internstr s)
+{
+ ushort *idx = pmap_get(&symht, s);
+ return idx ? &symtab.p[*idx] : NULL;
+}
+
+enum {
+ TEXT_SHNDX = 1,
+ RODATA_SHNDX = 2,
+ DATA_SHNDX = 3,
+ BSS_SHNDX = 4,
+};
+
+static const char sect2ndx[] = {
+ [Snone] = SHN_UND,
+ [Stext] = TEXT_SHNDX, [Srodata] = RODATA_SHNDX,
+ [Sdata] = DATA_SHNDX, [Sbss] = BSS_SHNDX,
+}, shndx2sect[] = {
+ [SHN_UND] = Snone,
+ [TEXT_SHNDX] = Stext, [RODATA_SHNDX] = Srodata,
+ [DATA_SHNDX] = Sdata, [BSS_SHNDX] = Sbss,
+};
+
+enum section
+elfhassym(internstr nam, uint *value)
+{
+ struct sym *sym = findsym(nam);
+ if (sym) {
+ if (value) *value = sym->value;
+ return shndx2sect[sym->shndx];
+ }
+ return Snone;
+}
+
+void
+elfaddsym(internstr nam, int info, enum section sect, uvlong value, uvlong size)
+{
+ struct sym *sym = findsym(nam), sym0;
+ if (!sym) {
+ sym = &sym0;
+ sym->name = str2idx(&nam->c);
+ }
+ sym->bind = info >> 4;
+ sym->type = info & 0xF;
+ sym->shndx = sect2ndx[sect];
+ sym->value = value;
+ sym->size = size;
+ if (sym == &sym0) {
+ assert(symtab.n < 1<<16);
+ pmap_set(&symht, nam, symtab.n);
+ vpush(&symtab, sym0);
+ }
+}
+
+static const ushort relktab[][NRELOCKIND] = {
+ [ISx86_64] = {
+ [REL_ABS64] = 1, /* R_X86_64_64 */
+ [REL_ABS32] = 10, /* R_X86_64_32 */
+ [REL_ABS32S] = 11, /* R_X86_64_32S */
+ [REL_PCREL32] = 2, /* R_X86_64_PC32 */
+ [REL_PLT32] = 4, /* R_X86_64_PLT32 */
+ [REL_GOTPCRELX] = 41, /* R_X86_64_GOTPCRELX */
+ [REL_GOTPCRELX_REX] = 42, /* R_X86_64_REX_GOTPCRELX */
+ },
+ [ISaarch64] = {
+ [REL_ABS64] = 257, /* R_AARCH64_ABS64 */
+ [REL_ABS32] = 258, /* R_AARCH64_ABS32 */
+ [REL_ABS32S] = 258, /* R_AARCH64_ABS32S */
+ [REL_PCREL32] = 261, /* R_AARCH64_PREL2 */
+ [REL_PLT32] = 314, /* R_AARCH64_PLT32 */
+ [REL_CALL26] = 283, /* R_AARCH64_CALL26 */
+ [REL_LD_PREL_LO19] = 273, /* R_AARCH64_LD_PREL_LO19 */
+ [REL_ADR_PREL_LO21] = 274, /* R_AARCH64_ADR_PREL_LO21 */
+ [REL_ADR_PREL_PG_HI21] = 276, /* R_AARCH64_ADR_PREL_PG_HI21_NC */
+ [REL_ADD_ABS_LO12_NC] = 277, /* R_AARCH64_ADD_ABS_LO12_NC */
+
+ }
+};
+
+void
+elfreloc(internstr sym, enum relockind kind, enum section section, uint off, vlong addend)
+{
+ switch (section) {
+ default: assert(0);
+ case Stext: ++ntextrel; break;
+ case Srodata: ++nrodatarel; break;
+ case Sdata: ++ndatarel; break;
+ }
+ assert(kind < NRELOCKIND);
+ vpush(&relocs, ((struct reloc) { section, relktab[target.arch][kind], off, addend, .symname = sym }));
+}
+
+static void
+elf64puthdr(struct wbuf *out, struct elf64hdr *hdr)
+{
+ if (!hostntarg_sameendian()) {
+ hdr->type = bswap16(hdr->type);
+ hdr->machine = bswap16(hdr->machine);
+ hdr->version = bswap32(hdr->version);
+ hdr->entry = bswap64(hdr->entry);
+ hdr->phoff = bswap64(hdr->phoff);
+ hdr->shoff = bswap64(hdr->shoff);
+ hdr->flags = bswap32(hdr->flags);
+ hdr->ehsize = bswap16(hdr->ehsize);
+ hdr->phentsize = bswap16(hdr->phentsize);
+ hdr->phnum = bswap16(hdr->phnum);
+ hdr->shentsize = bswap16(hdr->shentsize);
+ hdr->shnum = bswap16(hdr->shnum);
+ hdr->shstrndx = bswap16(hdr->shstrndx);
+ }
+ iowrite(out, hdr, sizeof *hdr);
+}
+
+static void
+elf32puthdr(struct wbuf *out, struct elf32hdr *hdr)
+{
+ if (!hostntarg_sameendian()) {
+ hdr->type = bswap16(hdr->type);
+ hdr->machine = bswap16(hdr->machine);
+ hdr->version = bswap32(hdr->version);
+ hdr->entry = bswap32(hdr->entry);
+ hdr->phoff = bswap32(hdr->phoff);
+ hdr->shoff = bswap32(hdr->shoff);
+ hdr->flags = bswap32(hdr->flags);
+ hdr->ehsize = bswap16(hdr->ehsize);
+ hdr->phentsize = bswap16(hdr->phentsize);
+ hdr->phnum = bswap16(hdr->phnum);
+ hdr->shentsize = bswap16(hdr->shentsize);
+ hdr->shnum = bswap16(hdr->shnum);
+ hdr->shstrndx = bswap16(hdr->shstrndx);
+ }
+ iowrite(out, hdr, sizeof *hdr);
+}
+
+static void
+elf64putshdr(struct wbuf *out, struct elf64shdr *shdr)
+{
+ if (!hostntarg_sameendian()) {
+ shdr->name = bswap32(shdr->name);
+ shdr->type = bswap32(shdr->type);
+ shdr->flags = bswap64(shdr->flags);
+ shdr->addr = bswap64(shdr->addr);
+ shdr->offset = bswap64(shdr->offset);
+ shdr->size = bswap64(shdr->size);
+ shdr->link = bswap32(shdr->link);
+ shdr->info = bswap32(shdr->info);
+ shdr->addralign = bswap64(shdr->addralign);
+ shdr->entsize = bswap64(shdr->entsize);
+ }
+ iowrite(out, shdr, sizeof *shdr);
+}
+
+static void
+elf32putshdr(struct wbuf *out, struct elf32shdr *shdr)
+{
+ if (!hostntarg_sameendian()) {
+ shdr->name = bswap32(shdr->name);
+ shdr->type = bswap32(shdr->type);
+ shdr->flags = bswap32(shdr->flags);
+ shdr->addr = bswap32(shdr->addr);
+ shdr->offset = bswap32(shdr->offset);
+ shdr->size = bswap32(shdr->size);
+ shdr->link = bswap32(shdr->link);
+ shdr->info = bswap32(shdr->info);
+ shdr->addralign = bswap32(shdr->addralign);
+ shdr->entsize = bswap32(shdr->entsize);
+ }
+ iowrite(out, shdr, sizeof *shdr);
+}
+
+static void
+elf64putsym(struct wbuf *out, struct elf64sym *sym)
+{
+ if (!hostntarg_sameendian()) {
+ sym->name = bswap32(sym->name);
+ sym->shndx = bswap16(sym->shndx);
+ sym->value = bswap64(sym->value);
+ sym->size = bswap64(sym->size);
+ }
+ iowrite(out, sym, sizeof *sym);
+}
+
+static void
+elf32putsym(struct wbuf *out, struct elf32sym *sym)
+{
+ if (!hostntarg_sameendian()) {
+ sym->name = bswap32(sym->name);
+ sym->value = bswap32(sym->value);
+ sym->size = bswap32(sym->size);
+ sym->shndx = bswap16(sym->shndx);
+ }
+ iowrite(out, sym, sizeof *sym);
+}
+
+static void
+putsym(struct wbuf *out, const struct sym *sym)
+{
+ if (targ_64bit) {
+ elf64putsym(out, &(struct elf64sym) {
+ sym->name, .info = ELF_S_INFO(sym->bind, sym->type),
+ .shndx = sym->shndx, .value = sym->value, .size = sym->size });
+ } else {
+ elf32putsym(out, &(struct elf32sym) {
+ sym->name, .info = ELF_S_INFO(sym->bind, sym->type),
+ .shndx = sym->shndx, .value = sym->value, .size = sym->size });
+ }
+}
+
+static void
+elf64putrel(struct wbuf *out, struct elf64rel *rel)
+{
+ if (!hostntarg_sameendian()) {
+ rel->offset = bswap64(rel->offset);
+ rel->info = bswap64(rel->info);
+ }
+ iowrite(out, rel, sizeof *rel);
+}
+
+static void
+elf32putrel(struct wbuf *out, struct elf32rel *rel)
+{
+ if (!hostntarg_sameendian()) {
+ rel->offset = bswap32(rel->offset);
+ rel->info = bswap32(rel->info);
+ }
+ iowrite(out, rel, sizeof *rel);
+}
+
+static void
+elf64putrela(struct wbuf *out, struct elf64rela *rel)
+{
+ if (!hostntarg_sameendian()) {
+ rel->offset = bswap64(rel->offset);
+ rel->info = bswap64(rel->info);
+ rel->addend = bswap64(rel->addend);
+ }
+ iowrite(out, rel, sizeof *rel);
+}
+
+static void
+elf32putrela(struct wbuf *out, struct elf32rela *rel)
+{
+ if (!hostntarg_sameendian()) {
+ rel->offset = bswap32(rel->offset);
+ rel->info = bswap32(rel->info);
+ rel->addend = bswap32(rel->addend);
+ }
+ iowrite(out, rel, sizeof *rel);
+}
+
+static void
+putreloc(struct wbuf *out, const struct reloc *rel, bool userela)
+{
+ if (userela) {
+ if (targ_64bit) {
+ elf64putrela(out, &(struct elf64rela) {
+ rel->off, ELF64_R_INFO(rel->symidx, rel->kind), rel->addend });
+ } else {
+ elf32putrela(out, &(struct elf32rela) {
+ rel->off, ELF32_R_INFO(rel->symidx, rel->kind), rel->addend });
+ }
+ } else {
+ if (targ_64bit) {
+ elf64putrel(out, &(struct elf64rel) {
+ rel->off, ELF64_R_INFO(rel->symidx, rel->kind) });
+ } else {
+ elf32putrel(out, &(struct elf32rel) {
+ rel->off, ELF32_R_INFO(rel->symidx, rel->kind) });
+ }
+ }
+}
+
+/* ensure .symtab entries are ordered like this:
+ * (0. zero entry: NOTYPE LOCAL UND)
+ * (1. file: FILE LOCAL ABS "...")
+ * 2. locals
+ * 3. defined globals
+ * 4. undefined globals
+ */
+static int
+symcmp(const void *aa, const void *bb)
+{
+ const ushort *a = aa, *b = bb;
+ const struct sym *l = &symtab.p[*a], *r = &symtab.p[*b];
+ int tmp;
+ if ((tmp = l->bind - r->bind)) return tmp; /* locals prio */
+ if ((tmp = r->shndx - l->shndx)) return tmp; /* section prio (real sections > SHN_UND) */
+ return l->name - r->name;
+}
+
+static void
+wordalign(struct wbuf *out, int align)
+{
+ size_t off = out->len + lseek(out->fd, 0, SEEK_CUR);
+ while (off++ & (align - 1)) ioputc(out, 0);
+}
+
+static const bool userelatab[] = { [ISx86_64] = 1, [ISaarch64] = 1 };
+
+void
+elffini(struct wbuf *out)
+{
+ enum {
+ shnam_text = 1, shnam_rodata = 7, shnam_data = 15, shnam_bss = 21, shnam_shstrtab = 26,
+ shnam_strtab = 36, shnam_symtab = 44, shnam_reltext = 52, shnam_relrodata = 63, shnam_reldata = 76
+ };
+ int align = targ_64bit ? 8 : 4;
+ bool userela = userelatab[target.arch];
+ char shstrs[] = "\0.text\0.rodata\0.data\0.bss\0.shstrtab\0.strtab\0.symtab\0"
+ ".rela.text\0.rela.rodata\0.rela.data";
+ if (!userela) {
+ /* .rela -> .rel */
+ memcpy(shstrs + shnam_reltext + 4, ".text\0", 6);
+ memcpy(shstrs + shnam_relrodata + 4, ".rodata\0", 8);
+ memcpy(shstrs + shnam_reldata + 4, ".data\0", 6);
+ }
+
+ symtab.p[1].name = str2idx(objout.infile);
+ /* create a mapping of original symbol index -> sorted symtab index */
+ uint ndefsym = symtab.n;
+ ushort ssbuf[2<<10],
+ *sortedsyms = ndefsym*2 < countof(ssbuf) ? ssbuf : xmalloc(sizeof *sortedsyms * ndefsym*2),
+ *defsym2idx = sortedsyms + ndefsym;
+ for (int i = 0; i < ndefsym; ++i) sortedsyms[i] = i;
+ qsort(sortedsyms+2, ndefsym-2, sizeof *sortedsyms, symcmp);
+ for (int i = 0; i < ndefsym; ++i) defsym2idx[sortedsyms[i]] = i;
+
+ /* fixup relocs */
+ for (int i = 0; i < relocs.n; ++i) {
+ struct reloc *rel = &relocs.p[i];
+ struct sym *sym = findsym(rel->symname);
+ if (sym) {
+ uint idx = sym - symtab.p;
+ rel->symidx = idx < ndefsym ? defsym2idx[idx] : idx;
+ } else {
+ assert(symtab.n < 1<<16);
+ vpush(&symtab, ((struct sym) { str2idx(&rel->symname->c), .bind = STB_GLOBAL, .type = STT_NOTYPE, .shndx = SHN_UND }));
+ pmap_set(&symht, rel->symname, symtab.n-1);
+ rel->symidx = symtab.n-1;
+ }
+ if (!userela) {
+ uchar *p;
+ switch (rel->section) {
+ default: assert(0);
+ case Sdata: p = O.data.p + rel->off; break;
+ case Srodata: p = O.rodata.p + rel->off; break;
+ case Stext: p = O.textbegin + rel->off; break;
+ }
+ if (targ_64bit)
+ wr64targ(p, rel->addend);
+ else
+ wr32targ(p, rel->addend);
+ }
+ }
+ size_t codesize = alignup(O.code - O.textbegin, align),
+ rodataoff = (targ_64bit ? sizeof hdr.h64 : sizeof hdr.h32) + codesize,
+ rodatasize = O.rodata.n,
+ dataoff = rodataoff + rodatasize,
+ datasize = O.data.n,
+ bsssize = O.nbss,
+ shstrsoff = dataoff + datasize,
+ shstrssize = sizeof(shstrs),
+ strsoff = shstrsoff + shstrssize,
+ strssize = strs.n,
+ symtaboff = alignup(strsoff + strssize, align),
+ symtabsize = symtab.n * (targ_64bit ? 24 : 16),
+ reltextoff = symtaboff + symtabsize,
+ relxsiz = userela ? (targ_64bit ? 24 : 12) : (targ_64bit ? 16 : 8),
+ reltextsize = ntextrel * relxsiz,
+ relrodataoff = reltextoff + reltextsize,
+ relrodatasize = nrodatarel * relxsiz,
+ reldataoff = relrodataoff + relrodatasize,
+ reldatasize = ndatarel * relxsiz;
+ int nlocal = 0;
+
+#define CHECKOFF(s, x) \
+ efmt(s " expect OFF: %u ; ACTUAL %u\n", (uint)(x),(uint)(lseek(out->fd, 0, SEEK_CUR) + out->len))
+
+ if (targ_64bit) {
+ hdr.h64.shoff = alignup(reldataoff + reldatasize, align);
+ hdr.h64.shnum = 11;
+ hdr.h64.shstrndx = 5;
+ } else {
+ hdr.h32.shoff = alignup(reldataoff + reldatasize, align);
+ hdr.h32.shnum = 11;
+ hdr.h32.shstrndx = 5;
+ }
+
+ /* elf header */
+ if (targ_64bit)
+ elf64puthdr(out, &hdr.h64);
+ else
+ elf32puthdr(out, &hdr.h32);
+
+ /* .text progbits */
+ iowrite(out, O.textbegin, codesize);
+
+ /* .rodata progbits */
+ iowrite(out, O.rodata.p, O.rodata.n);
+
+ /* .data progbits */
+ iowrite(out, O.data.p, O.data.n);
+
+ /* section names */
+ iowrite(out, shstrs, sizeof shstrs);
+
+ /* strings */
+ iowrite(out, strs.p, strs.n);
+
+ /* symtab */
+ wordalign(out, align);
+ for (int i = 0; i < symtab.n; ++i) {
+ struct sym *sym = &symtab.p[i < ndefsym ? sortedsyms[i] : i];
+ if (sym->bind == STB_LOCAL) ++nlocal;
+ putsym(out, sym);
+ }
+ if (sortedsyms != ssbuf) free(sortedsyms);
+
+ /* rel.* */
+ assert(relocs.n == ntextrel + nrodatarel + ndatarel);
+ for (enum section s = Stext; s <= Sbss; ++s) {
+ for (int i = 0; i < relocs.n; ++i) {
+ struct reloc *rel = &relocs.p[i];
+ if (rel->section != s) continue;
+ putreloc(out, rel, userela);
+ }
+ }
+
+ /** Section Headers **/
+ wordalign(out, align);
+#define putshdr(...) if (targ_64bit) elf64putshdr(out, &(struct elf64shdr) { __VA_ARGS__ }); \
+ else elf32putshdr(out, &(struct elf32shdr) { __VA_ARGS__ });
+ /* §0 null section */
+ putshdr(0);
+ /* §1 .text */
+ putshdr(.name = shnam_text, .type = SHT_PROGBITS,
+ .flags = SHF_ALLOC | SHF_EXECINSTR,
+ .offset = targ_64bit ? hdr.h64.ehsize : hdr.h32.ehsize, .size = codesize,
+ .addralign = align);
+ /* §2 .rodata */
+ putshdr(.name = shnam_rodata, .type = SHT_PROGBITS,
+ .flags = SHF_ALLOC,
+ .offset = rodataoff, .size = rodatasize,
+ .addralign = O.rodataalign,);
+ /* §3 .data */
+ putshdr(.name = shnam_data, .type = SHT_PROGBITS,
+ .flags = SHF_ALLOC | SHF_WRITE,
+ .offset = dataoff, .size = datasize,
+ .addralign = O.dataalign,);
+ /* §4 .bss */
+ putshdr(.name = shnam_bss, .type = SHT_NOBITS,
+ .size = bsssize,
+ .flags = SHF_ALLOC | SHF_WRITE,
+ .addralign = O.bssalign,);
+ /* §5 .shstrtab */
+ putshdr(.name = shnam_shstrtab, .type = SHT_STRTAB,
+ .offset = shstrsoff, .size = shstrssize,
+ .flags = SHF_STRINGS );
+ /* §6 .strtab */
+ putshdr(.name = shnam_strtab, .type = SHT_STRTAB,
+ .offset = strsoff, .size = strssize,
+ .flags = SHF_STRINGS );
+ /* §7 .symtab */
+ putshdr(.name = shnam_symtab, .type = SHT_SYMTAB,
+ .offset = symtaboff, .size = symtabsize,
+ .flags = SHF_STRINGS,
+ .link = 6, /* .strtab */
+ .info = nlocal,
+ .entsize = targ_64bit ? 24 : 16 );
+ /* §8 .rel.text */
+ putshdr(.name = shnam_reltext, .type = SHT_RELA,
+ .offset = reltextoff, .size = reltextsize,
+ .link = 7, /* .symtab */
+ .entsize = relxsiz,
+ .info = TEXT_SHNDX );
+ /* §9 .rel.rodata */
+ putshdr(.name = shnam_relrodata, .type = SHT_RELA,
+ .offset = relrodataoff, .size = relrodatasize,
+ .link = 7, /* .symtab */
+ .entsize = relxsiz,
+ .info = RODATA_SHNDX );
+ /* §10 .rel.data */
+ putshdr(.name = shnam_reldata, .type = SHT_RELA,
+ .offset = reldataoff, .size = reldatasize,
+ .link = 7, /* .symtab */
+ .entsize = relxsiz,
+ .info = DATA_SHNDX );
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/o_elf.h b/src/o_elf.h
new file mode 100644
index 0000000..c96ae8b
--- /dev/null
+++ b/src/o_elf.h
@@ -0,0 +1,206 @@
+#include "../common.h"
+
+#define ELFMAG "\177ELF"
+enum {
+ ELFCLASS32 = 1,
+ ELFCLASS64 = 2,
+
+ ELFDATA2LSB = 1,
+ ELFDATA2MSB = 2,
+
+ ELFVERSION = 1,
+
+ ELFOSABI_SYSV = 0,
+ ELFOSABI_ARM = 97,
+ ELFOSABI_STANDALONE = 255,
+
+ ET_NONE = 0,
+ ET_REL, ET_EXEC, ET_DYN, ET_CORE,
+
+ EM_NONE = 0,
+ EM_386 = 3,
+ EM_486 = 6,
+ EM_MIPS = 8,
+ EM_MIPS_RS4_BE = 0xA,
+ EM_ARM = 0x28,
+ EM_X86_64 = 0x3E,
+ EM_ARM64 = 0xB7,
+};
+
+#define ELF_HDRIDENT \
+ union { \
+ uchar ident[16]; \
+ struct { \
+ uchar i_mag[4], \
+ i_class, \
+ i_data, \
+ i_version, \
+ i_osabi, \
+ i_abiversion, \
+ i_pad[7]; \
+ }; \
+ }
+
+struct elf64hdr {
+ ELF_HDRIDENT;
+ ushort type,
+ machine;
+ uint version;
+ uvlong entry,
+ phoff,
+ shoff;
+ uint flags;
+ ushort ehsize,
+ phentsize,
+ phnum,
+ shentsize,
+ shnum,
+ shstrndx;
+};
+static_assert(sizeof(struct elf64hdr) == 64);
+
+struct elf32hdr {
+ ELF_HDRIDENT;
+ ushort type,
+ machine;
+ uint version;
+ uint entry,
+ phoff,
+ shoff;
+ uint flags;
+ ushort ehsize,
+ phentsize,
+ phnum,
+ shentsize,
+ shnum,
+ shstrndx;
+};
+static_assert(sizeof(struct elf32hdr) == 52);
+
+enum {
+ SHT_NULL = 0x0,
+ SHT_PROGBITS = 0x1,
+ SHT_SYMTAB = 0x2,
+ SHT_STRTAB = 0x3,
+ SHT_RELA = 0x4,
+ SHT_HASH = 0x5,
+ SHT_DYNAMIC = 0x6,
+ SHT_NOTE = 0x7,
+ SHT_NOBITS = 0x8,
+ SHT_REL = 0x9,
+ SHT_SHLIB = 0xA,
+ SHT_DYNSYM = 0xB,
+ SHT_INIT_ARRAY = 0xE,
+ SHT_FINI_ARRAY = 0xF,
+ SHT_PREINIT_ARRAY = 0x10,
+ SHT_GROUP = 0x12,
+ SHT_SYMTAB_SHNDX = 0x13,
+};
+
+enum {
+ SHF_WRITE = 0x1,
+ SHF_ALLOC = 0x2,
+ SHF_EXECINSTR = 0x4,
+ SHF_MERGE = 0x10,
+ SHF_STRINGS = 0x20,
+ SHF_INFO_LINK = 0x40,
+ SHF_LINK_ORDER = 0x80,
+ SHF_OS_NONCONFORMING = 0x100,
+ SHF_GROUP = 0x200,
+ SHF_TLS = 0x400,
+};
+
+struct elf64shdr {
+ uint name,
+ type;
+ uvlong flags,
+ addr,
+ offset,
+ size;
+ uint link,
+ info;
+ uvlong addralign,
+ entsize;
+};
+static_assert(sizeof(struct elf64shdr) == 64);
+
+struct elf32shdr {
+ uint name,
+ type,
+ flags,
+ addr,
+ offset,
+ size,
+ link,
+ info,
+ addralign,
+ entsize;
+};
+static_assert(sizeof(struct elf32shdr) == 40);
+
+enum {
+ STB_LOCAL,
+ STB_GLOBAL,
+ STB_WEAK
+};
+
+enum {
+ STT_NOTYPE,
+ STT_OBJECT,
+ STT_FUNC,
+ STT_SECTION,
+ STT_FILE,
+};
+
+enum {
+ SHN_UND = 0,
+ SHN_ABS = 0xFFF1,
+};
+
+#define ELF_S_INFO(b,t) ((b) << 4 | (t))
+
+struct elf64sym {
+ uint name;
+ uchar info,
+ other;
+ ushort shndx;
+ uvlong value,
+ size;
+};
+static_assert(sizeof(struct elf64sym) == 24);
+
+struct elf32sym {
+ uint name,
+ value,
+ size;
+ uchar info,
+ other;
+ ushort shndx;
+};
+static_assert(sizeof(struct elf32sym) == 16);
+
+#define ELF64_R_INFO(s,t) ((uvlong) (s) << 32 | (uint)(t))
+struct elf64rel {
+ uvlong offset, info;
+};
+static_assert(sizeof(struct elf64rel) == 16);
+
+#define ELF32_R_INFO(s,t) ((s) << 8 | (uchar)(t))
+struct elf32rel {
+ uint offset, info;
+};
+static_assert(sizeof(struct elf32rel) == 8);
+
+struct elf64rela {
+ uvlong offset, info;
+ vlong addend;
+};
+static_assert(sizeof(struct elf64rela) == 24);
+
+struct elf32rela {
+ uint offset, info;
+ int addend;
+};
+static_assert(sizeof(struct elf32rela) == 12);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/obj.c b/src/obj.c
new file mode 100644
index 0000000..12f0db7
--- /dev/null
+++ b/src/obj.c
@@ -0,0 +1,118 @@
+#include "obj.h"
+#include "../ir/ir.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+
+void elfinit(void);
+enum section elfhassym(internstr , uint *value);
+void elfaddsym(internstr , int info, enum section, uvlong value, uvlong size);
+void elfreloc(internstr sym, enum relockind, enum section, uint off, vlong addend);
+void elffini(struct wbuf *);
+
+struct objfile objout;
+
+enum { NTEXT = 4<<20 /* 4MiB */ };
+
+void
+objini(const char *infile, const char *outfile)
+{
+ assert(!objout.outfile);
+ objout.infile = infile;
+ objout.outfile = outfile;
+ objout.code = objout.textbegin = mapzeros(NTEXT);
+ objout.textend = objout.textbegin + NTEXT;
+
+ switch (mctarg->objkind) {
+ case OBJELF: elfinit(); break;
+ }
+}
+
+void
+objdeffunc(internstr nam, bool globl, uint off, uint siz)
+{
+ switch (mctarg->objkind) {
+ case OBJELF:
+ elfaddsym(nam, /*STT_LOCAL/GLOBAL*/globl << 4 | /*STT_FUNC*/2, Stext, off, siz);
+ break;
+ }
+}
+
+enum section
+objhassym(internstr name, uint *off)
+{
+ return elfhassym(name, off);
+}
+
+uint
+objnewdat(internstr name, enum section sec, bool globl, uint siz, uint align)
+{
+ struct objfile *o = &objout;
+ uint off;
+ assert(siz && align && ispo2(align));
+ switch (sec) {
+ default: assert(0);
+ case Stext:
+ assert(align <= targ_primsizes[TYPTR]);
+ assert(o->textend - siz > o->code);
+ while ((o->code - o->textbegin) & (align - 1)) ++o->code;
+ off = o->code - o->textbegin;
+ o->code += siz;
+ break;
+ case Srodata:
+ if (align > o->rodataalign) o->rodataalign = align;
+ while (o->rodata.n & (align - 1)) vpush(&o->rodata, 0);
+ off = o->rodata.n;
+ vresize(&o->rodata, o->rodata.n + siz);
+ memset(o->rodata.p+off, 0, siz);
+ break;
+ case Sdata:
+ if (align > o->dataalign) o->dataalign = align;
+ while (o->data.n & (align - 1)) vpush(&o->data, 0);
+ off = o->data.n;
+ vresize(&o->data, o->data.n + siz);
+ memset(o->data.p+off, 0, siz);
+ break;
+ case Sbss:
+ if (align > o->bssalign) o->bssalign = align;
+ off = alignup(o->nbss, align);
+ o->nbss = off + siz;
+ break;
+ }
+
+ switch (mctarg->objkind) {
+ case OBJELF:
+ elfaddsym(name, /*STT_LOCAL/GLOBAL*/globl<<4 | /*STT_OBJECT*/1, sec, off, siz);
+ break;
+ }
+ return off;
+}
+
+void
+objreloc(internstr sym, enum relockind reloc, enum section section, uint off, vlong addend)
+{
+ switch (mctarg->objkind) {
+ case OBJELF:
+ elfreloc(sym, reloc, section, off, addend);
+ break;
+ }
+}
+
+void
+objfini(void)
+{
+ static char buf[1<<12];
+ struct wbuf out = FDBUF(buf, sizeof buf, open(objout.outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666));
+ if (out.fd < 0) fatal(NULL, "could not open %'s for writing: %s", objout.outfile, strerror(errno));
+
+ switch (mctarg->objkind) {
+ case OBJELF: elffini(&out); break;
+ }
+
+ munmap(objout.textbegin, NTEXT);
+ ioflush(&out);
+ close(out.fd);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/obj.h b/src/obj.h
new file mode 100644
index 0000000..1982033
--- /dev/null
+++ b/src/obj.h
@@ -0,0 +1,36 @@
+#include "../common.h"
+
+extern struct objfile {
+ const char *infile, *outfile;
+ uchar *textbegin, *textend;
+ uchar *code;
+ uchar dataalign, rodataalign, bssalign;
+ uint nbss;
+ vec_of(uchar) data, rodata;
+} objout;
+
+enum relockind {
+ REL_ABS64,
+ REL_ABS32,
+ REL_ABS32S,
+ REL_PCREL32,
+ REL_PLT32,
+ REL_GOTPCRELX,
+ REL_GOTPCRELX_REX,
+ REL_CALL26,
+ REL_ADR_PREL_LO21,
+ REL_ADR_PREL_PG_HI21,
+ REL_ADD_ABS_LO12_NC,
+ REL_LD_PREL_LO19,
+ NRELOCKIND,
+};
+enum section { Snone, Stext, Srodata, Sdata, Sbss };
+
+void objini(const char *infile, const char *outfile);
+void objdeffunc(internstr nam, bool globl, uint off, uint siz);
+enum section objhassym(internstr name, uint *off);
+uint objnewdat(internstr name, enum section, bool globl, uint siz, uint align);
+void objreloc(internstr sym, enum relockind, enum section, uint off, vlong addend);
+void objfini(void);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_aarch64.h b/src/t_aarch64.h
new file mode 100644
index 0000000..828909e
--- /dev/null
+++ b/src/t_aarch64.h
@@ -0,0 +1,16 @@
+#include "../ir/ir.h"
+
+enum reg {
+ R0 = 0,
+#define R(n) (R0+n)
+ FP = R(29), LR = R(30), SP = R(31),
+ V0,
+#define V(n) (V0+n)
+};
+
+bool aarch64_logimm(uint *enc, enum irclass, uvlong x);
+void aarch64_isel(struct function *);
+void aarch64_emit(struct function *);
+
+/* vim:set ts=3 sw=3 expandtab: */
+
diff --git a/src/t_aarch64_aapcs.c b/src/t_aarch64_aapcs.c
new file mode 100644
index 0000000..fc08da1
--- /dev/null
+++ b/src/t_aarch64_aapcs.c
@@ -0,0 +1,77 @@
+#include "all.h"
+
+static int
+abiarg(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype typ)
+{
+ enum { NINT = 8, NFLT = 8 };
+ if (!typ.isagg) {
+ if (kisflt(cls[0] = typ.cls) && *nf < 8) {
+ r[0] = V(0) + (*nf)++;
+ } else if (kisint(cls[0]) && *ni < NINT) {
+ r[0] = R0 + (*ni)++;
+ } else {
+ r[0] = *ns;
+ *ns += 8;
+ return 0; /* MEMORY */
+ }
+ return 1;
+ } else assert(!"nyi");
+}
+
+static int
+abiret(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype typ)
+{
+ if (!typ.isagg) {
+ r[0] = kisflt(cls[0] = typ.cls) ? V(0) : R0;
+ return 1;
+ }
+ int nf = 0, ns = 0;
+ int ret = abiarg(r, cls, r2off, ni, &nf, &ns, typ);
+ if (ret) return ret;
+ /* caller-allocated result address in x8 */
+ assert(*ni == 0);
+ r[0] = -1;
+ r[1] = R(8);
+ return 0;
+}
+
+static void
+vastart(struct function *fn, struct block *blk, int *curi)
+{
+ assert(!"nyi");
+}
+
+static void
+vaarg(struct function *fn, struct block *blk, int *curi)
+{
+ assert(!"nyi");
+}
+
+static const char aarch64_rnames[][6] = {
+ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9","R10","R11","R12","R13","R14","R15",
+ "R16","R17","R18","R19","R20","R21","R22","R23","R24","R25","R26","R27","R28", "FP", "LR", "SP",
+ "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9","V10","V11","V12","V13","V14","V15",
+ "V16","V17","V18","V19","V20","V21","V22","V23","V24","V25","V26","V27","V28","V29","V30","V31",
+};
+
+const struct mctarg t_aarch64_aapcs = {
+ .gpr0 = R0, .ngpr = 31,
+ .bpr = FP,
+ .gprscratch = R(16), .fprscratch = V(31),
+ .fpr0 = V0, .nfpr = 32,
+ .rcallee = BIT(R(19)) | BIT(R(20)) | BIT(R(21)) | BIT(R(22)) | BIT(R(23))
+ | BIT(R(24)) | BIT(R(25)) | BIT(R(26)) | BIT(R(27)) | BIT(R(28))
+ | BIT( V(8)) | BIT( V(9)) | BIT(V(10)) | BIT(V(11)) | BIT(V(12))
+ | BIT(V(13)) | BIT(V(14)) | BIT(V(15)),
+ .rglob = BIT(FP) | BIT(LR) | BIT(SP),
+ .rnames = aarch64_rnames,
+ .objkind = OBJELF,
+ .abiret = abiret,
+ .abiarg = abiarg,
+ .vastart = vastart,
+ .vaarg = vaarg,
+ .isel = aarch64_isel,
+ .emit = aarch64_emit,
+};
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_aarch64_emit.c b/src/t_aarch64_emit.c
new file mode 100644
index 0000000..9fdcd83
--- /dev/null
+++ b/src/t_aarch64_emit.c
@@ -0,0 +1,1023 @@
+#include "all.h"
+#include "../obj/obj.h"
+#include "../endian.h"
+
+/* References:
+ * ARM ARM https://developer.arm.com/documentation/ddi0628/aa/?lang=en
+ * AAELF ABI https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
+ */
+
+enum operkind { ONONE, OREGZR, OREG, OIMM, OMEM, OSYM };
+enum shiftkind { SLSL, SLSR, SASR, SROR };
+enum addrmode { AIMMIDX, AREGIDX, APREIDX, APOSTIDX };
+enum addrregext { XUXTW = 2, XLSL = 3, XSXTW = 6, XSXTX = 7 };
+struct oper {
+ uchar t;
+ union {
+ struct { /* OREG (opt. shifted) */
+ uchar reg;
+ uchar shft : 2, /* enum shiftkind */
+ shamt : 6;
+ };
+ struct { /* OMEM */
+ uchar mode : 3; /* enum addrmode */
+ uchar base : 5; /* reg */
+ union {
+ struct {
+ uchar index : 5; /* reg */
+ uchar ext : 3; /* enum addrregext */
+ uchar shamt;
+ };
+ short disp;
+ };
+ } m;
+ vlong imm; uvlong uimm; /* OIMM */
+ struct { /* OSYM */
+ ushort con;
+ int cdisp;
+ };
+ };
+};
+
+#define REGZR ((struct oper){OREGZR, .reg=31})
+#define mkoper(t, ...) ((struct oper){(t), __VA_ARGS__})
+#define reg2oper(r) (assert((uint)(r) <= V(31)), mkoper(OREG, .reg = (r)))
+
+static struct oper
+mkmemoper(uint msiz, union ref r)
+{
+ if (r.t == RTMP) {
+ assert(in_range(instrtab[r.i].reg-1, R0, SP));
+ return mkoper(OMEM, .m = {AIMMIDX, .base = instrtab[r.i].reg-1});
+ } else if (r.t == RREG) {
+ return mkoper(OMEM, .m = {AIMMIDX, .base = r.i});
+ } else if (isaddrcon(r,1)) {
+ return mkoper(OSYM, .con = r.i,);
+ } else if (r.t == RADDR) {
+ const struct addr *addr = &addrtab.p[r.i];
+ assert(addr->shift <= 3 && (!addr->disp || !addr->index.bits));
+ if (isaddrcon(addr->base,0)) {
+ assert(!addr->index.bits);
+ return mkoper(OSYM, .con = addr->base.i, .cdisp = addr->disp);
+ }
+ assert(addr->base.t == RREG);
+ if (!addr->index.bits) {
+ return mkoper(OMEM, .m = {.mode = AIMMIDX, .base = addr->base.i, .disp = addr->disp});
+ } else {
+ assert(addr->index.t == RREG);
+ assert(addr->shift == 0 || 1<<addr->shift == msiz);
+ return mkoper(OMEM, .m = {
+ .mode = AREGIDX,
+ .base = addr->base.i,
+ .index = addr->index.i,
+ .ext = XLSL,
+ .shamt = !!addr->shift,
+ });
+ }
+ }
+ assert(!"nyi");
+}
+
+static struct oper
+ref2oper(union ref r)
+{
+ switch (r.t) {
+ case RTMP: return instrtab[r.i].reg ? mkoper(ONONE,) : reg2oper(instrtab[r.i].reg-1);
+ case RREG: return reg2oper(r.i);
+ case RICON: return mkoper(OIMM, .imm = r.i);
+ case RXCON:
+ if (kisint(contab.p[r.i].cls))
+ return mkoper(OIMM, .imm = contab.p[r.i].i);
+ else if (kisflt(contab.p[r.i].cls)) {
+ assert(contab.p[r.i].f == 0.0);
+ return mkoper(OIMM, .imm = 0);
+ } else if (!contab.p[r.i].cls) {
+ return mkoper(OSYM, .con = r.i);
+ }
+ assert(0);
+ case RADDR: return mkmemoper(0, r);
+ default: assert(0);
+ }
+}
+
+enum operpat {
+ PNONE,
+ PGPRZ, /* R0-R30,ZR */
+ PGPRSP, /* R0-R30,SP */
+ PSP, /* SP */
+ PGPRZSHFT, /* R0-30,ZR SFHT #n */
+ PFPR, /* V0 - V31 */
+ PZERO, /* zero immediate */
+ PU6, /* 6-bit uimm */
+ PU12SL12, /* 12 bit uimm, optionally left shifted by 12 */
+ PU16SL16, /* 16 bit uimm, left shift by 0/16/32/48 */
+ PLOGIMM, /* immediate for logical instrs */
+ PMEMAIMM, /* addr 12bit immediate byte offset */
+ PMEMAIMMH, /* addr 12bit immediate halfword offset (multiple of 2) */
+ PMEMAIMMW, /* addr 12bit immediate word offset (multiple of 4) */
+ PMEMAIMMX, /* addr 12bit immediate doubleword offset (multiple of 8) */
+ PMEMPREPOST, /* addr signed 9bit immediate byte offset */
+ PMEMAREG, /* addr reg offset, optionally left shifted */
+ PSYM, /* symbol */
+};
+enum operenc {
+ EN_ADDSUBEXT3R, /* add/sub-ext-reg */
+ EN_ADDSUBSHFT3R, /* add/sub-shift-reg */
+ EN_LOGSHFT3R, /* logical/shifted-reg */
+ EN_ARITH2R, /* data-processing/1src */
+ EN_ARITH3R, /* data-processing/2src */
+ EN_ADDSUBIMM, /* add/subtract-imm */
+ EN_LOGIMM, /* logical-imm */
+ EN_MOVEIMM, /* move/wide-imm */
+ EN_MEMAIMM, /* load/store/unsigned-imm */
+ EN_MEMAIMMH, /* load/store/unsigned-imm (halfword) */
+ EN_MEMAIMMW, /* load/store/unsigned-imm (word) */
+ EN_MEMAIMMX, /* load/store/unsigned-imm (doubleword) */
+ EN_MEMAPREPOST, /* load/store/pre/postidx-imm */
+ EN_MEMAREG, /* load/store/reg-offset */
+ EN_MEMPPREPOST, /* load/store-pair/pre/postidx-imm */
+ EN_ADRSYMLO21, /* for ADR <sym> */
+ EN_ADRSYMPGHI21, /* for ADRP <sym:pghi21> */
+ EN_ADDSYMLO12, /* for ADD x,x, <sym:lo12> */
+ EN_LDSYMLO19, /* for LDR (literal) */
+ EN_FP2R, /* float 1src */
+ EN_FP1GPR1, /* fpr + gpr */
+ EN_FP3R, /* float 2src */
+ EN_FPIMM, /* float-imm */
+ EN_FPCMPZ, /* float cmp with zero */
+ EN_FPCMP, /* float cmp-imm */
+};
+struct desc {
+ uchar psiz; /* subset of {4,8} */
+ uchar pt[3]; /* bitsets of enum operpat, up to 3 operands */
+ uint opc;
+ uchar operenc; /* enum operenc */
+};
+
+/* match operand against pattern */
+static inline bool
+opermatch(enum operpat pat, enum irclass k, struct oper o)
+{
+ switch (pat) {
+ case PNONE: return !o.t;
+ case PGPRZ:
+ return o.t == OREGZR || (o.t == OREG && in_range(o.reg, R0, R(30)) && !o.shamt);
+ case PGPRSP:
+ return o.t == OREG && in_range(o.reg, R0, R(31)) && !o.shamt;
+ case PGPRZSHFT:
+ return o.t == OREGZR || (o.t == OREG && in_range(o.reg, R0, R(30)));
+ case PSP: return o.t == OREG && o.reg == SP;
+ case PFPR: return o.t == OREG && in_range(o.reg, V0, V(31));
+ case PZERO: return o.t == OIMM && o.imm == 0;
+ case PU6: return o.t == OIMM && (uint)o.imm < 63;
+ case PSYM: return o.t == OSYM;
+ case PU12SL12:
+ return o.t == OIMM && ((o.imm &~ 0xFFF) == 0 || (o.imm &~ 0xFFF000) == 0);
+ case PU16SL16:
+ return o.t == OIMM
+ && ((o.imm &~ 0xFFFF) == 0 || (o.imm &~ 0xFFFF0000) == 0
+ || (o.imm &~ (0xFFFFull<<32)) == 0 || (o.imm &~ (0xFFFFull<<48)) == 0);
+ case PLOGIMM: return o.t == OIMM && aarch64_logimm(NULL, k, o.imm);
+ case PMEMAIMM:
+ return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<12);
+ case PMEMAIMMH:
+ return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<13) && !(o.m.disp % 2);
+ case PMEMAIMMW:
+ return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<14) && !(o.m.disp % 4);
+ case PMEMAIMMX:
+ return o.t == OMEM && o.m.mode == AIMMIDX && (uint)o.m.disp < (1<<15) && !(o.m.disp % 8);
+ case PMEMAREG:
+ return o.t == OMEM && o.m.mode == AREGIDX;
+ case PMEMPREPOST:
+ return o.t == OMEM && (o.m.mode == APREIDX || o.m.mode == APOSTIDX
+ || (o.m.mode == AIMMIDX && o.m.disp >= -256 && o.m.disp < 256));
+ }
+ assert(0);
+}
+
+/* code output helpers */
+#define W32(w) (wr32targ(*pcode, (w)), *pcode += 4)
+
+static uchar *fnstart;
+static internstr curfnsym;
+static bool usefp;
+static int rbpoff;
+
+/* Given an instruction description table, find the first entry that matches
+ * the operands and encode it. */
+static void
+encode(uchar **pcode, const struct desc *tab, int ntab, enum irclass k, struct oper o[3])
+{
+ const struct desc *en = NULL;
+ for (int i = 0; i < ntab; ++i) {
+ if (!(tab[i].psiz & cls2siz[k])) continue;
+ for (int j = 0; j < 3; ++j)
+ if (!opermatch(tab[i].pt[j], k, o[j]))
+ goto Skip;
+ en = &tab[i];
+ break;
+ Skip:;
+ }
+ assert(en && "no match for instr");
+
+ uint sf = cls2siz[k] >> 3;
+ uint ins = en->opc, sh, nimmrs;
+ switch (en->operenc) {
+ default: assert(!"nyi enc");
+ case EN_ADDSUBSHFT3R: case EN_LOGSHFT3R:
+ ins |= sf<<31 | o[2].shft<<22 | o[2].reg<<16 | o[2].shamt<<10 | o[1].reg<<5 | o[0].reg;
+ break;
+ case EN_ARITH3R:
+ ins |= sf<<31 | o[2].reg<<16 | o[1].reg<<5 | o[0].reg;
+ break;
+ case EN_ADDSUBIMM:
+ sh = o[2].imm > 0xFFF;
+ ins |= sf<<31 | sh<<22 | (o[2].uimm >> 12*sh)<<10 | o[1].reg<<5 | o[0].reg;
+ break;
+ case EN_LOGIMM:
+ assert(aarch64_logimm(&nimmrs, k, o[2].uimm));
+ ins |= sf<<31 | nimmrs<<10 | o[1].reg<<5 | o[0].reg;
+ break;
+ case EN_MOVEIMM:
+ sh = o[1].imm ? lowestsetbit(o[1].imm) / 16 : 0;
+ ins |= sf<<31 | sh<<21 | (o[1].uimm >> 16*sh)<<5 | o[0].reg;
+ break;
+ case EN_MEMAIMM: AImm:
+ ins |= o[1].m.disp<<10 | o[1].m.base<<5 | (o[0].reg&31);
+ break;
+ case EN_MEMAIMMH: o[1].m.disp >>= 1; goto AImm;
+ case EN_MEMAIMMW: o[1].m.disp >>= 2; goto AImm;
+ case EN_MEMAIMMX: o[1].m.disp >>= 3; goto AImm;
+ case EN_MEMAPREPOST:
+ ins |= (o[1].m.disp&0x1FF)<<12 | o[1].m.base<<5 | (o[0].reg&31);
+ if (o[1].m.mode == APREIDX) ins |= 3<<10;
+ else if (o[1].m.mode == APOSTIDX) ins |= 1<<10;
+ break;
+ case EN_MEMAREG:
+ assert(o[1].m.shamt <= 1);
+ ins |= o[1].m.index<<16 | o[1].m.ext<<13 | o[1].m.shamt<<12 | o[1].m.base<<5 | (o[0].reg&31);
+ break;
+ case EN_MEMPPREPOST:
+ assert(o[2].m.disp % 8 == 0);
+ ins |= (o[2].m.disp/8&0x7F)<<15 | (o[1].reg&31)<<10 | o[2].m.base<<5 | (o[0].reg&31);
+ if (o[2].m.mode == APREIDX) ins |= 3<<23;
+ else if (o[2].m.mode == APOSTIDX) ins |= 1<<23;
+ else ins |= 2<<23;
+ break;
+ case EN_ADRSYMLO21:
+ ins |= o[0].reg;
+ objreloc(xcon2sym(o[1].con), REL_ADR_PREL_LO21, Stext, *pcode - objout.textbegin, o[1].cdisp);
+ break;
+ case EN_ADRSYMPGHI21:
+ ins |= o[0].reg;
+ objreloc(xcon2sym(o[1].con), REL_ADR_PREL_PG_HI21, Stext, *pcode - objout.textbegin, o[1].cdisp);
+ break;
+ case EN_ADDSYMLO12:
+ ins |= sf<<31 | o[1].reg<<5 | o[0].reg;
+ objreloc(xcon2sym(o[2].con), REL_ADD_ABS_LO12_NC, Stext, *pcode - objout.textbegin, o[1].cdisp);
+ break;
+ case EN_LDSYMLO19:
+ ins |= o[0].reg;
+ objreloc(xcon2sym(o[1].con), REL_LD_PREL_LO19, Stext, *pcode - objout.textbegin, o[1].cdisp);
+ break;
+ case EN_FP2R:
+ ins |= sf<<22 | (o[1].reg&31)<<5 | (o[0].reg&31);
+ break;
+ case EN_FP1GPR1:
+ ins |= (o[1].reg&31)<<5 | (o[0].reg&31);
+ break;
+ case EN_FP3R:
+ ins |= sf<<22 | (o[2].reg&31)<<16 | (o[1].reg&31)<<5 | (o[0].reg&31);
+ break;
+ case EN_FPCMPZ:
+ ins |= sf<<22 | (o[0].reg&31)<<5;
+ break;
+ case EN_FPCMP:
+ ins |= sf<<22 | (o[1].reg&31)<<16 | (o[0].reg&31)<<5;
+ break;
+ }
+ W32(ins);
+}
+#define DEFINSTR1(X, ...) \
+ static void \
+ X(uchar **pcode, enum irclass k, struct oper a) \
+ { \
+ static const struct desc tab[] = { __VA_ARGS__ }; \
+ encode(pcode, tab, countof(tab), k, ((struct oper [3]){a})); \
+ }
+
+#define DEFINSTR2(X, ...) \
+ static void \
+ X(uchar **pcode, enum irclass k, struct oper op1, struct oper op2) \
+ { \
+ static const struct desc tab[] = { __VA_ARGS__ }; \
+ encode(pcode, tab, countof(tab), k, ((struct oper [3]){op1,op2})); \
+ }
+#define DEFINSTR3(X, ...) \
+ static void \
+ X(uchar **pcode, enum irclass k, struct oper op1, struct oper op2, struct oper op3) \
+ { \
+ static const struct desc tab[] = { __VA_ARGS__ }; \
+ encode(pcode, tab, countof(tab), k, ((struct oper [3]){op1,op2,op3})); \
+ }
+
+DEFINSTR2(Xadrp,
+ {8, {PGPRZ, PSYM}, 0x90000000, EN_ADRSYMPGHI21} /* ADR (sym pg hi21) */
+)
+DEFINSTR2(Xadr,
+ {8, {PGPRZ, PSYM}, 0x10000000, EN_ADRSYMLO21} /* ADR (sym pg hi21) */
+)
+
+DEFINSTR3(Xadd,
+ {4|8, {PGPRSP, PGPRSP, PU12SL12}, 0x11000000, EN_ADDSUBIMM}, /* ADD (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x0B000000, EN_ADDSUBSHFT3R}, /* ADD (shifted register) */
+ { 8, {PGPRZ, PGPRZ, PSYM}, 0x11000000, EN_ADDSYMLO12}, /* ADD (sym lo12) */
+)
+DEFINSTR3(Xsub,
+ {4|8, {PGPRSP, PGPRSP, PU12SL12}, 0x51000000, EN_ADDSUBIMM}, /* SUB (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x4B000000, EN_ADDSUBSHFT3R}, /* SUB (shifted register) */
+)
+DEFINSTR3(Xsubs,
+ {4|8, {PGPRZ, PGPRSP, PU12SL12}, 0x71000000, EN_ADDSUBIMM}, /* SUBS (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x6B000000, EN_ADDSUBSHFT3R}, /* SUBS (shifted register) */
+)
+
+static void
+Xmadd(uchar **pcode, enum irclass k, struct oper d, struct oper n, struct oper m, struct oper a)
+{
+ assert(opermatch(PGPRZ, k, d) && opermatch(PGPRZ, k, n)
+ && opermatch(PGPRZ, k, a) && opermatch(PGPRZ, k, m));
+ uint sf = k > KI32;
+ W32(0x1B000000 | sf<<31 | m.reg<<16 | a.reg<<10 | n.reg<<5 | d.reg);
+}
+
+DEFINSTR3(Xsdiv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC00C00, EN_ARITH3R})
+DEFINSTR3(Xudiv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC00800, EN_ARITH3R})
+
+DEFINSTR3(Xand,
+ {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x12000000, EN_LOGIMM}, /* AND (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x0A000000, EN_LOGSHFT3R}, /* AND (shifted register) */
+)
+DEFINSTR3(Xorr,
+ {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x32000000, EN_LOGIMM}, /* ORR (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x2A000000, EN_LOGSHFT3R}, /* ORR (shifted register) */
+)
+DEFINSTR3(Xorn, {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x2A200000, EN_LOGSHFT3R})
+DEFINSTR3(Xeor,
+ {4|8, {PGPRSP, PGPRZ, PLOGIMM}, 0x52000000, EN_LOGIMM}, /* EOR (immediate) */
+ {4|8, {PGPRZ, PGPRZ, PGPRZSHFT}, 0x4A000000, EN_LOGSHFT3R}, /* EOR (shifted register) */
+)
+DEFINSTR3(Xlslv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02000, EN_ARITH3R})
+DEFINSTR3(Xlsrv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02400, EN_ARITH3R})
+DEFINSTR3(Xasrv, {4|8, {PGPRZ, PGPRZ, PGPRZ}, 0x1AC02800, EN_ARITH3R})
+static void
+Xubfm(uchar **pcode, enum irclass k, struct oper rd, struct oper rn, uint immr, uint imms)
+{
+ uint x = k != KI32;
+ uint nbit = x ? 64 : 32;
+ assert(opermatch(PGPRZ, k, rd) && opermatch(PGPRZ, k, rn) && immr < nbit && imms < nbit);
+ W32(x<<31 | 0x53000000 | x<<22 | immr<<16 | imms<<10 | rn.reg<<5 | rd.reg);
+}
+static void
+Xsbfm(uchar **pcode, enum irclass k, struct oper rd, struct oper rn, uint immr, uint imms)
+{
+ uint x = k != KI32;
+ uint nbit = x ? 64 : 32;
+ assert(opermatch(PGPRZ, k, rd) && opermatch(PGPRZ, k, rn) && immr < nbit && imms < nbit);
+ W32(x<<31 | 0x13000000 | x<<22 | immr<<16 | imms<<10 | rn.reg<<5 | rd.reg);
+}
+
+DEFINSTR2(Xmovz, {4|8, {PGPRZ, PU16SL16}, 0x52800000, EN_MOVEIMM}, /* MOVZ */)
+DEFINSTR2(Xmovn, {4|8, {PGPRZ, PU16SL16}, 0x12800000, EN_MOVEIMM}, /* MOVN */)
+DEFINSTR2(Xmovk, {4|8, {PGPRZ, PU16SL16}, 0x72800000, EN_MOVEIMM}, /* MOVK */)
+DEFINSTR2(Xldr,
+ {4, {PGPRZ, PMEMAIMMW}, 0xB9400000, EN_MEMAIMMW}, /* LDR (immediate) */
+ {8, {PGPRZ, PMEMAIMMX}, 0xF9400000, EN_MEMAIMMX},
+ {4, {PGPRZ, PMEMAREG}, 0xB8600800, EN_MEMAREG}, /* LDR (register) */
+ {8, {PGPRZ, PMEMAREG}, 0xF8600800, EN_MEMAREG},
+ {4, {PGPRZ, PSYM}, 0x18000000, EN_LDSYMLO19}, /* LDR (literal) */
+ {8, {PGPRZ, PSYM}, 0x58000000, EN_LDSYMLO19},
+ {4, {PGPRZ, PMEMPREPOST}, 0xB8400000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */
+ {8, {PGPRZ, PMEMPREPOST}, 0xF8400000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xfldr,
+ {4, {PFPR, PMEMAIMMW}, 0xBD400000, EN_MEMAIMMW}, /* LDR (immediate) */
+ {8, {PFPR, PMEMAIMMX}, 0xFD400000, EN_MEMAIMMX},
+ {4, {PFPR, PMEMAREG}, 0xBC600800, EN_MEMAREG}, /* LDR (register) */
+ {8, {PFPR, PMEMAREG}, 0xFC600800, EN_MEMAREG},
+ {4, {PFPR, PMEMPREPOST}, 0xBC400000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */
+ {8, {PFPR, PMEMPREPOST}, 0xFC400000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xldrsw,
+ {8, {PGPRZ, PMEMAIMMW}, 0xB9800000, EN_MEMAIMMW}, /* LDRSW (immediate) */
+// {8, {PGPRZ, PMEMAREG}, 0xB8A00800, EN_MEMAREG}, /* LDRSW (register) */
+ {8, {PGPRZ, PMEMPREPOST}, 0xB8800000, EN_MEMAPREPOST}, /* LDRSW (immediate, (pre/postinc)) */
+)
+DEFINSTR2(Xldrh,
+ {4|8, {PGPRZ, PMEMAIMMH}, 0x79400000, EN_MEMAIMMH}, /* LDRH (immediate) */
+ {4|8, {PGPRZ, PMEMAREG}, 0x78600800, EN_MEMAREG}, /* LDRH (register) */
+ {4|8, {PGPRZ, PMEMPREPOST}, 0x78400000, EN_MEMAPREPOST}, /* LDRH (immediate, (pre/postinc)) */
+)
+DEFINSTR2(Xldrsh,
+ {4, {PGPRZ, PMEMAIMMH}, 0x79C00000, EN_MEMAIMMH}, /* LDRSH (immediate) */
+ {8, {PGPRZ, PMEMAIMMH}, 0x79800000, EN_MEMAIMMH},
+ {4, {PGPRZ, PMEMAREG}, 0x78E00800, EN_MEMAREG}, /* LDRSH (register) */
+ {8, {PGPRZ, PMEMAREG}, 0x78A00800, EN_MEMAREG},
+ {4, {PGPRZ, PMEMPREPOST}, 0x78C00000, EN_MEMAPREPOST}, /* LDRSH (immediate, (pre/postinc)) */
+ {8, {PGPRZ, PMEMPREPOST}, 0x78800000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xldrb,
+ {4|8, {PGPRZ, PMEMAIMM}, 0x39400000, EN_MEMAIMM}, /* LDRB (immediate) */
+ {4|8, {PGPRZ, PMEMAREG}, 0x38600800, EN_MEMAREG}, /* LDRB (register) */
+ {4|8, {PGPRZ, PMEMPREPOST}, 0x38400000, EN_MEMAPREPOST}, /* LDRB (immediate, (pre/postinc)) */
+)
+DEFINSTR2(Xldrsb,
+ {4, {PGPRZ, PMEMAIMM}, 0x39C00000, EN_MEMAIMM}, /* LDRSB (immediate) */
+ {8, {PGPRZ, PMEMAIMM}, 0x39800000, EN_MEMAIMM},
+ {4, {PGPRZ, PMEMAREG}, 0x38E00800, EN_MEMAREG}, /* LDRSB (register) */
+ {8, {PGPRZ, PMEMAREG}, 0x38A00800, EN_MEMAREG},
+ {4, {PGPRZ, PMEMPREPOST}, 0x38C00000, EN_MEMAPREPOST}, /* LDRSB (immediate, (pre/postinc)) */
+ {8, {PGPRZ, PMEMPREPOST}, 0x38800000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xstr,
+ {4, {PGPRZ, PMEMAIMMW}, 0xB9000000, EN_MEMAIMMW}, /* STR (immediate) */
+ {8, {PGPRZ, PMEMAIMMX}, 0xF9000000, EN_MEMAIMMX},
+ {4, {PGPRZ, PMEMAREG}, 0xB8200800, EN_MEMAREG}, /* STR (register) */
+ {8, {PGPRZ, PMEMAREG}, 0xF8200800, EN_MEMAREG},
+ {4, {PGPRZ, PMEMPREPOST}, 0xB8000000, EN_MEMAPREPOST}, /* STR (immediate, (pre/postinc)) */
+ {8, {PGPRZ, PMEMPREPOST}, 0xF8000000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xfstr,
+ {4, {PFPR, PMEMAIMMW}, 0xBD000000, EN_MEMAIMMW}, /* LDR (immediate) */
+ {8, {PFPR, PMEMAIMMX}, 0xFD000000, EN_MEMAIMMX},
+ {4, {PFPR, PMEMAREG}, 0xBC200800, EN_MEMAREG}, /* LDR (register) */
+ {8, {PFPR, PMEMAREG}, 0xFC200800, EN_MEMAREG},
+ {4, {PFPR, PMEMPREPOST}, 0xBC000000, EN_MEMAPREPOST}, /* LDR (immediate, (pre/postinc)) */
+ {8, {PFPR, PMEMPREPOST}, 0xFC000000, EN_MEMAPREPOST},
+)
+DEFINSTR2(Xstrh,
+ {4|8, {PGPRZ, PMEMAIMMH}, 0x79000000, EN_MEMAIMMH}, /* STRH (immediate) */
+ {4|8, {PGPRZ, PMEMAREG}, 0x78200800, EN_MEMAREG}, /* STRH (register) */
+ {4|8, {PGPRZ, PMEMPREPOST}, 0x78000000, EN_MEMAPREPOST}, /* STRH (immediate, (pre/postinc)) */
+)
+DEFINSTR2(Xstrb,
+ {4|8, {PGPRZ, PMEMAIMM}, 0x39000000, EN_MEMAIMM}, /* STRB (immediate) */
+ {4|8, {PGPRZ, PMEMAREG}, 0x38200800, EN_MEMAREG}, /* STRB (register) */
+ {4|8, {PGPRZ, PMEMPREPOST}, 0x38000000, EN_MEMAPREPOST}, /* STRB (immediate, (pre/postinc)) */
+)
+DEFINSTR3(Xldp,
+ {8, {PGPRZ, PGPRZ, PMEMPREPOST}, 0xA8400000, EN_MEMPPREPOST} /* LDP (immediate, (pre/postinc)) */
+)
+DEFINSTR3(Xstp,
+ {8, {PGPRZ, PGPRZ, PMEMPREPOST}, 0xA8000000, EN_MEMPPREPOST} /* STP (immediate, (pre/postinc)) */
+)
+DEFINSTR3(Xfldp,
+ {8, {PFPR, PFPR, PMEMPREPOST}, 0x6CC00000, EN_MEMPPREPOST} /* LDP (immediate, (pre/postinc)) */
+)
+DEFINSTR3(Xfstp,
+ {8, {PFPR, PFPR, PMEMPREPOST}, 0x6C800000, EN_MEMPPREPOST} /* STP (immediate, (pre/postinc)) */
+)
+static void
+Xcall(uchar **pcode, struct oper dst)
+{
+ if (dst.t == OSYM) {
+ objreloc(xcon2sym(dst.con), REL_CALL26, Stext, *pcode - objout.textbegin, 0);
+ W32(0x94000000); /* BL <rel26> */
+ } else {
+ assert(opermatch(PGPRZ, KPTR, dst));
+ W32(0xD63F0000 | dst.reg<<5); /* BLR Xn */
+ }
+}
+DEFINSTR2(Xfmov,
+ {4|8, {PFPR, PFPR}, 0x1E204000, EN_FP2R},
+ {4, {PFPR, PGPRZ}, 0x1E270000, EN_FP1GPR1},
+ { 8, {PFPR, PGPRZ}, 0x9E670000, EN_FP1GPR1},
+ {4, {PGPRZ, PFPR}, 0x1E260000, EN_FP1GPR1},
+ { 8, {PGPRZ, PFPR}, 0x9E660000, EN_FP1GPR1},
+)
+DEFINSTR2(Xfneg, {4|8, {PFPR, PFPR}, 0x1E214000, EN_FP2R})
+DEFINSTR2(Xscvtfw, {4|8, {PFPR, PGPRZ}, 0x1E220000, EN_FP2R})
+DEFINSTR2(Xscvtfx, {4|8, {PFPR, PGPRZ}, 0x9E220000, EN_FP2R})
+DEFINSTR2(Xfcvtzsw, {4|8, {PGPRZ, PFPR}, 0x1E380000, EN_FP2R})
+DEFINSTR2(Xfcvtzsx, {4|8, {PGPRZ, PFPR}, 0x9E380000, EN_FP2R})
+DEFINSTR2(Xucvtfw, {4|8, {PFPR, PGPRZ}, 0x1E230000, EN_FP2R})
+DEFINSTR2(Xucvtfx, {4|8, {PFPR, PGPRZ}, 0x9E230000, EN_FP2R})
+DEFINSTR2(Xfcvtzuw, {4|8, {PGPRZ, PFPR}, 0x1E390000, EN_FP2R})
+DEFINSTR2(Xfcvtzux, {4|8, {PGPRZ, PFPR}, 0x9E390000, EN_FP2R})
+DEFINSTR2(Xfcvtds, {4, {PFPR, PFPR}, 0x1E624000, EN_FP2R})
+DEFINSTR2(Xfcvtsd, {4, {PFPR, PFPR}, 0x1E22C000, EN_FP2R})
+DEFINSTR3(Xfadd, {4|8, {PFPR, PFPR, PFPR}, 0x1E202800, EN_FP3R})
+DEFINSTR3(Xfsub, {4|8, {PFPR, PFPR, PFPR}, 0x1E203800, EN_FP3R})
+DEFINSTR3(Xfmul, {4|8, {PFPR, PFPR, PFPR}, 0x1E200800, EN_FP3R})
+DEFINSTR3(Xfdiv, {4|8, {PFPR, PFPR, PFPR}, 0x1E201800, EN_FP3R})
+DEFINSTR2(Xfcmp,
+ {4|8, {PFPR, PZERO}, 0x1E602008, EN_FPCMPZ},
+ {4|8, {PFPR, PFPR}, 0x1E602000, EN_FPCMP},
+)
+
+static void
+gencopy(uchar **pcode, enum irclass cls, struct block *blk, int curi, struct oper dst, union ref val)
+{
+ assert(dst.t == OREG);
+ struct oper src;
+ if (val.bits == UNDREF.bits) return;
+ if (isintcon(val)) {
+ assert(dst.reg <= R(31));
+ /* MOV r, #imm */
+ uvlong u = intconval(val);
+ if (~u <= 0xFFFF) {
+ /* immediate can be encoded with 1 MOVN instruction */
+ Xmovn(pcode, cls, dst, mkoper(OIMM, .imm = ~u));
+ } else {
+ /* generate MOV (+ MOVKs) */
+ if (cls == KI32) u = (uint)u;
+ int s = 0;
+ while (s < 48 && (u >> s & 0xFFFF) == 0) s += 16;
+ if ((u &~ (0xFFFFull << s)) != 0 && aarch64_logimm(NULL, cls, u)) {
+ /* can be encoded as a logical immediate in 1 instr */
+ Xorr(pcode, cls, dst, REGZR, mkoper(OIMM, .uimm = u));
+ } else {
+ Xmovz(pcode, cls, dst, mkoper(OIMM, .imm = u & (0xFFFFull << s)));
+ for (s += 16; s <= 48; s += 16) {
+ if ((u >> s) & 0xFFFF)
+ Xmovk(pcode, cls, dst, mkoper(OIMM, .imm = u & (0xFFFFull << s)));
+ }
+ }
+ }
+ } else if (opermatch(PGPRZ, cls, (src = ref2oper(val))) && kisint(cls)) {
+ Xorr(pcode, cls, dst, REGZR, src); /* MOV Rd, Rn ==> ORR Rd, zr, Rn */
+ } else if (kisflt(cls) || opermatch(PFPR, 0, src)) {
+ if (src.t == OREG)
+ Xfmov(pcode, cls, dst, src);
+ else if (src.t == OIMM && src.imm == 0)
+ Xfmov(pcode, cls, dst, REGZR);
+ else assert(0);
+ } else if (isaddrcon(val,0) || (val.t == RADDR && isaddrcon(addrtab.p[val.i].base,0))) {
+ if ((ccopt.pic || (contab.p[val.i].flag & SFUNC)) && !(contab.p[val.i].flag & SLOCAL)) {
+ Xadrp(pcode, KPTR, dst, src);
+ Xadd(pcode, KPTR, dst, dst, src);
+ } else {
+ Xadr(pcode, KPTR, dst, src);
+ }
+ } else assert(0);
+}
+
+/* maps blk -> address when resolved; or to linked list of jump displacement
+ * relocations */
+static struct blkaddr {
+ bool resolved;
+ union {
+ uint addr;
+ uint relreloc;
+ };
+} *blkaddr;
+
+enum cc {
+ CCEQ, CCNE, CCCS, CCCC, CCMI, CCPL, CCVS, CCVC,
+ CCHI, CCLS, CCGE, CCLT, CCGT, CCLE, CCAL, CCNV,
+ CCHS = CCCS, CCLO = CCCC,
+};
+
+static void
+Xbcc(uchar **pcode, enum cc cc, struct block *dst)
+{
+ int disp, insaddr = *pcode - objout.textbegin;
+
+ if (blkaddr[dst->id].resolved) {
+ disp = (int)(blkaddr[dst->id].addr - insaddr)/4;
+ assert(disp >= -(1<<18) && disp < (1<<18));
+ } else {
+ disp = blkaddr[dst->id].relreloc;
+ blkaddr[dst->id].relreloc = insaddr;
+ }
+ assert(in_range(cc, 0, 0xF));
+ W32(0x54000000 | (disp & 0x7FFFF)<<5 | cc);
+}
+
+static void
+Xcbcc(uchar **pcode, enum irclass k, uint rt, enum cc cc, struct block *dst)
+{
+ int disp, insaddr = *pcode - objout.textbegin;
+ if (blkaddr[dst->id].resolved) {
+ disp = (int)(blkaddr[dst->id].addr - insaddr)/4;
+ assert(disp >= -(1<<18) && disp < (1<<18));
+ } else {
+ disp = blkaddr[dst->id].relreloc;
+ blkaddr[dst->id].relreloc = insaddr;
+ }
+ assert(in_range(cc, CCEQ, CCNE));
+ assert(in_range(rt, 0, 31));
+ W32(0x34000000 | (uint)(k > KI32)<<31 | cc<<24 | (disp & 0x7FFFF)<<5 | rt);
+}
+
+/* condition code for CMP */
+static const schar icmpop2cc[] = {
+ [Oequ] = CCEQ, [Oneq] = CCNE,
+ [Olth] = CCLT, [Ogth] = CCGT, [Olte] = CCLE, [Ogte] = CCGE,
+ [Oulth] = CCLO, [Ougth] = CCHI, [Oulte] = CCLS, [Ougte] = CCHS,
+}, fcmpop2cc[] = {
+ [Oequ] = CCEQ, [Oneq] = CCNE,
+ [Olth] = CCLO, [Ogth] = CCGT, [Olte] = CCLS, [Ogte] = CCGE,
+};
+
+static void
+emitbranch(uchar **pcode, struct block *blk)
+{
+ enum irclass cbk = 0;
+ struct oper cbopr;
+ enum cc cc = CCAL;
+ assert(blk->s1);
+ if (blk->s2) {
+ /* conditional branch.. */
+ union ref arg = blk->jmp.arg[0];
+ assert(arg.t == RTMP);
+ struct instr *ins = &instrtab[arg.i];
+ if (in_range(ins->op, Oequ, Oneq) && ins->r.bits == ZEROREF.bits) {
+ cc = ins->op == Oequ ? CCEQ : CCNE;
+ cbk = ins->cls;
+ cbopr = ref2oper(ins->l);
+ assert(opermatch(PGPRZ, ins->cls, cbopr));
+ } else if (oiscmp(ins->op)) {
+ /* for CMP instr */
+ cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op];
+ } else {
+ /* implicit by ZF */
+ cc = CCNE;
+ }
+ if (blk->s1 == blk->lnext) {
+ /* if s1 is next adjacent block, swap s1,s2 and flip condition to emit a
+ * single jump */
+ struct block *tmp = blk->s1;
+ blk->s1 = blk->s2;
+ blk->s2 = tmp;
+ cc ^= 1;
+ }
+ }
+ /* make sure to fallthru if jumping to next adjacent block */
+ if (blk->s2 || blk->s1 != blk->lnext) {
+ if (cbk) Xcbcc(pcode, cbk, cbopr.reg, cc, blk->s1);
+ else Xbcc(pcode, cc, blk->s1);
+ }
+ if (blk->s2 && blk->s2 != blk->lnext)
+ Xbcc(pcode, CCAL, blk->s2);
+}
+
+static struct instr *lastcmp;
+
+static void
+emitinstr(uchar **pcode, struct function *fn, struct block *blk, int curi, struct instr *ins)
+{
+ struct oper dst, o1, o2;
+ enum irclass cls = ins->cls;
+ void (*X3)(uchar **, enum irclass, struct oper, struct oper, struct oper) = NULL;
+ void (*X2)(uchar **, enum irclass, struct oper, struct oper) = NULL;
+
+ switch (ins->op) {
+ default: fatal(NULL, "aarch64 unimplemented instr: %s", opnames[ins->op]);
+ case Onop: break;
+ case Omove:
+ dst = ref2oper(ins->l);
+ gencopy(pcode, cls, blk, curi, dst, ins->r);
+ break;
+ case Oextu32: cls = KI32;
+ /* fallthru */
+ case Ocopy:
+ dst = reg2oper(ins->reg-1);
+ gencopy(pcode, cls, blk, curi, dst, ins->l);
+ break;
+ case Oswap:
+ o1 = ref2oper(ins->l), o2 = ref2oper(ins->r);
+ if (kisflt(ins->cls) && ins->l.i != mctarg->fprscratch && ins->r.i != mctarg->fprscratch) {
+ dst = reg2oper(mctarg->fprscratch);
+ Xfmov(pcode, cls, dst, o1);
+ Xfmov(pcode, cls, o1, o2);
+ Xfmov(pcode, cls, o2, dst);
+ } else if (ins->l.i != mctarg->gprscratch && ins->r.i != mctarg->gprscratch) {
+ dst = reg2oper(mctarg->gprscratch);
+ Xorr(pcode, cls, dst, REGZR, o1);
+ Xorr(pcode, cls, o1, REGZR, o2);
+ Xorr(pcode, cls, o2, REGZR, dst);
+ } else {
+ Xeor(pcode, cls, o1, o1, o2);
+ Xeor(pcode, cls, o2, o1, o2);
+ Xeor(pcode, cls, o1, o1, o2);
+ }
+ break;
+ case Onot: /* MVN Rd, Rn ==> ORN Rd, zr, Rn */
+ Xorn(pcode, cls, reg2oper(ins->reg-1), REGZR, ref2oper(ins->l));
+ break;
+ case Oneg:
+ if (kisint(ins->cls)) /* NEG Rd, Rn ==> SUB Rd, zr, Rn */
+ Xsub(pcode, cls, reg2oper(ins->reg-1), REGZR, ref2oper(ins->l));
+ else
+ Xfneg(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l));
+ break;
+ case Oexts8: case Oexts16: case Oexts32: /* SXTB/H/W Rd, Rn ==> SBFM Rd, Rn, #0, #7/15/31 */
+ Xsbfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), 0, (8<<(ins->op-Oexts8)/2)-1);
+ break;
+ case Oextu8: case Oextu16: /* UXTB/H Rd, Rn ==> UBFM Rd, Rn, #0, #7/15 */
+ Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), 0, (8<<(ins->op-Oexts8)/2)-1);
+ break;
+ case Ocvts32f: X2 = Xscvtfw; goto Cvt;
+ case Ocvts64f: X2 = Xscvtfx; goto Cvt;
+ case Ocvtf32s:
+ X2 = cls == KI32 ? Xfcvtzsw : Xfcvtzsx;
+ cls = KF32;
+ goto Cvt;
+ case Ocvtf64s:
+ X2 = cls == KI32 ? Xfcvtzsw : Xfcvtzsx;
+ cls = KF64;
+ goto Cvt;
+ case Ocvtu32f: X2 = Xucvtfw; goto Cvt;
+ case Ocvtu64f: X2 = Xucvtfx; goto Cvt;
+ case Ocvtf32u:
+ X2 = cls == KI32 ? Xfcvtzuw : Xfcvtzux;
+ cls = KF32;
+ goto Cvt;
+ case Ocvtf64u:
+ X2 = cls == KI32 ? Xfcvtzuw : Xfcvtzux;
+ cls = KF64;
+ goto Cvt;
+ case Ocvtf32f64: cls = KF32; X2 = Xfcvtsd; goto Cvt;
+ case Ocvtf64f32: cls = KF32; X2 = Xfcvtds; goto Cvt;
+ Cvt:
+ X2(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l));
+ break;
+ case Oadd: X3 = kisint(cls) ? Xadd : Xfadd; goto ALU3;
+ case Osub: X3 = kisint(cls) ? Xsub : Xfsub; goto ALU3;
+ case Omul: if (kisflt(cls)) { X3 = Xfmul; goto ALU3; }
+ /* MUL Rd,Rn,Rm ==> MADD Rd,Rn,Rm,zr */
+ Xmadd(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r), REGZR);
+ break;
+ case Odiv: X3 = kisint(cls) ? Xsdiv : Xfdiv; goto ALU3;
+ case Oudiv: X3 = Xudiv; goto ALU3;
+ case Oand: X3 = Xand; goto ALU3;
+ case Oior: X3 = Xorr; goto ALU3;
+ case Oxor: X3 = Xeor; goto ALU3;
+ ALU3:
+ X3(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r));
+ break;
+ case Oshl:
+ if (ins->r.t == RICON) {
+ uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1);
+ assert(s > 0);
+ Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), nbit-s, nbit-s-1);
+ } else {
+ X3 = Xlslv;
+ goto ALU3;
+ }
+ break;
+ case Oslr:
+ if (ins->r.t == RICON) {
+ uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1);
+ assert(s > 0);
+ Xubfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), s, nbit-1);
+ } else {
+ X3 = Xlsrv;
+ goto ALU3;
+ }
+ break;
+ case Osar:
+ if (ins->r.t == RICON) {
+ uint nbit = cls == KI32 ? 32 : 64, s = ins->r.i & (nbit-1);
+ assert(s > 0);
+ Xsbfm(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), s, nbit-1);
+ } else {
+ X3 = Xasrv;
+ goto ALU3;
+ }
+ break;
+ case Oequ: case Oneq:
+ if (!ins->reg && kisint(cls) && ins->r.bits == ZEROREF.bits) /* handled by emitbranch for CBZ/CBNZ */
+ break;
+ case Olth: case Ogth: case Olte: case Ogte:
+ case Oulth: case Ougth: case Oulte: case Ougte:
+ if (lastcmp && lastcmp->cls == cls
+ && lastcmp->l.bits == ins->l.bits && lastcmp->r.bits == ins->r.bits)
+ /* reuse flags from previous identical cmp */ ;
+ else if (kisflt(cls))
+ Xfcmp(pcode, cls, ref2oper(ins->l), ref2oper(ins->r));
+ else /* CMP ... ==> SUBS zr, ... */
+ Xsubs(pcode, cls, REGZR, ref2oper(ins->l), ref2oper(ins->r));
+ lastcmp = ins;
+ if (ins->reg) {
+ enum cc cc = (kisflt(cls) ? fcmpop2cc : icmpop2cc)[ins->op];
+ dst = reg2oper(ins->reg-1);
+ assert(dst.reg < R(31));
+ W32(0x1A9F07E0 | (cc^1)<<12 | dst.reg); /* CSET Wd, <invcond> */
+ }
+ break;
+ case Oloadu8: X2 = Xldrb; goto Load;
+ case Oloads8: X2 = Xldrsb; goto Load;
+ case Oloadu16: X2 = Xldrh; goto Load;
+ case Oloads16: X2 = Xldrsh; goto Load;
+ case Oloads32:
+ if (cls != KI32) {
+ X2 = Xldrsw;
+ goto Load;
+ }
+ case Oloadu32:
+ cls = KI32;
+ /* fallthru */
+ case Oloadi64: X2 = Xldr;
+ Load:
+ X2(pcode, cls, reg2oper(ins->reg-1), mkmemoper(1<<(ins->op - Oloads8)/2, ins->l));
+ break;
+ case Oloadf32: case Oloadf64:
+ Xfldr(pcode, cls, reg2oper(ins->reg-1), mkmemoper(ins->op == Oloadf32 ? 4 : 8, ins->l));
+ break;
+ case Ostorei8: cls = KI32; X2 = Xstrb; goto Store;
+ case Ostorei16: cls = KI32; X2 = Xstrh; goto Store;
+ case Ostorei32: cls = KI32; X2 = Xstr; goto Store;
+ case Ostorei64: cls = KI64; X2 = Xstr;
+ Store:
+ X2(pcode, cls, ins->r.bits == ZEROREF.bits ? REGZR : ref2oper(ins->r),
+ mkmemoper(1<<(ins->op-Ostorei8), ins->l));
+ break;
+ case Ostoref32: case Ostoref64:
+ Xfstr(pcode, KF32 + ins->op-Ostoref32, ref2oper(ins->r), mkmemoper(ins->op == Oloadf32 ? 4 : 8, ins->l));
+ break;
+ case Ocall:
+ Xcall(pcode, ref2oper(ins->l));
+ break;
+ }
+}
+
+struct frame {
+ regset save;
+ struct rpair { uchar a,b; } pairs[10];
+ uchar single[2];
+ uint nfpairs, ngpairs;
+};
+
+static void
+prologue(uchar **pcode, struct frame *frame, struct function *fn)
+{
+ *frame = (struct frame){0};
+ regset save = frame->save = (fn->regusage & mctarg->rcallee) | (usefp * BIT(FP)) | (!fn->isleaf * BIT(LR));
+ if (save) {
+ int prev = 0;
+ struct rpair *p = frame->pairs;
+ for (uint reg = V(8); reg <= V(15); ++reg) {
+ if (!rstest(save, reg)) continue;
+ if (prev) {
+ *p++ = (struct rpair) {prev, reg};
+ ++frame->nfpairs;
+ prev = 0;
+ } else prev = reg;
+ }
+ uint ngpr = popcnt(save & (BIT(32)-1));
+ if (prev) {
+ if (ngpr & 1) {
+ frame->single[0] = prev;
+ frame->single[1] = prev = lowestsetbit(save);
+ rsclr(&save, prev);
+ } else {
+ *p++ = (struct rpair) {prev, V(0)};
+ ++frame->nfpairs;
+ }
+ prev = 0;
+ } else if (ngpr & 1) {
+ prev = 0x100;
+ }
+ for (uint reg = R(19); reg <= LR; ++reg) {
+ if (!rstest(save, reg)) continue;
+ if (prev) {
+ *p++ = (struct rpair) {prev, reg};
+ ++frame->ngpairs;
+ prev = 0;
+ } else prev = reg;
+ }
+ assert(!prev);
+
+ p = frame->pairs;
+ struct oper adr = mkoper(OMEM, .m = {.mode = APREIDX, .base = SP, .disp = -16});
+ for (int i = 0; i < frame->nfpairs; ++i, ++p)
+ Xfstp(pcode, KF64, reg2oper(p->a), reg2oper(p->b), adr);
+ adr.m.disp = -8;
+ if (frame->single[0]) Xfstr(pcode, KF64, reg2oper(frame->single[0]), adr);
+ if (frame->single[1]) Xstr(pcode, KPTR, reg2oper(frame->single[1]), adr);
+ adr.m.disp = -16;
+ for (int i = 0; i < frame->ngpairs; ++i, ++p)
+ Xstp(pcode, KPTR, reg2oper(p->a), reg2oper(p->b), adr);
+ }
+
+ if (usefp) /* MOV x29, sp */
+ Xadd(pcode, KPTR, reg2oper(FP), reg2oper(SP), mkoper(OIMM,));
+
+ /* ensure stack is 16-byte aligned for function calls */
+ if (!fn->isleaf && ((fn->stksiz) & 0xF) != 0) {
+ assert(usefp);
+ rbpoff -= 8;
+ fn->stksiz += 8;
+ }
+ if (fn->stksiz) Xsub(pcode, KPTR, reg2oper(SP), reg2oper(SP), mkoper(OIMM, .imm = fn->stksiz));
+}
+
+static void
+epilogue(uchar **pcode, struct function *fn, struct frame *frame)
+{
+ if (fn->stksiz) Xadd(pcode, KPTR, reg2oper(SP), reg2oper(SP), mkoper(OIMM, .imm = fn->stksiz));
+ if (frame->save) {
+ struct rpair *p = frame->pairs + frame->nfpairs + frame->ngpairs - 1;
+ struct oper adr = mkoper(OMEM, .m = {.mode = APOSTIDX, .base = SP, .disp = 16});
+ for (int i = 0; i < frame->ngpairs; ++i, --p)
+ Xldp(pcode, KPTR, reg2oper(p->a), reg2oper(p->b), adr);
+ adr.m.disp = 8;
+ if (frame->single[1]) Xldr(pcode, KPTR, reg2oper(frame->single[1]), adr);
+ if (frame->single[0]) Xfldr(pcode, KF64, reg2oper(frame->single[0]), adr);
+ adr.m.disp = 16;
+ for (int i = 0; i < frame->nfpairs; ++i, --p)
+ Xfldp(pcode, KF64, reg2oper(p->a), reg2oper(p->b), adr);
+ }
+}
+
+static void
+emitbin(struct function *fn)
+{
+ struct block *blk;
+ uchar **pcode = &objout.code;
+
+ while ((*pcode - objout.textbegin) % 4) ++*pcode;
+ fnstart = *pcode;
+ curfnsym = fn->name;
+
+ /** prologue **/
+
+ /* only use frame pointer in non-leaf functions and functions that use the stack */
+ usefp = !fn->isleaf || fn->stksiz;
+ struct frame frame;
+ prologue(pcode, &frame, fn);
+
+ if (*pcode - fnstart > 8) {
+ /* largue prologue -> largue epilogue -> transform to use single exit point */
+ struct block *exit = NULL;
+ blk = fn->entry->lprev;
+ do {
+ if (blk->jmp.t == Jret) {
+ if (!exit) {
+ if (blk->ins.n == 0) {
+ exit = blk;
+ continue;
+ } else {
+ exit = newblk(fn);
+ exit->lnext = blk->lnext;
+ exit->lprev = blk;
+ blk->lnext = exit;
+ exit->lnext->lprev = exit;
+ exit->id = fn->nblk++;
+ exit->jmp.t = Jret;
+ }
+ }
+ blk->jmp.t = Jb;
+ memset(blk->jmp.arg, 0, sizeof blk->jmp.arg);
+ blk->s1 = exit;
+ } else if (exit) {
+ /* thread jumps to the exit block */
+ if (blk->s1 && !blk->s1->ins.n && blk->s1->s1 == exit && !blk->s1->s2) blk->s1 = exit;
+ if (blk->s2 && !blk->s2->ins.n && blk->s2->s1 == exit && !blk->s2->s2) blk->s2 = exit;
+ }
+ } while ((blk = blk->lprev) != fn->entry);
+ }
+
+ blkaddr = allocz(fn->passarena, fn->nblk * sizeof *blkaddr, 0);
+
+ blk = fn->entry;
+ do {
+ struct blkaddr *bb = &blkaddr[blk->id];
+ uint bbaddr = *pcode - objout.textbegin;
+ assert(!bb->resolved);
+ while (bb->relreloc) {
+ int disp = (bbaddr - bb->relreloc)/4;
+ assert(disp >= -(1<<18) && disp < (1<<18));
+ uint tmp = rd32targ(objout.textbegin + bb->relreloc);
+ wr32le(objout.textbegin + bb->relreloc, (tmp &~ (0x7FFFFu<<5)) | (disp & 0x7FFFF)<<5);
+ bb->relreloc = tmp>>5 & 0x7FFFF;
+ }
+ bb->resolved = 1;
+ bb->addr = bbaddr;
+
+ lastcmp = NULL;
+ for (int i = 0; i < blk->ins.n; ++i)
+ emitinstr(pcode, fn, blk, i, &instrtab[blk->ins.p[i]]);
+ if (blk->jmp.t == Jret) {
+ if (blk->lnext != fn->entry && blk->lnext->jmp.t == Jret && blk->lnext->ins.n == 0)
+ continue; /* fallthru to next blk's RET */
+ epilogue(pcode, fn, &frame);
+ W32(0xD65F03C0); /* RET */
+ } else if (blk->jmp.t == Jtrap) {
+ W32(0xD4200020); /* BRK #0x1 */
+ } else emitbranch(pcode, blk);
+ } while ((blk = blk->lnext) != fn->entry);
+ objdeffunc(fn->name, fn->globl, fnstart - objout.textbegin, *pcode - fnstart);
+}
+
+void
+aarch64_emit(struct function *fn)
+{
+ fn->stksiz = alignup(fn->stksiz, 8);
+ if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name);
+ emitbin(fn);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_aarch64_isel.c b/src/t_aarch64_isel.c
new file mode 100644
index 0000000..398ea28
--- /dev/null
+++ b/src/t_aarch64_isel.c
@@ -0,0 +1,515 @@
+#include "all.h"
+
+#define isimm32(r) (iscon(r) && concls(r) == KI32)
+
+static inline uint
+clz(uvlong x)
+{
+#if HAS_BUILTIN(clzll)
+ return __builtin_clzll(x);
+#else
+ int i = 0;
+ for (uvlong mask = BIT(63);; ++i, mask >>= 1)
+ if (x & mask)
+ break;
+ return i;
+#endif
+}
+
+/* Encode logical immediate */
+bool
+aarch64_logimm(uint *enc, enum irclass k, uvlong x)
+{
+ /* https://github.com/v8/v8/blob/927ccc6076e25a614787c7011315468e40fe39a4/src/codegen/arm64/assembler-arm64.cc#L4409 */
+ if (k == KI32) x = (uint)x | x << 32;
+ bool neg;
+ if ((neg = x & 1)) x = ~x;
+ if (x == 0) return 0;
+ uvlong a = x & (~x + 1),
+ xa = x + a,
+ b = xa & (~xa + 1),
+ xa_b = xa - b,
+ c = xa_b & (~xa_b + 1),
+ mask;
+ uint clza = clz(a),
+ d, outn;
+ if (c != 0) {
+ d = clza - clz(c);
+ mask = BIT(d) - 1;
+ outn = 0;
+ } else {
+ assert(a != 0);
+ d = 64;
+ mask = ~0ull;
+ outn = 1;
+ }
+ if (!ispo2(d)) return 0;
+ if (((b - a) & ~mask) != 0) return 0;
+ static const uvlong M[] = {
+ 0x0000000000000001, 0x0000000100000001, 0x0001000100010001,
+ 0x0101010101010101, 0x1111111111111111, 0x5555555555555555,
+ };
+ int i = clz(d) - 57;
+ assert((uint)i < countof(M));
+ uvlong m = M[i];
+ uvlong y = (b - a) * m;
+ if (y != x) return 0;
+ if (enc) {
+ int clzb = b == 0 ? -1 : clz(b),
+ s = clza - clzb, r;
+ if (neg) {
+ s = d - s;
+ r = (clzb + 1) & (d - 1);
+ } else {
+ r = (clza + 1) & (d - 1);
+ }
+ *enc = outn<<12 | r<<6 | (((-d * 2) | (s - 1)) & 0x3F);
+ }
+ return 1;
+}
+
+
+static void fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi);
+static void
+regarg(union ref *r, enum irclass k, struct block *blk, int *curi)
+{
+ if (r->t != RTMP) {
+ *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, *r));
+ if (kisflt(k) || instrtab[r->i].l.t == RSTACK) {
+ int iprev = *curi-1;
+ fixarg(&instrtab[r->i].l, &instrtab[r->i], blk, &iprev);
+ *curi = iprev+1;
+ }
+ }
+}
+
+static void
+fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi)
+{
+ enum op op = ins ? ins->op : 0;
+ if (isintcon(*r)) {
+ vlong x = intconval(*r);
+ switch (op) {
+ case Ocopy: return;
+ default:
+ if (oiscmp(op)) {
+ case Oadd: case Osub:
+ /* imm12 (lsl 12) */
+ if ((x &~ 0xFFF) == 0 || (x &~ 0xFFF000) == 0) return;
+ break;
+ case Oshl: case Osar: case Oslr:
+ if ((uvlong)x < (ins->cls == KI32 ? 32 : 64)) return;
+ break;
+ case Oand: case Oior: case Oxor:
+ if (aarch64_logimm(NULL, ins->cls, x)) return;
+ break;
+ }
+ }
+ goto Reg;
+ } else if (isfltcon(*r)) {
+ enum irclass k = concls(*r), ki = KI32 + k-KF32;
+ if (contab.p[r->i].f != 0.0) {
+ union {
+ vlong i64;
+ int i32;
+ float f32;
+ double f64;
+ } pun;
+ vlong i;
+ if (k == KF32) {
+ pun.f32 = contab.p[r->i].f;
+ i = pun.i32;
+ } else {
+ pun.f64 = contab.p[r->i].f;
+ i = pun.i64;
+ }
+ union ref gpr = insertinstr(blk, (*curi)++, mkinstr(Ocopy, ki, mkintcon(ki, i)));
+ *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, gpr));
+ } else if (oiscmp(op)) {
+ return;
+ } else {
+ *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, k, *r));
+ }
+ } else if (r->t == RSTACK) {
+ struct instr adr = mkinstr(Osub, KPTR, mkref(RREG, FP), mkintcon(KI32, r->i));
+ if (op == Ocopy)
+ *ins = adr;
+ else
+ *r = insertinstr(blk, (*curi)++, adr);
+ } else if (r->t != RTMP) Reg: {
+ regarg(r, r->t == RTMP ? instrtab[r->i].cls : ins->cls ? ins->cls : KI32, blk, curi);
+ }
+}
+
+static bool
+arithfold(struct instr *ins)
+{
+ if (isnumcon(ins->l) && (!ins->r.t || isnumcon(ins->r))) {
+ union ref r;
+ bool ok = ins->r.t ? foldbinop(&r, ins->op, ins->cls, ins->l, ins->r) : foldunop(&r, ins->op, ins->cls, ins->l);
+ assert(ok && "fold?");
+ *ins = mkinstr(Ocopy, insrescls(*ins), r);
+ return 1;
+ }
+ return 0;
+}
+
+static void
+selcall(struct function *fn, struct instr *ins, struct block *blk, int *curi)
+{
+ const struct call *call = &calltab.p[ins->r.i];
+ int iarg = *curi - 1;
+ enum irclass cls;
+ uint argstksiz = alignup(call->argstksiz, 16);
+
+ for (int i = call->narg - 1; i >= 0; --i) {
+ struct abiarg abi = call->abiarg[i];
+ struct instr *arg;
+ for (;; --iarg) {
+ assert(iarg >= 0 && i >= 0 && "arg?");
+ if ((arg = &instrtab[blk->ins.p[iarg]])->op == Oarg)
+ break;
+ }
+
+ if (!abi.isstk) {
+ assert(!abi.ty.isagg);
+ *arg = mkinstr(Omove, call->abiarg[i].ty.cls, mkref(RREG, abi.reg), arg->r);
+ } else {
+ union ref adr = mkaddr((struct addr){mkref(RREG, SP), .disp = abi.stk});
+ int iargsave = iarg;
+ if (!abi.ty.isagg) { /* scalar arg in stack */
+ *arg = mkinstr(cls2store[abi.ty.cls], 0, adr, arg->r);
+ if (isaddrcon(arg->r,1) || arg->r.t == RADDR)
+ arg->r = insertinstr(blk, iarg++, mkinstr(Ocopy, abi.ty.cls, arg->r));
+ else
+ fixarg(&ins->r, ins, blk, &iarg);
+ } else { /* aggregate arg in stack, callee stack frame destination address */
+ *arg = mkinstr(Ocopy, KPTR, adr);
+ }
+ *curi += iarg - iargsave;
+ }
+ }
+ if (call->argstksiz) {
+ union ref disp = mkref(RICON, argstksiz);
+ insertinstr(blk, iarg--, (struct instr){Osub, KPTR, .keep=1, .reg = SP+1, .l=mkref(RREG,SP), disp});
+ ++*curi;
+ insertinstr(blk, *curi+1, (struct instr){Oadd, KPTR, .keep=1, .reg = SP+1, .l=mkref(RREG,SP), disp});
+ }
+ if (isimm32(ins->l))
+ ins->l = mkaddr((struct addr){.base = ins->l});
+ else if (isintcon(ins->l))
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, ins->l));
+
+ cls = ins->cls;
+ ins->cls = 0;
+ if (cls) {
+ /* duplicate to reuse same TMP ref */
+ insertinstr(blk, (*curi)++, *ins);
+ *ins = mkinstr(Ocopy, cls, mkref(RREG, call->abiret[0].reg));
+ for (int i = 1; i <= 2; ++i) {
+ if (*curi + i >= blk->ins.n) break;
+ if (instrtab[blk->ins.p[*curi + i]].op == Ocall2r) {
+ ins = &instrtab[blk->ins.p[*curi += i]];
+ *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, call->abiret[1].reg));
+ break;
+ }
+ }
+ }
+}
+
+static bool
+aimm(struct addr *addr, int disp)
+{
+ if (addr->index.bits) return 0;
+ vlong a = addr->disp;
+ a += disp;
+ if ((int)a == a) {
+ addr->disp = a;
+ return 1;
+ }
+ return 0;
+}
+
+static bool
+ascale(struct addr *addr, union ref a, union ref b, uint siz/*1,2,4,8*/)
+{
+ if (b.t != RICON) return 0;
+ if (addr->index.bits || (addr->disp && !isaddrcon(addr->base,1))) return 0;
+ if ((unsigned)b.i > 3 || 1<<b.i != siz) return 0;
+ if (a.t == RREG || a.t == RTMP) {
+ addr->index = a;
+ addr->shift = b.i;
+ return 1;
+ }
+ return 0;
+}
+
+static bool
+aadd(struct addr *addr, struct block *blk, int *curi, union ref r, uint siz/*1,2,4,8*/)
+{
+ if (r.t == RSTACK) {
+ if (addr->base.bits || addr->index.bits || !aimm(addr, -r.i)) goto Ref;
+ addr->base = mkref(RREG, FP);
+ } else if (r.t == RTMP) {
+ struct instr *ins = &instrtab[r.i];
+ if (ins->op == Oadd) {
+ if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref;
+ if (!aadd(addr, blk, curi, ins->r, siz)) goto Ref;
+ ins->skip = 1;
+ } else if (ins->op == Osub) {
+ if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref;
+ if (!isintcon(ins->r)) goto Ref;
+ if (!aimm(addr, -intconval(ins->r))) goto Ref;
+ ins->skip = 1;
+ } else if (ins->op == Oshl) {
+ if (!ascale(addr, ins->l, ins->r, siz)) goto Ref;
+ ins->skip = 1;
+ } else if (ins->op == Ocopy) {
+ if (!aadd(addr, blk, curi, ins->l, siz)) goto Ref;
+ ins->skip = 1;
+ } else goto Ref;
+ } else if (isnumcon(r)) {
+ assert(isintcon(r));
+ return aimm(addr, intconval(r));
+ } else if (isaddrcon(r,1)) {
+ if (!addr->base.bits && !isaddrcon(addr->index,1)) addr->base = r;
+ else return 0;
+ } else if (r.t == RREG) {
+ /* temporaries are single assignment, but register aren't, so they can't be *
+ * safely hoisted into an address value, unless they have global lifetime */
+ if (!rstest(mctarg->rglob, r.i)) return 0;
+ Ref:
+ if (r.t == RSTACK && (addr->base.bits || addr->index.bits)) {
+ r = insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, mkref(RREG, FP), mkref(RICON, -r.i)));
+ }
+ if (!addr->base.bits) addr->base = r;
+ else if (!addr->index.bits) addr->index = r;
+ else return 0;
+ } else return 0;
+ return 1;
+}
+
+static bool
+fuseaddr(union ref *r, struct block *blk, int *curi, uint siz/*1,2,4,8*/)
+{
+ struct addr addr = {0};
+
+ if (isaddrcon(*r,1)) return 1;
+
+ if (r->t != RSTACK && r->t != RTMP) return 0;
+ if (!aadd(&addr, blk, curi, *r, siz)) return 0;
+ if (!(addr.disp >= -256 && addr.disp < 256) /* for 9-bit signed unscaled offset */
+ && !(!(addr.disp & (siz-1)) && (uvlong)addr.disp < (1<<12)*siz)) /* 12-bit unsigned scaled offset */
+ return 0;
+ if (isaddrcon(addr.base,0) && (!(contab.p[addr.base.i].flag & SLOCAL) || addr.index.bits)) {
+ /* first load symbol address into a temp register */
+ if (addr.disp && (ccopt.pic || (contab.p[addr.base.i].flag & SFUNC)) && !addr.index.bits) {
+ addr.base = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, .l = addr.base));
+ } else {
+ addr.base = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR,
+ mkaddr((struct addr){addr.base, .disp = addr.disp})));
+ addr.disp = 0;
+ }
+ }
+ *r = mkaddr(addr);
+ return 1;
+}
+
+static const uchar loadsz[] = {
+ [Oloads8 - Oloads8] = 1, [Oloadu8 - Oloads8] = 1,
+ [Oloads16 - Oloads8] = 2, [Oloadu16 - Oloads8] = 2,
+ [Oloads32 - Oloads8] = 4, [Oloadu32 - Oloads8] = 4,
+ [Oloadi64 - Oloads8] = 8,
+ [Oloadf32 - Oloads8] = 4,
+ [Oloadf64 - Oloads8] = 8,
+};
+static const uchar storesz[] = {
+ [Ostorei8 - Ostorei8] = 1,
+ [Ostorei16 - Ostorei8] = 2,
+ [Ostorei32 - Ostorei8] = 4,
+ [Ostorei64 - Ostorei8] = 8,
+ [Ostoref32 - Ostorei8] = 4,
+ [Ostoref64 - Ostorei8] = 8,
+};
+static void
+loadstoreaddr(struct block *blk, union ref *r, int *curi, enum op op)
+{
+ uint siz = oisload(op) ? loadsz[op-Oloads8] : storesz[op-Ostorei8];
+ if (isimm32(*r)) {
+ *r = mkaddr((struct addr){.base = *r});
+ } else if (isaddrcon(*r, 0)) {
+ bool pcrelok = in_range(op, Oloads32, Oloadi64); /* LDR-LDRSW have PC-relative literal form */
+ if (!pcrelok || !(contab.p[r->i].flag & SLOCAL))
+ regarg(r, KPTR, blk, curi);
+ } else if (r->t == RTMP || r->t == RSTACK) {
+ fuseaddr(r, blk, curi, siz);
+ } else if (r->t != RREG) {
+ *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, *r));
+ }
+}
+
+static void
+sel(struct function *fn, struct instr *ins, struct block *blk, int *curi)
+{
+ enum op op = ins->op;
+ enum irclass cls;
+
+ if (oisarith(ins->op) && arithfold(ins)) {
+ fixarg(&ins->l, ins, blk, curi);
+ return;
+ }
+
+ switch (op) {
+ //default: assert(0);
+ case Onop: break;
+ case Oalloca1: case Oalloca2: case Oalloca4: case Oalloca8: case Oalloca16:
+ assert(!"unlowered alloca");
+ break;
+ case Ocopy:
+ fixarg(&ins->l, ins, blk, curi);
+ break;
+ case Oparam:
+ assert(ins->l.t == RICON && ins->l.i < fn->nabiarg);
+ if (!fn->abiarg[ins->l.i].isstk)
+ *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, fn->abiarg[ins->l.i].reg));
+ else /* stack */
+ *ins = mkinstr(Oadd, KPTR, mkref(RREG, FP), mkref(RICON, 16+fn->abiarg[ins->l.i].stk));
+ break;
+ case Oneg: case Onot:
+ case Ocvtf32s: case Ocvtf32u:
+ case Ocvtf32f64: case Ocvtf64s:
+ case Ocvtf64u: case Ocvtf64f32:
+ case Ocvts32f: case Ocvtu32f:
+ case Ocvts64f: case Ocvtu64f:
+ case Oexts8: case Oextu8:
+ case Oexts16: case Oextu16:
+ case Oexts32:
+ regarg(&ins->l, ins->cls, blk, curi);
+ break;
+ case Oextu32:
+ regarg(&ins->l, ins->cls, blk, curi);
+ ins->op = Ocopy;
+ break;
+ case Oadd:
+ if (isnumcon(ins->l)) {
+ /* swap to have const in rhs */
+ union ref tmp = ins->l;
+ ins->l = ins->r;
+ ins->r = tmp;
+ }
+ case Osub:
+ if (ins->r.t == RICON && ins->r.i < 0) {
+ op = ins->op ^= 1;
+ ins->r.i = -ins->r.i;
+ }
+ if (!(isaddrcon(ins->l,0) && (contab.p[ins->l.i].flag & SLOCAL)))
+ regarg(&ins->l, ins->cls, blk, curi);
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Oand: case Oior: case Oxor:
+ if (isnumcon(ins->l)) {
+ /* swap to have const in rhs */
+ union ref tmp = ins->l;
+ ins->l = ins->r;
+ ins->r = tmp;
+ }
+ case Oshl: case Osar: case Oslr:
+ case Oequ: case Oneq:
+ case Olth: case Ogth: case Olte: case Ogte:
+ case Oulth: case Ougth: case Oulte: case Ougte:
+ case Omove:
+ regarg(&ins->l, ins->cls, blk, curi);
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Omul: case Odiv: case Oudiv: case Ourem:
+ regarg(&ins->l, ins->cls, blk, curi);
+ regarg(&ins->r, ins->cls, blk, curi);
+ break;
+ case Oarg:
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Ocall:
+ selcall(fn, ins, blk, curi);
+ break;
+ case Oloads8: case Oloadu8: case Oloads16: case Oloadu16:
+ case Oloads32: case Oloadu32: case Oloadi64: case Oloadf32: case Oloadf64:
+ loadstoreaddr(blk, &ins->l, curi, op);
+ break;
+ case Ostorei8: case Ostorei16: case Ostorei32: cls = KI32; goto Store;
+ case Ostorei64: cls = KI64; goto Store;
+ case Ostoref32: cls = KF32; goto Store;
+ case Ostoref64: cls = KF64; Store:
+ loadstoreaddr(blk, &ins->l, curi, op);
+ regarg(&ins->r, cls, blk, curi);
+ break;
+ }
+}
+
+static void
+seljmp(struct function *fn, struct block *blk)
+{
+ if (blk->jmp.t == Jb && blk->jmp.arg[0].bits) {
+ int curi = blk->ins.n;
+ fixarg(&blk->jmp.arg[0], NULL, blk, &curi);
+ union ref c = blk->jmp.arg[0];
+ if (c.t != RTMP) {
+ enum irclass cls = c.t == RICON ? KI32 : c.t == RXCON && contab.p[c.i].cls ? contab.p[c.i].cls : KPTR;
+ int curi = blk->ins.n;
+
+ c = insertinstr(blk, blk->ins.n, mkinstr(Ocopy, cls, c));
+ sel(fn, &instrtab[c.i], blk, &curi);
+ }
+ if (!oiscmp(instrtab[c.i].op)) {
+ enum irclass k = insrescls(instrtab[c.i]);
+ blk->jmp.arg[0] = insertinstr(blk, blk->ins.n, mkinstr(Oneq, k, c, kisint(k) ? ZEROREF : mkfltcon(k, 0)));
+ struct instr *ins = &instrtab[blk->jmp.arg[0].i];
+ ins->keep = 1;
+ } else {
+ instrtab[c.i].keep = 1;
+ }
+ } else if (blk->jmp.t == Jret) {
+ if (blk->jmp.arg[0].bits) {
+ union ref r = mkref(RREG, fn->abiret[0].reg);
+ struct instr *ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[0].ty.cls, r, blk->jmp.arg[0])).i];
+ int curi = blk->ins.n-1;
+ fixarg(&ins->r, ins, blk, &curi);
+ blk->jmp.arg[0] = r;
+ if (blk->jmp.arg[1].bits) {
+ r = mkref(RREG, fn->abiret[1].reg);
+ ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[1].ty.cls, r, blk->jmp.arg[1])).i];
+ }
+ }
+ }
+}
+
+void
+aarch64_isel(struct function *fn)
+{
+ struct block *blk = fn->entry;
+
+ do {
+ int i;
+ for (i = 0; i < blk->phi.n; ++i) {
+ struct instr *ins = &instrtab[blk->phi.p[i]];
+ union ref *phi = phitab.p[ins->l.i];
+ for (int i = 0; i < blk->npred; ++i) {
+ int curi = blkpred(blk, i)->ins.n;
+ fixarg(&phi[i], ins, blkpred(blk, i), &curi);
+ }
+ }
+ for (i = 0; i < blk->ins.n; ++i) {
+ struct instr *ins = &instrtab[blk->ins.p[i]];
+ sel(fn, ins, blk, &i);
+ }
+ seljmp(fn, blk);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (ccopt.dbg.i) {
+ bfmt(ccopt.dbgout, "<< After isel >>\n");
+ irdump(fn);
+ }
+
+ fn->prop = 0;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_x86-64.h b/src/t_x86-64.h
new file mode 100644
index 0000000..c0c38ff
--- /dev/null
+++ b/src/t_x86-64.h
@@ -0,0 +1,18 @@
+#include "../ir/ir.h"
+
+#define LIST_REGS(_) \
+ _(RAX) _(RCX) _(RDX) _(RBX) _(RSP) _(RBP) _(RSI) _(RDI) \
+ _(R8) _(R9) _(R10) _(R11) _(R12) _(R13) _(R14) _(R15) \
+ _(XMM0) _(XMM1) _(XMM2) _(XMM3) _(XMM4) _(XMM5) _(XMM6) _(XMM7) \
+ _(XMM8) _(XMM9) _(XMM10) _(XMM11) _(XMM12) _(XMM13) _(XMM14) _(XMM15)
+
+enum reg {
+#define R(r) r,
+ LIST_REGS(R)
+#undef R
+};
+
+void x86_64_isel(struct function *);
+void x86_64_emit(struct function *);
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_x86-64_emit.c b/src/t_x86-64_emit.c
new file mode 100644
index 0000000..d3a466b
--- /dev/null
+++ b/src/t_x86-64_emit.c
@@ -0,0 +1,1422 @@
+#include "all.h"
+#include "../obj/obj.h"
+#include "../endian.h"
+
+/** Instruction operands **
+ *
+ * Can be a register, a 32-bit immediate,
+ * a memory reference [base + index * scale + disp],
+ * or a relocatable reference to some symbol plus a displacement and maybe index*scale
+ */
+enum operkind { ONONE, OREG, OIMM, OMEM, OSYM, OSYMGOT };
+enum { NOBASE = 63, NOINDEX = 63 };
+struct oper {
+ uchar t;
+ union {
+ struct { uchar base; }; /* OMEM */
+ struct { uchar cindex : 6, cshift : 2; }; /* OSYM */
+ };
+ union {
+ struct { uchar index, shift; }; /* OMEM */
+ ushort con; /* OSYM */
+ };
+ union {
+ uchar reg; /* OREG */
+ int disp; /* OMEM, OSYM */
+ int imm; /* OIMM */
+ };
+};
+#define mkoper(t, ...) ((struct oper){(t), __VA_ARGS__})
+#define reg2oper(R) (assert((uint)(R) <= XMM15), mkoper(OREG, .reg = (R)))
+
+static struct oper mkmemoper(union ref);
+
+static struct oper
+ioper(int i)
+{
+ int reg = instrtab[i].reg - 1;
+ return reg < 0 ? mkoper(ONONE,) : reg2oper(reg);
+}
+
+static struct oper
+ref2oper(union ref r)
+{
+ switch (r.t) {
+ case RTMP: return ioper(r.i);
+ case RREG: return reg2oper(r.i);
+ case RICON: return mkoper(OIMM, .imm = r.i);
+ case RXCON:
+ if (contab.p[r.i].cls == KI32)
+ return mkoper(OIMM, .imm = contab.p[r.i].i);
+ else if (contab.p[r.i].cls == KI64) {
+ vlong i = contab.p[r.i].i;
+ assert(i == (int)i);
+ return mkoper(OIMM, .imm = i);
+ } else if (!contab.p[r.i].cls) {
+ return mkoper(OSYM, .con = r.i, .cindex = NOINDEX);
+ }
+ assert(0);
+ case RADDR: return mkmemoper(r);
+ default: assert(0);
+ }
+}
+
+static void
+addmemoper(struct oper *mem, struct oper add)
+{
+ assert(mem->t == OMEM);
+ if (add.t == OIMM) {
+ mem->disp += add.imm;
+ } else if (add.t == OREG) {
+ if (mem->base == NOBASE)
+ mem->base = add.reg;
+ else if (mem->index == NOINDEX)
+ mem->index = add.reg;
+ else
+ assert(0);
+ }
+}
+
+/* helpers to convert a reference to an operand of a specific kind,
+ * with assertions to make sure nothing went wrong */
+
+static inline struct oper
+mkregoper(union ref r)
+{
+ assert(r.t == RREG || (r.t == RTMP && ioper(r.i).t == OREG));
+ return r.t == RREG ? reg2oper(r.i) : ioper(r.i);
+}
+
+static inline struct oper
+mkimmoper(union ref r)
+{
+ assert(iscon(r) && concls(r) == KI32);
+ return mkoper(OIMM, .imm = intconval(r));
+}
+
+#define ismemref(ref) ((ref).t == RTMP && ioper((ref).i).t == OMEM)
+#define isregref(ref) ((ref).t == RREG || ((ref).t == RTMP && ioper((ref).i).t == OREG))
+
+static inline struct oper
+mkimmregoper(union ref r)
+{
+ assert(isregref(r) || (iscon(r) && concls(r) == KI32));
+ return ref2oper(r);
+}
+
+static inline struct oper
+mkdatregoper(union ref r)
+{
+ assert(isregref(r) || (r.t == RXCON && contab.p[r.i].deref));
+ return ref2oper(r);
+}
+
+static inline struct oper
+mkimmdatregoper(union ref r)
+{
+ assert(isregref(r) || r.t == RICON || (r.t == RXCON && (contab.p[r.i].cls == KI32 || contab.p[r.i].deref)));
+ return ref2oper(r);
+}
+
+static struct oper
+mkmemoper(union ref r)
+{
+ if (r.t == RTMP) {
+ struct oper wop = ioper(r.i);
+ if (wop.t == OMEM) return wop;
+ assert(wop.t == OREG);
+ return mkoper(OMEM, .base = wop.reg, .index = NOINDEX);
+ } else if (r.t == RADDR) {
+ const struct addr *addr = &addrtab.p[r.i];
+ assert(addr->shift <= 3);
+ if (isaddrcon(addr->base,0)) {
+ return mkoper(OSYM, .con = addr->base.i,
+ .cindex = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX,
+ .cshift = addr->shift,
+ .disp = addr->disp);
+ } else if (isintcon(addr->base)) {
+ assert(!addr->disp);
+ return mkoper(OMEM, .base = NOBASE,
+ .index = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX,
+ .disp = intconval(addr->base),
+ .shift = addr->shift);
+ } else if (isaddrcon(addr->index,0)) {
+ assert(!addr->shift);
+ return mkoper(OSYM, .con = addr->index.i,
+ .cindex = addr->base.bits ? mkregoper(addr->base).reg : NOINDEX,
+ .disp = addr->disp);
+ }
+ return mkoper(OMEM, .base = addr->base.bits ? mkregoper(addr->base).reg : NOBASE,
+ .index = addr->index.bits ? mkregoper(addr->index).reg : NOINDEX,
+ .disp = addr->disp,
+ .shift = addr->shift);
+ } else if (r.t == RXCON) {
+ assert(!contab.p[r.i].cls);
+ return mkoper(OSYM, .con = r.i, .cindex = NOINDEX);
+ } else {
+ return mkoper(OMEM, .base = isregref(r) ? ref2oper(r).reg : NOBASE,
+ .index = NOINDEX,
+ .disp = isregref(r) ? 0 : mkimmoper(r).imm);
+ }
+}
+
+/** Instruction description tables **
+ *
+ * Each instruction is a list of descs, and the first one that matches
+ * is emitted. Each entry has a size pattern field, which is a bitset
+ * of the sizes (in bytes) that the entry matches, and 2 operand patterns,
+ * which describe the operands that can match (for example, PRAX matches
+ * a RAX register operand, PGPR matches any integer register, I8 matches
+ * an immediate operand between [-128,127]) The rest of the fields describe
+ * the instruction's encoding.
+ * (reference: https://www.felixcloutier.com/x86/ & https://wiki.osdev.org/X86-64_Instruction_Encoding )
+ */
+
+enum operpat {
+ PNONE,
+ PRAX,
+ PRCX,
+ PGPR,
+ PFPR,
+ P1, /* imm = 1 */
+ PN1, /* imm = -1 */
+ PI8,
+ PU8,
+ PI16,
+ PU16,
+ PI32,
+ PU32,
+ PMEM,
+ PSYM,
+};
+enum operenc {
+ EN_R = 1, /* reg with /r */
+ EN_RR, /* reg, reg with /r */
+ EN_RRX, /* reg, reg with /r (inverted) */
+ EN_MR, /* mem, reg with /r */
+ EN_RM, /* reg, mem with /r */
+ EN_M, /* mem */
+ EN_RI8, /* reg, imm8 with /0 */
+ EN_RI32, /* reg, imm32 with /0 */
+ EN_MI8, /* mem, imm8 with /x */
+ EN_MI16, /* mem, imm16 with /x */
+ EN_MI32, /* mem, imm32 with /x */
+ EN_O, /* reg with op + reg */
+ EN_OI, /* reg, imm32 with op + reg */
+ EN_I8, /* imm8 */
+ EN_I32, /* imm32 */
+ EN_R32, /* rel32 */
+ NOPERENC,
+};
+struct desc {
+ uchar psiz; /* subset of {1,2,4,8} */
+ uchar ptd, pts; /* bitsets of enum operpat */
+ uchar nopc; /* countof opc */
+ const char opc[8]; /* opcode bytes */
+ uchar operenc; /* enum operenc */
+ uchar ext; /* ModR/M.reg opc extension */
+ bool r8; /* uses 8bit register */
+ bool norexw; /* do not use REX.W even if size is 64 bits */
+};
+
+/* match operand against pattern */
+static inline bool
+opermatch(enum operpat pat, struct oper oper)
+{
+ switch (pat) {
+ case PNONE: return !oper.t;
+ case PRAX: return oper.t == OREG && oper.reg == RAX;
+ case PRCX: return oper.t == OREG && oper.reg == RCX;
+ case PGPR: return oper.t == OREG && oper.reg <= R15;
+ case PFPR: return oper.t == OREG && oper.reg >= XMM0;
+ case P1: return oper.t == OIMM && oper.imm == 1;
+ case PN1: return oper.t == OIMM && oper.imm == -1;
+ case PI8: return oper.t == OIMM && (schar)oper.imm == oper.imm;
+ case PU8: return oper.t == OIMM && (uchar)oper.imm == oper.imm;
+ case PI16: return oper.t == OIMM && (short)oper.imm == oper.imm;
+ case PU16: return oper.t == OIMM && (ushort)oper.imm == oper.imm;
+ case PI32: return oper.t == OIMM;
+ case PU32: return oper.t == OIMM && oper.imm >= 0;
+ case PMEM: return in_range(oper.t, OMEM, OSYMGOT);
+ case PSYM: return oper.t == OSYM || oper.t == OSYMGOT;
+ }
+ assert(0);
+}
+
+/* code output helpers */
+#define B(b) (*(*pcode)++ = (b))
+#define D(xs, N) (memcpy(*pcode, (xs), (N)), (*pcode) += (N))
+#define I16(w) (wr16le(*pcode, (w)), *pcode += 2)
+#define I32(w) (wr32le(*pcode, (w)), *pcode += 4)
+#define DS(S) D(S, sizeof S - 1)
+
+static bool usebp; /* use RBP? */
+static int rbpoff;
+static internstr curfnsym;
+static uchar *fnstart;
+
+/* Given an instruction description table, find the first entry that matches
+ * the operands (where dst, src are the operands in intel syntax order) and encode it */
+static void
+encode(uchar **pcode, const struct desc *tab, int ntab, enum irclass k, struct oper dst, struct oper src)
+{
+ const uchar *opc;
+ int nopc;
+ struct oper mem;
+ enum reg reg;
+ const struct desc *en = NULL;
+ for (int i = 0; i < ntab; ++i) {
+ if ((tab[i].psiz & cls2siz[k]) && opermatch(tab[i].ptd, dst) && opermatch(tab[i].pts, src)) {
+ en = &tab[i];
+ break;
+ }
+ }
+ assert(en && "no match for instr");
+
+ if (en->ptd == PFPR) dst.reg &= 15;
+ if (en->pts == PFPR) src.reg &= 15;
+ opc = (uchar *)en->opc;
+ nopc = en->nopc;
+ /* mandatory prefixes go before REX */
+ if (*opc == 0x66 || *opc == 0xF2 || *opc == 0xF3)
+ B(*opc++), --nopc;
+ int rex = in_range(k, KI64, KPTR) << 3; /* REX.W */
+ if (en->norexw) rex = 0;
+ switch (en->operenc) {
+ case EN_RR: /* mod = 11; reg = dst; rm = src */
+ rex |= (dst.reg >> 3) << 2; /* REX.R */
+ rex |= (src.reg >> 3) << 0; /* REX.B */
+ if (rex) B(0x40 | rex);
+ else if (en->r8 && in_range(src.reg, RSP, RDI)) {
+ /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */
+ B(0x40);
+ }
+ D(opc, nopc);
+ B(0300 | (dst.reg & 7) << 3 | (src.reg & 7));
+ break;
+ case EN_RRX: /* mod = 11; reg = src; rm = dst */
+ rex |= (src.reg >> 3) << 2; /* REX.R */
+ rex |= (dst.reg >> 3) << 0; /* REX.B */
+ if (rex) B(0x40 | rex);
+ else if (en->r8 && in_range(dst.reg, RSP, RDI)) {
+ /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */
+ B(0x40);
+ }
+ D(opc, nopc);
+ B(0300 | (src.reg & 7) << 3 | (dst.reg & 7));
+ break;
+ case EN_MR:
+ mem = dst;
+ reg = src.reg;
+ goto Mem;
+ case EN_RM:
+ mem = src;
+ reg = dst.reg;
+ goto Mem;
+ case EN_M: case EN_MI8: case EN_MI16: case EN_MI32:
+ mem = dst;
+ reg = en->ext;
+ Mem:
+ if (mem.t == OMEM) {
+ if (mem.base != NOBASE) rex |= mem.base >> 3; /* REX.B */
+ if (mem.index != NOINDEX) rex |= mem.index >> 3 << 1; /* REX.X */
+ } else {
+ if (mem.cindex != NOINDEX) rex |= mem.cindex >> 3 << 1; /* REX.X */
+ }
+ if (en->operenc != EN_M)
+ rex |= (reg >> 3) << 2; /* REX.R */
+ if (rex) B(0x40 | rex);
+ else if (en->r8 && in_range(reg, RSP, RDI)) B(0x40);
+
+ if (mem.t == OSYM || mem.t == OSYMGOT) {
+ D(opc, nopc);
+ if (mem.cindex == NOINDEX) {
+ /* %rip(var) */
+ static uchar offs[NOPERENC] = { [EN_MI8] = 1, [EN_MI16] = 2, [EN_MI32] = 4 };
+ uint addr;
+ int disp = mem.disp - 4 - offs[en->operenc];
+ internstr sym = xcon2sym(mem.con);
+ B(/*mod 0*/ (reg & 7) << 3 | RBP);
+ if (objhassym(sym, &addr) == Stext && mem.t != OSYMGOT) {
+ I32(addr - (*pcode - objout.textbegin) + disp);
+ } else {
+ enum relockind r = REL_PCREL32;
+ if (mem.t == OSYMGOT) r = rex ? REL_GOTPCRELX_REX : REL_GOTPCRELX;
+ objreloc(xcon2sym(mem.con), r, Stext, *pcode - objout.textbegin, disp);
+ I32(0);
+ }
+ } else {
+ /* var(,%reg,shift) */
+ assert(!ccopt.pic && !ccopt.pie && "cannot encode [RIP-rel + REG] for position independent");
+ B(/*mod 0*/ (reg & 7) << 3 | RSP);
+ B(mem.cshift << 6 | ((mem.cindex & 7) << 3) | RBP); /* SIB [index*s + disp32] */
+ objreloc(xcon2sym(mem.con), REL_ABS32S, Stext, *pcode - objout.textbegin, mem.disp);
+ I32(0);
+ }
+ } else {
+ int mod;
+ bool sib = 0;
+ if (mem.base == RBP) {
+ if (!usebp) {
+ /* if RBP isn't being set up (leaf functions with no stack allocations),
+ * access thru RSP (function arguments in the stack) */
+ mem.base = RSP;
+ mem.disp -= 8;
+ } else if (mem.disp <= 0) {
+ mem.disp += rbpoff;
+ }
+ }
+ if (mem.base != NOBASE) {
+ if (mem.index == NOINDEX && mem.shift == 0) sib = 0;
+ else sib = 1;
+ mod = !mem.disp ? 0 /* disp = 0 -> mod = 00 */
+ : (schar)mem.disp == mem.disp ? 1 /* disp8 -> mod = 01 */
+ : 2; /* disp32 -> mod = 10 */
+ if (mod == 0 && (mem.base == RBP || mem.base == R13)) mod = 1;
+ if (mem.base == RSP || mem.base == R12) sib = 1;
+ } else {
+ /* [disp + (index*s)] */
+ sib = 1;
+ mem.base = RBP;
+ mod = 0;
+ assert(mem.index != RSP);
+ }
+ D(opc, nopc);
+ B(mod << 6 | (reg & 7) << 3 | (sib ? 4 : (mem.base & 7)));
+ if (sib) {
+ if (mem.index == NOINDEX) mem.index = RSP;
+ B(mem.shift << 6 | (mem.index & 7) << 3 | (mem.base & 7));
+ }
+ if (mod == 1) B(mem.disp);
+ else if (mod == 2 || (mod == 0 && mem.base == RBP/*RIP-rel*/) || (mod == 0 && sib && mem.base == RBP/*absolute*/)) {
+ I32(mem.disp);
+ }
+ }
+ if (en->operenc == EN_MI8) B(src.imm);
+ if (en->operenc == EN_MI16) I16(src.imm);
+ if (en->operenc == EN_MI32) I32(src.imm);
+ break;
+ case EN_R: case EN_RI32: case EN_RI8:
+ rex |= (dst.reg >> 3) << 0; /* REX.B */
+ if (rex) B(0x40 | rex);
+ else if (en->r8 && in_range(dst.reg, RSP, RDI)) {
+ /* /r8 needs REX to encode SP,BP,SI,DI (otherwise -> AH..BH) */
+ B(0x40);
+ }
+ D(opc, nopc);
+ B(0300 | en->ext << 3 | (dst.reg & 7));
+ if (en->operenc == EN_RI32)
+ I32(src.imm);
+ else if (en->operenc == EN_RI8)
+ B(src.imm);
+ break;
+ case EN_O: case EN_OI:
+ rex |= (dst.reg >> 3) << 0; /* REX.B */
+ if (rex) B(0x40 | rex);
+ D(opc, nopc - 1);
+ B(opc[nopc-1] + (dst.reg & 7));
+ if (en->operenc == EN_OI) I32(src.imm);
+ break;
+ case EN_I8:
+ if (rex) B(0x40 | rex);
+ D(opc, nopc);
+ B(src.imm);
+ break;
+ case EN_I32:
+ if (rex) B(0x40 | rex);
+ D(opc, nopc);
+ I32(src.imm);
+ break;
+ case EN_R32:
+ if (rex) B(0x40 | rex);
+ D(opc, nopc);
+ assert(dst.t == OSYM);
+ internstr sym = xcon2sym(dst.con);
+ uint addr;
+ if (sym == curfnsym) {
+ I32(fnstart - *pcode - 4);
+ } else if (objhassym(sym, &addr) == Stext) {
+ I32(addr - (*pcode - objout.textbegin) - 4);
+ } else {
+ enum relockind r = (ccopt.pie|ccopt.pic) ? REL_PLT32 : REL_PCREL32;
+ objreloc(sym, r, Stext, *pcode - objout.textbegin, -4);
+ I32(0);
+ }
+ break;
+ }
+}
+
+#define DEFINSTR1(X, ...) \
+ static void \
+ X(uchar **pcode, enum irclass k, struct oper oper) \
+ { \
+ static const struct desc tab[] = { __VA_ARGS__ }; \
+ encode(pcode, tab, countof(tab), k, oper, mkoper(0,)); \
+ }
+
+#define DEFINSTR2(X, ...) \
+ static void \
+ X(uchar **pcode, enum irclass k, struct oper dst, struct oper src) \
+ { \
+ static const struct desc tab[] = { __VA_ARGS__ }; \
+ encode(pcode, tab, countof(tab), k, dst, src); \
+ }
+
+#define O(s) (sizeof s)-1,s
+DEFINSTR2(Xmovb,
+ {-1, PMEM, PGPR, O("\x88"), EN_MR, .r8=1}, /* MOV m8, r8 */
+ {-1, PMEM, PI32, O("\xC6"), EN_MI8, .r8=1}, /* MOV m8, imm8 */
+)
+DEFINSTR2(Xmovw,
+ {-1, PMEM, PGPR, O("\x66\x89"), EN_MR}, /* MOV m16, r16 */
+ {-1, PMEM, PI32, O("\x66\xC7"), EN_MI16}, /* MOV m16, imm16 */
+)
+static void Xmov(uchar **pcode, enum irclass k, struct oper dst, struct oper src)
+{
+ static const struct desc all[] = {
+ {4 , PGPR, PI32, O("\xB8"), EN_OI}, /* MOV r32, imm */
+ {4|8, PGPR, PGPR, O("\x8B"), EN_RR}, /* MOV r32/64, r32/64 */
+ {4|8, PMEM, PGPR, O("\x89"), EN_MR}, /* MOV m32/64, r32/64 */
+ {4|8, PGPR, PMEM, O("\x8B"), EN_RM}, /* MOV r32/64, m32/64 */
+ {4|8, PMEM, PI32, O("\xC7"), EN_MI32}, /* MOV m32/64, imm */
+ { 8, PGPR, PU32, O("\xB8"), EN_OI, .norexw=1}, /* MOV r64, uimm */
+ { 8, PGPR, PI32, O("\xC7"), EN_RI32}, /* MOV r64, imm */
+ {4 , PFPR, PFPR, O("\x0F\x28"), EN_RR}, /* MOVPS xmm, xmm */
+ {4 , PFPR, PMEM, O("\xF3\x0F\x10"), EN_RM}, /* MOVSS xmm, m32 */
+ {4 , PMEM, PFPR, O("\xF3\x0F\x11"), EN_MR}, /* MOVSS m32, xmm */
+ {8 , PFPR, PFPR, O("\x0F\x28"), EN_RR}, /* MOVPS xmm, xmm */
+ {8 , PFPR, PMEM, O("\xF2\x0F\x10"), EN_RM}, /* MOVSD xmm, m64 */
+ {8 , PMEM, PFPR, O("\xF2\x0F\x11"), EN_MR}, /* MOVSS m64, xmm */
+ {4|8, PFPR, PGPR, O("\x66\x0F\x6E"), EN_RR}, /* MOVD/Q xmm, r64/32 */
+ {4|8, PGPR, PFPR, O("\x66\x0F\x7E"), EN_RRX}, /* MOVD/Q r64/32, xmm */
+ };
+ static const uchar k2off[] = {
+ [KI32] = 0,
+ [KI64] = 1, [KPTR] = 1,
+ [KF32] = 7,
+ [KF64] = 10,
+ };
+ if (kisflt(k) && src.t == OIMM && src.imm == 0) {
+ /* special case for storing zero float : use integer instruction with zero immediate */
+ k = KI32 + (k - KF32);
+ }
+ encode(pcode, all + k2off[k], countof(all) - k2off[k], k, dst, src);
+}
+DEFINSTR2(Xmovsxl,
+ {8, PGPR, PMEM, O("\x63"), EN_RM}, /* MOVSXD r64, m32 */
+ {8, PGPR, PGPR, O("\x63"), EN_RR}, /* MOVSXD r64, r32 */
+ {4, PGPR, PMEM, O("\x8B"), EN_RM}, /* MOV r32, m32 */
+ {4, PGPR, PGPR, O("\x8B"), EN_RR}, /* MOV r32, r32 */
+)
+DEFINSTR2(Xmovsxw,
+ {4|8, PGPR, PMEM, O("\x0F\xBF"), EN_RM}, /* MOVSX r64, m16 */
+ {4|8, PGPR, PGPR, O("\x0F\xBF"), EN_RR}, /* MOVSX r64, r16 */
+)
+DEFINSTR2(Xmovsxb,
+ {4|8, PGPR, PMEM, O("\x0F\xBE"), EN_RM}, /* MOVSX r64, m8 */
+ {4|8, PGPR, PGPR, O("\x0F\xBE"), EN_RR, .r8=1}, /* MOVSX r64, r8 */
+)
+DEFINSTR2(Xmovzxw,
+ {4|8, PGPR, PMEM, O("\x0F\xB7"), EN_RM}, /* MOVZX r64, m16 */
+ {4|8, PGPR, PGPR, O("\x0F\xB7"), EN_RR}, /* MOVZX r64, r16 */
+)
+DEFINSTR2(Xmovzxb,
+ {4|8, PGPR, PMEM, O("\x0F\xB6"), EN_RM}, /* MOVZX r64, m8 */
+ {4|8, PGPR, PGPR, O("\x0F\xB6"), EN_RR, .r8=1}, /* MOVZX r64, r8 */
+)
+DEFINSTR2(Xmovaps,
+ {-1, PMEM, PFPR, O("\x0F\x29"), EN_MR}, /* MOVAPS mem, xmm */
+)
+DEFINSTR2(Xxchg,
+ {4|8, PGPR, PGPR, O("\x87"), EN_RR}, /* XCHG r32/64, r32/64 */
+ {4|8, PGPR, PMEM, O("\x87"), EN_RM}, /* XCHG r32/64, m32/64 */
+ {4|8, PMEM, PGPR, O("\x87"), EN_MR}, /* XCHG r32/64, m32/64 */
+)
+DEFINSTR2(Xlea,
+ {4|8, PGPR, PMEM, O("\x8D"), EN_RM}, /* LEA r32/64,m32/64 */
+ { 8, PGPR, PSYM, O("\x8D"), EN_RM}, /* LEA r32/64,rel32 */
+)
+DEFINSTR2(Xadd,
+ {4|8, PGPR, PGPR, O("\x03"), EN_RR}, /* ADD r32/64, r32/64 */
+ {4|8, PGPR, P1, O("\xFF"), EN_R, .ext=0}, /* INC r32/64 */
+ {4|8, PGPR, PN1, O("\xFF"), EN_R, .ext=1}, /* DEC r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8}, /* ADD r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x05"), EN_I32}, /* ADD eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32}, /* ADD r32/64, imm */
+ { 8, PGPR, PMEM, O("\x03"), EN_RM}, /* ADD r64, m64 */
+)
+DEFINSTR2(Xaddf,
+ {4, PFPR, PFPR, O("\xF3\x0F\x58"), EN_RR}, /* ADDSS xmm, xmm */
+ {8, PFPR, PFPR, O("\xF2\x0F\x58"), EN_RR}, /* ADDSD xmm, xmm */
+ {4, PFPR, PMEM, O("\xF3\x0F\x58"), EN_RM}, /* ADDSS xmm, m32 */
+ {8, PFPR, PMEM, O("\xF2\x0F\x58"), EN_RM}, /* ADDSD xmm, m64 */
+)
+DEFINSTR2(Xsub,
+ {4|8, PGPR, PGPR, O("\x2B"), EN_RR}, /* SUB r32/64, r32/64 */
+ {4|8, PGPR, P1, O("\xFF"), EN_R, .ext=1}, /* DEC r32/64 */
+ {4|8, PGPR, PN1, O("\xFF"), EN_R, .ext=0}, /* INC r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=5}, /* SUB r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x2D"), EN_I32}, /* SUB eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=5}, /* SUB r32/64, imm */
+ { 8, PGPR, PMEM, O("\x2B"), EN_RM}, /* SUB r64, m64 */
+)
+DEFINSTR2(Xsubf,
+ {4, PFPR, PFPR, O("\xF3\x0F\x5C"), EN_RR}, /* SUBSS xmm, xmm */
+ {8, PFPR, PFPR, O("\xF2\x0F\x5C"), EN_RR}, /* SUBSD xmm, xmm */
+ {4, PFPR, PMEM, O("\xF3\x0F\x5C"), EN_RM}, /* SUBSS xmm, m32 */
+ {8, PFPR, PMEM, O("\xF2\x0F\x5C"), EN_RM}, /* SUBSD xmm, m64 */
+)
+DEFINSTR2(Xmulf,
+ {4, PFPR, PFPR, O("\xF3\x0F\x59"), EN_RR}, /* MULSS xmm, xmm */
+ {8, PFPR, PFPR, O("\xF2\x0F\x59"), EN_RR}, /* MULSD xmm, xmm */
+ {4, PFPR, PMEM, O("\xF3\x0F\x59"), EN_RM}, /* MULSS xmm, m32 */
+ {8, PFPR, PMEM, O("\xF2\x0F\x59"), EN_RM}, /* MULSD xmm, m64 */
+)
+DEFINSTR2(Xdivf,
+ {4, PFPR, PFPR, O("\xF3\x0F\x5E"), EN_RR}, /* DIVSS xmm, xmm */
+ {8, PFPR, PFPR, O("\xF2\x0F\x5E"), EN_RR}, /* DIVSD xmm, xmm */
+ {4, PFPR, PMEM, O("\xF3\x0F\x5E"), EN_RM}, /* DIVSS xmm, m32 */
+ {8, PFPR, PMEM, O("\xF2\x0F\x5E"), EN_RM}, /* DIVSD xmm, m64 */
+)
+DEFINSTR2(Xand,
+ {4|8, PGPR, PGPR, O("\x23"), EN_RR}, /* AND r32/64, r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=4}, /* AND r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x25"), EN_I32}, /* AND eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=4}, /* AND r32/64, imm */
+ { 8, PGPR, PMEM, O("\x23"), EN_RM}, /* AND r64, m64 */
+)
+DEFINSTR2(Xior,
+ {4|8, PGPR, PGPR, O("\x0B"), EN_RR}, /* OR r32/64, r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=1}, /* OR r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x0D"), EN_I32}, /* OR eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=1}, /* OR r32/64, imm */
+ { 8, PGPR, PMEM, O("\x0B"), EN_RM}, /* OR r64, m64 */
+ {4|8, PFPR, PFPR, O("\x0F\x57"), EN_RR}, /* ORPS xmm, xmm */
+)
+DEFINSTR2(Xxor,
+ {4|8, PGPR, PGPR, O("\x33"), EN_RR}, /* XOR r32/64, r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=6}, /* XOR r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x35"), EN_I32}, /* XOR eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=6}, /* XOR r32/64, imm */
+ { 8, PGPR, PMEM, O("\x33"), EN_RM}, /* XOR r64, m64 */
+ {4|8, PFPR, PFPR, O("\x0F\x57"), EN_RR}, /* XORPS xmm, xmm */
+ {4|8, PFPR, PMEM, O("\x0F\x57"), EN_RM}, /* XORPS xmm, m128 */
+)
+DEFINSTR2(Xshl,
+ {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=4}, /* SHL r32/64, 1 */
+ {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=4}, /* SHL r32/64, imm */
+ {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=4}, /* SHL r32/64, CL */
+)
+DEFINSTR2(Xsar,
+ {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=7}, /* SAR r32/64, 1 */
+ {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=7}, /* SAR r32/64, imm */
+ {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=7}, /* SAR r32/64, CL */
+)
+DEFINSTR2(Xrolw,
+ {-1, PGPR, PI8, O("\x66\xC1"), EN_RI8}, /* ROL r16, imm */
+)
+DEFINSTR2(Xshr,
+ {4|8, PGPR, P1, O("\xD1"), EN_R, .ext=5}, /* SHR r32/64, 1 */
+ {4|8, PGPR, PI32, O("\xC1"), EN_RI8, .ext=5}, /* SHR r32/64, imm */
+ {4|8, PGPR, PRCX, O("\xD3"), EN_R, .ext=5}, /* SHR r32/64, CL */
+)
+DEFINSTR2(Xcvtss2sd,
+ {-1, PFPR, PFPR, O("\xF3\x0F\x5A"), EN_RR}, /* CVTSS2SD xmm, xmm */
+ {-1, PFPR, PMEM, O("\xF3\x0F\x5A"), EN_RM}, /* CVTSS2SD xmm, m32/64 */
+)
+DEFINSTR2(Xcvtsd2ss,
+ {-1, PFPR, PFPR, O("\xF2\x0F\x5A"), EN_RR}, /* CVTSD2SS xmm, xmm */
+ {-1, PFPR, PMEM, O("\xF2\x0F\x5A"), EN_RM}, /* CVTSD2SS xmm, m32/64 */
+)
+DEFINSTR2(Xcvtsi2ss,
+ {-1, PFPR, PGPR, O("\xF3\x0F\x2A"), EN_RR}, /* CVTSI2SS xmm, r32/64 */
+ {-1, PFPR, PMEM, O("\xF3\x0F\x2A"), EN_RM}, /* CVTSI2SS xmm, m32/64 */
+)
+DEFINSTR2(Xcvtsi2sd,
+ {-1, PFPR, PGPR, O("\xF2\x0F\x2A"), EN_RR}, /* CVTSI2SD xmm, r32/64 */
+ {-1, PFPR, PMEM, O("\xF2\x0F\x2A"), EN_RM}, /* CVTSI2SD xmm, m32/64 */
+)
+DEFINSTR2(Xcvttss2si,
+ {-1, PGPR, PFPR, O("\xF3\x0F\x2C"), EN_RR}, /* CVTTSS2SI r32/64, xmm */
+ {-1, PGPR, PMEM, O("\xF3\x0F\x2C"), EN_RM}, /* CVTTSS2SI r32/64, m32 */
+)
+DEFINSTR2(Xcvttsd2si,
+ {-1, PGPR, PFPR, O("\xF2\x0F\x2C"), EN_RR}, /* CVTTSD2SI r32/64, xmm */
+ {-1, PGPR, PMEM, O("\xF2\x0F\x2C"), EN_RM}, /* CVTTSD2SI r32/64, m32 */
+)
+DEFINSTR1(Xneg,
+ {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=3} /* NEG r32/64 */
+)
+DEFINSTR1(Xnot,
+ {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=2} /* NOT r32/64 */
+)
+DEFINSTR1(Xidiv,
+ {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=7}, /* IDIV r32/64 */
+ {4|8, PMEM, 0, O("\xF7"), EN_M, .ext=7}, /* IDIV m32/64 */
+)
+DEFINSTR1(Xdiv,
+ {4|8, PGPR, 0, O("\xF7"), EN_R, .ext=6}, /* DIV r32/64 */
+ {4|8, PMEM, 0, O("\xF7"), EN_M, .ext=6}, /* DIV m32/64 */
+)
+DEFINSTR1(Xbswap,
+ {4|8, PGPR, 0, O("\x0F\xC8"), EN_O}, /* BSWAP r32/64 */
+)
+DEFINSTR1(Xcall,
+ {-1, PSYM, 0, O("\xE8"), EN_R32, .norexw=1}, /* CALL rel32 */
+ {-1, PGPR, 0, O("\xFF"), EN_R, .ext=2, .norexw=1}, /* CALL r64 */
+ {-1, PMEM, 0, O("\xFF"), EN_M, .ext=2, .norexw=1}, /* CALL m64 */
+)
+DEFINSTR2(Xcmp,
+ {4|8, PGPR, PGPR, O("\x3B"), EN_RR}, /* CMP r32/64, r32/64 */
+ {4|8, PGPR, PI8, O("\x83"), EN_RI8, .ext=7}, /* CMP r32/64, imm8 */
+ {4|8, PRAX, PI32, O("\x3D"), EN_I32}, /* CMP eax/rax, imm */
+ {4|8, PGPR, PI32, O("\x81"), EN_RI32, .ext=7}, /* CMP r32/64, imm */
+ { 8, PGPR, PMEM, O("\x3B"), EN_RM}, /* CMP r64, m64 */
+ {4 , PFPR, PFPR, O("\x0F\x2E"), EN_RR}, /* UCOMISS xmm, xmm */
+ {4 , PFPR, PMEM, O("\x0F\x2E"), EN_RM}, /* UCOMISS xmm, m32 */
+ { 8, PFPR, PFPR, O("\x66\x0F\x2E"), EN_RR}, /* UCOMISD xmm, xmm */
+ { 8, PFPR, PMEM, O("\x66\x0F\x2E"), EN_RM}, /* UCOMISD xmm, m64 */
+)
+DEFINSTR2(Xtest,
+ {4|8, PRAX, PI8, O("\xA8"), EN_I8, .norexw=1}, /* TEST AL, imm8 */
+ {4, PRAX, PI32, O("\xA9"), EN_I32}, /* TEST EAX, imm32 */
+ { 8, PRAX, PU32, O("\xA9"), EN_I32, .norexw=1}, /* TEST EAX, imm32 */
+ { 8, PRAX, PI32, O("\xA9"), EN_I32}, /* TEST RAX, imm32 */
+ {4|8, PGPR, PI8, O("\xF6"), EN_RI8, .r8=1,.norexw=1}, /* TEST r8, imm8 */
+ {4|8, PGPR, PI32, O("\xF7"), EN_RI32, .ext=0}, /* TEST r32/64, imm32 */
+ {4|8, PGPR, PGPR, O("\x85"), EN_RR}, /* TEST r32/64, r32/64 */
+ {4|8, PGPR, PMEM, O("\x85"), EN_RM}, /* TEST r32/64, m32/64 */
+)
+
+DEFINSTR2(Ximul2,
+ {4|8, PGPR, PGPR, O("\x0F\xAF"), EN_RR}, /* IMUL r32/64, r32/64 */
+ {4|8, PGPR, PMEM, O("\x0F\xAF"), EN_RM}, /* IMUL r32/64, m32/64 */
+)
+static const struct desc imul3_imm8tab[] = {
+ {4|8, PGPR, PGPR, O("\x6B"), EN_RR}, /* IMUL r32/64, r32/64, (imm8) */
+ {4|8, PGPR, PMEM, O("\x6B"), EN_RM}, /* IMUL r32/64, m32/64, (imm8) */
+}, imul3_imm32tab[] = {
+ {4|8, PGPR, PGPR, O("\x69"), EN_RR}, /* IMUL r32/64, r32/64, (imm32) */
+ {4|8, PGPR, PMEM, O("\x69"), EN_RM}, /* IMUL r32/64, m32/64, (imm32) */
+};
+#undef O
+static void
+Ximul(uchar **pcode, enum irclass k, struct oper dst, struct oper s1, struct oper s2)
+{
+ if (!memcmp(&dst, &s1, sizeof dst) && s2.t != OIMM) {
+ Ximul2(pcode, k, dst, s2);
+ return;
+ }
+ assert(s2.t == OIMM);
+ if (-128 <= s2.imm && s2.imm < 128) {
+ encode(pcode, imul3_imm8tab, countof(imul3_imm8tab), k, dst, s1);
+ B(s2.imm);
+ } else {
+ encode(pcode, imul3_imm32tab, countof(imul3_imm32tab), k, dst, s1);
+ I32(s2.imm);
+ }
+}
+
+enum cc {
+ CCO = 0x0, /* OF = 1*/
+ CCNO = 0x1, /* OF = 0*/
+ CCB = 0x2, CCC = 0x2, CCNAE = 0x2, /* below; CF = 1; not above or equal */
+ CCAE = 0x3, CCNB = 0x3, CCNC = 0x3, /* above or equal; not below; CF = 0 */
+ CCE = 0x4, CCZ = 0x4, /* equal; ZF = 1 */
+ CCNE = 0x5, CCNZ = 0x5, /* not equal; ZF = 0 */
+ CCBE = 0x6, CCNA = 0x6, /* below or equal; not above; CF=1 or ZF=1 */
+ CCA = 0x7, CCNBE = 0x7, /* above; not below or equal; CF=0 and ZF=0 */
+ CCS = 0x8, /* ZS = 1; negative */
+ CCNS = 0x9, /* ZS = 0; non-negative */
+ CCP = 0xA, CCPE = 0xA, /* PF = 1; parity even */
+ CCNP = 0xB, CCPO = 0xB, /* PF = 0; parity odd */
+ CCL = 0xC, CCNGE = 0xC, /* lower; not greater or equal; SF != OF */
+ CCGE = 0xD, CCNL = 0xD, /* greater or equal; not lower; SF == OF */
+ CCLE = 0xE, CCNG = 0xE, /* less or equal; not greater; ZF=1 or SF != OF */
+ CCG = 0xF, CCNLE = 0xF, /* greater; not less or equal; ZF=0 and SF = OF*/
+ ALWAYS,
+};
+
+/* maps blk -> address when resolved; or to linked list of jump displacement
+ * relocations */
+static struct blkaddr {
+ bool resolved;
+ union {
+ uint addr;
+ uint relreloc;
+ };
+} *blkaddr;
+
+static void
+Xjcc(uchar **pcode, enum cc cc, struct block *dst)
+{
+ int disp, insaddr = *pcode - objout.textbegin;
+ bool rel8 = 0;
+
+ if (blkaddr[dst->id].resolved) {
+ disp = blkaddr[dst->id].addr - (insaddr + 2);
+ if ((uint)(disp + 128) < 256) /* can use 1-byte displacement? */
+ rel8 = 1;
+ else { /* otherwise 4-byte displacement */
+ disp -= 3;
+ disp -= cc != ALWAYS; /* 'Jcc rel32' has 2 opcode bytes */
+ }
+ } else {
+ disp = blkaddr[dst->id].relreloc;
+ blkaddr[dst->id].relreloc = insaddr + 1 + (cc != ALWAYS);
+ }
+ if (cc == ALWAYS) {
+ B(rel8 ? 0xEB : 0xE9); /* JMP rel8/rel32 */
+ } else {
+ assert(in_range(cc, 0, 0xF));
+ if (rel8) B(0x70 + cc); /* Jcc rel8 */
+ else B(0x0F), B(0x80 + cc); /* Jcc rel32 */
+ }
+ if (rel8) B(disp); else I32(disp);
+}
+
+static void
+Xsetcc(uchar **pcode, enum cc cc, enum reg reg)
+{
+ int rex = 0;
+ assert(in_range(cc, 0x0, 0xF));
+ assert(in_range(reg, RAX, R15));
+
+ if (in_range(reg, RSP, RDI)) rex = 0x40;
+ rex |= (reg >> 3); /* REX.B */
+ if (rex) B(rex | 0x40);
+ B(0x0F), B(0x90+cc); /* SETcc */
+ B(0xC0 + (reg & 7)); /* ModR/M with mod=11, rm=reg */
+}
+
+static void
+Xpush(uchar **pcode, enum reg reg)
+{
+ if (in_range(reg, RAX, R15)) {
+ if (reg >> 3) B(0x41); /* REX.B */
+ B(0x50 + (reg & 7)); /* PUSH reg */
+ } else {
+ assert(in_range(reg, XMM0, XMM15));
+ DS("\x48\x8d\x64\x24\xF8"); /* LEA RSP, [RSP-8] */
+ Xmov(pcode, KF64, mkoper(OMEM, .base = RSP, .index = NOINDEX), reg2oper(reg)); /* MOVD [rsp],xmm0 */
+ }
+}
+
+static void
+Xpop(uchar **pcode, enum reg reg)
+{
+ if (in_range(reg, RAX, R15)) {
+ if (reg >> 3) B(0x41); /* REX.B */
+ B(0x58 + (reg & 7)); /* POP reg */
+ } else {
+ assert(in_range(reg, XMM0, XMM15));
+ Xmov(pcode, KF64, reg2oper(reg), mkoper(OMEM, .base = RSP, .index = NOINDEX)); /* MOVD xmm0,[rsp] */
+ DS("\x48\x8d\x64\x24\x08"); /* LEA RSP, [RSP+8] */
+ }
+}
+
+/* are flags live at given instruction? */
+static bool
+flagslivep(struct block *blk, int curi)
+{
+ int cmpi;
+ /* conditional branch that references a previous comparison instruction? */
+ if (blk->jmp.t != Jb || !blk->jmp.arg[0].bits)
+ return 0;
+ assert(blk->jmp.arg[0].t == RTMP);
+ cmpi = blk->jmp.arg[0].i;
+ for (int i = blk->ins.n - 1; i > curi; --i) {
+ if (blk->ins.p[i] == cmpi)
+ /* flags defined after given instruction, dead here */
+ return 0;
+ }
+ /* flags defined before given instruction, live here */
+ return 1;
+}
+
+/* Copy dst = val, with some peephole optimizations */
+static void
+gencopy(uchar **pcode, enum irclass cls, struct block *blk, int curi, struct oper dst, union ref val)
+{
+ assert(dst.t == OREG);
+ if (val.bits == UNDREF.bits) {
+ /* can be generated by ssa construction, since value is undefined no move is needed */
+ return;
+ }
+ if (val.t == RADDR) {
+ /* this is a LEA, but maybe it can be lowered to a 2-address instruction,
+ * which may clobber flags */
+ const struct addr *addr = &addrtab.p[val.i];
+ if (flagslivep(blk, curi)) goto Lea;
+ if (addr->base.t != RREG) goto Lea;
+ if (addr->base.bits && dst.reg == mkregoper(addr->base).reg) { /* base = dst */
+ if (addr->index.bits && !addr->disp && !addr->shift){
+ /* lea Rx, [Rx + Ry] -> add Rx, Ry */
+ Xadd(pcode, cls, dst, mkregoper(addr->index));
+ return;
+ } else if (!addr->index.bits) {
+ if (!addr->disp) /* lea Rx, [Rx] -> mov Rx, Rx */
+ Xmov(pcode, cls, dst, dst);
+ else /* lea Rx, [Rx + Imm] -> add Rx, Imm */
+ Xadd(pcode, cls, dst, mkoper(OIMM, .imm = addr->disp));
+ return;
+ }
+ } else if (addr->index.bits && dst.reg == mkregoper(addr->index).reg) { /* index = dst */
+ if (addr->base.bits && !addr->disp && !addr->shift) {
+ /* lea Rx, [Ry + Rx] -> add Rx, Ry */
+ Xadd(pcode, cls, dst, mkregoper(addr->base));
+ return;
+ } else if (!addr->base.bits) {
+ if (!addr->disp && !addr->shift) /* lea Rx, [Rx] -> mov Rx, Rx */
+ Xmov(pcode, cls, dst, dst);
+ else if (!addr->shift) /* lea Rx, [Rx + Imm] -> add Rx, Imm */
+ Xadd(pcode, cls, dst, mkoper(OIMM, .imm = addr->disp));
+ else if (!addr->disp) /* lea Rx, [Rx LSL s] -> shl Rx, s */
+ Xshl(pcode, cls, dst, mkoper(OIMM, .imm = addr->shift));
+ else
+ goto Lea;
+ return;
+ }
+ }
+ /* normal (not 2-address) case */
+ Lea:
+ if (isaddrcon(addr->base,0) && (ccopt.pic || (contab.p[addr->base.i].flag & SFUNC))
+ && !(contab.p[addr->base.i].flag & SLOCAL)) {
+ assert(!addr->disp && !addr->index.bits);
+ val = addr->base;
+ goto GOTLoad;
+ }
+ Xlea(pcode, cls, dst, ref2oper(val));
+ } else if (val.bits == ZEROREF.bits && dst.t == OREG && (kisflt(cls) || !flagslivep(blk, curi))) {
+ /* dst = 0 -> xor dst, dst; but only if it is ok to clobber flags */
+ Xxor(pcode, kisint(cls) ? KI32 : cls, dst, dst);
+ } else if (isaddrcon(val,0)) {
+ if ((ccopt.pic || (contab.p[val.i].flag & SFUNC)) && (contab.p[val.i].flag & (SLOCAL|SFUNC)) != (SLOCAL|SFUNC)) {
+ GOTLoad:
+ /* for mov reg, [rip(sym@GOTPCREL)] */
+ Xmov(pcode, cls, dst, mkoper(OSYMGOT, .con = val.i, .cindex = NOINDEX));
+ } else {
+ /* for lea reg, [rip(sym)] */
+ Xlea(pcode, cls, dst, mkoper(OSYM, .con = val.i, .cindex = NOINDEX));
+ }
+ } else if (val.t == RXCON && in_range(concls(val), KI64, KPTR)) {
+ /* movabs */
+ assert(dst.t == OREG && in_range(dst.reg, RAX, R15));
+ B(0x48 | (dst.reg >> 3)); /* REX.W (+ REX.B) */
+ B(0xB8 + (dst.reg & 0x7)); /* MOVABS r64, */
+ wr64le(*pcode, intconval(val)); /* imm64 */
+ *pcode += 8;
+ } else {
+ struct oper src = mkimmdatregoper(val);
+ if (memcmp(&dst, &src, sizeof dst) != 0)
+ Xmov(pcode, cls == KF64 && src.t == OREG && src.reg < XMM0 ? KI64 : cls, dst, src);
+ }
+}
+
+static void
+Xvaprologue(uchar **pcode, struct function *fn, struct oper sav)
+{
+ uint gpr0 = 0, fpr0 = 0, jmpaddr;
+ for (int i = 0; i < fn->nabiarg; ++i) {
+ struct abiarg abi = fn->abiarg[i];
+ if (!abi.isstk) {
+ if (abi.reg < XMM0) ++gpr0;
+ else ++fpr0;
+ }
+ }
+ assert(sav.t == OMEM && sav.base == RBP);
+ /* save GPRS */
+ for (int r = 0; r < 6; ++r) {
+ static const char reg[] = {RDI,RSI,RDX,RCX,R8,R9};
+ if (r >= gpr0)
+ Xmov(pcode, KI64, sav, reg2oper(reg[r]));
+ sav.disp += 8;
+ }
+
+ /* save FPRs, but only if al is non zero */
+ if (fpr0 < 8) {
+ DS("\x84\xC0"); /* TEST al,al */
+ jmpaddr = *pcode - objout.textbegin;
+ DS("\x74\xFE"); /* JE rel8 */
+ }
+ for (int r = 0; r < 8; ++r) {
+ if (r >= fpr0)
+ Xmovaps(pcode, KF64, sav, reg2oper(XMM0 + r));
+ sav.disp += 16;
+ }
+ if (fpr0 < 8) {/* patch relative jump */
+ int off = (*pcode - objout.textbegin) - jmpaddr - 2;
+ objout.textbegin[jmpaddr+1] = off;
+ }
+}
+
+/* condition code for CMP */
+static const uchar icmpop2cc[] = {
+ [Oequ] = CCE, [Oneq] = CCNE,
+ [Olth] = CCL, [Ogth] = CCG, [Olte] = CCLE, [Ogte] = CCGE,
+ [Oulth] = CCB, [Ougth] = CCA, [Oulte] = CCBE, [Ougte] = CCAE,
+ [Oand] = CCNE, [Osub] = CCNE,
+}, fcmpop2cc[] = {
+ [Oequ] = CCE, [Oneq] = CCNE,
+ [Olth] = CCB, [Ogth] = CCA, [Olte] = CCBE, [Ogte] = CCAE,
+};
+/* condition code for TEST reg,reg (compare with zero) */
+static const uchar icmpzero2cc[] = {
+ [Oequ] = CCE, [Oulte] = CCE,
+ [Oneq] = CCNE, [Ougth] = CCNE,
+ [Olth] = CCS, [Ogte] = CCNS,
+ [Olte] = CCLE, [Ogth] = CCG,
+ [Oulth] = CCB, [Ougte] = CCAE, /* actually constants */
+};
+
+static void
+emitinstr(uchar **pcode, struct function *fn, struct block *blk, int curi, struct instr *ins)
+{
+ struct oper dst, src;
+ bool regzeroed;
+ enum irclass cls = ins->cls;
+ void (*X)(uchar **, enum irclass, struct oper, struct oper) = NULL;
+ void (*X1)(uchar **, enum irclass, struct oper) = NULL;
+
+ switch (ins->op) {
+ default:
+ fatal(NULL, "x86_64: in %y; unimplemented instr '%s'", fn->name, opnames[ins->op]);
+ case Onop: break;
+ case Omove:
+ dst = ref2oper(ins->l);
+ gencopy(pcode, cls, blk, curi, dst, ins->r);
+ break;
+ case Ocopy:
+ dst = reg2oper(ins->reg-1);
+ gencopy(pcode, cls, blk, curi, dst, ins->l);
+ break;
+ case Ostorei8: cls = KI32, X = Xmovb; goto Store;
+ case Ostorei16: cls = KI32, X = Xmovw; goto Store;
+ case Ostorei32: cls = KI32, X = Xmov; goto Store;
+ case Ostorei64: cls = KI64, X = Xmov; goto Store;
+ case Ostoref32: cls = KF32, X = Xmov; goto Store;
+ case Ostoref64: cls = KF64, X = Xmov; goto Store;
+ Store:
+ src = mkimmregoper(ins->r);
+ X(pcode, cls, mkmemoper(ins->l), src);
+ break;
+ case Oexts8: src = mkregoper(ins->l); goto Movsxb;
+ case Oextu8: src = mkregoper(ins->l); goto Movzxb;
+ case Oexts16: src = mkregoper(ins->l); goto Movsxw;
+ case Oextu16: src = mkregoper(ins->l); goto Movzxw;
+ case Oexts32: src = mkregoper(ins->l); goto Movsxl;
+ case Oextu32: src = mkregoper(ins->l); goto Movzxl;
+ case Oloads8: src = mkmemoper(ins->l); Movsxb: Xmovsxb(pcode, cls, reg2oper(ins->reg-1), src); break;
+ case Oloadu8: src = mkmemoper(ins->l); Movzxb: Xmovzxb(pcode, cls, reg2oper(ins->reg-1), src); break;
+ case Oloads16: src = mkmemoper(ins->l); Movsxw: Xmovsxw(pcode, cls, reg2oper(ins->reg-1), src); break;
+ case Oloadu16: src = mkmemoper(ins->l); Movzxw: Xmovzxw(pcode, cls, reg2oper(ins->reg-1), src); break;
+ case Oloads32: src = mkmemoper(ins->l); Movsxl: Xmovsxl(pcode, cls, reg2oper(ins->reg-1), src); break;
+ case Oloadu32: src = mkmemoper(ins->l); Movzxl: Xmov(pcode, KI32, reg2oper(ins->reg-1), src); break;
+ case Oloadf32: case Oloadf64: Xmov(pcode, cls, reg2oper(ins->reg-1), mkmemoper(ins->l)); break;
+ case Oloadi64: Xmov(pcode, KI64, reg2oper(ins->reg-1), mkmemoper(ins->l)); break;
+ case Ocvtf32f64: X = Xcvtss2sd; goto FloatsCvt;
+ case Ocvtf64f32: X = Xcvtsd2ss; goto FloatsCvt;
+ case Ocvtf32s: X = Xcvttss2si; goto FloatsCvt;
+ case Ocvtf64s: X = Xcvttsd2si; goto FloatsCvt;
+ case Ocvts32f: X = cls == KF32 ? Xcvtsi2ss : Xcvtsi2sd; cls = KI32; goto FloatsCvt;
+ case Ocvts64f: X = cls == KF32 ? Xcvtsi2ss : Xcvtsi2sd; cls = KI64; goto FloatsCvt;
+ FloatsCvt:
+ X(pcode, cls, reg2oper(ins->reg-1), mkdatregoper(ins->l));
+ break;
+ case Oadd:
+ dst = mkregoper(ins->l);
+ if (kisflt(cls)) {
+ Xaddf(pcode, cls, dst, mkimmdatregoper(ins->r));
+ } else if (ins->reg-1 == dst.reg) { /* two-address add */
+ src = ref2oper(ins->r);
+ if (src.t == OIMM && src.imm < 0) /* ADD -imm -> SUB imm, for niceness */
+ Xsub(pcode, cls, dst, (src.imm = -(uint)src.imm, src));
+ else
+ Xadd(pcode, cls, dst, src);
+ } else if (isregref(ins->r) && ins->reg-1 == mkregoper(ins->r).reg) {
+ /* also two-address after swapping operands */
+ Xadd(pcode, cls, reg2oper(ins->reg-1), mkimmdatregoper(ins->l));
+ } else { /* three-address add (lea) */
+ struct oper mem = { OMEM, .base = NOBASE, .index = NOINDEX };
+ dst = reg2oper(ins->reg-1);
+ addmemoper(&mem, ref2oper(ins->l));
+ addmemoper(&mem, ref2oper(ins->r));
+ Xlea(pcode, cls, dst, mem);
+ }
+ break;
+ case Osub:
+ dst = mkregoper(ins->l);
+ if (kisflt(cls)) {
+ Xsubf(pcode, cls, dst, mkimmdatregoper(ins->r));
+ } else if (!ins->reg) {
+ Xcmp(pcode, cls, mkregoper(ins->l), mkimmdatregoper(ins->r));
+ } else if (ins->reg-1 == dst.reg) { /* two-address */
+ Xsub(pcode, cls, dst, ref2oper(ins->r));
+ } else {
+ assert(isintcon(ins->r));
+ Xlea(pcode, cls, reg2oper(ins->reg-1),
+ mkoper(OMEM, .base = mkregoper(ins->l).reg, .index = NOINDEX, .disp = -intconval(ins->r)));
+ }
+ break;
+ case Oshl:
+ dst = reg2oper(ins->reg-1);
+ src = mkregoper(ins->l);
+ if (dst.reg == src.reg)
+ Xshl(pcode, cls, dst, mkimmdatregoper(ins->r));
+ else {
+ uint sh = ins->r.i;
+ assert(ins->r.t == RICON && sh <= 3);
+ if (sh == 1) /* shl x, 1 -> lea [x + x] */
+ Xlea(pcode, cls, dst, mkoper(OMEM, .base = src.reg, .index = src.reg));
+ else /* shl x, n -> lea [x*(1<<n)+0x0] */
+ Xlea(pcode, cls, dst, mkoper(OMEM, .base = NOBASE, .index = src.reg, .shift = sh));
+ }
+ break;
+ case Osar: X = Xsar; goto ALU2;
+ case Oslr: X = Xshr; goto ALU2;
+ case Oand:
+ if (!ins->reg) {
+ Xtest(pcode, cls, mkregoper(ins->l), mkimmdatregoper(ins->r));
+ break;
+ }
+ X = Xand;
+ goto ALU2;
+ case Oxor: X = Xxor; goto ALU2;
+ case Oior: X = Xior; goto ALU2;
+ ALU2:
+ dst = mkregoper(ins->l);
+ assert(ins->reg-1 == dst.reg);
+ X(pcode, cls, dst, mkimmdatregoper(ins->r));
+ break;
+ case Oneg: X1 = Xneg; goto ALU1;
+ case Onot: X1 = Xnot; goto ALU1;
+ ALU1:
+ dst = mkregoper(ins->l);
+ assert(ins->reg-1 == dst.reg);
+ X1(pcode, cls, dst);
+ break;
+ case Obswap16:
+ dst = mkregoper(ins->l);
+ assert(ins->reg-1 == dst.reg);
+ if (dst.reg < 4) { /* AX,BX,CX,DX */
+ /* XCHG rH, rL */
+ B(0x86), B(0xC4 | dst.reg | (dst.reg)<<3);
+ } else {
+ /* ROL r16,8 */
+ Xrolw(pcode, KI32, dst, mkoper(OIMM, .imm = 8));
+ }
+ break;
+ case Obswap32: case Obswap64: X1 = Xbswap; goto ALU1;
+ case Omul:
+ if (kisint(cls))
+ Ximul(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->l), ref2oper(ins->r));
+ else
+ Xmulf(pcode, cls, reg2oper(ins->reg-1), ref2oper(ins->r));
+ break;
+ case Odiv:
+ switch (cls) {
+ default: assert(0);
+ case KPTR:
+ case KI64: B(0x48); /* REX.W */
+ case KI32: B(0x99); /* CDQ/CQO */
+ assert(mkregoper(ins->l).reg == RAX);
+ Xidiv(pcode, cls, mkdatregoper(ins->r));
+ break;
+ case KF32: case KF64:
+ Xdivf(pcode, cls, reg2oper(ins->reg-1), mkdatregoper(ins->r));
+ break;
+ }
+ break;
+ case Oudiv:
+ DS("\x31\xD2"); /* XOR EDX,EDX */
+ assert(mkregoper(ins->l).reg == RAX);
+ Xdiv(pcode, cls, mkdatregoper(ins->r));
+ break;
+ case Oequ: case Oneq:
+ case Olth: case Ogth: case Olte: case Ogte:
+ case Oulth: case Ougth: case Oulte: case Ougte:
+ dst = mkregoper(ins->l);
+ src = ref2oper(ins->r);
+ regzeroed = 0;
+ if (ins->reg && dst.reg != ins->reg-1 && (src.t != OREG || src.reg != ins->reg-1)) {
+ /* can zero output reg before test instruction (differs from both inputs) */
+ /* XXX this doesn't check if a source operand is an addr containing the register */
+ struct oper dst = reg2oper(ins->reg-1);
+ Xxor(pcode, KI32, dst, dst);
+ regzeroed = 1;
+ }
+ if (kisint(ins->cls) && ins->r.bits == ZEROREF.bits)
+ Xtest(pcode, cls, dst, dst);
+ else
+ Xcmp(pcode, cls, dst, src);
+ if (ins->reg) {
+ enum cc cc;
+ dst = reg2oper(ins->reg-1);
+ if (ins->r.bits != ZEROREF.bits) { /* CMP */
+ cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op];
+ } else { /* TEST r,r (CMP r, 0) */
+ assert(kisint(ins->cls));
+ cc = icmpzero2cc[ins->op];
+ }
+ if (kisflt(ins->cls)) { /* handle float unordered result */
+ int unordres = ins->op == Oneq ? 1 : 0;
+ int rex = 0;
+ if (in_range(dst.reg, RSP, RDI)) rex = 0x40;
+ rex |= (dst.reg >> 3); /* REX.B */
+ int jpoff = 3 + (rex != 0);
+ if (regzeroed && unordres == 0) {
+ /* if cmp unordered, just jump over the SETcc; result reg was already zeroed */
+ B(0x7A), B(jpoff); /* JP <off> */
+ } else {
+ /* JNP .a
+ * MOV r8, 0/1
+ * JMP .b
+ * .a: SETcc r8
+ * .b: MOVZX r, r8
+ */
+ B(0x7B), B(jpoff+1); /* JNP <off> */
+ if (rex) B(rex | 0x40);
+ B(0xB0 + (dst.reg & 7)), B(unordres); /* MOV r8, 0/1 */
+ B(0xEB), B(jpoff); /* JMP <off> */
+ }
+ }
+ Xsetcc(pcode, cc, dst.reg);
+ if (!regzeroed)
+ Xmovzxb(pcode, KI32, dst, dst);
+ }
+ break;
+ case Oswap:
+ if (kisint(cls))
+ Xxchg(pcode, cls, ref2oper(ins->l), mkregoper(ins->r));
+ else {
+ struct oper l = mkregoper(ins->l), r = mkregoper(ins->r);
+ Xxor(pcode, cls, l, r);
+ Xxor(pcode, cls, r, l);
+ Xxor(pcode, cls, l, r);
+ }
+ break;
+ case Ocall:
+ Xcall(pcode, KPTR, ref2oper(ins->l));
+ break;
+ case Oxvaprologue:
+ Xvaprologue(pcode, fn, mkmemoper(ins->l));
+ break;
+ }
+}
+
+static void
+emitbranch(uchar **pcode, struct block *blk)
+{
+ enum cc cc = ALWAYS;
+ assert(blk->s1);
+ if (blk->s2) {
+ /* conditional branch.. */
+ union ref arg = blk->jmp.arg[0];
+ struct block *unord = NULL;
+ assert(arg.t == RTMP);
+ struct instr *ins = &instrtab[arg.i];
+ if ((oiscmp(ins->op) || ins->op == Oand || ins->op == Osub)) {
+ if (ins->r.bits != ZEROREF.bits) {
+ /* for CMP instr */
+ cc = (kisint(ins->cls) ? icmpop2cc : fcmpop2cc)[ins->op];
+ unord = ins->op == Oneq ? blk->s1 : blk->s2;
+ } else {
+ assert(kisint(ins->cls));
+ /* for TEST instr, which modifies ZF and SF and sets CF = OF = 0 */
+ cc = icmpzero2cc[ins->op];
+ }
+ } else {
+ /* implicit by ZF */
+ cc = CCNZ;
+ }
+ if (kisflt(ins->cls)) {
+ /* handle float unordered result */
+ Xjcc(pcode, CCP, unord);
+ }
+ if (blk->s1 == blk->lnext) {
+ /* if s1 is next adjacent block, swap s1,s2 and flip condition to emit a
+ * single jump */
+ struct block *tmp = blk->s1;
+ blk->s1 = blk->s2;
+ blk->s2 = tmp;
+ cc ^= 1;
+ }
+ }
+ /* make sure to fallthru if jumping to next adjacent block */
+ if (blk->s2 || blk->s1 != blk->lnext)
+ Xjcc(pcode, cc, blk->s1);
+ if (blk->s2 && blk->s2 != blk->lnext)
+ Xjcc(pcode, ALWAYS, blk->s2);
+}
+
+static bool
+calleesave(int *npush, uchar **pcode, struct function *fn)
+{
+ bool any = 0;
+ if (rstest(fn->regusage, RBX)) {
+ Xpush(pcode, RBX);
+ ++*npush;
+ any = 1;
+ }
+ for (int r = R12; r <= R15; ++r)
+ if (rstest(fn->regusage, r)) {
+ Xpush(pcode, r);
+ ++*npush;
+ any = 1;
+ }
+ return any;
+}
+
+static void
+calleerestore(uchar **pcode, struct function *fn)
+{
+ for (int r = R15; r >= R12; --r)
+ if (rstest(fn->regusage, r))
+ Xpop(pcode, r);
+ if (rstest(fn->regusage, RBX)) Xpop(pcode, RBX);
+}
+
+/* align code using NOPs */
+static void
+nops(uchar **pcode, int align)
+{
+ int rem;
+ while ((rem = (*pcode - objout.textbegin) & (align - 1)) != 0) {
+ switch (align - rem) {
+ case 15: case 14: case 13: case 12: case 11: case 10:
+ case 9: B(0x66);
+ case 8: DS("\x0f\x1f\x84\x00\x00\x00\x00\x00"); break;
+ case 7: DS("\x0f\x1f\x80\x00\x00\x00\x00"); break;
+ case 6: B(0x66);
+ case 5: DS("\x0f\x1f\x44\x00\x00"); break;
+ case 4: DS("\x0f\x1f\x40\x00"); break;
+ case 3: DS("\x0f\x1f\00"); break;
+ case 2: B(0x66);
+ case 1: B(0x90); break;
+ }
+ }
+}
+
+static void
+emitbin(struct function *fn)
+{
+ struct block *blk;
+ uchar **pcode = &objout.code;
+ int npush = 0;
+ bool saverestore;
+
+ nops(pcode, 16);
+ fnstart = *pcode;
+ curfnsym = fn->name;
+
+ /** prologue **/
+
+ /* only use frame pointer in non-leaf functions and functions that use the stack */
+ usebp = 0;
+ if (!fn->isleaf || fn->stksiz) {
+ usebp = 1;
+ /* push rbp; mov rbp, rsp */
+ DS("\x55\x48\x89\xE5");
+ }
+ saverestore = calleesave(&npush, pcode, fn);
+ if (usebp) rbpoff = -npush*8;
+
+ /* ensure stack is 16-byte aligned for function calls */
+ if (!fn->isleaf && ((fn->stksiz + npush*8) & 0xF) != 0) {
+ assert(usebp);
+ if ((rbpoff & 0xF) == 0) {
+ rbpoff -= 16;
+ fn->stksiz += 24;
+ } else {
+ rbpoff -= 8;
+ fn->stksiz += 8;
+ }
+ }
+
+ if (fn->stksiz != 0) {
+ /* sub rsp, <stack size> */
+ if (fn->stksiz < 128)
+ DS("\x48\x83\xEC"), B(fn->stksiz);
+ else if (fn->stksiz == 128)
+ DS("\x48\x83\xC4\x80"); /* add rsp, -128 */
+ else
+ DS("\x48\x81\xEC"), I32(fn->stksiz);
+ }
+
+ if (*pcode - fnstart > 6) {
+ /* largue prologue -> largue epilogue -> transform to use single exit point */
+ struct block *exit = NULL;
+ blk = fn->entry->lprev;
+ do {
+ if (blk->jmp.t == Jret) {
+ if (!exit) {
+ if (blk->ins.n == 0) {
+ exit = blk;
+ continue;
+ } else {
+ exit = newblk(fn);
+ exit->lnext = blk->lnext;
+ exit->lprev = blk;
+ blk->lnext = exit;
+ exit->lnext->lprev = exit;
+ exit->id = fn->nblk++;
+ exit->jmp.t = Jret;
+ }
+ }
+ blk->jmp.t = Jb;
+ memset(blk->jmp.arg, 0, sizeof blk->jmp.arg);
+ blk->s1 = exit;
+ } else if (exit) {
+ /* thread jumps to the exit block */
+ if (blk->s1 && !blk->s1->ins.n && blk->s1->s1 == exit && !blk->s1->s2) blk->s1 = exit;
+ if (blk->s2 && !blk->s2->ins.n && blk->s2->s1 == exit && !blk->s2->s2) blk->s2 = exit;
+ }
+ } while ((blk = blk->lprev) != fn->entry);
+ }
+
+ blkaddr = allocz(fn->passarena, fn->nblk * sizeof *blkaddr, 0);
+
+ blk = fn->entry;
+ do {
+ struct blkaddr *bb = &blkaddr[blk->id];
+ uint bbaddr = *pcode - objout.textbegin;
+ assert(!bb->resolved);
+ while (bb->relreloc) {
+ uint next;
+ memcpy(&next, objout.textbegin + bb->relreloc, 4);
+ int disp = bbaddr - bb->relreloc - 4;
+ wr32le(objout.textbegin + bb->relreloc, disp);
+ bb->relreloc = next;
+ }
+ bb->resolved = 1;
+ bb->addr = bbaddr;
+
+ for (int i = 0; i < blk->ins.n; ++i)
+ emitinstr(pcode, fn, blk, i, &instrtab[blk->ins.p[i]]);
+
+ if (blk->jmp.t == Jret) {
+ if (blk->lnext != fn->entry && blk->lnext->jmp.t == Jret && blk->lnext->ins.n == 0)
+ continue; /* fallthru to next blk's RET */
+ /* epilogue */
+ if (fn->stksiz && (saverestore || !usebp))
+ Xadd(pcode, KPTR, mkoper(OREG, .reg = RSP), mkoper(OIMM, .imm = fn->stksiz));
+ if (saverestore)
+ calleerestore(pcode, fn);
+ if (usebp) B(0xC9); /* leave */
+ B(0xC3); /* ret */
+ } else if (blk->jmp.t == Jtrap) {
+ DS("\x0F\x0B"); /* UD2 */
+ } else emitbranch(pcode, blk);
+ } while ((blk = blk->lnext) != fn->entry);
+ objdeffunc(fn->name, fn->globl, fnstart - objout.textbegin, *pcode - fnstart);
+}
+
+void
+x86_64_emit(struct function *fn)
+{
+ fn->stksiz = alignup(fn->stksiz, 8);
+ if (fn->stksiz > 1<<24) error(NULL, "'%s' stack frame too big", fn->name);
+ emitbin(fn);
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_x86-64_isel.c b/src/t_x86-64_isel.c
new file mode 100644
index 0000000..4b4a099
--- /dev/null
+++ b/src/t_x86-64_isel.c
@@ -0,0 +1,652 @@
+#include "all.h"
+#include "../endian.h"
+
+enum flag {
+ ZF = 1 << 0,
+ SF = 1 << 1,
+ CF = 1 << 2,
+ OF = 1 << 3,
+ CLOBF = 1 << 4,
+};
+
+/* flags modified by each integer op */
+static const uchar opflags[NOPER] = {
+ [Oneg] = ZF|CLOBF,
+ [Oadd] = ZF|CLOBF,
+ [Osub] = ZF|CLOBF,
+ [Omul] = CLOBF,
+ [Odiv] = CLOBF,
+ [Oudiv] = CLOBF,
+ [Orem] = CLOBF,
+ [Ourem] = CLOBF,
+ [Oand] = ZF|CLOBF,
+ [Oior] = ZF|CLOBF,
+ [Oxor] = ZF|CLOBF,
+ [Oshl] = ZF|CLOBF,
+ [Osar] = ZF|CLOBF,
+ [Oslr] = ZF|CLOBF,
+ [Oequ] = ZF|CLOBF,
+ [Oneq] = ZF|CLOBF,
+ [Olth] = ZF|CLOBF,
+ [Ogth] = ZF|CLOBF,
+ [Olte] = ZF|CLOBF,
+ [Ogte] = ZF|CLOBF,
+ [Oulth] = ZF|CLOBF,
+ [Ougth] = ZF|CLOBF,
+ [Oulte] = ZF|CLOBF,
+ [Ougte] = ZF|CLOBF,
+ [Ocall] = CLOBF,
+};
+
+static int iflagsrc = -1;
+
+#define inscopy(blk, pcuri, k, r) insertinstr((blk), (*(pcuri))++, mkinstr(Ocopy, k, .l = (r)))
+static void
+picfixsym(union ref *r, struct block *blk, int *curi)
+{
+ if (!ccopt.pic || !isaddrcon(*r,0)) return;
+ *r = inscopy(blk, curi, KPTR, *r);
+}
+
+static void
+fixarg(union ref *r, struct instr *ins, struct block *blk, int *curi)
+{
+ int sh;
+ enum op op = ins ? ins->op : 0;
+ enum irclass cls = ins ? ins->cls : 0;
+
+Begin:
+ if (r->t == RXCON) {
+ struct xcon *con = &contab.p[r->i];
+ if (in_range(op, Oshl, Oslr) && r == &ins->r) {
+ sh = con->i;
+ goto ShiftImm;
+ } else if (cls == KI32 && in_range(con->cls, KI64, KPTR)) {
+ *r = mkintcon(KI32, (int)con->i);
+ goto Begin;
+ }
+ if (in_range(op, Oadd, Osub) && con->i == 2147483648 && r == &ins->r) {
+ /* add X, INT32MAX+1 -> sub X, INT32MIN */
+ ins->op = Oadd + (op == Oadd);
+ *r = mkintcon(KI32, -2147483648);
+ } else if (kisflt(con->cls) && con->i == 0) {
+ /* copy of positive float zero -> regular zero, that emit() will turn into xor x,x */
+ if (in_range(op, Ocopy, Omove) || op == Ophi)
+ *r = ZEROREF;
+ else
+ *r = inscopy(blk, curi, con->cls, ZEROREF);
+ } else if (con->cls >= KI64) {
+ /* float immediates & 64bit immediates are loaded from memory */
+ uchar data[8];
+ uint ksiz = cls2siz[con->cls];
+ union type ctype;
+ /* can't use memory arg in rhs if lhs is memory */
+ bool docopy = ins && &ins->l != r && (oisstore(ins->op) || ins->l.t == RADDR);
+ if (con->cls <= KPTR && (in_range(op, Ocopy, Omove) || op == Ophi))
+ /* in this case we can use movabs */
+ return;
+ else if (!docopy || con->cls >= KF32) {
+ if (con->cls != KF32) {
+ wr64le(data, con->i);
+ ctype = mktype(con->cls == KF64 ? TYDOUBLE : TYVLONG);
+ } else {
+ union { float f; int i; } pun = { con->f };
+ wr32le(data, pun.i);
+ ctype = mktype(TYFLOAT);
+ }
+ *r = mkdatref(NULL, ctype, ksiz, /*align*/ksiz, data, ksiz, /*deref*/1 , /*funclocal*/1);
+ }
+ if (docopy)
+ *r = inscopy(blk, curi, con->cls, *r);
+ } else if (op != Omove && ins && isaddrcon(*r,0) && r == &ins->r) {
+ *r = inscopy(blk, curi, KPTR, *r);
+ } else if (in_range(op, Odiv, Ourem) && kisint(ins->cls))
+ goto DivImm;
+ } else if (r->t == RICON && in_range(op, Odiv, Ourem) && kisint(ins->cls) && r == &ins->r) {
+ DivImm: /* there is no division by immediate, must be copied to a register */
+ *r = inscopy(blk, curi, ins->cls, *r);
+ } else if (r->t == RICON && in_range(op, Oshl, Oslr) && r == &ins->r) {
+ sh = r->i;
+ ShiftImm: /* shift immediate is always 8bit */
+ *r = mkref(RICON, sh & 255);
+ } else if (r->t == RSTACK) {
+ struct instr adr = mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkintcon(KI32, -r->i));
+ if (op == Ocopy)
+ *ins = adr;
+ else
+ *r = insertinstr(blk, (*curi)++, adr);
+ } else if (r->bits == UNDREF.bits && ins && !in_range(op, Ocopy, Omove) && op != Ophi) {
+ *r = inscopy(blk, curi, ins->cls, *r);
+ }
+ picfixsym(r, blk, curi);
+}
+
+#define isimm32(r) (iscon(r) && concls(r) == KI32)
+
+static void
+selcall(struct function *fn, struct instr *ins, struct block *blk, int *curi)
+{
+ const struct call *call = &calltab.p[ins->r.i];
+ int iarg = *curi - 1;
+ enum irclass cls;
+ uint argstksiz = alignup(call->argstksiz, 16);
+ int nsse = 0;
+
+ for (int i = call->narg - 1; i >= 0; --i) {
+ struct abiarg abi = call->abiarg[i];
+ struct instr *arg;
+ for (;; --iarg) {
+ assert(iarg >= 0 && i >= 0 && "arg?");
+ if ((arg = &instrtab[blk->ins.p[iarg]])->op == Oarg)
+ break;
+ }
+
+ if (!abi.isstk) {
+ assert(!abi.ty.isagg);
+ *arg = mkinstr(Omove, call->abiarg[i].ty.cls, mkref(RREG, abi.reg), arg->r);
+ if (abi.reg >= XMM0) ++nsse;
+ } else {
+ union ref adr = mkaddr((struct addr){mkref(RREG, RSP), .disp = abi.stk});
+ int iargsave = iarg;
+ if (!abi.ty.isagg) { /* scalar arg in stack */
+ *arg = mkinstr(cls2store[abi.ty.cls], 0, adr, arg->r);
+ if (isaddrcon(arg->r,1) || arg->r.t == RADDR)
+ arg->r = insertinstr(blk, iarg++, mkinstr(Ocopy, abi.ty.cls, arg->r));
+ else
+ fixarg(&ins->r, ins, blk, &iarg);
+ } else { /* aggregate arg in stack, callee stack frame destination address */
+ *arg = mkinstr(Ocopy, KPTR, adr);
+ }
+ *curi += iarg - iargsave;
+ }
+ }
+ if (call->argstksiz) {
+ union ref disp = mkref(RICON, argstksiz);
+ insertinstr(blk, iarg--, (struct instr){Osub, KPTR, .keep=1, .reg = RSP+1, .l=mkref(RREG,RSP), disp});
+ ++*curi;
+ insertinstr(blk, *curi+1, (struct instr){Oadd, KPTR, .keep=1, .reg = RSP+1, .l=mkref(RREG,RSP), disp});
+ }
+ if (isimm32(ins->l))
+ ins->l = mkaddr((struct addr){.base = ins->l});
+ else if (isintcon(ins->l))
+ ins->l = inscopy(blk, curi, KPTR, ins->l);
+
+ if (call->vararg >= 0) {
+ /* variadic calls write number of sse regs used to AL */
+ insertinstr(blk, (*curi)++, mkinstr(Omove, KI32, mkref(RREG, RAX), mkref(RICON, nsse), .keep=1));
+ }
+ cls = ins->cls;
+ ins->cls = 0;
+ if (cls) {
+ /* duplicate to reuse same TMP ref */
+ insertinstr(blk, (*curi)++, *ins);
+ *ins = mkinstr(Ocopy, cls, mkref(RREG, call->abiret[0].reg));
+ for (int i = 1; i <= 2; ++i) {
+ if (*curi + i >= blk->ins.n) break;
+ if (instrtab[blk->ins.p[*curi + i]].op == Ocall2r) {
+ ins = &instrtab[blk->ins.p[*curi += i]];
+ *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, call->abiret[1].reg));
+ break;
+ }
+ }
+ }
+}
+
+static bool
+aimm(struct addr *addr, vlong disp)
+{
+ vlong a = addr->disp;
+ a += disp;
+ if ((int)a == a) {
+ addr->disp = a;
+ return 1;
+ }
+ return 0;
+}
+
+static bool
+ascale(struct addr *addr, union ref a, union ref b)
+{
+ if (b.t != RICON) return 0;
+ if (addr->index.bits) return 0;
+ if ((unsigned)b.i > 3) return 0;
+ if (a.t == RREG) {
+ Scaled:
+ addr->index = a;
+ addr->shift = b.i;
+ return 1;
+ } else if (a.t == RTMP) {
+ struct instr *ins = &instrtab[a.i];
+ /* factor out shifted immediate from 'shl {add %x, imm}, s' */
+ /* XXX maybe we shouldn't do this here because it should be done by a generic
+ * arithemetic optimization pass ? */
+ if (ins->op == Oadd && (ins->l.t == RREG || ins->l.t == RTMP) && isintcon(ins->r)) {
+ vlong a = ((vlong) addr->disp + intconval(ins->r)) * (1 << b.i);
+ if (a != (int) a) return 0;
+ addr->disp = a;
+ addr->index = ins->l;
+ addr->shift = b.i;
+ return 1;
+ } else {
+ goto Scaled;
+ }
+ }
+ return 0;
+}
+
+static bool
+aadd(struct addr *out, struct block *blk, int *curi, union ref r, bool recurring)
+{
+ if (r.t == RSTACK) {
+ if (out->base.bits || !aimm(out, -r.i)) {
+ r = insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, -r.i)));
+ goto Ref;
+ }
+ out->base = mkref(RREG, RBP);
+ } else if (r.t == RTMP) {
+ struct instr *ins = &instrtab[r.i];
+ struct addr adr = {0};
+ if (ins->op == Oadd) {
+ if (recurring) goto Ref;
+ if (aadd(&adr, blk, curi, ins->l, 1) && aadd(&adr, blk, curi, ins->r, 1)) {
+ Add2:;
+ int n1 = !!out->base.bits + !!out->index.bits;
+ int n2 = !!adr.base.bits + !!adr.index.bits;
+ vlong off = (vlong) out->disp + adr.disp;
+ if (n1+n2 > 2 || (int)off != off) goto Ref;
+ if (n1 == 0) {
+ *out = adr;
+ } else if (n1 == 1 && n2 == 1) {
+ if (!out->index.bits) {
+ out->index = adr.index.bits ? adr.index : adr.base;
+ out->shift = adr.index.bits ? adr.shift : 0;
+ } else {
+ if (adr.index.bits && adr.shift) return 0;
+ out->base = adr.index.bits ? adr.index : adr.base;
+ }
+ } else assert(n1 <= 2 && n2 == 0);
+ out->disp = off;
+ ins->skip = 1;
+ } else return 0;
+ } else if (ins->op == Ocopy && ins->l.t == RADDR) {
+ const struct addr *adr2 = &addrtab.p[ins->l.i];
+ adr = *adr2;
+ goto Add2;
+ } else if (ins->op == Oshl) {
+ if (!ascale(out, ins->l, ins->r)) goto Ref;
+ ins->skip = 1;
+ } else goto Ref;
+ } else if (isnumcon(r)) {
+ assert(isintcon(r));
+ return aimm(out, intconval(r));
+ } else if (isaddrcon(r,1)) {
+ if (!out->base.bits && !isaddrcon(out->index,1)) out->base = r;
+ else return 0;
+ } else if (r.t == RREG) {
+ /* temporaries are single assignment, but register aren't, so they can't be *
+ * safely hoisted into an address value, unless they have global lifetime */
+ if (!rstest(mctarg->rglob, r.i)) return 0;
+ Ref:
+ if (!out->base.bits) out->base = r;
+ else if (!out->index.bits) out->index = r;
+ else return 0;
+ } else return 0;
+ return 1;
+}
+
+static bool
+fuseaddr(union ref *r, struct block *blk, int *curi)
+{
+ struct addr addr = { 0 };
+
+ if (isaddrcon(*r,1)) return 1;
+ if (!aadd(&addr, blk, curi, *r, 0)) return 0;
+
+ if (isaddrcon(addr.base,0) && (ccopt.pic || (ccopt.pie && addr.index.bits) || (contab.p[addr.base.i].flag & SFUNC))) {
+ /* pic needs to load from GOT */
+ /* pie cannot encode RIP-relative address with index register */
+ /* first load symbol address into a temp register */
+ union ref temp = mkaddr((struct addr){.base = addr.base, .disp = ccopt.pic ? 0 : addr.disp});
+ addr.base = inscopy(blk, curi, KPTR, temp);
+ if (!ccopt.pic) addr.disp = 0;
+ }
+
+ if (!addr.base.bits) {
+ /* absolute int address in disp */
+ if (addr.index.bits) return 0;
+ addr.base = mkintcon(KPTR, addr.disp);
+ addr.disp = 0;
+ }
+
+ *r = mkaddr(addr);
+ return 1;
+}
+
+/* is add instruction with this arg a candidate to transform into efective addr? */
+static bool
+addarg4addrp(union ref r)
+{
+ struct instr *ins;
+ if (isaddrcon(r, 0)) return 1;
+ if (r.t == RSTACK) return 1;
+ if (r.t != RTMP) return 0;
+ ins = &instrtab[r.i];
+ return (ins->op == Ocopy && ins->l.t == RADDR) || ins->op == Oadd || ins->op == Oshl;
+}
+
+static void
+loadstoreaddr(struct block *blk, union ref *r, int *curi)
+{
+ if (isimm32(*r)) {
+ *r = mkaddr((struct addr){.base = *r});
+ } else if (isaddrcon(*r, 0)) {
+ picfixsym(r, blk, curi);
+ } else if (r->t == RSTACK || (r->t == RTMP && addarg4addrp(*r))) {
+ fuseaddr(r, blk, curi);
+ } else if (r->t != RREG) {
+ *r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, *r));
+ }
+}
+
+static bool
+arithfold(struct instr *ins)
+{
+ if (isnumcon(ins->l) && (!ins->r.t || isnumcon(ins->r))) {
+ union ref r;
+ bool ok = ins->r.t ? foldbinop(&r, ins->op, ins->cls, ins->l, ins->r) : foldunop(&r, ins->op, ins->cls, ins->l);
+ if (ok) {
+ *ins = mkinstr(Ocopy, insrescls(*ins), r);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void
+sel(struct function *fn, struct instr *ins, struct block *blk, int *curi)
+{
+ int t = ins - instrtab;
+ struct instr temp = {0};
+ enum op op = ins->op;
+
+ if (oisarith(ins->op) && arithfold(ins)) {
+ fixarg(&ins->l, ins, blk, curi);
+ return;
+ }
+
+ switch (op) {
+ default: assert(0);
+ case Onop: break;
+ case Oalloca1: case Oalloca2: case Oalloca4: case Oalloca8: case Oalloca16:
+ assert(!"unlowered alloca");
+ break;
+ case Oparam:
+ assert(ins->l.t == RICON && ins->l.i < fn->nabiarg);
+ if (!fn->abiarg[ins->l.i].isstk)
+ *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, fn->abiarg[ins->l.i].reg));
+ else /* stack */
+ *ins = mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, 16+fn->abiarg[ins->l.i].stk));
+ break;
+ case Oarg:
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Ocall:
+ selcall(fn, ins, blk, curi);
+ break;
+ case Ointrin:
+ break;
+ case Oshl: case Osar: case Oslr:
+ if (!isintcon(ins->r)) {
+ /* shift amount register is always CL */
+ insertinstr(blk, (*curi)++, mkinstr(Omove, KI32, mkref(RREG, RCX), ins->r));
+ ins->r = mkref(RREG, RCX);
+ }
+ goto ALU;
+ case Oequ: case Oneq:
+ case Olth: case Ogth: case Olte: case Ogte:
+ case Oulth: case Ougth: case Oulte: case Ougte:
+ if (iscon(ins->l) && !iscon(ins->r)) {
+ /* lth imm, x -> gth x, imm */
+ if (!in_range(ins->op, Oequ, Oneq))
+ ins->op = ((op - Olth) ^ 1) + Olth;
+ rswap(ins->l, ins->r);
+ }
+ if (ins->l.t != RTMP && ins->l.t != RREG && ins->l.t != RSTACK)
+ ins->l = inscopy(blk, curi, ins->cls, ins->l);
+ else
+ fixarg(&ins->l, ins, blk, curi);
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Odiv: case Oudiv: case Orem: case Ourem:
+ if (kisflt(ins->cls)) goto ALU;
+ /* TODO fuse div/rem pair */
+
+ /* (I)DIV dividend is always in RDX:RAX, output also in those regs */
+ insertinstr(blk, (*curi)++, mkinstr(Omove, ins->cls, mkref(RREG, RAX), ins->l));
+ /* mark RDX as clobbered. sign/zero-extending RAX into RDX is handled in emit() */
+ insertinstr(blk, (*curi)++, mkinstr(Omove, ins->cls, mkref(RREG, RDX), mkref(RREG, RDX)));
+ fixarg(&ins->r, ins, blk, curi); /* make sure rhs is memory or reg */
+ ins->l = mkref(RREG, RAX);
+ ins->keep = 1;
+ if (op == Orem) ins->op = Odiv;
+ else if (op == Ourem) ins->op = Oudiv;
+ insertinstr(blk, (*curi)++, *ins); /* duplicate ins to reuse tmp ref */
+ *ins = mkinstr(Ocopy, ins->cls, mkref(RREG, op < Orem ? RAX : RDX)); /* get output */
+ temp = mkinstr(Ocopy, ins->cls, mkref(RREG, op < Orem ? RDX : RAX)); /* clobber other reg*/
+ insertinstr(blk, ++(*curi), temp);
+ /* swap instrs so that clobber goes first */
+ t = blk->ins.p[*curi - 1];
+ blk->ins.p[*curi - 1] = blk->ins.p[*curi - 0];
+ blk->ins.p[*curi - 0] = t;
+ break;
+ case Osub:
+ if (isintcon(ins->l)) {
+ /* sub imm, x -> sub x, imm; neg x */
+ fixarg(&ins->l, ins, blk, curi);
+ ins->inplace = 1;
+ struct instr sub = *ins;
+ rswap(sub.l, sub.r);
+ ins->op = op = Oneg;
+ ins->l = insertinstr(blk, (*curi)++, sub);
+ ins->r = NOREF;
+ goto ALU;
+ } else if (kisint(ins->cls) && isintcon(ins->r)) {
+ ins->op = op = Oadd;
+ ins->r = mkintcon(concls(ins->r), -(uvlong)intconval(ins->r));
+ } else {
+ goto ALU;
+ }
+ /* fallthru */
+ case Oadd:
+ if (kisint(ins->cls) && (addarg4addrp(ins->l) || addarg4addrp(ins->r))) {
+ union ref it = mkref(RTMP, ins - instrtab);
+ if (fuseaddr(&it, blk, curi)) {
+ *ins = mkinstr(Ocopy, ins->cls, it);
+ break;
+ }
+ }
+ /* fallthru */
+ case Omul:
+ case Oand: case Oxor: case Oior:
+ /* commutative ops */
+ if (iscon(ins->l))
+ rswap(ins->l, ins->r);
+ goto ALU;
+ case Oneg:
+ if (kisflt(ins->cls)) {
+ /* flip sign bit with XORPS/D */
+ static const uvlong sd[2] = {0x8000000000000000,0x8000000000000000};
+ static const uint sf[4] = {0x80000000,80000000,0x80000000,80000000};
+ ins->op = Oxor;
+ ins->r = mkdatref(NULL, mktype(ins->cls == KF32 ? TYFLOAT : TYDOUBLE), /*siz*/16,
+ /*align*/16, ins->cls == KF32 ? (void *)sf : sd, /*siz*/16, /*deref*/1, /*funclocal*/1);
+ }
+ /* fallthru */
+ case Onot:
+ ALU:
+ if (!(op == Oadd && kisint(ins->cls))) /* 3-address add is lea */
+ if (!(op == Omul && kisint(ins->cls) && isimm32(ins->r))) /* for (I)MUL r,r/m,imm */
+ if (!(op == Oshl && ins->r.t == RICON && ins->r.i <= 3)) /* can be lea */
+ ins->inplace = 1;
+ if (iscon(ins->l) || ins->l.t == RSTACK) {
+ fixarg(&ins->l, ins, blk, curi);
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(Ocopy, ins->cls, ins->l));
+ }
+ if (ins->r.bits)
+ case Omove:
+ fixarg(&ins->r, ins, blk, curi);
+ if (op == Oadd && isaddrcon(ins->r,1)) /* no 3-address add if rhs is mem */
+ ins->inplace = 1;
+ break;
+ case Oloads8: case Oloadu8: case Oloads16: case Oloadu16:
+ case Oloads32: case Oloadu32: case Oloadi64: case Oloadf32: case Oloadf64:
+ loadstoreaddr(blk, &ins->l, curi);
+ break;
+ case Ostorei8: case Ostorei16: case Ostorei32: case Ostorei64: case Ostoref32: case Ostoref64:
+ loadstoreaddr(blk, &ins->l, curi);
+ if (isaddrcon(ins->r,1) || ins->r.t == RADDR)
+ ins->r = insertinstr(blk, (*curi)++, mkinstr(Ocopy, KPTR, ins->r));
+ else
+ fixarg(&ins->r, ins, blk, curi);
+ break;
+ case Ocvtu32f:
+ fixarg(&ins->l, ins, blk, curi);
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(Oextu32, KI64, ins->l));
+ ins->op = Ocvts64f;
+ break;
+ case Ocvtf32u: case Ocvtf64u:
+ fixarg(&ins->l, ins, blk, curi);
+ if (ins->cls == KI32) {
+ ins->l = insertinstr(blk, (*curi)++, mkinstr(ins->op == Ocvtf32u ? Ocvtf32s : Ocvtf64s, KI64, ins->l));
+ ins->op = Oextu32;
+ } else assert(!"nyi flt -> u64");
+ break;
+ case Oextu32:
+ if (ins->l.t == RTMP && insrescls(instrtab[ins->l.i]) == KI32 && instrtab[ins->l.i].op != Ocopy) {
+ /* no need to explicitly zero extend 32 -> 64bit regs in x86-64 */
+ /* this copy can be optimized away in regalloc */
+ ins->op = op = Ocopy;
+ ins->cls = KI32;
+ }
+ /* fallthru */
+ case Ocvtf32f64: case Ocvtf64f32: case Ocvtf32s: case Ocvtf64s: case Ocvts32f: case Ocvts64f:
+ case Ocvtu64f:
+ case Oexts8: case Oextu8: case Oexts16: case Oextu16: case Oexts32:
+ if (isnumcon(ins->l)) {
+ union ref it;
+ bool ok = foldunop(&it, ins->op, ins->cls, ins->l);
+ assert(ok);
+ ins->op = Ocopy;
+ ins->l = it;
+ break;
+ }
+ case Ocopy:
+ fixarg(&ins->l, ins, blk, curi);
+ break;
+ case Obswap16: case Obswap32: case Obswap64:
+ ins->inplace = 1;
+ if (ins->l.t != RTMP) {
+ ins->l = insertinstr(blk, *curi, mkinstr(Ocopy, ins->cls, ins->l));
+ fixarg(&instrtab[ins->l.i].l, ins, blk, curi);
+ ++*curi;
+ }
+ break;
+ case Oxvaprologue:
+ fuseaddr(&ins->l, blk, curi);
+ assert(ins->l.t == RADDR);
+ /* !this must be the first instruction */
+ assert(*curi == 1);
+ assert(blk == fn->entry);
+ t = blk->ins.p[0];
+ blk->ins.p[0] = blk->ins.p[1];
+ blk->ins.p[1] = t;
+ break;
+ }
+}
+
+static void
+seljmp(struct function *fn, struct block *blk)
+{
+ if (blk->jmp.t == Jb && blk->jmp.arg[0].bits) {
+ int curi = blk->ins.n;
+ fixarg(&blk->jmp.arg[0], NULL, blk, &curi);
+ union ref c = blk->jmp.arg[0];
+ if (c.t != RTMP) {
+ enum irclass cls = c.t == RICON ? KI32 : c.t == RXCON && contab.p[c.i].cls ? contab.p[c.i].cls : KPTR;
+ int curi = blk->ins.n;
+
+ c = insertinstr(blk, blk->ins.n, mkinstr(Ocopy, cls, c));
+ sel(fn, &instrtab[c.i], blk, &curi);
+ }
+ if (iflagsrc == c.i /* test cmp */
+ && (oiscmp(instrtab[c.i].op) || instrtab[c.i].op == Oand || instrtab[c.i].op == Osub)) {
+ instrtab[c.i].keep = 1;
+ } else {
+ if (kisflt(instrtab[c.i].cls) || !(opflags[instrtab[c.i].op] & ZF) || blk->ins.n == 0 || c.i != blk->ins.p[blk->ins.n - 1]) {
+ struct instr *ins;
+ int curi = blk->ins.n;
+ blk->jmp.arg[0] = insertinstr(blk, blk->ins.n, mkinstr(Oneq, insrescls(instrtab[c.i]), c, ZEROREF));
+ ins = &instrtab[blk->jmp.arg[0].i];
+ if (kisflt(ins->cls)) {
+ ins->r = insertinstr(blk, curi, mkinstr(Ocopy, ins->cls, ZEROREF));
+ }
+ ins->keep = 1;
+ } else if (instrtab[c.i].op == Oadd) {
+ /* prevent a 3-address add whose flag results are used from becoming a LEA */
+ instrtab[c.i].inplace = 1;
+ }
+ }
+ } else if (blk->jmp.t == Jret) {
+ if (blk->jmp.arg[0].bits) {
+ int curi;
+ union ref r = mkref(RREG, fn->abiret[0].reg);
+ struct instr *ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[0].ty.cls, r, blk->jmp.arg[0])).i];
+ curi = blk->ins.n-1;
+ fixarg(&ins->r, ins, blk, &curi);
+ blk->jmp.arg[0] = r;
+ if (blk->jmp.arg[1].bits) {
+ r = mkref(RREG, fn->abiret[1].reg);
+ ins = &instrtab[insertinstr(blk, blk->ins.n, mkinstr(Omove, fn->abiret[1].ty.cls, r, blk->jmp.arg[1])).i];
+ curi = blk->ins.n-1;
+ fixarg(&ins->r, ins, blk, &curi);
+ blk->jmp.arg[1] = r;
+ }
+ }
+ }
+}
+
+void
+x86_64_isel(struct function *fn)
+{
+ struct block *blk = fn->entry;
+
+ do {
+ for (int i = 0; i < blk->phi.n; ++i) {
+ struct instr *ins = &instrtab[blk->phi.p[i]];
+ union ref *phi = phitab.p[ins->l.i];
+ for (int i = 0; i < blk->npred; ++i) {
+ int curi = blkpred(blk, i)->ins.n;
+ fixarg(&phi[i], ins, blkpred(blk, i), &curi);
+ }
+ }
+ iflagsrc = -1;
+ for (int i = 0; i < blk->ins.n; ++i) {
+ struct instr *ins = &instrtab[blk->ins.p[i]];
+ sel(fn, ins, blk, &i);
+ if (ins->op < countof(opflags) && kisint(insrescls(*ins))) {
+ if (opflags[ins->op] & ZF) iflagsrc = ins - instrtab;
+ else if (opflags[ins->op] & CLOBF) iflagsrc = -1;
+ }
+ }
+ seljmp(fn, blk);
+ } while ((blk = blk->lnext) != fn->entry);
+
+ if (ccopt.dbg.i) {
+ bfmt(ccopt.dbgout, "<< After isel >>\n");
+ irdump(fn);
+ }
+
+ fn->prop = 0;
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/t_x86-64_sysv.c b/src/t_x86-64_sysv.c
new file mode 100644
index 0000000..317f40f
--- /dev/null
+++ b/src/t_x86-64_sysv.c
@@ -0,0 +1,310 @@
+#include "all.h"
+
+static int classify(uchar cls[2], const struct typedata *td, uint off);
+
+static void
+clsscalar(uchar cls[2], uint off, union type ty)
+{
+ enum irclass k = type2cls[scalartypet(ty)];
+ uchar *fcls = &cls[off/8];
+ if (isflt(ty)) { /* SSE */
+ if (!*fcls || (*fcls == KF32 && k > *fcls))
+ *fcls = k;
+ } else { /* INTEGER */
+ assert(isint(ty) || ty.t == TYPTR);
+ if (cls2siz[*fcls] < cls2siz[k])
+ *fcls = k == KPTR ? KI64 : k;
+ }
+ if (off % 8 >= 4 && cls2siz[*fcls] < 8)
+ *fcls = kisint(*fcls) ? KI64 : KF64;
+}
+
+static int
+classifyarr(uchar cls[2], union type ty, uint off)
+{
+ union type chld = typechild(ty);
+ uint n = typearrlen(ty), siz = typesize(chld);
+ assert(n > 0);
+ for (uint i = 0; i < n; ++i) {
+ uint offx = off + i * siz;
+ if (isagg(chld)) {
+ if (!classify(cls, &typedata[chld.dat], offx))
+ return cls[0] = cls[1] = 0;
+ } else if (chld.t == TYARRAY) {
+ if (!classifyarr(cls, chld, offx))
+ return cls[0] = cls[1] = 0;
+ } else {
+ clsscalar(cls, offx, chld);
+ }
+ }
+ return !!cls[0] + !!cls[1];
+}
+
+static int
+classify(uchar cls[2], const struct typedata *td, uint off)
+{
+ uint siz = alignup(td->siz, 4);
+ if (siz > 16) /* MEMORY */
+ return 0;
+ for (int i = 0; i < td->nmemb; ++i) {
+ struct fielddata *fld = &td->fld[i].f;
+ uint align = typealign(fld->t);
+ if (alignup(fld->off, align) != fld->off) /* unaligned field -> MEMORY */
+ return cls[0] = cls[1] = 0;
+ if (isagg(fld->t)) {
+ if (!classify(cls, &typedata[fld->t.dat], off + fld->off))
+ return cls[0] = cls[1] = 0;
+ } else if (fld->t.t == TYARRAY) {
+ if (isincomplete(fld->t)) continue;
+ if (!classifyarr(cls, fld->t, off + fld->off))
+ return cls[0] = cls[1] = 0;
+ } else {
+ clsscalar(cls, fld->off + off, fld->t);
+ }
+ }
+ return !!cls[0] + !!cls[1];
+}
+
+static int
+abiarg(short r[2], uchar cls[2], uchar *r2off, int *ni, int *nf, int *ns, union irtype typ)
+{
+ static const uchar intregs[] = { RDI, RSI, RDX, RCX, R8, R9 };
+ enum { NINT = countof(intregs), NFLT = 8 };
+
+ if (!typ.isagg) {
+ if (kisflt(cls[0] = typ.cls) && *nf < NFLT) {
+ r[0] = XMM0 + (*nf)++;
+ } else if (kisint(cls[0]) && *ni < NINT) {
+ r[0] = intregs[(*ni)++];
+ } else {
+ r[0] = *ns;
+ *ns += 8;
+ return 0; /* MEMORY */
+ }
+ return 1;
+ }
+ cls[0] = cls[1] = 0;
+ int ret = classify(cls, &typedata[typ.dat], 0);
+ if (!ret) { /*MEMORY*/
+ r[0] = *ns;
+ *ns = alignup(*ns + typedata[typ.dat].siz, 8);
+ return 0;
+ }
+ assert(ret <= 2);
+ int ni_save = *ni, nf_save = *nf;
+ *r2off = 8;
+ for (int i = 0; i < ret; ++i) {
+ assert(cls[i]);
+ if (kisflt(cls[i]) && *nf < NFLT)
+ r[i] = XMM0 + (*nf)++;
+ else if (kisint(cls[i]) && *ni < NINT)
+ r[i] = intregs[(*ni)++];
+ else { /* MEMORY */
+ *ni = ni_save, *nf = nf_save;
+ r[0] = *ns;
+ *ns = alignup(*ns + typedata[typ.dat].siz, 8);
+ r[1] = -1;
+ return cls[0] = cls[1] = 0;
+ }
+ }
+ return ret;
+}
+
+static int
+abiret(short r[2], uchar cls[2], uchar *r2off, int *ni, union irtype typ)
+{
+ if (!typ.isagg) {
+ r[0] = kisflt(cls[0] = typ.cls) ? XMM0 : RAX;
+ return 1;
+ }
+
+ cls[0] = cls[1] = 0;
+ int ret = classify(cls, &typedata[typ.dat], 0);
+ if (!ret) { /* MEMORY */
+ assert(*ni == 0);
+ r[0] = RAX; /* on return should contain result location address */
+ r[1] = RDI; /* register for caller-owned result location argument */
+ ++*ni;
+ return 0;
+ }
+ assert(ret <= 2);
+ *r2off = 8;
+ for (int i = 0, ni = 0, nf = 0; i < ret; ++i) {
+ assert(cls[i]);
+ if (kisflt(cls[i])) /* SSE (XMM0, XMM1) */
+ r[i] = XMM0 + nf++;
+ else if (kisint(cls[i])) /* INTEGER (RAX, RDX) */
+ r[i] = ni++ == 0 ? RAX : RDX;
+ else assert(0);
+ }
+ return ret;
+}
+
+/* Layout of va_list:
+ * struct {
+ * ( 0) unsigned int gp_offset;
+ * ( 4) unsigned int fp_offset;
+ * ( 8) void *overflow_arg_area;
+ * (16) void *reg_save_area;
+ * }
+ * Layout of register save area (align 16):
+ * reg off
+ * rdi 0
+ * rsi 8
+ * rdx 16
+ * rcx 24
+ * r8 32
+ * r9 40
+ * xmm0 48
+ * xmm1 64
+ * ...
+ * in x86_64/emit xvaprologue generates the code to save the registers to a stack slot
+ * there only needs to be one xvaprologue if there's any vastart instrs, and it has to be
+ * at the beginning of the function (before IR generated by regalloc can touch any registers)
+ * then vastart can initialize va_list.reg_save_area with a pointer to that
+ */
+
+static void
+vastart(struct function *fn, struct block *blk, int *curi)
+{
+ union ref rsave; /* register save area */
+ int gpr0 = 0, fpr0 = 0, stk0 = 0;
+ struct instr *ins = &instrtab[blk->ins.p[*curi]];
+ union ref ap = ins->l, src, dst;
+ assert(ins->op == Ovastart);
+ /* add xvaprologue if not there yet, which must be the first
+ * real instruction in the function (following alloca) */
+ if (fn->entry->ins.n > 1 && instrtab[fn->entry->ins.p[1]].op == Oxvaprologue) {
+ rsave = mkref(RTMP, fn->entry->ins.p[0]); /* alloca instruction */
+ assert(instrtab[rsave.i].op == Oalloca16);
+ } else {
+ rsave = insertinstr(fn->entry, 0, mkalloca(192, 16));
+ insertinstr(fn->entry, 1, mkinstr(Oxvaprologue, 0, rsave, .keep=1));
+ }
+ /* find first unnamed gpr and fpr */
+ for (int i = 0; i < fn->nabiarg; ++i) {
+ struct abiarg abi = fn->abiarg[i];
+ if (!abi.isstk){
+ if (abi.reg < XMM0) ++gpr0;
+ else ++fpr0;
+ } else {
+ stk0 = abi.stk+8;
+ }
+ }
+ /* set ap->reg_save_area */
+ *ins = mkinstr(Oadd, KPTR, ap, mkref(RICON, 16));
+ dst = mkref(RTMP, ins - instrtab);
+ int i = *curi + 1;
+ insertinstr(blk, i++, mkinstr(Ostorei64, 0, dst, rsave));
+ /* set ap->overflow_arg_area */
+ src = insertinstr(blk, i++, mkinstr(Oadd, KPTR, mkref(RREG, RBP), mkref(RICON, 16+stk0)));
+ dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 8)));
+ insertinstr(blk, i++, mkinstr(Ostorei64, 0, dst, src));
+ /* set ap->gp_offset */
+ insertinstr(blk, i++, mkinstr(Ostorei32, 0, ap, mkref(RICON, gpr0*8)));
+ /* set ap->fp_offset */
+ dst = insertinstr(blk, i++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4)));
+ insertinstr(blk, i++, mkinstr(Ostorei32, 0, dst, mkref(RICON, 6*8 + fpr0*16)));
+ *curi = i-1;
+}
+
+static void
+vaarg(struct function *fn, struct block *blk, int *curi)
+{
+ short r[2];
+ uchar cls[2];
+ union ref tmp;
+ int ni = 0, nf = 0, ns = 0;
+ uchar r2off;
+ int var = blk->ins.p[*curi];
+ union ref ap = instrtab[var].l;
+ union irtype ty = ref2type(instrtab[var].r);
+
+ assert(instrtab[var].op == Ovaarg);
+ blk->ins.p[*curi] = newinstr(blk, (struct instr){Onop});
+
+ int ret = abiarg(r, cls, &r2off, &ni, &nf, &ns, ty);
+
+ if (ret == 2) assert(!"nyi");
+ else if (ret == 1) {
+ struct block *merge;
+ union ref phi, phiargs[2];
+ /* int: l->gp_offset < 48 - num_gp * 8 */
+ /* sse: l->fp_offset < 304 - num_gp * 16 (why 304? ... 176) */
+ tmp = ni ? ap : insertinstr(blk, (*curi)++, mkinstr(Oadd, KPTR, ap, mkref(RICON, 4)));
+ tmp = insertinstr(blk, (*curi)++, mkinstr(Oloadu32, KI32, tmp));
+ tmp = insertinstr(blk, (*curi)++, mkinstr(Oulte, KI32, tmp, mkref(RICON, ni ? 48 - ni*8 : 176 - nf*16)));
+ merge = blksplitafter(fn, blk, *curi);
+ blk->jmp.t = 0;
+ useblk(fn, blk);
+ putcondbranch(fn, tmp, newblk(fn), newblk(fn));
+ useblk(fn, blk->s1);
+ {
+ /* phi0: &l->reg_save_area[l->gp/fp_offset] */
+ union ref sav = addinstr(fn, mkinstr(Oloadi64, KPTR, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 16))));
+ union ref roff = addinstr(fn, mkinstr(Oloadu32, KI32, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4))));
+ phiargs[0] = irbinop(fn, Oadd, KPTR, sav, roff);
+ /* l->gp/fp_offset += num_gp/fp * 8(16) */
+ roff = irbinop(fn, Oadd, KI32, roff, mkref(RICON, ni ? ni * 8 : nf * 16));
+ addinstr(fn, mkinstr(Ostorei32, 0, irbinop(fn, Oadd, KPTR, ap, mkref(RICON, ni ? 0 : 4)), roff));
+ assert(merge->npred == 1);
+ blkpred(merge, 0) = blk->s1;
+ blk->s1->jmp.t = Jb;
+ blk->s1->s1 = merge;
+ }
+ useblk(fn, blk->s2);
+ {
+ /* phi1: l->overflow_arg_area */
+ union ref adr = irbinop(fn, Oadd, KPTR, ap, mkref(RICON, 8));
+ union ref ovf = addinstr(fn, mkinstr(Oloadi64, KPTR, adr));
+ /* align no-op */
+
+ phiargs[1] = ovf;
+ /* update l->overflow_arg_area += size */
+ int siz = 8;
+ addinstr(fn, mkinstr(Ostorei64, 0, adr, irbinop(fn, Oadd, KPTR, ovf, mkref(RICON, siz))));
+ putbranch(fn, merge);
+ }
+ assert(merge->npred == 2);
+ vpush(&merge->ins, 0);
+ memmove(merge->ins.p+1, merge->ins.p, (merge->ins.n-1)*sizeof *merge->ins.p);
+ merge->ins.p[0] = var;
+ phi = insertphi(merge, KPTR);
+ memcpy(phitab.p[instrtab[phi.i].l.i], phiargs, sizeof phiargs);
+ if (!ty.isagg) {
+ instrtab[var] = mkinstr(cls2load[cls[0]], cls[0], phi);
+ } else {
+ instrtab[var] = mkalloca(8, 8);
+ tmp = insertinstr(merge, 1, mkinstr(Oloadi64, KI64, phi));
+ insertinstr(merge, 2, mkinstr(Ostorei64, 0, mkref(RTMP, var), tmp));
+ }
+ fn->prop &= ~FNUSE;
+ } else {
+ assert(!"nyi");
+ }
+}
+
+static const char x86_64_rnames[][6] = {
+#define R(r) #r,
+ LIST_REGS(R)
+#undef R
+};
+
+const struct mctarg t_x86_64_sysv = {
+ .gpr0 = RAX, .ngpr = R15 - RAX + 1,
+ .bpr = RBP,
+ .gprscratch = R11, .fprscratch = XMM15,
+ .fpr0 = XMM0, .nfpr = XMM15 - XMM0 + 1,
+ .rcallee = 1<<RBX | 1<<R12 | 1<<R13 | 1<<R14 | 1<<R15,
+ .rglob = 1<<RSP | 1<<RBP,
+ .rnames = x86_64_rnames,
+ .objkind = OBJELF,
+ .abiret = abiret,
+ .abiarg = abiarg,
+ .vastart = vastart,
+ .vaarg = vaarg,
+ .isel = x86_64_isel,
+ .emit = x86_64_emit
+};
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/u_endian.h b/src/u_endian.h
new file mode 100644
index 0000000..34b7721
--- /dev/null
+++ b/src/u_endian.h
@@ -0,0 +1,189 @@
+#ifndef ENDIAN_H_
+#define ENDIAN_H_
+
+#include "common.h"
+extern bool targ_bigendian;
+
+/*** Macros and functions for endian specific memory access ***/
+
+/** byte-swapping functions **/
+
+#if HAS_BUILTIN(bswap16)
+#define bswap16 __builtin_bswap16
+#else
+static inline ushort
+bswap16(ushort x) {
+ return x >> 8 | x << 8;
+}
+#endif
+
+#if HAS_BUILTIN(bswap32)
+#define bswap32 __builtin_bswap32
+#else
+static inline uint
+bswap32(uint x) {
+ return x >> 24 & 0x000000FF | x >> 8 & 0x0000FF00
+ | x << 8 & 0x00FF0000 | x << 24 & 0xFF000000;
+}
+#endif
+
+#if HAS_BUILTIN(bswap64)
+#define bswap64 __builtin_bswap64
+#else
+static inline uvlong
+bswap64(uvlong x) {
+ return (uvlong) bswap32(x) << 32 | bswap32(x >> 32);
+}
+#endif
+
+#if (defined __BYTE_ORDER__ && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \
+ defined __hppa__ || defined __m68k__ || defined mc68000 || defined _M_M68K || \
+ (defined __MIPS__ && defined __MIPSEB__) || \
+ defined __ppc__ || defined __POWERPC__ || defined __powerpc__ || defined __PPC__ || \
+ defined __sparc__
+#define HOST_BIG_ENDIAN
+#define hostntarg_sameendian() (targ_bigendian)
+#else
+#define HOST_LIL_ENDIAN
+#define hostntarg_sameendian() (!targ_bigendian)
+#endif
+
+/** little-endian memory writes **/
+
+static inline void
+wr16le(uchar *p, ushort x)
+{
+#ifndef HOST_LIL_ENDIAN
+ x = bswap16(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr32le(uchar *p, uint x)
+{
+#ifndef HOST_LIL_ENDIAN
+ x = bswap32(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr64le(uchar *p, uvlong x)
+{
+#ifndef HOST_LIL_ENDIAN
+ x = bswap64(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+/** big-endian memory writes **/
+
+static inline void
+wr16be(uchar *p, ushort x)
+{
+#ifndef HOST_BIG_ENDIAN
+ x = bswap16(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr32be(uchar *p, uint x)
+{
+#ifndef HOST_BIG_ENDIAN
+ x = bswap32(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr64be(uchar *p, uvlong x)
+{
+#ifndef HOST_BIG_ENDIAN
+ x = bswap64(x);
+#endif
+ memcpy(p, &x, sizeof x);
+}
+
+/** target-endian memory read/write **/
+
+static inline ushort
+rd16targ(uchar *p)
+{
+ ushort x;
+ memcpy(&x, p, sizeof x);
+ if (!hostntarg_sameendian()) x = bswap16(x);
+ return x;
+}
+
+static inline uint
+rd32targ(uchar *p)
+{
+ uint x;
+ memcpy(&x, p, sizeof x);
+ if (!hostntarg_sameendian()) x = bswap32(x);
+ return x;
+}
+
+static inline uvlong
+rd64targ(uchar *p)
+{
+ uvlong x;
+ memcpy(&x, p, sizeof x);
+ if (!hostntarg_sameendian()) x = bswap64(x);
+ return x;
+}
+
+static inline float
+rdf32targ(uchar *p)
+{
+ union { uint i; float f; } u = { rd32targ(p) };
+ return u.f;
+}
+
+static inline double
+rdf64targ(uchar *p)
+{
+ union { uvlong i; double f; } u = { rd64targ(p) };
+ return u.f;
+}
+
+static inline void
+wr16targ(uchar *p, ushort x)
+{
+ if (!hostntarg_sameendian()) x = bswap16(x);
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr32targ(uchar *p, uint x)
+{
+ if (!hostntarg_sameendian()) x = bswap32(x);
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wr64targ(uchar *p, uvlong x)
+{
+ if (!hostntarg_sameendian()) x = bswap64(x);
+ memcpy(p, &x, sizeof x);
+}
+
+static inline void
+wrf32targ(uchar *p, float x)
+{
+ union { float f; uint i; } u = { x };
+ wr32targ(p, u.i);
+}
+
+static inline void
+wrf64targ(uchar *p, double x)
+{
+ union { double f; uvlong i; } u = { x };
+ wr64targ(p, u.i);
+}
+
+#endif /* ENDIAN_H_ */
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/u_mem.c b/src/u_mem.c
new file mode 100644
index 0000000..9ee8864
--- /dev/null
+++ b/src/u_mem.c
@@ -0,0 +1,390 @@
+#include "common.h"
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+
+static void
+allocerr(const char *f)
+{
+ efmt("%s: %s\n", f, strerror(errno));
+ ioflush(&bstdout);
+ ioflush(&bstderr);
+ abort();
+}
+
+void *
+(xmalloc)(size_t n, const char *f)
+{
+ void *p = malloc(n);
+ if (!p) allocerr(f);
+ return p;
+}
+
+void *
+(xcalloc)(size_t n, const char *f)
+{
+ void *p = calloc(n, 1);
+ if (!p) allocerr(f);
+ return p;
+}
+
+void *
+(xrealloc)(void *p, size_t n, const char *f)
+{
+ p = p ? realloc(p, n) : malloc(n);
+ if (!p) allocerr(f);
+ return p;
+}
+
+/** string interning **/
+internstr
+intern_(const char *s, uint len)
+{
+ static uint N, n;
+ static struct ht {
+ internstr s;
+ size_t h;
+ } *ht;
+ static struct { char m[sizeof(struct arena) + (2<<10)]; struct arena *_a; } amem;
+ static struct arena *arena;
+
+ if (!N) {
+ ht = xcalloc((sizeof *ht) * (N = 1<<10));
+ arena = (void *)amem.m, arena->cap = sizeof amem.m - sizeof(struct arena);
+ }
+
+ for (size_t h = len ? hashb(0, s, len) : hashs(0, s), i = h;;) {
+ i &= N - 1;
+ if (!ht[i].s) { /* insert */
+ if (n < N/4*3 /*load factor 75%*/) {
+ ++n;
+ ht[i].h = h;
+ ht[i].s = alloccopy(&arena, s, (len ? len : strlen(s))+1, 1);
+ if (len) ((char *)ht[i].s)[len] = 0;
+ return ht[i].s;
+ }
+ /* resize */
+ size_t nnew = N * 2;
+ struct ht *new = xcalloc(sizeof *new * nnew);
+ for (uint i = 0; i < N; ++i) { /* rehash */
+ if (!ht[i].s) continue;
+ uint j = ht[i].h;
+ while (new[j &= nnew-1].s) ++j;
+ new[j] = ht[i];
+ }
+ free(ht);
+ ht = new;
+ N = nnew;
+ i = h;
+ continue;
+ } else if (h == ht[i].h) {
+ if (!len && !strcmp(s, &ht[i].s->c))
+ return ht[i].s;
+ else if (len && !strncmp(s, &ht[i].s->c, len) && ht[i].s[len].c == 0)
+ return ht[i].s;
+ }
+ ++i;
+ }
+}
+
+/** vec **/
+void
+vinit_(struct vecbase *v, void *inlbuf, uint cap, uint siz)
+{
+ assert(!v->p);
+ v->cap = cap;
+ if (inlbuf) {
+ v->p = inlbuf;
+ v->dyn = 0;
+ } else if (cap) {
+ v->p = xmalloc(cap*siz);
+ v->dyn = 1;
+ }
+}
+
+void
+vpush_(struct vecbase *v, uint siz)
+{
+ if (v->n < v->cap) return;
+ if (!v->dyn && v->n >= v->cap) { /* empty or inline buffer */
+ int cap = v->cap ? v->cap * 2 : 8;
+ void *old = v->p;
+ v->p = xmalloc(cap * siz);
+ if (old) memcpy(v->p, old, v->cap * siz);
+ v->cap = cap;
+ v->dyn = 1;
+ } else if (v->dyn && v->n >= v->cap) { /* dyn buf */
+ v->p = xrealloc(v->p, (v->cap *= 2) * siz);
+ }
+ while (v->n >= v->cap)
+ v->p = xrealloc(v->p, (v->cap *= 2) * siz);
+ assert(v->cap > v->n && "overflow");
+}
+
+void *
+vpushn_(struct vecbase *v, uint siz, const void *dat, uint ndat)
+{
+ void *beg;
+
+ if (!ndat) return v->p;
+ v->n += ndat;
+ while (v->n >= v->cap)
+ vpush_(v, siz);
+ beg = (char *)v->p + (v->n - ndat) * siz;
+ assert(dat);
+ memcpy(beg, dat, ndat * siz);
+ return beg;
+}
+
+void
+vresize_(struct vecbase *v, uint siz, uint N)
+{
+ while (v->cap < N) {
+ vpush_(v, siz);
+ v->n = v->cap;
+ }
+ v->n = N;
+}
+
+/** arena **/
+struct arena *
+newarena(uint chunksiz)
+{
+ struct arena *ar = xmalloc(offsetof(struct arena, mem) + chunksiz);
+ assert(chunksiz < 1u<<31 && "toobig");
+ ar->prev = NULL;
+ ar->cap = chunksiz;
+ ar->dyn = 1;
+ ar->n = 0;
+ return ar;
+}
+
+void *
+alloc(struct arena **par, uint siz, uint align)
+{
+ uint idx;
+ struct arena *new;
+
+ if (siz > (*par)->cap) {
+ new = newarena(siz);
+ new->n = siz;
+ new->prev = (*par)->prev;
+ (*par)->prev = new;
+ return new->mem;
+ }
+ align = align ? align : sizeof(void *);
+ idx = (uchar *)alignup((uintptr_t)&(*par)->mem[(*par)->n], align) - (*par)->mem;
+ if ((*par)->cap - idx >= siz) {
+ (*par)->n = idx + siz;
+ return (*par)->mem + idx;
+ }
+ new = newarena((*par)->cap);
+ new->prev = *par;
+ *par = new;
+ new->n = siz;
+ return new->mem;
+}
+
+void *
+allocz(struct arena **par, uint siz, uint align)
+{
+ return memset(alloc(par, siz, align), 0, siz);
+}
+
+void
+freearena(struct arena **par)
+{
+ struct arena *prev;
+ for (; *par; *par = prev) {
+ prev = (*par)->prev;
+ if ((*par)->dyn)
+ free(*par);
+ else {
+ if (prev) freearena(&prev);
+ (*par)->prev = NULL;
+ (*par)->n = 0;
+ return;
+ }
+ }
+}
+
+/** integer hashmap **/
+void
+imap_init_(struct imapbase *m, void **v, uint vsiz, uint N)
+{
+ uint sizk = N*sizeof*m->k,
+ sizv = N*vsiz,
+ sizbs = BSSIZE(N)*sizeof(struct bitset);
+
+ assert(ispo2(N));
+ m->k = xcalloc(sizk + sizv + sizbs);
+ *v = (char *)m->k + sizk;
+ m->bs = (struct bitset *)((char *)*v + sizv);
+ m->N = N;
+}
+
+int
+imap_get_(struct imapbase *m, short k)
+{
+ if (!m->N) return -1;
+ for (int i = k;; ++i) {
+ bool notempty = bstest(m->bs, i &= m->N - 1);
+ if (bstest(m->bs, i) && m->k[i] == k) return i;
+ if (!notempty) return -1;
+ }
+}
+
+static void
+imap_rehash(struct imapbase *m, void **v, uint vsiz)
+{
+ short *newk, k;
+ int i, j;
+ void *newv;
+ struct bitset *newbs;
+ uint N2 = m->N ? m->N << 1 : 16,
+ sizk = N2*sizeof*m->k,
+ sizv = N2*vsiz,
+ sizbs = BSSIZE(N2)*sizeof(struct bitset);
+
+ assert(N2);
+ newk = xcalloc(sizk + sizv + sizbs);
+ memset(newk, 0, sizk + sizv + sizbs);
+ newv = (char *)newk + sizk;
+ newbs = (struct bitset *)((char *)newv + sizv);
+ for (i = 0; i < m->N; ++i) {
+ if (!bstest(m->bs, i))
+ continue;
+ j = k = m->k[i];
+ for (;; ++j) {
+ j &= N2 - 1;
+ if (!bstest(newbs, j)) {
+ bsset(newbs, j);
+ newk[j] = k;
+ memcpy((char *)newv + j*vsiz, (char *)*v + i*vsiz, vsiz);
+ break;
+ }
+ }
+ }
+ free(m->k);
+ m->k = newk;
+ *v = newv;
+ m->bs = newbs;
+ m->N = N2;
+}
+
+int
+imap_set_(struct imapbase *m, void **v, uint vsiz, short k)
+{
+ if (m->n >= m->N/4*3 /*load factor 75*/) {
+ imap_rehash(m, v, vsiz);
+ assert(m->n < m->N);
+ }
+ for (int i = k;; ++i) {
+ bool notempty = bstest(m->bs, i &= m->N - 1);
+ if (notempty && m->k[i] == k)
+ return i;
+ if (!notempty) {
+ m->k[i] = k;
+ bsset(m->bs, i);
+ ++m->n;
+ return i;
+ }
+ }
+}
+
+/** pointer hashmap **/
+void
+pmap_init_(struct pmapbase *m, void **v, uint vsiz, uint N)
+{
+ assert(ispo2(N));
+ uint sizk = N*sizeof*m->k,
+ sizv = N*vsiz;
+
+ m->k = xcalloc(sizk + sizv);
+ *v = (char *)m->k + sizk;
+ m->N = N;
+}
+
+int
+pmap_get_(struct pmapbase *m, const void *k)
+{
+ assert(k && "null key");
+ if (!m->N) return -1;
+ for (size_t i = ptrhash(k);; ++i) {
+ i &= m->N - 1;
+ if (m->k[i] == k) return i;
+ if (!m->k[i]) return -1;
+ }
+}
+
+char pmap_tombstone_[1];
+
+static void
+pmap_rehash(struct pmapbase *m, void **v, uint vsiz)
+{
+ void **newk;
+ int i, j;
+ void *k;
+ void *newv;
+ uint N2 = m->N ? m->N << 1 : 16,
+ sizk = N2*sizeof*m->k,
+ sizv = N2*vsiz;
+
+ assert(N2);
+ newk = xcalloc(sizk + sizv);
+ newv = (char *)newk + sizk;
+ for (i = 0; i < m->N; ++i) {
+ if (!m->k[i] || m->k[i] == pmap_tombstone_)
+ continue;
+ j = ptrhash(k = m->k[i]);
+ for (;; ++j) {
+ j &= N2 - 1;
+ if (!newk[j]) {
+ newk[j] = k;
+ memcpy((char *)newv + j*vsiz, (char *)*v + i*vsiz, vsiz);
+ break;
+ }
+ }
+ }
+ free(m->k);
+ m->k = newk;
+ *v = newv;
+ m->N = N2;
+}
+
+int
+pmap_set_(struct pmapbase *m, void **v, uint vsiz, const void *k)
+{
+ assert(k && "null key");
+ if (m->n >= m->N/4*3 /*load factor 75%*/) {
+ pmap_rehash(m, v, vsiz);
+ assert(m->n < m->N);
+ }
+ for (size_t i = ptrhash(k);; ++i) {
+ i &= m->N - 1;
+ if (m->k[i] == k)
+ return i;
+ if (!m->k[i] || m->k[i] == pmap_tombstone_) {
+ m->k[i] = (void *)k;
+ ++m->n;
+ return i;
+ }
+ }
+}
+
+void
+pmap_del_(struct pmapbase *m, const void *k)
+{
+ assert(k && "null key");
+ for (size_t i = ptrhash(k);; ++i) {
+ i &= m->N - 1;
+ if (m->k[i] == k) {
+ m->k[i] = pmap_tombstone_;
+ --m->n;
+ return;
+ } else if (!m->k[i])
+ return;
+ }
+}
+
+/* vim:set ts=3 sw=3 expandtab: */
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..3abd4a1
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,13 @@
+#ifndef VERSION_H_
+#define VERSION_H_
+
+/* 0.1.10 */
+#define ANTCC_VERSION_MAJOR 0
+#define ANTCC_VERSION_MINOR 1
+#define ANTCC_VERSION_PATCH 10
+
+#define XSTR_(...) #__VA_ARGS__
+#define XSTR(...) XSTR_(__VA_ARGS__)
+#define ANTCC_VERSION_STR XSTR(ANTCC_VERSION_MAJOR) "." XSTR(ANTCC_VERSION_MINOR) "." XSTR(ANTCC_VERSION_PATCH)
+
+#endif