kit

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

asm_runner.c (23406B)


      1 /* asm-runner — file-driven assembler/disassembler test runner.
      2  *
      3  *   asm-runner --encode  IN.s   OUT.hex   # kit as -> raw .text bytes (hex)
      4  *   asm-runner --decode  IN.hex OUT.txt   # kit_disasm_iter_* over bytes
      5  *   asm-runner --listing IN.bin OUT.txt   # kit_obj_disasm over an ELF
      6  *   asm-runner --emit    IN.s   OUT.o     # kit_compile_obj_emit -> ELF .o
      7  *   asm-runner --jit     IN.s             # parse + link_jit -> test_main()
      8  *
      9  * Exclusively uses the public kit.h surface (same path real driver
     10  * consumers take). Built once; the shell runner walks the sub-corpora
     11  * and invokes one mode per case-path pair.
     12  *
     13  * Phase 1: asm_parse and the disasm iterator are still stubs in
     14  * src/api/stubs.c. The runner returns nonzero when the underlying API
     15  * fails; smoke cases each carry a .skip sidecar so the harness reports
     16  * them cleanly until phases 3 and 4 land.
     17  *
     18  * The execmem (W^X) boilerplate mirrors test/parse/harness/parse_runner.c
     19  * — strict dual-mapping on Apple/Linux, single mapping elsewhere. Only
     20  * --jit exercises it. */
     21 
     22 #ifndef _GNU_SOURCE
     23 #define _GNU_SOURCE
     24 #endif
     25 
     26 #include <ctype.h>
     27 #include <fcntl.h>
     28 #include <kit/compile.h>
     29 #include <kit/core.h>
     30 #include <kit/disasm.h>
     31 #include <kit/jit.h>
     32 #include <kit/link.h>
     33 #include <kit/object.h>
     34 #include <stdarg.h>
     35 #include <stdint.h>
     36 #include <stdio.h>
     37 #include <stdlib.h>
     38 #include <string.h>
     39 #include <sys/mman.h>
     40 #include <sys/stat.h>
     41 #include <unistd.h>
     42 
     43 #include "lib/kit_test_target.h"
     44 
     45 /* ---- env: heap, diag ---- */
     46 
     47 static void* h_alloc(KitHeap* h, size_t n, size_t a) {
     48   (void)h;
     49   (void)a;
     50   return n ? malloc(n) : NULL;
     51 }
     52 static void* h_realloc(KitHeap* h, void* p, size_t o, size_t n, size_t a) {
     53   (void)h;
     54   (void)o;
     55   (void)a;
     56   return realloc(p, n);
     57 }
     58 static void h_free(KitHeap* h, void* p, size_t n) {
     59   (void)h;
     60   (void)n;
     61   free(p);
     62 }
     63 static KitHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
     64 
     65 static void diag_emit(KitDiagSink* s, KitDiagKind k, KitSrcLoc loc,
     66                       const char* fmt, va_list ap) {
     67   static const char* names[] = {"note", "warning", "error", "fatal"};
     68   (void)s;
     69   fprintf(stderr, "[%u]:%u:%u: %s: ", loc.file_id, loc.line, loc.col, names[k]);
     70   vfprintf(stderr, fmt, ap);
     71   fputc('\n', stderr);
     72 }
     73 static KitDiagSink g_diag = {diag_emit, NULL, 0, 0};
     74 
     75 /* ---- env: execmem (W^X) — copied verbatim from parse_runner.c. Only the
     76  * --jit mode actually exercises it; the other modes never touch execmem,
     77  * but the env is shared. */
     78 
     79 #if defined(__APPLE__)
     80 #include <mach/mach.h>
     81 #include <mach/mach_vm.h>
     82 #define XM_DUAL_APPLE 1
     83 #else
     84 #define XM_DUAL_APPLE 0
     85 #endif
     86 #if defined(__linux__)
     87 #include <sys/syscall.h>
     88 #define XM_DUAL_LINUX 1
     89 #else
     90 #define XM_DUAL_LINUX 0
     91 #endif
     92 
     93 static int xm_to_posix(int p) {
     94   int q = 0;
     95   if (p & KIT_PROT_READ) q |= PROT_READ;
     96   if (p & KIT_PROT_WRITE) q |= PROT_WRITE;
     97   if (p & KIT_PROT_EXEC) q |= PROT_EXEC;
     98   return q;
     99 }
    100 #if XM_DUAL_LINUX && defined(__x86_64__) && defined(MAP_32BIT)
    101 #define XM_MAP_32BIT MAP_32BIT
    102 static uintptr_t g_xm_low_runtime_hint = 0x40000000u;
    103 static void* xm_low_runtime_hint(size_t n) {
    104   uintptr_t p = g_xm_low_runtime_hint;
    105   uintptr_t step = (uintptr_t)((n + 0xffffu) & ~(size_t)0xffffu);
    106   if (step < 0x10000u) step = 0x10000u;
    107   g_xm_low_runtime_hint = p + step + 0x10000u;
    108   if (g_xm_low_runtime_hint > 0x78000000u) g_xm_low_runtime_hint = 0x40000000u;
    109   return (void*)p;
    110 }
    111 #elif XM_DUAL_LINUX
    112 #define XM_MAP_32BIT 0
    113 static void* xm_low_runtime_hint(size_t n) {
    114   (void)n;
    115   return NULL;
    116 }
    117 #endif
    118 typedef struct XmTok {
    119   void* w;
    120   void* r;
    121   size_t n;
    122 } XmTok;
    123 static KitStatus xm_reserve_single(size_t n, KitExecMemRegion* out) {
    124   void* p =
    125       mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    126   if (p == MAP_FAILED) return KIT_NOMEM;
    127   out->write = out->runtime = p;
    128   out->size = n;
    129   out->token = NULL;
    130   return KIT_OK;
    131 }
    132 static KitStatus xm_reserve(void* u, size_t n, int p, KitExecMemRegion* out) {
    133   (void)u;
    134   if (!out || !n) return KIT_INVALID;
    135   if (!(p & KIT_PROT_EXEC)) return xm_reserve_single(n, out);
    136 #if XM_DUAL_APPLE
    137   {
    138     void* w =
    139         mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
    140     mach_vm_address_t r = 0;
    141     vm_prot_t cur = 0, max = 0;
    142     XmTok* tok;
    143     if (w == MAP_FAILED) return KIT_NOMEM;
    144     if (mach_vm_remap(mach_task_self(), &r, (mach_vm_size_t)n, 0,
    145                       VM_FLAGS_ANYWHERE, mach_task_self(),
    146                       (mach_vm_address_t)(uintptr_t)w, FALSE, &cur, &max,
    147                       VM_INHERIT_NONE) != KERN_SUCCESS) {
    148       munmap(w, n);
    149       return KIT_NOMEM;
    150     }
    151     if (mprotect((void*)(uintptr_t)r, n, PROT_READ) != 0) {
    152       munmap((void*)(uintptr_t)r, n);
    153       munmap(w, n);
    154       return KIT_NOMEM;
    155     }
    156     tok = (XmTok*)malloc(sizeof(*tok));
    157     if (!tok) {
    158       munmap((void*)(uintptr_t)r, n);
    159       munmap(w, n);
    160       return KIT_NOMEM;
    161     }
    162     tok->w = w;
    163     tok->r = (void*)(uintptr_t)r;
    164     tok->n = n;
    165     out->write = w;
    166     out->runtime = (void*)(uintptr_t)r;
    167     out->size = n;
    168     out->token = tok;
    169     return KIT_OK;
    170   }
    171 #elif XM_DUAL_LINUX
    172   {
    173     int fd = (int)syscall(SYS_memfd_create, "kit-asm-jit", 0u);
    174     void *w, *r;
    175     XmTok* tok;
    176     if (fd < 0) return KIT_NOMEM;
    177     if (ftruncate(fd, (off_t)n) != 0) {
    178       close(fd);
    179       return KIT_NOMEM;
    180     }
    181     w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | XM_MAP_32BIT, fd, 0);
    182     if (w == MAP_FAILED) {
    183       close(fd);
    184       return KIT_NOMEM;
    185     }
    186     r = mmap(xm_low_runtime_hint(n), n, PROT_READ, MAP_SHARED | XM_MAP_32BIT,
    187              fd, 0);
    188     close(fd);
    189     if (r == MAP_FAILED) {
    190       munmap(w, n);
    191       return KIT_NOMEM;
    192     }
    193     tok = (XmTok*)malloc(sizeof(*tok));
    194     if (!tok) {
    195       munmap(r, n);
    196       munmap(w, n);
    197       return KIT_NOMEM;
    198     }
    199     tok->w = w;
    200     tok->r = r;
    201     tok->n = n;
    202     out->write = w;
    203     out->runtime = r;
    204     out->size = n;
    205     out->token = tok;
    206     return KIT_OK;
    207   }
    208 #else
    209   return xm_reserve_single(n, out);
    210 #endif
    211 }
    212 static KitStatus xm_protect(void* u, void* a, size_t n, int p) {
    213   (void)u;
    214   return mprotect(a, n, xm_to_posix(p)) == 0 ? KIT_OK : KIT_IO;
    215 }
    216 static void xm_release(void* u, KitExecMemRegion* region) {
    217   (void)u;
    218   if (!region || !region->size) return;
    219   if (region->token) {
    220     XmTok* tok = (XmTok*)region->token;
    221     if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n);
    222     if (tok->w) munmap(tok->w, tok->n);
    223     free(tok);
    224   } else if (region->write) {
    225     munmap(region->write, region->size);
    226   }
    227   region->write = region->runtime = NULL;
    228   region->size = 0;
    229   region->token = NULL;
    230 }
    231 static void xm_flush(void* u, void* a, size_t n) {
    232   (void)u;
    233 #if defined(__aarch64__) || defined(__arm__) || defined(__riscv)
    234 #if defined(__riscv)
    235   __asm__ __volatile__("fence.i" ::: "memory");
    236 #endif
    237   __builtin___clear_cache((char*)a, (char*)a + n);
    238 #else
    239   (void)a;
    240   (void)n;
    241 #endif
    242 }
    243 static KitExecMem g_execmem = {
    244     16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL,
    245 };
    246 
    247 static void ctx_init(KitContext* ctx) {
    248   memset(ctx, 0, sizeof *ctx);
    249   ctx->heap = &g_heap;
    250   ctx->diag = &g_diag;
    251   ctx->now = -1;
    252 }
    253 
    254 static void target_from_env(KitTargetSpec* t) {
    255   if (kit_test_target_init(t) != 0) {
    256     fprintf(stderr, "asm-runner: kit_test_target_init failed\n");
    257     exit(2);
    258   }
    259 }
    260 
    261 static KitStatus compiler_new_for_target(KitTargetSpec spec, KitContext* ctx,
    262                                          KitTarget** target_out,
    263                                          KitCompiler** compiler_out) {
    264   KitTargetOptions opts;
    265   KitStatus st;
    266   if (target_out) *target_out = NULL;
    267   if (compiler_out) *compiler_out = NULL;
    268   memset(&opts, 0, sizeof opts);
    269   opts.spec = spec;
    270   /* Thread the ISA/ABI the resolver needs to honor the spec's float_abi (rv32
    271    * -> ilp32f), so the emitted object's e_flags match the bare harness. */
    272   kit_test_target_isa_abi(&spec, &opts.isa, &opts.abi);
    273   st = kit_target_new(ctx, &opts, target_out);
    274   if (st != KIT_OK) return st;
    275   st = kit_compiler_new(*target_out, ctx, compiler_out);
    276   if (st != KIT_OK) {
    277     kit_target_free(*target_out);
    278     *target_out = NULL;
    279   }
    280   return st;
    281 }
    282 
    283 static KitStatus compile_asm_obj(KitCompiler* c,
    284                                  const KitAsmCompileOptions* opts,
    285                                  KitSlice name, const KitSlice* in,
    286                                  KitObjBuilder** out) {
    287   KitCompileSessionOptions sopts;
    288   KitCompileSession* session = NULL;
    289   KitSourceInput sin;
    290   KitStatus st;
    291   memset(&sopts, 0, sizeof(sopts));
    292   sopts.lang = KIT_LANG_ASM;
    293   sopts.compile.code = opts->code;
    294   sopts.compile.diagnostics = opts->diagnostics;
    295   sopts.compile.language_options = opts;
    296   memset(&sin, 0, sizeof(sin));
    297   sin.name = name;
    298   sin.bytes = *in;
    299   sin.lang = KIT_LANG_ASM;
    300   st = kit_compile_session_new(c, &sopts, &session);
    301   if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, out);
    302   kit_compile_session_free(session);
    303   return st;
    304 }
    305 
    306 static KitStatus compile_asm_emit(KitCompiler* c,
    307                                   const KitAsmCompileOptions* opts,
    308                                   KitSlice name, const KitSlice* in,
    309                                   KitWriter* w) {
    310   KitObjBuilder* ob = NULL;
    311   KitStatus st = compile_asm_obj(c, opts, name, in, &ob);
    312   if (st == KIT_OK) st = kit_obj_builder_emit(ob, w);
    313   kit_obj_builder_free(ob);
    314   return st;
    315 }
    316 
    317 static KitStatus link_one_obj_jit(KitCompiler* c, KitObjBuilder* ob,
    318                                   const KitJitHost* host, const char* entry,
    319                                   KitJit** out) {
    320   KitLinkSessionOptions opts;
    321   KitLinkSession* link = NULL;
    322   KitStatus st;
    323   memset(&opts, 0, sizeof(opts));
    324   opts.output_kind = KIT_LINK_OUTPUT_JIT;
    325   opts.entry = kit_slice_cstr(entry);
    326   opts.jit_host = host;
    327   st = kit_link_session_new(c, &opts, &link);
    328   if (st == KIT_OK) st = kit_link_session_add_obj(link, ob);
    329   if (st == KIT_OK) st = kit_link_session_jit(link, out);
    330   kit_link_session_free(link);
    331   return st;
    332 }
    333 
    334 /* ---- file helpers ---- */
    335 
    336 static int read_file(const char* path, uint8_t** out, size_t* out_len) {
    337   FILE* f = fopen(path, "rb");
    338   long n;
    339   uint8_t* buf;
    340   size_t got;
    341   if (!f) return 1;
    342   if (fseek(f, 0, SEEK_END) != 0) {
    343     fclose(f);
    344     return 1;
    345   }
    346   n = ftell(f);
    347   if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
    348     fclose(f);
    349     return 1;
    350   }
    351   buf = (uint8_t*)malloc((size_t)n + 1);
    352   if (!buf) {
    353     fclose(f);
    354     return 1;
    355   }
    356   got = fread(buf, 1, (size_t)n, f);
    357   fclose(f);
    358   if (got != (size_t)n) {
    359     free(buf);
    360     return 1;
    361   }
    362   buf[n] = 0;
    363   *out = buf;
    364   *out_len = (size_t)n;
    365   return 0;
    366 }
    367 
    368 static int write_all(const char* path, const uint8_t* data, size_t len) {
    369   int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    370   size_t off = 0;
    371   if (fd < 0) {
    372     perror(path);
    373     return 1;
    374   }
    375   while (off < len) {
    376     ssize_t k = write(fd, data + off, len - off);
    377     if (k <= 0) {
    378       perror("write");
    379       close(fd);
    380       return 1;
    381     }
    382     off += (size_t)k;
    383   }
    384   close(fd);
    385   return 0;
    386 }
    387 
    388 /* Decode an ASCII hex stream — whitespace and a leading "0x" per token are
    389  * tolerated. Used by --decode to read the .hex input fixture. */
    390 static int hex_decode(const uint8_t* in, size_t in_len, uint8_t** out,
    391                       size_t* out_len) {
    392   uint8_t* buf = (uint8_t*)malloc(in_len / 2 + 1);
    393   size_t n = 0;
    394   int hi = -1;
    395   size_t i;
    396   if (!buf) return 1;
    397   for (i = 0; i < in_len; ++i) {
    398     int c = in[i];
    399     int v;
    400     if (isspace(c)) continue;
    401     if (c == '0' && i + 1 < in_len && (in[i + 1] == 'x' || in[i + 1] == 'X')) {
    402       ++i;
    403       continue;
    404     }
    405     if (c >= '0' && c <= '9')
    406       v = c - '0';
    407     else if (c >= 'a' && c <= 'f')
    408       v = 10 + c - 'a';
    409     else if (c >= 'A' && c <= 'F')
    410       v = 10 + c - 'A';
    411     else {
    412       free(buf);
    413       fprintf(stderr, "asm-runner: bad hex byte 0x%02x\n", c);
    414       return 1;
    415     }
    416     if (hi < 0) {
    417       hi = v;
    418     } else {
    419       buf[n++] = (uint8_t)((hi << 4) | v);
    420       hi = -1;
    421     }
    422   }
    423   if (hi >= 0) {
    424     free(buf);
    425     fprintf(stderr, "asm-runner: odd hex nibble count\n");
    426     return 1;
    427   }
    428   *out = buf;
    429   *out_len = n;
    430   return 0;
    431 }
    432 
    433 /* ---- modes ---- */
    434 
    435 /* --encode: compile a .s through KIT_LANG_ASM, then walk the result via
    436  * the public Obj reader to dump the raw bytes of every PROGBITS section
    437  * marked executable. Output is lowercase hex, no separators. The .text-
    438  * only choice keeps the golden simple for phase-1 smoke; multi-section
    439  * cases will pivot to per-section dumps once the parser lands. */
    440 static int mode_encode(const char* src_path, const char* out_path) {
    441   uint8_t* src = NULL;
    442   size_t src_len = 0;
    443   KitTargetSpec tgt;
    444   KitContext ctx;
    445   KitTarget* target = NULL;
    446   KitCompiler* c = NULL;
    447   KitSlice in;
    448   KitAsmCompileOptions opts;
    449   KitWriter* w = NULL;
    450   const uint8_t* obj_bytes;
    451   size_t obj_len = 0;
    452   KitObjFile* of = NULL;
    453   KitSlice obj_in;
    454   uint32_t nsec, i;
    455   uint8_t* hex = NULL;
    456   size_t hex_len = 0;
    457   int rc = 0;
    458 
    459   if (read_file(src_path, &src, &src_len)) {
    460     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    461     return 2;
    462   }
    463   target_from_env(&tgt);
    464   ctx_init(&ctx);
    465   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    466     free(src);
    467     return 2;
    468   }
    469 
    470   memset(&in, 0, sizeof in);
    471   in.data = src;
    472   in.len = src_len;
    473   memset(&opts, 0, sizeof opts);
    474 
    475   (void)kit_writer_mem(&g_heap, &w);
    476   if (compile_asm_emit(c, &opts, kit_slice_cstr(src_path), &in, w) != KIT_OK) {
    477     kit_writer_close(w);
    478     kit_compiler_free(c);
    479     kit_target_free(target);
    480     free(src);
    481     return 1;
    482   }
    483   obj_bytes = kit_writer_mem_bytes(w, &obj_len);
    484 
    485   memset(&obj_in, 0, sizeof obj_in);
    486   obj_in.data = obj_bytes;
    487   obj_in.len = obj_len;
    488   if (kit_obj_open(&ctx, kit_slice_cstr(src_path), &obj_in, &of) != KIT_OK ||
    489       !of) {
    490     kit_writer_close(w);
    491     kit_compiler_free(c);
    492     kit_target_free(target);
    493     free(src);
    494     return 1;
    495   }
    496 
    497   nsec = kit_obj_nsections(of);
    498   for (i = 0; i < nsec; ++i) {
    499     KitObjSecInfo s;
    500     size_t n = 0;
    501     const uint8_t* data = NULL;
    502     size_t j;
    503     char* p;
    504     if (kit_obj_section(of, i, &s) != KIT_OK) continue;
    505     if (s.kind != KIT_SEC_TEXT) continue;
    506     if (kit_obj_section_data(of, i, &data, &n) != KIT_OK) continue;
    507     if (!data || !n) continue;
    508     p = (char*)realloc(hex, hex_len + n * 2 + 1);
    509     if (!p) {
    510       rc = 1;
    511       break;
    512     }
    513     hex = (uint8_t*)p;
    514     for (j = 0; j < n; ++j) {
    515       static const char H[] = "0123456789abcdef";
    516       hex[hex_len + 2 * j + 0] = (uint8_t)H[data[j] >> 4];
    517       hex[hex_len + 2 * j + 1] = (uint8_t)H[data[j] & 0xf];
    518     }
    519     hex_len += n * 2;
    520   }
    521   if (rc == 0 && hex_len > 0) {
    522     /* Trailing newline so the golden file has a final \n (diff-friendly). */
    523     char* p = (char*)realloc(hex, hex_len + 1);
    524     if (!p) {
    525       rc = 1;
    526     } else {
    527       hex = (uint8_t*)p;
    528       hex[hex_len++] = '\n';
    529       rc = write_all(out_path, hex, hex_len);
    530     }
    531   } else if (rc == 0) {
    532     /* No text — emit an empty file so the diff still has a target. */
    533     rc = write_all(out_path, (const uint8_t*)"", 0);
    534   }
    535 
    536   free(hex);
    537   kit_obj_free(of);
    538   kit_writer_close(w);
    539   kit_compiler_free(c);
    540   kit_target_free(target);
    541   free(src);
    542   return rc;
    543 }
    544 
    545 /* --decode: read hex bytes and walk them through kit_disasm_iter_*.
    546  * Output is one instruction per line: "<vaddr-hex>:\t<mnemonic>\t<operands>"
    547  * (annotation appended when non-empty, prefixed by " ; "). vaddr starts at
    548  * 0; the caller can post-process if they want addresses relative to a
    549  * specific section. */
    550 static int mode_decode(const char* in_path, const char* out_path) {
    551   uint8_t* raw = NULL;
    552   size_t raw_len = 0;
    553   uint8_t* bytes = NULL;
    554   size_t nbytes = 0;
    555   KitTargetSpec tgt;
    556   KitContext ctx;
    557   KitTarget* target = NULL;
    558   KitCompiler* c = NULL;
    559   KitDisasmIter* it = NULL;
    560   KitDisasmContext dctx;
    561   FILE* out;
    562   KitInsn ins;
    563   int rc = 0;
    564 
    565   if (read_file(in_path, &raw, &raw_len)) {
    566     fprintf(stderr, "asm-runner: cannot read %s\n", in_path);
    567     return 2;
    568   }
    569   if (hex_decode(raw, raw_len, &bytes, &nbytes)) {
    570     free(raw);
    571     return 2;
    572   }
    573   free(raw);
    574 
    575   target_from_env(&tgt);
    576   ctx_init(&ctx);
    577   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    578     free(bytes);
    579     return 2;
    580   }
    581 
    582   memset(&dctx, 0, sizeof dctx);
    583   dctx.target = target;
    584   dctx.context = ctx;
    585   if (kit_disasm_iter_new(&dctx, bytes, nbytes, 0, NULL, &it) != KIT_OK ||
    586       !it) {
    587     kit_compiler_free(c);
    588     kit_target_free(target);
    589     free(bytes);
    590     return 1;
    591   }
    592 
    593   out = fopen(out_path, "wb");
    594   if (!out) {
    595     perror(out_path);
    596     kit_disasm_iter_free(it);
    597     kit_compiler_free(c);
    598     kit_target_free(target);
    599     free(bytes);
    600     return 2;
    601   }
    602 
    603   for (;;) {
    604     KitIterResult r = kit_disasm_iter_next(it, &ins);
    605     if (r != KIT_ITER_ITEM) break;
    606     fprintf(out, "%llx:\t%.*s", (unsigned long long)ins.vaddr,
    607             KIT_SLICE_ARG(ins.mnemonic));
    608     if (ins.operands.len) {
    609       fprintf(out, "\t%.*s", KIT_SLICE_ARG(ins.operands));
    610     }
    611     if (ins.annotation.len) {
    612       fprintf(out, " ; %.*s", KIT_SLICE_ARG(ins.annotation));
    613     }
    614     fputc('\n', out);
    615   }
    616   if (fclose(out) != 0) rc = 1;
    617 
    618   kit_disasm_iter_free(it);
    619   kit_compiler_free(c);
    620   kit_target_free(target);
    621   free(bytes);
    622   return rc;
    623 }
    624 
    625 /* --listing: walk a relocatable ELF and run kit_obj_disasm into a
    626  * mem-Writer, then dump to the output path. Annotation overlay comes from
    627  * the ELF's own sym/reloc tables; the harness just stores whatever the
    628  * library emits. */
    629 static int mode_listing(const char* in_path, const char* out_path) {
    630   uint8_t* bytes = NULL;
    631   size_t nbytes = 0;
    632   KitTargetSpec tgt;
    633   KitContext ctx;
    634   KitTarget* target = NULL;
    635   KitCompiler* c = NULL;
    636   KitSlice in;
    637   KitWriter* w = NULL;
    638   const uint8_t* data;
    639   size_t len = 0;
    640   int rc;
    641 
    642   if (read_file(in_path, &bytes, &nbytes)) {
    643     fprintf(stderr, "asm-runner: cannot read %s\n", in_path);
    644     return 2;
    645   }
    646   target_from_env(&tgt);
    647   ctx_init(&ctx);
    648   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    649     free(bytes);
    650     return 2;
    651   }
    652 
    653   memset(&in, 0, sizeof in);
    654   in.data = bytes;
    655   in.len = nbytes;
    656 
    657   (void)kit_writer_mem(&g_heap, &w);
    658   if (kit_disasm_obj_bytes(&ctx, &in, w) != KIT_OK) {
    659     kit_writer_close(w);
    660     kit_compiler_free(c);
    661     kit_target_free(target);
    662     free(bytes);
    663     return 1;
    664   }
    665   data = kit_writer_mem_bytes(w, &len);
    666   rc = write_all(out_path, data, len);
    667 
    668   kit_writer_close(w);
    669   kit_compiler_free(c);
    670   kit_target_free(target);
    671   free(bytes);
    672   return rc;
    673 }
    674 
    675 /* --emit: assemble .s to a relocatable ELF .o on disk. Mirrors
    676  * parse_runner's --emit so the path-J/E shell plumbing (link-exe-runner /
    677  * jit-runner) can be reused verbatim. */
    678 static int mode_emit(const char* src_path, const char* out_path) {
    679   uint8_t* src = NULL;
    680   size_t src_len = 0;
    681   KitTargetSpec tgt;
    682   KitContext ctx;
    683   KitTarget* target = NULL;
    684   KitCompiler* c = NULL;
    685   KitSlice in;
    686   KitAsmCompileOptions opts;
    687   KitWriter* w = NULL;
    688   int rc = 0;
    689   size_t len = 0;
    690   const uint8_t* data;
    691 
    692   if (read_file(src_path, &src, &src_len)) {
    693     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    694     return 2;
    695   }
    696   target_from_env(&tgt);
    697   ctx_init(&ctx);
    698   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    699     free(src);
    700     return 2;
    701   }
    702 
    703   memset(&in, 0, sizeof in);
    704   in.data = src;
    705   in.len = src_len;
    706   memset(&opts, 0, sizeof opts);
    707 
    708   (void)kit_writer_mem(&g_heap, &w);
    709   if (compile_asm_emit(c, &opts, kit_slice_cstr(src_path), &in, w) != KIT_OK) {
    710     kit_writer_close(w);
    711     kit_compiler_free(c);
    712     kit_target_free(target);
    713     free(src);
    714     return 1;
    715   }
    716 
    717   data = kit_writer_mem_bytes(w, &len);
    718   rc = write_all(out_path, data, len);
    719 
    720   kit_writer_close(w);
    721   kit_compiler_free(c);
    722   kit_target_free(target);
    723   free(src);
    724   return rc;
    725 }
    726 
    727 /* On AArch64 host, set up TLS Local-Exec image before invoking JITed
    728  * code. Mirrors parse_runner / cg_runner: msr → call() must be back-to-
    729  * back with no libc invocations in between. Cases that don't reference
    730  * TLS see the lookups fail and the block stays zeroed — harmless. */
    731 #if defined(__aarch64__) || defined(__arm64__)
    732 static char g_tls_block[8192] __attribute__((aligned(16)));
    733 #endif
    734 
    735 /* --jit: parse + link_jit + call test_main(). Exit code is test_main's
    736  * return mod 256, matching the parse/cg conventions. */
    737 static int mode_jit(const char* src_path) {
    738   uint8_t* src = NULL;
    739   size_t src_len = 0;
    740   KitTargetSpec tgt;
    741   KitContext ctx;
    742   KitTarget* target = NULL;
    743   KitCompiler* c = NULL;
    744   KitSlice in;
    745   KitAsmCompileOptions opts;
    746   KitObjBuilder* ob = NULL;
    747   KitJitHost jhost;
    748   KitJit* jit = NULL;
    749   int (*fn)(void);
    750   int result;
    751 
    752   if (read_file(src_path, &src, &src_len)) {
    753     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    754     return 2;
    755   }
    756   target_from_env(&tgt);
    757   ctx_init(&ctx);
    758   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    759     free(src);
    760     return 2;
    761   }
    762 
    763   memset(&in, 0, sizeof in);
    764   in.data = src;
    765   in.len = src_len;
    766   memset(&opts, 0, sizeof opts);
    767 
    768   if (compile_asm_obj(c, &opts, kit_slice_cstr(src_path), &in, &ob) != KIT_OK ||
    769       !ob) {
    770     kit_compiler_free(c);
    771     kit_target_free(target);
    772     free(src);
    773     return 1;
    774   }
    775 
    776   memset(&jhost, 0, sizeof jhost);
    777   jhost.execmem = &g_execmem;
    778 
    779   if (link_one_obj_jit(c, ob, &jhost, "test_main", &jit) != KIT_OK || !jit) {
    780     kit_compiler_free(c);
    781     kit_target_free(target);
    782     free(src);
    783     return 1;
    784   }
    785 
    786   fn = (int (*)(void))kit_jit_lookup(jit, KIT_SLICE_LIT("test_main"));
    787 
    788 #if defined(__aarch64__) || defined(__arm64__)
    789   {
    790     char* td_start = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_start"));
    791     char* td_end = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_end"));
    792     unsigned long bs_n = (unsigned long)(unsigned long long)kit_jit_lookup(
    793         jit, KIT_SLICE_LIT("__tbss_size"));
    794     if (td_start && td_end) {
    795       unsigned long td_n = (unsigned long)(td_end - td_start);
    796       unsigned long i;
    797       for (i = 0; i < td_n; ++i) g_tls_block[16 + i] = td_start[i];
    798       for (i = 0; i < bs_n; ++i) g_tls_block[16 + td_n + i] = 0;
    799     }
    800   }
    801 #endif
    802 
    803   if (fn) {
    804 #if defined(__aarch64__) || defined(__arm64__)
    805     __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory");
    806 #endif
    807     result = fn();
    808   } else {
    809     result = 1;
    810   }
    811 
    812   kit_jit_free(jit);
    813   kit_compiler_free(c);
    814   kit_target_free(target);
    815   free(src);
    816   return result;
    817 }
    818 
    819 static int usage(void) {
    820   fprintf(stderr,
    821           "usage: asm-runner --encode  IN.s   OUT.hex\n"
    822           "       asm-runner --decode  IN.hex OUT.txt\n"
    823           "       asm-runner --listing IN.bin OUT.txt\n"
    824           "       asm-runner --emit    IN.s   OUT.o\n"
    825           "       asm-runner --jit     IN.s\n");
    826   return 2;
    827 }
    828 
    829 int main(int argc, char** argv) {
    830   long ps = sysconf(_SC_PAGESIZE);
    831   if (ps > 0) g_execmem.page_size = (size_t)ps;
    832   if (argc < 2) return usage();
    833   if (!strcmp(argv[1], "--encode") && argc == 4)
    834     return mode_encode(argv[2], argv[3]);
    835   if (!strcmp(argv[1], "--decode") && argc == 4)
    836     return mode_decode(argv[2], argv[3]);
    837   if (!strcmp(argv[1], "--listing") && argc == 4)
    838     return mode_listing(argv[2], argv[3]);
    839   if (!strcmp(argv[1], "--emit") && argc == 4)
    840     return mode_emit(argv[2], argv[3]);
    841   if (!strcmp(argv[1], "--jit") && argc == 3) return mode_jit(argv[2]);
    842   return usage();
    843 }