kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

parse_stmt.c (29186B)


      1 /* parse_stmt.c — statement parsers.
      2  *
      3  * Covers §6.8: if, while, for, do-while, return, break, continue, goto,
      4  * labeled, case, default, switch, _Static_assert, asm, compound,
      5  * and the top-level parse_stmt dispatcher.
      6  */
      7 
      8 #include "parse/parse_priv.h"
      9 
     10 /* ============================================================
     11  * File-local helpers
     12  * ============================================================ */
     13 
     14 static SrcLoc tok_loc_stmt(Parser* p, const Tok* t) {
     15   return pp_materialize_loc(p->pp, t->loc);
     16 }
     17 
     18 static int accept_kw_stmt(Parser* p, CKw k) {
     19   if (!is_kw(p, &p->cur, k)) return 0;
     20   advance(p);
     21   return 1;
     22 }
     23 
     24 static void parse_stmt_suppressed(Parser* p) {
     25   c_cg_codegen_suppress_push(p);
     26   parse_stmt(p);
     27   c_cg_codegen_suppress_pop(p);
     28 }
     29 
     30 /* ============================================================
     31  * Statement parsers
     32  * ============================================================ */
     33 
     34 static void parse_if_stmt(Parser* p) {
     35   i64 cond = 0;
     36   int cond_known;
     37   KitCgIf it;
     38   expect_punct(p, '(', "'('");
     39   parse_expr(p);
     40   to_rvalue(p);
     41   if (!c_type_is_scalar(c_cg_top_type(p))) {
     42     perr(p, "if condition requires scalar type");
     43   }
     44   cond_known = kit_cg_top_const_int(p->cg, &cond);
     45   expect_punct(p, ')', "')'");
     46   if (cond_known) {
     47     c_cg_drop(p);
     48     if (cond) {
     49       parse_stmt(p);
     50       if (accept_kw_stmt(p, KW_ELSE)) parse_stmt_suppressed(p);
     51     } else {
     52       parse_stmt_suppressed(p);
     53       if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p);
     54     }
     55     return;
     56   }
     57   /* Structured `if`: nested SCOPE_BLOCKs with break. Lets every backend that
     58    * already lowers SCOPE_BLOCK natively (native arches, Wasm) emit
     59    * `if`/`if-else` without falling back to label/jump primitives.
     60    *
     61    * Under codegen suppression no cond is on the SValue stack, so emit
     62    * nothing and just walk the body for the diagnostics-only pass. */
     63   if (!c_cg_emit_enabled(p)) {
     64     parse_stmt(p);
     65     if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p);
     66     return;
     67   }
     68   it = kit_cg_if_begin(p->cg);
     69   parse_stmt(p);
     70   kit_cg_if_else(p->cg, it);
     71   if (accept_kw_stmt(p, KW_ELSE)) parse_stmt(p);
     72   kit_cg_if_end(p->cg, it);
     73 }
     74 
     75 static void parse_while_stmt(Parser* p) {
     76   /* Drive the structured-CF API so the C-source target can lower this to
     77    * `for (;;) { … break; … continue; }` instead of goto soup. The labels
     78    * the scope mints are reused as cur_break/cur_continue, so parse_break
     79    * /parse_continue/etc. keep using their existing raw `c_cg_jump` calls —
     80    * the C target recognizes the labels as the innermost scope's
     81    * boundaries and emits the structured keywords on its own. */
     82   CGLabel saved_break = p->cur_break;
     83   CGLabel saved_continue = p->cur_continue;
     84   KitCgScope scope;
     85   CGLabel L_top;
     86   CGLabel L_end;
     87   int emit = c_cg_emit_enabled(p);
     88   if (emit) {
     89     scope = kit_cg_scope_begin(p->cg);
     90     L_top = kit_cg_scope_continue_label(p->cg, scope);
     91     L_end = kit_cg_scope_break_label(p->cg, scope);
     92   } else {
     93     scope = 0;
     94     L_top = (CGLabel)1;
     95     L_end = (CGLabel)1;
     96   }
     97   expect_punct(p, '(', "'('");
     98   parse_expr(p);
     99   to_rvalue(p);
    100   if (!c_type_is_scalar(c_cg_top_type(p))) {
    101     perr(p, "while condition requires scalar type");
    102   }
    103   expect_punct(p, ')', "')'");
    104   if (!emit) {
    105     c_cg_drop(p);
    106     p->cur_break = L_end;
    107     p->cur_continue = L_top;
    108     parse_stmt(p);
    109     p->cur_break = saved_break;
    110     p->cur_continue = saved_continue;
    111     return;
    112   }
    113   c_cg_branch_false(p, L_end);
    114   p->cur_break = L_end;
    115   p->cur_continue = L_top;
    116   parse_stmt(p);
    117   p->cur_break = saved_break;
    118   p->cur_continue = saved_continue;
    119   c_cg_jump(p, L_top);
    120   kit_cg_scope_end(p->cg, scope);
    121 }
    122 
    123 static void parse_for_stmt(Parser* p) {
    124   CGLabel L_top = c_cg_label_new(p);
    125   CGLabel L_step = c_cg_label_new(p);
    126   CGLabel L_end = c_cg_label_new(p);
    127   CGLabel saved_break = p->cur_break;
    128   CGLabel saved_continue = p->cur_continue;
    129 
    130   scope_push(p);
    131   expect_punct(p, '(', "'('");
    132 
    133   /* init: declaration | expr | ; */
    134   if (!accept_punct(p, ';')) {
    135     DeclSpecs specs;
    136     if (parse_decl_specs(p, &specs)) {
    137       parse_local_decl(p, &specs);
    138     } else {
    139       parse_expr(p);
    140       c_cg_drop(p);
    141       expect_punct(p, ';', "';'");
    142     }
    143   }
    144 
    145   c_cg_label_place(p, L_top);
    146   if (!is_punct(&p->cur, ';')) {
    147     parse_expr(p);
    148     to_rvalue(p);
    149     if (!c_type_is_scalar(c_cg_top_type(p))) {
    150       perr(p, "for condition requires scalar type");
    151     }
    152     c_cg_branch_false(p, L_end);
    153   }
    154   expect_punct(p, ';', "';'");
    155 
    156   {
    157     CGLabel L_body = c_cg_label_new(p);
    158     c_cg_jump(p, L_body);
    159     c_cg_label_place(p, L_step);
    160     if (!is_punct(&p->cur, ')')) {
    161       parse_expr(p);
    162       c_cg_drop(p);
    163     }
    164     c_cg_jump(p, L_top);
    165     expect_punct(p, ')', "')'");
    166     c_cg_label_place(p, L_body);
    167 
    168     p->cur_break = L_end;
    169     p->cur_continue = L_step;
    170     parse_stmt(p);
    171     p->cur_break = saved_break;
    172     p->cur_continue = saved_continue;
    173 
    174     c_cg_jump(p, L_step);
    175     c_cg_label_place(p, L_end);
    176   }
    177   scope_pop(p);
    178 }
    179 
    180 static void parse_return_stmt(Parser* p) {
    181   if (accept_punct(p, ';')) {
    182     if (p->cur_func_ret && p->cur_func_ret->kind != TY_VOID) {
    183       perr(p, "return with no value in non-void function");
    184     }
    185     c_cg_ret(p, 0);
    186     return;
    187   }
    188   if (p->cur_func_ret && p->cur_func_ret->kind == TY_VOID) {
    189     perr(p, "return with a value in void function");
    190   }
    191   parse_expr(p);
    192   to_rvalue(p);
    193   {
    194     const Type* rhs = c_cg_top_type(p);
    195     CSemCheck chk = c_sem_check_assignment(p->pool, p->cur_func_ret, rhs);
    196     if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message)));
    197   }
    198   /* Convert the value to the function return type, as `return` performs the
    199    * equivalent of assignment to an object of the return type (§6.8.6.4).
    200    * c_cg_ret expects the value already in the return type; without this a
    201    * narrower value (e.g. a _Bool call result returned from an int function)
    202    * would be reloaded at the wider return width and read adjacent bytes. */
    203   coerce_top_to_type(p, p->cur_func_ret);
    204   expect_punct(p, ';', "';' after return value");
    205   c_cg_ret(p, 1);
    206 }
    207 
    208 static void parse_break_stmt(Parser* p) {
    209   if (p->cur_break == 0) perr(p, "'break' outside of loop or switch");
    210   c_cg_jump(p, p->cur_break);
    211   expect_punct(p, ';', "';' after break");
    212 }
    213 
    214 static void parse_continue_stmt(Parser* p) {
    215   if (p->cur_continue == 0) perr(p, "'continue' outside of loop");
    216   c_cg_jump(p, p->cur_continue);
    217   expect_punct(p, ';', "';' after continue");
    218 }
    219 
    220 static void parse_do_stmt(Parser* p) {
    221   CGLabel L_top = c_cg_label_new(p);
    222   CGLabel L_cond = c_cg_label_new(p);
    223   CGLabel L_end = c_cg_label_new(p);
    224   CGLabel saved_break = p->cur_break;
    225   CGLabel saved_continue = p->cur_continue;
    226   c_cg_label_place(p, L_top);
    227   p->cur_break = L_end;
    228   p->cur_continue = L_cond;
    229   parse_stmt(p);
    230   p->cur_break = saved_break;
    231   p->cur_continue = saved_continue;
    232   c_cg_label_place(p, L_cond);
    233   if (!is_kw(p, &p->cur, KW_WHILE)) perr(p, "expected 'while' after do-body");
    234   advance(p); /* while */
    235   expect_punct(p, '(', "'('");
    236   parse_expr(p);
    237   to_rvalue(p);
    238   if (!c_type_is_scalar(c_cg_top_type(p))) {
    239     perr(p, "do-while condition requires scalar type");
    240   }
    241   expect_punct(p, ')', "')' after do-while condition");
    242   expect_punct(p, ';', "';' after do-while");
    243   c_cg_branch_true(p, L_top);
    244   c_cg_label_place(p, L_end);
    245 }
    246 
    247 GotoLabel* label_get_or_create(Parser* p, Sym name, SrcLoc loc) {
    248   GotoLabel* gl;
    249   for (gl = p->goto_labels; gl; gl = gl->next) {
    250     if (gl->name == name) return gl;
    251   }
    252   gl = arena_new(p->pool->arena, GotoLabel);
    253   if (!gl) perr(p, "out of memory in label_get_or_create");
    254   memset(gl, 0, sizeof *gl);
    255   gl->name = name;
    256   /* A goto label's CG-label id outlives any momentary codegen suppression: the
    257    * `LABEL:` placement and its `goto LABEL` references can straddle a
    258    * constant-false (suppressed) region. Key allocation off whether the function
    259    * emits at all, not the transient suppress depth — otherwise a label first
    260    * mentioned inside a suppressed `goto` would cache c_cg_label_new's
    261    * suppression sentinel and later alias the function's first real label
    262    * ("placed twice"). */
    263   gl->label = p->cur_func_emits ? kit_cg_label_new(p->cg) : c_cg_label_new(p);
    264   gl->placed = 0;
    265   gl->first_use = loc;
    266   gl->min_forward_vla_mark = p->vla_mark;
    267   gl->label_vla_mark = 0;
    268   gl->next = p->goto_labels;
    269   p->goto_labels = gl;
    270   return gl;
    271 }
    272 
    273 CGLabel take_label_addr(Parser* p, Sym name, SrcLoc loc) {
    274   GotoLabel* gl;
    275   if (!p->cur_func_name) {
    276     perr(p, "label address ('&&label') is only valid inside a function");
    277   }
    278   gl = label_get_or_create(p, name, loc);
    279   if (p->computed_goto_emitted && !gl->addr_taken) {
    280     perr(p,
    281          "label address taken after a computed 'goto *'; take all label "
    282          "addresses before the first computed goto in a function");
    283   }
    284   gl->addr_taken = 1;
    285   return gl->label;
    286 }
    287 
    288 /* Computed goto: `goto *expr;` (GNU labels-as-values). The branch may target
    289  * any label whose address has been taken in this function with `&&label`. */
    290 static void parse_computed_goto(Parser* p) {
    291   GotoLabel* gl;
    292   CGLabel* targets;
    293   u32 ntargets = 0;
    294   u32 i = 0;
    295   advance(p); /* '*' */
    296   parse_expr(p);
    297   to_rvalue(p);
    298   if (!type_is_ptr(c_cg_top_type(p))) {
    299     perr(p, "computed goto requires a pointer operand");
    300   }
    301   expect_punct(p, ';', "';' after computed goto");
    302   for (gl = p->goto_labels; gl; gl = gl->next) {
    303     if (gl->addr_taken) ++ntargets;
    304   }
    305   if (ntargets == 0) {
    306     perr(p,
    307          "computed 'goto *' requires at least one label whose address is "
    308          "taken with '&&label'");
    309   }
    310   targets = arena_array(p->pool->arena, CGLabel, ntargets);
    311   if (!targets) perr(p, "out of memory for computed goto targets");
    312   for (gl = p->goto_labels; gl; gl = gl->next) {
    313     if (gl->addr_taken) targets[i++] = gl->label;
    314   }
    315   p->computed_goto_emitted = 1;
    316   c_cg_computed_goto(p, targets, ntargets);
    317 }
    318 
    319 static void parse_goto_stmt(Parser* p) {
    320   Sym name;
    321   SrcLoc loc;
    322   GotoLabel* gl;
    323   if (is_punct(&p->cur, '*')) {
    324     parse_computed_goto(p);
    325     return;
    326   }
    327   if (p->cur.kind != TOK_IDENT ||
    328       ident_kw_inline(p, tok_ident(&p->cur)) != KW_NONE) {
    329     perr(p, "expected label name after 'goto'");
    330   }
    331   name = tok_ident(&p->cur);
    332   loc = tok_loc_stmt(p, &p->cur);
    333   advance(p);
    334   expect_punct(p, ';', "';' after goto");
    335   gl = label_get_or_create(p, name, loc);
    336   if (gl->placed) {
    337     if (p->vla_mark < gl->label_vla_mark) {
    338       perr(p, "goto into scope of variably modified object");
    339     }
    340   } else if (p->vla_mark < gl->min_forward_vla_mark) {
    341     gl->min_forward_vla_mark = p->vla_mark;
    342   }
    343   c_cg_jump(p, gl->label);
    344 }
    345 
    346 static void parse_label_stmt(Parser* p) {
    347   Sym name = tok_ident(&p->cur);
    348   SrcLoc loc = tok_loc_stmt(p, &p->cur);
    349   GotoLabel* gl;
    350   advance(p); /* IDENT */
    351   advance(p); /* ':' */
    352   gl = label_get_or_create(p, name, loc);
    353   if (gl->placed) perr(p, "duplicate label");
    354   if (gl->min_forward_vla_mark < p->vla_mark) {
    355     perr(p, "goto into scope of variably modified object");
    356   }
    357   gl->placed = 1;
    358   gl->label_vla_mark = p->vla_mark;
    359   c_cg_label_place(p, gl->label);
    360   parse_stmt(p);
    361 }
    362 
    363 static void parse_case_stmt(Parser* p) {
    364   i64 v;
    365   CGLabel L;
    366   CaseEntry* ce;
    367   SrcLoc loc = tok_loc_stmt(p, &p->cur);
    368   if (!p->cur_switch) perr(p, "'case' label not in switch statement");
    369   v = eval_const_int(p, loc);
    370   for (ce = p->cur_switch->cases; ce; ce = ce->next) {
    371     if (ce->value == v) perr(p, "duplicate case value");
    372   }
    373   expect_punct(p, ':', "':' after case constant");
    374   L = c_cg_label_new(p);
    375   c_cg_label_place(p, L);
    376   ce = arena_new(p->pool->arena, CaseEntry);
    377   if (!ce) perr(p, "out of memory in parse_case_stmt");
    378   ce->value = v;
    379   ce->label = L;
    380   ce->next = p->cur_switch->cases;
    381   p->cur_switch->cases = ce;
    382   parse_stmt(p);
    383 }
    384 
    385 static void parse_default_stmt(Parser* p) {
    386   CGLabel L;
    387   if (!p->cur_switch) perr(p, "'default' label not in switch statement");
    388   expect_punct(p, ':', "':' after default");
    389   if (p->cur_switch->default_label != 0) perr(p, "duplicate 'default' label");
    390   L = c_cg_label_new(p);
    391   c_cg_label_place(p, L);
    392   p->cur_switch->default_label = L;
    393   parse_stmt(p);
    394 }
    395 
    396 static void parse_switch_stmt(Parser* p) {
    397   /* Wrap the whole switch in a structured scope so the C-source target
    398    * renders `break;` as the keyword. Continue isn't applicable to
    399    * switch (C `continue` skips switches and targets the enclosing loop),
    400    * so cur_continue is left alone. The dispatch itself goes through
    401    * `kit_cg_switch_value`, which native arches lower to a cmp_branch
    402    * chain (unchanged behaviour) and which the C target overrides to
    403    * emit a real `switch (sel) { case V: goto L_V; …; default: goto
    404    * L_def; }`. */
    405   CGLabel saved_break = p->cur_break;
    406   SwitchCtx ctx;
    407   SwitchCtx* saved_switch = p->cur_switch;
    408   KitCgScope scope;
    409   CGLabel L_dispatch;
    410   CGLabel L_end;
    411   int emit = c_cg_emit_enabled(p);
    412   FrameSlotDesc fsd;
    413   const Type* vty;
    414   CaseEntry* it;
    415   CaseEntry* prev;
    416   CaseEntry* head;
    417 
    418   if (emit) {
    419     scope = kit_cg_scope_begin(p->cg);
    420     L_dispatch = c_cg_label_new(p);
    421     L_end = kit_cg_scope_break_label(p->cg, scope);
    422   } else {
    423     scope = 0;
    424     L_dispatch = (CGLabel)1;
    425     L_end = (CGLabel)1;
    426   }
    427 
    428   expect_punct(p, '(', "'('");
    429   parse_expr(p);
    430   to_rvalue(p);
    431   vty = c_cg_top_type(p);
    432   if (!vty) vty = type_prim(p->pool, TY_INT);
    433   if (!type_is_int(vty)) perr(p, "switch expression requires integer type");
    434   /* C99 6.8.4.2: the integer promotions are performed on the controlling
    435    * expression. Without this, a `signed char` selector is stored to a
    436    * byte-sized slot and reloaded with an unsigned-byte load, dropping the
    437    * sign bit and miscomparing against negative case constants. */
    438   {
    439     const Type* prom = type_unqual(p->pool, vty);
    440     if (prom && prom->kind == TY_ENUM)
    441       prom = type_prim(p->pool, TY_INT);
    442     else
    443       prom = type_promoted(p->pool, prom);
    444     if (prom && prom != vty) {
    445       c_cg_convert(p, prom);
    446       vty = prom;
    447     }
    448   }
    449   expect_punct(p, ')', "')' after switch expression");
    450 
    451   if (!emit) {
    452     c_cg_drop(p);
    453     memset(&ctx, 0, sizeof ctx);
    454     ctx.parent = saved_switch;
    455     p->cur_switch = &ctx;
    456     p->cur_break = L_end;
    457     parse_stmt(p);
    458     p->cur_break = saved_break;
    459     p->cur_switch = saved_switch;
    460     return;
    461   }
    462 
    463   memset(&ctx, 0, sizeof ctx);
    464   memset(&fsd, 0, sizeof fsd);
    465   fsd.type = vty;
    466   fsd.size = c_abi_sizeof(p->abi, p->pool, vty);
    467   fsd.align = c_abi_alignof(p->abi, p->pool, vty);
    468   fsd.kind = FS_LOCAL;
    469   ctx.value_slot = c_cg_local(p, &fsd);
    470   ctx.value_type = vty;
    471   ctx.parent = saved_switch;
    472 
    473   c_cg_push_local_typed(p, ctx.value_slot, vty);
    474   c_cg_swap(p);
    475   c_cg_store_void(p);
    476 
    477   c_cg_jump(p, L_dispatch);
    478 
    479   p->cur_switch = &ctx;
    480   p->cur_break = L_end;
    481   parse_stmt(p);
    482   p->cur_break = saved_break;
    483   p->cur_switch = saved_switch;
    484 
    485   c_cg_jump(p, L_end);
    486 
    487   c_cg_label_place(p, L_dispatch);
    488   /* Reverse cases into source order; CaseEntry list grows at the head
    489    * during parsing so iteration here is LIFO without the flip. */
    490   prev = NULL;
    491   head = ctx.cases;
    492   while (head) {
    493     CaseEntry* nxt = head->next;
    494     head->next = prev;
    495     prev = head;
    496     head = nxt;
    497   }
    498   /* Count and pack into the value/label arrays the public API expects. */
    499   {
    500     u32 ncases = 0;
    501     for (it = prev; it; it = it->next) ncases++;
    502     if (ncases == 0 && ctx.default_label == 0) {
    503       /* `switch (x) {}` — no cases, no default. Nothing to dispatch.
    504        * Fall through to scope_end which terminates the loop. */
    505     } else {
    506       KitCgSwitchCase* cases =
    507           ncases ? arena_array(p->pool->arena, KitCgSwitchCase, ncases) : NULL;
    508       if (ncases && !cases) perr(p, "out of memory in parse_switch_stmt");
    509       {
    510         u32 i = 0;
    511         for (it = prev; it; it = it->next) {
    512           cases[i].value = (uint64_t)it->value;
    513           cases[i].label = (KitCgLabel)it->label;
    514           i++;
    515         }
    516       }
    517       c_cg_push_local_typed(p, ctx.value_slot, vty);
    518       c_cg_load(p);
    519       {
    520         KitCgSwitch sw;
    521         memset(&sw, 0, sizeof sw);
    522         sw.selector_type = c_cg_tid(p, vty);
    523         sw.default_label = ctx.default_label ? (KitCgLabel)ctx.default_label
    524                                              : (KitCgLabel)L_end;
    525         sw.cases = cases;
    526         sw.ncases = ncases;
    527         sw.hint = KIT_CG_SWITCH_TARGET_DEFAULT;
    528         kit_cg_switch(p->cg, sw);
    529       }
    530     }
    531   }
    532   kit_cg_scope_end(p->cg, scope);
    533 }
    534 
    535 void parse_static_assert(Parser* p) {
    536   SrcLoc loc = tok_loc_stmt(p, &p->cur);
    537   i64 v;
    538   if (!accept_kw_stmt(p, KW_STATIC_ASSERT)) {
    539     perr(p, "expected _Static_assert");
    540   }
    541   expect_punct(p, '(', "'(' after _Static_assert");
    542   v = eval_const_int(p, tok_loc_stmt(p, &p->cur));
    543   expect_punct(p, ',', "',' separating _Static_assert args");
    544   if (p->cur.kind != TOK_STR) {
    545     perr(p, "expected string literal as _Static_assert message");
    546   }
    547   {
    548     Tok msg = p->cur;
    549     advance(p);
    550     expect_punct(p, ')', "')' after _Static_assert");
    551     expect_punct(p, ';', "';' after _Static_assert");
    552     if (!v) {
    553       KitSlice msg_sl = pp_text_slice(p->pp, &msg);
    554       size_t mlen = msg_sl.len;
    555       const char* mstr = msg_sl.s;
    556       compiler_panic(p->c, loc, "static assertion failed: %.*s", (int)mlen,
    557                      mstr ? mstr : "");
    558     }
    559   }
    560 }
    561 
    562 /* GNU inline-asm statement. The leading 'asm'/'__asm__' keyword has
    563  * already been consumed by parse_stmt. */
    564 typedef struct AsmOutLValue {
    565   FrameSlot addr_slot;
    566   FrameSlot value_slot;
    567   const Type* ptr_ty;
    568   const Type* val_ty;
    569   u8 direct_local;
    570   u8 pad[3];
    571 } AsmOutLValue;
    572 
    573 static void asm_out_lvalue_push(Parser* p, const AsmOutLValue* lv) {
    574   if (lv->direct_local) {
    575     c_cg_push_local_typed(p, lv->value_slot, lv->val_ty);
    576     return;
    577   }
    578   c_cg_push_local_typed(p, lv->addr_slot, lv->ptr_ty);
    579   c_cg_load(p);
    580   c_cg_deref(p, lv->val_ty);
    581 }
    582 
    583 static void asm_out_value_push(Parser* p, const AsmOutLValue* lv) {
    584   asm_out_lvalue_push(p, lv);
    585   c_cg_load(p);
    586 }
    587 
    588 static Sym parse_asm_operand_name(Parser* p) {
    589   Sym name = 0;
    590   if (!is_punct(&p->cur, '[')) return 0;
    591   advance(p);
    592   if (p->cur.kind != TOK_IDENT) {
    593     perr(p, "expected identifier inside '[name]' on asm operand");
    594   }
    595   name = tok_ident(&p->cur);
    596   advance(p);
    597   expect_punct(p, ']', "']' after asm operand name");
    598   return name;
    599 }
    600 
    601 static const char* parse_asm_str(Parser* p, const char* what) {
    602   u8* bytes;
    603   size_t nlen = 0;
    604   Sym s;
    605   Tok t;
    606   if (p->cur.kind != TOK_STR) {
    607     perr(p, "expected string literal in %.*s",
    608          KIT_SLICE_ARG(kit_slice_cstr(what)));
    609   }
    610   t = p->cur;
    611   advance(p);
    612   bytes = decode_string_literal(p, &t, &nlen);
    613   if (nlen > 0) nlen -= 1;
    614   s = kit_sym_intern(p->pool->c,
    615                      (KitSlice){.s = (const char*)bytes, .len = nlen});
    616   kit_compiler_context(p->c)->heap->free(kit_compiler_context(p->c)->heap,
    617                                          bytes, 0);
    618   return kit_sym_str(p->pool->c, s).s;
    619 }
    620 
    621 /* GNU local register variables: when an asm operand is exactly a bare reference
    622  * to a `register T x __asm__("reg")` local, return that register name (else 0).
    623  * Called with p->cur positioned at the first token of the operand expression,
    624  * so it only peeks — it must not consume. The operand has to be a lone
    625  * identifier (the canonical idiom); anything more complex is not a
    626  * hard-register operand under GCC's rules either. The name is carried opaquely
    627  * on the constraint's `reg` field; CG/native code validates that the constraint
    628  * is a target register constraint and only the target resolves it to a
    629  * register. */
    630 static Sym asm_operand_pinned_reg(Parser* p, FrameSlot* slot_out) {
    631   Tok nxt;
    632   SymEntry* e;
    633   if (p->cur.kind != TOK_IDENT) return 0;
    634   nxt = peek1(p);
    635   if (!is_punct(&nxt, ')')) return 0;
    636   e = scope_lookup(p, tok_ident(&p->cur));
    637   if (!e || e->kind != SEK_LOCAL) return 0;
    638   if (e->reg_asm_name && slot_out) *slot_out = e->v.slot;
    639   return e->reg_asm_name;
    640 }
    641 
    642 static void parse_asm_stmt(Parser* p) {
    643   const char* tmpl;
    644   AsmConstraint* outs = NULL;
    645   AsmConstraint* ins = NULL;
    646   Sym* clobbers = NULL;
    647   AsmOutLValue* out_lvs = NULL;
    648   u32 nout = 0, nin = 0, nclob = 0;
    649   u32 cap_out = 0, cap_in = 0, cap_clob = 0;
    650   u32 flags = 0;
    651   int saw_goto = 0;
    652   SrcLoc loc = tok_loc_stmt(p, &p->cur);
    653 
    654   for (;;) {
    655     if (accept_kw_stmt(p, KW_VOLATILE)) {
    656       flags |= KIT_CG_ASM_VOLATILE;
    657       continue; /* `volatile` or `__volatile__` */
    658     }
    659     break;
    660   }
    661   if (accept_kw_stmt(p, KW_GOTO)) saw_goto = 1;
    662 
    663   expect_punct(p, '(', "'(' after asm");
    664   tmpl = parse_asm_str(p, "asm template");
    665 
    666   if (accept_punct(p, ':')) {
    667     if (!is_punct(&p->cur, ':') && !is_punct(&p->cur, ')')) {
    668       cap_out = 4;
    669       outs =
    670           (AsmConstraint*)arena_array(p->pool->arena, AsmConstraint, cap_out);
    671       out_lvs =
    672           (AsmOutLValue*)arena_array(p->pool->arena, AsmOutLValue, cap_out);
    673       for (;;) {
    674         AsmConstraint c;
    675         AsmOutLValue lv;
    676         const Type* val_ty;
    677         const Type* ptr_ty;
    678         FrameSlotDesc fsd;
    679         FrameSlot slot;
    680         FrameSlot pinned_slot;
    681         memset(&c, 0, sizeof c);
    682         memset(&lv, 0, sizeof lv);
    683         pinned_slot = FRAME_SLOT_NONE;
    684         c.name = parse_asm_operand_name(p);
    685         c.str = parse_asm_str(p, "asm output constraint");
    686         if (c.str && c.str[0] == '+')
    687           c.dir = ASM_INOUT;
    688         else
    689           c.dir = ASM_OUT;
    690         expect_punct(p, '(', "'(' before asm output lvalue");
    691         c.reg = asm_operand_pinned_reg(p, &pinned_slot);
    692         parse_assign_expr(p);
    693         val_ty = c_cg_top_type(p);
    694         if (!val_ty) perr(p, "asm output: cannot determine lvalue type");
    695         c.type = val_ty;
    696         if (c.reg && pinned_slot != FRAME_SLOT_NONE) {
    697           c_cg_drop(p);
    698           lv.direct_local = 1;
    699           lv.value_slot = pinned_slot;
    700         } else {
    701           c_cg_addr(p);
    702           ptr_ty = c_cg_top_type(p);
    703           if (!ptr_ty) perr(p, "asm output: cannot take address");
    704           memset(&fsd, 0, sizeof fsd);
    705           fsd.type = ptr_ty;
    706           fsd.size = 8;
    707           fsd.align = 8;
    708           fsd.kind = FS_LOCAL;
    709           slot = c_cg_local(p, &fsd);
    710           c_cg_push_local_typed(p, slot, ptr_ty);
    711           c_cg_swap(p);
    712           c_cg_store_void(p);
    713           lv.addr_slot = slot;
    714           lv.ptr_ty = ptr_ty;
    715         }
    716         lv.val_ty = val_ty;
    717         expect_punct(p, ')', "')' after asm output lvalue");
    718         if (nout == cap_out) {
    719           u32 nc = cap_out * 2;
    720           AsmConstraint* nb =
    721               (AsmConstraint*)arena_array(p->pool->arena, AsmConstraint, nc);
    722           AsmOutLValue* nlv =
    723               (AsmOutLValue*)arena_array(p->pool->arena, AsmOutLValue, nc);
    724           memcpy(nb, outs, sizeof(AsmConstraint) * nout);
    725           memcpy(nlv, out_lvs, sizeof(AsmOutLValue) * nout);
    726           outs = nb;
    727           out_lvs = nlv;
    728           cap_out = nc;
    729         }
    730         outs[nout] = c;
    731         out_lvs[nout] = lv;
    732         nout++;
    733         if (!accept_punct(p, ',')) break;
    734       }
    735     }
    736 
    737     if (accept_punct(p, ':')) {
    738       if (!is_punct(&p->cur, ':') && !is_punct(&p->cur, ')')) {
    739         cap_in = 4;
    740         ins =
    741             (AsmConstraint*)arena_array(p->pool->arena, AsmConstraint, cap_in);
    742         for (;;) {
    743           AsmConstraint c;
    744           memset(&c, 0, sizeof c);
    745           c.name = parse_asm_operand_name(p);
    746           c.str = parse_asm_str(p, "asm input constraint");
    747           c.dir = ASM_IN;
    748           expect_punct(p, '(', "'(' before asm input expression");
    749           c.reg = asm_operand_pinned_reg(p, NULL);
    750           parse_assign_expr(p);
    751           to_rvalue(p);
    752           c.type = c_cg_top_type(p);
    753           expect_punct(p, ')', "')' after asm input expression");
    754           if (nin == cap_in) {
    755             u32 nc = cap_in * 2;
    756             AsmConstraint* nb =
    757                 (AsmConstraint*)arena_array(p->pool->arena, AsmConstraint, nc);
    758             memcpy(nb, ins, sizeof(AsmConstraint) * nin);
    759             ins = nb;
    760             cap_in = nc;
    761           }
    762           ins[nin++] = c;
    763           if (!accept_punct(p, ',')) break;
    764         }
    765       }
    766 
    767       if (accept_punct(p, ':')) {
    768         if (!is_punct(&p->cur, ':') && !is_punct(&p->cur, ')')) {
    769           cap_clob = 4;
    770           clobbers = (Sym*)arena_array(p->pool->arena, Sym, cap_clob);
    771           for (;;) {
    772             const char* cstr;
    773             Sym cs;
    774             cstr = parse_asm_str(p, "asm clobber");
    775             cs = kit_sym_intern(p->pool->c, kit_slice_cstr(cstr));
    776             if (nclob == cap_clob) {
    777               u32 nc = cap_clob * 2;
    778               Sym* nb = (Sym*)arena_array(p->pool->arena, Sym, nc);
    779               memcpy(nb, clobbers, sizeof(Sym) * nclob);
    780               clobbers = nb;
    781               cap_clob = nc;
    782             }
    783             clobbers[nclob++] = cs;
    784             if (!accept_punct(p, ',')) break;
    785           }
    786         }
    787 
    788         if (accept_punct(p, ':')) {
    789           if (!is_punct(&p->cur, ')')) {
    790             for (;;) {
    791               if (p->cur.kind != TOK_IDENT) {
    792                 perr(p, "expected label identifier in asm-goto label list");
    793               }
    794               advance(p);
    795               if (!accept_punct(p, ',')) break;
    796             }
    797           }
    798         }
    799       }
    800     }
    801   }
    802 
    803   expect_punct(p, ')', "')' to close asm");
    804   expect_punct(p, ';', "';' after asm statement");
    805 
    806   (void)saw_goto;
    807 
    808   u32 ninout = 0;
    809   for (u32 i = 0; i < nout; ++i) {
    810     if (outs[i].dir == ASM_INOUT) ninout++;
    811   }
    812   if (ninout > 0) {
    813     static const char* const k_match_strs[10] = {"0", "1", "2", "3", "4",
    814                                                  "5", "6", "7", "8", "9"};
    815     u32 need = nin + ninout;
    816     if (need > cap_in) {
    817       u32 nc = cap_in ? cap_in : 4;
    818       while (nc < need) nc *= 2;
    819       AsmConstraint* nb =
    820           (AsmConstraint*)arena_array(p->pool->arena, AsmConstraint, nc);
    821       if (nin) memcpy(nb, ins, sizeof(AsmConstraint) * nin);
    822       ins = nb;
    823       cap_in = nc;
    824     }
    825     for (u32 i = 0; i < nout; ++i) {
    826       if (outs[i].dir != ASM_INOUT) continue;
    827       if (i >= 10) {
    828         perr(p,
    829              "asm: '+r' constraint at output index >9 exceeds "
    830              "matching-digit syntax");
    831       }
    832       AsmOutLValue* lv = &out_lvs[i];
    833       asm_out_value_push(p, lv);
    834       AsmConstraint mc;
    835       memset(&mc, 0, sizeof mc);
    836       mc.str = k_match_strs[i];
    837       mc.dir = ASM_IN;
    838       mc.type = lv->val_ty;
    839       ins[nin++] = mc;
    840     }
    841   }
    842 
    843   c_cg_set_loc(p, loc);
    844   c_cg_inline_asm(p, tmpl, outs, nout, ins, nin, clobbers, nclob, flags);
    845 
    846   if (nout > 0) {
    847     u32 i;
    848     for (i = nout; i-- > 0;) {
    849       AsmOutLValue* lv = &out_lvs[i];
    850       asm_out_lvalue_push(p, lv);
    851       c_cg_swap(p);
    852       c_cg_store_void(p);
    853     }
    854   }
    855 }
    856 
    857 void parse_compound_stmt(Parser* p) {
    858   expect_punct(p, '{', "'{'");
    859   scope_push(p);
    860   while (!is_punct(&p->cur, '}') && p->cur.kind != TOK_EOF) {
    861     if (p->cur.kind == TOK_NEWLINE || is_pp_hash(&p->cur)) {
    862       advance(p);
    863       continue;
    864     }
    865     if (is_kw(p, &p->cur, KW_STATIC_ASSERT)) {
    866       parse_static_assert(p);
    867       continue;
    868     }
    869     {
    870       DeclSpecs specs;
    871       Tok save_tok = p->cur;
    872       (void)save_tok;
    873       if (parse_decl_specs(p, &specs)) {
    874         parse_local_decl(p, &specs);
    875       } else {
    876         parse_stmt(p);
    877       }
    878     }
    879     /* Statement boundary: the value stack is back to empty, so every transient
    880      * compiler temp minted by this statement is dead. Recycle their frame homes
    881      * for the next statement, bounding the -O0 frame. */
    882     c_cg_reclaim_temps(p);
    883   }
    884   expect_punct(p, '}', "'}'");
    885   scope_pop(p);
    886 }
    887 
    888 void parse_stmt(Parser* p) {
    889   c_cg_set_loc(p, tok_loc_stmt(p, &p->cur));
    890   if (p->cur.kind == TOK_IDENT &&
    891       ident_kw_inline(p, tok_ident(&p->cur)) == KW_NONE) {
    892     Tok n = peek1(p);
    893     if (is_punct(&n, ':')) {
    894       parse_label_stmt(p);
    895       return;
    896     }
    897   }
    898   if (is_punct(&p->cur, '{')) {
    899     parse_compound_stmt(p);
    900     return;
    901   }
    902   if (is_punct(&p->cur, ';')) {
    903     advance(p);
    904     return;
    905   }
    906   if (is_kw(p, &p->cur, KW_IF)) {
    907     advance(p);
    908     parse_if_stmt(p);
    909     return;
    910   }
    911   if (is_kw(p, &p->cur, KW_WHILE)) {
    912     advance(p);
    913     parse_while_stmt(p);
    914     return;
    915   }
    916   if (is_kw(p, &p->cur, KW_FOR)) {
    917     advance(p);
    918     parse_for_stmt(p);
    919     return;
    920   }
    921   if (is_kw(p, &p->cur, KW_DO)) {
    922     advance(p);
    923     parse_do_stmt(p);
    924     return;
    925   }
    926   if (is_kw(p, &p->cur, KW_RETURN)) {
    927     advance(p);
    928     parse_return_stmt(p);
    929     return;
    930   }
    931   if (is_kw(p, &p->cur, KW_BREAK)) {
    932     advance(p);
    933     parse_break_stmt(p);
    934     return;
    935   }
    936   if (is_kw(p, &p->cur, KW_CONTINUE)) {
    937     advance(p);
    938     parse_continue_stmt(p);
    939     return;
    940   }
    941   if (is_kw(p, &p->cur, KW_GOTO)) {
    942     advance(p);
    943     parse_goto_stmt(p);
    944     return;
    945   }
    946   if (is_kw(p, &p->cur, KW_SWITCH)) {
    947     advance(p);
    948     parse_switch_stmt(p);
    949     return;
    950   }
    951   if (is_kw(p, &p->cur, KW_CASE)) {
    952     advance(p);
    953     parse_case_stmt(p);
    954     return;
    955   }
    956   if (is_kw(p, &p->cur, KW_DEFAULT)) {
    957     advance(p);
    958     parse_default_stmt(p);
    959     return;
    960   }
    961   if (is_kw(p, &p->cur, KW_ASM) || is_kw(p, &p->cur, KW_BUILTIN_ASM)) {
    962     advance(p);
    963     parse_asm_stmt(p);
    964     return;
    965   }
    966   /* Expression statement. */
    967   parse_expr(p);
    968   c_cg_drop(p);
    969   expect_punct(p, ';', "';' after expression");
    970 }