#include "c.h" #include "lex.h" #include "ir.h" #include "endian.h" #include "obj.h" /** C compiler state **/ struct comp { struct lexer lx; struct env *env; struct arena *fnarena, *exarena; struct span fnblkspan; uint loopdepth, switchdepth; struct block *loopbreak, *loopcont; pmap_of(struct label) labels; }; struct label { struct span usespan; struct block *blk; /* 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 */ }; /** Parsing helper functions **/ #define peek(Cm,Tk) lexpeek(&(Cm)->lx,Tk) #define lex(Cm,Tk) lex(&(Cm)->lx,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; } static struct token expectdie(struct comp *cm, enum toktag t, const char *s) { struct token tk; if (!match(cm, &tk, t)) fatal(&tk.span, "expected %'tt%s%s", t, s?" ":"",s ? s : ""); return tk; } /******************************************/ /* 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; enum storageclass scls; enum qualifier qual; uint align; bool more, /* caller should keep calling pdecl to get next decl */ varini, /* caller should parse an initializer ('=' ) and call pdecl() to advance state before checking .more */ funcdef, /* caller should parse an func definition ('{' '}'). the declaration list is finished. */ tagdecl; const char **pnames; /* param names for function definition */ struct span *pspans; /* param spans ditto */ }; static struct decl pdecl(struct declstate *st, struct comp *cm); static struct decl *finddecl(struct comp *cm, const char *name); /* next token starts a decl? */ static bool isdecltok(struct comp *cm) { struct decl *decl; struct token tk; switch (peek(cm, &tk)) { case TKWsigned: case TKWunsigned: case TKWshort: case TKWlong: case TKWint: case TKWchar: case TKW_Bool: case TKWauto: case TKWstruct: case TKWunion: case TKWenum: case TKWtypedef: case TKWextern: case TKWstatic: case TKWinline: case TKW_Noreturn: case TKWconst: case TKWvolatile: case TKWvoid: case TKWfloat: case TKWdouble: case TKWregister: case TKW_Static_assert: return 1; case TKIDENT: return (decl = finddecl(cm, tk.s)) && decl->scls == SCTYPEDEF; } return 0; } /**********************************/ /* Environment (scope) management */ /**********************************/ static struct decl envdeclsbuf[1<<10]; static vec_of(struct decl) envdecls = VINIT(envdeclsbuf, arraylength(envdeclsbuf)); struct tagged { /* a tagged type declaration */ union type ty; struct span span; }; static struct tagged envtaggedbuf[1<<10]; static vec_of(struct tagged) envtagged = VINIT(envtaggedbuf, arraylength(envtaggedbuf)); struct env { struct env *up; /* list of decls is implicitly envdecls[decl..ndecl] */ ushort decl, ndecl; /* ditto for envtagged[] */ ushort tagged, ntagged; }; static struct env toplevel; static void envdown(struct comp *cm, struct env *e) { assert(cm->env->decl + cm->env->ndecl == envdecls.n); assert(cm->env->tagged + cm->env->ntagged == envtagged.n); e->decl = envdecls.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 == envdecls.n); envdecls.n -= env->ndecl; envtagged.n -= env->ntagged; assert(env->up); cm->env = env->up; } static struct decl * envadddecl(struct env *env, const struct decl *d) { assert(env->decl + env->ndecl == envdecls.n); vpush(&envdecls, *d); ++env->ndecl; return &envdecls.p[envdecls.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 = &envdecls.p[env->decl + env->ndecl - 1]; else if (*d == &envdecls.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); ++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) { if (old->scls != new->scls) return 0; switch (old->scls) { case SCTYPEDEF: return old->ty.bits == new->ty.bits; } return 0; } static struct decl * putdecl(struct comp *cm, const struct decl *decl) { struct decl *l; for (l = NULL; enviterdecl(&l, cm->env);) { if (decl->name == l->name && !redeclarationok(l, decl)) { error(&decl->span, "incompatible redeclaration of '%s'", decl->name); note(&l->span, "previously declared here"); } } l = envadddecl(cm->env, decl); return l; } static struct decl * finddecl(struct comp *cm, const char *name) { struct env *e; struct decl *l; assert(name); for (e = cm->env; e; e = e->up) { for (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, const char *name, bool dodef) { struct env *e; struct tagged *l; struct typedata td = {0}; assert(name); for (e = cm->env; e; e = e->up) { for (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; } } } Break2: if (tt == TYENUM) return mktype(0); 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, const char *name, union type ty) { struct tagged *l; struct typedata td = {0}; assert(name); for (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; } 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); return t; } static bool assigncheck(union type t, const struct expr *src) { if (assigncompat(t, typedecay(src->ty))) return 1; if (t.t == TYPTR && iszero(*src)) 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(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 void /* 6.5.3.4 The sizeof operator */ sizeofcheck(const struct span *span, union type ty) { if (isincomplete(ty)) error(span, "cannot apply sizeof to incomplete type (%ty)", ty); else if (ty.t == TYFUNC) error(span, "cannot apply sizeof to function type (%ty)", ty); } 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 }}; if (ex->t == ECAST && ex->ty.bits == voidptr.bits) ex = ex->sub; return 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); return t1.dat == t2.dat || typechild(t1).t == TYVOID || typechild(t2).t == TYVOID; } if (isptrcvt(t1) && isnullpo(b)) return 1; return 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 && 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); } } if (t1.t == TYPTR && isnullpo(b)) return t1; if (isnullpo(a) && t2.t == TYPTR) return t2; 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)) goto Error; ty.t = intpromote(ty.t); assert(ty.t); break; case BCEQL: if (!equalitycheck(lhs, rhs)) goto Error; ty = mktype(TYINT); break; case BCCMP: if (!relationalcheck(lhs, rhs)) goto Error; ty = mktype(TYINT); break; case BCLOG: /* 6.5.13-14 Logical AND/OR operator */ if (!isscalar(ty) || !isscalar(rhs->ty)) goto Error; ty = mktype(TYINT); 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 memcpy(alloc(&cm->exarena, sizeof *e, 0), e, sizeof *e); } 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); 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 = &typedata[ty.dat]; struct expr argbuf[10]; vec_of(struct expr) args = VINIT(argbuf, arraylength(argbuf)); bool spanok = joinspan(&span.ex, span_->ex); bool printsig = 0; if (callee->t == ESYM && !callee->ty.t) { /* implicit function decl.. */ const char *name = (void *)callee->sym; struct decl decl = { ty = mkfntype(mktype(TYINT), 0, NULL, NULL, /* kandr */ 1, 0), .scls = SCEXTERN, .span = callee->span, .name = name }; warn(&callee->span, "call to undeclared function '%s'", name); ((struct expr *)callee)->ty = decl.ty; ((struct expr *)callee)->sym = putdecl(cm, &decl); td = &typedata[ty.dat]; } if (ty.t == TYPTR) /* auto-deref when calling a function pointer */ ty = typechild(ty); if (ty.t != TYFUNC) error(&callee->span, "calling a value of type '%ty'", callee->ty); if (!match(cm, &tk, ')')) for (;;) { arg = expr(cm); spanok = spanok && joinspan(&span.ex, callee->span.ex); if (ty.t == TYFUNC && args.n == td->nmemb && !td->variadic && !td->kandr) { error(&arg.span, "too many args to function taking %d params", td->nmemb); printsig = 1; } if (ty.t == TYFUNC && 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->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); return ex; } static inline int tkprec(int tt) { return ((uint)tt < arraylength(bintab)) ? bintab[tt].prec : 0; } /* parse an expression with the given operator precedence */ /* param ident is a kludge to support block labels without backtracking or extra lookahead * see stmt() */ static struct expr exprparse(struct comp *cm, int prec, const struct token *ident) { struct token tk, tk2; struct span span; struct expr ex, rhs, tmp; struct decl *decl; union type ty; int opprec; enum exprkind ek; 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 '+': 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 >= arraylength(unops)) { ex = exprparse(cm, 999, NULL); break; } goto Unary; /* 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 number literal %'tk", &tk); ex.ty.t = ty.t ? ty.t : TYINT; break; case TKSTRLIT: ex = mkexpr(ESTRLIT, tk.span, mkarrtype(mktype(TYCHAR), 0, tk.len+1), .s = { (uchar *)tk.s, tk.len }); break; case TKIDENT: Ident: decl = finddecl(cm, tk.s); if (!decl) { if (peek(cm, NULL) == '(') { /* implicit function decl? */ ex = mkexpr(ESYM, tk.span, mktype(0), .sym = (void *)tk.s); } else { error(&tk.span, "undeclared identifier %'tk", &tk); ex = mkexpr(ESYM, tk.span, mktype(TYINT), .sym = NULL); } } else if (decl->scls == SCTYPEDEF) { error(&tk.span, "unexpected typename %'tk (expected expression)", &tk); ex = mkexpr(ESYM, tk.span, decl->ty, .sym = NULL); } else if (decl->isenum) { ex = mkexpr(ENUMLIT, tk.span, decl->ty, .i = decl->value); } else { ex = mkexpr(ESYM, tk.span, decl->ty, .qual = decl->qual, .sym = decl); } break; /* might be unary op or primary expr */ case '(': if (!isdecltok(cm)) { /* (expr) */ ex = commaexpr(cm); expect(cm, ')', NULL); break; } else { /* (type) expr */ struct declstate st = { DCASTEXPR }; struct decl decl = pdecl(&st, cm); expect(cm, ')', NULL); assert(decl.ty.t); unops[nunop].span = tk.span; unops[nunop].ty = decl.ty; if (++nunop >= arraylength(unops)) { ex = exprparse(cm, 999, NULL); break; } goto Unary; } case TKWsizeof: span = tk.span; if (!match(cm, NULL, '(')) /* sizeof expr */ goto Unops; else if (isdecltok(cm)) { /* sizeof (type) */ struct declstate st = { DCASTEXPR }; ty = pdecl(&st, cm).ty; } else { /* sizeof (expr) */ ty = commaexpr(cm).ty; } peek(cm, &tk); if (expect(cm, ')', NULL)) joinspan(&span.ex, tk.span.ex); sizeofcheck(&span, ty); ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype), .u = typesize(ty)); break; default: fatal(&tk.span, "expected expression (near %'tk)", &tk); } /* postfix operators */ Postfix: switch (peek(cm, &tk)) { default: break; 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)); goto Postfix; 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, &tk2), !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; goto Postfix; case '(': /* call(args) */ lex(cm, &tk); span = ex.span; ex = callexpr(cm, &span, &ex); goto Postfix; 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.s)) error(&span, "'%ty' has no such field: '%s'", ex.ty, tk2.s); 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 }); } } goto Postfix; } /* unary operators (process) */ while (nunop-- > 0) { 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 (isincomplete(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: sizeofcheck(&span, ex.ty); ex = mkexpr(ENUMLIT, span, mktype(targ_sizetype), .u = typesize(ex.ty)); 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); ex = mkexpr(ECAST, span, ty, .sub = exprdup(cm, &ex)); } } /* binary operators */ while ((opprec = tkprec(peek(cm, &tk))) >= prec) { lex(cm, &tk); ek = bintab[tk.t].t; 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); 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 { /* ex ? tmp : rhs */ struct expr *sub; span.sl = tk.span.sl; span.ex = ex.span.ex; if (!isscalar(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 = expr(cm); 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, "bad operands to 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, 2, NULL); /* non-comma expr */ } static struct expr commaexpr(struct comp *cm) { return exprparse(cm, 1, NULL); } /****************/ /* Initializers */ /****************/ static uint nmemb(union type ty) { if (ty.t == TYARRAY) return typearrlen(ty) ? typearrlen(ty) : -1u; if (isagg(ty)) return typedata[ty.dat].nmemb; return 1; } static bool objectp(union type ty) { return isagg(ty) || ty.t == TYARRAY; } static bool chararrayp(union type ty) { return ty.t == TYARRAY && in_range(typechild(ty).t, TYCHAR, TYUCHAR); } static union type membertype(uint *off, union type ty, uint idx) { 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) { *off = typedata[ty.dat].fld[idx].f.off; return typedata[ty.dat].fld[idx].f.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; /* size is not known until parsing done (implicit array size) */ 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; const char *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 union ref expraddr(struct function *, const struct expr *); static bool globsym(union ref *psym, const struct expr *ex) { if (ex->t == ESTRLIT || (ex->t == ESYM && (ex->sym->scls & (SCSTATIC | SCEXTERN)))) { *psym = expraddr(NULL, ex); return 1; } return 0; } static void expr2reloc(union ref *psym, vlong *paddend, const struct expr *ex) { if (ex->t == EADDROF && globsym(psym, ex)) { *paddend = 0; } else if (globsym(psym, ex) && in_range(ex->ty.t, TYARRAY, TYFUNC)) { *paddend = 0; } else if (ex->t == ESUB && globsym(psym, &ex->sub[0]) && isint(ex->sub[1].ty) && ex->sub[1].t == ENUMLIT) { *paddend = ex->sub[1].i; } else if (ex->t == EADD) { for (int swp = 0; swp < 2; ++swp) { if (globsym(psym, &ex->sub[swp]) && isint(ex->sub[swp^1].ty) && ex->sub[swp^1].t == ENUMLIT) { *paddend = ex->sub[swp^1].i; return; } } } else assert(0 && "non static reloc"); } static void iniwrite(struct initparser *ip, uint off, union type ty, struct expr *ex) { uchar *p; if (ex->ty.t == TYSTRUCT) { assert(ty.bits == ex->ty.bits); for (uint i = 0, n = nmemb(ex->ty); i < n; ++i) { uint suboff; union type sub = membertype(&suboff, ex->ty, i); iniwrite(ip, off + suboff, sub, &mkexpr(EGETF, ex->span, sub, .sub = ex)); } } else if (ip->ev == EVSTATICINI) { uint siz = typesize(ty); if (ip->dyn) { if (ip->ddat.n < off + siz) { vresize(&ip->ddat, off + siz); 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; eval(e, EVSTATICINI); assert(e->t == ENUMLIT); } // efmt("#%u' wr %lx at %u\n", ip->dyn?0:ip->off, e->u, off); ioflush(&bstderr); 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 if (ty.t == TYARRAY && ex->t == ESTRLIT) { uint n = siz < ex->s.n ? siz : ex->s.n; //efmt("%s wrs %'S at %u\n", dat->name, ex->s.p, n, off); memcpy(p , ex->s.p, n); } else { union ref sym; vlong addend; expr2reloc(&sym, &addend, ex); assert(sym.t == RXCON); if (!ip->dyn) { objreloc(xcon2sym(sym.i), 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 = xcon2sym(sym.i); rel->off = off; rel->addend = addend; ip->drel = rel; } } } } static struct initcur * iniadvance(struct initparser *ip, struct initcur *c, const struct span *span) { if (c - ip->buf >= arraylength(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) { uint off; union type targ = membertype(&off, ip->sub->ty, idx); struct initcur *next = iniadvance(ip, ip->cur, span); 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 (typesize(*ty) < ex->s.n) { warn(&ex->span, "string literal in initializer is truncated from %u to %u bytes", ex->s.n+1, typesize(*ty)); } ex->ty = *ty; } /* read scalar initializer into initializer list and avance */ static void ininext(struct initparser *ip, struct comp *cm) { uint off; union type targ; struct expr ex = expr(cm); Retry: targ = membertype(&off, 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 && chararrayp(targ)) { assert(!isincomplete(targ)); inistrlit(cm, &ex, &targ); iniwrite(ip, ip->sub->off + off, targ, &ex); ++ip->sub->idx; 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 == arraylength(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 (!assigncheck(targ, &ex)) error(&ex.span, "cannot initialize '%ty' with expression of type '%ty'", targ, ex.ty); else { if (ip->ev && !eval(&ex, ip->ev)) error(&ex.span, "cannot evaluate expression statically"); else iniwrite(ip, ip->sub->off + off, targ, &ex); } } 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, const char *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; } } if (span) error(span, "%ty has no such field: '%s'", ty, name); return -1; } static bool designators(struct initparser *ip, struct comp *cm) { struct token tk; struct span span; bool some = 0; for (;;) { 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) { uint off; union type ty = membertype(&off, 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, ']', 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) { uint off; union type ty = membertype(&off, 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) { idx = aggdesignator(ip, ip->sub->ty, tk.s, &span); ip->sub->idx = idx; 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, const char *name) { 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 && !typearrlen(*ty)) { ip->dyn = 1; } else { ip->sec = qual & QCONST ? Srodata : Sdata; ip->off = objnewdat(name, ip->sec, globl, typesize(*ty), typealign(*ty)); } } else { ip->init = &res; } if (!match(cm, &tk, '{')) { struct expr ex = expr(cm); if (ex.t == ESTRLIT && chararrayp(*ty)) { inistrlit(cm, &ex, ty); iniwrite(ip, 0, *ty, &ex); if (ip->dyn) goto Dynfix; } if (!assigncheck(*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(ip, 0, *ty, &ex); } return ex; } 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 (ip->dyn) { enum section sec; uint off, siz, align; uchar *p; 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: sec = qual & QCONST ? Srodata : Sdata; off = objnewdat(name, 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); vpush(&dattab, ((struct irdat) { align, globl, sec, siz, off, name })); vfree(&ip->ddat); 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); } } dumpini(ip); if (ev == EVSTATICINI) { return (struct expr){0}; } else { return mkexpr(EINIT, span, *ty, .init = alloccopy(&cm->exarena, &res, sizeof res, 0)); } } /*****************/ /* Decls Parsing */ /*****************/ static union type buildagg(struct comp *cm, enum typetag tt, const char *name, int id) { struct token tk; union type t; struct span flexspan; struct namedfield fbuf[32]; vec_of(struct namedfield) fld = VINIT(fbuf, arraylength(fbuf)); struct typedata td = {tt}; bool isunion = tt == TYUNION; const char *tag = isunion ? "union" : "struct"; while (!match(cm, &tk, '}')) { struct declstate st = { DFIELD }; do { struct decl decl = pdecl(&st, cm); 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); } if (decl.ty.t) { uint align = typealign(decl.ty); uint siz = typesize(decl.ty); uint off = isunion ? 0 : alignup(td.siz, align); struct namedfield f = { decl.name, { decl.ty, off, .qual = decl.qual }}; if (!decl.name) { 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"); } } 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) 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; } } 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 = { "", { 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 : -(1ll << (bits - 1)); *max = isunsignedt(tt) ? ~0ull >> (64 - bits) : (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, const char *name, const struct span *span) { 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.s; 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; 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)); 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; const char *tag = NULL; peek(cm, &tk); if (match(cm, &tk, TKIDENT)) tag = tk.s; 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) == ';'); if (tt == TYENUM && !t.t) { error(&tk.span, "cannot forward-declare enum"); return mktype(TYINT); } } else { if (tt != TYENUM) { 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, mktype(0)); note(&span, "previous definition:"); } } t = buildagg(cm, tt, tag, tag ? typedata[t.dat].id : -1); } else { t = buildenum(cm, tag, &span); if (tag) deftagged(cm, &span, TYENUM, tag, t); } } 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 void declspec(struct declstate *st, struct comp *cm) { 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, } arith = 0; struct span span = {0}; for (;;) { peek(cm, &tk); switch (tk.t) { case TKWconst: st->qual |= QCONST; break; case TKWvolatile: st->qual |= QVOLATILE; break; case TKW_Noreturn: st->qual |= QNORETURN; break; case TKWinline: st->qual |= QINLINE; break; case TKWvoid: st->base = mktype(TYVOID); break; case TKWsigned: arith |= KSIGNED; break; case TKWunsigned: arith |= KUNSIGNED; break; case TKW_Bool: case TKWbool: if (arith & KBOOL) goto Dup; arith |= KBOOL; break; case TKWchar: if (arith & KCHAR) { Dup: 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 Dup; arith |= KINT; break; case TKWfloat: if (arith & KFLOAT) goto Dup; arith |= KFLOAT; break; case TKWdouble: if (arith & KDOUBLE) goto Dup; arith |= KDOUBLE; break; case TKWenum: case TKWstruct: case TKWunion: lex(cm, &tk); st->base = tagtype(cm, tk.t); st->tagdecl = 1; if (!span.ex.len) span.ex = tk.span.ex; joinspan(&span.ex, tk.span.ex); goto End; case TKIDENT: if (!st->base.t && !arith && (decl = finddecl(cm, tk.s)) && decl->scls == SCTYPEDEF) { st->base = decl->ty; break; } /* fallthru */ default: if (!span.ex.len) span.ex = tk.span.ex; goto End; case TKW_BitInt: case TKW_Complex: case TKW_Decimal128: case TKW_Decimal32: case TKW_Decimal64: case TKW_Imaginary: error(&tk.span, "%'tk is unsupported", &tk); arith = arith ? arith : KINT; } if (!span.ex.len) span.ex = tk.span.ex; joinspan(&span.ex, tk.span.ex); lex(cm, &tk); if (st->base.t) break; } End: if (st->base.t && arith) { /* combining arith type specifiers and other types */ Bad: error(&span, "invalid declaration specifier"); st->base = mktype(TYINT); } else if (!st->base.t && arith) { enum typetag t; ioflush(&bstderr); if (arith == KFLOAT) t = TYFLOAT; else if (arith == KDOUBLE) t = TYDOUBLE; else if (arith == (KLONG | KDOUBLE)) { t = TYLDOUBLE; error(&span, "`long double' is unsupported"); } 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 goto Bad; st->base = mktype(t ? t : TYINT); } else if (!st->base.t && ccopt.cstd < STDC23) { warn(&span, "type implicitly declared as int"); st->base = mktype(TYINT); } else if (!st->base.t) fatal(&span, "expected declaration type specifier"); } /* circular doubly linked list used to parse declarators */ static struct decllist { struct decllist *prev, *next; uchar t; /* TYPTR, TYARRAY or TYFUNC */ union { uchar qual; /* TYPTR */ uint len; /* TYARRAY */ struct { /* TYFUNC */ union type *param; const char **pnames; struct span *pspans; uchar *pqual; short npar; bool kandr : 1, variadic : 1; }; }; struct span span; } decltmp[64], *declfreelist; static union type declparamtmp[16]; static const char *declpnamestmp[16]; static struct span declpspanstmp[16]; static uchar declpqualtmp[tdqualsiz(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 sclass(struct comp *cm, struct span *span) { struct token tk; int sc = 0, first = 1; for (;; lex(cm, &tk)) { switch (peek(cm, &tk)) { case TKWtypedef: sc |= SCTYPEDEF; break; case TKWextern: sc |= SCEXTERN; break; case TKWstatic: sc |= SCSTATIC; break; case TKWauto: sc |= SCAUTO; break; case TKWregister: sc |= SCREGISTER; break; case TKWthread_local: case TKW_Thread_local: sc |= SCTHREADLOCAL; break; default: return sc; } if (first) *span = tk.span; else joinspan(&span->ex, tk.span.ex); first = 0; } } static int cvqual(struct comp *cm) { struct token tk; int q = 0; while (match(cm, &tk, TKWconst) || match(cm, &tk, TKWvolatile)) q |= tk.t == TKWconst ? QCONST : QVOLATILE; return q; } static void decltypes(struct comp *cm, struct decllist *list, const char **name, struct span *span) { 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); } 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.variadic = 0; node.kandr = 1; node.npar = 0; declinsert(ptr->prev, &node); break; } else { decltypes(cm, list, name, span); expect(cm, ')', NULL); } break; case TKIDENT: if (!name) error(&tk.span, "unexpected identifier in type name"); else { *name = tk.s; *span = tk.span; } lex(cm, &tk); break; default: *span = tk.span; if (name) *name = NULL; } for (;;) { if (match(cm, &tk, '[')) { node.span = tk.span; uint n = 0; if (!match(cm, &tk, ']')) { struct expr ex = expr(cm); if (!eval(&ex, EVINTCONST)) { error(&ex.span, "array length is not an integer constant"); } else if (typesize(ex.ty) < 8 && 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) { error(&ex.span, "array cannot have zero length"); } else { n = ex.u; } peek(cm, &tk); joinspan(&node.span.ex, tk.span.ex); expect(cm, ']', NULL); } node.t = TYARRAY; node.len = n; declinsert(ptr->prev, &node); } else if (match(cm, &tk, '(')) Func: { static int depth = 0; vec_of(union type) params = {0}; vec_of(uchar) qual = {0}; vec_of(const char *) names = {0}; vec_of(struct span) spans = {0}; bool anyqual = 0; if (depth++ == 0) { vinit(¶ms, declparamtmp, arraylength(declparamtmp)); vinit(&qual, declpqualtmp, arraylength(declpqualtmp)); vinit(&names, declpnamestmp, arraylength(declpnamestmp)); vinit(&spans, declpspanstmp, arraylength(declpspanstmp)); } node.span = tk.span; node.kandr = 0; node.variadic = 0; while (!match(cm, &tk, ')')) { struct declstate st = { DFUNCPARAM }; struct decl decl; if (match(cm, &tk, TKDOTS)) { node.variadic = 1; expect(cm, ')', NULL); break; } decl = pdecl(&st, cm); decl.ty = typedecay(decl.ty); vpush(¶ms, decl.ty); vpush(&names, decl.name); vpush(&spans, decl.span); if (decl.qual) { anyqual = 1; while (qual.n < tdqualsiz(params.n)) vpush(&qual, 0); tdsetqual(qual.p, params.n-1, decl.qual); } if (isincomplete(decl.ty)) { if (params.n > 1 || decl.ty.t != TYVOID || decl.qual || decl.name) { error(&decl.span, "function parameter #%d has incomplete type (%tq)", params.n, decl.ty, tdgetqual(qual.p, params.n-1)); } } joinspan(&node.span.ex, tk.span.ex); if (!match(cm, &tk, ',')) { expect(cm, ')', NULL); break; } } --depth; node.kandr = params.n == 0 && ccopt.cstd < STDC23; if (params.n == 1 && params.p[0].t == TYVOID && !qual.n && !names.p[0]) { /* (void) */ vfree(¶ms); vfree(&names); vfree(&spans); } else if (params.n && params.p[0].t == TYVOID && !qual.n && !names.p[0]) { error(&node.span, "function parameter #1 has incomplete type (%tq)", params.p[0], tdgetqual(qual.p, 0)); } node.t = TYFUNC; node.param = params.n ? params.p : NULL; node.pqual = anyqual ? 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); } else break; } } static struct decl declarator(struct declstate *st, struct comp *cm) { struct decl decl = { st->base, st->scls, st->qual, st->align }; struct decllist list = { &list, &list }, *l; static bool inidecltmp = 0; if (!inidecltmp) { inidecltmp = 1; for (int i = 0; i < arraylength(decltmp); ++i) { decltmp[i].next = declfreelist; declfreelist = &decltmp[i]; } } decltypes(cm, &list, st->kind == DCASTEXPR ? NULL : &decl.name, &decl.span); 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); decl.ty = mkarrtype(decl.ty, decl.qual, l->len); decl.qual = 0; 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", decl.ty); else if (decl.ty.t != TYVOID && isincomplete(decl.ty)) error(&decl.span, "function cannot return incomplete type (%ty)", decl.ty); decl.ty = mkfntype(decl.ty, l->npar, l->param, l->pqual, l->kandr, l->variadic); if (l->param != declparamtmp) free(l->param); if (l->pqual != declpqualtmp) free(l->pqual); if (l->prev == &list && l->npar) { /* last */ st->pnames = alloc(&cm->fnarena, l->npar * sizeof(char *), 0); st->pspans = alloc(&cm->fnarena, l->npar * sizeof(struct span), 0); memcpy(st->pnames, l->pnames, l->npar * sizeof(char *)); memcpy(st->pspans, l->pspans, l->npar * sizeof(struct span)); } if (l->pnames != declpnamestmp) free(l->pnames); if (l->pspans != declpspanstmp) free(l->pspans); decl.qual = 0; break; } l->next = declfreelist; declfreelist = l; } return decl; } static void pstaticassert(struct comp *cm, struct span *span) { struct expr ex; struct token tk, msg = {0}; /* _Static_assert '(' [ ',' ] ')' ';' */ expect(cm, '(', NULL); ex = expr(cm); peek(cm, &tk); if (match(cm, &tk, ',')) { peek(cm, &msg); expect(cm, TKSTRLIT, NULL); } peek(cm, &tk); expect(cm, ')', NULL); expect(cm, ';', NULL); joinspan(&span->ex, tk.span.ex); if (!msg.t && ccopt.cstd == STDC11) warn(span, "static assert without message is a C23 extension"); if (!eval(&ex, EVINTCONST)) { error(&ex.span, "static assert expression is not an integer constant"); } else if (iszero(ex)) { if (msg.t) error(&ex.span, "static assertion failed: %'S", msg.s, msg.len); else error(&ex.span, "static assertion failed"); } } static struct decl pdecl(struct declstate *st, struct comp *cm) { struct token tk; struct decl decl; bool iniallowed = st->kind != DFIELD && st->kind != DFUNCPARAM && st->kind != DCASTEXPR; bool staticassertok = iniallowed; bool first = 0; if (st->varini) { memset(&decl, 0, sizeof decl); goto AfterVarIni; } if (!st->base.t) { if (staticassertok && (match(cm, &tk, TKW_Static_assert) || match(cm, &tk, TKWstatic_assert))) { pstaticassert(cm, &tk.span); return decl = (struct decl){0}; } first = 1; st->scls = sclass(cm, &tk.span); if (popcnt(st->scls) > 1) error(&tk.span, "invalid combination of storage class specifiers"); else { int allowed; switch (st->kind) { case DTOPLEVEL: allowed = SCTYPEDEF | SCEXTERN | SCSTATIC | SCTHREADLOCAL; break; case DCASTEXPR: allowed = 0; break; case DFIELD: allowed = 0; break; case DFUNCPARAM: allowed = 0; break; case DFUNCVAR: allowed = SCTYPEDEF | SCREGISTER | SCAUTO | SCEXTERN | SCSTATIC | SCTHREADLOCAL; break; default: assert(0); } if ((st->scls & allowed) != st->scls) error(&tk.span, "this storage class is not allowed in this context"); st->scls &= allowed; } declspec(st, cm); } if (first && st->tagdecl && match(cm, &tk, ';')) { decl = (struct decl) { st->base, st->scls, st->qual, st->align, tk.span }; return decl; } decl = declarator(st, cm); if (iniallowed && match(cm, &tk, '=')) { st->varini = 1; return decl; } else if (first && decl.ty.t == TYFUNC && match(cm, &tk, '{')) { st->funcdef = 1; return decl; } AfterVarIni: st->varini = 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 union ref expraddr(struct function *, const struct expr *); static union ref compileexpr(struct function *, const struct expr *, bool discard); 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 union ref expraddr(struct function *fn, const struct expr *ex) { struct decl *decl; union ref r; struct instr ins = {0}; switch (ex->t) { case ESYM: decl = ex->sym; assert(decl != NULL); switch (decl->scls) { case SCAUTO: case SCREGISTER: return mkref(RTMP, decl->id); case SCEXTERN: case SCNONE: return mksymref(decl->name); case SCSTATIC: assert(!"nyi"); break; default: assert(0); } break; case ESTRLIT: return mkdatref(NULL, ex->s.n+1, /*align*/ 1, ex->s.p, ex->s.n, /*deref*/0); case EDEREF: return exprvalue(fn, ex->sub); case EGETF: r = expraddr(fn, ex->sub); assert(ex->fld.bitsiz == 0); if (ex->fld.off == 0) return r; ins.cls = KPTR; ins.op = Oadd; ins.l = r; ins.r = mkintcon(KI4, ex->fld.off); return addinstr(fn, ins); 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 ESEQ: expreffects(fn, &ex->sub[0]); return expraddr(fn, &ex->sub[1]); case ECALL: assert(isagg(ex->ty)); return compilecall(fn, ex); default: assert(!"lvalue?>"); } } static union ref genload(struct function *fn, union type t, union ref ref) { struct instr ins = {0}; assert(isscalar(t)); ins.cls = type2cls[t.t]; switch (typesize(t)) { case 1: ins.op = issigned(t) ? Oloads1 : Oloadu1; break; case 2: ins.op = issigned(t) ? Oloads2 : Oloadu2; break; case 4: ins.op = isflt(t) ? Oloadf4 : issigned(t) ? Oloads4 : Oloadu4; break; case 8: ins.op = isflt(t) ? Oloadf8 : Oloadi8; break; default: assert(0); } ins.l = ref; 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 = Ostore1; break; case 2: ins.op = Ostore2; break; case 4: ins.op = Ostore4; break; case 8: ins.op = Ostore8; break; default: assert(0); } ins.l = ptr; ins.r = val; return addinstr(fn, ins); } static union ref cvt(struct function *fn, enum typetag to, enum typetag from, union ref ref) { enum irclass kto = type2cls[to], kfrom = type2cls[from]; struct instr ins = {0}; if (kto == kfrom && to != TYBOOL) return ref; if (ref.t == RICON && kto < KF4) return ref; ins.cls = kto; ins.l = ref; if (kisflt(kto) || kisflt(kfrom)) { if (ref.t == RICON) { assert(kisflt(kto) && kisint(kfrom)); return mkfltcon(kto, kto == KF4 ? (float)ref.i : (double)ref.i); } if (kisflt(kto) && kfrom == KI4) ins.op = issignedt(from) ? Ocvts4f : Ocvtu4f; else if (to == TYBOOL && kisflt(kfrom)) ins.op = Oneq, ins.r = mkfltcon(kfrom, 0.0); else if (kisflt(kto) && kfrom == KI8) ins.op = issignedt(from) ? Ocvts8f : Ocvtu8f; else if (kto == KF8 && kfrom == KF4) ins.op = Ocvtf4f8; else if (kto == KF4 && kfrom == KF8) ins.op = Ocvtf8f4; else if (kfrom == KF4) ins.op = issignedt(to) ? Ocvtf4s : Ocvtf4u; else if (kfrom == KF8) ins.op = issignedt(to) ? Ocvtf8s : Ocvtf8u; else assert(0); } else { if (to == TYBOOL) { if (from == TYBOOL) return ref; if (ref.t == RTMP) /* these instrs already have output range of [0,1] */ if (oiscmp(instrtab[ref.i].op)) return ref; ins.op = Oneq, ins.r = ZEROREF; } else if (kfrom == KI4 && issignedt(from)) ins.op = Oexts4; else if (kfrom == KI4) ins.op = Oextu4; else if (kto == KI4 && isintcon(ref)) return issignedt(to) ? mkintcon(kto, (int)intconval(ref)) : mkintcon(kto, (uint)intconval(ref)); else ins.op = Ocopy; } return addinstr(fn, ins); } static union ref narrow(struct function *fn, enum irclass to, enum typetag tt, union ref ref) { struct instr ins = {0}; assert(isscalart(tt)); if (targ_primsizes[tt] >= cls2siz[to]) return ref; ins.cls = to; if (isfltt(tt)) { assert(to == KF4 && tt == TYDOUBLE); ins.op = Ocvtf8f4; } else { static const enum op ext[5][2] = { [1] = {Oextu1, Oexts1}, [2] = {Oextu2, Oexts2}, [4] = {Oextu4, Oexts4} }; ins.op = ext[targ_primsizes[tt]][issignedt(tt)]; } ins.l = ref; return addinstr(fn, ins); } union ref genptroff(struct function *fn, enum op op, uint siz, union ref ptr, enum typetag tt, union ref idx) { uint cls = type2cls[targ_sizetype]; union ref off; assert(siz); idx = cvt(fn, targ_sizetype, tt, idx); if (siz == 1) off = idx; else if (idx.t == RICON) off = mkintcon(cls, idx.i * siz); else if (ispo2(siz)) off = addinstr(fn, mkinstr(Oshl, cls, .l = idx, .r = mkintcon(cls, ilog2(siz)))); else off = addinstr(fn, mkinstr(Omul, cls, .l = idx, .r = mkintcon(cls, siz))); assert(in_range(op, Oadd, Osub)); return addinstr(fn, mkinstr(op, KPTR, .l = ptr, .r = off)); } union ref genptrdiff(struct function *fn, uint siz, union ref a, union ref b) { uint cls = type2cls[targ_ptrdifftype]; assert(siz > 0); a = addinstr(fn, mkinstr(Osub, cls, .l = a, .r = b)); if (siz == 1) return a; else if ((siz & (siz-1)) == 0) /* is power of 2 */ return addinstr(fn, mkinstr(Osar, cls, a, mkintcon(cls, ilog2(siz)))); else return addinstr(fn, mkinstr(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; Loop: while (ex->t == ESEQ) { expreffects(fn, &ex->sub[0]); ex = &ex->sub[1]; } if (ex->t == ELOGAND) { next = newblk(fn); condjump(fn, &ex->sub[0], next, fl); useblk(fn, next); ex = &ex->sub[1]; goto Loop; } else if (ex->t == ELOGIOR) { next = newblk(fn); condjump(fn, &ex->sub[0], tr, next); useblk(fn, next); ex = &ex->sub[1]; goto Loop; } 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 Loop; } 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 Loop; } else { putcondbranch(fn, exprvalue(fn, ex), tr, fl); } } struct condphis { vec_of(union ref) ref; }; static void condexprrec(struct function *fn, const struct expr *ex, struct condphis *phis, int boolcon, struct block *const next, struct block *end) { struct block *tr, *fl; union ref r; while (ex->t == ESEQ) { expreffects(fn, &ex->sub[0]); ex = &ex->sub[1]; } if (ex->t == ELOGAND) { tr = newblk(fn); condexprrec(fn, &ex->sub[0], phis, 0, tr, end); useblk(fn, tr); condexprrec(fn, &ex->sub[1], phis, 0, next, end); } else if (ex->t == ELOGIOR) { fl = newblk(fn); condexprrec(fn, &ex->sub[0], phis, 1, end, fl); useblk(fn, fl); condexprrec(fn, &ex->sub[1], phis, 1, end, next ? next : end); } else if (ex->t == ECOND) { tr = newblk(fn); fl = newblk(fn); condjump(fn, &ex->sub[0], tr, fl); useblk(fn, tr); condexprrec(fn, &ex->sub[1], phis, -1, end, end); useblk(fn, fl); condexprrec(fn, &ex->sub[2], phis, -1, end, end); } else { r = exprvalue(fn, ex); if (boolcon >= 0) { if (!next || next == end) { boolcon = -1; r = cvt(fn, TYBOOL, ex->ty.t, r); } else { r = mkref(RICON, boolcon); } } vpush(&phis->ref, r); if (next && next != end) { putcondbranch(fn, r, next, end); } else { assert(boolcon < 0); 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) { union ref refbuf[8]; struct condphis phis = { VINIT(refbuf, arraylength(refbuf)) }; struct block *dst = newblk(fn); union ref r; condexprrec(fn, ex, &phis, -1, NULL, dst); useblk(fn, dst); r = addphi(fn, type2cls[ex->ty.t], 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, arraylength(insnsbuf)); ins.op = Ocall; if (isagg(ex->ty)) { ins.cls = KPTR; } else { assert(isscalar(ex->ty) || ex->ty.t == TYVOID); ins.cls = type2cls[ex->ty.t]; } 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 = cvt(fn, ty.t, arg->ty.t, 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); return addinstr(fn, ins); } static union ref compileexpr(struct function *fn, const struct expr *ex, bool discard) { union type ty; union ref r, q; enum irclass cls = type2cls[ex->ty.t]; struct instr ins = {0}; 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 */ 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)); case EGETF: if (discard && !(ex->qual & QVOLATILE)) return NOREF; return genload(fn, ex->ty, expraddr(fn, ex)); case ECAST: if (ex->ty.t == TYVOID) { expreffects(fn, sub); return NOREF; } /* fallthru */ case EPLUS: r = compileexpr(fn, sub, discard); if (discard) return NOREF; return cvt(fn, ex->ty.t, sub->ty.t, r); case ENEG: ins.op = Oneg; goto Unary; case ECOMPL: ins.op = Onot; Unary: ins.l = compileexpr(fn, sub, discard); if (discard) return NOREF; ins.l = cvt(fn, ex->ty.t, sub->ty.t, ins.l); ins.cls = cls; return addinstr(fn, ins); case ELOGNOT: for (; sub->t == ELOGNOT; ex = sub, sub = sub->sub) swp ^= 1; ins.op = Oequ + swp; ins.l = compileexpr(fn, sub, discard); if (discard) return NOREF; ins.l = cvt(fn, ex->ty.t, sub->ty.t, ins.l); ins.r = mkintcon(cls, 0); ins.cls = cls; return addinstr(fn, ins); case EDEREF: discard &= (ex->qual & QVOLATILE) == 0; r = compileexpr(fn, sub, discard); if (discard) return NOREF; return genload(fn, ex->ty, r); case EADDROF: return expraddr(fn, sub); case EMUL: ins.op = isunsigned(ex->ty) ? Oumul : Omul; goto BinArith; case EDIV: ins.op = isunsigned(ex->ty) ? Oudiv : Odiv; goto BinArith; case EREM: ins.op = issigned(ex->ty) ? Orem : Ourem; goto BinArith; case EBAND: ins.op = Oand; goto BinArith; case EXOR: ins.op = Oxor; goto BinArith; case EBIOR: ins.op = Oior; goto BinArith; case ESHL: ins.op = Oshl; goto BinArith; case ESHR: ins.op = issigned(ex->ty) ? Osar : Oslr; goto BinArith; case ESUB: ins.op = Osub; goto BinArith; case EADD: ins.op = Oadd; BinArith: ins.l = compileexpr(fn, &sub[0], discard); ins.r = compileexpr(fn, &sub[1], discard); if (discard) return NOREF; if (ins.op == Osub && isptrcvt(sub[0].ty) && isptrcvt(sub[1].ty)) { /* ptr - ptr */ return genptrdiff(fn, typesize(typechild(sub[0].ty)), ins.l, ins.r); } else if ((ins.op != Oadd && ins.op != Osub) || cls != KPTR) { /* num OP num */ ins.l = cvt(fn, ex->ty.t, sub[0].ty.t, ins.l); ins.r = cvt(fn, ex->ty.t, sub[1].ty.t, ins.r); } else { assert(isptrcvt(sub[0].ty)); /* ptr +/- num */ return genptroff(fn, ins.op, typesize(typechild(sub[0].ty)), ins.l, sub[1].ty.t, ins.r); } ins.cls = cls; return addinstr(fn, ins); case EPOSTINC: case EPOSTDEC: ins.op = ex->t == EPOSTINC ? Oadd : Osub; ins.cls = cls; r = expraddr(fn, sub); ins.l = genload(fn, sub->ty, r); if (ex->ty.t == TYPTR) ins.r = mkintcon(KI4, typesize(typechild(ex->ty))); else ins.r = mkref(RICON, 1); genstore(fn, sub->ty, r, addinstr(fn, ins)); return ins.l; case EPREINC: case EPREDEC: ins.op = ex->t == EPREINC ? Oadd : Osub; ins.cls = cls; r = expraddr(fn, sub); ins.l = genload(fn, sub->ty, r); if (ex->ty.t == TYPTR) ins.r = mkintcon(KI4, typesize(typechild(ex->ty))); else ins.r = mkref(RICON, 1); q = addinstr(fn, ins); genstore(fn, sub->ty, r, q); if (discard) return NOREF; return narrow(fn, cls, ex->ty.t, q); case EEQU: ins.op = Oequ; goto Cmp; case ENEQ: ins.op = Oneq; goto Cmp; case ELTH: ins.op = Olth; goto Cmp; case ELTE: ins.op = Olte; goto Cmp; case EGTH: ins.op = Ogth; goto Cmp; case EGTE: ins.op = Ogte; Cmp: ty = cvtarith(sub[0].ty, sub[1].ty); if (!ty.t) ty.t = TYPTR; if (isunsigned(ty) && in_range(ins.op, Olth, Ogte)) ins.op += Oulth - Olth; ins.l = compileexpr(fn, &sub[0], discard); ins.r = compileexpr(fn, &sub[1], discard); if (discard) return NOREF; ins.l = cvt(fn, ty.t, sub[0].ty.t, ins.l); ins.r = cvt(fn, ty.t, sub[1].ty.t, ins.r); ins.cls = type2cls[ty.t]; return addinstr(fn, ins); case ESET: assert(isscalar(ex->ty)); q = cvt(fn, sub[0].ty.t, sub[1].ty.t, exprvalue(fn, &sub[1])); r = expraddr(fn, &sub[0]); genstore(fn, ex->ty, r, q); if (discard) return NOREF; return narrow(fn, cls, sub[0].ty.t, q); case ESETMUL: ins.op = isunsigned(ex->ty) ? Oumul : Omul; goto Compound; case ESETDIV: ins.op = isunsigned(ex->ty) ? Oudiv : Odiv; goto Compound; case ESETREM: ins.op = issigned(ex->ty) ? Orem : Ourem; goto Compound; case ESETAND: ins.op = Oand; goto Compound; case ESETXOR: ins.op = Oxor; goto Compound; case ESETIOR: ins.op = Oior; goto Compound; case ESETSHL: ins.op = Oshl; goto Compound; case ESETSHR: ins.op = issigned(ex->ty) ? Osar : Oslr; goto Compound; case ESETSUB: ins.op = Osub; goto Compound; case ESETADD: ins.op = Oadd; Compound: r = expraddr(fn, &sub[0]); ty = in_range(ex->t, ESETSHL, ESETSHR) ? mktype(intpromote(ex->ty.t)) : cvtarith(sub[0].ty, sub[1].ty); ins.cls = cls; ins.l = genload(fn, ex->ty, r); ins.r = exprvalue(fn, &sub[1]); if ((ins.op != Oadd && ins.op != Osub) || cls != KPTR) { ins.l = cvt(fn, ty.t, sub[0].ty.t, ins.l); ins.r = cvt(fn, ex->ty.t, sub[1].ty.t, ins.r); q = addinstr(fn, ins); } else { q = genptroff(fn, ins.op, typesize(typechild(ex->ty)), ins.l, sub[1].ty.t, ins.r); } genstore(fn, ex->ty, r, q); if (discard) return NOREF; return narrow(fn, cls, ex->ty.t, q); case ECALL: r = compilecall(fn, ex); if (isint(ex->ty)) return narrow(fn, cls, ex->ty.t, r); return r; case ECOND: if (ex->ty.t == TYVOID) { 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); putbranch(fn, end); useblk(fn, fl); expreffects(fn, &sub[2]); putbranch(fn, end); useblk(fn, end); return NOREF; } /* fallthru */ case ELOGAND: case ELOGIOR: return condexprvalue(fn, ex); 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); static void deflabel(struct comp *cm, struct function *fn, const struct span *span, const char *name) { struct label *label = pmap_get(&cm->labels, name); if (label && label->usespan.ex.len == 0) { error(span, "redefinition of label '%s'", name); } else if (label) { struct block *new; 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 = fn->curblk; if (!nerror) useblk(fn, new); } else { if (!nerror) { struct block *new = newblk(fn); if (fn->curblk) putbranch(fn, new); useblk(fn, new); } pmap_set(&cm->labels, name, ((struct label) { .blk = fn->curblk })); } } 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->loopbreak, save[1] = cm->loopcont; cm->loopbreak = brk, cm->loopcont = cont; ++cm->loopdepth; terminates = stmt(cm, fn); --cm->loopdepth; cm->loopbreak = save[0], cm->loopcont = save[1]; return terminates; } 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 expr ex; struct env e; union ref r; struct token tk; bool terminates = 0; bool doemit = fn->curblk; #define EMITS if (doemit && !nerror) while (match(cm, &tk, TKIDENT)) { if (match(cm, NULL, ':')){ /*