kit

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

asm_runner.c (23195B)


      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   st = kit_target_new(ctx, &opts, target_out);
    271   if (st != KIT_OK) return st;
    272   st = kit_compiler_new(*target_out, ctx, compiler_out);
    273   if (st != KIT_OK) {
    274     kit_target_free(*target_out);
    275     *target_out = NULL;
    276   }
    277   return st;
    278 }
    279 
    280 static KitStatus compile_asm_obj(KitCompiler* c,
    281                                  const KitAsmCompileOptions* opts,
    282                                  KitSlice name, const KitSlice* in,
    283                                  KitObjBuilder** out) {
    284   KitCompileSessionOptions sopts;
    285   KitCompileSession* session = NULL;
    286   KitSourceInput sin;
    287   KitStatus st;
    288   memset(&sopts, 0, sizeof(sopts));
    289   sopts.lang = KIT_LANG_ASM;
    290   sopts.compile.code = opts->code;
    291   sopts.compile.diagnostics = opts->diagnostics;
    292   sopts.compile.language_options = opts;
    293   memset(&sin, 0, sizeof(sin));
    294   sin.name = name;
    295   sin.bytes = *in;
    296   sin.lang = KIT_LANG_ASM;
    297   st = kit_compile_session_new(c, &sopts, &session);
    298   if (st == KIT_OK) st = kit_compile_session_compile(session, &sin, out);
    299   kit_compile_session_free(session);
    300   return st;
    301 }
    302 
    303 static KitStatus compile_asm_emit(KitCompiler* c,
    304                                   const KitAsmCompileOptions* opts,
    305                                   KitSlice name, const KitSlice* in,
    306                                   KitWriter* w) {
    307   KitObjBuilder* ob = NULL;
    308   KitStatus st = compile_asm_obj(c, opts, name, in, &ob);
    309   if (st == KIT_OK) st = kit_obj_builder_emit(ob, w);
    310   kit_obj_builder_free(ob);
    311   return st;
    312 }
    313 
    314 static KitStatus link_one_obj_jit(KitCompiler* c, KitObjBuilder* ob,
    315                                   const KitJitHost* host, const char* entry,
    316                                   KitJit** out) {
    317   KitLinkSessionOptions opts;
    318   KitLinkSession* link = NULL;
    319   KitStatus st;
    320   memset(&opts, 0, sizeof(opts));
    321   opts.output_kind = KIT_LINK_OUTPUT_JIT;
    322   opts.entry = kit_slice_cstr(entry);
    323   opts.jit_host = host;
    324   st = kit_link_session_new(c, &opts, &link);
    325   if (st == KIT_OK) st = kit_link_session_add_obj(link, ob);
    326   if (st == KIT_OK) st = kit_link_session_jit(link, out);
    327   kit_link_session_free(link);
    328   return st;
    329 }
    330 
    331 /* ---- file helpers ---- */
    332 
    333 static int read_file(const char* path, uint8_t** out, size_t* out_len) {
    334   FILE* f = fopen(path, "rb");
    335   long n;
    336   uint8_t* buf;
    337   size_t got;
    338   if (!f) return 1;
    339   if (fseek(f, 0, SEEK_END) != 0) {
    340     fclose(f);
    341     return 1;
    342   }
    343   n = ftell(f);
    344   if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
    345     fclose(f);
    346     return 1;
    347   }
    348   buf = (uint8_t*)malloc((size_t)n + 1);
    349   if (!buf) {
    350     fclose(f);
    351     return 1;
    352   }
    353   got = fread(buf, 1, (size_t)n, f);
    354   fclose(f);
    355   if (got != (size_t)n) {
    356     free(buf);
    357     return 1;
    358   }
    359   buf[n] = 0;
    360   *out = buf;
    361   *out_len = (size_t)n;
    362   return 0;
    363 }
    364 
    365 static int write_all(const char* path, const uint8_t* data, size_t len) {
    366   int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    367   size_t off = 0;
    368   if (fd < 0) {
    369     perror(path);
    370     return 1;
    371   }
    372   while (off < len) {
    373     ssize_t k = write(fd, data + off, len - off);
    374     if (k <= 0) {
    375       perror("write");
    376       close(fd);
    377       return 1;
    378     }
    379     off += (size_t)k;
    380   }
    381   close(fd);
    382   return 0;
    383 }
    384 
    385 /* Decode an ASCII hex stream — whitespace and a leading "0x" per token are
    386  * tolerated. Used by --decode to read the .hex input fixture. */
    387 static int hex_decode(const uint8_t* in, size_t in_len, uint8_t** out,
    388                       size_t* out_len) {
    389   uint8_t* buf = (uint8_t*)malloc(in_len / 2 + 1);
    390   size_t n = 0;
    391   int hi = -1;
    392   size_t i;
    393   if (!buf) return 1;
    394   for (i = 0; i < in_len; ++i) {
    395     int c = in[i];
    396     int v;
    397     if (isspace(c)) continue;
    398     if (c == '0' && i + 1 < in_len && (in[i + 1] == 'x' || in[i + 1] == 'X')) {
    399       ++i;
    400       continue;
    401     }
    402     if (c >= '0' && c <= '9')
    403       v = c - '0';
    404     else if (c >= 'a' && c <= 'f')
    405       v = 10 + c - 'a';
    406     else if (c >= 'A' && c <= 'F')
    407       v = 10 + c - 'A';
    408     else {
    409       free(buf);
    410       fprintf(stderr, "asm-runner: bad hex byte 0x%02x\n", c);
    411       return 1;
    412     }
    413     if (hi < 0) {
    414       hi = v;
    415     } else {
    416       buf[n++] = (uint8_t)((hi << 4) | v);
    417       hi = -1;
    418     }
    419   }
    420   if (hi >= 0) {
    421     free(buf);
    422     fprintf(stderr, "asm-runner: odd hex nibble count\n");
    423     return 1;
    424   }
    425   *out = buf;
    426   *out_len = n;
    427   return 0;
    428 }
    429 
    430 /* ---- modes ---- */
    431 
    432 /* --encode: compile a .s through KIT_LANG_ASM, then walk the result via
    433  * the public Obj reader to dump the raw bytes of every PROGBITS section
    434  * marked executable. Output is lowercase hex, no separators. The .text-
    435  * only choice keeps the golden simple for phase-1 smoke; multi-section
    436  * cases will pivot to per-section dumps once the parser lands. */
    437 static int mode_encode(const char* src_path, const char* out_path) {
    438   uint8_t* src = NULL;
    439   size_t src_len = 0;
    440   KitTargetSpec tgt;
    441   KitContext ctx;
    442   KitTarget* target = NULL;
    443   KitCompiler* c = NULL;
    444   KitSlice in;
    445   KitAsmCompileOptions opts;
    446   KitWriter* w = NULL;
    447   const uint8_t* obj_bytes;
    448   size_t obj_len = 0;
    449   KitObjFile* of = NULL;
    450   KitSlice obj_in;
    451   uint32_t nsec, i;
    452   uint8_t* hex = NULL;
    453   size_t hex_len = 0;
    454   int rc = 0;
    455 
    456   if (read_file(src_path, &src, &src_len)) {
    457     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    458     return 2;
    459   }
    460   target_from_env(&tgt);
    461   ctx_init(&ctx);
    462   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    463     free(src);
    464     return 2;
    465   }
    466 
    467   memset(&in, 0, sizeof in);
    468   in.data = src;
    469   in.len = src_len;
    470   memset(&opts, 0, sizeof opts);
    471 
    472   (void)kit_writer_mem(&g_heap, &w);
    473   if (compile_asm_emit(c, &opts, kit_slice_cstr(src_path), &in, w) != KIT_OK) {
    474     kit_writer_close(w);
    475     kit_compiler_free(c);
    476     kit_target_free(target);
    477     free(src);
    478     return 1;
    479   }
    480   obj_bytes = kit_writer_mem_bytes(w, &obj_len);
    481 
    482   memset(&obj_in, 0, sizeof obj_in);
    483   obj_in.data = obj_bytes;
    484   obj_in.len = obj_len;
    485   if (kit_obj_open(&ctx, kit_slice_cstr(src_path), &obj_in, &of) != KIT_OK ||
    486       !of) {
    487     kit_writer_close(w);
    488     kit_compiler_free(c);
    489     kit_target_free(target);
    490     free(src);
    491     return 1;
    492   }
    493 
    494   nsec = kit_obj_nsections(of);
    495   for (i = 0; i < nsec; ++i) {
    496     KitObjSecInfo s;
    497     size_t n = 0;
    498     const uint8_t* data = NULL;
    499     size_t j;
    500     char* p;
    501     if (kit_obj_section(of, i, &s) != KIT_OK) continue;
    502     if (s.kind != KIT_SEC_TEXT) continue;
    503     if (kit_obj_section_data(of, i, &data, &n) != KIT_OK) continue;
    504     if (!data || !n) continue;
    505     p = (char*)realloc(hex, hex_len + n * 2 + 1);
    506     if (!p) {
    507       rc = 1;
    508       break;
    509     }
    510     hex = (uint8_t*)p;
    511     for (j = 0; j < n; ++j) {
    512       static const char H[] = "0123456789abcdef";
    513       hex[hex_len + 2 * j + 0] = (uint8_t)H[data[j] >> 4];
    514       hex[hex_len + 2 * j + 1] = (uint8_t)H[data[j] & 0xf];
    515     }
    516     hex_len += n * 2;
    517   }
    518   if (rc == 0 && hex_len > 0) {
    519     /* Trailing newline so the golden file has a final \n (diff-friendly). */
    520     char* p = (char*)realloc(hex, hex_len + 1);
    521     if (!p) {
    522       rc = 1;
    523     } else {
    524       hex = (uint8_t*)p;
    525       hex[hex_len++] = '\n';
    526       rc = write_all(out_path, hex, hex_len);
    527     }
    528   } else if (rc == 0) {
    529     /* No text — emit an empty file so the diff still has a target. */
    530     rc = write_all(out_path, (const uint8_t*)"", 0);
    531   }
    532 
    533   free(hex);
    534   kit_obj_free(of);
    535   kit_writer_close(w);
    536   kit_compiler_free(c);
    537   kit_target_free(target);
    538   free(src);
    539   return rc;
    540 }
    541 
    542 /* --decode: read hex bytes and walk them through kit_disasm_iter_*.
    543  * Output is one instruction per line: "<vaddr-hex>:\t<mnemonic>\t<operands>"
    544  * (annotation appended when non-empty, prefixed by " ; "). vaddr starts at
    545  * 0; the caller can post-process if they want addresses relative to a
    546  * specific section. */
    547 static int mode_decode(const char* in_path, const char* out_path) {
    548   uint8_t* raw = NULL;
    549   size_t raw_len = 0;
    550   uint8_t* bytes = NULL;
    551   size_t nbytes = 0;
    552   KitTargetSpec tgt;
    553   KitContext ctx;
    554   KitTarget* target = NULL;
    555   KitCompiler* c = NULL;
    556   KitDisasmIter* it = NULL;
    557   KitDisasmContext dctx;
    558   FILE* out;
    559   KitInsn ins;
    560   int rc = 0;
    561 
    562   if (read_file(in_path, &raw, &raw_len)) {
    563     fprintf(stderr, "asm-runner: cannot read %s\n", in_path);
    564     return 2;
    565   }
    566   if (hex_decode(raw, raw_len, &bytes, &nbytes)) {
    567     free(raw);
    568     return 2;
    569   }
    570   free(raw);
    571 
    572   target_from_env(&tgt);
    573   ctx_init(&ctx);
    574   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    575     free(bytes);
    576     return 2;
    577   }
    578 
    579   memset(&dctx, 0, sizeof dctx);
    580   dctx.target = target;
    581   dctx.context = ctx;
    582   if (kit_disasm_iter_new(&dctx, bytes, nbytes, 0, NULL, &it) != KIT_OK ||
    583       !it) {
    584     kit_compiler_free(c);
    585     kit_target_free(target);
    586     free(bytes);
    587     return 1;
    588   }
    589 
    590   out = fopen(out_path, "wb");
    591   if (!out) {
    592     perror(out_path);
    593     kit_disasm_iter_free(it);
    594     kit_compiler_free(c);
    595     kit_target_free(target);
    596     free(bytes);
    597     return 2;
    598   }
    599 
    600   for (;;) {
    601     KitIterResult r = kit_disasm_iter_next(it, &ins);
    602     if (r != KIT_ITER_ITEM) break;
    603     fprintf(out, "%llx:\t%.*s", (unsigned long long)ins.vaddr,
    604             KIT_SLICE_ARG(ins.mnemonic));
    605     if (ins.operands.len) {
    606       fprintf(out, "\t%.*s", KIT_SLICE_ARG(ins.operands));
    607     }
    608     if (ins.annotation.len) {
    609       fprintf(out, " ; %.*s", KIT_SLICE_ARG(ins.annotation));
    610     }
    611     fputc('\n', out);
    612   }
    613   if (fclose(out) != 0) rc = 1;
    614 
    615   kit_disasm_iter_free(it);
    616   kit_compiler_free(c);
    617   kit_target_free(target);
    618   free(bytes);
    619   return rc;
    620 }
    621 
    622 /* --listing: walk a relocatable ELF and run kit_obj_disasm into a
    623  * mem-Writer, then dump to the output path. Annotation overlay comes from
    624  * the ELF's own sym/reloc tables; the harness just stores whatever the
    625  * library emits. */
    626 static int mode_listing(const char* in_path, const char* out_path) {
    627   uint8_t* bytes = NULL;
    628   size_t nbytes = 0;
    629   KitTargetSpec tgt;
    630   KitContext ctx;
    631   KitTarget* target = NULL;
    632   KitCompiler* c = NULL;
    633   KitSlice in;
    634   KitWriter* w = NULL;
    635   const uint8_t* data;
    636   size_t len = 0;
    637   int rc;
    638 
    639   if (read_file(in_path, &bytes, &nbytes)) {
    640     fprintf(stderr, "asm-runner: cannot read %s\n", in_path);
    641     return 2;
    642   }
    643   target_from_env(&tgt);
    644   ctx_init(&ctx);
    645   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    646     free(bytes);
    647     return 2;
    648   }
    649 
    650   memset(&in, 0, sizeof in);
    651   in.data = bytes;
    652   in.len = nbytes;
    653 
    654   (void)kit_writer_mem(&g_heap, &w);
    655   if (kit_disasm_obj_bytes(&ctx, &in, w) != KIT_OK) {
    656     kit_writer_close(w);
    657     kit_compiler_free(c);
    658     kit_target_free(target);
    659     free(bytes);
    660     return 1;
    661   }
    662   data = kit_writer_mem_bytes(w, &len);
    663   rc = write_all(out_path, data, len);
    664 
    665   kit_writer_close(w);
    666   kit_compiler_free(c);
    667   kit_target_free(target);
    668   free(bytes);
    669   return rc;
    670 }
    671 
    672 /* --emit: assemble .s to a relocatable ELF .o on disk. Mirrors
    673  * parse_runner's --emit so the path-J/E shell plumbing (link-exe-runner /
    674  * jit-runner) can be reused verbatim. */
    675 static int mode_emit(const char* src_path, const char* out_path) {
    676   uint8_t* src = NULL;
    677   size_t src_len = 0;
    678   KitTargetSpec tgt;
    679   KitContext ctx;
    680   KitTarget* target = NULL;
    681   KitCompiler* c = NULL;
    682   KitSlice in;
    683   KitAsmCompileOptions opts;
    684   KitWriter* w = NULL;
    685   int rc = 0;
    686   size_t len = 0;
    687   const uint8_t* data;
    688 
    689   if (read_file(src_path, &src, &src_len)) {
    690     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    691     return 2;
    692   }
    693   target_from_env(&tgt);
    694   ctx_init(&ctx);
    695   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    696     free(src);
    697     return 2;
    698   }
    699 
    700   memset(&in, 0, sizeof in);
    701   in.data = src;
    702   in.len = src_len;
    703   memset(&opts, 0, sizeof opts);
    704 
    705   (void)kit_writer_mem(&g_heap, &w);
    706   if (compile_asm_emit(c, &opts, kit_slice_cstr(src_path), &in, w) != KIT_OK) {
    707     kit_writer_close(w);
    708     kit_compiler_free(c);
    709     kit_target_free(target);
    710     free(src);
    711     return 1;
    712   }
    713 
    714   data = kit_writer_mem_bytes(w, &len);
    715   rc = write_all(out_path, data, len);
    716 
    717   kit_writer_close(w);
    718   kit_compiler_free(c);
    719   kit_target_free(target);
    720   free(src);
    721   return rc;
    722 }
    723 
    724 /* On AArch64 host, set up TLS Local-Exec image before invoking JITed
    725  * code. Mirrors parse_runner / cg_runner: msr → call() must be back-to-
    726  * back with no libc invocations in between. Cases that don't reference
    727  * TLS see the lookups fail and the block stays zeroed — harmless. */
    728 #if defined(__aarch64__) || defined(__arm64__)
    729 static char g_tls_block[8192] __attribute__((aligned(16)));
    730 #endif
    731 
    732 /* --jit: parse + link_jit + call test_main(). Exit code is test_main's
    733  * return mod 256, matching the parse/cg conventions. */
    734 static int mode_jit(const char* src_path) {
    735   uint8_t* src = NULL;
    736   size_t src_len = 0;
    737   KitTargetSpec tgt;
    738   KitContext ctx;
    739   KitTarget* target = NULL;
    740   KitCompiler* c = NULL;
    741   KitSlice in;
    742   KitAsmCompileOptions opts;
    743   KitObjBuilder* ob = NULL;
    744   KitJitHost jhost;
    745   KitJit* jit = NULL;
    746   int (*fn)(void);
    747   int result;
    748 
    749   if (read_file(src_path, &src, &src_len)) {
    750     fprintf(stderr, "asm-runner: cannot read %s\n", src_path);
    751     return 2;
    752   }
    753   target_from_env(&tgt);
    754   ctx_init(&ctx);
    755   if (compiler_new_for_target(tgt, &ctx, &target, &c) != KIT_OK || !c) {
    756     free(src);
    757     return 2;
    758   }
    759 
    760   memset(&in, 0, sizeof in);
    761   in.data = src;
    762   in.len = src_len;
    763   memset(&opts, 0, sizeof opts);
    764 
    765   if (compile_asm_obj(c, &opts, kit_slice_cstr(src_path), &in, &ob) != KIT_OK ||
    766       !ob) {
    767     kit_compiler_free(c);
    768     kit_target_free(target);
    769     free(src);
    770     return 1;
    771   }
    772 
    773   memset(&jhost, 0, sizeof jhost);
    774   jhost.execmem = &g_execmem;
    775 
    776   if (link_one_obj_jit(c, ob, &jhost, "test_main", &jit) != KIT_OK || !jit) {
    777     kit_compiler_free(c);
    778     kit_target_free(target);
    779     free(src);
    780     return 1;
    781   }
    782 
    783   fn = (int (*)(void))kit_jit_lookup(jit, KIT_SLICE_LIT("test_main"));
    784 
    785 #if defined(__aarch64__) || defined(__arm64__)
    786   {
    787     char* td_start = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_start"));
    788     char* td_end = (char*)kit_jit_lookup(jit, KIT_SLICE_LIT("__tdata_end"));
    789     unsigned long bs_n = (unsigned long)(unsigned long long)kit_jit_lookup(
    790         jit, KIT_SLICE_LIT("__tbss_size"));
    791     if (td_start && td_end) {
    792       unsigned long td_n = (unsigned long)(td_end - td_start);
    793       unsigned long i;
    794       for (i = 0; i < td_n; ++i) g_tls_block[16 + i] = td_start[i];
    795       for (i = 0; i < bs_n; ++i) g_tls_block[16 + td_n + i] = 0;
    796     }
    797   }
    798 #endif
    799 
    800   if (fn) {
    801 #if defined(__aarch64__) || defined(__arm64__)
    802     __asm__ volatile("msr tpidr_el0, %0" ::"r"(g_tls_block) : "memory");
    803 #endif
    804     result = fn();
    805   } else {
    806     result = 1;
    807   }
    808 
    809   kit_jit_free(jit);
    810   kit_compiler_free(c);
    811   kit_target_free(target);
    812   free(src);
    813   return result;
    814 }
    815 
    816 static int usage(void) {
    817   fprintf(stderr,
    818           "usage: asm-runner --encode  IN.s   OUT.hex\n"
    819           "       asm-runner --decode  IN.hex OUT.txt\n"
    820           "       asm-runner --listing IN.bin OUT.txt\n"
    821           "       asm-runner --emit    IN.s   OUT.o\n"
    822           "       asm-runner --jit     IN.s\n");
    823   return 2;
    824 }
    825 
    826 int main(int argc, char** argv) {
    827   long ps = sysconf(_SC_PAGESIZE);
    828   if (ps > 0) g_execmem.page_size = (size_t)ps;
    829   if (argc < 2) return usage();
    830   if (!strcmp(argv[1], "--encode") && argc == 4)
    831     return mode_encode(argv[2], argv[3]);
    832   if (!strcmp(argv[1], "--decode") && argc == 4)
    833     return mode_decode(argv[2], argv[3]);
    834   if (!strcmp(argv[1], "--listing") && argc == 4)
    835     return mode_listing(argv[2], argv[3]);
    836   if (!strcmp(argv[1], "--emit") && argc == 4)
    837     return mode_emit(argv[2], argv[3]);
    838   if (!strcmp(argv[1], "--jit") && argc == 3) return mode_jit(argv[2]);
    839   return usage();
    840 }