kit

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

cg_control_test.c (19493B)


      1 /* cg_control_test — drives the conditional structured control-flow ops
      2  * (break_true / break_false / continue_true / continue_false) through the
      3  * public KitCg API. break_true / continue_true / continue_false have zero
      4  * callers in any frontend (toy uses only break / break_false / continue), so
      5  * like the unordered FP compares they are reachable *only* through this entry
      6  * point — this is the guard against an advertise-but-ignore gap in the
      7  * structured-control surface.
      8  *
      9  * Each variant builds the same loop, `int f(int n) { return 0+1+..+(n-1); }`,
     10  * spelled with a different conditional break/continue, and:
     11  *
     12  *   Execution — captures each into the in-process interpreter (target-
     13  *     independent IR) and asserts the sum for a spread of n.
     14  *
     15  *   Emission — builds each for aarch64 / x86-64 / riscv64 at -O0 and -O1 and
     16  *     finalizes; a backend that mishandles the lowered branches panics, which
     17  *     fails the test.
     18  *
     19  *   Rejection — confirms `continue` on a forward-only block scope (which has no
     20  *     loop header) is rejected with a clean diagnostic rather than emitting a
     21  *     jump to a nonexistent label.
     22  *
     23  * Run by: make test-cg-api
     24  */
     25 
     26 #include <kit/cg.h>
     27 #include <kit/core.h>
     28 #include <kit/frontend.h>
     29 #include <kit/interp.h>
     30 #include <kit/object.h>
     31 #include <stdint.h>
     32 #include <stdio.h>
     33 #include <string.h>
     34 
     35 #include "lib/kit_unit.h"
     36 
     37 static KitUnit g_u;
     38 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__)
     39 
     40 typedef enum {
     41   V_BREAK_TRUE,
     42   V_BREAK_FALSE,
     43   V_CONTINUE_TRUE,
     44   V_CONTINUE_FALSE
     45 } Variant;
     46 
     47 typedef struct {
     48   Variant variant;
     49   const char* name;
     50 } VariantDesc;
     51 
     52 static const VariantDesc VARIANTS[] = {
     53     {V_BREAK_TRUE, "break_true"},
     54     {V_BREAK_FALSE, "break_false"},
     55     {V_CONTINUE_TRUE, "continue_true"},
     56     {V_CONTINUE_FALSE, "continue_false"},
     57 };
     58 enum { NVAR = (int)(sizeof VARIANTS / sizeof VARIANTS[0]) };
     59 
     60 /* Reference: sum 0 + 1 + ... + (n-1), with n clamped at 0. */
     61 static int64_t loop_expected(int64_t n) {
     62   int64_t acc = 0, i;
     63   for (i = 0; i < n; ++i) acc += i;
     64   return acc;
     65 }
     66 
     67 /* Build `int <name>(int n)` computing sum(0..n-1) via `variant`.
     68  *
     69  * break_*  : cond-at-top loop  — test, conditionally exit, else body, continue.
     70  * continue_*: cond-at-bottom loop — body, test, conditionally re-loop.
     71  * All four are equivalent; they exercise the four conditional ops plus plain
     72  * `continue` (in the break_* shapes). */
     73 static void build_loop_fn(KitCompiler* c, KitCg* cg, const char* name,
     74                           Variant variant) {
     75   KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32);
     76   KitCgFuncParam param;
     77   KitCgFuncResult result;
     78   KitCgFuncSig sig;
     79   KitCgDecl decl;
     80   KitCgSym sym;
     81   KitCgLocalAttrs la;
     82   KitCgLocal n_param, i_local, acc_local;
     83   KitCgMemAccess ma;
     84   KitCgScope scope;
     85 
     86   memset(&param, 0, sizeof param);
     87   param.type = i32;
     88   memset(&result, 0, sizeof result);
     89   result.type = i32;
     90   memset(&sig, 0, sizeof sig);
     91   sig.result = result;
     92   sig.params = &param;
     93   sig.nparams = 1;
     94   sig.call_conv = KIT_CG_CC_TARGET_C;
     95 
     96   memset(&decl, 0, sizeof decl);
     97   decl.kind = KIT_CG_DECL_FUNC;
     98   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name));
     99   decl.display_name = decl.linkage_name;
    100   decl.type = kit_cg_type_func(c, sig);
    101   decl.sym.bind = KIT_SB_GLOBAL;
    102   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    103   sym = kit_cg_decl(cg, decl);
    104   EXPECT(sym != KIT_CG_SYM_NONE, "%s: decl failed", name);
    105 
    106   kit_cg_func_begin(cg, sym);
    107   memset(&la, 0, sizeof la);
    108   n_param = kit_cg_param(cg, 0, i32, la);
    109   i_local = kit_cg_local(cg, i32, la);
    110   acc_local = kit_cg_local(cg, i32, la);
    111 
    112   memset(&ma, 0, sizeof ma);
    113   ma.type = i32;
    114   ma.align = kit_cg_type_align(c, i32);
    115 
    116   /* i = 0; acc = 0; */
    117   kit_cg_push_int(cg, 0, i32);
    118   kit_cg_local_write(cg, i_local, ma);
    119   kit_cg_push_int(cg, 0, i32);
    120   kit_cg_local_write(cg, acc_local, ma);
    121 
    122   scope = kit_cg_scope_begin(cg);
    123 
    124   if (variant == V_BREAK_TRUE || variant == V_BREAK_FALSE) {
    125     /* test at top: break out when i has reached n */
    126     kit_cg_local_read(cg, i_local, ma);
    127     kit_cg_local_read(cg, n_param, ma);
    128     if (variant == V_BREAK_TRUE) {
    129       kit_cg_int_cmp(cg, KIT_CG_INT_GE_S); /* i >= n */
    130       kit_cg_break_true(cg, scope);        /* exit when true */
    131     } else {
    132       kit_cg_int_cmp(cg, KIT_CG_INT_LT_S); /* i < n */
    133       kit_cg_break_false(cg, scope);       /* exit when false (i >= n) */
    134     }
    135     /* acc += i; i += 1; */
    136     kit_cg_local_read(cg, acc_local, ma);
    137     kit_cg_local_read(cg, i_local, ma);
    138     kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    139     kit_cg_local_write(cg, acc_local, ma);
    140     kit_cg_local_read(cg, i_local, ma);
    141     kit_cg_push_int(cg, 1, i32);
    142     kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    143     kit_cg_local_write(cg, i_local, ma);
    144     kit_cg_continue(cg, scope);
    145   } else {
    146     /* body first, test at bottom: re-loop while i < n */
    147     kit_cg_local_read(cg, acc_local, ma);
    148     kit_cg_local_read(cg, i_local, ma);
    149     kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    150     kit_cg_local_write(cg, acc_local, ma);
    151     kit_cg_local_read(cg, i_local, ma);
    152     kit_cg_push_int(cg, 1, i32);
    153     kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    154     kit_cg_local_write(cg, i_local, ma);
    155     kit_cg_local_read(cg, i_local, ma);
    156     kit_cg_local_read(cg, n_param, ma);
    157     if (variant == V_CONTINUE_TRUE) {
    158       kit_cg_int_cmp(cg, KIT_CG_INT_LT_S); /* i < n */
    159       kit_cg_continue_true(cg, scope);     /* re-loop while true */
    160     } else {
    161       kit_cg_int_cmp(cg, KIT_CG_INT_GE_S); /* i >= n */
    162       kit_cg_continue_false(cg, scope);    /* re-loop while false (i < n) */
    163     }
    164   }
    165 
    166   kit_cg_scope_end(cg, scope);
    167   kit_cg_local_read(cg, acc_local, ma);
    168   kit_cg_ret(cg);
    169   kit_cg_func_end(cg);
    170 }
    171 
    172 /* ---- Multi-value scopes (kit_cg_*_begin_sig) ------------------------- *
    173  * Drives the multi-value structured-scope surface added for Wasm multi-value
    174  * blocks: a block carrying N results, and a loop carrying N params snapshotted
    175  * across the back edge via kit_cg_scope_store_params. Each builds `int f(int)`
    176  * so the same interpreter/emit harness as the conditional variants applies. */
    177 
    178 /* Boilerplate: declare `int <name>(int)` and open it; returns the param
    179  * local and fills *out_i32 with the i32 type id and *out_ma with an i32 access.
    180  */
    181 static KitCgLocal begin_int_int_fn(KitCompiler* c, KitCg* cg, const char* name,
    182                                    KitCgTypeId* out_i32,
    183                                    KitCgMemAccess* out_ma) {
    184   KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32);
    185   KitCgFuncParam param;
    186   KitCgFuncResult result;
    187   KitCgFuncSig sig;
    188   KitCgDecl decl;
    189   KitCgSym sym;
    190   KitCgLocalAttrs la;
    191   memset(&param, 0, sizeof param);
    192   param.type = i32;
    193   memset(&result, 0, sizeof result);
    194   result.type = i32;
    195   memset(&sig, 0, sizeof sig);
    196   sig.result = result;
    197   sig.params = &param;
    198   sig.nparams = 1;
    199   sig.call_conv = KIT_CG_CC_TARGET_C;
    200   memset(&decl, 0, sizeof decl);
    201   decl.kind = KIT_CG_DECL_FUNC;
    202   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name));
    203   decl.display_name = decl.linkage_name;
    204   decl.type = kit_cg_type_func(c, sig);
    205   decl.sym.bind = KIT_SB_GLOBAL;
    206   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    207   sym = kit_cg_decl(cg, decl);
    208   EXPECT(sym != KIT_CG_SYM_NONE, "%s: decl failed", name);
    209   kit_cg_func_begin(cg, sym);
    210   memset(&la, 0, sizeof la);
    211   *out_i32 = i32;
    212   memset(out_ma, 0, sizeof *out_ma);
    213   out_ma->type = i32;
    214   out_ma->align = kit_cg_type_align(c, i32);
    215   return kit_cg_param(cg, 0, i32, la);
    216 }
    217 
    218 /* int two_result_block(int x) { return (x+1) + (x+2); }  // == 2x + 3
    219  * via a block carrying two i32 results that scope_end yields bottom->top. */
    220 static void build_two_result_block(KitCompiler* c, KitCg* cg,
    221                                    const char* name) {
    222   KitCgTypeId i32;
    223   KitCgMemAccess ma;
    224   KitCgLocal x = begin_int_int_fn(c, cg, name, &i32, &ma);
    225   KitCgTypeId results[2] = {i32, i32};
    226   KitCgScopeSig sig;
    227   KitCgScope blk;
    228   memset(&sig, 0, sizeof sig);
    229   sig.results = results;
    230   sig.nresults = 2;
    231   blk = kit_cg_block_begin_sig(cg, &sig);
    232   kit_cg_local_read(cg, x, ma); /* x */
    233   kit_cg_push_int(cg, 1, i32);
    234   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); /* x+1 */
    235   kit_cg_local_read(cg, x, ma);
    236   kit_cg_push_int(cg, 2, i32);
    237   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); /* x+2 */
    238   kit_cg_scope_end(cg, blk);               /* yields [x+1, x+2] */
    239   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); /* (x+1)+(x+2) */
    240   kit_cg_ret(cg);
    241   kit_cg_func_end(cg);
    242 }
    243 
    244 /* int one_param_loop(int n) { int acc=0; for (int i=0;i<n;i++) acc+=i; return
    245  * acc; } The loop carries i as a single param across the back edge; acc is a
    246  * plain local. Exercises scope_begin_sig(1 param) + store_params + continue,
    247  * with the loop exited by falling through the body bottom (no break). */
    248 static void build_one_param_loop(KitCompiler* c, KitCg* cg, const char* name) {
    249   KitCgTypeId i32;
    250   KitCgMemAccess ma;
    251   KitCgLocal n = begin_int_int_fn(c, cg, name, &i32, &ma);
    252   KitCgLocalAttrs la;
    253   KitCgLocal i_l, acc;
    254   KitCgTypeId params[1];
    255   KitCgScopeSig sig;
    256   KitCgScope loop;
    257   KitCgLabel done;
    258   memset(&la, 0, sizeof la);
    259   i_l = kit_cg_local(cg, i32, la);
    260   acc = kit_cg_local(cg, i32, la);
    261   kit_cg_push_int(cg, 0, i32);
    262   kit_cg_local_write(cg, acc, ma);
    263   kit_cg_push_int(cg, 0, i32); /* initial loop param i = 0 */
    264   params[0] = i32;
    265   memset(&sig, 0, sizeof sig);
    266   sig.params = params;
    267   sig.nparams = 1;
    268   loop = kit_cg_scope_begin_sig(cg, &sig);
    269   done = kit_cg_label_new(cg);
    270   kit_cg_local_write(cg, i_l, ma); /* pop the loop param i */
    271   kit_cg_local_read(cg, i_l, ma);
    272   kit_cg_local_read(cg, n, ma);
    273   kit_cg_int_cmp(cg, KIT_CG_INT_LT_S); /* i < n ? */
    274   kit_cg_branch_false(cg, done);       /* exit when i >= n */
    275   kit_cg_local_read(cg, acc, ma);
    276   kit_cg_local_read(cg, i_l, ma);
    277   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    278   kit_cg_local_write(cg, acc, ma); /* acc += i */
    279   kit_cg_local_read(cg, i_l, ma);
    280   kit_cg_push_int(cg, 1, i32);
    281   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    282   kit_cg_local_write(cg, i_l, ma); /* i += 1 */
    283   kit_cg_local_read(cg, i_l, ma);  /* fresh loop param */
    284   kit_cg_scope_store_params(cg, loop);
    285   kit_cg_jump(cg, kit_cg_scope_continue_label(cg, loop));
    286   kit_cg_label_place(cg, done);
    287   kit_cg_scope_end(cg, loop);
    288   kit_cg_local_read(cg, acc, ma);
    289   kit_cg_ret(cg);
    290   kit_cg_func_end(cg);
    291 }
    292 
    293 /* int two_param_loop(int n) { ... same sum, both i and acc carried as the two
    294  * loop params, and the result carried out as the loop's single result. }
    295  * Exercises scope_begin_sig(2 params, 1 result) + reverse-order store_params +
    296  * a value-carrying fall-through scope_end. */
    297 static void build_two_param_loop(KitCompiler* c, KitCg* cg, const char* name) {
    298   KitCgTypeId i32;
    299   KitCgMemAccess ma;
    300   KitCgLocal n = begin_int_int_fn(c, cg, name, &i32, &ma);
    301   KitCgLocalAttrs la;
    302   KitCgLocal i_l, acc_l;
    303   KitCgTypeId params[2];
    304   KitCgTypeId results[1];
    305   KitCgScopeSig sig;
    306   KitCgScope loop;
    307   KitCgLabel done;
    308   memset(&la, 0, sizeof la);
    309   i_l = kit_cg_local(cg, i32, la);
    310   acc_l = kit_cg_local(cg, i32, la);
    311   kit_cg_push_int(cg, 0, i32); /* i */
    312   kit_cg_push_int(cg, 0, i32); /* acc */
    313   params[0] = i32;
    314   params[1] = i32;
    315   results[0] = i32;
    316   memset(&sig, 0, sizeof sig);
    317   sig.params = params;
    318   sig.nparams = 2;
    319   sig.results = results;
    320   sig.nresults = 1;
    321   loop = kit_cg_scope_begin_sig(cg, &sig);
    322   done = kit_cg_label_new(cg);
    323   /* stack on body entry: [i, acc] (bottom->top). Pop acc then i. */
    324   kit_cg_local_write(cg, acc_l, ma);
    325   kit_cg_local_write(cg, i_l, ma);
    326   kit_cg_local_read(cg, i_l, ma);
    327   kit_cg_local_read(cg, n, ma);
    328   kit_cg_int_cmp(cg, KIT_CG_INT_LT_S);
    329   kit_cg_branch_false(cg, done); /* exit (with acc) when i >= n */
    330   kit_cg_local_read(cg, acc_l, ma);
    331   kit_cg_local_read(cg, i_l, ma);
    332   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    333   kit_cg_local_write(cg, acc_l, ma); /* acc += i */
    334   kit_cg_local_read(cg, i_l, ma);
    335   kit_cg_push_int(cg, 1, i32);
    336   kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0);
    337   kit_cg_local_write(cg, i_l, ma);     /* i += 1 */
    338   kit_cg_local_read(cg, i_l, ma);      /* fresh param i */
    339   kit_cg_local_read(cg, acc_l, ma);    /* fresh param acc */
    340   kit_cg_scope_store_params(cg, loop); /* reverse-pop into [i,acc] locals */
    341   kit_cg_jump(cg, kit_cg_scope_continue_label(cg, loop));
    342   kit_cg_label_place(cg, done);
    343   kit_cg_local_read(cg, acc_l, ma); /* fall-through result */
    344   kit_cg_scope_end(cg, loop);       /* yields acc */
    345   kit_cg_ret(cg);
    346   kit_cg_func_end(cg);
    347 }
    348 
    349 typedef struct {
    350   const char* name;
    351   void (*build)(KitCompiler*, KitCg*, const char*);
    352 } MvDesc;
    353 static const MvDesc MVS[] = {
    354     {"two_result_block", build_two_result_block},
    355     {"one_param_loop", build_one_param_loop},
    356     {"two_param_loop", build_two_param_loop},
    357 };
    358 enum { NMV = (int)(sizeof MVS / sizeof MVS[0]) };
    359 
    360 /* Reference value for the multi-value builders given input x. */
    361 static int64_t mv_expected(const char* name, int64_t x) {
    362   if (strcmp(name, "two_result_block") == 0) return 2 * x + 3;
    363   return loop_expected(x); /* the two loops both compute sum(0..x-1) */
    364 }
    365 
    366 /* ---- Execution coverage (in-process interpreter) -------------------- */
    367 
    368 static const int64_t NS[] = {0, 1, 2, 5, 10};
    369 enum { NNS = (int)(sizeof NS / sizeof NS[0]) };
    370 
    371 static void run_exec(void) {
    372   KitTargetSpec tgt =
    373       kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    374   KitCompiler* c = NULL;
    375   KitInterpProgram* pp;
    376   KitObjBuilder* ob = NULL;
    377   KitCg* cg = NULL;
    378   KitCodeOptions opts;
    379   int i, j;
    380   char nm[32];
    381 
    382   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    383     EXPECT(0, "exec: compiler_new failed");
    384     return;
    385   }
    386   pp = kit_interp_program_new(c);
    387   EXPECT(pp != NULL, "exec: interp_program_new failed");
    388   kit_interp_program_attach(pp, c);
    389 
    390   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "exec: obj_new");
    391   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "exec: cg_new");
    392   memset(&opts, 0, sizeof opts);
    393   opts.opt_level = 1; /* interp capture requires the optimizer pass */
    394   kit_cg_begin(cg, ob, &opts);
    395 
    396   for (i = 0; i < NVAR; ++i) {
    397     snprintf(nm, sizeof nm, "loop_%s", VARIANTS[i].name);
    398     build_loop_fn(c, cg, nm, VARIANTS[i].variant);
    399   }
    400   for (i = 0; i < NMV; ++i) MVS[i].build(c, cg, MVS[i].name);
    401   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "exec: finish");
    402   EXPECT(kit_cg_detach(cg) == KIT_OK, "exec: detach");
    403 
    404   for (i = 0; i < NVAR; ++i) {
    405     KitInterpFunc* fn;
    406     snprintf(nm, sizeof nm, "loop_%s", VARIANTS[i].name);
    407     fn = kit_interp_lookup(pp, kit_slice_cstr(nm));
    408     EXPECT(fn != NULL, "exec: %s not captured", VARIANTS[i].name);
    409     if (!fn) continue;
    410     for (j = 0; j < NNS; ++j) {
    411       uint64_t args[1] = {(uint64_t)NS[j]};
    412       int64_t ret = -1;
    413       int64_t want = loop_expected(NS[j]);
    414       KitInterpStatus s = kit_interp_call_args(pp, fn, args, 1, &ret);
    415       EXPECT(s == KIT_INTERP_DONE && ret == want,
    416              "%s(%lld): want %lld got %lld (status %d)", VARIANTS[i].name,
    417              (long long)NS[j], (long long)want, (long long)ret, (int)s);
    418     }
    419   }
    420 
    421   for (i = 0; i < NMV; ++i) {
    422     KitInterpFunc* fn = kit_interp_lookup(pp, kit_slice_cstr(MVS[i].name));
    423     EXPECT(fn != NULL, "exec: %s not captured", MVS[i].name);
    424     if (!fn) continue;
    425     for (j = 0; j < NNS; ++j) {
    426       uint64_t args[1] = {(uint64_t)NS[j]};
    427       int64_t ret = -1;
    428       int64_t want = mv_expected(MVS[i].name, NS[j]);
    429       KitInterpStatus s = kit_interp_call_args(pp, fn, args, 1, &ret);
    430       EXPECT(s == KIT_INTERP_DONE && ret == want,
    431              "%s(%lld): want %lld got %lld (status %d)", MVS[i].name,
    432              (long long)NS[j], (long long)want, (long long)ret, (int)s);
    433     }
    434   }
    435 
    436   kit_cg_free(cg);
    437   kit_obj_builder_free(ob);
    438   kit_interp_program_free(pp);
    439   kit_compiler_free(c);
    440 }
    441 
    442 /* ---- Emission coverage (every native backend, no execution) --------- */
    443 
    444 static void run_emit(KitArchKind arch, KitOSKind os, KitObjFmt fmt,
    445                      const char* tag, int opt_level) {
    446   KitTargetSpec tgt = kit_unit_target(arch, os, fmt);
    447   KitCompiler* c = NULL;
    448   KitObjBuilder* ob = NULL;
    449   KitCg* cg = NULL;
    450   KitCodeOptions opts;
    451   int i;
    452   char nm[48];
    453 
    454   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    455     EXPECT(0, "%s/O%d: compiler_new failed", tag, opt_level);
    456     return;
    457   }
    458   EXPECT(kit_obj_builder_new(c, &ob) == KIT_OK && ob, "%s: obj_new", tag);
    459   EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "%s: cg_new", tag);
    460   memset(&opts, 0, sizeof opts);
    461   opts.opt_level = opt_level;
    462   kit_cg_begin(cg, ob, &opts);
    463 
    464   for (i = 0; i < NVAR; ++i) {
    465     snprintf(nm, sizeof nm, "emit_%s_o%d_%s", tag, opt_level, VARIANTS[i].name);
    466     build_loop_fn(c, cg, nm, VARIANTS[i].variant);
    467   }
    468   for (i = 0; i < NMV; ++i) {
    469     snprintf(nm, sizeof nm, "emit_%s_o%d_%s", tag, opt_level, MVS[i].name);
    470     MVS[i].build(c, cg, nm);
    471   }
    472   EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "%s/O%d: finish failed", tag,
    473          opt_level);
    474   EXPECT(kit_cg_detach(cg) == KIT_OK, "%s/O%d: detach failed", tag, opt_level);
    475 
    476   kit_cg_free(cg);
    477   kit_obj_builder_free(ob);
    478   kit_compiler_free(c);
    479 }
    480 
    481 /* ---- Rejection: continue on a forward-only block scope -------------- */
    482 
    483 typedef struct {
    484   KitObjBuilder* ob;
    485   KitCg* cg;
    486 } RejectCtx;
    487 
    488 static KitStatus reject_body(KitCompiler* c, void* user) {
    489   RejectCtx* r = (RejectCtx*)user;
    490   KitCgFuncSig sig;
    491   KitCgFuncResult result;
    492   KitCgDecl decl;
    493   KitCgSym sym;
    494   KitCgScope blk;
    495   memset(&sig, 0, sizeof sig);
    496   memset(&result, 0, sizeof result);
    497   result.type = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID);
    498   sig.result = result;
    499   sig.call_conv = KIT_CG_CC_TARGET_C; /* void() */
    500   memset(&decl, 0, sizeof decl);
    501   decl.kind = KIT_CG_DECL_FUNC;
    502   decl.linkage_name = kit_sym_intern(c, kit_slice_cstr("reject_fn"));
    503   decl.display_name = decl.linkage_name;
    504   decl.type = kit_cg_type_func(c, sig);
    505   decl.sym.bind = KIT_SB_GLOBAL;
    506   decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    507   sym = kit_cg_decl(r->cg, decl);
    508   kit_cg_func_begin(r->cg, sym);
    509   blk = kit_cg_block_begin(r->cg);
    510   kit_cg_continue(r->cg, blk); /* <- must panic: blocks have no loop header */
    511   kit_cg_scope_end(r->cg, blk);
    512   kit_cg_ret(r->cg);
    513   kit_cg_func_end(r->cg);
    514   return KIT_OK;
    515 }
    516 
    517 static void run_reject(void) {
    518   KitTargetSpec tgt =
    519       kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF);
    520   KitCompiler* c = NULL;
    521   RejectCtx r;
    522   KitCodeOptions opts;
    523   KitStatus st;
    524   memset(&r, 0, sizeof r);
    525   if (kit_unit_compiler_new(&g_u, tgt, &c) != KIT_OK || !c) {
    526     EXPECT(0, "reject: compiler_new failed");
    527     return;
    528   }
    529   EXPECT(kit_obj_builder_new(c, &r.ob) == KIT_OK && r.ob, "reject: obj_new");
    530   EXPECT(kit_cg_new(c, &r.cg) == KIT_OK && r.cg, "reject: cg_new");
    531   memset(&opts, 0, sizeof opts);
    532   kit_cg_begin(r.cg, r.ob, &opts);
    533 
    534   g_u.suppress_fatal = 1;
    535   st = kit_frontend_run(c, reject_body, &r);
    536   g_u.suppress_fatal = 0;
    537 
    538   EXPECT(st == KIT_ERR, "continue on block scope should be rejected");
    539   EXPECT(strstr(g_u.last_diag, "forward-only block scope") != NULL,
    540          "rejection diagnostic should name the block scope (got: %s)",
    541          g_u.last_diag);
    542 
    543   kit_compiler_free(c);
    544 }
    545 
    546 int main(void) {
    547   int opt;
    548   kit_unit_init(&g_u);
    549 
    550   run_exec();
    551 
    552   for (opt = 0; opt <= 1; ++opt) {
    553     run_emit(KIT_ARCH_ARM_64, KIT_OS_LINUX, KIT_OBJ_ELF, "aa64", opt);
    554     run_emit(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF, "x64", opt);
    555     run_emit(KIT_ARCH_RV64, KIT_OS_LINUX, KIT_OBJ_ELF, "rv64", opt);
    556   }
    557 
    558   run_reject();
    559 
    560   kit_unit_summary(&g_u, "cg_control_test");
    561   return kit_unit_status(&g_u);
    562 }