~lsof/antcc

80ee6292c33b6342e97c46af9577a588f9fabd4a — lemon 9 months ago d21f02d
c: move codegen code after decl parser
1 files changed, 1702 insertions(+), 1702 deletions(-)

M c.c
M c.c => c.c +1702 -1702
@@ 1009,1856 1009,1856 @@ commaexpr(struct comp *cm)
}

/*****************/
/* IR Generation */
/* Decls Parsing */
/*****************/

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)
static union type
buildagg(struct comp *cm, enum typetag tt, const char *name, int id)
{
   compileexpr(fn, ex, /*discard*/ 1);
}
   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 && 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 void
structcopy(struct function *fn, union type ty, union ref dst, union ref src)
static inline void
inttyminmax(vlong *min, uvlong *max, enum typetag tt)
{
   union irtype typ = mkirtype(ty);
   addinstr(fn, mkarginstr(typ, dst));
   addinstr(fn, mkarginstr(typ, src));
   addinstr(fn, mkintrin(INstructcopy, 0, 2));
   uint bits = 8*targ_primsizes[tt];
   *min = isunsignedt(tt) ? 0 : -(1ll << (bits - 1));
   *max = isunsignedt(tt) ? ~0ull >> (64 - bits) : (1ll << (bits - 1)) - 1;
}

static union ref
structreturn(struct function *fn, const struct expr *src)
/* 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)
{
   return expraddr(fn, src);
}
   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;

static union ref compilecall(struct function *fn, const struct expr *ex);
   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;

static union ref
expraddr(struct function *fn, const struct expr *ex)
{
   struct decl *decl;
   union ref r;
   struct instr ins = {0};
      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);
      }
   }

   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");
   td.backing = 0;
   for (int t = TYINT; t <= TYUVLONG; ++t) {
      inttyminmax(&tymin, &tymax, t);
      if (minv >= tymin && maxv <= tymax) {
         td.backing = t;
         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);
   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));
   }
   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};
   if (td.backing >= TYVLONG && !somelonglong && ccopt.cstd == STDC89 && ccopt.pedant)
      warn(span, "enum backing type is '%ty' in %M", mktype(td.backing));

   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);
   ty = mktagtype(name, &td);
   ty.backing = td.backing;
   return ty;
}

static union ref
cvt(struct function *fn, enum typetag to, enum typetag from, union ref ref)
static union type
tagtype(struct comp *cm, enum toktag kind)
{
   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;
   struct token tk;
   union type t;
   struct span span;
   enum typetag tt = kind == TKWenum ? TYENUM : kind == TKWstruct ? TYSTRUCT : TYUNION;
   const char *tag = NULL;

   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);
   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);
      }
      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;
      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 (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)];
      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);
      }
   }
   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);
   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;
}

struct condphis {
   vec_of(union ref) ref;
};

static void
condexprrec(struct function *fn, const struct expr *ex, struct condphis *phis,
            int boolcon, struct block *end, struct block *zero)
{
   struct block *tr, *fl, *next;
   union ref r;
   while (ex->t == ESEQ) {
      expreffects(fn, &ex->sub[0]);
      ex = &ex->sub[1];
   }
   if (ex->t == ELOGAND) {
      next = newblk(fn);
      condexprrec(fn, &ex->sub[0], phis, 0, next, end);
      useblk(fn, next);
      condexprrec(fn, &ex->sub[1], phis, -2, end, zero);
   } else if (ex->t == ELOGIOR) {
      next = newblk(fn);
      condexprrec(fn, &ex->sub[0], phis, 1, end, next);
      useblk(fn, next);
      condexprrec(fn, &ex->sub[1], phis, -2, end, zero);
   } 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, zero);
      useblk(fn, fl);
      condexprrec(fn, &ex->sub[2], phis, -1, end, zero);
   } else {
      r = exprvalue(fn, ex);
      if (boolcon == -2)
         r = cvt(fn, TYBOOL, ex->ty.t, r);
      if (boolcon >= 0)
         vpush(&phis->ref, mkintcon(KI4, boolcon));
      else
         vpush(&phis->ref, r);
      if (zero) {
         putcondbranch(fn, r, end, zero);
      } 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)
declspec(struct declstate *st, struct comp *cm)
{
   union ref refbuf[8];
   struct condphis phis = { VINIT(refbuf, arraylength(refbuf)) };
   struct block *dst = newblk(fn);
   union ref r;
   condexprrec(fn, ex, &phis, -1, dst, NULL);
   useblk(fn, dst);
   assert(fn->curblk->npred == phis.ref.n);
   r = addphi(fn, type2cls[ex->ty.t], phis.ref.p);
   vfree(&phis.ref);
   return r;
}
   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};

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];
   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;
   }
   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));
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;
   }
   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)
static int
cvqual(struct comp *cm)
{
   union type ty;
   union ref r, q;
   enum irclass cls = type2cls[ex->ty.t];
   struct instr ins = {0};
   int swp = 0;
   struct expr *sub;
   struct token tk;
   int q = 0;
   while (match(cm, &tk, TKWconst) || match(cm, &tk, TKWvolatile))
      q |= tk.t == TKWconst ? QCONST : QVOLATILE;
   return q;
}

   eval((struct expr *)ex, EVFOLD);
   sub = ex->sub;
static void
decltypes(struct comp *cm, struct decllist *list, const char **name, struct span *span) {
   struct token tk;
   struct decllist *ptr, node;

   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;
   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, ':')){
         /* <label> ':' */
         deflabel(cm, fn, &tk.span, tk.s);
         doemit = 1;
      } else {
         /* kludge for no backtracking and no lookahead */
         ex = exprparse(cm, 1, &tk);
         EMITS expreffects(fn, &ex);
         return fn->curblk != NULL;
      }
   }

   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;
   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 {
         EMITS {
            if (!terminates) putbranch(fn, end = newblk(fn));
            useblk(fn, fl);
         }
         terminates &= stmt(cm, fn);
         EMITS {
            if (fn->curblk) putbranch(fn, end);
         }
      }
      EMITS if (!terminates) useblk(fn, end);
      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:
       *   <-
       */
      EMITS {
         putbranch(fn, begin = newblk(fn));
         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);
         decltypes(cm, list, name, span);
         expect(cm, ')', NULL);
      }
      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:
       *   <-
       */
      EMITS {
         putbranch(fn, begin = newblk(fn));
         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);
   case TKIDENT:
      if (!name)
         error(&tk.span, "unexpected identifier in type name");
      else {
         *name = tk.s;
         *span = tk.span;
      }
      lex(cm, &tk);
      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);
   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);
         }
      }
      EMITS {
         putbranch(fn, end = tr = begin = newblk(fn));
         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 (%ty)", ex.ty);
         EMITS {
            tr = newblk(fn);
            condjump(fn, &ex, tr, fl);
            useblk(fn, tr);
         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(&params, 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(&params, 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(&params);
            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];
      }
      if (!match(cm, NULL, ')')) { /* iter */
         ex = commaexpr(cm);
         end = newblk(fn);
         expect(cm, ')', NULL);
   }

   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;
      }

      terminates = loopbody(cm, fn, fl, end);
      l->next = declfreelist;
      declfreelist = l;
   }

      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);
         useblk(fn, fl);
   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 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)) {
         pstaticassert(cm, &tk.span);
         return decl = (struct decl){0};
      }
      envup(cm);
      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->loopbreak);
      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 = pmap_get(&cm->labels, tk.s);
         if (!label) {
            /* create reloc list */
            pmap_set(&cm->labels, tk.s, ((struct label) { .usespan = tk.span, .blk = fn->curblk }));
            fn->curblk = NULL;
         } else if (label && label->usespan.ex.len != 0) {
            /* append to relocs list */
            struct block *next = label->blk;
            fn->curblk->s1 = next;
            label->blk = fn->curblk;
            fn->curblk = NULL;
         } else {
            EMITS putbranch(fn, label->blk);
      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;
      }
      stmtterm(cm);
      break;
   case TKWreturn:
      lex(cm, NULL);
      if (fn->retty.t != TYVOID) {
         ex = commaexpr(cm);
         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 = cvt(fn, fn->retty.t, ex.ty.t, exprvalue(fn, &ex));
            else
               r = structreturn(fn, &ex);
            putreturn(fn, r, NOREF);
         }
      } else {
         EMITS putreturn(fn, NOREF, NOREF);
      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);
      }
      stmtterm(cm);
      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:
      ex = commaexpr(cm);
      stmtterm(cm);
      EMITS expreffects(fn, &ex);
      break;
      assert(!"lvalue?>");
   }
   freearena(cm->exarena);
   return fn->curblk == NULL;

}

/* parse and compile a function-local declaration */
static void
localdecl(struct comp *cm, struct function *fn, bool forini)
static union ref
genload(struct function *fn, union type t, union ref ref)
{
   struct expr ini;
   struct token tk;
   const bool doemit = fn->curblk;
   struct declstate st = { DFUNCVAR };
   struct instr ins = {0};

   if (!forini && match(cm, &tk, TKIDENT)) {
      if (match(cm, NULL, ':')) {
         /* <label> ':' */
         deflabel(cm, fn, &tk.span, tk.s);
         stmt(cm, fn);
         return;
      }
      st.base = finddecl(cm, tk.s)->ty;
   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);
   }
   do {
      struct decl decl = pdecl(&st, cm);
      if (decl.name) {
         static int staticid;
         bool put = 0;

         switch (decl.scls) {
         case SCSTATIC:
            if (forini)
               error(&decl.span, "static declaration in 'for' loop initializer");
            decl.id = ++staticid;
            break;
         case SCNONE:
            decl.scls = SCAUTO;
         case SCAUTO:
         case SCREGISTER:
            if (isincomplete(decl.ty) || decl.ty.t == TYFUNC) {
               error(&decl.span,
                     "declaring variable '%s' with %s type '%ty'",
                     decl.name, decl.ty.t == TYFUNC ? "function" : "incomplete",
                     decl.ty);
               goto Err;
            }
            EMITS {
               decl.id = addinstr(fn, mkalloca(typesize(decl.ty), typealign(decl.ty))).i;
            }
            if (st.varini) {
               putdecl(cm, &decl);
               put = 1;
               ini = expr(cm);
               pdecl(&st, cm);
               if (!assigncheck(decl.ty, &ini)) {
                  struct span span = decl.span;
                  joinspan(&span.ex, ini.span.ex);
                  error(&span, "cannot initialize '%ty' variable with '%ty'",
                        decl.ty, ini.ty);
               }
               EMITS {
                  if (isagg(decl.ty))
                     structcopy(fn, decl.ty, mkref(RTMP, decl.id), expraddr(fn, &ini));
                  else
                     genstore(fn, decl.ty, mkref(RTMP, decl.id), exprvalue(fn, &ini));
               }
            }
            break;
         case SCTYPEDEF:
            if (forini)
               error(&decl.span, "typedef in 'for' loop initializer");
            break;
         case SCEXTERN:
            if (forini)
               error(&decl.span, "extern declaration in 'for' loop initializer");
            break;
         default: assert(0);
         }
      Err:
         if (!put) putdecl(cm, &decl);
      } else if (forini)
         error(&decl.span, "non-variable declaration in 'for' loop initializer");
   } while (st.more);
   ins.l = ref;
   return addinstr(fn, ins);
}

static void
block(struct comp *cm, struct function *fn)
static union ref
genstore(struct function *fn, union type t, union ref ptr, union ref val)
{
   struct token tk;
   struct instr ins = {0};

   while (!match(cm, &tk, '}')) {
      if (isdecltok(cm))
         localdecl(cm, fn, 0);
      else
         stmt(cm, fn);
   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);
   }
   cm->fnblkspan = tk.span;
   ins.l = ptr;
   ins.r = val;
   return addinstr(fn, ins);
}

static void
function(struct comp *cm, struct function *fn, const char **pnames, const struct span *pspans)
static union ref
cvt(struct function *fn, enum typetag to, enum typetag from, union ref ref)
{
   const struct typedata *td = &typedata[fn->fnty.dat];
   const bool doemit = fn->curblk;
   struct env e;
   envdown(cm, &e);
   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;

   /* 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 = tdgetqual(td->quals, i),
                             .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);
   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);
      }
   }
   /* end prologue */
   EMITS {
      struct block *blk;
      putbranch(fn, blk = newblk(fn));
      useblk(fn, blk);
   }
   block(cm, fn);
   envup(cm);
   if (cm->labels.mb.n) {
      const char *name;
      struct label *label;
      pmap_each(&cm->labels, name, label) {
         if (label->usespan.ex.len) {
            error(&label->usespan, "label '%s' used but never defined", name);
         }
      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;
      }
      pmap_free(&cm->labels);
      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;
   }
   if (fn->curblk) {
      if (!strcmp(fn->name, "main") && fn->retty.t == TYINT) {
         /* implicit return 0 for main function */
         putreturn(fn, ZEROREF, NOREF);
      } else {
         if (fn->retty.t != TYVOID && !nerror) {
            warn(&cm->fnblkspan, "non-void function may not return a value");
         }
         putreturn(fn, NOREF, NOREF);
      }
   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);
}

/*****************/
/* Decls Parsing */
/*****************/

static union type
buildagg(struct comp *cm, enum typetag tt, const char *name, int id)
union ref
genptroff(struct function *fn, enum op op, uint siz, union ref ptr,
          enum typetag tt, union ref idx)
{
   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";
   uint cls = type2cls[targ_sizetype];
   union ref off;
   assert(siz);

   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 && 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);
   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
      t = mktagtype(name, &td);
   vfree(&fld);
   return t;
      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));
}

static inline void
inttyminmax(vlong *min, uvlong *max, enum typetag tt)
union ref
genptrdiff(struct function *fn, uint siz, union ref a, union ref b)
{
   uint bits = 8*targ_primsizes[tt];
   *min = isunsignedt(tt) ? 0 : -(1ll << (bits - 1));
   *max = isunsignedt(tt) ? ~0ull >> (64 - bits) : (1ll << (bits - 1)) - 1;
   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)));
}

/* 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)
/* 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 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;
   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);
   }
}

   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;
struct condphis {
   vec_of(union ref) ref;
};

      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);
static void
condexprrec(struct function *fn, const struct expr *ex, struct condphis *phis,
            int boolcon, struct block *end, struct block *zero)
{
   struct block *tr, *fl, *next;
   union ref r;
   while (ex->t == ESEQ) {
      expreffects(fn, &ex->sub[0]);
      ex = &ex->sub[1];
   }
   if (ex->t == ELOGAND) {
      next = newblk(fn);
      condexprrec(fn, &ex->sub[0], phis, 0, next, end);
      useblk(fn, next);
      condexprrec(fn, &ex->sub[1], phis, -2, end, zero);
   } else if (ex->t == ELOGIOR) {
      next = newblk(fn);
      condexprrec(fn, &ex->sub[0], phis, 1, end, next);
      useblk(fn, next);
      condexprrec(fn, &ex->sub[1], phis, -2, end, zero);
   } 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, zero);
      useblk(fn, fl);
      condexprrec(fn, &ex->sub[2], phis, -1, end, zero);
   } else {
      r = exprvalue(fn, ex);
      if (boolcon == -2)
         r = cvt(fn, TYBOOL, ex->ty.t, r);
      if (boolcon >= 0)
         vpush(&phis->ref, mkintcon(KI4, boolcon));
      else
         vpush(&phis->ref, r);
      if (zero) {
         putcondbranch(fn, r, end, zero);
      } else {
         assert(boolcon < 0);
         putbranch(fn, end);
      }
   }
}

   td.backing = 0;
   for (int t = TYINT; t <= TYUVLONG; ++t) {
      inttyminmax(&tymin, &tymax, t);
      if (minv >= tymin && maxv <= tymax) {
         td.backing = t;
         break;
      }
/* 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, dst, NULL);
   useblk(fn, dst);
   assert(fn->curblk->npred == phis.ref.n);
   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];
   }
   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));
   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));
   }
   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;
   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 type
tagtype(struct comp *cm, enum toktag kind)
static union ref
compileexpr(struct function *fn, const struct expr *ex, bool discard)
{
   struct token tk;
   union type t;
   struct span span;
   enum typetag tt = kind == TKWenum ? TYENUM : kind == TKWstruct ? TYSTRUCT : TYUNION;
   const char *tag = NULL;
   union type ty;
   union ref r, q;
   enum irclass cls = type2cls[ex->ty.t];
   struct instr ins = {0};
   int swp = 0;
   struct expr *sub;

   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);
   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;
      }
      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);
      /* 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);
      }
   } 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);
      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 {
         t = buildenum(cm, tag, &span);
         if (tag) deftagged(cm, &span, TYENUM, tag, t);
         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");
   }

   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};
/************************************/
/* Statements parsing & compilation */
/************************************/

   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");
static void
stmtterm(struct comp *cm)
{
   expect(cm, ';', "to terminate previous statement");
}

/* 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 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
declinsert(struct decllist *list, const struct decllist *node)
deflabel(struct comp *cm, struct function *fn, const struct span *span, const char *name)
{
   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;
   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 int
sclass(struct comp *cm, struct span *span)
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;
   struct expr ex;
   struct env e;
   union ref r;
   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;
   bool terminates = 0;
   bool doemit = fn->curblk;

#define EMITS if (doemit && !nerror)

   while (match(cm, &tk, TKIDENT)) {
      if (match(cm, NULL, ':')){
         /* <label> ':' */
         deflabel(cm, fn, &tk.span, tk.s);
         doemit = 1;
      } else {
         /* kludge for no backtracking and no lookahead */
         ex = exprparse(cm, 1, &tk);
         EMITS expreffects(fn, &ex);
         return fn->curblk != NULL;
      }
   }

   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 {
            if (!terminates) putbranch(fn, end = newblk(fn));
            useblk(fn, fl);
         }
         terminates &= stmt(cm, fn);
         EMITS {
            if (fn->curblk) putbranch(fn, end);
         }
      }
      EMITS if (!terminates) useblk(fn, end);
      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:
       *   <-
       */
      EMITS {
         putbranch(fn, begin = newblk(fn));
         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:
       *   <-
       */
      EMITS {
         putbranch(fn, begin = newblk(fn));
         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);
         }
      }
      EMITS {
         putbranch(fn, end = tr = begin = newblk(fn));
         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 (%ty)", ex.ty);
         EMITS {
            tr = newblk(fn);
            condjump(fn, &ex, tr, fl);
            useblk(fn, tr);
         }
      }
      if (!match(cm, NULL, ')')) { /* iter */
         ex = commaexpr(cm);
         end = newblk(fn);
         expect(cm, ')', NULL);
      }
      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;
      terminates = loopbody(cm, fn, fl, end);

   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);
      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);
         useblk(fn, fl);
      }
      envup(cm);
      break;
   case TKIDENT:
      if (!name)
         error(&tk.span, "unexpected identifier in type name");
      else {
         *name = tk.s;
         *span = tk.span;
      }
   case TKWbreak:
      lex(cm, &tk);
      if (!cm->loopdepth && !cm->switchdepth)
         error(&tk.span, "'break' outside of loop or switch statement");
      EMITS putbranch(fn, cm->loopbreak);
      stmtterm(cm);
      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(&params, 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(&params, 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(&params);
            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));
   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 = pmap_get(&cm->labels, tk.s);
         if (!label) {
            /* create reloc list */
            pmap_set(&cm->labels, tk.s, ((struct label) { .usespan = tk.span, .blk = fn->curblk }));
            fn->curblk = NULL;
         } else if (label && label->usespan.ex.len != 0) {
            /* append to relocs list */
            struct block *next = label->blk;
            fn->curblk->s1 = next;
            label->blk = fn->curblk;
            fn->curblk = NULL;
         } else {
            EMITS putbranch(fn, label->blk);
         }
         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));
      stmtterm(cm);
      break;
   case TKWreturn:
      lex(cm, NULL);
      if (fn->retty.t != TYVOID) {
         ex = commaexpr(cm);
         if (!assigncheck(fn->retty, &ex)) {
            error(&ex.span,
                  "cannot return '%ty' value from function with return type '%ty'",
                  ex.ty, fn->retty);
         }
         if (l->pnames != declpnamestmp) free(l->pnames);
         if (l->pspans != declpspanstmp) free(l->pspans);
         decl.qual = 0;
         break;
         EMITS {
            if (isscalar(fn->retty))
               r = cvt(fn, fn->retty.t, ex.ty.t, exprvalue(fn, &ex));
            else
               r = structreturn(fn, &ex);
            putreturn(fn, r, NOREF);
         }
      } else {
         EMITS putreturn(fn, NOREF, NOREF);
      }

      l->next = declfreelist;
      declfreelist = l;
      stmtterm(cm);
      break;
   default:
      ex = commaexpr(cm);
      stmtterm(cm);
      EMITS expreffects(fn, &ex);
      break;
   }

   return decl;
   freearena(cm->exarena);
   return fn->curblk == NULL;
}

/* parse and compile a function-local declaration */
static void
pstaticassert(struct comp *cm, struct span *span)
localdecl(struct comp *cm, struct function *fn, bool forini)
{
   struct expr ex;
   struct token tk, msg = {0};
   struct expr ini;
   struct token tk;
   const bool doemit = fn->curblk;
   struct declstate st = { DFUNCVAR };

   /* _Static_assert '(' <expr> [ ',' <strlit> ] ')' ';' */
   expect(cm, '(', NULL);
   ex = expr(cm);
   peek(cm, &tk);
   if (match(cm, &tk, ',')) {
      peek(cm, &msg);
      expect(cm, TKSTRLIT, NULL);
   if (!forini && match(cm, &tk, TKIDENT)) {
      if (match(cm, NULL, ':')) {
         /* <label> ':' */
         deflabel(cm, fn, &tk.span, tk.s);
         stmt(cm, fn);
         return;
      }
      st.base = finddecl(cm, tk.s)->ty;
   }
   peek(cm, &tk);
   expect(cm, ')', NULL);
   expect(cm, ';', NULL);
   do {
      struct decl decl = pdecl(&st, cm);
      if (decl.name) {
         static int staticid;
         bool put = 0;

   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");
   }
         switch (decl.scls) {
         case SCSTATIC:
            if (forini)
               error(&decl.span, "static declaration in 'for' loop initializer");
            decl.id = ++staticid;
            break;
         case SCNONE:
            decl.scls = SCAUTO;
         case SCAUTO:
         case SCREGISTER:
            if (isincomplete(decl.ty) || decl.ty.t == TYFUNC) {
               error(&decl.span,
                     "declaring variable '%s' with %s type '%ty'",
                     decl.name, decl.ty.t == TYFUNC ? "function" : "incomplete",
                     decl.ty);
               goto Err;
            }
            EMITS {
               decl.id = addinstr(fn, mkalloca(typesize(decl.ty), typealign(decl.ty))).i;
            }
            if (st.varini) {
               putdecl(cm, &decl);
               put = 1;
               ini = expr(cm);
               pdecl(&st, cm);
               if (!assigncheck(decl.ty, &ini)) {
                  struct span span = decl.span;
                  joinspan(&span.ex, ini.span.ex);
                  error(&span, "cannot initialize '%ty' variable with '%ty'",
                        decl.ty, ini.ty);
               }
               EMITS {
                  if (isagg(decl.ty))
                     structcopy(fn, decl.ty, mkref(RTMP, decl.id), expraddr(fn, &ini));
                  else
                     genstore(fn, decl.ty, mkref(RTMP, decl.id), exprvalue(fn, &ini));
               }
            }
            break;
         case SCTYPEDEF:
            if (forini)
               error(&decl.span, "typedef in 'for' loop initializer");
            break;
         case SCEXTERN:
            if (forini)
               error(&decl.span, "extern declaration in 'for' loop initializer");
            break;
         default: assert(0);
         }
      Err:
         if (!put) putdecl(cm, &decl);
      } else if (forini)
         error(&decl.span, "non-variable declaration in 'for' loop initializer");
   } while (st.more);
}

static struct decl
pdecl(struct declstate *st, struct comp *cm) {
static void
block(struct comp *cm, struct function *fn)
{
   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;
   while (!match(cm, &tk, '}')) {
      if (isdecltok(cm))
         localdecl(cm, fn, 0);
      else
         stmt(cm, fn);
   }
   cm->fnblkspan = tk.span;
}

   if (!st->base.t) {
      if (staticassertok && match(cm, &tk, TKW_Static_assert)) {
         pstaticassert(cm, &tk.span);
         return decl = (struct decl){0};
static void
function(struct comp *cm, struct function *fn, const char **pnames, const struct span *pspans)
{
   const struct typedata *td = &typedata[fn->fnty.dat];
   const bool doemit = fn->curblk;
   struct env e;
   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);
      }
      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);
   }
   /* 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 = tdgetqual(td->quals, i),
                             .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;
            }
         }
         if ((st->scls & allowed) != st->scls)
            error(&tk.span, "this storage class is not allowed in this context");
         st->scls &= allowed;
         putdecl(cm, &arg);
      } else if (ccopt.cstd < STDC23) {
         warn(&pspans[i], "missing name of parameter #%d", i+1);
      }
      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;
   /* end prologue */
   EMITS {
      struct block *blk;
      putbranch(fn, blk = newblk(fn));
      useblk(fn, blk);
   }
   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;
   block(cm, fn);
   envup(cm);
   if (cm->labels.mb.n) {
      const char *name;
      struct label *label;
      pmap_each(&cm->labels, name, label) {
         if (label->usespan.ex.len) {
            error(&label->usespan, "label '%s' used but never defined", name);
         }
      }
      pmap_free(&cm->labels);
   }

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 `,'");
   if (fn->curblk) {
      if (!strcmp(fn->name, "main") && fn->retty.t == TYINT) {
         /* implicit return 0 for main function */
         putreturn(fn, ZEROREF, NOREF);
      } else {
         if (fn->retty.t != TYVOID && !nerror) {
            warn(&cm->fnblkspan, "non-void function may not return a value");
         }
         putreturn(fn, NOREF, NOREF);
      }
   }

   return decl;
}


void
docomp(struct comp *cm)
{